Skip to content

Instantly share code, notes, and snippets.

@philholden
Last active October 23, 2024 19:53
Show Gist options
  • 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: "[email protected]",
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)
@tjconcept
Copy link

As the ASN.1 DER encoded algorithms are few and will be replaced eventually, I’ll keep “hacking” it to save that dependency.
I’ll eventually publish a library with small webauthn related helpers (possibly individual ones). It’s at least the to/from JSON ponyfills, parsing the authentication bits and this unwrapping.

Does anyone have actual examples of assertations/attestations and respective public keys for other algorithms than -7? I’m particularly interested in a -8 fixture.

@getify
Copy link

getify commented Oct 15, 2024

@tjconcept

IIUC, is this helpful? I used a virtual-authenticator in Chrome (plus my webauthn-local-client library) to generate this info.


Attestation

origin: http://localhost:8080
rp.id: localhost
user.id (base64): RUdXZWU5dDlna3RvL1E9PQ==
public-key COSE: -8
public-key OID: 2b6570
public-key (base64): Zj4Jsg2QAVqGXZxIjwgYa61CBPE+aKiV8T2YtQLzJBE=
attestation-object (base64): o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViBSZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2NFAAAAAQECAwQFBgcIAQIDBAUGBwgAIMfDlCtULLYymHeov2p8ykNyZx4YzgpNxPrTwk0yF7/IpAEBAycgBiFYIGY+CbINkAFahl2cSI8IGGutQgTxPmiolfE9mLUC8yQR
client-data-json (base64): eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoicnplalpydmJ1WTJUbE1zV0g4Q2x2WW1BUjRNIiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ==

Assertion

origin: http://localhost:8080
rpId: localhost
challenge (base64): Q5Q9zaVkPyGMXe1SULLvGZhQF4w=
signature (base64): 9AXVFr7Ap3Xy+QsPArglgeOKUijfj8d2YBbzACrdQ9QwrU6vGfXsMEaJ6TA9WhyCsyInxA4UdKBMXIj8J0h3Ag==
authenticator-data (base64): SZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2MFAAAAAg==
client-data-json (base64): eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiUTVROXphVmtQeUdNWGUxU1VMTHZHWmhRRjR3Iiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ==

@tjconcept
Copy link

Yes! Thanks a lot. I got it working.

Is this the raw output of .create/get? I'm surprised the public key is in raw format.

I wasn't able to generate anything but -7 keys in Chrome... Did you configure it somehow or did it just create one when adding the algorithm to pubKeyCredParams?

@getify
Copy link

getify commented Oct 16, 2024

@tjconcept

Is this the raw output of .create/get?

Some of it is raw, some of it was processed by logic in my webauthn-local-client library, including some parsing, and the base64 encoding (for ease of output above).

I'm surprised the public key is in raw format.

Sorry for the confusion, no it doesn't come back raw.

It always comes back in SPKI format. My library uses this function for parsing the SPKI format out into the raw public-key and its algo's OID.

I should have included the SPKI, sorry. If you need that, I can re-run a new test to get those values. Or maybe someone clever can just re-pack the key and COSE I provided back into an equivalent SPKI. ;-)

Did you configure it somehow

The only "configuration" I did was in setting up the virtual-authenticator like this:

virtual-authenticator

did it just create one when adding the algorithm to pubKeyCredParams?

Yeah, the library passes in four requested algorithms, in preference order of -8 (ed25519), -7 (es256), -37 (rsassa-pss), and -257 (rsassa-pkcs1-v1_5). So basically, the pubKeyCredParams passed to create() is this array:

[
   { type: "public-key", alg: -8 },
   { type: "public-key", alg: -7 },
   { type: "public-key", alg: -37 },
   { type: "public-key", alg: -257 }
]

@tjconcept
Copy link

tjconcept commented Oct 17, 2024

Interesting. What's your OS? I'm on MacOS, and can only produce -7 keys, regardless of the flagged support.

Ideally, I could use the output of toJSON (or similar ponyfill) from both and attestation and an assertion using each algo 😇
I'll then use it as a test fixture for both my ponyfills of toJSON, related fromJSON functions, and verify-functions, in a webauthn stand-alone low-level helpers library (to be published).

@getify
Copy link

getify commented Oct 17, 2024

@tjconcept I'm on windows 11. I'm surprised that OS would change how Chrome's virtual authenticator works, but I suppose it's possible.

In any case, if you'd like to provide a snippet of code (standalone, whatever) that I can run for you, and send you results, I'm happy to help with that.

@tjconcept
Copy link

It wasn't the OS after all, sorry. I'm not sure why, but I now managed to produce the same through Chrome's virtual authenticator. Thanks anyway 🙂

I've put everything into a couple of highly-specific low-level libraries as well as a brief demo (without all the irrelevant boilerplate and cruft that the other ones I found carry).

I hope they can be helpful to others too. Any feedback is appreciated.

https://github.com/tjconcept/webauthn-tools
https://github.com/tjconcept/webauthn-json
https://github.com/tjconcept/webauthn-demo

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