Code Monkey home page Code Monkey logo

webauthn's Introduction

WebAuthn Library

GoDoc Go Report Card

This library is meant to handle Web Authentication for Go apps that wish to implement a passwordless solution for users. This library conforms as much as possible to the guidelines and implementation procedures outlined by the document.

Fork

This library is a hard fork of github.com/duo-labs/webauthn and is the natural successor to that library.

See the migration guide for more information about how to migrate and the differences between the libraries.

It is distributed under the same 3-Clause BSD license as the original fork, with the only amendment being the additional 3-Clause BSD license attributing license rights to this repository.

Go Version Support Policy

This library; unless otherwise explicitly expressed; will officially support versions of go which are currently supported by the go maintainers (usually 3 minor versions) with a brief transition time (usually 1 patch release of go, for example if go 1.21.0 is released, we will likely still support go 1.17 until go 1.21.1 is released). These specific rules apply at the time of a published release.

This library in our opinion handles a critical element of security in a dependent project and we aim to avoid backwards compatibility at the cost of security wherever possible. We also consider this especially important in a language like go where their backwards compatibility when upgrading the compile tools is usually flawless.

This policy means that users who wish to build this with older versions of go may find there are features being used which are not available in that version. The current intentionally supported versions of go are as follows:

  • go 1.22
  • go 1.21
  • go 1.20:
    • Go 1.20 support has been removed due to the new toolchain directive and lack of support in Go 1.20. This directive is unfortunately being used in dependent libraries and we'd opt for ensuring we can easily obtain potential fixes to CVE's rather than backwards compatibility. A such we have lifted the version requirement and implemented the toolchain directive in our module to reflect the intended toolchain.

Status

This library is still version 0, as per Semantic Versioning 2.0 rules there may be breaking changes without warning. While we strive to avoid such changes and strive to notify users they may be unavoidable.

Quickstart

go get github.com/go-webauthn/webauthn and initialize it in your application with basic configuration values.

Make sure your user model is able to handle the interface functions laid out in webauthn/types.go. This means also supporting the storage and retrieval of the credential and authenticator structs in webauthn/credential.go and webauthn/authenticator.go, respectively.

Examples

The following examples show some basic use cases of the library. For consistency sake the following variables are used to denote specific things:

  • Variable webAuthn: the webauthn.WebAuthn instance you initialize elsewhere in your code
  • Variable datastore: the pseudocode backend service that stores your webauthn session data and users such as PostgreSQL
  • Variable session: the webauthn.SessionData object
  • Variable user: your webauthn.User implementation

We try to avoid using specific external libraries (excluding stdlib) where possible, and you'll need to adapt these examples with this in mind.

Initialize the request handler

package example

import (
	"fmt"

	"github.com/go-webauthn/webauthn/webauthn"
)

var (
	webAuthn *webauthn.WebAuthn
	err error
)

// Your initialization function
func main() {
	wconfig := &webauthn.Config{
		RPDisplayName: "Go Webauthn", // Display Name for your site
		RPID: "go-webauthn.local", // Generally the FQDN for your site
		RPOrigins: []string{"https://login.go-webauthn.local"}, // The origin URLs allowed for WebAuthn requests
	}
	
	if webAuthn, err = webauthn.New(wconfig); err != nil {
		fmt.Println(err)
	}
}

Registering an account

package example

func BeginRegistration(w http.ResponseWriter, r *http.Request) {
	user := datastore.GetUser() // Find or create the new user  
	options, session, err := webAuthn.BeginRegistration(user)
	// handle errors if present
	// store the sessionData values 
	JSONResponse(w, options, http.StatusOK) // return the options generated
	// options.publicKey contain our registration options
}

func FinishRegistration(w http.ResponseWriter, r *http.Request) {
	user := datastore.GetUser() // Get the user
	
	// Get the session data stored from the function above
	session := datastore.GetSession()
		
	credential, err := webAuthn.FinishRegistration(user, session, r)
	if err != nil {
		// Handle Error and return.

		return
	}
	
	// If creation was successful, store the credential object
	// Pseudocode to add the user credential.
	user.AddCredential(credential)
	datastore.SaveUser(user)

	JSONResponse(w, "Registration Success", http.StatusOK) // Handle next steps
}

Logging into an account

package example

func BeginLogin(w http.ResponseWriter, r *http.Request) {
	user := datastore.GetUser() // Find the user
	
	options, session, err := webAuthn.BeginLogin(user)
	if err != nil {
		// Handle Error and return.

		return
	}
	
	// store the session values
	datastore.SaveSession(session)
	
	JSONResponse(w, options, http.StatusOK) // return the options generated
	// options.publicKey contain our registration options
}

func FinishLogin(w http.ResponseWriter, r *http.Request) {
	user := datastore.GetUser() // Get the user 
	
	// Get the session data stored from the function above
	session := datastore.GetSession()
	
	credential, err := webAuthn.FinishLogin(user, session, r)
	if err != nil {
		// Handle Error and return.

		return
	}

	// Handle credential.Authenticator.CloneWarning

	// If login was successful, update the credential object
	// Pseudocode to update the user credential.
	user.UpdateCredential(credential)
	datastore.SaveUser(user)
	
	JSONResponse(w, "Login Success", http.StatusOK)
}

Modifying Credential Options

You can modify the default credential creation options for registration and login by providing optional structs to the BeginRegistration and BeginLogin functions.

Registration modifiers

You can modify the registration options in the following ways:

package example

import (
	"github.com/go-webauthn/webauthn/protocol"
	"github.com/go-webauthn/webauthn/webauthn"
)

var webAuthn webauthn.WebAuthn // init this in your init function

func beginRegistration() {
	// Updating the AuthenticatorSelection options. 
	// See the struct declarations for values
	authSelect := protocol.AuthenticatorSelection{
		AuthenticatorAttachment: protocol.AuthenticatorAttachment("platform"),
		RequireResidentKey: protocol.ResidentKeyNotRequired(),
		UserVerification: protocol.VerificationRequired,
	}

	// Updating the ConveyencePreference options. 
	// See the struct declarations for values
	conveyancePref := protocol.PreferNoAttestation

	user := datastore.GetUser() // Get the user  
	opts, session, err := webAuthn.BeginRegistration(user, webauthn.WithAuthenticatorSelection(authSelect), webauthn.WithConveyancePreference(conveyancePref))

	// Handle next steps
}

Login modifiers

You can modify the login options to allow only certain credentials:

package example

import (
	"github.com/go-webauthn/webauthn/protocol"
	"github.com/go-webauthn/webauthn/webauthn"
)

var webAuthn webauthn.WebAuthn // init this in your init function

func beginLogin() {
	// Updating the AuthenticatorSelection options. 
	// See the struct declarations for values
	allowList := make([]protocol.CredentialDescriptor, 1)
	allowList[0] = protocol.CredentialDescriptor{
		CredentialID: credentialToAllowID,
		Type: protocol.CredentialType("public-key"),
	}

	user := datastore.GetUser() // Get the user  

	opts, session, err := w.BeginLogin(user, webauthn.WithAllowedCredentials(allowList))

	// Handle next steps
}

Timeout Mechanics

The library by default does not enforce timeouts. However the default timeouts sent to the browser are taken from the specification. You can override both of these behaviours however.

package example

import (
	"fmt"
	"time"
	
	"github.com/go-webauthn/webauthn/protocol"
	"github.com/go-webauthn/webauthn/webauthn"
)

