Skip to content

Commit ff6c02e

Browse files
leopoldjoyOpenCode
andcommitted
Merge origin/main into canonical cert integers
Co-authored-by: OpenCode <opencode-noreply@coinbase.com>
2 parents 2ca2cf3 + a25cae2 commit ff6c02e

6 files changed

Lines changed: 334 additions & 98 deletions

File tree

src/Asn1Decode.sol

Lines changed: 31 additions & 19 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

@@ -194,22 +199,22 @@ library Asn1Decode {
194199
pure
195200
returns (uint256 start, uint256 valueLength)
196201
{
197-
require(der[ptr.header()] == 0x02, "Not type INTEGER");
202+
if (der[ptr.header()] != 0x02) revert InvalidAsn1Type();
198203
valueLength = ptr.length();
199-
require(valueLength > 0, "invalid INTEGER length");
204+
if (valueLength == 0) revert InvalidAsn1Length();
200205
start = ptr.content();
201206

202207
if (der[start] == 0) {
203208
if (valueLength > 1) {
204-
require(der[start + 1] & 0x80 == 0x80, "non-canonical INTEGER");
209+
if (der[start + 1] & 0x80 != 0x80) revert InvalidAsn1Value();
205210
start++;
206211
valueLength--;
207212
}
208213
} else {
209-
require(der[start] & 0x80 == 0, "Not positive");
214+
if (der[start] & 0x80 != 0) revert InvalidAsn1Value();
210215
}
211216

212-
require(valueLength <= maxValueLength, "invalid INTEGER length");
217+
if (valueLength > maxValueLength) revert InvalidAsn1Length();
213218
}
214219

215220
/*
@@ -224,12 +229,12 @@ library Asn1Decode {
224229
uint256 length = ptr.length();
225230

226231
// content validation:
227-
require((_type == 0x17 && length == 13) || (_type == 0x18 && length == 15), "Invalid TIMESTAMP");
228-
require(der[offset + length - 1] == 0x5A, "TIMESTAMP must be UTC"); // 0x5A == 'Z'
232+
if ((_type != 0x17 || length != 13) && (_type != 0x18 || length != 15)) revert InvalidAsn1Value();
233+
if (der[offset + length - 1] != 0x5A) revert InvalidAsn1Value(); // 0x5A == 'Z'
229234
for (uint256 i = 0; i < length - 1; i++) {
230235
// all other characters must be digits between 0 and 9
231236
uint8 v = uint8(der[offset + i]);
232-
require(48 <= v && v <= 57, "Invalid character in TIMESTAMP");
237+
if (v < 48 || v > 57) revert InvalidAsn1Value();
233238
}
234239

235240
uint16 _years;
@@ -250,14 +255,19 @@ library Asn1Decode {
250255
}
251256

252257
function readNodeLength(bytes memory der, uint256 ix) private pure returns (Asn1Ptr) {
253-
require(der[ix] & 0x1f != 0x1f, "ASN.1 tags longer than 1-byte are not supported");
258+
if (ix + 1 >= der.length) revert InvalidAsn1Length();
259+
if (der[ix] & 0x1f == 0x1f) revert UnsupportedAsn1Tag();
254260
uint256 length;
255261
uint256 ixFirstContentByte;
256262
if ((der[ix + 1] & 0x80) == 0) {
257263
length = uint8(der[ix + 1]);
258264
ixFirstContentByte = ix + 2;
259265
} else {
260266
uint8 lengthbytesLength = uint8(der[ix + 1] & 0x7F);
267+
if (lengthbytesLength == 0 || lengthbytesLength > 32 || ix + 2 + lengthbytesLength > der.length) {
268+
revert InvalidAsn1Length();
269+
}
270+
if (der[ix + 2] == 0) revert InvalidAsn1Length();
261271
if (lengthbytesLength == 1) {
262272
length = uint8(der[ix + 2]);
263273
} else if (lengthbytesLength == 2) {
@@ -266,8 +276,10 @@ library Asn1Decode {
266276
length = uint256(readBytesN(der, ix + 2, lengthbytesLength) >> (32 - lengthbytesLength) * 8);
267277
require(length <= 2 ** 64 - 1); // bound to max uint64 to be safe
268278
}
279+
if (length < 128) revert InvalidAsn1Length();
269280
ixFirstContentByte = ix + 2 + lengthbytesLength;
270281
}
282+
if (ixFirstContentByte + length > der.length) revert InvalidAsn1Length();
271283
return LibAsn1Ptr.toAsn1Ptr(ix, ixFirstContentByte, length);
272284
}
273285

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)