diff options
Diffstat (limited to 'vendor/github.com/duo-labs/webauthn/protocol/authenticator.go')
-rw-r--r-- | vendor/github.com/duo-labs/webauthn/protocol/authenticator.go | 256 |
1 files changed, 256 insertions, 0 deletions
diff --git a/vendor/github.com/duo-labs/webauthn/protocol/authenticator.go b/vendor/github.com/duo-labs/webauthn/protocol/authenticator.go new file mode 100644 index 0000000000..ceb21526d9 --- /dev/null +++ b/vendor/github.com/duo-labs/webauthn/protocol/authenticator.go @@ -0,0 +1,256 @@ +package protocol + +import ( + "bytes" + "encoding/binary" + "fmt" + + "github.com/fxamacker/cbor/v2" +) + +var minAuthDataLength = 37 + +// Authenticators respond to Relying Party requests by returning an object derived from the +// AuthenticatorResponse interface. See §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 From §6.1 of the spec. +// 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, at least during attestation, contains the Public Key that the RP stores +// and will associate with the user attempting to register. +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 https://www.w3.org/TR/webauthn/#platform-attachment +type AuthenticatorAttachment string + +const ( + // Platform - 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 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" +) + +// 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 may obtain a list of transports hints from some +// attestation statement formats or via some out-of-band mechanism; it is outside the scope of this +// specification to define that mechanism. +// See §5.10.4. Authenticator Transport https://www.w3.org/TR/webauthn/#transport +type AuthenticatorTransport string + +const ( + // USB The authenticator should transport information over USB + USB AuthenticatorTransport = "usb" + // NFC The authenticator should transport information over Near Field Communication Protocol + NFC AuthenticatorTransport = "nfc" + // BLE The authenticator should transport information over Bluetooth + BLE AuthenticatorTransport = "ble" + // Internal the client should use an internal source like a TPM or SE + Internal AuthenticatorTransport = "internal" +) + +// 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. +// See §5.10.6. User Verification Requirement Enumeration https://www.w3.org/TR/webauthn/#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. +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 + FlagUserPresent AuthenticatorFlags = 1 << iota // Referred to as UP + _ // Reserved + // FlagUserVerified Bit 00000100 in the byte sequence. Tells us if user is verified + // by the authenticator using a biometric or PIN + FlagUserVerified // Referred to as UV + _ // Reserved + _ // Reserved + _ // Reserved + // FlagAttestedCredentialData Bit 01000000 in the byte sequence. Indicates whether + // the authenticator added attested credential data. + FlagAttestedCredentialData // Referred to as AT + // FlagHasExtension Bit 10000000 in the byte sequence. Indicates if the authenticator data has extensions. + FlagHasExtensions // Referred to as ED +) + +// UserPresent returns if the UP flag was set +func (flag AuthenticatorFlags) UserPresent() bool { + return (flag & FlagUserPresent) == FlagUserPresent +} + +// UserVerified returns if the UV flag was set +func (flag AuthenticatorFlags) UserVerified() 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 +} + +// Unmarshal will take the raw Authenticator Data and marshalls 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) error { + if minAuthDataLength > len(rawAuthData) { + err := ErrBadRequest.WithDetails("Authenticator data length too short") + info := fmt.Sprintf("Expected data greater than %d bytes. Got %d bytes\n", minAuthDataLength, len(rawAuthData)) + return err.WithInfo(info) + } + + 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) > minAuthDataLength { + a.unmarshalAttestedData(rawAuthData) + 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) { + a.AttData.AAGUID = rawAuthData[37:53] + idLength := binary.BigEndian.Uint16(rawAuthData[53:55]) + a.AttData.CredentialID = rawAuthData[55 : 55+idLength] + a.AttData.CredentialPublicKey = unmarshalCredentialPublicKey(rawAuthData[55+idLength:]) +} + +// Unmarshall the credential's Public Key into CBOR encoding +func unmarshalCredentialPublicKey(keyBytes []byte) []byte { + var m interface{} + cbor.Unmarshal(keyBytes, &m) + rawBytes, _ := cbor.Marshal(m) + return rawBytes +} + +// ResidentKeyRequired - Require that the key be private key resident to the client device +func ResidentKeyRequired() *bool { + required := true + return &required +} + +// ResidentKeyUnrequired - Do not require that the private key be resident to the client device. +func ResidentKeyUnrequired() *bool { + required := false + return &required +} + +// Verify on AuthenticatorData handles Steps 9 through 12 for Registration +// and Steps 11 through 14 for Assertion. +func (a *AuthenticatorData) Verify(rpIdHash, 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 %s and Received %s\n", 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 +} |