mirror of
https://github.com/go-gitea/gitea.git
synced 2025-11-15 10:16:03 +01:00
Support webauthn (#17957)
Migrate from U2F to Webauthn Co-authored-by: Andrew Thornton <art27@cantab.net> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
256
vendor/github.com/duo-labs/webauthn/protocol/authenticator.go
generated
vendored
Normal file
256
vendor/github.com/duo-labs/webauthn/protocol/authenticator.go
generated
vendored
Normal file
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user