lib/webauthn/u2f_migrator.rb
# frozen_string_literal: true require 'webauthn/fake_client' require 'webauthn/attestation_statement/fido_u2f' module WebAuthn class U2fMigrator def initialize(app_id:, certificate:, key_handle:, public_key:, counter:) @app_id = app_id @certificate = certificate @key_handle = key_handle @public_key = public_key @counter = counter end def authenticator_data @authenticator_data ||= WebAuthn::FakeAuthenticator::AuthenticatorData.new( rp_id_hash: OpenSSL::Digest::SHA256.digest(@app_id.to_s), credential: { id: credential_id, public_key: credential_cose_key }, sign_count: @counter, user_present: true, user_verified: false, aaguid: WebAuthn::AuthenticatorData::AttestedCredentialData::ZEROED_AAGUID ) end def credential @credential ||= begin hash = authenticator_data.send(:credential) WebAuthn::AuthenticatorData::AttestedCredentialData::Credential.new( id: hash[:id], public_key: hash[:public_key].serialize ) end end def attestation_type WebAuthn::AttestationStatement::ATTESTATION_TYPE_BASIC_OR_ATTCA end def attestation_trust_path @attestation_trust_path ||= [OpenSSL::X509::Certificate.new(Base64.strict_decode64(@certificate))] end private # https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#u2f-authenticatorMakeCredential-interoperability # Let credentialId be a credentialIdLength byte array initialized with CTAP1/U2F response key handle bytes. def credential_id Base64.urlsafe_decode64(@key_handle) end # Let x9encodedUserPublicKey be the user public key returned in the U2F registration response message [U2FRawMsgs]. # Let coseEncodedCredentialPublicKey be the result of converting x9encodedUserPublicKey’s value from ANS X9.62 / # Sec-1 v2 uncompressed curve point representation [SEC1V2] to COSE_Key representation ([RFC8152] Section 7). def credential_cose_key decoded_public_key = Base64.strict_decode64(@public_key) if WebAuthn::AttestationStatement::FidoU2f::PublicKey.uncompressed_point?(decoded_public_key) COSE::Key::EC2.new( alg: COSE::Algorithm.by_name("ES256").id, crv: 1, x: decoded_public_key[1..32], y: decoded_public_key[33..-1] ) else raise "expected U2F public key to be in uncompressed point format" end end end end