Skip to content

Commit

Permalink
feat: support hints and attestation formats (#216)
Browse files Browse the repository at this point in the history
This adds support to the hints and attestation format options, both of which are new elements from Level 3 which are effectively optional. This allows users who wish to leverage this option to do so with no negative effects for those who do not.
  • Loading branch information
james-d-elliott authored Apr 26, 2024
1 parent 4bb5779 commit 824017d
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 40 deletions.
8 changes: 4 additions & 4 deletions protocol/attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,11 @@ type AttestationObject struct {

type attestationFormatValidationHandler func(AttestationObject, []byte) (string, []interface{}, error)

var attestationRegistry = make(map[string]attestationFormatValidationHandler)
var attestationRegistry = make(map[AttestationFormat]attestationFormatValidationHandler)

// RegisterAttestationFormat is a method to register attestation formats with the library. Generally using one of the
// locally registered attestation formats is sufficient.
func RegisterAttestationFormat(format string, handler attestationFormatValidationHandler) {
func RegisterAttestationFormat(format AttestationFormat, handler attestationFormatValidationHandler) {
attestationRegistry[format] = handler
}

Expand Down Expand Up @@ -135,15 +135,15 @@ func (attestationObject *AttestationObject) Verify(relyingPartyID string, client

// But first let's make sure attestation is present. If it isn't, we don't need to handle
// any of the following steps
if attestationObject.Format == "none" {
if AttestationFormat(attestationObject.Format) == AttestationFormatNone {
if len(attestationObject.AttStatement) != 0 {
return ErrAttestationFormat.WithInfo("Attestation format none with attestation present")
}

return nil
}

formatHandler, valid := attestationRegistry[attestationObject.Format]
formatHandler, valid := attestationRegistry[AttestationFormat(attestationObject.Format)]
if !valid {
return ErrAttestationFormat.WithInfo(fmt.Sprintf("Attestation format %s is unsupported", attestationObject.Format))
}
Expand Down
4 changes: 1 addition & 3 deletions protocol/attestation_androidkey.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@ import (
"github.com/go-webauthn/webauthn/protocol/webauthncose"
)

var androidAttestationKey = "android-key"

func init() {
RegisterAttestationFormat(androidAttestationKey, verifyAndroidKeyFormat)
RegisterAttestationFormat(AttestationFormatAndroidKey, verifyAndroidKeyFormat)
}

// The android-key attestation statement looks like:
Expand Down
4 changes: 1 addition & 3 deletions protocol/attestation_apple.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@ import (
"github.com/go-webauthn/webauthn/protocol/webauthncose"
)

var appleAttestationKey = "apple"

func init() {
RegisterAttestationFormat(appleAttestationKey, verifyAppleFormat)
RegisterAttestationFormat(AttestationFormatApple, verifyAppleFormat)
}

// The apple attestation statement looks like:
Expand Down
8 changes: 3 additions & 5 deletions protocol/attestation_packed.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@ import (
"github.com/go-webauthn/webauthn/protocol/webauthncose"
)

var packedAttestationKey = "packed"

func init() {
RegisterAttestationFormat(packedAttestationKey, verifyPackedFormat)
RegisterAttestationFormat(AttestationFormatPacked, verifyPackedFormat)
}

// The packed attestation statement looks like:
Expand Down Expand Up @@ -45,13 +43,13 @@ func verifyPackedFormat(att AttestationObject, clientDataHash []byte) (string, [

alg, present := att.AttStatement["alg"].(int64)
if !present {
return packedAttestationKey, nil, ErrAttestationFormat.WithDetails("Error retrieving alg value")
return string(AttestationFormatPacked), nil, ErrAttestationFormat.WithDetails("Error retrieving alg value")
}

// Get the sig value - A byte string containing the attestation signature.
sig, present := att.AttStatement["sig"].([]byte)
if !present {
return packedAttestationKey, nil, ErrAttestationFormat.WithDetails("Error retrieving sig value")
return string(AttestationFormatPacked), nil, ErrAttestationFormat.WithDetails("Error retrieving sig value")
}

// Step 2. If x5c is present, this indicates that the attestation type is not ECDAA.
Expand Down
4 changes: 1 addition & 3 deletions protocol/attestation_safetynet.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@ import (
"github.com/go-webauthn/webauthn/metadata"
)

var safetyNetAttestationKey = "android-safetynet"

func init() {
RegisterAttestationFormat(safetyNetAttestationKey, verifySafetyNetFormat)
RegisterAttestationFormat(AttestationFormatAndroidSafetyNet, verifySafetyNetFormat)
}

type SafetyNetResponse struct {
Expand Down
4 changes: 1 addition & 3 deletions protocol/attestation_tpm.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@ import (
"github.com/go-webauthn/webauthn/protocol/webauthncose"
)

var tpmAttestationKey = "tpm"

func init() {
RegisterAttestationFormat(tpmAttestationKey, verifyTPMFormat)
RegisterAttestationFormat(AttestationFormatTPM, verifyTPMFormat)
}

func verifyTPMFormat(att AttestationObject, clientDataHash []byte) (string, []interface{}, error) {
Expand Down
4 changes: 1 addition & 3 deletions protocol/attestation_u2f.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,8 @@ import (
"github.com/go-webauthn/webauthn/protocol/webauthncose"
)

var u2fAttestationKey = "fido-u2f"

func init() {
RegisterAttestationFormat(u2fAttestationKey, verifyU2FFormat)
RegisterAttestationFormat(AttestationFormatFIDOU2F, verifyU2FFormat)
}

// verifyU2FFormat - Follows verification steps set out by https://www.w3.org/TR/webauthn/#fido-u2f-attestation
Expand Down
90 changes: 76 additions & 14 deletions protocol/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,33 @@ type CredentialAssertion struct {
// In order to create a Credential via create(), the caller specifies a few parameters in a
// PublicKeyCredentialCreationOptions object.
//
// TODO: There is one field missing from this for WebAuthn Level 3. A string slice named 'attestationFormats'.
//
// Specification: §5.4. Options for Credential Creation (https://www.w3.org/TR/webauthn/#dictionary-makecredentialoptions)
type PublicKeyCredentialCreationOptions struct {
RelyingParty RelyingPartyEntity `json:"rp"`
User UserEntity `json:"user"`
Challenge URLEncodedBase64 `json:"challenge"`
Parameters []CredentialParameter `json:"pubKeyCredParams,omitempty"`
Timeout int `json:"timeout,omitempty"`
CredentialExcludeList []CredentialDescriptor `json:"excludeCredentials,omitempty"`
AuthenticatorSelection AuthenticatorSelection `json:"authenticatorSelection,omitempty"`
Attestation ConveyancePreference `json:"attestation,omitempty"`
Extensions AuthenticationExtensions `json:"extensions,omitempty"`
RelyingParty RelyingPartyEntity `json:"rp"`
User UserEntity `json:"user"`
Challenge URLEncodedBase64 `json:"challenge"`
Parameters []CredentialParameter `json:"pubKeyCredParams,omitempty"`
Timeout int `json:"timeout,omitempty"`
CredentialExcludeList []CredentialDescriptor `json:"excludeCredentials,omitempty"`
AuthenticatorSelection AuthenticatorSelection `json:"authenticatorSelection,omitempty"`
Hints []PublicKeyCredentialHint `json:"hints,omitempty"`
Attestation ConveyancePreference `json:"attestation,omitempty"`
AttestationFormats []AttestationFormat `json:"attestationFormats,omitempty"`
Extensions AuthenticationExtensions `json:"extensions,omitempty"`
}

// The PublicKeyCredentialRequestOptions dictionary supplies get() with the data it needs to generate an assertion.
// Its challenge member MUST be present, while its other members are OPTIONAL.
//
// TODO: There are two fields missing from this for WebAuthn Level 3. A string type named 'attestation', and a string
// slice named 'attestationFormats'.
//
// Specification: §5.5. Options for Assertion Generation (https://www.w3.org/TR/webauthn/#dictionary-assertion-options)
type PublicKeyCredentialRequestOptions struct {
Challenge URLEncodedBase64 `json:"challenge"`
Timeout int `json:"timeout,omitempty"`
RelyingPartyID string `json:"rpId,omitempty"`
AllowedCredentials []CredentialDescriptor `json:"allowCredentials,omitempty"`
UserVerification UserVerificationRequirement `json:"userVerification,omitempty"`
Attestation ConveyancePreference `json:"attestation,omitempty"`
AttestationFormats []AttestationFormat `json:"attestationFormats,omitempty"`
Extensions AuthenticationExtensions `json:"extensions,omitempty"`
}

Expand Down Expand Up @@ -126,6 +125,69 @@ type AuthenticatorSelection struct {
UserVerification UserVerificationRequirement `json:"userVerification,omitempty"`
}

// PublicKeyCredentialHint is a type representing the enum PublicKeyCredentialHints from
// https://www.w3.org/TR/webauthn-3/#enum-hints.
type PublicKeyCredentialHint string

const (
// PublicKeyCredentialHintSecurityKey is a PublicKeyCredentialHint that indicates that the Relying Party believes
// that users will satisfy this request with a physical security key. For example, an enterprise Relying Party may
// set this hint if they have issued security keys to their employees and will only accept those authenticators for
// registration and authentication.
//
// For compatibility with older user agents, when this hint is used in PublicKeyCredentialCreationOptions, the
// authenticatorAttachment SHOULD be set to cross-platform.
PublicKeyCredentialHintSecurityKey PublicKeyCredentialHint = "security-key"

// PublicKeyCredentialHintClientDevice is a PublicKeyCredentialHint that indicates that the Relying Party believes
// that users will satisfy this request with a platform authenticator attached to the client device.
//
// For compatibility with older user agents, when this hint is used in PublicKeyCredentialCreationOptions, the
// authenticatorAttachment SHOULD be set to platform.
PublicKeyCredentialHintClientDevice PublicKeyCredentialHint = "client-device"

// PublicKeyCredentialHintHybrid is a PublicKeyCredentialHint that indicates that the Relying Party believes that
// users will satisfy this request with general-purpose authenticators such as smartphones. For example, a consumer
// Relying Party may believe that only a small fraction of their customers possesses dedicated security keys. This
// option also implies that the local platform authenticator should not be promoted in the UI.
//
// For compatibility with older user agents, when this hint is used in PublicKeyCredentialCreationOptions, the
// authenticatorAttachment SHOULD be set to cross-platform.
PublicKeyCredentialHintHybrid PublicKeyCredentialHint = "hybrid"
)

type AttestationFormat string

const (
// AttestationFormatPacked is the "packed" attestation statement format is a WebAuthn-optimized format for
// attestation. It uses a very compact but still extensible encoding method. This format is implementable by
//authenticators with limited resources (e.g., secure elements).
AttestationFormatPacked AttestationFormat = "packed"

// AttestationFormatTPM is the TPM attestation statement format returns an attestation statement in the same format
// as the packed attestation statement format, although the rawData and signature fields are computed differently.
AttestationFormatTPM AttestationFormat = "tpm"

// AttestationFormatAndroidKey is the attestation statement format for platform authenticators on versions "N", and
// later, which may provide this proprietary "hardware attestation" statement.
AttestationFormatAndroidKey AttestationFormat = "android-key"

// AttestationFormatAndroidSafetyNet is the attestation statement format that Android-based platform authenticators
// MAY produce an attestation statement based on the Android SafetyNet API.
AttestationFormatAndroidSafetyNet AttestationFormat = "android-safetynet"

// AttestationFormatFIDOU2F is the attestation statement format that is used with FIDO U2F authenticators.
AttestationFormatFIDOU2F AttestationFormat = "fido-u2f"

// AttestationFormatApple is the attestation statement format that is used with Apple devices' platform
// authenticators.
AttestationFormatApple AttestationFormat = "apple"

// AttestationFormatNone is the attestation statement format that is used to replace any authenticator-provided
// attestation statement when a WebAuthn Relying Party indicates it does not wish to receive attestation information.
AttestationFormatNone AttestationFormat = "none"
)

// ConveyancePreference is the type representing the AttestationConveyancePreference IDL.
//
// WebAuthn Relying Parties may use AttestationConveyancePreference to specify their preference regarding attestation
Expand Down
16 changes: 16 additions & 0 deletions webauthn/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,22 @@ func WithUserVerification(userVerification protocol.UserVerificationRequirement)
}
}

// WithLoginConveyancePreference adjusts the non-default parameters regarding whether the authenticator should attest to the
// credential.
func WithLoginConveyancePreference(preference protocol.ConveyancePreference) LoginOption {
return func(cco *protocol.PublicKeyCredentialRequestOptions) {
cco.Attestation = preference
}
}

// WithLoginAttestationFormats adjusts the preferred attestation formats for this credential request in most to least
// preferable. Advisory only.
func WithLoginAttestationFormats(formats ...protocol.AttestationFormat) LoginOption {
return func(cco *protocol.PublicKeyCredentialRequestOptions) {
cco.AttestationFormats = formats
}
}

// WithAssertionExtensions adjusts the requested extensions.
func WithAssertionExtensions(extensions protocol.AuthenticationExtensions) LoginOption {
return func(cco *protocol.PublicKeyCredentialRequestOptions) {
Expand Down
26 changes: 24 additions & 2 deletions webauthn/registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,21 +101,43 @@ func WithAuthenticatorSelection(authenticatorSelection protocol.AuthenticatorSel
}
}

func WithHints(hints ...protocol.PublicKeyCredentialHint) RegistrationOption {
return func(cco *protocol.PublicKeyCredentialCreationOptions) {
cco.Hints = hints
}
}

// WithExclusions adjusts the non-default parameters regarding credentials to exclude from registration.
func WithExclusions(excludeList []protocol.CredentialDescriptor) RegistrationOption {
return func(cco *protocol.PublicKeyCredentialCreationOptions) {
cco.CredentialExcludeList = excludeList
}
}

// WithConveyancePreference adjusts the non-default parameters regarding whether the authenticator should attest to the
// credential.
// WithConveyancePreference is a direct alias for WithRegistrationConveyancePreference.
//
// Deprecated: Use WithRegistrationConveyancePreference in favor of WithConveyancePreference as this function will be
// likely be removed in a future release.
func WithConveyancePreference(preference protocol.ConveyancePreference) RegistrationOption {
return WithRegistrationConveyancePreference(preference)
}

// WithRegistrationConveyancePreference adjusts the non-default parameters regarding whether the authenticator should attest to the
// credential.
func WithRegistrationConveyancePreference(preference protocol.ConveyancePreference) RegistrationOption {
return func(cco *protocol.PublicKeyCredentialCreationOptions) {
cco.Attestation = preference
}
}

// WithRegistrationAttestationFormats adjusts the preferred attestation formats for this credential creation in most to
// least preferable. Advisory only.
func WithRegistrationAttestationFormats(formats ...protocol.AttestationFormat) RegistrationOption {
return func(cco *protocol.PublicKeyCredentialCreationOptions) {
cco.AttestationFormats = formats
}
}

// WithExtensions adjusts the extension parameter in the registration options.
func WithExtensions(extension protocol.AuthenticationExtensions) RegistrationOption {
return func(cco *protocol.PublicKeyCredentialCreationOptions) {
Expand Down

0 comments on commit 824017d

Please sign in to comment.