summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/duo-labs/webauthn/protocol/authenticator.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/duo-labs/webauthn/protocol/authenticator.go')
-rw-r--r--vendor/github.com/duo-labs/webauthn/protocol/authenticator.go256
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
+}