lib/webauthn/attestation_statement/tpm.rb
# frozen_string_literal: true require "cose/algorithm" require "openssl" require "tpm/key_attestation" require "webauthn/attestation_statement/base" module WebAuthn module AttestationStatement class TPM < Base TPM_V2 = "2.0" COSE_ALG_TO_TPM = { "RS1" => { signature: ::TPM::ALG_RSASSA, hash: ::TPM::ALG_SHA1 }, "RS256" => { signature: ::TPM::ALG_RSASSA, hash: ::TPM::ALG_SHA256 }, "PS256" => { signature: ::TPM::ALG_RSAPSS, hash: ::TPM::ALG_SHA256 }, "ES256" => { signature: ::TPM::ALG_ECDSA, hash: ::TPM::ALG_SHA256 }, }.freeze def valid?(authenticator_data, client_data_hash) attestation_type == ATTESTATION_TYPE_ATTCA && ver == TPM_V2 && valid_key_attestation?( authenticator_data.data + client_data_hash, authenticator_data.credential.public_key_object, authenticator_data.aaguid ) && matching_aaguid?(authenticator_data.attested_credential_data.raw_aaguid) && trustworthy?(aaguid: authenticator_data.aaguid) && [attestation_type, attestation_trust_path] end private def valid_key_attestation?(certified_extra_data, key, aaguid) key_attestation = ::TPM::KeyAttestation.new( statement["certInfo"], signature, statement["pubArea"], certificates, OpenSSL::Digest.digest(cose_algorithm.hash_function, certified_extra_data), signature_algorithm: tpm_algorithm[:signature], hash_algorithm: tpm_algorithm[:hash], trusted_certificates: root_certificates(aaguid: aaguid) ) key_attestation.valid? && key_attestation.key && key_attestation.key.to_pem == key.to_pem end def valid_certificate_chain?(**_) # Already performed as part of #valid_key_attestation? true end def default_root_certificates ::TPM::KeyAttestation::TRUSTED_CERTIFICATES end def tpm_algorithm COSE_ALG_TO_TPM[cose_algorithm.name] || raise("Unsupported algorithm #{cose_algorithm.name}") end def ver statement["ver"] end def cose_algorithm @cose_algorithm ||= COSE::Algorithm.find(algorithm) end def attestation_type if raw_certificates ATTESTATION_TYPE_ATTCA else raise "Attestation type invalid" end end end end end