Skip to content

Commit

Permalink
🐛 fix challenge offset constants
Browse files Browse the repository at this point in the history
The constant value used as an offset to retrieve the
challenge from the webauthn response was only correct
for payload of type `webauthn.get`. This commit makes
the offset value dynamic to also handle `webauthn.create`
payloads. Both payloads are now correctly supported.
  • Loading branch information
qd-qd committed Mar 25, 2024
1 parent 8590224 commit faf33fe
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 6 deletions.
22 changes: 19 additions & 3 deletions src/WebAuthn256r1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@ pragma solidity >=0.8.19 <0.9.0;

import { ECDSA256r1 } from "../lib/secp256r1-verify/src/ECDSA256r1.sol";
import { Base64 } from "../lib/solady/src/utils/Base64.sol";
import { UV_FLAG_MASK, OFFSET_CLIENT_CHALLENGE, OFFSET_FLAG } from "src/utils.sol";
import {
UV_FLAG_MASK,

Check warning on line 7 in src/WebAuthn256r1.sol

View workflow job for this annotation

GitHub Actions / lint

imported name UV_FLAG_MASK is not used

Check warning on line 7 in src/WebAuthn256r1.sol

View workflow job for this annotation

GitHub Actions / lint

imported name UV_FLAG_MASK is not used
OFFSET_CLIENT_CHALLENGE_GET,

Check warning on line 8 in src/WebAuthn256r1.sol

View workflow job for this annotation

GitHub Actions / lint

imported name OFFSET_CLIENT_CHALLENGE_GET is not used

Check warning on line 8 in src/WebAuthn256r1.sol

View workflow job for this annotation

GitHub Actions / lint

imported name OFFSET_CLIENT_CHALLENGE_GET is not used
OFFSET_CLIENT_CHALLENGE_CREATE,

Check warning on line 9 in src/WebAuthn256r1.sol

View workflow job for this annotation

GitHub Actions / lint

imported name OFFSET_CLIENT_CHALLENGE_CREATE is not used

Check warning on line 9 in src/WebAuthn256r1.sol

View workflow job for this annotation

GitHub Actions / lint

imported name OFFSET_CLIENT_CHALLENGE_CREATE is not used
OFFSET_FLAG,

Check warning on line 10 in src/WebAuthn256r1.sol

View workflow job for this annotation

GitHub Actions / lint

imported name OFFSET_FLAG is not used

Check warning on line 10 in src/WebAuthn256r1.sol

View workflow job for this annotation

GitHub Actions / lint

imported name OFFSET_FLAG is not used
OFFSET_CLIENT_TYPE,

Check warning on line 11 in src/WebAuthn256r1.sol

View workflow job for this annotation

GitHub Actions / lint

imported name OFFSET_CLIENT_TYPE is not used

Check warning on line 11 in src/WebAuthn256r1.sol

View workflow job for this annotation

GitHub Actions / lint

imported name OFFSET_CLIENT_TYPE is not used
TYPE_GET_INDICATOR,

Check warning on line 12 in src/WebAuthn256r1.sol

View workflow job for this annotation

GitHub Actions / lint

imported name TYPE_GET_INDICATOR is not used

Check warning on line 12 in src/WebAuthn256r1.sol

View workflow job for this annotation

GitHub Actions / lint

imported name TYPE_GET_INDICATOR is not used
TYPE_CREATE_INDICATOR

Check warning on line 13 in src/WebAuthn256r1.sol

View workflow job for this annotation

GitHub Actions / lint

imported name TYPE_CREATE_INDICATOR is not used

Check warning on line 13 in src/WebAuthn256r1.sol

View workflow job for this annotation

GitHub Actions / lint

imported name TYPE_CREATE_INDICATOR is not used
} from "src/utils.sol";

