Skip to content

Commit

Permalink
Add WebAuthnVerifier contract
Browse files Browse the repository at this point in the history
  • Loading branch information
mmv08 committed Mar 4, 2024
1 parent df6f17d commit 59cd9ca
Show file tree
Hide file tree
Showing 3 changed files with 258 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ interface IWebAuthnVerifier {
* Both functions take the authenticator data, authenticator flags, challenge, client data fields, r and s components of the signature, and x and y coordinates of the public key as input.
* The `verifyWebAuthnSignature` function also checks for signature malleability by ensuring that the s component is less than the curve order n/2.
*/
contract WebAuthnVerifier is IWebAuthnVerifier, P256Wrapper {
contract WebAuthnVerifier is IWebAuthnVerifier {
constructor(address verifier) P256Wrapper(verifier) {}

/**
Expand Down
76 changes: 76 additions & 0 deletions modules/passkey/contracts/vendor/FCL/utils/Base64Url.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

/**
* @dev Encode (without '=' padding)
* @author evmbrahmin, adapted from hiromin's Base64URL libraries
*/
library Base64Url {
/**
* @dev Base64Url Encoding Table
*/
string internal constant ENCODING_TABLE =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";

function encode(bytes memory data) internal pure returns (string memory) {
if (data.length == 0) return "";

// Load the table into memory
string memory table = ENCODING_TABLE;

string memory result = new string(4 * ((data.length + 2) / 3));

// @solidity memory-safe-assembly
assembly {
let tablePtr := add(table, 1)
let resultPtr := add(result, 32)

for {
let dataPtr := data
let endPtr := add(data, mload(data))
} lt(dataPtr, endPtr) {

} {
dataPtr := add(dataPtr, 3)
let input := mload(dataPtr)

mstore8(
resultPtr,
mload(add(tablePtr, and(shr(18, input), 0x3F)))
)
resultPtr := add(resultPtr, 1)

mstore8(
resultPtr,
mload(add(tablePtr, and(shr(12, input), 0x3F)))
)
resultPtr := add(resultPtr, 1)

mstore8(
resultPtr,
mload(add(tablePtr, and(shr(6, input), 0x3F)))
)
resultPtr := add(resultPtr, 1)

mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F))))
resultPtr := add(resultPtr, 1)
}

// Remove the padding adjustment logic
switch mod(mload(data), 3)
case 1 {
// Adjust for the last byte of data
resultPtr := sub(resultPtr, 2)
}
case 2 {
// Adjust for the last two bytes of data
resultPtr := sub(resultPtr, 1)
}

// Set the correct length of the result string
mstore(result, sub(resultPtr, add(result, 32)))
}

return result;
}
}
181 changes: 181 additions & 0 deletions modules/passkey/contracts/verifiers/WebAuthnVerifier.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// SPDX-License-Identifier: LGPL-3.0-only
/* solhint-disable one-contract-per-file */
pragma solidity >=0.8.0;

import {IP256Verifier, P256VerifierLib} from "./IP256Verifier.sol";
import {Base64Url} from "../vendor/FCL/utils/Base64Url.sol";

/**
* @title WebAuthnConstants
* @dev Library that defines constants for WebAuthn verification.
*/
library WebAuthnConstants {
/**
* @dev Constants representing the flags in the authenticator data of a WebAuthn verification.
*
* - `AUTH_DATA_FLAGS_UP`: User Presence (UP) flag in the authenticator data.
* - `AUTH_DATA_FLAGS_UV`: User Verification (UV) flag in the authenticator data.
* - `AUTH_DATA_FLAGS_BE`: Attested Credential Data (BE) flag in the authenticator data.
* - `AUTH_DATA_FLAGS_BS`: Extension Data (BS) flag in the authenticator data.
*/
bytes1 internal constant AUTH_DATA_FLAGS_UP = 0x01;
bytes1 internal constant AUTH_DATA_FLAGS_UV = 0x04;
bytes1 internal constant AUTH_DATA_FLAGS_BE = 0x08;
bytes1 internal constant AUTH_DATA_FLAGS_BS = 0x10;
}

/**
* @title IWebAuthnVerifier
* @dev Interface for verifying WebAuthn signatures.
*/
interface IWebAuthnVerifier {
/**
* @dev Verifies a WebAuthn signature allowing malleability.
* @param authenticatorData The authenticator data.
* @param authenticatorFlags The authenticator flags.
* @param challenge The challenge.
* @param clientDataFields The client data fields.
* @param rs The signature components.
* @param qx The x-coordinate of the public key.
* @param qy The y-coordinate of the public key.
* @return A boolean indicating whether the signature is valid.
*/
function verifyWebAuthnSignatureAllowMalleability(
bytes calldata authenticatorData,
bytes1 authenticatorFlags,
bytes32 challenge,
bytes calldata clientDataFields,
uint256[2] calldata rs,
uint256 qx,
uint256 qy
) external view returns (bool);

/**
* @dev Verifies a WebAuthn signature.
* @param authenticatorData The authenticator data.
* @param authenticatorFlags The authenticator flags.
* @param challenge The challenge.
* @param clientDataFields The client data fields.
* @param rs The signature components.
* @param qx The x-coordinate of the public key.
* @param qy The y-coordinate of the public key.
* @return A boolean indicating whether the signature is valid.
*/
function verifyWebAuthnSignature(
bytes calldata authenticatorData,
bytes1 authenticatorFlags,
bytes32 challenge,
bytes calldata clientDataFields,
uint256[2] calldata rs,
uint256 qx,
uint256 qy
) external view returns (bool);
}

