diff --git a/modules/4337/contracts/experimental/verifiers/WebAuthnVerifier.sol b/modules/4337/contracts/experimental/verifiers/WebAuthnVerifier.sol index 989158da6..fe478d66d 100644 --- a/modules/4337/contracts/experimental/verifiers/WebAuthnVerifier.sol +++ b/modules/4337/contracts/experimental/verifiers/WebAuthnVerifier.sol @@ -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) {} /** diff --git a/modules/passkey/contracts/vendor/FCL/utils/Base64Url.sol b/modules/passkey/contracts/vendor/FCL/utils/Base64Url.sol new file mode 100644 index 000000000..0d9ac2d0a --- /dev/null +++ b/modules/passkey/contracts/vendor/FCL/utils/Base64Url.sol @@ -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; + } +} diff --git a/modules/passkey/contracts/verifiers/WebAuthnVerifier.sol b/modules/passkey/contracts/verifiers/WebAuthnVerifier.sol new file mode 100644 index 000000000..1b3c7a512 --- /dev/null +++ b/modules/passkey/contracts/verifiers/WebAuthnVerifier.sol @@ -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); + } +}