From b5ecd1d3496a0bf7bfa2b7aa2912154f38a896af Mon Sep 17 00:00:00 2001 From: James Elliott Date: Mon, 25 Nov 2024 17:30:30 +1100 Subject: [PATCH] feat(webauthn): include new credential flags func This adds a new function NewCredentialFlags which is leveraged by us to derive the flags that the spec requires implementers store. The addition of this function ensures that added functionality or flags that need to be stored can relatively easily and painlessly be stored and restored by third parties. --- README.md | 59 ++++++++++++++++++++++++++++++++++++++++++ webauthn/credential.go | 28 +++++++++++++++----- 2 files changed, 81 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7296f74b..202c1775 100644 --- a/README.md +++ b/README.md @@ -313,6 +313,65 @@ table for more information. We also include JSON mappings for those that wish to | 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 | | +### Flags + +It's important to note that the recommendations and requirements for flag storage have changed over the course of the +evolution of the WebAuthn specification. We at the present time only make the flags classified like this available for +easy storage however we also make the Protocol Value available. At such a time as these recommendations or requirements +change we will adapt accordingly. The Protocol Value is a raw representation of the flags and as such is resistant to +breaking changes whereas the other flags or lack thereof may not be. + +Implementers are therefore encouraged to use +[func (CredentialFlags) ProtocolValue](https://pkg.go.dev/github.com/go-webauthn/webauthn/webauthn#CredentialFlags.ProtocolValue) +to retrieve the raw value and +[webauthn.NewCredentialFlags](https://pkg.go.dev/github.com/go-webauthn/webauthn/webauthn#NewCredentialFlags) to +restore it; and instead of using the individual flags to store the value store the Protocol Value, and only store the +individual flags as a means to perform compliance related decisions. + +#### Notable Changes + +This contains some notable changes to the flags over the life of the library. + +##### v0.11.0 + +In v0.11.0 we started validating the backup related flags to ensure that they were in a valid state as per the +requirements in the spec. This introduced issues for some users as they had not been storing them and at least at one +point the flag values were difficult to obtain. + +This has lead to an effective breaking change and a state where some credentials cannot be validated. The resolution to +this particular issue is to adapt current storage methods so that the values of the flags or each individual flag default +to a null-like value and manually perform an update to the storage and struct when a credential with null-like values is +observed. + +The values can be obtained prior to validating the parsed response similar to the example below: + +```go +package example + +import ( + "net/http" + + "github.com/go-webauthn/webauthn/protocol" + "github.com/go-webauthn/webauthn/webauthn" +) + +func FinishLogin(w http.ResponseWriter, r *http.Request) { + // Abstract Business Logic: Get the WebAuthn User. + user := datastore.GetUser() + + // Abstract Business Logic: Get the WebAuthn Session Data. + session := datastore.GetSession() + + parsedResponse, err := protocol.ParseCredentialRequestResponse(r) + if err != nil { + // Handle Error and return. + return + } + + // Handle updating the appropriate credential using the flags value. + flags := webauthn.NewCredentialFlags(parsedResponse.Response.AuthenticatorData.Flags) +} +``` ### Storage It is also important to note that restoring the [webauthn.Credential] with the correct values will likely affect the diff --git a/webauthn/credential.go b/webauthn/credential.go index 19e45f94..51b4fa4d 100644 --- a/webauthn/credential.go +++ b/webauthn/credential.go @@ -35,6 +35,19 @@ type Credential struct { Attestation CredentialAttestation `json:"attestation"` } +// NewCredentialFlags is a utility function that is used to derive the Credential's Flags field. This allows +// implementers to solely save the Raw field of the CredentialFlags to restore them appropriately for appropriate +// processing without concern that changes forced upon implementers by the W3C will introduce breaking changes. +func NewCredentialFlags(flags protocol.AuthenticatorFlags) CredentialFlags { + return CredentialFlags{ + UserPresent: flags.HasUserPresent(), + UserVerified: flags.HasUserVerified(), + BackupEligible: flags.HasBackupEligible(), + BackupState: flags.HasBackupState(), + raw: flags, + } +} + type CredentialFlags struct { // Flag UP indicates the users presence. UserPresent bool `json:"userPresent"` @@ -48,6 +61,14 @@ type CredentialFlags struct { // Flag BS indicates the credential has been backed up and/or sync'd. This value can change but it's recommended // that RP's keep track of this value. BackupState bool `json:"backupState"` + + raw protocol.AuthenticatorFlags +} + +// ProtocolValue returns the underlying protocol.AuthenticatorFlags provided this CredentialFlags was created using +// NewCredentialFlags. +func (f CredentialFlags) ProtocolValue() protocol.AuthenticatorFlags { + return f.raw } type CredentialAttestation struct { @@ -75,12 +96,7 @@ func NewCredential(clientDataHash []byte, c *protocol.ParsedCredentialCreationDa PublicKey: c.Response.AttestationObject.AuthData.AttData.CredentialPublicKey, AttestationType: c.Response.AttestationObject.Format, Transport: c.Response.Transports, - Flags: CredentialFlags{ - UserPresent: c.Response.AttestationObject.AuthData.Flags.HasUserPresent(), - UserVerified: c.Response.AttestationObject.AuthData.Flags.HasUserVerified(), - BackupEligible: c.Response.AttestationObject.AuthData.Flags.HasBackupEligible(), - BackupState: c.Response.AttestationObject.AuthData.Flags.HasBackupState(), - }, + Flags: NewCredentialFlags(c.Response.AttestationObject.AuthData.Flags), Authenticator: Authenticator{ AAGUID: c.Response.AttestationObject.AuthData.AttData.AAGUID, SignCount: c.Response.AttestationObject.AuthData.Counter,