func main() {
	wconfig := &webauthn.Config{
		RPDisplayName: "Go Webauthn",                               // Display Name for your site
		RPID:          "go-webauthn.local",                         // Generally the FQDN for your site
		RPOrigins:     []string{"https://login.go-webauthn.local"}, // The origin URLs allowed for WebAuthn requests
		Timeouts: webauthn.TimeoutsConfig{
			Login: webauthn.TimeoutConfig{
				Enforce:    true, // Require the response from the client comes before the end of the timeout.
				Timeout:    time.Second * 60, // Standard timeout for login sessions.
				TimeoutUVD: time.Second * 60, // Timeout for login sessions which have user verification set to discouraged.
			},
			Registration: webauthn.TimeoutConfig{
				Enforce:    true, // Require the response from the client comes before the end of the timeout.
				Timeout:    time.Second * 60, // Standard timeout for registration sessions.
				TimeoutUVD: time.Second * 60, // Timeout for login sessions which have user verification set to discouraged.
			},
		},
	}
	
	webAuthn, err := webauthn.New(wconfig)
	if err != nil {
		fmt.Println(err)
	}
}

Credential Record

The WebAuthn Level 3 specification describes the Credential Record which includes several required and optional elements that you should store for. See § 4 Terminology for details.

This section describes this element.

The fields listed in the specification have corresponding fields in the webauthn.Credential struct. See the below table for more information. We also include JSON mappings for those that wish to just store these values as JSON.

Specification Field Library Field JSON Field Notes
type N/A N/A This field is always publicKey for WebAuthn
id ID id
publicKey PublicKey publicKey
signCount Authenticator.SignCount authenticator.signCount
transports Transport transport
uvInitialized Flags.UserVerified flags.userVerified
backupEligible Flags.BackupEligible flags.backupEligible
backupState Flags.BackupState flags.backupState
attestationObject Attestation.Object attestation.object This field is a composite of the attestationObject and the relevant values to validate it
attestationClientDataJSON Attestation.ClientDataJSON attestation.clientDataJSON

Storage

It is also important to note that restoring the webauthn.Credential with the correct values will likely affect the validity of the webauthn.Credential, i.e. if some values are not restored the webauthn.Credential may fail validation in this scenario.

Verification

As long as the webauthn.Credential struct has exactly the same values when restored the Credential Verify function can be leveraged to verify the credential against the metadata.Provider. This can be either done during registration, on every login, or with a audit schedule.

In addition to using the Credential Verify function the webauthn.Config can contain a provider which will process all registrations automatically.

At this time no tooling exists to verify the credential automatically outside the registration flow. Implementation of this is considered domain logic and beyond the scope of what we provide documentation for; we just provide the necessary tooling to implement this yourself.

Acknowledgements

We graciously acknowledge the original authors of this library github.com/duo-labs/webauthn for their amazing implementation. Without their amazing work this library could not exist.

webauthn's People

Contributors

aseigler avatar berwyn avatar christian-2 avatar cmueller-tp avatar dependabot[bot] avatar e3b0c442 avatar fmitra avatar hanzhao-yu avatar james-d-elliott avatar jordan-wright avatar kylelady avatar lambang1 avatar machiel avatar matoous avatar mitar avatar nicksteele avatar printfn avatar pschultz avatar pulsejet avatar renovate[bot] avatar robinbrandt avatar smiller171 avatar step-security-bot avatar tarrencev avatar tobiaszheller avatar torkelrogstad avatar vincentserpoul avatar vvakame avatar zahrakeshtkar avatar zeidlermicha avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

webauthn's Issues

Library uses json.NewDecoder(body).Decode which might not error on trailing extra data

Version

0.9.2

Description

For example, in ParseCredentialCreationResponseBody you can see:

	var ccr CredentialCreationResponse

	if err = json.NewDecoder(body).Decode(&ccr); err != nil {
		return nil, ErrBadRequest.WithDetails("Parse error for Registration").WithInfo(err.Error())
	}

json.NewDecoder(r).Decode by itself is meant as a streaming parser and it does not fail on its own if there is trailing data after payload. I use such check in my own code to mitigate that.

Reproduction

N/A

Expectations

No response

Documentation

See: golang/go#36225

x509: unhandled critical extension error when using TPM attestation format

Version

0.11.0

Description

Thank you for developing this library

