Giter VIP home page Giter VIP logo

webauthn's Introduction

THIS LIBRARY IS DEPRECATED

Projects using this library should consider migrating to the community-maintained fork over at github.com/go-webauthn/webauthn. See the Migration Guide for more information.

The original README continues below.

WebAuthn Library

============= GoDoc Build Status Go Report Card

This library is meant to handle Web Authentication for Go apps that wish to implement a passwordless solution for users. While the specification is currently in Candidate Recommendation, this library conforms as much as possible to the guidelines and implementation procedures outlined by the document.

Demo at webauthn.io

An implementation of this library can be used at webauthn.io and the code for this website can be found in the Duo Labs repository webauthn-io.

Simplified demo

A simplified demonstration of this library can be found here. It includes a minimal interface and is great for quickly testing out the code. The associated blog post can be found here.

Quickstart

go get github.com/duo-labs/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/user.go. This means also supporting the storage and retrieval of the credential and authenticator structs in webauthn/credential.go and webauthn/authenticator.go, respectively.

Initialize the request handler

import "github.com/duo-labs/webauthn/webauthn"

var (
    web *webauthn.WebAuthn
    err error
)

// Your initialization function
func main() {
    web, err = webauthn.New(&webauthn.Config{
        RPDisplayName: "Duo Labs", // Display Name for your site
        RPID: "duo.com", // Generally the FQDN for your site
        RPOrigin: "https://login.duo.com", // The origin URL for WebAuthn requests
        RPIcon: "https://duo.com/logo.png", // Optional icon URL for your site
    })
    if err != nil {
        fmt.Println(err)
    }
}

Registering an account

func BeginRegistration(w http.ResponseWriter, r *http.Request) {
    user := datastore.GetUser() // Find or create the new user
    options, sessionData, err := web.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
    // using gorilla/sessions it could look like this
    sessionData := store.Get(r, "registration-session")
    parsedResponse, err := protocol.ParseCredentialCreationResponseBody(r.Body)
    credential, err := web.CreateCredential(&user, sessionData, parsedResponse)
    // Handle validation or input errors
    // If creation was successful, store the credential object
    JSONResponse(w, "Registration Success", http.StatusOK) // Handle next steps
}

Logging into an account

func BeginLogin(w http.ResponseWriter, r *http.Request) {
    user := datastore.GetUser() // Find the user
    options, sessionData, err := webauthn.BeginLogin(&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 FinishLogin(w http.ResponseWriter, r *http.Request) {
    user := datastore.GetUser() // Get the user
    // Get the session data stored from the function above
    // using gorilla/sessions it could look like this
    sessionData := store.Get(r, "login-session")
    parsedResponse, err := protocol.ParseCredentialRequestResponseBody(r.Body)
    credential, err := webauthn.ValidateLogin(&user, sessionData, parsedResponse)
    // Handle validation or input errors
    // If login was successful, handle next steps
    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:

// Wherever you handle your WebAuthn requests
import "github.com/duo-labs/webauthn/protocol"
import "github.com/duo-labs/webauthn/webauthn"

var webAuthnHandler 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.ResidentKeyUnrequired(),
        UserVerification: protocol.VerificationRequired
    }

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

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

    // Handle next steps
}

Login modifiers

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

// Wherever you handle your WebAuthn requests
import "github.com/duo-labs/webauthn/protocol"
import "github.com/duo-labs/webauthn/webauthn"

var webAuthnHandler 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, sessionData, err := webAuthnHandler.BeginLogin(&user, webauthn.wat.WithAllowedCredentials(allowList))

    // Handle next steps
}

Acknowledgements

I could not have made this library without the work of Jordan Wright and the designs done for our demo site by Emily Rosen. When I began refactoring this library in December 2018, Koen Vlaswinkel's Golang WebAuthn library really helped set me in the right direction. A huge thanks to Alex Seigler for his continuing work on this WebAuthn library and many others. Thanks to everyone who submitted issues and pull requests to help make this library what it is today!

webauthn's People

Contributors

6543 avatar aseigler avatar berwyn avatar blobonat avatar cmueller-tp avatar e3b0c442 avatar fmitra avatar gburt avatar hanzhao-yu avatar hbolimovsky avatar horgh avatar j0 avatar james-d-elliott avatar jordan-wright avatar kylelady avatar lambang1 avatar liouk avatar littlelies avatar machiel avatar masterkale avatar nicksteele avatar pschultz avatar pulsejet avatar saxenautkarsh avatar smiller171 avatar thinkofher avatar vincentserpoul avatar vvakame avatar zachhuff386 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

webauthn's Issues

Please don't write to stdout

In a number of places, this library writes to stdout using functions like fmt.Println. This is inappropriate for a library.

Update instructions to help users understand what is supposed to happen

This isn't really an issue, but with this being a demo to help learn about the new tech, I hope it's okay I ask here:

