Class: Unmagic::Passkeys::WebAuthn::Authenticator::Data

Inherits:
Object
  • Object
show all
Defined in:
lib/unmagic/passkeys/web_authn/authenticator/data.rb

Overview

Action Pack WebAuthn Authenticator Data

Decodes and represents the authenticator data structure from WebAuthn responses. This binary format contains information about the authenticator and, during registration, the newly created credential.

Structure

The authenticator data consists of:

  • RP ID Hash (32 bytes) - SHA-256 hash of the relying party ID

  • Flags (1 byte) - Bit flags for user presence, verification, etc.

  • Sign Count (4 bytes) - Signature counter for replay detection

  • Attested Credential Data (variable) - Present only during registration

Usage

data = Unmagic::Passkeys::WebAuthn::Authenticator::Data.decode(bytes)

data.user_present?   # => true
data.user_verified?  # => true
data.sign_count      # => 42
data.credential_id   # => "abc123..." (registration only)
data.public_key      # => OpenSSL::PKey::EC (registration only)

Flags

user_present?

Returns true if the user performed a test of user presence (e.g., touched the authenticator).

user_verified?

Returns true if the user was verified through biometrics, PIN, or other method. This is stronger than mere presence.

backup_eligible?

Returns true if the credential can be backed up (e.g., synced passkeys from Apple, Google, or Microsoft). Indicates multi-device credential support.

backed_up?

Returns true if the credential is currently backed up to cloud storage. Useful for risk assessment—backed-up credentials may be accessible from multiple devices.

Constant Summary collapse

RELYING_PARTY_ID_HASH_LENGTH =

Segment lengths

32
FLAGS_LENGTH =
1
SIGN_COUNT_LENGTH =
4
AAGUID_LENGTH =
16
CREDENTIAL_ID_LENGTH_BYTES =
2
USER_PRESENT_FLAG =

Flags

0x01
USER_VERIFIED_FLAG =
0x04
BACKUP_ELIGIBLE_FLAG =
0x08
BACKUP_STATE_FLAG =
0x10
ATTESTED_CREDENTIAL_DATA_FLAG =
0x40

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(bytes:, relying_party_id_hash:, flags:, sign_count:, aaguid: nil, credential_id:, public_key_bytes:) ⇒ Data

Returns a new instance of Data.



135
136
137
138
139
140
141
142
143
# File 'lib/unmagic/passkeys/web_authn/authenticator/data.rb', line 135

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

Instance Attribute Details

#aaguidObject (readonly)

Returns the value of attribute aaguid.



60
61
62
# File 'lib/unmagic/passkeys/web_authn/authenticator/data.rb', line 60

def aaguid
  @aaguid
end

#bytesObject (readonly)

Returns the value of attribute bytes.



60
61
62
# File 'lib/unmagic/passkeys/web_authn/authenticator/data.rb', line 60

def bytes
  @bytes
end

#credential_idObject (readonly)

Returns the value of attribute credential_id.



60
61
62
# File 'lib/unmagic/passkeys/web_authn/authenticator/data.rb', line 60

def credential_id
  @credential_id
end

#flagsObject (readonly)

Returns the value of attribute flags.



60
61
62
# File 'lib/unmagic/passkeys/web_authn/authenticator/data.rb', line 60

def flags
  @flags
end

#public_key_bytesObject (readonly)

Returns the value of attribute public_key_bytes.



60
61
62
# File 'lib/unmagic/passkeys/web_authn/authenticator/data.rb', line 60

def public_key_bytes
  @public_key_bytes
end

#relying_party_id_hashObject (readonly)

Returns the value of attribute relying_party_id_hash.



60
61
62
# File 'lib/unmagic/passkeys/web_authn/authenticator/data.rb', line 60

def relying_party_id_hash
  @relying_party_id_hash
end

#sign_countObject (readonly)

Returns the value of attribute sign_count.



60
61
62
# File 'lib/unmagic/passkeys/web_authn/authenticator/data.rb', line 60

def sign_count
  @sign_count
end

Class Method Details