My Test Environment:
OS: Windows 11 Home
Version: 23H2
OS Build: 22631.3880
Browser: Brave (Brave 1.68.137 Chromium: 127.0.6533.100 (Official Build) (64 bit)

Actual library version I used is 0.11.1 but Version selection of issue template is not shown so selected 0.11.0.

I encountered below error when using this library at registration logic with Windows Hello.

Unable to validate attestation signature statement during attestation validation: invalid certificate chain from MDS: x509: unhandled critical extension

When I used version 0.10.2 of this library, this error never happened.
I tried using metadata.Provider feature so I guessed it is the reason.
I read some code and found the reason.

The error happens here.

if _, err = x5c.Verify(entry.MetadataStatement.Verifier()); err != nil {
return ErrInvalidAttestation.WithDetails(fmt.Sprintf("Unable to validate attestation signature statement during attestation validation: invalid certificate chain from MDS: %v", err))
}

and when I was debugging the code in x509.ParseCertificate in standard library, I reached here unhandled = true
https://github.com/golang/go/blob/72735094660a475a69050b7368c56b25346f5406/src/crypto/x509/parser.go#L691

out.DNSNames, out.EmailAddresses, out.IPAddresses, out.URIs, err = parseSANExtension(e.Value)
if err != nil {
    return err
}

if len(out.DNSNames) == 0 && len(out.EmailAddresses) == 0 && len(out.IPAddresses) == 0 && len(out.URIs) == 0 {
    // If we didn't parse anything then we do the critical check, below.
    unhandled = true
}

after that, reaches here
https://github.com/golang/go/blob/72735094660a475a69050b7368c56b25346f5406/src/crypto/x509/parser.go#L819-L821

if e.Critical && unhandled {
      out.UnhandledCriticalExtensions = append(out.UnhandledCriticalExtensions, e.Id)
}

and then in x5c.Verify finally reaches here
https://github.com/golang/go/blob/72735094660a475a69050b7368c56b25346f5406/src/crypto/x509/verify.go#L564-L566

if len(c.UnhandledCriticalExtensions) > 0 {
    return UnhandledCriticalExtension{}
}

parseSANExtension in standard library is here.
https://github.com/golang/go/blob/72735094660a475a69050b7368c56b25346f5406/src/crypto/x509/parser.go#L374-L417

func parseSANExtension(der cryptobyte.String) (dnsNames, emailAddresses []string, ipAddresses []net.IP, uris []*url.URL, err error) {
	err = forEachSAN(der, func(tag int, data []byte) error {
		switch tag {
		case nameTypeEmail:
			email := string(data)
			if err := isIA5String(email); err != nil {
				return errors.New("x509: SAN rfc822Name is malformed")
			}
			emailAddresses = append(emailAddresses, email)
		case nameTypeDNS:
			name := string(data)
			if err := isIA5String(name); err != nil {
				return errors.New("x509: SAN dNSName is malformed")
			}
			dnsNames = append(dnsNames, string(name))
		case nameTypeURI:
			uriStr := string(data)
			if err := isIA5String(uriStr); err != nil {
				return errors.New("x509: SAN uniformResourceIdentifier is malformed")
			}
			uri, err := url.Parse(uriStr)
			if err != nil {
				return fmt.Errorf("x509: cannot parse URI %q: %s", uriStr, err)
			}
			if len(uri.Host) > 0 {
				if _, ok := domainToReverseLabels(uri.Host); !ok {
					return fmt.Errorf("x509: cannot parse URI %q: invalid domain", uriStr)
				}
			}
			uris = append(uris, uri)
		case nameTypeIP:
			switch len(data) {
			case net.IPv4len, net.IPv6len:
				ipAddresses = append(ipAddresses, data)
			default:
				return errors.New("x509: cannot parse IP address of length " + strconv.Itoa(len(data)))
			}
		}

		return nil
	})

	return
}

By the way, at attestation_tpm.go, custom certificate extension validation logic exists like below.

for _, ext := range aikCert.Extensions {
if ext.Id.Equal([]int{2, 5, 29, 17}) {
manufacturer, model, version, err = parseSANExtension(ext.Value)
if err != nil {
return "", nil, err
}
}
}

and custom parseSANExtension which is not compatible with standard library
func parseSANExtension(value []byte) (manufacturer string, model string, version string, err error) {
err = forEachSAN(value, func(tag int, data []byte) error {
switch tag {
case nameTypeDN:
tpmDeviceAttributes := pkix.RDNSequence{}
_, err := asn1.Unmarshal(data, &tpmDeviceAttributes)
if err != nil {
return err
}
for _, rdn := range tpmDeviceAttributes {
if len(rdn) == 0 {
continue
}
for _, atv := range rdn {
value, ok := atv.Value.(string)
if !ok {
continue
}
if atv.Type.Equal(tcgAtTpmManufacturer) {
manufacturer = strings.TrimPrefix(value, "id:")
}
if atv.Type.Equal(tcgAtTpmModel) {
model = value
}
if atv.Type.Equal(tcgAtTpmVersion) {
version = strings.TrimPrefix(value, "id:")
}
}
}
}
return nil
})
return
}

that's why this error happens.

Reproduction

static/register.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Bug Test</title>
</head>
<body>
    <script type="module">
        
        const base64ToUint8Array = (base64) =>{
            const base64Characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
            //const base64URLCharacters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';

            let cleanedBase64 = String(base64).replace(/-/g, '+').replace(/_/g, '/');
            const padding = (4 - (cleanedBase64.length % 4)) % 4;
            cleanedBase64 += '='.repeat(padding);

            const rawLength = cleanedBase64.length;
            const decodedLength = (rawLength * 3) / 4 - padding;

            const uint8Array = new Uint8Array(decodedLength);

            let byteIndex = 0;
            for (let i = 0; i < rawLength; i += 4) {
                const encoded1 = base64Characters.indexOf(cleanedBase64[i]);
                const encoded2 = base64Characters.indexOf(cleanedBase64[i + 1]);
                const encoded3 = base64Characters.indexOf(cleanedBase64[i + 2]);
                const encoded4 = base64Characters.indexOf(cleanedBase64[i + 3]);

                const decoded1 = (encoded1 << 2) | (encoded2 >> 4);
                const decoded2 = ((encoded2 & 15) << 4) | (encoded3 >> 2);
                const decoded3 = ((encoded3 & 3) << 6) | encoded4;

                uint8Array[byteIndex++] = decoded1;
                if (encoded3 !== 64) uint8Array[byteIndex++] = decoded2;
                if (encoded4 !== 64) uint8Array[byteIndex++] = decoded3;
            }

            return uint8Array;
        }

        const Uint8ArrayToBase64 = (uint8Array) =>{
            //const base64Characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
            const base64URLCharacters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
            const base64Characters = base64URLCharacters;

            let base64 = '';
            const { length } = uint8Array;

            for (let i = 0; i < length; i += 3) {
                const byte1 = uint8Array[i];
                const byte2 = uint8Array[i + 1];
                const byte3 = uint8Array[i + 2];

                const encoded1 = byte1 >> 2;
                const encoded2 = ((byte1 & 3) << 4) | (byte2 >> 4);
                const encoded3 = ((byte2 & 15) << 2) | (byte3 >> 6);
                const encoded4 = byte3 & 63;

                base64 += base64Characters[encoded1] + base64Characters[encoded2];
                base64 += byte2 !== undefined ? base64Characters[encoded3] : '=';
                base64 += byte3 !== undefined ? base64Characters[encoded4] : '=';
            }

            return base64;
        }

        const j = await fetch("/register").then(r => r.json());
        j.publicKey.challenge = base64ToUint8Array(j.publicKey.challenge).buffer;
        j.publicKey.user.id = base64ToUint8Array(j.publicKey.user.id).buffer;
        console.log(j);
        const r = await navigator.credentials.create({
            publicKey: {
                ...j.publicKey,
               attestation: "direct" 
            }
        });
        console.log(r);
        //r.rawId = Uint8ArrayToBase64(new Uint8Array(r.rawId))
        const js =JSON.stringify({
            id: r.id,
            rawId: Uint8ArrayToBase64(new Uint8Array(r.rawId)),
            response: {
                clientDataJSON: Uint8ArrayToBase64(new Uint8Array(r.response.clientDataJSON)),
                attestationObject: Uint8ArrayToBase64(new Uint8Array(r.response.attestationObject))
            },
            type: r.type
        })
        console.log(js);
        await fetch("/verify", {
            method: "POST",
            headers: {
                "Content-Type": "application/json"
            },
            body: js
        });


    </script>
</body>
</html>

main.go

package main

import (
	_ "embed"
	"encoding/base64"
	"encoding/json"
	"log"
	"net/http"

	"github.com/go-webauthn/webauthn/metadata/providers/cached"
	"github.com/go-webauthn/webauthn/protocol"
	"github.com/go-webauthn/webauthn/webauthn"
)

type user struct{}

func (u *user) WebAuthnID() []byte {
	return []byte("test")
}

func (u *user) WebAuthnName() string {
	return "test"
}

func (u *user) WebAuthnDisplayName() string {
	return "test"
}

func (u *user) WebAuthnIcon() string {
	return ""
}

func (u *user) WebAuthnCredentials() []webauthn.Credential {
	return nil
}

var _ webauthn.User = &user{}

func marshal(v interface{}) string {
	b, err := json.Marshal(v)
	if err != nil {
		panic(err)
	}
	return string(b)
}

func marshalAndBase64(v interface{}) string {
	return base64.StdEncoding.EncodeToString([]byte(marshal(v)))
}

func unmarshal(s string, v interface{}) error {
	return json.Unmarshal([]byte(s), v)
}

func unmarshalFromBase64(s string, v interface{}) error {
	b, err := base64.StdEncoding.DecodeString(s)
	if err != nil {
		return err
	}
	return unmarshal(string(b), v)
}

//go:embed static/register.html
var registerHTML string

func main() {
	v, err := cached.New(cached.WithPath("metadata.json"))
	if err != nil {
		log.Fatal("metadata.json error:", err)
	}
	a, err := webauthn.New(&webauthn.Config{
		RPID: "localhost",
		RPOrigins: []string{
			"http://localhost:8081",
		},
		RPDisplayName: "localhost",
		MDS:           v,
	})
	if err != nil {
		log.Fatal("webauthn error:", err)
	}
	http.HandleFunc("/register", func(w http.ResponseWriter, r *http.Request) {
		creation, session, err := a.BeginRegistration(&user{}, webauthn.WithAttestationFormats([]protocol.AttestationFormat{
			protocol.AttestationFormatTPM,
		}))
		if err != nil {
			log.Println(err)
			return
		}
		http.SetCookie(w, &http.Cookie{
			Name:     "session",
			Value:    marshalAndBase64(session),
			SameSite: http.SameSiteStrictMode,
		})
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(marshal(creation)))
	})

	http.HandleFunc("/verify", func(w http.ResponseWriter, r *http.Request) {
		session := &webauthn.SessionData{}
		cookie, err := r.Cookie("session")
		if err != nil {
			log.Println(err)
			return
		}
		err = unmarshalFromBase64(cookie.Value, session)
		if err != nil {
			log.Println(err)
			return
		}
		attestation, err := a.FinishRegistration(&user{}, *session, r)
		if err != nil {
			log.Println(err) // error will be printed from here
			return
		}
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(marshal(attestation)))
	})

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "text/html")
		w.Write([]byte(registerHTML))
	})
        
        log.Println("listening on localhost:8081")
	log.Fatal(http.ListenAndServe("localhost:8081", nil))
}

