@@ -60,11 +60,6 @@ library WebAuthn {
60
60
/// @dev Bit 4 of the authenticator data flags: "Backup State" bit.
61
61
bytes1 private constant AUTH_DATA_FLAGS_BS = 0x10 ;
62
62
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
-
68
63
/**
69
64
* @dev Performs the absolute minimal verification of a WebAuthn Authentication Assertion.
70
65
* This function includes only the essential checks required for basic WebAuthn security:
@@ -89,18 +84,16 @@ library WebAuthn {
89
84
// - 32 bytes for rpIdHash
90
85
// - 1 byte for flags
91
86
// - 4 bytes for signature counter
92
- if (auth.authenticatorData.length < 37 ) return false ;
93
- bytes memory clientDataJSON = bytes (auth.clientDataJSON);
94
-
95
87
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
98
91
// Handles signature malleability internally
99
92
P256.verify (
100
93
sha256 (
101
94
abi.encodePacked (
102
95
auth.authenticatorData,
103
- sha256 (clientDataJSON) // 19
96
+ sha256 (bytes (auth. clientDataJSON) ) // 19
104
97
)
105
98
),
106
99
auth.r,
@@ -222,29 +215,64 @@ library WebAuthn {
222
215
* @dev Validates that the https://www.w3.org/TR/webauthn-2/#type[Type] field in the client data JSON
223
216
* is set to "webauthn.get".
224
217
*/
225
- function validateExpectedTypeHash (bytes memory clientDataJSON , uint256 typeIndex ) internal pure returns (bool ) {
218
+ function validateExpectedTypeHash (string memory clientDataJSON , uint256 typeIndex ) internal pure returns (bool ) {
226
219
// 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" ' );
229
224
}
230
225
231
226
/// @dev Validates that the challenge in the client data JSON matches the `expectedChallenge`.
232
227
function validateChallenge (
233
- bytes memory clientDataJSON ,
228
+ string memory clientDataJSON ,
234
229
uint256 challengeIndex ,
235
- bytes memory expectedChallenge
230
+ bytes memory challenge
236
231
) 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 )
246
236
);
247
237
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);
249
277
}
250
278
}
0 commit comments