/// @title WebAuthn256r1
/// @notice A library to verify ECDSA signature though WebAuthn on the secp256r1 curve
Expand Down Expand Up @@ -76,12 +84,20 @@ library WebAuthn256r1 {
// Encode the client challenge in base64 and explicitly convert it to bytes
bytes memory challengeEncoded = bytes(Base64.encode(clientChallenge, true, true));

// Extract the client challenge offset based on the client type
// By checking the indicator we can determine if we need to use the offset for the get or create flow
// @dev: we don't need to check the overflow here as the EVM will automatically revert if
// `OFFSET_CLIENT_TYPE` is out of bound.
uint256 clientChallengeOffset = clientData[OFFSET_CLIENT_TYPE] == TYPE_CREATE_INDICATOR
? OFFSET_CLIENT_CHALLENGE_CREATE
: OFFSET_CLIENT_CHALLENGE_GET;

// Extract the challenge from the client data and hash it
// @dev: we don't need to check the overflow here as the EVM will automatically revert if
// `OFFSET_CLIENT_CHALLENGE + challengeEncoded.length` overflow. This is because we will
// `clientChallengeOffset + challengeEncoded.length` overflow. This is because we will
// try to access a chunk of memory by passing an end index lower than the start index
bytes32 challengeHashed =
keccak256(clientData[OFFSET_CLIENT_CHALLENGE:(OFFSET_CLIENT_CHALLENGE + challengeEncoded.length)]);
keccak256(clientData[clientChallengeOffset:(clientChallengeOffset + challengeEncoded.length)]);

// Hash the encoded challenge and check both challenges are equal
if (keccak256(challengeEncoded) != challengeHashed) {
Expand Down
24 changes: 21 additions & 3 deletions src/utils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,32 @@ bytes1 constant UP_FLAG_MASK = 0x01;
bytes1 constant UV_FLAG_MASK = 0x04;
bytes1 constant BOTH_FLAG_MASK = 0x05;

// The offset of the client challenge in the client data
uint256 constant OFFSET_CLIENT_CHALLENGE = 0x24;
// The challenge is stored in the client data field of the WebAuthn response.
// The client data is a JSON object that contains a type, the challenge and the origin.
// For passkeys, the type is always "webauthn.get" or "webauthn.create".
// Ex: `{"type":"webauthn.create","challenge":<challenge>,"origin":"<origin>"}`
// Ex: `{"type":"webauthn.get","challenge":<challenge>,"origin":"<origin>"}`
//
// The client data always starts with a constant value which is the same for both
// the get and create flows. This constant value correspond to the beginning of
// the JSON object which is: `{"type":"webauthn.`. The next byte allow to distinguish
// between the get and create flows. It is either `g` (0x67) for get or `c` (0x63) for create.
// The three constants located below can be used to know if a WebAuthn response is a get or create flow.
uint256 constant OFFSET_CLIENT_TYPE = 0x12;
bytes1 constant TYPE_GET_INDICATOR = 0x67;
bytes1 constant TYPE_CREATE_INDICATOR = 0x63;
// The offset of the client challenge for the get flow
// Correspond to the constant value `{"type":"webauthn.get","challenge":`
uint256 constant OFFSET_CLIENT_CHALLENGE_GET = 0x24;
// The offset of the client challenge for the create flow
// Correspond to the constant value `{"type":"webauthn.create","challenge":`
uint256 constant OFFSET_CLIENT_CHALLENGE_CREATE = 0x27;
// The offset where the credential ID length starts and its length
uint256 constant OFFSET_CREDID_LENGTH = 0x35; // 53
uint256 constant CREDID_LENGTH_LENGTH = 0x02;
// The offset where the credential ID value starts
uint256 constant OFFSET_CREDID = 0x37; // 55
// The offset of the flag mask
uint256 constant OFFSET_FLAG = 0x20; // 32

// The length of the public key coordinates
uint256 constant P256R1_PUBKEY_COORD_LENGTH = 0x20; // 32

0 comments on commit faf33fe

Please sign in to comment.