lib/webauthn/attestation_statement/tpm.rb in webauthn-2.0.0 vs lib/webauthn/attestation_statement/tpm.rb in webauthn-2.1.0

- old
+ new

@@ -1,19 +1,24 @@ # frozen_string_literal: true require "cose/algorithm" require "openssl" +require "tpm/constants" require "webauthn/attestation_statement/base" require "webauthn/attestation_statement/tpm/cert_info" require "webauthn/attestation_statement/tpm/pub_area" require "webauthn/signature_verifier" module WebAuthn module AttestationStatement class TPM < Base CERTIFICATE_V3 = 2 CERTIFICATE_EMPTY_NAME = OpenSSL::X509::Name.new([]).freeze + CERTIFICATE_SAN_DIRECTORY_NAME = 4 + OID_TCG_AT_TPM_MANUFACTURER = "2.23.133.2.1" + OID_TCG_AT_TPM_MODEL = "2.23.133.2.2" + OID_TCG_AT_TPM_VERSION = "2.23.133.2.3" OID_TCG_KP_AIK_CERTIFICATE = "2.23.133.8.3" TPM_V2 = "2.0" def valid?(authenticator_data, client_data_hash) case attestation_type @@ -22,11 +27,12 @@ ver == TPM_V2 && valid_signature? && valid_attestation_certificate? && pub_area.valid?(authenticator_data.credential.public_key) && - cert_info.valid?(statement["pubArea"], OpenSSL::Digest.digest(cose_algorithm.hash, att_to_be_signed)) && + cert_info.valid?(statement["pubArea"], + OpenSSL::Digest.digest(cose_algorithm.hash_function, att_to_be_signed)) && matching_aaguid?(authenticator_data.attested_credential_data.raw_aaguid) && [attestation_type, attestation_trust_path] when ATTESTATION_TYPE_ECDAA raise( WebAuthn::AttestationStatement::Base::NotSupportedError, @@ -46,15 +52,34 @@ def valid_attestation_certificate? extensions = attestation_certificate.extensions attestation_certificate.version == CERTIFICATE_V3 && attestation_certificate.subject.eql?(CERTIFICATE_EMPTY_NAME) && + valid_subject_alternative_name? && certificate_in_use?(attestation_certificate) && extensions.find { |ext| ext.oid == 'basicConstraints' }&.value == "CA:FALSE" && extensions.find { |ext| ext.oid == "extendedKeyUsage" }&.value == OID_TCG_KP_AIK_CERTIFICATE end + def valid_subject_alternative_name? + extension = attestation_certificate.extensions.detect { |ext| ext.oid == "subjectAltName" } + return unless extension&.critical? + + san_asn1 = OpenSSL::ASN1.decode(extension).find do |val| + val.tag_class == :UNIVERSAL && val.tag == OpenSSL::ASN1::OCTET_STRING + end + directory_name = OpenSSL::ASN1.decode(san_asn1.value).find do |val| + val.tag_class == :CONTEXT_SPECIFIC && val.tag == CERTIFICATE_SAN_DIRECTORY_NAME + end + name = OpenSSL::X509::Name.new(directory_name.value.first).to_a + manufacturer = name.assoc(OID_TCG_AT_TPM_MANUFACTURER).at(1) + model = name.assoc(OID_TCG_AT_TPM_MODEL).at(1) + version = name.assoc(OID_TCG_AT_TPM_VERSION).at(1) + + ::TPM::VENDOR_IDS[manufacturer] && !model.empty? && !version.empty? + end + def certificate_in_use?(certificate) now = Time.now certificate.not_before < now && now < certificate.not_after end @@ -78,20 +103,16 @@ def cose_algorithm @cose_algorithm ||= COSE::Algorithm.find(algorithm) end def attestation_type - if raw_attestation_certificates && !raw_ecdaa_key_id + if raw_certificates && !raw_ecdaa_key_id ATTESTATION_TYPE_ATTCA - elsif raw_ecdaa_key_id && !raw_attestation_certificates + elsif raw_ecdaa_key_id && !raw_certificates ATTESTATION_TYPE_ECDAA else raise "Attestation type invalid" end - end - - def attestation_trust_path - attestation_certificate_chain end end end end