Share
Explore

WebAuthn-ish Authentication

For the MANY Network
Felix Morency
|
Tommy Tan
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
Build the COSE ProtectedHeader as usual, using the public key from 2.
Set the challenge to the following CBOR map
{0: CBOR(ProtectedHeader), 1: Base64(SHA512(RequestMessage))}
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
validating that the sender is not anonymous

We should drop non-webauthn requests. See
Q: Exactly what security guarantees can we provide to users since we’re not spec compliant?
A:
Q: Are we ok with prompting the user twice during registration in order to save the credentials to the k-v store?
A: Yes
Q: Does every dApp need its own instance of k-v store to store credentials?
A: Yes for now. Ultimately, we want to have the same experience as OAuth2.
What didn’t work with the previous plan?
The previous plan used the COSE signature/verification mechanism to perform the WebAuthn signature verification. However, it failed because
Cose signature & verification introduces an hardcoded CoseSignatureContext
The signature is performed on more than just ProtectedHeader + Payload
This is part of the spec, and we didn’t want to derive from it
The ProtectedHeader needs to be part of the challenge; otherwise, it could be tampered with.

Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.