-
-
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) |
@dagnelies Thank you
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. ;)
Unlike ECDSA, EdDSA defines a single canonical signature encoding (which consists of the concatenation of two fixed-length byte strings), and symmetrically the EdDSA verification procedure accepts only this encoding. So in EdDSA there is no ambiguity in how signatures are represented.
I'm having issues supporting Passkeys created by Dashlane. I hope someone here is able to crack this one.
publicKeyAlgorithm: -7
transports: [internal, hybrid]
authenticatorAttachment: platform
type: public-key
crypto.subtle.importKey(
'spki',
// base64url(attestation.response.getPublicKey())
decodeBase64Url('pQECAyYgASFYIKWn5SwwD4LmJy3JHe0f396dpUyLo1RYu73ByigzTmViIlggB_TICGayY6pKSA322an0cYMK3_oaGX9p6_6ENG0p9j8'),
{name: 'ECDSA', namedCurve: 'P-256'},
false,
['verify'],
)
// ASN.1 error: unexpected ASN.1 DER tag: expected SEQUENCE, got CONTEXT-SPECIFIC [5] (constructed)
When decoding the attestation object and comparing the raw key bits with those of .getPublicKey()
, they are identical. I assume it is because both are COSE-encoded (CBOR).
Am I correct that this is apparently a bug in either Dashlane or Chrome as this property should be DER-encoded?
Given this, which is unlikely to change anytime soon, what's the best way to work around it? Is the AAGUID a good way to identify Dashlane? Would it be problematic to simply "cut and paste" the x and y components from the bytes of the COSE key onto a static SPKI header (also naively assuming same key size)?
@jolestar Pay attention, how you encode the signature must depend on the algorithm! (crazy huh!) I'd like to post an old reply of mine: