-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
258 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
181
modules/passkey/contracts/verifiers/WebAuthnVerifier.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |