WebAuthn solves the use-case where one needs to register a new account (registration) and sign in to said account (authentication), i.e., open a session.
Standard WebAuthn application is NOT the use case we are trying to solve at The Lifted Initiative. Our use case is stateless.
TODO
Clean and Move this to the specification repo.
The Lifted Initiative use-case
Key-pair creation on HSM from the browser. The private key is not accessible. The HSM is performing cryptographic operations such as message signing.
A hash derived from the user’s public key forms the user’s public address.
Map a recall phrase to the public key & credential ID.
Map the public address to the public key & credential ID.
Store the map in the blockchain
Should one forget their public address, one should be able to retrieve their account from the blockchain using only the recall phrase + their HSM.
Should one forget their recall phrase, one should be able to retrieve their account from the blockchain using only their public address + their HSM.
Send signed messages to MANY to, e.g., execute transactions, such as sending tokens to another wallet.
Unfortunately, WebAuthn doesn’t allow a user to sign some random payload using the private key located on the HSM. Signing random payload is NOT part of the WebAuthn API.
After the key-pair creation, only the get() ceremony can perform the signature operation, which returns the signature of authData + sha256(clientDataJSON).
Solution
(Once on account creation) Perform the key-pair creation using WebAuthn create(). Return PublicKeyCredential
(Once on account creation) Extract the public key from the WebAuthn PublicKeyCredential
Perform the signature using WebAuthn get(). Return credentials .
The returned signature will match authData + sha256(clientDataJSON)
Create a CoseSign1 message
payload is a MANY RequestMessage
signature is an empty bstr
The ProtectedHeader is the one from 3. above
The UnprotectedHeader contains
webauthn entry set to true
authData entry set to credentials.response.authenticatorData (as Bytes)
clientData entry set to credentials.response.clientDataJSON (as a JSON string)
signature entry set to the credentials.response.signature
Send CoseSign1 to MANY server
MANY server receive the message
Is it a WebAuthn message?
YES!
Get clientData from UnprotectedHeader
Create JSON mapping from clientData
Verify the WebAuthn request type is webauthn.get
Verify request origin against the server allowed list
Get authData from UnprotectedHeader
Get signature from UnprotectedHeader
Get payload
Compute base64(sha512(payload))
Decode the challenge
Verify challenge hash against the payload hash
Decode the ProtectedHeader from the challenge
Verify the decoded ProtectedHeader against the CoseSign1 ProtectedHeader
Verify signature against authData + sha256(clientData)
Done!
NO!
Verify the message as usual
(Future: We should also perform additional verification depending on the type of PublicKeyCredential object we receive. Server could exclude weak authenticator, etc)
CONS
The key-pair create() part would need the user to touch its authenticator twice. First time to create the key-pair, and a second time to sign the message to get the recall phrase.
WebAuthn key creation & storage
The idstore.store is an endpoint of the MANY server. It is responsible for the recall phrase generation as well as storing the maps in the permanent storage.
Use the procedure described in Solution to handle the request on the Server.
* The CoseSign1 message is constructed following
CoseSign1 {
protected {
header {
alg: the algorithm extracted from the authData credential public key,
key_id: the public key extracted rom the authData credential public key,
... other fields as usual, including the "keyset"
}
}
unprotected {
webauthn: "true",
authData: credentials.response.authenticatorData,
clientData: credentials.response.clientDataJSON
signature: credentials.response.signature
}
payload: CBOR(RequestMessage),
signature: []
}
The COSE/WebAuthn procedure described in this document can be used to reach any MANY endpoint.
Further Discussions/Questions
Q: How secure is this?
A:
If webauthn entry is tampered with : message will not be handled correctly and result in an error.
If credentials.authData or credentials.clientDataJSON entries are tampered with : signature verification will fail. Signature should match authData + sha256(clientDataJSON)
If payload is tampered with: verification will fail. Real payload hash is part of the challenge and verified.
If signature is tampered with: verification will fail. signature will not match authData + sha256(clientDataJSON)
If ProtectedHeader is tampered with: verification will fail. ProtectedHeader is part of the challenge, this part of the signature .
If alg is tampered with: verification will fail. Wrong algorithm will be used to try and verify the signature.
If key_id is tampered with: verification will fail. The key_id will not match the key used to sign the challenge.
If keyset is tampered with: verification will fail. The keyset is part of the ProtectedHeader
Unit testing should cover those cases.
Q: What happens if the whole message is tampered with, i.e., every field is modified by a bad actor?
A: The public key/public address will change, and the verification will fail.
Q: What happens if an attacker tries to authenticate users by pulling the credentials from the k-v store?
A: Credentials are created and remembered by the browser relative to the origin the user was on when the credential was created. Even if the malicious site pulled the credential ID’s and tried to authenticate a user, this would not work because the credential was created on a different origin.
Q: How do we prevent malicious intents of writing to the k-v store?
A:
FM: Right now, only using
origin validation,
validating that the identity to be stored is valid