1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 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 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393
|
package protocol
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/go-webauthn/webauthn/protocol/webauthncbor"
)
const (
minAuthDataLength = 37
minAttestedAuthLength = 55
maxCredentialIDLength = 1023
)
// AuthenticatorResponse represents the IDL with the same name.
//
// Authenticators respond to Relying Party requests by returning an object derived from the AuthenticatorResponse
// interface
//
// Specification: §5.2. Authenticator Responses (https://www.w3.org/TR/webauthn/#iface-authenticatorresponse)
type AuthenticatorResponse struct {
// From the spec https://www.w3.org/TR/webauthn/#dom-authenticatorresponse-clientdatajson
// This attribute contains a JSON serialization of the client data passed to the authenticator
// by the client in its call to either create() or get().
ClientDataJSON URLEncodedBase64 `json:"clientDataJSON"`
}
// AuthenticatorData represents the IDL with the same name.
//
// The authenticator data structure encodes contextual bindings made by the authenticator. These bindings are controlled
// by the authenticator itself, and derive their trust from the WebAuthn Relying Party's assessment of the security
// properties of the authenticator. In one extreme case, the authenticator may be embedded in the client, and its
// bindings may be no more trustworthy than the client data. At the other extreme, the authenticator may be a discrete
// entity with high-security hardware and software, connected to the client over a secure channel. In both cases, the
// Relying Party receives the authenticator data in the same format, and uses its knowledge of the authenticator to make
// trust decisions.
//
// The authenticator data has a compact but extensible encoding. This is desired since authenticators can be devices
// with limited capabilities and low power requirements, with much simpler software stacks than the client platform.
//
// Specification: §6.1. Authenticator Data (https://www.w3.org/TR/webauthn/#sctn-authenticator-data)
type AuthenticatorData struct {
RPIDHash []byte `json:"rpid"`
Flags AuthenticatorFlags `json:"flags"`
Counter uint32 `json:"sign_count"`
AttData AttestedCredentialData `json:"att_data"`
ExtData []byte `json:"ext_data"`
}
type AttestedCredentialData struct {
AAGUID []byte `json:"aaguid"`
CredentialID []byte `json:"credential_id"`
// The raw credential public key bytes received from the attestation data.
CredentialPublicKey []byte `json:"public_key"`
}
// AuthenticatorAttachment represents the IDL enum of the same name, and is used as part of the Authenticator Selection
// Criteria.
//
// This enumeration’s values describe authenticators' attachment modalities. Relying Parties use this to express a
// preferred authenticator attachment modality when calling navigator.credentials.create() to create a credential.
//
// If this member is present, eligible authenticators are filtered to only authenticators attached with the specified
// §5.4.5 Authenticator Attachment Enumeration (enum AuthenticatorAttachment). The value SHOULD be a member of
// AuthenticatorAttachment but client platforms MUST ignore unknown values, treating an unknown value as if the member
// does not exist.
//
// Specification: §5.4.4. Authenticator Selection Criteria (https://www.w3.org/TR/webauthn/#dom-authenticatorselectioncriteria-authenticatorattachment)
//
// Specification: §5.4.5. Authenticator Attachment Enumeration (https://www.w3.org/TR/webauthn/#enum-attachment)
type AuthenticatorAttachment string
const (
// Platform represents a platform authenticator is attached using a client device-specific transport, called
// platform attachment, and is usually not removable from the client device. A public key credential bound to a
// platform authenticator is called a platform credential.
Platform AuthenticatorAttachment = "platform"
// CrossPlatform represents a roaming authenticator is attached using cross-platform transports, called
// cross-platform attachment. Authenticators of this class are removable from, and can "roam" among, client devices.
// A public key credential bound to a roaming authenticator is called a roaming credential.
CrossPlatform AuthenticatorAttachment = "cross-platform"
)
// ResidentKeyRequirement represents the IDL of the same name.
//
// This enumeration’s values describe the Relying Party's requirements for client-side discoverable credentials
// (formerly known as resident credentials or resident keys).
//
// Specifies the extent to which the Relying Party desires to create a client-side discoverable credential. For
// historical reasons the naming retains the deprecated “resident” terminology. The value SHOULD be a member of
// ResidentKeyRequirement but client platforms MUST ignore unknown values, treating an unknown value as if the member
// does not exist. If no value is given then the effective value is required if requireResidentKey is true or
// discouraged if it is false or absent.
//
// Specification: §5.4.4. Authenticator Selection Criteria (https://www.w3.org/TR/webauthn/#dom-authenticatorselectioncriteria-residentkey)
//
// Specification: §5.4.6. Resident Key Requirement Enumeration (https://www.w3.org/TR/webauthn/#enumdef-residentkeyrequirement)
type ResidentKeyRequirement string
const (
// ResidentKeyRequirementDiscouraged indicates the Relying Party prefers creating a server-side credential, but will
// accept a client-side discoverable credential. This is the default.
ResidentKeyRequirementDiscouraged ResidentKeyRequirement = "discouraged"
// ResidentKeyRequirementPreferred indicates to the client we would prefer a discoverable credential.
ResidentKeyRequirementPreferred ResidentKeyRequirement = "preferred"
// ResidentKeyRequirementRequired indicates the Relying Party requires a client-side discoverable credential, and is
// prepared to receive an error if a client-side discoverable credential cannot be created.
ResidentKeyRequirementRequired ResidentKeyRequirement = "required"
)
// AuthenticatorTransport represents the IDL enum with the same name.
//
// Authenticators may implement various transports for communicating with clients. This enumeration defines hints as to
// how clients might communicate with a particular authenticator in order to obtain an assertion for a specific
// credential. Note that these hints represent the WebAuthn Relying Party's best belief as to how an authenticator may
// be reached. A Relying Party will typically learn of the supported transports for a public key credential via
// getTransports().
//
// Specification: §5.8.4. Authenticator Transport Enumeration (https://www.w3.org/TR/webauthn/#enumdef-authenticatortransport)
type AuthenticatorTransport string
const (
// USB indicates the respective authenticator can be contacted over removable USB.
USB AuthenticatorTransport = "usb"
// NFC indicates the respective authenticator can be contacted over Near Field Communication (NFC).
NFC AuthenticatorTransport = "nfc"
// BLE indicates the respective authenticator can be contacted over Bluetooth Smart (Bluetooth Low Energy / BLE).
BLE AuthenticatorTransport = "ble"
// Hybrid indicates the respective authenticator can be contacted using a combination of (often separate)
// data-transport and proximity mechanisms. This supports, for example, authentication on a desktop computer using
// a smartphone.
//
// WebAuthn Level 3.
Hybrid AuthenticatorTransport = "hybrid"
// Internal indicates the respective authenticator is contacted using a client device-specific transport, i.e., it
// is a platform authenticator. These authenticators are not removable from the client device.
Internal AuthenticatorTransport = "internal"
)
// UserVerificationRequirement is a representation of the UserVerificationRequirement IDL enum.
//
// A WebAuthn Relying Party may require user verification for some of its operations but not for others,
// and may use this type to express its needs.
//
// Specification: §5.8.6. User Verification Requirement Enumeration (https://www.w3.org/TR/webauthn/#enum-userVerificationRequirement)
type UserVerificationRequirement string
const (
// VerificationRequired User verification is required to create/release a credential
VerificationRequired UserVerificationRequirement = "required"
// VerificationPreferred User verification is preferred to create/release a credential
VerificationPreferred UserVerificationRequirement = "preferred" // This is the default
// VerificationDiscouraged The authenticator should not verify the user for the credential
VerificationDiscouraged UserVerificationRequirement = "discouraged"
)
// AuthenticatorFlags A byte of information returned during during ceremonies in the
// authenticatorData that contains bits that give us information about the
// whether the user was present and/or verified during authentication, and whether
// there is attestation or extension data present. Bit 0 is the least significant bit.
//
// Specification: §6.1. Authenticator Data - Flags (https://www.w3.org/TR/webauthn/#flags)
type AuthenticatorFlags byte
// The bits that do not have flags are reserved for future use.
const (
// FlagUserPresent Bit 00000001 in the byte sequence. Tells us if user is present. Also referred to as the UP flag.
FlagUserPresent AuthenticatorFlags = 1 << iota // Referred to as UP
// FlagRFU1 is a reserved for future use flag.
FlagRFU1
// FlagUserVerified Bit 00000100 in the byte sequence. Tells us if user is verified
// by the authenticator using a biometric or PIN. Also referred to as the UV flag.
FlagUserVerified
// FlagBackupEligible Bit 00001000 in the byte sequence. Tells us if a backup is eligible for device. Also referred
// to as the BE flag.
FlagBackupEligible // Referred to as BE
// FlagBackupState Bit 00010000 in the byte sequence. Tells us if a backup state for device. Also referred to as the
// BS flag.
FlagBackupState
// FlagRFU2 is a reserved for future use flag.
FlagRFU2
// FlagAttestedCredentialData Bit 01000000 in the byte sequence. Indicates whether
// the authenticator added attested credential data. Also referred to as the AT flag.
FlagAttestedCredentialData
// FlagHasExtensions Bit 10000000 in the byte sequence. Indicates if the authenticator data has extensions. Also
// referred to as the ED flag.
FlagHasExtensions
)
// UserPresent returns if the UP flag was set.
func (flag AuthenticatorFlags) UserPresent() bool {
return flag.HasUserPresent()
}
// UserVerified returns if the UV flag was set.
func (flag AuthenticatorFlags) UserVerified() bool {
return flag.HasUserVerified()
}
// HasUserPresent returns if the UP flag was set.
func (flag AuthenticatorFlags) HasUserPresent() bool {
return (flag & FlagUserPresent) == FlagUserPresent
}
// HasUserVerified returns if the UV flag was set.
func (flag AuthenticatorFlags) HasUserVerified() bool {
return (flag & FlagUserVerified) == FlagUserVerified
}
// HasAttestedCredentialData returns if the AT flag was set.
func (flag AuthenticatorFlags) HasAttestedCredentialData() bool {
return (flag & FlagAttestedCredentialData) == FlagAttestedCredentialData
}
// HasExtensions returns if the ED flag was set.
func (flag AuthenticatorFlags) HasExtensions() bool {
return (flag & FlagHasExtensions) == FlagHasExtensions
}
// HasBackupEligible returns if the BE flag was set.
func (flag AuthenticatorFlags) HasBackupEligible() bool {
return (flag & FlagBackupEligible) == FlagBackupEligible
}
// HasBackupState returns if the BS flag was set.
func (flag AuthenticatorFlags) HasBackupState() bool {
return (flag & FlagBackupState) == FlagBackupState
}
// Unmarshal will take the raw Authenticator Data and marshals it into AuthenticatorData for further validation.
// The authenticator data has a compact but extensible encoding. This is desired since authenticators can be
// devices with limited capabilities and low power requirements, with much simpler software stacks than the client platform.
// The authenticator data structure is a byte array of 37 bytes or more, and is laid out in this table:
// https://www.w3.org/TR/webauthn/#table-authData
func (a *AuthenticatorData) Unmarshal(rawAuthData []byte) (err error) {
if minAuthDataLength > len(rawAuthData) {
return ErrBadRequest.
WithDetails("Authenticator data length too short").
WithInfo(fmt.Sprintf("Expected data greater than %d bytes. Got %d bytes", minAuthDataLength, len(rawAuthData)))
}
a.RPIDHash = rawAuthData[:32]
a.Flags = AuthenticatorFlags(rawAuthData[32])
a.Counter = binary.BigEndian.Uint32(rawAuthData[33:37])
remaining := len(rawAuthData) - minAuthDataLength
if a.Flags.HasAttestedCredentialData() {
if len(rawAuthData) > minAttestedAuthLength {
if err = a.unmarshalAttestedData(rawAuthData); err != nil {
return err
}
attDataLen := len(a.AttData.AAGUID) + 2 + len(a.AttData.CredentialID) + len(a.AttData.CredentialPublicKey)
remaining = remaining - attDataLen
} else {
return ErrBadRequest.WithDetails("Attested credential flag set but data is missing")
}
} else {
if !a.Flags.HasExtensions() && len(rawAuthData) != 37 {
return ErrBadRequest.WithDetails("Attested credential flag not set")
}
}
if a.Flags.HasExtensions() {
if remaining != 0 {
a.ExtData = rawAuthData[len(rawAuthData)-remaining:]
remaining -= len(a.ExtData)
} else {
return ErrBadRequest.WithDetails("Extensions flag set but extensions data is missing")
}
}
if remaining != 0 {
return ErrBadRequest.WithDetails("Leftover bytes decoding AuthenticatorData")
}
return nil
}
// If Attestation Data is present, unmarshall that into the appropriate public key structure.
func (a *AuthenticatorData) unmarshalAttestedData(rawAuthData []byte) (err error) {
a.AttData.AAGUID = rawAuthData[37:53]
idLength := binary.BigEndian.Uint16(rawAuthData[53:55])
if len(rawAuthData) < int(55+idLength) {
return ErrBadRequest.WithDetails("Authenticator attestation data length too short")
}
if idLength > maxCredentialIDLength {
return ErrBadRequest.WithDetails("Authenticator attestation data credential id length too long")
}
a.AttData.CredentialID = rawAuthData[55 : 55+idLength]
a.AttData.CredentialPublicKey, err = unmarshalCredentialPublicKey(rawAuthData[55+idLength:])
if err != nil {
return ErrBadRequest.WithDetails(fmt.Sprintf("Could not unmarshal Credential Public Key: %v", err))
}
return nil
}
// Unmarshall the credential's Public Key into CBOR encoding.
func unmarshalCredentialPublicKey(keyBytes []byte) (rawBytes []byte, err error) {
var m interface{}
if err = webauthncbor.Unmarshal(keyBytes, &m); err != nil {
return nil, err
}
if rawBytes, err = webauthncbor.Marshal(m); err != nil {
return nil, err
}
return rawBytes, nil
}
// ResidentKeyRequired - Require that the key be private key resident to the client device.
func ResidentKeyRequired() *bool {
required := true
return &required
}
// ResidentKeyNotRequired - Do not require that the private key be resident to the client device.
func ResidentKeyNotRequired() *bool {
required := false
return &required
}
// Deprecated: ResidentKeyUnrequired is an alias for ResidentKeyNotRequired and will be completely removed in the future.
func ResidentKeyUnrequired() *bool {
return ResidentKeyNotRequired()
}
// Verify on AuthenticatorData handles Steps 9 through 12 for Registration
// and Steps 11 through 14 for Assertion.
func (a *AuthenticatorData) Verify(rpIdHash []byte, appIDHash []byte, userVerificationRequired bool) error {
// Registration Step 9 & Assertion Step 11
// Verify that the RP ID hash in authData is indeed the SHA-256
// hash of the RP ID expected by the RP.
if !bytes.Equal(a.RPIDHash[:], rpIdHash) && !bytes.Equal(a.RPIDHash[:], appIDHash) {
return ErrVerification.WithInfo(fmt.Sprintf("RP Hash mismatch. Expected %x and Received %x", a.RPIDHash, rpIdHash))
}
// Registration Step 10 & Assertion Step 12
// Verify that the User Present bit of the flags in authData is set.
if !a.Flags.UserPresent() {
return ErrVerification.WithInfo(fmt.Sprintln("User presence flag not set by authenticator"))
}
// Registration Step 11 & Assertion Step 13
// If user verification is required for this assertion, verify that
// the User Verified bit of the flags in authData is set.
if userVerificationRequired && !a.Flags.UserVerified() {
return ErrVerification.WithInfo(fmt.Sprintln("User verification required but flag not set by authenticator"))
}
// Registration Step 12 & Assertion Step 14
// Verify that the values of the client extension outputs in clientExtensionResults
// and the authenticator extension outputs in the extensions in authData are as
// expected, considering the client extension input values that were given as the
// extensions option in the create() call. In particular, any extension identifier
// values in the clientExtensionResults and the extensions in authData MUST be also be
// present as extension identifier values in the extensions member of options, i.e., no
// extensions are present that were not requested. In the general case, the meaning
// of "are as expected" is specific to the Relying Party and which extensions are in use.
// This is not yet fully implemented by the spec or by browsers.
return nil
}
|