(sorry, the code may be bit dirty)

  1. paste above code into main.go and static/register.html
  2. go mod init if at the first time
  3. go run .
  4. then access to localhost:8081 and do WebAuthn registration
  5. the error will be printed on the terminal

Expectations

Currently, I would skip this check by using metadata/providers/memory.WithValidateTrustAnchor(false) option.
But I think this additional validation feature is maybe unnecessary because custom trust anchor verification logics looks already implemented for each attestation type. Otherwise I think verification features should be merged in different way.
(note that this is only my opinion from my narrow (not understanding all of features of this library and not perfectly understanding certificate verification) perspective)

Documentation

No response

Project Takeover Tasks

Description

The following information is to be used for the purposes of keeping track of issues closed in the migration to this repository from the previous. I will be sifting through them as I get time and creating relevant issues as necessary and/or contact users who made PR's. Anyone is welcome to do the same or reply if they know the status of an issue (some were already fixed).

Use Case

Ensuring the future of the project migration is seamless.

Documentation

PR's

Issues

Verify topOrigin

Description

The latest draft discusses verifying topOrigin along with origin. Right now this library only validates origin. Maybe an RPTopOrigins field could be added to Config, though this means the default would be to not allow any topOrigin value which might not be backwards-compatible. It also wouldn't be easy for an RP to allow any topOrigin value. Maybe instead it could be some sort of enum like AllowAll, AllowRPOrigins, AllowNone, but then you couldn't specify third-party ones.

Use Case

Our particular use-case is that we don't allow any topOrigin value, but longer-term we might want to allow our top-level domain as a valid topOrigin.

Documentation

See w3c/webauthn#1891 and https://w3c.github.io/webauthn/#sctn-validating-origin.

metadata: Make unmarshalMDSBLOB public/exported

Description

Hello!

I was looking at adding bits of the metadata package to my service for showing names & descriptions of registered authenticators (via aaguid). As far as I know the correct way for doing this is with the FIDO MDS service which is usable via the metadata package with the ProductionMDSURL const and the PopulateMetadata function.