What exactly is this supposed to do? Firefox Beta (which is v60) has the security.webauth.webauthn flag enabled by default. I click on the register and I get a "Tap your security key to finish registration" message, but I don't know what that means. Is this just a nonworking proof of concept, or is there something I'm missing?

I read the Duo Article about it, and I understand I'm supposed to be able to authenticate with my phone or a USB-key, but hows does one actually make that connection (and I don't even have the latter). I tried it with FF Beta on my (Android) phone, and the popup just shows for a split second before closing when I try to register.

The instructions don't really say anything more than "Get excited for WebAuthn!" but without understanding, it's hard to be excited ๐Ÿ˜„

Using Protonmail's GopenPGP webauthn build fails in webauthncose

I am playing around with protonmail's GopenPGP V2
https://github.com/ProtonMail/gopenpgp

Being forced to add
replace golang.org/x/crypto => github.com/ProtonMail/crypto v0.0.0-20191122234321-e77a1f03baa0
to go.mod the build suddenly fails while compiling webauthncose:

# github.com/duo-labs/webauthn/protocol/webauthncose
P:\go\dev\pkg\mod\github.com\duo-labs\[email protected]\protocol\webauthncose\webauthncose.go:298:39: cannot use oKey (type "golang.org/x/crypto/ed25519".PublicKey) as type "crypto/ed25519".PublicKey in argument to marshalEd25519PublicKey

Obviously being unexperienced any hint or RTFM is appreciated!

How does navigator.credentials.create interact with the authenticator?

The WebAuthn spec says

Clients can communicate with authenticators using a variety of mechanisms. For example, a client MAY use a client device-specific API to communicate with an authenticator which is physically bound to a client device. On the other hand, a client can use a variety of standardized cross-platform transport protocols such as Bluetooth (see ยง5.10.4 Authenticator Transport Enumeration (enum AuthenticatorTransport)) to discover and communicate with cross-platform attached authenticators.