/**
* @title WebAuthnVerifier
* @dev A contract that implements a WebAuthn signature verification following the precompile's interface.
* The contract inherits from `P256VerifierWithWrapperFunctions` and provides wrapper functions for WebAuthn signatures.
*
* This contract is designed to allow verifying signatures from WebAuthn-compatible devices, such as biometric authenticators.
* It works by generating a signing message based on the authenticator data, challenge, and client data fields, and then verifying the signature using the P256 elliptic curve.
*
* The contract provides two main functions:
* - `verifyWebAuthnSignatureAllowMalleability`: Verifies the signature of a WebAuthn message using P256 elliptic curve, allowing for signature malleability.
* - `verifyWebAuthnSignature`: Verifies the signature of a WebAuthn message using the P256 elliptic curve, checking for signature malleability.
*
* Both functions take the authenticator data, authenticator flags, challenge, client data fields, r and s components of the signature, and x and y coordinates of the public key as input.
* The `verifyWebAuthnSignature` function also checks for signature malleability by ensuring that the s component is less than the curve order n/2.
*/
contract WebAuthnVerifier is IWebAuthnVerifier {
IP256Verifier internal immutable P256_VERIFIER;

constructor(IP256Verifier verifier) {
P256_VERIFIER = verifier;
}

/**
* @dev Generates a signing message based on the authenticator data, challenge, and client data fields.
* @param authenticatorData Authenticator data.
* @param challenge Challenge.
* @param clientDataFields Client data fields.
* @return message Signing message.
*/
function signingMessage(
bytes calldata authenticatorData,
bytes32 challenge,
bytes calldata clientDataFields
) internal pure returns (bytes32 message) {
string memory encodedChallenge = Base64Url.encode(abi.encodePacked(challenge));
/* solhint-disable quotes */
bytes memory clientDataJson = abi.encodePacked(
'{"type":"webauthn.get","challenge":"',
encodedChallenge,
'",',
clientDataFields,
"}"
);
/* solhint-enable quotes */
message = sha256(abi.encodePacked(authenticatorData, sha256(clientDataJson)));
}

/**
* @dev Verifies the signature of a WebAuthn message using P256 elliptic curve, allowing for signature malleability.
* @param authenticatorData Authenticator data.
* @param authenticatorFlags Authenticator flags.
* @param challenge Challenge.
* @param clientDataFields Client data fields.
* @param rs R and S components of the signature.
* @param qx X coordinate of the public key.
* @param qy Y coordinate of the public key.
* @return result Whether the signature is valid.
*/
function verifyWebAuthnSignatureAllowMalleability(
bytes calldata authenticatorData,
bytes1 authenticatorFlags,
bytes32 challenge,
bytes calldata clientDataFields,
uint256[2] calldata rs,
uint256 qx,
uint256 qy
) public view returns (bool result) {
// check authenticator flags, e.g. for User Presence (0x01) and/or User Verification (0x04)
if ((authenticatorData[32] & authenticatorFlags) != authenticatorFlags) {
return false;
}

bytes32 message = signingMessage(authenticatorData, challenge, clientDataFields);

result = P256VerifierLib.verifySignatureAllowMalleability(P256_VERIFIER, message, rs[0], rs[1], qx, qy);
}

/**
* @dev Verifies the signature of a WebAuthn message using the P256 elliptic curve, checking for signature malleability.
* @param authenticatorData Authenticator data.
* @param authenticatorFlags Authenticator flags.
* @param challenge Challenge.
* @param clientDataFields Client data fields.
* @param rs R and S components of the signature.
* @param qx X coordinate of the public key.
* @param qy Y coordinate of the public key.
* @return result Whether the signature is valid.
*/
function verifyWebAuthnSignature(
bytes calldata authenticatorData,
bytes1 authenticatorFlags,
bytes32 challenge,
bytes calldata clientDataFields,
uint256[2] calldata rs,
uint256 qx,
uint256 qy
) public view returns (bool result) {
// check authenticator flags, e.g. for User Presence (0x01) and/or User Verification (0x04)
if ((authenticatorData[32] & authenticatorFlags) != authenticatorFlags) {
return false;
}

bytes32 message = signingMessage(authenticatorData, challenge, clientDataFields);

result = P256VerifierLib.verifySignature(P256_VERIFIER, message, rs[0], rs[1], qx, qy);
}
}

0 comments on commit 59cd9ca

Please sign in to comment.