@@ -60,11 +60,6 @@ library WebAuthn {
6060 /// @dev Bit 4 of the authenticator data flags: "Backup State" bit.
6161 bytes1 private constant AUTH_DATA_FLAGS_BS = 0x10 ;
6262
63- /// @dev The expected type string in the client data JSON when verifying assertion signatures.
64- /// https://www.w3.org/TR/webauthn-2/#dom-collectedclientdata-type
65- // solhint-disable-next-line quotes
66- bytes32 private constant EXPECTED_TYPE_HASH = keccak256 ('"type":"webauthn.get" ' );
67-
6863 /**
6964 * @dev Performs the absolute minimal verification of a WebAuthn Authentication Assertion.
7065 * This function includes only the essential checks required for basic WebAuthn security:
@@ -89,18 +84,16 @@ library WebAuthn {
8984 // - 32 bytes for rpIdHash
9085 // - 1 byte for flags
9186 // - 4 bytes for signature counter
92- if (auth.authenticatorData.length < 37 ) return false ;
93- bytes memory clientDataJSON = bytes (auth.clientDataJSON);
94-
9587 return
96- validateExpectedTypeHash (clientDataJSON, auth.typeIndex) && // 11
97- validateChallenge (clientDataJSON, auth.challengeIndex, challenge) && // 12
88+ auth.authenticatorData.length > 36 &&
89+ validateExpectedTypeHash (auth.clientDataJSON, auth.typeIndex) && // 11
90+ validateChallenge (auth.clientDataJSON, auth.challengeIndex, challenge) && // 12
9891 // Handles signature malleability internally
9992 P256.verify (
10093 sha256 (
10194 abi.encodePacked (
10295 auth.authenticatorData,
103- sha256 (clientDataJSON) // 19
96+ sha256 (bytes (auth. clientDataJSON) ) // 19
10497 )
10598 ),
10699 auth.r,
@@ -222,29 +215,64 @@ library WebAuthn {
222215 * @dev Validates that the https://www.w3.org/TR/webauthn-2/#type[Type] field in the client data JSON
223216 * is set to "webauthn.get".
224217 */
225- function validateExpectedTypeHash (bytes memory clientDataJSON , uint256 typeIndex ) internal pure returns (bool ) {
218+ function validateExpectedTypeHash (string memory clientDataJSON , uint256 typeIndex ) internal pure returns (bool ) {
226219 // 21 = length of '"type":"webauthn.get"'
227- bytes memory typeValueBytes = Bytes.slice (clientDataJSON, typeIndex, typeIndex + 21 );
228- return keccak256 (typeValueBytes) == EXPECTED_TYPE_HASH;
220+ bytes memory typeValueBytes = Bytes.slice (bytes (clientDataJSON), typeIndex, typeIndex + 21 );
221+
222+ // solhint-disable-next-line quotes
223+ return bytes21 (typeValueBytes) == bytes21 ('"type":"webauthn.get" ' );
229224 }
230225
231226 /// @dev Validates that the challenge in the client data JSON matches the `expectedChallenge`.
232227 function validateChallenge (
233- bytes memory clientDataJSON ,
228+ string memory clientDataJSON ,
234229 uint256 challengeIndex ,
235- bytes memory expectedChallenge
230+ bytes memory challenge
236231 ) internal pure returns (bool ) {
237- bytes memory expectedChallengeBytes = bytes (
238- // solhint-disable-next-line quotes
239- string .concat ('"challenge":" ' , Base64.encodeURL (expectedChallenge), '" ' )
240- );
241- if (challengeIndex + expectedChallengeBytes.length > clientDataJSON.length ) return false ;
242- bytes memory actualChallengeBytes = Bytes.slice (
243- clientDataJSON,
244- challengeIndex,
245- challengeIndex + expectedChallengeBytes.length
232+ // solhint-disable-next-line quotes
233+ string memory expectedChallenge = string .concat ('"challenge":" ' , Base64.encodeURL (challenge), '" ' );
234+ string memory actualChallenge = string (
235+ Bytes.slice (bytes (clientDataJSON), challengeIndex, challengeIndex + bytes (expectedChallenge).length )
246236 );
247237
248- return Strings.equal (string (actualChallengeBytes), string (expectedChallengeBytes));
238+ return Strings.equal (actualChallenge, expectedChallenge);
239+ }
240+
241+ /**
242+ * @dev Verifies that calldata bytes (`input`) represents a valid `WebAuthnAuth` object. If encoding is valid,
243+ * returns true and the calldata view at the object. Otherwise, returns false and an invalid calldata object.
244+ *
245+ * NOTE: The returned `auth` object should not be accessed if `success` is false. Trying to access the data may
246+ * cause revert/panic.
247+ */
248+ function tryDecodeAuth (bytes calldata input ) internal pure returns (bool success , WebAuthnAuth calldata auth ) {
249+ assembly ("memory-safe" ) {
250+ auth := input.offset
251+ }
252+
253+ // Minimum length to hold 6 objects (32 bytes each)
254+ if (input.length < 0xC0 ) return (false , auth);
255+
256+ // Get offset of non-value-type elements relative to the input buffer
257+ uint256 authenticatorDataOffset = uint256 (bytes32 (input[0x80 :]));
258+ uint256 clientDataJSONOffset = uint256 (bytes32 (input[0xa0 :]));
259+
260+ // The elements length (at the offset) should be 32 bytes long. We check that this is within the
261+ // buffer bounds. Since we know input.length is at least 32, we can subtract with no overflow risk.
262+ if (input.length - 0x20 < authenticatorDataOffset || input.length - 0x20 < clientDataJSONOffset)
263+ return (false , auth);
264+
265+ // Get the lengths. offset + 32 is bounded by input.length so it does not overflow.
266+ uint256 authenticatorDataLength = uint256 (bytes32 (input[authenticatorDataOffset:]));
267+ uint256 clientDataJSONLength = uint256 (bytes32 (input[clientDataJSONOffset:]));
268+
269+ // Check that the input buffer is long enough to store the non-value-type elements
270+ // Since we know input.length is at least xxxOffset + 32, we can subtract with no overflow risk.
271+ if (
272+ input.length - authenticatorDataOffset - 0x20 < authenticatorDataLength ||
273+ input.length - clientDataJSONOffset - 0x20 < clientDataJSONLength
274+ ) return (false , auth);
275+
276+ return (true , auth);
249277 }
250278}
0 commit comments