I don't understand exactly how this works though. My understanding is, the Relying Party's JavaScript executes navigator.credentials.create in the user agent. How does the browser know what authenticator to use (whether it's bluetooth, nfc, etc.)? What if there are multiple bluetooth devices available? Does the browser itself perform the connection over a web api?

Does the authenticator need to be "installed" or registered in some way into the web browser via an add-on? If I wanted to just use another operating system process as the authenticator, as I think in this example, what would be the communication method between the browser's JS and the external process?

macOS Chrome Canary supports TPM for creations but not login

On macOS 10.13.16 using Chrome Canary 71.0.3558.0, I can create an account using the Macbook Pro fingerprint scanner by slecting the TPM option, however I cannot login the same way.

When attempting to do so the macOS systemdialog prompting for a fingerprint does not appear and my Yubikey begins flashing the same as if I had selected "cross platform" as my authentication type.

Difference in user verification logic for registration and login

For registration:

  • In Begin, the user verification is set in code to preferrred.
  • In Finish, the user verification is obtained from config.

For login:

  • In Begin, the user verification is obtained from config and stored in session data.
  • In Finish, the user verification is obtained from the session data.

Why the difference between the two?
How do I change the value for registration? If I use an options setter to change it, the value gets ignored in Finish.

Metadata

Hi-
Apart from the tests, do you have any pointers for using the metadata code to check a user's authenticator capabilities?
Thanks
David

Revoke dependency leads to broken import of google.golang.org/grpc/naming

A long chain of dependencies is introduced via revoke's CT support. This currently leads to a broken module dependency for GRPC:

        github.com/duo-labs/webauthn/protocol imports
        github.com/duo-labs/webauthn/metadata imports
        github.com/cloudflare/cfssl/revoke imports
        github.com/cloudflare/cfssl/helpers imports
        github.com/google/certificate-transparency-go imports
        go.etcd.io/etcd imports
        github.com/coreos/etcd/etcdmain imports
        github.com/coreos/etcd/proxy/grpcproxy imports
        google.golang.org/grpc/naming: module google.golang.org/grpc@latest found (v1.30.0), but does not contain package google.golang.org/grpc/naming

Edit: It looks like revoke's validation is needed here. Any suggestions for fixing this dependency issue?

Incorrect algorithm in ValidateLogin

@nicksteele If I am not mistaken, this code is meant to determine whether allowed credential IDs are a subset of user credentials:

webauthn/webauthn/login.go

Lines 115 to 124 in 1daaee8

var credentialsOwned bool
for _, userCredential := range userCredentials {
for _, allowedCredentialID := range session.AllowedCredentialIDs {
if bytes.Equal(userCredential.ID, allowedCredentialID) {
credentialsOwned = true
break
}
credentialsOwned = false
}
}

However, if session.AllowedCredentialIDs = {c1} and userCredentials = {c1, c2}, the code finishes with credentialsOwned = false instead of true. For a correct and efficient algorithm, consider https://stackoverflow.com/questions/18879109/subset-check-with-slices-in-go

Ambiguous import for ugorji/go/codec

Importing this module can lead to the following error:

build github.com/hdm/thing: cannot load github.com/ugorji/go/codec: ambiguous import: found github.com/ugorji/go/codec in multiple modules:
        github.com/ugorji/go v1.1.4 (C:\Users\Developer\go\pkg\mod\github.com\ugorji\[email protected]\codec)
        github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43 (C:\Users\Developer\go\pkg\mod\github.com\ugorji\go\[email protected]

A quick fix is adding the following to go.mod:
replace github.com/ugorji/go => github.com/ugorji/go/codec v1.1.7

The specific import is github.com/duo-labs/[email protected] github.com/ugorji/go/[email protected]

This might be fixed by upgrading the dependency on codec to v1.1.7 in this module.

URLEncodeBase64.UnmarshalJSON doesn't handle "null" values

image

In the login response object, if userHandle == null the Unmarshaled parsed value has the value of:

[]byte{158,233,101}

Which is the value of "null" based64 decoded.

$ echo null | base64 -D|od -t u1
0000000   158 233 101

It appears that the URLEncodeBase64.UnmarshalJSON() method doesn't handle null as input.

Test data shouldn't have execute bit set

The MetadataTOCParsing files in testdata/ have the execute bit set, but they are not executables. This was noticed while working on packaging this library for Debian.

navigator.credentials.get() option to suppress chrome UI popup if allowCredentials in not present in the authenticator

I was playing with this implementation in the chrome with mac inbuilt fingerprint scanner and wondering what will be the solution to this kind of flow for a relying party.

  1. Let's say user registered webauthn in device A with the relying party R.
    Authentication flow is like this, the user comes to the relying party R input username and clicks next, relying party pulls the previously registered credential Ids for the user to create allowCredentials and query the authenticator for authentication. All goes fine as authenticator has one of those allowCredentials associated private key

  2. But let's say the same user goes to the device B and tries to login with the same relying party R by putting username and clicking next. This time again the relying party pulls over the registered credential Ids for the user to create allowCredentials and query the authenticator for authentication. But this time as the device B does not have any associated private key it shows an error popup window in chrome.

Is there any way we can pass any option to the navigator.credentials.get call so that if the associated private key is not present for the given allowCredentials we can just stop the webauthn flow and fall back to some other authentication method without showing the error popup window.

Do not accept null/empty rpid. as the specification states supported.

the spec said๏ผš

If options.rpId is not present, then set rpId to effectiveDomain.
Otherwise:

If options.rpId is not a registrable domain suffix of and is not equal to effectiveDomain, return a DOMException whose name is "SecurityError", and terminate this algorithm.

Set rpId to options.rpId.

Note: rpId represents the callerโ€™s RP ID. The RP ID defaults to being the callerโ€™s origin's effective domain unless the caller has explicitly set options.rpId when calling get().

But we could not set it to empty / null.

This is useful when webauthn rpid could not be pre-assigned. such as in LAN-local application which accessed by ip address.

register works but not login

Yubikey w/ Chrome 70 on macOS Mojave:

Steps:

  1. insert yubikey

  2. Load https://webauthn.io/

  3. enter "test" for the username

  4. leave attestation type "none" and authentication type "cross platform"

  5. click "register"

  6. yubikey flashes

  7. tap flashing indicator

  8. "You're logged in!"

  9. load https://webauthn.io/

  10. enter "test" for the username

  11. leave attestation type "none" and authentication type "cross platform"

  12. click "login with credential"

  13. yubikey does not flash

  14. the page shows "Logging In..." "Tap your security key to login."

  15. a prompt appears "Use your security key with webauthn.io" "Plug in your security key and activate it".
    -- my security key is plugged in. it is not flashing

  16. I press the button anyway.

  17. Nothing happens.

The same behavior happened on Firefox Nightly on Mojave.

Yubikey w/ edge 44.18272.1000.0 on windows 10 1809 / 18272.1000

Steps:

  1. insert yubikey

  2. Load https://webauthn.io/

  3. enter "test" for the username

  4. leave attestation type "none" and authentication type "cross platform"

  5. click "register"

  6. the first time, windows security asked me to create a security pin (pin field twice) all subsequent instances the pin field is listed once

  7. yubikey flashes

  8. tap flashing indicator

  9. "You're logged in!"

  10. load https://webauthn.io/

  11. enter "test" for the username

  12. leave attestation type "none" and authentication type "cross platform"

  13. click "login with credential"

  14. yubikey does not flash

  15. the page shows "Logging In..." "Tap your security key to login." -- briefly

  16. it disappears and I'm left with "UnknownError"

README typo RPID-->RPIcon

Hey Guys,

I've just noticed that in the README there is a small typo.
Under https://github.com/duo-labs/webauthn#initialize-the-request-handler in the example, there is RPID twice, instead of RPID and RPIcon.
it should be:

func main() {
    web = webauthn.New(&webauthn.Config{
        RPDisplayName: "Duo Labs", // Display Name for your site
        RPID: "duo.com", // Generally the FQDN for your site
        RPOrigin: "https://login.duo.com", // The origin URL for WebAuthn requests
        RPIcon: "https://duo.com/logo.png", // Optional icon URL for your site
    })

FIDO2 C++ based application using WebAuthn.dll for "YUBIKEY 5 NFC" (External authenticator) gives "This Security Key doesn't look familiar. Please try a different one."

Hi

I am writing FIDO2 C++ based application using WebAuthn.dll for "YUBIKEY 5 NFC" (External authenticator) using the following WebAutheN APIs of Microsoft from the
https://github.com/microsoft/webauthn/blob/master/webauthn.h

			HRESULT WINAPI WebAuthNAuthenticatorMakeCredential(
						 _In_        HWND                                                hWnd,
							 _In_        PCWEBAUTHN_RP_ENTITY_INFORMATION                    pRpInformation,
							 _In_        PCWEBAUTHN_USER_ENTITY_INFORMATION                  pUserInformation,
							 _In_        PCWEBAUTHN_COSE_CREDENTIAL_PARAMETERS               pPubKeyCredParams,
							 _In_        PCWEBAUTHN_CLIENT_DATA                              pWebAuthNClientData,
							 _In_opt_    PCWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS    pWebAuthNMakeCredentialOptions,
							 _Outptr_result_maybenull_ PWEBAUTHN_CREDENTIAL_ATTESTATION      *ppWebAuthNCredentialAttestation);

			HRESULT WINAPI WebAuthNAuthenticatorGetAssertion(
							_In_        HWND                                                hWnd,
							_In_        LPCWSTR                                             pwszRpId,
							_In_        PCWEBAUTHN_CLIENT_DATA                              pWebAuthNClientData,
							_In_opt_    PCWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS      pWebAuthNGetAssertionOptions,
							_Outptr_result_maybenull_ PWEBAUTHN_ASSERTION                   *ppWebAuthNAssertion);

I am able to get the registration successfully with the IdP (Identity Provider) using C++ API WebAuthNAuthenticatorMakeCredential() in my code.

But when it comes to Authentication, I am facing problem with the api WebAuthNAuthenticatorGetAssertion() as follows:

"This Security Key doesn't look familiar.  Please try a different one."

image

Below is my code :

			HWND  hWnd = GetForegroundWindow();
			LPCWSTR   pwszRpId = L"tenet.domain.com";
		        
                            //My client data in json format is as below...
			sClientData64 = {"type":"webauthn.get","challenge":"MKB9ApSESppXbU-oVW_M","origin":"https://tenet.domain.com","crossOrigin":true};

			WEBAUTHN_CLIENT_DATA oClientData_in = { WEBAUTHN_CLIENT_DATA_CURRENT_VERSION,
								static_cast<DWORD>(sClientData64.length()),
								(PBYTE)(sClientData64.data()),
								WEBAUTHN_HASH_ALGORITHM_SHA_256 
			      			      	      };

I tried to manipulate the values as in the following link: https://github.com/Yubico/python-fido2/blob/master/fido2/win_api.py to implement
the structure "WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS" below:

My allow list code is as follows:

			std::vector<WEBAUTHN_CREDENTIAL_EX> allowCredentials;				
			WEBAUTHN_CREDENTIAL_EX* pAllowCredentials = nullptr;
			std::vector<WEBAUTHN_CREDENTIAL_EX*> allowCredentialsPtrs;
			
			WEBAUTHN_CREDENTIAL_LIST allowCredentialList = { 0 };
			WEBAUTHN_CREDENTIAL_LIST* pAllowCredentialList = nullptr;

			DWORD dwVersion = WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_4; // the value is 4

			if(dwversion is >= 4)
			{
				DWORD winTransports = 0;
						
				std::string sAuthenticatorType = "usb";
				int nTransport = 0;
				if(sAuthenticatorType == "usb")
				{ 
					nTransport = 1;
				}
				
				if (nTransport & U2F_AUTHENTICATOR_TRANSPORT_USB)
				{
					winTransports |= WEBAUTHN_CTAP_TRANSPORT_USB;
				}					

				WEBAUTHN_CREDENTIAL_EX webCredEx = {	WEBAUTHN_CREDENTIAL_EX_CURRENT_VERSION,
									static_cast<DWORD>(sCredentialId.length()),
									((BYTE*)(sCredentialId.c_str())),
									WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY,
									winTransports
							   	   };

				allowCredentials.push_back(webCredEx);				

				pAllowCredentials = allowCredentials.data();
				for (DWORD i = 0; i < allowCredentials.size(); i++)
				{
					allowCredentialsPtrs.push_back(&pAllowCredentials[i]);
				}
				allowCredentialList.cCredentials = allowCredentials.size();  // original
				allowCredentialList.ppCredentials = allowCredentialsPtrs.data(); // original
				pAllowCredentialList = &allowCredentialList;  // my AllowCredentialList value
			}

// Credentials is as follows:

			WEBAUTHN_CREDENTIALS webCredentials = {0,NULL};

			DWORD dwVersion = WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_3; // the value is 3

			if(dwversion is >= 3)
			{
				std::vector<WEBAUTHN_CREDENTIAL> vCredential;
				std::vector<WEBAUTHN_CREDENTIAL*> pCredential;
				WEBAUTHN_CREDENTIAL webAuthCredential = {WEBAUTHN_CREDENTIAL_CURRENT_VERSION,
				 				 static_cast<DWORD>(sCredentialId.length()),
				 				 ((BYTE*)(sCredentialId.c_str())),
								 WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY
								};
				WEBAUTHN_CREDENTIAL *pwebCredential;
				vCredential.push_back(webAuthCredential);
				pwebCredential = vCredential.data();
				for (DWORD i = 0; i < vCredential.size(); i++)
				{
					pCredential.push_back(&pwebCredential[i]);
				}
				webCredentials.cCredentials = vCredential.size();
				webCredentials.pCredentials = pwebCredential;
			}

			WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS   oWebAuthNGetAssertionOptions = { 
					   dwVersion,
					   10000,
					   webCredentials,  //WEBAUTHN_CREDENTIALS  // if (dwVersion >=3)  {webCredentials;} else {0,NULL}                                                                                                                            {webCredentials = NULL}
					   {0, NULL},  //WEBAUTHN_EXTENSIONS
					   dwAuthenticatorAttachement,  // option for Platform (Windows Hello) vs Cross platform authenticator (Yubikey)
					   winUserVerificationReq,  // user Verification Required (preferred)
					   0,  // dwFlags
					   NULL, // as json data received it is null
 	                                           pbU2fAppIdUsed, (FALSE) this is a pointer
					   NULL,  // pCancellationId
					   pAllowCredentialList  // allowList if (dwversion is >= 4) else allowlist is null
			                   };

			WEBAUTHN_ASSERTION* pWebAuthNAssertion = nullptr;  // this is an output parameter, which will get data of signature, Authenticator etc...

			 // on this call i get this message dialog popup saying  " This Security key doesn't look familiar. please try a different one."

			hResult = WebAuthNAuthenticatorGetAssertion(hWnd, pwszRpId &oClientData_in, &oWebAuthNAssertionOptions, &pWebAuthNAssertion);  

I took the python code for reference from the https://github.com/Yubico/python-fido2/blob/master/fido2/win_api.py

			if (dwVersion >= 3)
        			self.pCancellationId = cancellationId  (NULL)

    			if self.dwVersion >= 4:
        			clist = WebAuthNCredentialList(credentials)
        			self.pAllowCredentialList = ctypes.pointer(clist)
    			else:
        			self.CredentialList = WebAuthNCredentials(credentials)

========================================================================

I have posted the same issue in MSDN forums.
https://docs.microsoft.com/en-us/answers/questions/305670/fido2-c-based-application-using-webauthndll-for-34.html

Please let me know, if any thing has to added in my code.

Thankyou.

Option to allow all authenticator types

Currently, WebAuthn.io requires the user to select an authentictor type. However, as I understand from the spec, authenticatorSelection is an optional field when creating the credentials.

Wouldn't it make sense to also provide a third "either" Authenticator Type option on WebAuthn.io? I guess this would also be useful in real-world applications, where I don't really care if the user has a platform authenticator or a roaming authenticator. In the end the user is always responsible for keeping their private key secure, whether it is on device or on an external.

It would also be useful to demonstrate how browsers present the choice between platform and roaming authenticators in case the user has both.

Timeout

Is there any particular reason the Timeout field in webauthn.Config isn't a time.Duration?

Not update Authenticator.SignCount when cloned authenticator is detected

@nicksteele @emlun I noticed that the code updates the SignCount even when a cloned authenticator is detected. I believe we should be returning immediately after the clone warning is true. wdyt?

https://github.com/duo-labs/webauthn/blob/master/webauthn/authenticator.go#L47

func (a *Authenticator) UpdateCounter(authDataCount uint32) {
	if authDataCount <= a.SignCount && (authDataCount != 0 || a.SignCount != 0) {
		a.CloneWarning = true
	}
	a.SignCount = authDataCount
}

please provide minimal working example

I like to use webauthn.io as reference implementation, but its quiet specific in some cases, especially the frontend is not really minimal (using jquery etc.)

It would be nice to have a minimal working example to compare the own implementation against.

Due to the fact that the standard is not very old the error handling in chrome is not very nice, and I only get a DomException whenever I try to navigator.credentials.create(). With a minimal example I could at least compare my options and the the options from the reference implementation.

appid extension: u2f registered keys compat

So I'm experimenting with this library. Currently the issue is with the appid extension enabled it's claiming the device registered via u2f is not familiar. Newly registered keys work with my config, but the old FIDO ones do not. This issue occurs when navigator.credentials.get is called in either Chrome or Firefox. Code snips:

Creating the handler:

	if webauthnHandler, err = webauthn.New(&webauthn.Config{
		RPID:                  "webauthn.example.com",
		RPDisplayName:         "Webauthn",
		RPOrigin:              "https://webauthn.example.com",
		AttestationPreference: protocol.PreferIndirectAttestation,
	}); err != nil {
		panic(err)
	}

Credential added the users credentials:

	keyID, _ := base64.StdEncoding.DecodeString(encodedKeyHandleIDHere)
	pubkey, _ := base64.StdEncoding.DecodeString(encodedPubKeyHere)

	credential := webauthn.Credential{
		ID:              keyID,
		PublicKey:       pubkey,
		AttestationType: "fido-u2f", // Also tried with this commented.
		Authenticator: webauthn.Authenticator{
			SignCount: 0,
			AAGUID:    []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		},
	}

Creating the assertion:

	assertion, data, err := webauthnHandler.BeginLogin(user,
		webauthn.WithUserVerification(body.UserVerification),
		webauthn.WithAssertionExtensions(map[string]interface{}{"appid": "https://webauthn.example.com"}),
	)

SECURITY: 1 decode of < 10 bytes of malformed CBOR data can exhaust memory and cause fatal error

๐Ÿ”ฅ this CBOR security issue is already public knowledge since Sep 2019 or earlier.

duo-labs/webauthn is using a CBOR library that can exhaust memory in 1 decode attempt of malformed CBOR data (which can be shorter than "hello world".)

Example of affected code in duo-labs/webauthn:

// Unmarshall the credential's Public Key into CBOR encoding
func unmarshalCredentialPublicKey(keyBytes []byte) []byte {
var cborHandler codec.Handle = new(codec.CborHandle)
var m interface{}
codec.NewDecoderBytes(keyBytes, cborHandler).Decode(&m)

Search for "CborHandle" and see if it gets used for decoding to find additional relevant code.

Background

October 2013 - RFC 7049 (CBOR) is approved by IETF as an Internet Standard. Section 8 of RFC 7049 warns of malicious CBOR being used for resource exhaustion attacks.

September 2019 - oasislabs finds tiny malformed CBOR data causes fatal out of memory error when decoded with latest releases of ugorji/go and switched to fxamacker/cbor in early October 2019.

February 2020 - smartcontractkit/chainlink has a security issue with ugorji/go that is closed by switching to fxamacker/cbor. I don't have login access to view their security issue.

March 2020 - @fxamacker finds tiny malformed CBOR data that causes fatal out of memory error when decoded with any release of ugorji/go (1.1.0 - 1.1.7 as well as last commit 42bc974).

Comparisons

fxamacker/cbor comparisons to ugorji/go.

alt text

Click to expand:

CBOR Program Size Comparison

fxamacker/cbor can produce smaller programs.

alt text

CBOR Speed Comparison

fxamacker/cbor can be faster for CBOR data such as CBOR Web Tokens.

alt text

CBOR Memory Comparison

fxamacker/cbor can use less memory for CBOR data such as CBOR Web Tokens.

alt text

Benchmarks used example data from RFC 8392 Appendix A.1 and default options for CBOR libraries.

Challenge: urlsafe vs standard base64

While working with this library and the Webauth.io Source I noticed a discrepancy in encoding type of the challenges.

On the one hand the Challenge is defined as a urfsafe base64:

type Challenge URLEncodedBase64

in challenge.go.
The urfsafe encoding is used for session object.

On the other hand when send as a JSONResponse, the challenge is encoded to a standard base64 string, by the go json encoder.
(This can easily be checked by inspecting the server response on https://webauthn.io)

Is this discrepancy known and intended?

signature verification for basic attestation broken

handleBasicAttestation attempts to convert a COSE algorithm identifier (alg in the code below) to x509.SignatureAlgorithm (unless the device seems to be a YubiKey).

These values obviously don't align, so all verifications fail with

x509: cannot verify signature: algorithm unimplemented

if strings.Contains(attCert.Subject.CommonName, "Yubico") {
err = attCert.CheckSignature(x509.ECDSAWithSHA256, signatureData, signature)
} else {
err = attCert.CheckSignature(x509.SignatureAlgorithm(alg), signatureData, signature)
}

This code should map the COSE ID to one of the x509 constants instead.

grep shows similar code in attestation_tpm.go and attestation_androidkey.go.

inconsistency between devices / hostnames

I'm using this library to add webauthn to a pet project. On my test domain, it was working just fine. I can register both my macbook pro (2017) and my phone (galaxy s9+), when I deploy it to my "production" environment, I can register my macbook, but I cannot register my phone.

When I try to register my phone I get the following error:

Error finding cert issued to correct hostname: x509: certificate signed by unknown authority.

Test domain is similar to: tst.tmp.example.nl,
production domain is similar to: prod.example.xyz.

Is there any reason why a different end point would not be allowed?

Support for Second Factor Flow?

Hi!
Our first use of FIDO2 and WebAuthn will use the 2FA flow. I am able to Register via 2FA (requireResidentKey=false) but am not able to 'Login' (login meaning, I'm assuming, that simply the second factor is authenticated). I'm using U2F compatible Yubikey 4 series. Shoud this work?

I am able to use 2FA at https://webauthndemo.appspot.com/Home. Is there an issue authenticating using localhost?

Thanks,
Brian

Webauthn Spec: Level 2

Currently none of the level 2 specific features are implemented in this library but it has been ratified by the w3c, and level 3 is currently being worked on by w3c.

Reference Material:

Key Parts:

  • Authenticator Selection Criteria: new residentKey parameter. #133
  • Attestation Conveyance Preference: new enterprise option. From looking at the spec this seems like it's simply just added and it's handled by the browser/credential; though it'd be nice if someone else knew about this, or could take a read of the changes above to ensure I am correct in my understanding.
  • The cross origin changes Yubico mentions are not entirely clear to me.
  • The largeBlob extension: this is not as relevant, but it may make sense to eventually tackle extensions properly. All of the extensions have reliable input/output which we can implement in a struct rather than a map[string]interface{}. The uvm extension is the only one that's semi complicated. Basically seems like it returns bytes which need to be decoded.

CollectedClientData.Verify does not match spec

Hello,

I'm currently working on using this library to migrate Gitea's FIDO U2F implementation to standard WebAuthn. While doing so, I came across an error: Error validating origin.

Digging deeper, I discovered in the Verify(storedChallenge string, ceremony CeremonyType, relyingPartyOrigin string) error method on *CollectedClientData that the validation does not appear to be matching the spec. These lines verify the origin as described in Assertion step 9, however, they are not actually comparing the origins. By calling clientDataOrigin.Hostname(), the comparison ends up being mydomain.com vs https://mydomain.com; that is to say, clientDataOrigin.Hostname() is the same as the Relying Party ID, not the Relying Party Origin.

This may not be caught due to the fact that on initialization, if RPOrigin is not provided in the config struct, RPOrigin is set to RPID.

Happy to submit a PR if desired, but I potentially see this breaking implementations that are already using this library.

Project maintained?

I see this project has several good PRs waiting to be merged, including security updates.

Is this project still maintained? If not, it might be better to mark it as read-only, so people know they must move on. Looking at the project "Network" graph, I see a fork from authelia that has several of these PRs merged.

Verify "alg" parameter

Hi all! Your library has been very helpful to me in understanding webauthn. I've been trying to implement webauthn as well as make sure all the steps for registering a credential are followed ("https://www.w3.org/TR/webauthn/#sctn-registering-a-new-credential"). Currently I am on step 16 "Verify that the "alg" parameter in the credential public key in authData matches the alg attribute of one of the items in options.pubKeyCredParams." I am having difficulty getting the "alg" parameter in credential public key. I noticed that in protocol/authenticator.go:46 that the credential public key is kept as bytes and not given a struct with fields. Should CredentialPublicKey have a corresponding struct?
I also noticed that the "alg" parameter in attestation statements is used for verification, such as in attestation_packed.go. Does this do what I am trying to do?
Thanks so much! :)

works for Desktop and Mobile apps ?

I tested the server with my yubikey and chrome on your demo website and works great. thanks for this.

But what about Desktop Apps and Mobile Apps ?

I want to enable my desktop and mobile apps ( i write them in flutter, but use golang with them ) to also use a yubikey.

local golang webauthn.io: Error Validating Origin on update

Start with git clone https://github.com/duo-labs/webauthn.io.git and golang 1.14.3 and use this config.json:

{
    "db_name": "sqlite3",
    "db_path": "webauthn.db",
    "host_address": "127.0.0.1",
    "host_port": "9005",
    "relying_party": "localhost"
}

All goes swimmingly. I can register a use and log in.

go get -u ./... doesn't work for unrelated (and presumably fixable, somewhere, reasons, so removing go.mod and go.sum and re-creating them with go mod init github.com/duo-labs/webauthn.io and then building produces a server that doesn't work -- registration appears to be successful (from the browser) but the server reports "error validating origin" and login either with the previous registered user or a new user fails.

At least that's what it did last night. Today it's just hanging in the registration process. It's not just the webauthn.io demo, of course, other apps using the webauthn code also fail.

The working version from go.mod is
github.com/duo-labs/webauthn v0.0.0-20191119193225-4bf9a0f776d4

Unsupported Public Key Type after upgrading webauthn

We were using version v0.0.0-20190206165356-8065b78cf2cb of this webauthn library for our initial Webauthn support. And recently tried updating to the latest version which at this point is v0.0.0-20190521121529-2fa2ec45d1eb (based on versions in go.mod)

After updating, all existing user keys that we've stored are no longer working, and the error we are seeing in the logs is:

Error validating the assertion signature: Unsupported Public Key Type

I've been reading through the changes made to protocol/assertion.go but have not been able to determine what the incompatibility is.
If I save the keys using the latest version of the library it works fine without changing any of our code.

Hoping you guys have some ideas on what I can do.

Use CTAP2 canonical CBOR decoding

With #67 fxamacker/cbor's cbor.Unmarshal() is used to decode CBOR. This method uses the module's default decoding mode, which does not follow the restrictions of the CTAP2 canonical CBOR encoding form, e.g. max nested levels is set to 32 instead of the required 4.

I suggest to use the following decoding options:

cbor.DecOptions{
	DupMapKey:        cbor.DupMapKeyEnforcedAPF,
	TimeTag:          cbor.DecTagIgnored,
	MaxNestedLevels:  4,
	MaxArrayElements: 131072,
	MaxMapPairs:      131072,
	IndefLength:      cbor.IndefLengthForbidden,
	TagsMd:           cbor.TagsForbidden,
}

DANGER: This library depends on AGPL-licensed code

Although this library is licensed under a BSD license, it depends on github.com/katzenpost/core/crypto/eddsa, which is licensed under the AGPL.

Consequentially, anyone using github.com/duo-labs/webauthn in a network-accessible application is required to release the source code of their application under the AGPL.

I imagine that this is not what most consumers of this library are expecting.

webauthn/metadata/metadata_test broken

The webauthn/metadata/metadata_test are failing due to Get "https://fidoalliance.co.nz/mds/pki/MDSROOT.crt": dial tcp: lookup fidoalliance.co.nz: no such host which is correct since this url is unreachable. I tried running the tests with the new urls (below) and now I am running into an error saying CRL path is invalid.

func getMetdataTOCSigningTrustAnchor(c http.Client) ([]byte, error) {
	rooturl := ""
	if Conformance {
		rooturl = "https://mds.certinfra.fidoalliance.org/pki/MDSROOT.crt"
	} else {
		rooturl = "https://valid.r3.roots.globalsign.com/"
	}

	return downloadBytes(rooturl, c)
}

Error with CRL path invalid (below). Indeed this url is unreachable too but looking at the code I can't tell where is it coming from. Could you please help @nicksteele @aseigler, thank you!

2021/07/20 14:44:58 [WARNING] failed to fetch CRL: Get "https://fidoalliance.co.nz/mds/crl/MDSROOT.crl": dial tcp: lookup fidoalliance.co.nz: no such host
2021/07/20 14:44:58 [WARNING] error checking revocation via CRL
err Certificate revocation list is unavailable tt.wantErr <nil>--- FAIL: TestMetadataTOCParsing

How to distinguish between 2FA and passwordless login?

How do I make sure that only a FIDO2 key is used, both on the client and the server? From the spec I see that passwordless login should only be supported for FIDO2 keys, but the webauthn.io demo seems to work fine with U2F too. Which sounds odd as it isn't recommended to use U2F keys for passwordless login

From what I understand from the spec, if you log in with a U2F device instead of a FIDO2 device, the security key will not set the userHandle as it doesn't keep track of it. My idea was to decide on the presence of userHandle whether this is a second factor or a passwordless login. However, the webauthn library doesn't expose this attribute to the enduser in FinishRegistration

Is there a way for the server to make sure that the client who initiated the login indeed used a FIDO2 device, and thus it is safe to do a passwordless login?

Also (but not related to this code per se) can I force the client to make sure only FIDO2 keys work when calling register or create ?

Compiling to JavaScript

I managed to compile this library to JS (described in this blog post) with GopherJS with a smalll wrapper and a few changes:

  1. fxamacker/cbor instead of ugorji/go/codec - the latter is bloat, and doesn't play well with GopherJS
  2. Removed net/http dependency by getting rid of cfssl and breaking some stuff in metadata. This isn't necessary, but reduces the bundle size to half, and wouldn't work anyway if I understand correctly.

Just wanted to know if there is any chance the first change could be incorporated in this repo, so keeping in sync will be easier.

Hello Code is inoperative

Until there is an Insider or Stable build of Edge with updated and documented WebAuthn support, we're in a holding pattern.

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.