Skip to content

Instantly share code, notes, and snippets.

@philholden
Last active March 21, 2024 05:49
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save philholden/50120652bfe0498958fd5926694ba354 to your computer and use it in GitHub Desktop.
Save philholden/50120652bfe0498958fd5926694ba354 to your computer and use it in GitHub Desktop.
// paste in console of any https site to run (e.g. this page)
// sample arguments for registration
// https://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915/fido-u2f-raw-message-formats-v1.1-id-20160915.html#authentication-response-message-success
var createCredentialDefaultArgs = {
publicKey: {
// Relying Party (a.k.a. - Service):
rp: {
name: "Acme"
},
// User:
user: {
id: new Uint8Array(16),
name: "john.p.smith@example.com",
displayName: "John P. Smith"
},
pubKeyCredParams: [{
type: "public-key",
alg: -7
}],
attestation: "direct",
timeout: 60000,
challenge: new Uint8Array([ // must be a cryptographically random number sent from a server
0x8C, 0x0A, 0x26, 0xFF, 0x22, 0x91, 0xC1, 0xE9, 0xB9, 0x4E, 0x2E, 0x17, 0x1A, 0x98, 0x6A, 0x73,
0x71, 0x9D, 0x43, 0x48, 0xD5, 0xA7, 0x6A, 0x15, 0x7E, 0x38, 0x94, 0x52, 0x77, 0x97, 0x0F, 0xEF
]).buffer
}
};
// sample arguments for login
var getCredentialDefaultArgs = {
publicKey: {
timeout: 60000,
// allowCredentials: [newCredential] // see below
challenge: new Uint8Array([ // must be a cryptographically random number sent from a server
0x79, 0x50, 0x68, 0x71, 0xDA, 0xEE, 0xEE, 0xB9, 0x94, 0xC3, 0xC2, 0x15, 0x67, 0x65, 0x26, 0x22,
0xE3, 0xF3, 0xAB, 0x3B, 0x78, 0x2E, 0xD5, 0x6F, 0x81, 0x26, 0xE2, 0xA6, 0x01, 0x7D, 0x74, 0x50
]).buffer
},
};
// register / create a new credential
var cred = await navigator.credentials.create(createCredentialDefaultArgs)
console.log("NEW CREDENTIAL", cred);
// normally the credential IDs available for an account would come from a server
// but we can just copy them from above...
var idList = [{
id: cred.rawId,
transports: ["usb", "nfc", "ble"],
type: "public-key"
}];
getCredentialDefaultArgs.publicKey.allowCredentials = idList;
var assertation = await navigator.credentials.get(getCredentialDefaultArgs);
console.log("ASSERTION", assertation);
// verify signature on server
var signature = await assertation.response.signature;
console.log("SIGNATURE", signature)
var clientDataJSON = await assertation.response.clientDataJSON;
console.log("clientDataJSON", clientDataJSON)
var authenticatorData = new Uint8Array(await assertation.response.authenticatorData);
console.log("authenticatorData", authenticatorData)
var clientDataHash = new Uint8Array(await crypto.subtle.digest("SHA-256", clientDataJSON));
console.log("clientDataHash", clientDataHash)
// concat authenticatorData and clientDataHash
var signedData = new Uint8Array(authenticatorData.length + clientDataHash.length);
signedData.set(authenticatorData);
signedData.set(clientDataHash, authenticatorData.length);
console.log("signedData", signedData)
// import key
var key = await crypto.subtle.importKey(
// The getPublicKey() operation thus returns the credential public key as a SubjectPublicKeyInfo. See:
//
// https://w3c.github.io/webauthn/#sctn-public-key-easy
//
// crypto.subtle can import the spki format:
//
// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey
"spki", // "spki" Simple Public Key Infrastructure rfc2692
cred.response.getPublicKey(),
{
// these are the algorithm options
// await cred.response.getPublicKeyAlgorithm() // returns -7
// -7 is ES256 with P-256 // search -7 in https://w3c.github.io/webauthn
// the W3C webcrypto docs:
//
// https://www.w3.org/TR/WebCryptoAPI/#informative-references (scroll down a bit)
//
// ES256 corrisponds with the following AlgorithmIdentifier:
name: "ECDSA",
namedCurve: "P-256",
hash: { name: "SHA-256" }
},
false, //whether the key is extractable (i.e. can be used in exportKey)
["verify"] //"verify" for public key import, "sign" for private key imports
);
// check signature with public key and signed data
var verified = await crypto.subtle.verify(
{ name: "ECDSA", namedCurve: "P-256", hash: { name: "SHA-256" } },
key,
signature,
signedData.buffer
);
// verified is false I want it to be true
console.log('verified', verified)
@getify
Copy link

getify commented Mar 10, 2024

the signature may or may not be ASN.1 wrapped!

yep... that's true. the spec basically says that -7 (ECDSA P-256) is the only one that's wrapped, and it says that other algorithms "should not" be wrapped. so I only call that function on that specific algorithm.

@getify
Copy link

getify commented Mar 12, 2024

@dagnelies

I recommend using a lib like mine

Your lib is great, and quite comprehensive. I've been working on a smaller lib for a more narrow purpose (and I link to yours as an alternative option):

https://github.com/mylofi/webauthn-local-client

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment