-
-
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) |
philholden
commented
Mar 9, 2024
via email
@getify Pay attention that depending on the algorithm used by the credential, the signature may or may not be ASN.1 wrapped!
Moreover, I would like to emphasise that whether the signature is ASN.1 wrapped or not depends on the algorithm used according to the spec https://w3c.github.io/webauthn/#sctn-signature-attestation-types
6.5.6. Signature Formats for Packed Attestation, FIDO U2F Attestation, and Assertion Signatures
[...] For COSEAlgorithmIdentifier -7 (ES256) [...] the sig value MUST be encoded as an ASN.1 [...]
[...] For COSEAlgorithmIdentifier -257 (RS256) [...] The signature is not ASN.1 wrapped.
[...] For COSEAlgorithmIdentifier -37 (PS256) [...] The signature is not ASN.1 wrapped.What about the -8 algo that is also recommended? ASN.1 wrapped or not? I guess that's simply missing in the specs right now. ;)
I recommend using a lib like mine to avoid such issues: https://webauthn.passwordless.id/demos/playground.html , you can also verify signatures there.
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.
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):