Skip to content

Commit 13dfdf1

Browse files
committed
Merge remote-tracking branch 'origin/main' into docs/cert-parser-strictness
2 parents 51b91cf + a25cae2 commit 13dfdf1

6 files changed

Lines changed: 329 additions & 93 deletions

File tree

src/Asn1Decode.sol

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ library LibAsn1Ptr {
5858
}
5959

6060
library Asn1Decode {
61+
error InvalidAsn1Length();
62+
error InvalidAsn1Type();
63+
error InvalidAsn1Value();
64+
error UnsupportedAsn1Tag();
65+
6166
using LibAsn1Ptr for Asn1Ptr;
6267
using LibBytes for bytes;
6368

@@ -97,7 +102,7 @@ library Asn1Decode {
97102
* @return A pointer to the first child node
98103
*/
99104
function firstChildOf(bytes memory der, Asn1Ptr ptr) internal pure returns (Asn1Ptr) {
100-
require(der[ptr.header()] & 0x20 == 0x20, "Not a constructed type");
105+
if (der[ptr.header()] & 0x20 != 0x20) revert InvalidAsn1Type();
101106
return readNodeLength(der, ptr.content());
102107
}
103108

@@ -108,9 +113,9 @@ library Asn1Decode {
108113
* @return A pointer to a bitstring
109114
*/
110115
function bitstring(bytes memory der, Asn1Ptr ptr) internal pure returns (Asn1Ptr) {
111-
require(der[ptr.header()] == 0x03, "Not type BIT STRING");
116+
if (der[ptr.header()] != 0x03) revert InvalidAsn1Type();
112117
// Only 00 padded bitstr can be converted to bytestr!
113-
require(der[ptr.content()] == 0x00, "Non-0-padded BIT STRING");
118+
if (der[ptr.content()] != 0x00) revert InvalidAsn1Value();
114119
return LibAsn1Ptr.toAsn1Ptr(ptr.header(), ptr.content() + 1, ptr.length() - 1);
115120
}
116121

@@ -122,22 +127,22 @@ library Asn1Decode {
122127
* significant byte, so X.509 bit masks are stable across multi-octet encodings.
123128
*/
124129
function bitstringUintAt(bytes memory der, Asn1Ptr ptr) internal pure returns (uint256) {
125-
require(der[ptr.header()] == 0x03, "Not type BIT STRING");
126-
require(ptr.length() > 0, "invalid BIT STRING length");
130+
if (der[ptr.header()] != 0x03) revert InvalidAsn1Type();
131+
if (ptr.length() == 0) revert InvalidAsn1Length();
127132

128133
uint256 unusedBits = uint8(der[ptr.content()]);
129-
require(unusedBits <= 7, "invalid BIT STRING padding");
134+
if (unusedBits > 7) revert InvalidAsn1Value();
130135

131136
uint256 len = ptr.length() - 1;
132-
require(len <= 32, "BIT STRING too long");
137+
if (len > 32) revert InvalidAsn1Length();
133138
if (len == 0) {
134-
require(unusedBits == 0, "invalid BIT STRING padding");
139+
if (unusedBits != 0) revert InvalidAsn1Value();
135140
return 0;
136141
}
137142

138143
if (unusedBits != 0) {
139144
uint8 unusedMask = uint8((uint256(1) << unusedBits) - 1);
140-
require(uint8(der[ptr.content() + len]) & unusedMask == 0, "Non-zero unused BIT STRING bits");
145+
if (uint8(der[ptr.content() + len]) & unusedMask != 0) revert InvalidAsn1Value();
141146
}
142147

143148
uint256 value;
@@ -154,7 +159,7 @@ library Asn1Decode {
154159
* @return A pointer to an octet string
155160
*/
156161
function octetString(bytes memory der, Asn1Ptr ptr) internal pure returns (Asn1Ptr) {
157-
require(der[ptr.header()] == 0x04, "Not type OCTET STRING");
162+
if (der[ptr.header()] != 0x04) revert InvalidAsn1Type();
158163
return readNodeLength(der, ptr.content());
159164
}
160165

@@ -165,8 +170,8 @@ library Asn1Decode {
165170
* @return Uint value of node
166171
*/
167172
function uintAt(bytes memory der, Asn1Ptr ptr) internal pure returns (uint256) {
168-
require(der[ptr.header()] == 0x02, "Not type INTEGER");
169-
require(der[ptr.content()] & 0x80 == 0, "Not positive");
173+
if (der[ptr.header()] != 0x02) revert InvalidAsn1Type();
174+
if (der[ptr.content()] & 0x80 != 0) revert InvalidAsn1Value();
170175
uint256 len = ptr.length();
171176
return uint256(readBytesN(der, ptr.content(), len) >> (32 - len) * 8);
172177
}
@@ -178,8 +183,8 @@ library Asn1Decode {
178183
* @return 384-bit uint encoded in uint128 and uint256
179184
*/
180185
function uint384At(bytes memory der, Asn1Ptr ptr) internal pure returns (uint128, uint256) {
181-
require(der[ptr.header()] == 0x02, "Not type INTEGER");
182-
require(der[ptr.content()] & 0x80 == 0, "Not positive");
186+
if (der[ptr.header()] != 0x02) revert InvalidAsn1Type();
187+
if (der[ptr.content()] & 0x80 != 0) revert InvalidAsn1Value();
183188
uint256 valueLength = ptr.length();
184189
uint256 start = ptr.content();
185190
if (der[start] == 0) {
@@ -205,12 +210,12 @@ library Asn1Decode {
205210
uint256 length = ptr.length();
206211

207212
// content validation:
208-
require((_type == 0x17 && length == 13) || (_type == 0x18 && length == 15), "Invalid TIMESTAMP");
209-
require(der[offset + length - 1] == 0x5A, "TIMESTAMP must be UTC"); // 0x5A == 'Z'
213+
if ((_type != 0x17 || length != 13) && (_type != 0x18 || length != 15)) revert InvalidAsn1Value();
214+
if (der[offset + length - 1] != 0x5A) revert InvalidAsn1Value(); // 0x5A == 'Z'
210215
for (uint256 i = 0; i < length - 1; i++) {
211216
// all other characters must be digits between 0 and 9
212217
uint8 v = uint8(der[offset + i]);
213-
require(48 <= v && v <= 57, "Invalid character in TIMESTAMP");
218+
if (v < 48 || v > 57) revert InvalidAsn1Value();
214219
}
215220

216221
uint16 _years;
@@ -231,14 +236,19 @@ library Asn1Decode {
231236
}
232237

233238
function readNodeLength(bytes memory der, uint256 ix) private pure returns (Asn1Ptr) {
234-
require(der[ix] & 0x1f != 0x1f, "ASN.1 tags longer than 1-byte are not supported");
239+
if (ix + 1 >= der.length) revert InvalidAsn1Length();
240+
if (der[ix] & 0x1f == 0x1f) revert UnsupportedAsn1Tag();
235241
uint256 length;
236242
uint256 ixFirstContentByte;
237243
if ((der[ix + 1] & 0x80) == 0) {
238244
length = uint8(der[ix + 1]);
239245
ixFirstContentByte = ix + 2;
240246
} else {
241247
uint8 lengthbytesLength = uint8(der[ix + 1] & 0x7F);
248+
if (lengthbytesLength == 0 || lengthbytesLength > 32 || ix + 2 + lengthbytesLength > der.length) {
249+
revert InvalidAsn1Length();
250+
}
251+
if (der[ix + 2] == 0) revert InvalidAsn1Length();
242252
if (lengthbytesLength == 1) {
243253
length = uint8(der[ix + 2]);
244254
} else if (lengthbytesLength == 2) {
@@ -247,8 +257,10 @@ library Asn1Decode {
247257
length = uint256(readBytesN(der, ix + 2, lengthbytesLength) >> (32 - lengthbytesLength) * 8);
248258
require(length <= 2 ** 64 - 1); // bound to max uint64 to be safe
249259
}
260+
if (length < 128) revert InvalidAsn1Length();
250261
ixFirstContentByte = ix + 2 + lengthbytesLength;
251262
}
263+
if (ixFirstContentByte + length > der.length) revert InvalidAsn1Length();
252264
return LibAsn1Ptr.toAsn1Ptr(ix, ixFirstContentByte, length);
253265
}
254266

src/CertManager.sol

Lines changed: 53 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,20 @@ contract CertManager is ICertManager {
1717
using LibAsn1Ptr for Asn1Ptr;
1818
using LibBytes for bytes;
1919

20+
error InvalidExtension();
21+
error InvalidBasicConstraints();
22+
error InvalidSubjectPublicKey();
23+
error UnsupportedCriticalExtension();
24+
error NotOwner();
25+
error NotRevoker();
26+
error IncompleteCertChain();
27+
error DeprecatedEntrypoint();
28+
error InvalidOwner();
29+
error InvalidRevoker();
30+
2031
event CertVerified(bytes32 indexed certHash);
21-
event CertRevoked(bytes32 indexed certHash);
22-
event CertUnrevoked(bytes32 indexed certHash);
32+
event CertRevoked(bytes32 indexed certHash, address indexed account);
33+
event CertUnrevoked(bytes32 indexed certHash, address indexed account);
2334
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
2435
event RevokerUpdated(address indexed previousRevoker, address indexed newRevoker);
2536

@@ -78,11 +89,11 @@ contract CertManager is ICertManager {
7889
}
7990

8091
function _onlyOwner() internal view {
81-
require(msg.sender == owner, "not owner");
92+
if (msg.sender != owner) revert NotOwner();
8293
}
8394

8495
function _onlyRevoker() internal view {
85-
require(msg.sender == revoker, "not revoker");
96+
if (msg.sender != revoker) revert NotRevoker();
8697
}
8798

8899
constructor(IP384Verifier p384Verifier_) {
@@ -107,12 +118,12 @@ contract CertManager is ICertManager {
107118
/// @notice DEPRECATED — always reverts. The fully on-chain (non-hinted) path is too expensive
108119
/// post-Fusaka and has been removed. Use {verifyCACertWithHints}.
109120
function verifyCACert(bytes memory, bytes32) external pure returns (bytes32) {
110-
revert("use hinted cert verification");
121+
revert DeprecatedEntrypoint();
111122
}
112123

113124
/// @notice DEPRECATED — always reverts. Use {verifyClientCertWithHints}.
114125
function verifyClientCert(bytes memory, bytes32) external pure returns (VerifiedCert memory) {
115-
revert("use hinted cert verification");
126+
revert DeprecatedEntrypoint();
116127
}
117128

118129
/// @notice Verify a CA certificate against its (already-cached) parent and cache the result.
@@ -168,13 +179,13 @@ contract CertManager is ICertManager {
168179
}
169180

170181
function transferOwnership(address newOwner) external onlyOwner {
171-
require(newOwner != address(0), "invalid owner");
182+
if (newOwner == address(0)) revert InvalidOwner();
172183
emit OwnershipTransferred(owner, newOwner);
173184
owner = newOwner;
174185
}
175186

176187
function setRevoker(address newRevoker) external onlyOwner {
177-
require(newRevoker != address(0), "invalid revoker");
188+
if (newRevoker == address(0)) revert InvalidRevoker();
178189
emit RevokerUpdated(revoker, newRevoker);
179190
revoker = newRevoker;
180191
}
@@ -195,12 +206,12 @@ contract CertManager is ICertManager {
195206

196207
function unrevokeCert(bytes32 certId) external onlyOwner {
197208
revoked[certId] = false;
198-
emit CertUnrevoked(certId);
209+
emit CertUnrevoked(certId, msg.sender);
199210
}
200211

201212
function _revokeCert(bytes32 certId) internal {
202213
revoked[certId] = true;
203-
emit CertRevoked(certId);
214+
emit CertRevoked(certId, msg.sender);
204215
}
205216

206217
function _requireCanRevoke(bytes32 certId) internal view {
@@ -235,7 +246,7 @@ contract CertManager is ICertManager {
235246
// Fail closed: a chain that terminates at bytes32(0) without reaching the pinned root is
236247
// broken and must not be treated as a verified, non-revoked chain. Reverting here instead
237248
// of returning silently means revocation safety never depends on upstream guards.
238-
revert("incomplete cert chain");
249+
revert IncompleteCertChain();
239250
}
240251

241252
function _verifyCert(
@@ -400,17 +411,19 @@ contract CertManager is ICertManager {
400411
Asn1Ptr subjectPublicKeyPtr = certificate.nextSiblingOf(pubKeyAlgoPtr);
401412
Asn1Ptr subjectPubKeyPtr = certificate.bitstring(subjectPublicKeyPtr);
402413

403-
require(
404-
certificate.keccak(pubKeyAlgoIdPtr.content(), pubKeyAlgoIdPtr.length()) == EC_PUB_KEY_OID,
405-
"invalid cert algo id"
406-
);
407-
require(
408-
certificate.keccak(algoParamsPtr.content(), algoParamsPtr.length()) == SECP_384_R1_OID,
409-
"invalid cert algo param"
410-
);
414+
if (certificate.keccak(pubKeyAlgoIdPtr.content(), pubKeyAlgoIdPtr.length()) != EC_PUB_KEY_OID) {
415+
revert InvalidSubjectPublicKey();
416+
}
417+
if (certificate.keccak(algoParamsPtr.content(), algoParamsPtr.length()) != SECP_384_R1_OID) {
418+
revert InvalidSubjectPublicKey();
419+
}
411420

412-
uint256 end = subjectPubKeyPtr.content() + subjectPubKeyPtr.length();
413-
subjectPubKey = certificate.slice(end - 96, 96);
421+
uint256 keyStart = subjectPubKeyPtr.content();
422+
uint256 keyLength = subjectPubKeyPtr.length();
423+
if (keyLength != 97 || keyStart + keyLength > certificate.length || certificate[keyStart] != 0x04) {
424+
revert InvalidSubjectPublicKey();
425+
}
426+
subjectPubKey = certificate.slice(keyStart + 1, 96);
414427
}
415428

416429
function _verifyValidity(bytes memory certificate, Asn1Ptr validityPtr) internal view returns (uint64 notAfter) {
@@ -433,7 +446,7 @@ contract CertManager is ICertManager {
433446
pure
434447
returns (int64 maxPathLen)
435448
{
436-
require(certificate[extensionsPtr.header()] == 0xa3, "invalid extensions");
449+
if (certificate[extensionsPtr.header()] != 0xa3) revert InvalidExtension();
437450
extensionsPtr = certificate.firstChildOf(extensionsPtr);
438451
Asn1Ptr extensionPtr = certificate.firstChildOf(extensionsPtr);
439452
uint256 end = extensionsPtr.content() + extensionsPtr.length();
@@ -444,16 +457,16 @@ contract CertManager is ICertManager {
444457
while (true) {
445458
Asn1Ptr oidPtr = certificate.firstChildOf(extensionPtr);
446459
bytes32 oid = certificate.keccak(oidPtr.content(), oidPtr.length());
460+
Asn1Ptr valuePtr = certificate.nextSiblingOf(oidPtr);
461+
bool recognized = oid == BASIC_CONSTRAINTS_OID || oid == KEY_USAGE_OID;
447462

448-
if (oid == BASIC_CONSTRAINTS_OID || oid == KEY_USAGE_OID) {
449-
Asn1Ptr valuePtr = certificate.nextSiblingOf(oidPtr);
450-
451-
if (certificate[valuePtr.header()] == 0x01) {
452-
// skip optional critical bool
453-
require(valuePtr.length() == 1, "invalid critical bool value");
454-
valuePtr = certificate.nextSiblingOf(valuePtr);
455-
}
463+
if (certificate[valuePtr.header()] == 0x01) {
464+
if (valuePtr.length() != 1) revert InvalidExtension();
465+
if (!recognized && certificate[valuePtr.content()] != 0x00) revert UnsupportedCriticalExtension();
466+
valuePtr = certificate.nextSiblingOf(valuePtr);
467+
}
456468

469+
if (recognized) {
457470
valuePtr = certificate.octetString(valuePtr);
458471

459472
if (oid == BASIC_CONSTRAINTS_OID) {
@@ -471,17 +484,15 @@ contract CertManager is ICertManager {
471484
extensionPtr = certificate.nextSiblingOf(extensionPtr);
472485
}
473486

474-
require(basicConstraintsFound, "basicConstraints not found");
475-
require(keyUsageFound, "keyUsage not found");
476-
require(ca || maxPathLen == -1, "maxPathLen must be undefined for client cert");
487+
if (!basicConstraintsFound || !keyUsageFound || (!ca && maxPathLen != -1)) revert InvalidExtension();
477488
}
478489

479490
function _verifyBasicConstraintsExtension(bytes memory certificate, Asn1Ptr valuePtr, bool ca)
480491
internal
481492
pure
482493
returns (int64 maxPathLen)
483494
{
484-
require(certificate[valuePtr.header()] == 0x30, "invalid basicConstraints");
495+
if (certificate[valuePtr.header()] != 0x30) revert InvalidBasicConstraints();
485496

486497
maxPathLen = -1;
487498
bool isCA;
@@ -493,37 +504,37 @@ contract CertManager is ICertManager {
493504
cursor = _requireAsn1ChildWithin(basicConstraintsPtr, end);
494505

495506
if (certificate[basicConstraintsPtr.header()] == 0x01) {
496-
require(basicConstraintsPtr.length() == 1, "invalid isCA bool value");
507+
if (basicConstraintsPtr.length() != 1) revert InvalidBasicConstraints();
497508
isCA = certificate[basicConstraintsPtr.content()] == 0xff;
498509

499510
if (cursor == end) {
500-
require(ca == isCA, "isCA must be true for CA certs");
511+
if (ca != isCA) revert InvalidBasicConstraints();
501512
return maxPathLen;
502513
}
503514

504515
basicConstraintsPtr = certificate.nextSiblingOf(basicConstraintsPtr);
505516
cursor = _requireAsn1ChildWithin(basicConstraintsPtr, end);
506517
}
507518

508-
require(ca == isCA, "isCA must be true for CA certs");
519+
if (ca != isCA) revert InvalidBasicConstraints();
509520

510521
if (certificate[basicConstraintsPtr.header()] == 0x02) {
511-
require(basicConstraintsPtr.length() > 0, "invalid pathLenConstraint");
522+
if (basicConstraintsPtr.length() == 0) revert InvalidBasicConstraints();
512523
maxPathLen = int64(uint64(certificate.uintAt(basicConstraintsPtr)));
513524
} else {
514-
revert("invalid basicConstraints field");
525+
revert InvalidBasicConstraints();
515526
}
516527

517-
require(cursor == end, "trailing basicConstraints fields");
528+
if (cursor != end) revert InvalidBasicConstraints();
518529
return maxPathLen;
519530
}
520531

521-
require(ca == isCA, "isCA must be true for CA certs");
532+
if (ca != isCA) revert InvalidBasicConstraints();
522533
}
523534

524535
function _requireAsn1ChildWithin(Asn1Ptr ptr, uint256 parentEnd) internal pure returns (uint256 childEnd) {
525536
childEnd = ptr.header() + ptr.totalLength();
526-
require(childEnd <= parentEnd, "basicConstraints out of bounds");
537+
if (childEnd > parentEnd) revert InvalidBasicConstraints();
527538
}
528539

529540
function _verifyKeyUsageExtension(bytes memory certificate, Asn1Ptr valuePtr, bool ca) internal pure {

src/ICertManager.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ interface ICertManager {
4545

4646
// --- DEPRECATED: these always revert; use the *WithHints variants above. ---
4747

48-
/// @dev DEPRECATED — always reverts ("use hinted cert verification").
48+
/// @dev DEPRECATED — always reverts.
4949
function verifyCACert(bytes memory cert, bytes32 parentCertHash) external returns (bytes32);
5050

51-
/// @dev DEPRECATED — always reverts ("use hinted cert verification").
51+
/// @dev DEPRECATED — always reverts.
5252
function verifyClientCert(bytes memory cert, bytes32 parentCertHash) external returns (VerifiedCert memory);
5353
}

0 commit comments

Comments
 (0)