go-webauthn / webauthn Goto Github PK
View Code? Open in Web Editor NEWWebauthn/FIDO2 library in golang
License: Other
Webauthn/FIDO2 library in golang
License: Other
0.9.1
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
.
wa.BeginRegistration
.navigator.credentials.create
Expectation is that the resulting JSON would contain displayName: ''
but instead displayName
is undefined.
https://w3c.github.io/webauthn/#dom-publickeycredentialcreationoptions-user
0.11.0
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.
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),
)
}
}
No response
No response
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.
Using WebAuthn and SPC to authenticate payments on Chrome and other supported browsers.
Secure payment confirmation spec: https://www.w3.org/TR/secure-payment-confirmation/
Google docs: https://developer.chrome.com/articles/secure-payment-confirmation/
0.3.1
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".
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.
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.
Updating the locations that reference []byte
to use URLEncodedBase64
should do it, but there might be backwards compatibility concerns?
Line 7 in 83e3622
Line 47 in 83e3622
webauthn/protocol/assertion.go
Line 35 in 83e3622
webauthn/protocol/assertion.go
Line 65 in 83e3622
It would be nice if this library added support for enterprise attestation
For enterprise deployments (where all hardware and configurations and centrally maintained) it would be nice to be able to request enterprise attestation.
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
The MDS2 is deprecated and we need to implement MDS3
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).
Ensuring the future of the project migration is seamless.
0.11.0
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.
webauthn/protocol/attestation.go
Lines 257 to 259 in cf1758a
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.
webauthn/protocol/attestation_tpm.go
Lines 175 to 182 in 9ca2fae
parseSANExtension
which is not compatible with standard librarywebauthn/protocol/attestation_tpm.go
Lines 301 to 336 in 9ca2fae
that's why this error happens.
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)
go mod init
if at the first timego run .
localhost:8081
and do WebAuthn registrationCurrently, 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)
No response
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.
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
.
See w3c/webauthn#1891 and https://w3c.github.io/webauthn/#sctn-validating-origin.
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.
credential, err := webAuthn.FinishRegistration(user, session, r)
credential, err := webAuthn.FinishLogin(user, session, r)
0.8.4
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.
Try to not pass RP ID
No response
No response
0.11.0
webauthn/metadata/providers/memory/options.go
Lines 64 to 69 in f8b9177
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
.
memory.WithValidateAttestationTypes(false)
option.I expect the attestation types not to be verified if I set the option WithValidateAttestationTypes
to false
.
No response
0.9.2
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.
N/A
No response
See: golang/go#36225
0.8.6
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?
Register a phone as a discoverable credential, then try login with the credential id listed in allowedCredentials.
No response
0.9.2
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())
?
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
}
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:
Implementation Specifics (all ideas at this point):
No response
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?
No response
No response
0.8.2
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?
Enroll a device with:
AttestationType "direct"
AuthenticatorType "platform"
Authenticator: "Windows Hello Authenticator"
The device should be enrolled succesfully.
No response
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.
No response
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"`
}
0.8.6
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.
This is related to the API and the available methods.
I would expect that a method FinishDiscoverableLogin would be available.
No response
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:
root certificates
against which the attestation certificate could be verified.ExportAttestationCertificates
method that would delegate to the client the responsibility to implement the chain-of-trust verification.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
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:
No response
0.8.2
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.
Run the following 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:
I would expect
2023/05/29 11:48:07 display name: Go Webauthn
No response
Android origin has the format of android:apk-key-hash:${androidHash}
and for that reason is not a fullly qualified name. This case need to be handled outside the package?
No response
https://github.com/google/webauthndemo/blob/main/src/libs/webauthn.ts
0.8.2
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
Look at BeginLogin
and BeginRegistration
functions
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
No response
Implement AuthenticatorAttestationResponse interface metheds:
It's not easy to parse CBOR encoded data in some enviroments. It will be helpfull if implement
the methods described in spec, e.g:
getPublicKey()
This operation returns the DER SubjectPublicKeyInfo of the new credential
https://www.w3.org/TR/webauthn-2/#authenticatorattestationresponse
hints
is an array of strings that is on the opt-level of PublicKeyCredentialCreationOptions
.
We'd like to use the new hints field to hint to the platform which kind of credentials we support.
See https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-hints and https://www.w3.org/TR/webauthn-3/#enum-hints
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.
Share (passkeys) credentials between a mobile app and a webpage.
No response
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.
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
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.
No response
No response
0.8.6
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.
See gravitational/teleport#31322 for test and reproducer.
// 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}
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.
No response
FIDO announced to add hybrid as transport method see here and Safari under macOS already sends hybrid.
Safari running on macOS sends already transportation hybrid.
(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?
No response
No response
0.8.2
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.
Run the following
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.
Maybe wrapping the error like
Failed to lookup Client-side Discoverable Credential: Oops, bad things happened
No response
0.9.4
Attestation bug with Direct & TPM on Windows Hello (Windows 11 22H2).
Searching found it was reported in another library too:
MasterKale/SimpleWebAuthn#238
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.
Should enroll the device successfully.
No response
0.3.1
All links into maintainers group on security page does not work (outside of go-webauthn org I suppose).
Check https://github.com/orgs/go-webauthn/teams/maintainers inside incognito mode in browser.
No response
No response
0.10.1
When parsing the metadata, PatternAccuracyDescriptor.MinComplexity is too small; needs to be uint64 not uint32.
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
}
}
modify to uint64
Line 239 in 7dee1ee
No response
It would be nice to have such a list to see what has been fixed and what not. A simple list with cross-reference would be enough I guess. :)
Just noticed that there is even a second fork you work on as well: https://github.com/authelia/webauthn
Could you explain which one is the most up-to-date? Seems like you actively contribute to all of them @james-d-elliott :D
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!
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.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).No response
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.
No response
Specifications:
Diffs:
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.