Skip to content

Commit cd40bc3

Browse files
leopoldjoyOpenCode
andcommitted
Merge origin/main into strict cert signature wrapper
Co-authored-by: OpenCode <opencode-noreply@coinbase.com>
2 parents 53f4288 + a25cae2 commit cd40bc3

6 files changed

Lines changed: 328 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: 52 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,20 @@ contract CertManager is ICertManager {
1818
using LibBytes for bytes;
1919

2020
error InvalidCertSignature();
21+
error InvalidExtension();
22+
error InvalidBasicConstraints();
23+
error InvalidSubjectPublicKey();
24+
error UnsupportedCriticalExtension();
25+
error NotOwner();
26+
error NotRevoker();
27+
error IncompleteCertChain();
28+
error DeprecatedEntrypoint();
29+
error InvalidOwner();
30+
error InvalidRevoker();
2131

2232
event CertVerified(bytes32 indexed certHash);
23-
event CertRevoked(bytes32 indexed certHash);
24-
event CertUnrevoked(bytes32 indexed certHash);
33+
event CertRevoked(bytes32 indexed certHash, address indexed account);
34+
event CertUnrevoked(bytes32 indexed certHash, address indexed account);
2535
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
2636
event RevokerUpdated(address indexed previousRevoker, address indexed newRevoker);
2737

@@ -80,11 +90,11 @@ contract CertManager is ICertManager {
8090
}
8191

8292
function _onlyOwner() internal view {
83-
require(msg.sender == owner, "not owner");
93+
if (msg.sender != owner) revert NotOwner();
8494
}
8595

8696
function _onlyRevoker() internal view {
87-
require(msg.sender == revoker, "not revoker");
97+
if (msg.sender != revoker) revert NotRevoker();
8898
}
8999

90100
constructor(IP384Verifier p384Verifier_) {
@@ -109,12 +119,12 @@ contract CertManager is ICertManager {
109119
/// @notice DEPRECATED — always reverts. The fully on-chain (non-hinted) path is too expensive
110120
/// post-Fusaka and has been removed. Use {verifyCACertWithHints}.
111121
function verifyCACert(bytes memory, bytes32) external pure returns (bytes32) {
112-
revert("use hinted cert verification");
122+
revert DeprecatedEntrypoint();
113123
}
114124

115125
/// @notice DEPRECATED — always reverts. Use {verifyClientCertWithHints}.
116126
function verifyClientCert(bytes memory, bytes32) external pure returns (VerifiedCert memory) {
117-
revert("use hinted cert verification");
127+
revert DeprecatedEntrypoint();
118128
}
119129

120130
/// @notice Verify a CA certificate against its (already-cached) parent and cache the result.
@@ -170,13 +180,13 @@ contract CertManager is ICertManager {
170180
}
171181

172182
function transferOwnership(address newOwner) external onlyOwner {
173-
require(newOwner != address(0), "invalid owner");
183+
if (newOwner == address(0)) revert InvalidOwner();
174184
emit OwnershipTransferred(owner, newOwner);
175185
owner = newOwner;
176186
}
177187

178188
function setRevoker(address newRevoker) external onlyOwner {
179-
require(newRevoker != address(0), "invalid revoker");
189+
if (newRevoker == address(0)) revert InvalidRevoker();
180190
emit RevokerUpdated(revoker, newRevoker);
181191
revoker = newRevoker;
182192
}
@@ -197,12 +207,12 @@ contract CertManager is ICertManager {
197207

198208
function unrevokeCert(bytes32 certId) external onlyOwner {
199209
revoked[certId] = false;
200-
emit CertUnrevoked(certId);
210+
emit CertUnrevoked(certId, msg.sender);
201211
}
202212

203213
function _revokeCert(bytes32 certId) internal {
204214
revoked[certId] = true;
205-
emit CertRevoked(certId);
215+
emit CertRevoked(certId, msg.sender);
206216
}
207217

208218
function _requireCanRevoke(bytes32 certId) internal view {
@@ -237,7 +247,7 @@ contract CertManager is ICertManager {
237247
// Fail closed: a chain that terminates at bytes32(0) without reaching the pinned root is
238248
// broken and must not be treated as a verified, non-revoked chain. Reverting here instead
239249
// of returning silently means revocation safety never depends on upstream guards.
240-
revert("incomplete cert chain");
250+
revert IncompleteCertChain();
241251
}
242252

243253
function _verifyCert(
@@ -402,17 +412,19 @@ contract CertManager is ICertManager {
402412
Asn1Ptr subjectPublicKeyPtr = certificate.nextSiblingOf(pubKeyAlgoPtr);
403413
Asn1Ptr subjectPubKeyPtr = certificate.bitstring(subjectPublicKeyPtr);
404414

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

414-
uint256 end = subjectPubKeyPtr.content() + subjectPubKeyPtr.length();
415-
subjectPubKey = certificate.slice(end - 96, 96);
422+
uint256 keyStart = subjectPubKeyPtr.content();
423+
uint256 keyLength = subjectPubKeyPtr.length();
424+
if (keyLength != 97 || keyStart + keyLength > certificate.length || certificate[keyStart] != 0x04) {
425+
revert InvalidSubjectPublicKey();
426+
}
427+
subjectPubKey = certificate.slice(keyStart + 1, 96);
416428
}
417429

418430
function _verifyValidity(bytes memory certificate, Asn1Ptr validityPtr) internal view returns (uint64 notAfter) {
@@ -435,7 +447,7 @@ contract CertManager is ICertManager {
435447
pure
436448
returns (int64 maxPathLen)
437449
{
438-
require(certificate[extensionsPtr.header()] == 0xa3, "invalid extensions");
450+
if (certificate[extensionsPtr.header()] != 0xa3) revert InvalidExtension();
439451
extensionsPtr = certificate.firstChildOf(extensionsPtr);
440452
Asn1Ptr extensionPtr = certificate.firstChildOf(extensionsPtr);
441453
uint256 end = extensionsPtr.content() + extensionsPtr.length();
@@ -446,16 +458,16 @@ contract CertManager is ICertManager {
446458
while (true) {
447459
Asn1Ptr oidPtr = certificate.firstChildOf(extensionPtr);
448460
bytes32 oid = certificate.keccak(oidPtr.content(), oidPtr.length());
461+
Asn1Ptr valuePtr = certificate.nextSiblingOf(oidPtr);
462+
bool recognized = oid == BASIC_CONSTRAINTS_OID || oid == KEY_USAGE_OID;
449463

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

470+
if (recognized) {
459471
valuePtr = certificate.octetString(valuePtr);
460472

461473
if (oid == BASIC_CONSTRAINTS_OID) {
@@ -473,17 +485,15 @@ contract CertManager is ICertManager {
473485
extensionPtr = certificate.nextSiblingOf(extensionPtr);
474486
}
475487

476-
require(basicConstraintsFound, "basicConstraints not found");
477-
require(keyUsageFound, "keyUsage not found");
478-
require(ca || maxPathLen == -1, "maxPathLen must be undefined for client cert");
488+
if (!basicConstraintsFound || !keyUsageFound || (!ca && maxPathLen != -1)) revert InvalidExtension();
479489
}
480490

481491
function _verifyBasicConstraintsExtension(bytes memory certificate, Asn1Ptr valuePtr, bool ca)
482492
internal
483493
pure
484494
returns (int64 maxPathLen)
485495
{
486-
require(certificate[valuePtr.header()] == 0x30, "invalid basicConstraints");
496+
if (certificate[valuePtr.header()] != 0x30) revert InvalidBasicConstraints();
487497

488498
maxPathLen = -1;
489499
bool isCA;
@@ -495,37 +505,37 @@ contract CertManager is ICertManager {
495505
cursor = _requireAsn1ChildWithin(basicConstraintsPtr, end);
496506

497507
if (certificate[basicConstraintsPtr.header()] == 0x01) {
498-
require(basicConstraintsPtr.length() == 1, "invalid isCA bool value");
508+
if (basicConstraintsPtr.length() != 1) revert InvalidBasicConstraints();
499509
isCA = certificate[basicConstraintsPtr.content()] == 0xff;
500510

501511
if (cursor == end) {
502-
require(ca == isCA, "isCA must be true for CA certs");
512+
if (ca != isCA) revert InvalidBasicConstraints();
503513
return maxPathLen;
504514
}
505515

506516
basicConstraintsPtr = certificate.nextSiblingOf(basicConstraintsPtr);
507517
cursor = _requireAsn1ChildWithin(basicConstraintsPtr, end);
508518
}
509519

510-
require(ca == isCA, "isCA must be true for CA certs");
520+
if (ca != isCA) revert InvalidBasicConstraints();
511521

512522
if (certificate[basicConstraintsPtr.header()] == 0x02) {
513-
require(basicConstraintsPtr.length() > 0, "invalid pathLenConstraint");
523+
if (basicConstraintsPtr.length() == 0) revert InvalidBasicConstraints();
514524
maxPathLen = int64(uint64(certificate.uintAt(basicConstraintsPtr)));
515525
} else {
516-
revert("invalid basicConstraints field");
526+
revert InvalidBasicConstraints();
517527
}
518528

519-
require(cursor == end, "trailing basicConstraints fields");
529+
if (cursor != end) revert InvalidBasicConstraints();
520530
return maxPathLen;
521531
}
522532

523-
require(ca == isCA, "isCA must be true for CA certs");
533+
if (ca != isCA) revert InvalidBasicConstraints();
524534
}
525535

526536
function _requireAsn1ChildWithin(Asn1Ptr ptr, uint256 parentEnd) internal pure returns (uint256 childEnd) {
527537
childEnd = ptr.header() + ptr.totalLength();
528-
require(childEnd <= parentEnd, "basicConstraints out of bounds");
538+
if (childEnd > parentEnd) revert InvalidBasicConstraints();
529539
}
530540

531541
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)