Auto-suggest name for new Passkeys
- Use plain client data json everywhere - Expose AAGUID and backed_up on Credentials - Make attestation verifiers configurable - Auto-suggest names for passkeys and show icons
This commit is contained in:
@@ -38,7 +38,7 @@
|
||||
class ActionPack::WebAuthn::Authenticator::Attestation
|
||||
attr_reader :authenticator_data, :format, :attestation_statement
|
||||
|
||||
delegate :credential_id, :public_key, :public_key_bytes, :sign_count, to: :authenticator_data
|
||||
delegate :credential_id, :public_key, :public_key_bytes, :sign_count, :aaguid, :backed_up?, to: :authenticator_data
|
||||
|
||||
def self.decode(bytes)
|
||||
cbor = ActionPack::WebAuthn::CborDecoder.decode(bytes)
|
||||
|
||||
@@ -25,10 +25,10 @@
|
||||
# In addition to the base Response validations, this class verifies:
|
||||
#
|
||||
# * The client data type is "webauthn.create"
|
||||
# * The attestation format is "none" (other formats require certificate verification)
|
||||
# * The attestation format has a registered verifier
|
||||
# * The attestation statement passes format-specific verification
|
||||
#
|
||||
class ActionPack::WebAuthn::Authenticator::AttestationResponse < ActionPack::WebAuthn::Authenticator::Response
|
||||
SUPPORTED_ATTESTATION_FORMATS = %w[ none ].freeze
|
||||
attr_reader :attestation_object
|
||||
|
||||
def initialize(attestation_object:, **attributes)
|
||||
@@ -43,9 +43,13 @@ class ActionPack::WebAuthn::Authenticator::AttestationResponse < ActionPack::Web
|
||||
raise InvalidResponseError, "Client data type is not webauthn.create"
|
||||
end
|
||||
|
||||
unless SUPPORTED_ATTESTATION_FORMATS.include?(attestation.format)
|
||||
verifier = ActionPack::WebAuthn.attestation_verifiers[attestation.format]
|
||||
|
||||
unless verifier
|
||||
raise InvalidResponseError, "Unsupported attestation format: #{attestation.format}"
|
||||
end
|
||||
|
||||
verifier.verify!(attestation, client_data_json: client_data_json)
|
||||
end
|
||||
|
||||
def attestation
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# = Action Pack WebAuthn None Attestation Verifier
|
||||
#
|
||||
# Verifies attestation responses with the "none" format, which indicates the
|
||||
# authenticator did not provide any attestation statement. This is the default
|
||||
# format used by most consumer authenticators.
|
||||
#
|
||||
# == Implementing Custom Verifiers
|
||||
#
|
||||
# To support other attestation formats (e.g., "packed", "fido-u2f"), implement
|
||||
# a class with the same +verify!+ interface and register it:
|
||||
#
|
||||
# ActionPack::WebAuthn.register_attestation_verifier("packed", MyPackedVerifier.new)
|
||||
#
|
||||
# The +verify!+ method receives the decoded +Attestation+ object and the raw
|
||||
# +client_data_json+ bytes. Raise +InvalidResponseError+ if verification fails.
|
||||
#
|
||||
class ActionPack::WebAuthn::Authenticator::AttestationVerifiers::None
|
||||
def verify!(attestation, client_data_json:)
|
||||
if attestation.attestation_statement.present?
|
||||
raise ActionPack::WebAuthn::Authenticator::Response::InvalidResponseError,
|
||||
"Attestation statement must be empty for 'none' format"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -57,7 +57,7 @@ class ActionPack::WebAuthn::Authenticator::Data
|
||||
BACKUP_STATE_FLAG = 0x10
|
||||
ATTESTED_CREDENTIAL_DATA_FLAG = 0x40
|
||||
|
||||
attr_reader :bytes, :relying_party_id_hash, :flags, :sign_count, :credential_id, :public_key_bytes
|
||||
attr_reader :bytes, :relying_party_id_hash, :flags, :sign_count, :aaguid, :credential_id, :public_key_bytes
|
||||
|
||||
class << self
|
||||
def wrap(data)
|
||||
@@ -81,10 +81,13 @@ class ActionPack::WebAuthn::Authenticator::Data
|
||||
sign_count = bytes[position, SIGN_COUNT_LENGTH].pack("C*").unpack1("N")
|
||||
position += SIGN_COUNT_LENGTH
|
||||
|
||||
aaguid = nil
|
||||
credential_id = nil
|
||||
public_key_bytes = nil
|
||||
|
||||
if flags & ATTESTED_CREDENTIAL_DATA_FLAG != 0
|
||||
aaguid_bytes = bytes[position, AAGUID_LENGTH].pack("C*")
|
||||
aaguid = aaguid_bytes.unpack("H8H4H4H4H12").join("-")
|
||||
position += AAGUID_LENGTH
|
||||
|
||||
credential_id_length = bytes[position, CREDENTIAL_ID_LENGTH_BYTES].pack("C*").unpack1("n")
|
||||
@@ -101,17 +104,19 @@ class ActionPack::WebAuthn::Authenticator::Data
|
||||
relying_party_id_hash: relying_party_id_hash,
|
||||
flags: flags,
|
||||
sign_count: sign_count,
|
||||
aaguid: aaguid,
|
||||
credential_id: credential_id,
|
||||
public_key_bytes: public_key_bytes
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(bytes:, relying_party_id_hash:, flags:, sign_count:, credential_id:, public_key_bytes:)
|
||||
def initialize(bytes:, relying_party_id_hash:, flags:, sign_count:, aaguid: nil, credential_id:, public_key_bytes:)
|
||||
@bytes = bytes
|
||||
@relying_party_id_hash = relying_party_id_hash
|
||||
@flags = flags
|
||||
@sign_count = sign_count
|
||||
@aaguid = aaguid
|
||||
@credential_id = credential_id
|
||||
@public_key_bytes = public_key_bytes
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user