.decode(bytes) ⇒ Object

Decodes raw authenticator data bytes into a Data instance, parsing the RP ID hash, flags, sign count, and (if present) attested credential data.



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/unmagic/passkeys/web_authn/authenticator/data.rb', line 78

def decode(bytes)
  bytes = bytes.bytes if bytes.is_a?(String)

  minimum_length = RELYING_PARTY_ID_HASH_LENGTH + FLAGS_LENGTH + SIGN_COUNT_LENGTH
  if bytes.length < minimum_length
    raise Unmagic::Passkeys::WebAuthn::InvalidResponseError, "Authenticator data is too short"
  end

  position = 0

  relying_party_id_hash = bytes[position, RELYING_PARTY_ID_HASH_LENGTH].pack("C*")
  position += RELYING_PARTY_ID_HASH_LENGTH

  flags = bytes[position]
  position += FLAGS_LENGTH

  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
    if bytes.length < position + AAGUID_LENGTH + CREDENTIAL_ID_LENGTH_BYTES
      raise Unmagic::Passkeys::WebAuthn::InvalidResponseError, "Authenticator data is too short for attested credential data"
    end

    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")
    position += CREDENTIAL_ID_LENGTH_BYTES

    if bytes.length < position + credential_id_length + 1
      raise Unmagic::Passkeys::WebAuthn::InvalidResponseError, "Authenticator data is too short for credential ID and public key"
    end

    credential_id = Base64.urlsafe_encode64(bytes[position, credential_id_length].pack("C*"), padding: false)
    position += credential_id_length

    public_key_bytes = bytes[position..].pack("C*")
  end

  new(
    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

.wrap(data) ⇒ Object

Wraps raw authenticator data into a Data instance. Accepts an existing Data object (returned as-is), a Base64URL-encoded string, or raw binary.



65
66
67
68
69
70
71
72
73
74
# File 'lib/unmagic/passkeys/web_authn/authenticator/data.rb', line 65

def wrap(data)
  if data.is_a?(self)
    data
  else
    data = Base64.urlsafe_decode64(data) unless data.encoding == Encoding::BINARY
    decode(data)
  end
rescue ArgumentError
  raise Unmagic::Passkeys::WebAuthn::InvalidResponseError, "Invalid base64 encoding in authenticator data"
end

Instance Method Details

#backed_up?Boolean

Returns true if the credential is currently backed up to cloud storage. Only meaningful when backup_eligible? is true.

Returns:

  • (Boolean)


164
165
166
# File 'lib/unmagic/passkeys/web_authn/authenticator/data.rb', line 164

def backed_up?
  flags & BACKUP_STATE_FLAG != 0
end

#backup_eligible?Boolean

Returns true if the credential is eligible for backup (e.g., synced passkey). This indicates the authenticator supports multi-device credentials.

Returns:

  • (Boolean)


158
159
160
# File 'lib/unmagic/passkeys/web_authn/authenticator/data.rb', line 158

def backup_eligible?
  flags & BACKUP_ELIGIBLE_FLAG != 0
end

#public_keyObject

Decodes the COSE public key bytes into an OpenSSL key object. Returns nil when no attested credential data is present (authentication responses).



171
172
173
# File 'lib/unmagic/passkeys/web_authn/authenticator/data.rb', line 171

def public_key
  @public_key ||= Unmagic::Passkeys::WebAuthn::CoseKey.decode(public_key_bytes).to_openssl_key if public_key_bytes
end

#user_present?Boolean

Returns true if the user performed a test of presence (e.g., touched the authenticator).

Returns:

  • (Boolean)


147
148
149
# File 'lib/unmagic/passkeys/web_authn/authenticator/data.rb', line 147

def user_present?
  flags & USER_PRESENT_FLAG != 0
end

#user_verified?Boolean

Returns true if the user was verified via biometrics, PIN, or similar.

Returns:

  • (Boolean)


152
153
154
# File 'lib/unmagic/passkeys/web_authn/authenticator/data.rb', line 152

def user_verified?
  flags & USER_VERIFIED_FLAG != 0
end