Skip to content

Commit

Permalink
♻️ Add WebAuthn plain verify method (#1156)
Browse files Browse the repository at this point in the history
  • Loading branch information
Vectorized authored Nov 6, 2024
1 parent eef8c4f commit fef3cd7
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 86 deletions.
100 changes: 57 additions & 43 deletions src/utils/WebAuthn.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,28 +21,16 @@ library WebAuthn {
// The WebAuthn client data JSON.
// See: https://www.w3.org/TR/webauthn-2/#dom-authenticatorresponse-clientdatajson.
string clientDataJSON;
// The index at which "challenge":"..." occurs in `clientDataJSON`.
// Start index of "challenge":"..." in `clientDataJSON`.
uint256 challengeIndex;
// The index at which "type":"..." occurs in `clientDataJSON`.
// Start index of "type":"..." in `clientDataJSON`.
uint256 typeIndex;
// The r value of secp256r1 signature
// The r value of secp256r1 signature.
bytes32 r;
// The s value of secp256r1 signature
// The s value of secp256r1 signature.
bytes32 s;
}

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @dev Bit 0 of the authenticator data struct, corresponding to the "User Present" bit.
/// See: https://www.w3.org/TR/webauthn-2/#flags.
uint256 private constant _AUTH_DATA_FLAGS_UP = 0x01;

/// @dev Bit 2 of the authenticator data struct, corresponding to the "User Verified" bit.
/// See: https://www.w3.org/TR/webauthn-2/#flags.
uint256 private constant _AUTH_DATA_FLAGS_UV = 0x04;

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* WEBAUTHN VERIFICATION OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
Expand Down Expand Up @@ -98,23 +86,27 @@ library WebAuthn {
function verify(
bytes memory challenge,
bool requireUserVerification,
WebAuthnAuth memory auth,
bytes memory authenticatorData,
string memory clientDataJSON,
uint256 challengeIndex,
uint256 typeIndex,
bytes32 r,
bytes32 s,
bytes32 x,
bytes32 y
) internal view returns (bool result) {
bytes32 messageHash;
string memory encoded = Base64.encode(challenge, true, true);
bytes32 h; // The message hash.
string memory b = Base64.encode(challenge, true, true);
/// @solidity memory-safe-assembly
assembly {
let clientDataJSON := mload(add(auth, 0x20))
let n := mload(clientDataJSON) // `clientDataJSON`'s length.
let o := add(clientDataJSON, 0x20) // Start of `clientData`'s bytes.
{
let c := mload(add(auth, 0x40)) // Challenge index in `clientDataJSON`.
let t := mload(add(auth, 0x60)) // Type index in `clientDataJSON`.
let l := mload(encoded) // Cache `encoded`'s length.
let q := add(l, 0x0d) // Length of `encoded` prefixed with '"challenge":"'.
mstore(encoded, shr(152, '"challenge":"')) // Temp prefix with '"challenge":"'.
let n := mload(clientDataJSON) // `clientDataJSON`'s length.
let o := add(clientDataJSON, 0x20) // Start of `clientData`'s bytes.
let c := challengeIndex
let t := typeIndex
let l := mload(b) // Cache `b`'s length.
let q := add(l, 0x0d) // Length of `b` prefixed with '"challenge":"'.
mstore(b, shr(152, '"challenge":"')) // Temp prefix with '"challenge":"'.
result :=
and(
// 11. Verify JSON's type. Also checks for possible addition overflows.
Expand All @@ -124,39 +116,61 @@ library WebAuthn {
),
// 12. Verify JSON's challenge. Includes a check for the closing '"'.
and(
eq(keccak256(add(o, c), q), keccak256(add(encoded, 0x13), q)),
eq(keccak256(add(o, c), q), keccak256(add(b, 0x13), q)),
and(eq(byte(0, mload(add(add(o, c), q))), 34), lt(add(q, c), n))
)
)
mstore(encoded, l) // Restore `encoded`'s length, in case of string interning.
mstore(b, l) // Restore `b`'s length, in case of string interning.
h := n // Reuse `h` for `n`, to prevent stack-too-deep.
b := o // Reuse `b` for `o`, to prevent stack-too-deep.
}
// Skip 13., 14., 15.
let l := mload(mload(auth)) // Length of `authenticatorData`.
let r :=
or(
// 16. Verify that the "User Present" flag is set.
_AUTH_DATA_FLAGS_UP,
// 17. Verify that the "User Verified" flag is set, if required.
mul(_AUTH_DATA_FLAGS_UV, iszero(iszero(requireUserVerification)))
)
result :=
and(and(result, gt(l, 0x20)), eq(and(byte(0, mload(add(mload(auth), 0x40))), r), r))
let l := mload(authenticatorData) // Length of `authenticatorData`
// 16. Verify that the "User Present" flag is set (bit 0).
// 17. Verify that the "User Verified" flag is set (bit 2), if required.
// See: https://www.w3.org/TR/webauthn-2/#flags.
let u := or(1, shl(2, iszero(iszero(requireUserVerification))))
// forgefmt: disable-next-item
result := and(and(result, gt(l, 0x20)),
eq(and(byte(0, mload(add(authenticatorData, 0x40))), u), u))
if result {
let p := add(mload(auth), 0x20) // Start of `authenticatorData`'s bytes.
let p := add(authenticatorData, 0x20) // Start of `authenticatorData`'s bytes.
let e := add(p, l) // Location of the word after `authenticatorData`.
let w := mload(e) // Cache the word after `authenticatorData`.
// 19. Compute `sha256(clientDataJSON)`.
pop(staticcall(gas(), 2, o, n, e, 0x20))
pop(staticcall(gas(), 2, b, h, e, 0x20))
// 20. Compute `sha256(authenticatorData ‖ sha256(clientDataJSON))`.
pop(staticcall(gas(), 2, p, add(l, 0x20), 0x00, returndatasize()))
// `returndatasize()` is `0x20` on `sha256` success, and `0x00` otherwise.
if iszero(returndatasize()) { invalid() }
mstore(e, w) // Restore the word after `authenticatorData`, in case of reuse.
messageHash := mload(0x00)
h := mload(0x00)
}
}
// `P256.verifySignature` returns false if `s > N/2` due to the malleability check.
return result && P256.verifySignature(messageHash, auth.r, auth.s, x, y);
if (result) result = P256.verifySignature(h, r, s, x, y);
}

/// @dev Returns if the `auth` is valid.
function verify(
bytes memory challenge,
bool requireUserVerification,
WebAuthnAuth memory auth,
bytes32 x,
bytes32 y
) internal view returns (bool) {
return verify(
challenge,
requireUserVerification,
auth.authenticatorData,
auth.clientDataJSON,
auth.challengeIndex,
auth.typeIndex,
auth.r,
auth.s,
x,
y
);
}

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
Expand Down
100 changes: 57 additions & 43 deletions src/utils/g/WebAuthn.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ struct WebAuthnAuth {
// The WebAuthn client data JSON.
// See: https://www.w3.org/TR/webauthn-2/#dom-authenticatorresponse-clientdatajson.
string clientDataJSON;
// The index at which "challenge":"..." occurs in `clientDataJSON`.
// Start index of "challenge":"..." in `clientDataJSON`.
uint256 challengeIndex;
// The index at which "type":"..." occurs in `clientDataJSON`.
// Start index of "type":"..." in `clientDataJSON`.
uint256 typeIndex;
// The r value of secp256r1 signature
// The r value of secp256r1 signature.
bytes32 r;
// The s value of secp256r1 signature
// The s value of secp256r1 signature.
bytes32 s;
}

Expand All @@ -35,18 +35,6 @@ import {P256} from "../P256.sol";
/// @author Modified from Daimo WebAuthn (https://github.com/daimo-eth/p256-verifier/blob/master/src/WebAuthn.sol)
/// @author Modified from Coinbase WebAuthn (https://github.com/base-org/webauthn-sol/blob/main/src/WebAuthn.sol)
library WebAuthn {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @dev Bit 0 of the authenticator data struct, corresponding to the "User Present" bit.
/// See: https://www.w3.org/TR/webauthn-2/#flags.
uint256 private constant _AUTH_DATA_FLAGS_UP = 0x01;

/// @dev Bit 2 of the authenticator data struct, corresponding to the "User Verified" bit.
/// See: https://www.w3.org/TR/webauthn-2/#flags.
uint256 private constant _AUTH_DATA_FLAGS_UV = 0x04;

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* WEBAUTHN VERIFICATION OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
Expand Down Expand Up @@ -102,23 +90,27 @@ library WebAuthn {
function verify(
bytes memory challenge,
bool requireUserVerification,
WebAuthnAuth memory auth,
bytes memory authenticatorData,
string memory clientDataJSON,
uint256 challengeIndex,
uint256 typeIndex,
bytes32 r,
bytes32 s,
bytes32 x,
bytes32 y
) internal view returns (bool result) {
bytes32 messageHash;
string memory encoded = Base64.encode(challenge, true, true);
bytes32 h; // The message hash.
string memory b = Base64.encode(challenge, true, true);
/// @solidity memory-safe-assembly
assembly {
let clientDataJSON := mload(add(auth, 0x20))
let n := mload(clientDataJSON) // `clientDataJSON`'s length.
let o := add(clientDataJSON, 0x20) // Start of `clientData`'s bytes.
{
let c := mload(add(auth, 0x40)) // Challenge index in `clientDataJSON`.
let t := mload(add(auth, 0x60)) // Type index in `clientDataJSON`.
let l := mload(encoded) // Cache `encoded`'s length.
let q := add(l, 0x0d) // Length of `encoded` prefixed with '"challenge":"'.
mstore(encoded, shr(152, '"challenge":"')) // Temp prefix with '"challenge":"'.
let n := mload(clientDataJSON) // `clientDataJSON`'s length.
let o := add(clientDataJSON, 0x20) // Start of `clientData`'s bytes.
let c := challengeIndex
let t := typeIndex
let l := mload(b) // Cache `b`'s length.
let q := add(l, 0x0d) // Length of `b` prefixed with '"challenge":"'.
mstore(b, shr(152, '"challenge":"')) // Temp prefix with '"challenge":"'.
result :=
and(
// 11. Verify JSON's type. Also checks for possible addition overflows.
Expand All @@ -128,39 +120,61 @@ library WebAuthn {
),
// 12. Verify JSON's challenge. Includes a check for the closing '"'.
and(
eq(keccak256(add(o, c), q), keccak256(add(encoded, 0x13), q)),
eq(keccak256(add(o, c), q), keccak256(add(b, 0x13), q)),
and(eq(byte(0, mload(add(add(o, c), q))), 34), lt(add(q, c), n))
)
)
mstore(encoded, l) // Restore `encoded`'s length, in case of string interning.
mstore(b, l) // Restore `b`'s length, in case of string interning.
h := n // Reuse `h` for `n`, to prevent stack-too-deep.
b := o // Reuse `b` for `o`, to prevent stack-too-deep.
}
// Skip 13., 14., 15.
let l := mload(mload(auth)) // Length of `authenticatorData`.
let r :=
or(
// 16. Verify that the "User Present" flag is set.
_AUTH_DATA_FLAGS_UP,
// 17. Verify that the "User Verified" flag is set, if required.
mul(_AUTH_DATA_FLAGS_UV, iszero(iszero(requireUserVerification)))
)
result :=
and(and(result, gt(l, 0x20)), eq(and(byte(0, mload(add(mload(auth), 0x40))), r), r))
let l := mload(authenticatorData) // Length of `authenticatorData`
// 16. Verify that the "User Present" flag is set (bit 0).
// 17. Verify that the "User Verified" flag is set (bit 2), if required.
// See: https://www.w3.org/TR/webauthn-2/#flags.
let u := or(1, shl(2, iszero(iszero(requireUserVerification))))
// forgefmt: disable-next-item
result := and(and(result, gt(l, 0x20)),
eq(and(byte(0, mload(add(authenticatorData, 0x40))), u), u))
if result {
let p := add(mload(auth), 0x20) // Start of `authenticatorData`'s bytes.
let p := add(authenticatorData, 0x20) // Start of `authenticatorData`'s bytes.
let e := add(p, l) // Location of the word after `authenticatorData`.
let w := mload(e) // Cache the word after `authenticatorData`.
// 19. Compute `sha256(clientDataJSON)`.
pop(staticcall(gas(), 2, o, n, e, 0x20))
pop(staticcall(gas(), 2, b, h, e, 0x20))
// 20. Compute `sha256(authenticatorData ‖ sha256(clientDataJSON))`.
pop(staticcall(gas(), 2, p, add(l, 0x20), 0x00, returndatasize()))
// `returndatasize()` is `0x20` on `sha256` success, and `0x00` otherwise.
if iszero(returndatasize()) { invalid() }
mstore(e, w) // Restore the word after `authenticatorData`, in case of reuse.
messageHash := mload(0x00)
h := mload(0x00)
}
}
// `P256.verifySignature` returns false if `s > N/2` due to the malleability check.
return result && P256.verifySignature(messageHash, auth.r, auth.s, x, y);
if (result) result = P256.verifySignature(h, r, s, x, y);
}

/// @dev Returns if the `auth` is valid.
function verify(
bytes memory challenge,
bool requireUserVerification,
WebAuthnAuth memory auth,
bytes32 x,
bytes32 y
) internal view returns (bool) {
return verify(
challenge,
requireUserVerification,
auth.authenticatorData,
auth.clientDataJSON,
auth.challengeIndex,
auth.typeIndex,
auth.r,
auth.s,
x,
y
);
}

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
Expand Down

0 comments on commit fef3cd7

Please sign in to comment.