However the options to the latter are few, it uses a hardcoded http.Client followed by a io.Readall (which is generally something I'd recommend against).

It looks like there is a private function that could be exposed to allow more uses cases, that function being unmarshalMDSBLOB for use cases I've listed below.

PS: I don't mind submitting a MR for this as it's a relatively easy change. Or perhaps there is another reason this functionality wasn't already exposed in the metadata package? I searched existing/previous issues and didn't see anything on this.

Thank you!

Use Case

  1. For development, caching the blob locally instead of reading it remotely each time would save on network traffic.
  2. The ParseMetadata function loads the data into a global variable in an unsafe way (mostly concurrency issues). Perhaps the client would like to parse/load some or all of the data into state stored elsewhere.
  3. unmarshalMDSBLOB doesn't appear to use the http.Client it is being passed. Plus if it were exposed the blob could be retrieved using a custom http.Client or other method (before being passed to the unmarhal function).

Documentation

No response

Windows Hello - Mismatch between ECCParameters in pubArea and credentialPublicKey

Version

0.9.4

Description

Attestation bug with Direct & TPM on Windows Hello (Windows 11 22H2).
Searching found it was reported in another library too:
MasterKale/SimpleWebAuthn#238

Reproduction

Trying to enroll Windows 11 22H2 Hello authenticator with
Attestation=Direct
AuthenticatorType=Platform(TPM)

It ends in "Mismatch between ECCParameters in pubArea and credentialPublicKey" error but it should work.

Expectations

Should enroll the device successfully.

Documentation

No response

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

This repository currently has no open or pending branches.

Detected dependencies

gomod
go.mod
  • go 1.23
  • go 1.23.0
  • github.com/fxamacker/cbor/v2 v2.7.0
  • github.com/go-webauthn/x v0.1.14
  • github.com/golang-jwt/jwt/v5 v5.2.1
  • github.com/google/go-tpm v0.9.1
  • github.com/google/uuid v1.6.0
  • github.com/mitchellh/mapstructure v1.5.0
  • github.com/stretchr/testify v1.9.0
  • golang.org/x/crypto v0.26.0

  • Check this box to trigger a request for Renovate to run again on this repository

ValidateDiscoverableLogin hides handler error

Version

0.8.2

Description

The ValidateDiscoverableLogin method masks the underlying error message returned by DiscoverableUserHandler, which makes troubleshooting harder. Please consider methods like wrapping the error to allow the source problem to be exposed.

Reproduction

Run the following

code
package main

import (
	"fmt"
	"log"

	"github.com/go-webauthn/webauthn/protocol"
	"github.com/go-webauthn/webauthn/webauthn"
)

func main() {
	wconfig := &webauthn.Config{
		RPDisplayName: "Go Webauthn",                               // Display Name for your site
		RPID:          "go-webauthn.local",                         // Generally the FQDN for your site
		RPOrigins:     []string{"https://login.go-webauthn.local"}, // The origin URLs allowed for WebAuthn requests
	}

	w, err := webauthn.New(wconfig)
	if err != nil {
		log.Fatal(err)
	}

	handler := func(rawID, userHandle []byte) (webauthn.User, error) {
		return nil, fmt.Errorf("Oops, bad things happened")
	}

	_, err = w.ValidateDiscoverableLogin(
		handler,
		webauthn.SessionData{},
		&protocol.ParsedCredentialAssertionData{Response: protocol.ParsedAssertionResponse{UserHandle: []byte{1, 2, 3}}},
	)
	log.Println(err)
}

type user struct {
	id                []byte
	displayName, name string
	credentials       []webauthn.Credential
}

func (u user) WebAuthnID() []byte {
	return u.id
}

func (u user) WebAuthnName() string {
	return u.name
}

func (u user) WebAuthnDisplayName() string {
	return u.displayName
}

func (u user) WebAuthnCredentials() []webauthn.Credential {
	return u.credentials
}

func (u user) WebAuthnIcon() string {
	return ""
}

The terminal will print something like

2023/05/29 12:48:41 Failed to lookup Client-side Discoverable Credential

which does not reveal the cause of the error.

Expectations

Maybe wrapping the error like

Failed to lookup Client-side Discoverable Credential: Oops, bad things happened

Documentation

No response

Attestation verification through trust anchor

Description

Hello all.

After studying a bit the library and stumbling in this piece of comment, I would like to open a discussion on how could be an interesting way of supporting a trust verification using the library.
My understanding is that up to this point, the library does not export any facility for doing trust assessments on the embedded attestation certificates. I couldn't also find a convenient way through a public method to extract the embedded certificates without having to copy the whole procedure of attestation-object protocol decoding.
I'm opening this feature-request to trigger a discussion around how that could be accomplished. Here are some options that come to my mind, any others would be welcomed:

  1. Implement in the library the possibility to provide a list of trusted root certificates against which the attestation certificate could be verified.
  2. Implement in the library something like ExportAttestationCertificates method that would delegate to the client the responsibility to implement the chain-of-trust verification.
  3. Document if there is an existing mechanism to achieve that.

What are your thoughts on that? I would be happy to try to help wherever possible with some code-contributions. 😃

Thank you in advance for your work and support,
Rodrigo

Use Case

Some types of webauthn attestation verification would require verification against an RP policy. This verification is done by verifying the embedded attestation certificate against an RP-trusted set of root certificates (otherwise called a trust store).
Use cases:

  • Allow RP to filter which kinds/types of authenticators to allow. Some RPs might only want to support apple devices. Others only Yubico, and so on and so forth.
  • Check the validity and trustworthiness of the provided attestation-certificate

Documentation

No response

Credential struct should have json struct tags

Description

It seems one has to persist credentials. One easy way to do so would be to store them as JSON. But corresponding JSON is ugly because Credential does not have json struct tags.

Use Case

No response

Documentation

No response

UserDisplayName of SessionData is unused

Version

0.8.2

Description

The UserDisplayName field in the SessionData struct is never referenced in the package.
I suppose it is designed to store the user information in the methods like BeginRegistration()?
Maybe consider copying from User.WebAuthnDisplayName() or remove this field to avoid confusion.

Reproduction

Run the following code

code
package main

import (
	"log"

	"github.com/go-webauthn/webauthn/webauthn"
)

func main() {
	wconfig := &webauthn.Config{
		RPDisplayName: "Go Webauthn",                               // Display Name for your site
		RPID:          "go-webauthn.local",                         // Generally the FQDN for your site
		RPOrigins:     []string{"https://login.go-webauthn.local"}, // The origin URLs allowed for WebAuthn requests
	}

	w, err := webauthn.New(wconfig)
	if err != nil {
		log.Fatal(err)
	}

	u := user{id: []byte{1, 2, 3}, displayName: "Go Webauthn", name: "webauthn"}
	_, session, err := w.BeginRegistration(u)
	if err != nil {
		log.Fatal(err)
	}

	log.Println("display name: ", session.UserDisplayName)
}

type user struct {
	id                []byte
	displayName, name string
	credentials       []webauthn.Credential
}

func (u user) WebAuthnID() []byte {
	return u.id
}

func (u user) WebAuthnName() string {
	return u.name
}

func (u user) WebAuthnDisplayName() string {
	return u.displayName
}

func (u user) WebAuthnCredentials() []webauthn.Credential {
	return u.credentials
}

func (u user) WebAuthnIcon() string {
	return ""
}

The terminal prints

2023/05/29 11:48:07 display name:  

Expectations

I would expect

2023/05/29 11:48:07 display name:  Go Webauthn

Documentation

No response

UserEntity.DisplayName should not have omitempty

Version

0.9.1

Description

Its value’s name, displayName and id members are REQUIRED.

but when a user is registering for the first time the RP might not require the user to enter their name and the displayName might be empty, which would then get omitted and cause an error in JavaScript when calling navigator.credentials.create.

Reproduction

  1. Create a user with an empty displayName
  2. Pass user to wa.BeginRegistration.
  3. Encode the result as JSON
  4. Pass to navigator.credentials.create

Expectations

Expectation is that the resulting JSON would contain displayName: '' but instead displayName is undefined.

Documentation

https://w3c.github.io/webauthn/#dom-publickeycredentialcreationoptions-user

Direct Extensions Support

Description

Implement all of the extensions directly in the library including all validations. It would be nice to allow backwards compat and a low level implementation as this is an evolving area, however I don't believe that either of these ideas should be a critical requirement.

Use Case

No response

Documentation

package protocol

// AppIDExtensionsClientInputs is the input parameters for the appid extension.
//
// This extension allows WebAuthn Relying Parties that have previously registered a credential using the legacy FIDO U2F
// JavaScript API FIDOU2FJavaScriptAPI to request an assertion. The FIDO APIs use an alternative identifier for Relying
// Parties called an AppID FIDO-APPID, and any credentials created using those APIs will be scoped to that identifier.
// Without this extension, they would need to be re-registered in order to be scoped to an RP ID.
//
// Stages: Authentication
//
// Specification: §10.2. FIDO AppID Extension (https://www.w3.org/TR/webauthn/#sctn-appid-extension)
type AppIDExtensionsClientInputs struct {
	AppID string `json:"appid,omitempty"`
}

// AppIDExtensionsClientOutputs is the output parameters for the appid extension.
//
// This extension allows WebAuthn Relying Parties that have previously registered a credential using the legacy FIDO U2F
// JavaScript API FIDOU2FJavaScriptAPI to request an assertion. The FIDO APIs use an alternative identifier for Relying
// Parties called an AppID FIDO-APPID, and any credentials created using those APIs will be scoped to that identifier.
// Without this extension, they would need to be re-registered in order to be scoped to an RP ID.
//
// Stages: Authentication
//
// Specification: §10.2. FIDO AppID Extension (https://www.w3.org/TR/webauthn/#sctn-appid-extension)
type AppIDExtensionsClientOutputs struct {
	AppID bool `json:"appid"`
}

// AppIDExcludeExtensionsClientInputs is the input parameters for the appidExclude extension.
//
// This registration extension allows WebAuthn Relying Parties to exclude authenticators that contain specified
// credentials that were created with the legacy FIDO U2F JavaScript API FIDOU2FJavaScriptAPI.
//
// Stages: Registration
//
// Specification: §10.2. FIDO AppID Exclusion Extension (https://www.w3.org/TR/webauthn/#sctn-appid-exclude-extension)
type AppIDExcludeExtensionsClientInputs struct {
	AppID string `json:"appidExclude,omitempty"`
}

// AppIDExcludeExtensionsClientOutputs is the output parameters for the appidExclude extension.
//
// This registration extension allows WebAuthn Relying Parties to exclude authenticators that contain specified
// credentials that were created with the legacy FIDO U2F JavaScript API FIDOU2FJavaScriptAPI.
//
// Stages: Registration
//
// Specification: §10.2. FIDO AppID Exclusion Extension (https://www.w3.org/TR/webauthn/#sctn-appid-exclude-extension)
type AppIDExcludeExtensionsClientOutputs struct {
	AppID bool `json:"appidExclude"`
}

// UVMClientInputs is the input parameters for the uvm extension.
//
// This extension enables use of a user verification method.
//
// Stages: Registration, Authentication
//
// Specification: §10.3. User Verification Method Extension (https://www.w3.org/TR/webauthn/#sctn-uvm-extension)
type UVMClientInputs struct {
	UVM bool `json:"uvm"`
}

// UVMClientOutputs is the input parameters for the uvm extension.
//
// This extension enables use of a user verification method.
//
// TODO: Investigation of the CBOR structure.
//
// Stages: Registration, Authentication
//
// Specification: §10.3. User Verification Method Extension (https://www.w3.org/TR/webauthn/#sctn-uvm-extension)
type UVMClientOutputs struct {
	UVM [][]uint `json:"uvm"`
}

// CredentialPropertiesClientInputs is the input parameters for the credProps extension.
//
// This client registration extension facilitates reporting certain credential properties known by the client to the
// requesting WebAuthn Relying Party upon creation of a public key credential source as a result of a registration
// ceremony.
//
// Stages: Registration
//
// Specification: §10.4. Credential Properties Extension (https://www.w3.org/TR/webauthn/#sctn-authenticator-credential-properties-extension)
type CredentialPropertiesClientInputs struct {
	CredentialProperties bool `json:"credProps"`
}

// CredentialPropertiesClientOutputs is the output parameters for the credProps extension.
//
// This client registration extension facilitates reporting certain credential properties known by the client to the
// requesting WebAuthn Relying Party upon creation of a public key credential source as a result of a registration
// ceremony.
//
// Stages: Registration
//
// Specification: §10.4. Credential Properties Extension (https://www.w3.org/TR/webauthn/#sctn-authenticator-credential-properties-extension)
type CredentialPropertiesClientOutputs struct {
	ClientSideDiscoverableCredential bool `json:"rk"`
}

// LargeBlobSupport represents the IDL of the same name.
//
// Specification: §10.5. Large blob storage extension (https://www.w3.org/TR/webauthn/#enumdef-largeblobsupport)
type LargeBlobSupport string

const (
	LargeBlobSupportRequired  LargeBlobSupport = "required"
	LargeBlobSupportPreferred LargeBlobSupport = "preferred"
)

// LargeBlobStorageClientRegistrationInputs is the input parameters for the largeBlob extension.
//
// This client registration extension and authentication extension allows a Relying Party to store opaque data
// associated with a credential. Since authenticators can only store small amounts of data, and most Relying Parties are
// online services that can store arbitrary amounts of state for a user, this is only useful in specific cases. For
// example, the Relying Party might wish to issue certificates rather than run a centralised authentication service.
//
// Stages: Registration, Authentication
//
// Specification: §10.5. Large blob storage extension (https://www.w3.org/TR/webauthn/#sctn-authenticator-credential-properties-extension)
type LargeBlobStorageClientRegistrationInputs struct {
	LargeBlob RegistrationExtensionsLargeBlobInputs `json:"largeBlob"`
}

// LargeBlobStorageClientAuthenticationInputs is the input parameters for the largeBlob extension.
//
// This client registration extension and authentication extension allows a Relying Party to store opaque data
// associated with a credential. Since authenticators can only store small amounts of data, and most Relying Parties are
// online services that can store arbitrary amounts of state for a user, this is only useful in specific cases. For
// example, the Relying Party might wish to issue certificates rather than run a centralised authentication service.
//
// Stages: Authentication
//
// Specification: §10.5. Large blob storage extension (https://www.w3.org/TR/webauthn/#sctn-authenticator-credential-properties-extension)
type LargeBlobStorageClientAuthenticationInputs struct {
	LargeBlob AuthenticationExtensionsLargeBlobInputs `json:"largeBlob"`
}

type RegistrationExtensionsLargeBlobInputs struct {
	Support LargeBlobSupport `json:"support"`
}

type AuthenticationExtensionsLargeBlobInputs struct {
	Read bool             `json:"read"`
	Data URLEncodedBase64 `json:"write,omitempty"`
}

// LargeBlobStorageClientRegistrationOutputs is the output parameters for the largeBlob extension.
//
// This client registration extension and authentication extension allows a Relying Party to store opaque data
// associated with a credential. Since authenticators can only store small amounts of data, and most Relying Parties are
// online services that can store arbitrary amounts of state for a user, this is only useful in specific cases. For
// example, the Relying Party might wish to issue certificates rather than run a centralised authentication service.
//
// Stages: Registration
//
// Specification: §10.5. Large blob storage extension (https://www.w3.org/TR/webauthn/#sctn-authenticator-credential-properties-extension)
type LargeBlobStorageClientRegistrationOutputs struct {
	LargeBlob RegistrationExtensionsLargeBlobOutputs `json:"largeBlob"`
}

// LargeBlobStorageClientAuthenticationOutputs is the output parameters for the largeBlob extension.
//
// This client registration extension and authentication extension allows a Relying Party to store opaque data
// associated with a credential. Since authenticators can only store small amounts of data, and most Relying Parties are
// online services that can store arbitrary amounts of state for a user, this is only useful in specific cases. For
// example, the Relying Party might wish to issue certificates rather than run a centralised authentication service.
//
// Stages: Registration, Authentication
//
// Specification: §10.5. Large blob storage extension (https://www.w3.org/TR/webauthn/#sctn-authenticator-credential-properties-extension)
type LargeBlobStorageClientAuthenticationOutputs struct {
	LargeBlob AuthenticationExtensionsLargeBlobOutputs `json:"largeBlob"`
}

type RegistrationExtensionsLargeBlobOutputs struct {
	Support bool `json:"supported"`
}

type AuthenticationExtensionsLargeBlobOutputs struct {
	Written bool             `json:"written"`
	Data    URLEncodedBase64 `json:"blob,omitempty"`
}

Inconsistent base64 encoding/decoding via JSON

Version

0.3.1

Description

The protocol.CredentialCreation object returned by webauthn.BeginRegistration uses the WebauthnID method from the webauthn.User interface to get the Id of the user.

If this object is serialized via the encoding/json module, the field gets serialized as base64 via base64.StdEncoding.

Later, after registration, if the credential is sent back as part of the login process, it will be decoded as the UserHandle portion of a AuthenticatorAssertionResponse, which has the type defined as URLEncodedBase64, which defines a custom json marshaller which builds on base64.RawURLEncoding.

Since these two base64 encoding aren't compatible, parsing the assertion fails and login can't proceed, specifically generating the error "Parse error for Assertion".

Reproduction

This issue can be reproduced by registering a new credential, and returning the result of BeginRegistration directly encoded as json. Finish the registration without changing or parsing the Id on the client side.

Attempt to use the credential without changing the UserHandle field after deserialzing via the standard json encoder.

Expectations

The expected behavior is that the WebauthnID as returned from the interface method would be able to make a roundtrip in this fashion without needed modification on the server or client side.

Documentation

Updating the locations that reference []byte to use URLEncodedBase64 should do it, but there might be backwards compatibility concerns?

WebAuthnID() []byte

ID []byte `json:"id"`

UserHandle URLEncodedBase64 `json:"userHandle,omitempty"`

return nil, ErrBadRequest.WithDetails("Parse error for Assertion")

Level Implementations

Description

Convert the specs into a feature matrix and advertise library features. I consider it important that we properly support the spec and document the library itself and provide adequate documentation in the user-facing portion of the library.

Use Case

No response

Documentation

Specifications:

Diffs:

Api design issue

Version

0.8.2

Description

Hi,

I don't understand why the user is required to BeginLogin and BeginRegistration to me in that situation, we don't have enough elements to know who the user is

Reproduction

Look at BeginLogin and BeginRegistration functions

Expectations

I would expect to be able to use BeginLogin and BeginRegistration without having to provide a user as what matters in that case is the challenge and RPID/RPOrigins for the credendial manager to be able to create the correct key.

Maybe it makes sense for registration as the user can provide personal information like email at this point of time. But for login, it forces the user to enter his email while he could only press a button and being logged in right away

Documentation

No response

Implement live comprehensive example

Description

It would be good as some users have mentioned to produce a live example that's published showcasing not only what this library is capable of but also what webauthn is capable of. This would help users implement the library, and show what options are available.

The following things can be showcased:

  • Simple email/username and password registration/login for demonstration purposes
  • Webauthn Registration
    • Standard Registration (non-discoverable)
    • Discoverable Registration
    • Identityless/Userless Registration i.e. via a discoverable credential which links to an account UUID/GUID
    • Support for all of the standard registration options:
      • Attachment
      • Discoverable / Resident Key / Require Resident Key
      • User Verification
      • Conveyancing Preference
  • Webauthn Authentication
    • "True" passwordless logins (no username required) which also supports usernameless login
    • 2FA demonstration

Implementation Specifics (all ideas at this point):

  • React (via go embed)
  • Database
    • Postgres?
  • Hosting:
    • I can probably self host it in the cloud but it'd be good to find some form of sponsor for this which allows a distributed model maybe

Use Case

No response

Documentation

#47

Attestation with full attestation from authenticator that does not support full attestation

Version

0.8.2

Description

Doing an enrollment with a Windows Hello Authenticator device with "direct" attestation throws this error that happens at attestation.go:187.

This happens because the MetatadataStatement for Windows Hello Authenticator (aaguid 08987058-cadc-4b81-b6e1-30de50dcbe96) doesn't have "basic_full" in the attestationTypes array.

"attestationTypes": [
"attca"
]

Is this a correct behavior?

Reproduction

Enroll a device with:
AttestationType "direct"
AuthenticatorType "platform"
Authenticator: "Windows Hello Authenticator"

Expectations

The device should be enrolled succesfully.

Documentation

No response

RP ID should be optional

Version

0.8.4

Description

Hi,

It seems like not passing RP ID is invalid, but it should be valid. In case we don't set the RP ID in the options, it will use the current domain. This will also make it usable in localhost, for example.

By default, the RP ID for a WebAuthn operation is set to the caller’s origin's effective domain.

Reproduction

Try to not pass RP ID

Expectations

No response

Documentation

No response

metadata: Cached provider codepath may close file before attempting to truncate it

Version

0.11.0

Description

Hello again,

As mentioned in #247 I've updated to v0.11 and had been using the latest changes to the metadata service with the provided cache provider. It was working well until today when the service was restarted, at which point it attempted to update the mds blob.

It kept failing with truncate <file name>: file already closed

I noticed here that the decoder when passed the file is closing the file when it is finished. However just a few lines below, if the data is out of date and a file needs updating, then doTruncateCopyAndSeekStart is called which attempts to truncate the file that the decoder has already closed.

I tried passing the WithForceUpdate true option as a workaround but that fails with "invalid argument", I suspect this is because the file was Opened with READ only, and truncate is a write-level permission.

I think the suggested fix would be to not defer close the passed readcloser in the decoder (perhaps just pass it as a reader), and also open the file with OpenFile passing RW flags rather than just R flags. I can submit a PR if that helps.

Reproduction

Open an outdated MDS blob with the cached provider, default settings should reproduce it. For reference here is my abridged constructor, I pass a NewFunc because I need to be able to configure PermitZeroAAGUID to the underlying memory provider

wconf.MDS, err = cached.New(
			cached.WithClient(common.CleanHTTP()),
			cached.WithPath(conf.MDSPath),
			cached.WithNew(providerFunc(conf.MDSPermitZeroAAGUID)),
		)

// providerFunc
func providerFunc(permitZeroAAGUID bool) cached.NewFunc {
	// This is the same as the default in webauthn/metadata/providers/cached
	// but with a configurable permit zero.
	return func(mds *metadata.Metadata) (metadata.Provider, error) {
		return memory.New(
			memory.WithMetadata(mds.ToMap()),
			memory.WithValidateEntry(true),
			memory.WithValidateEntryPermitZeroAAGUID(permitZeroAAGUID),
			memory.WithValidateTrustAnchor(true),
			memory.WithValidateStatus(true),
		)
	}
}

Expectations

No response

Documentation

No response

Response timeout - unclear code

Version

0.9.2

Description

I am reading code in BeginRegistration and I wonder why such code:

	if creation.Response.Timeout == 0 {
		switch {
		case creation.Response.AuthenticatorSelection.UserVerification == protocol.VerificationDiscouraged:
			creation.Response.Timeout = int(webauthn.Config.Timeouts.Registration.Timeout.Milliseconds())
		default:
			creation.Response.Timeout = int(webauthn.Config.Timeouts.Registration.Timeout.Milliseconds())
		}
	}

Shouldn't the first case be creation.Response.Timeout = int(webauthn.Config.Timeouts.Registration.TimeoutUVD.Milliseconds())?

Documentation

It says like that in documentation fro TimeoutConfig:

// TimeoutConfig represents the WebAuthn timeouts configuration for either registration or login..
type TimeoutConfig struct {
	// Enforce the timeouts at the Relying Party / Server. This means if enabled and the user takes too long that even
	// if the browser does not enforce the timeout the Relying Party / Server will.
	Enforce bool

	// Timeout is the timeout for logins/registrations when the UserVerificationRequirement is set to anything other
	// than discouraged.
	Timeout time.Duration

	// TimeoutUVD is the timeout for logins/registrations when the UserVerificationRequirement is set to discouraged.
	TimeoutUVD time.Duration
}

Secure Payment Confirmation Support

Description

Adding support for secure payment confirmation would enable developers who already use this library for webauthn to facilitate and validate secure payment confirmation payloads without needing to migrate. Since registration is unaffected only new methods need to be added to support the payment (authentication) flow.

Use Case

Using WebAuthn and SPC to authenticate payments on Chrome and other supported browsers.

Documentation

Secure payment confirmation spec: https://www.w3.org/TR/secure-payment-confirmation/

Google docs: https://developer.chrome.com/articles/secure-payment-confirmation/

Add additional fields to AuthenticatorAttestationResponse

Description

So AuthenticatorAttestationResponse struct contains only minimal fields which might be sent in JSON, but it looks the spec allows additional fields (which are just extracted from attestationOjbect):

To remove the need to parse CBOR at all in many cases, getAuthenticatorData() returns the authenticator data from attestationObject. The authenticator data contains other fields that are encoded in a binary format. However, helper functions are not provided to access them because Relying Parties already need to extract those fields when getting an assertion.

The issue I have is that I send AuthenticatorAttestationResponse over the wire in JSON from the client. And client adds those fields. But on the server I want to use JSON unmarshal with DisallowUnknownFields set (primarily to detect changes in API I should check and possibly adapt to). And this is then currently not possible.

So I wonder if struct should define those additional optional fields, but not use them/ignore them?

Use Case

No response

Documentation

No response

Strip base64 padding in protocol.URLEncodedBase64

Description

(version: github.com/go-webauthn/webauthn v0.5.0)

It seems the type protocol.URLEncodedBase64 is, correctly, decoding base64 without padding. (ref) however, a frontend tool I'm using to implement webauthn seems to be adding padding to the publicKey.response.userHandle.

Now I can strip this out myself, but would it be incorrect for this library to conveniently strip padding automatically given that it's not meant to be there anyway?

Use Case

No response

Documentation

No response

PatternAccuracyDescriptor.MinComplexity needs to be uint64 not uint32

Version

0.10.1

Description

When parsing the metadata, PatternAccuracyDescriptor.MinComplexity is too small; needs to be uint64 not uint32.

Reproduction

Parsing the latest version of the metadata blob (no=80):

json: cannot unmarshal number 34359738368 into Go struct field PatternAccuracyDescriptor.metadataStatement.userVerificationDetails.paDesc.minComplexity of type uint32

some pseudo code:

import (
	webauthnMetadata "github.com/go-webauthn/webauthn/metadata"
)

...
func() whatever(j *jwt) {

	raw := j.Payload.RawVal("entries")
	var entries []webauthnMetadata.MetadataBLOBPayloadEntry
	if err := json.Unmarshal(raw, &entries); err != nil {
		return err
	}
}

Expectations

modify to uint64

MinComplexity uint32 `json:"minComplexity"`

Documentation

No response

Trailing extraneous bytes passed to `cbor.Unmarshal()`, also it may contain authenticator extension data

Version

0.8.6

Description

This issue has a public test from August 31, 2023 at gravitational/teleport#31322. I maintain fxamacker/cbor, my apologies for not catching some aspects of this sooner. 🙏

ParseCredentialCreationResponseBody() is called with data that causes code in webauthncbor.go to eventually pass 91 bytes to cbor.Unmarshal().

Those 91 bytes represent a CBOR Sequence (RFC 8742) instead of CBOR data item (RFC 8949).

cbor.Unmarshal() is for parsing a CBOR data item rather than CBOR Sequence (concatenation of CBOR data items).

In old versions of cbor library, the extra trailing bytes are ignored by cbor.Unmarshal() instead of returning error.

For some uses cases, ignoring trailing bytes is undesirable and can create risks. In this test case, the extraneous 14 bytes is a CBOR data item that appears to represent authenticator extension data {"credProtect": 2}.

Upgrading to fxamacker/cbor v2.5.0 makes it easier to detect and handle extraneous data:

  • cbor.Unmarshal() detects trailing bytes and returns ExtraneousDataError.
  • cbor.UnmarshalFirst() function was added to explicitly handle extraneous bytes without ExtraneousDataError.
  • cbor.DiagnoseFirst() function was added to return human readable text (Diagnostic Notation) for logging/debugging.
// UnmarshalFirst decodes first CBOR data item and returns remaining bytes.
rest, err = cbor.UnmarshalFirst(b, &v)   // decode []byte b to v

If you choose to upgrade to v2.5.0, please see ⭐ Notable Changes to Review Before Upgrading in the release notes.

My apologies again for not catching some of this sooner. Please let me know if I can help make the upgrade go smoothly.

Reproduction

See gravitational/teleport#31322 for test and reproducer.

TestIssue31187_errorParsingAttestationResponse

// TestIssue31187_errorParsingAttestationResponse reproduces the root cause of
// https://github.com/gravitational/teleport/issues/31187 by attempting to parse
// a current CCR created using a Chrome/Yubikey pair.
//
// The test exposes a poor interaction between go-webauthn/webauthn v0.8.6 and
// fxamacker/cbor/v2 v2.5.0.
func TestIssue31187_errorParsingAttestationResponse(t *testing.T) {
	// Captured from an actual Yubikey 5Ci registration request.
	const body = `{"id":"ibfM_71b4q2_xWPZDyvhZmJ_KU8f-mOCCLXHp-fTVoHZpDelym5lvBJDPr1EtD_l","type":"public-key","rawId":"ibfM_71b4q2_xWPZDyvhZmJ_KU8f-mOCCLXHp-fTVoHZpDelym5lvBJDPr1EtD_l","response":{"clientDataJSON":"eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoidEdiVFhEbzBGMXRNUVlmamRSLWNETlV1TUNvVURTX0w0OElSWmY4MUVuWSIsIm9yaWdpbiI6Imh0dHBzOi8vemFycXVvbi5kZXY6MzA4MCIsImNyb3NzT3JpZ2luIjpmYWxzZSwib3RoZXJfa2V5c19jYW5fYmVfYWRkZWRfaGVyZSI6ImRvIG5vdCBjb21wYXJlIGNsaWVudERhdGFKU09OIGFnYWluc3QgYSB0ZW1wbGF0ZS4gU2VlIGh0dHBzOi8vZ29vLmdsL3lhYlBleCJ9","attestationObject":"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjCnNjmsqMh0nu-_tuMkxVZkZShAhdoz0tK9evxg8ys9CLFAAAAAQAAAAAAAAAAAAAAAAAAAAAAMIm3zP-9W-Ktv8Vj2Q8r4WZifylPH_pjggi1x6fn01aB2aQ3pcpuZbwSQz69RLQ_5aUBAgMmIAEhWCCJt8z_vVvirb_FY9kPpoIwbfhER3VHTmOV0Y6xs7uHySJYIMFARJxlUoR4DbDzlKYnfJKitWgR3GHK9_Lz211z-128oWtjcmVkUHJvdGVjdAI"}}`

	_, err := protocol.ParseCredentialCreationResponseBody(strings.NewReader(body))
	require.NoError(t, err, "ParseCredentialCreationResponseBody failed")
}

Reproducer calls ParseCredentialCreationResponseBody with data that causes code in webauthncbor.go to eventually pass 91 bytes to cbor.Unmarshal().

Those 91 bytes represent a CBOR Sequence (RFC 8742) instead of CBOR data item (RFC 8949).

In Diagnostic Notation, first CBOR data item represents:

{1: 2, 3: -7, -1: 1, 
-2: h'89b7ccffbd5be2adbfc563d90fa682306df8444775474e6395d18eb1b3bb87c9',
-3: h'c140449c655284780db0f394a6277c92a2b56811dc61caf7f2f3db5d73fb5dbc'}

The 2nd CBOR data item (aka extraneous data in RFC 8949) represents:

{"credProtect": 2}

Expectations

Detect or prevent extraneous bytes or CBOR Sequence being passed to cbor.Unmarshal().

If needed, handle authenticator extension data currently contained in the trailing bytes.

Documentation

No response

Allow multiple RPOrigins

Description

Allow multiple RPOrigins as suggested in duo-labs/webauthn#143.

Currently only one RPOrigin can be defined, which is sufficient for most use cases. But if you have a webpage and a mobile app (Android or/and iOS) and they should share the (passkeys) credentials you can't do this right now.

Use Case

Share (passkeys) credentials between a mobile app and a webpage.

Documentation

No response

Methods should be designed to be flexible and not dictate or constrain the project’s API /Protocol definitions

Description

The problem is if you wan't to use another protocol to communicate with the server, method that require http.Request such as FinishRegistration makes it tedious to use the library.

The issue is that if you include additional data in the same request (which is my current approach), the library cannot be used according to the documentation.

Use Case

  • Sending custom data with the finish registration
  • Using other protocol than http

Documentation

credential, err := webAuthn.FinishRegistration(user, session, r)
credential, err := webAuthn.FinishLogin(user, session, r)

[API Consistency] There is no FinishDiscoverableLogin in Webauthn

Version

0.8.6

Description

While using the code, I wanted to use the discoverable credentials feature. There is a BeginDiscoverableLogin method, a ValidateDiscoverableLogin method but no FinishDiscoverableLogin method.

I was wondering if there is a reason for this. I imagine that the code would be :

// FinishDiscoverableLogin takes the response from the client and validate it against the user handler and stored session data.
func (webauthn *WebAuthn) FinishDiscoverableLogin(handler DiscoverableUserHandler, session SessionData, response *http.Request) (*Credential, error) {
	parsedResponse, err := protocol.ParseCredentialRequestResponse(response)
	if err != nil {
		return nil, err
	}

	return webauthn.ValidateDiscoverableLogin(handler, session, parsedResponse)
}

but I may miss something.

Reproduction

This is related to the API and the available methods.

Expectations

I would expect that a method FinishDiscoverableLogin would be available.

Documentation

No response

Add AttestationConveyancePreference enterprise support

Description

It would be nice if this library added support for enterprise attestation

Use Case

For enterprise deployments (where all hardware and configurations and centrally maintained) it would be nice to be able to request enterprise attestation.

Documentation

https://www.w3.org/TR/webauthn-2/#enum-attestation-convey

https://www.w3.org/TR/webauthn-2/#dom-attestationconveyancepreference-enterprise

https://groups.google.com/a/fidoalliance.org/g/fido-dev/c/TdCoQUsgFZU?pli=1

Functional option WithValidateAttestationTypes of in-memory provider sets wrong flag

Version

0.11.0

Description

func WithValidateAttestationTypes(validate bool) Option {
return func(provider *Provider) (err error) {
provider.status = validate
return nil
}

This functional option of the in-memory provider implementation should set the provider.attestation flag instead of the status flag. The status flag can be set via WithValidateStatus.

Reproduction

  1. Create an in-memory provider with the memory.WithValidateAttestationTypes(false) option.
  2. Verify an attestation.
  3. Observe, that the attestation types of the attestation are still verified against the entry of the authenticator of the attestation.

Expectations

I expect the attestation types not to be verified if I set the option WithValidateAttestationTypes to false.

Documentation

No response

Handle is nil when allowCredentials is non-empty during discoverable login

Version

0.8.6

Description

Using phone as a passkey / credential seems to give a response without a handle duing a discoverable login with allowedCredentials set.

Currently there are checks that invalidates the response if the handle is empty.

Reading about user handles in w3c github;

Discoverable credentials store this identifier and MUST return it as response.userHandle in authentication ceremonies started with an empty allowCredentials argument.

This doesn't specifically say anything about what must and musnt be provided when starting authentication ceremonies with povided allowedCredentials, however, I take it that responses with empty handle are valid if the ceremony was started with given allowedCredentials?

Reproduction

Register a phone as a discoverable credential, then try login with the credential id listed in allowedCredentials.

Expectations

No response

Documentation

w3c github about user handles

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.