lib/webauthn/authenticator_data.rb
# frozen_string_literal: true require "bindata" require "webauthn/authenticator_data/attested_credential_data" require "webauthn/error" module WebAuthn class AuthenticatorDataFormatError < WebAuthn::Error; end class AuthenticatorData < BinData::Record RP_ID_HASH_LENGTH = 32 FLAGS_LENGTH = 1 SIGN_COUNT_LENGTH = 4 endian :big count_bytes_remaining :data_length string :rp_id_hash, length: RP_ID_HASH_LENGTH struct :flags do bit1 :extension_data_included bit1 :attested_credential_data_included bit1 :reserved_for_future_use_2 bit1 :backup_state bit1 :backup_eligibility bit1 :user_verified bit1 :reserved_for_future_use_1 bit1 :user_present end bit32 :sign_count count_bytes_remaining :trailing_bytes_length string :trailing_bytes, length: :trailing_bytes_length def self.deserialize(data) read(data) rescue EOFError raise AuthenticatorDataFormatError end def data to_binary_s end def valid? (!attested_credential_data_included? || attested_credential_data.valid?) && (!extension_data_included? || extension_data) && valid_length? end def user_flagged? user_present? || user_verified? end def user_present? flags.user_present == 1 end def user_verified? flags.user_verified == 1 end def credential_backup_eligible? flags.backup_eligibility == 1 end def credential_backed_up? flags.backup_state == 1 end def attested_credential_data_included? flags.attested_credential_data_included == 1 end def extension_data_included? flags.extension_data_included == 1 end def credential if attested_credential_data_included? attested_credential_data.credential end end def attested_credential_data @attested_credential_data ||= AttestedCredentialData.deserialize(trailing_bytes) rescue AttestedCredentialDataFormatError raise AuthenticatorDataFormatError end def extension_data @extension_data ||= CBOR.decode(raw_extension_data) end def aaguid raw_aaguid = attested_credential_data.raw_aaguid unless raw_aaguid == WebAuthn::AuthenticatorData::AttestedCredentialData::ZEROED_AAGUID attested_credential_data.aaguid end end private def valid_length? data_length == base_length + attested_credential_data_length + extension_data_length end def raw_extension_data if extension_data_included? if attested_credential_data_included? trailing_bytes[attested_credential_data.length..-1] else trailing_bytes.snapshot end end end def attested_credential_data_length if attested_credential_data_included? attested_credential_data.length else 0 end end def extension_data_length if extension_data_included? raw_extension_data.length else 0 end end def base_length RP_ID_HASH_LENGTH + FLAGS_LENGTH + SIGN_COUNT_LENGTH end end end