Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support hints and attestation formats #216

Merged
merged 2 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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