Skip to content

Commit 1bbd94a

Browse files
leopoldjoyOpenCode
andcommitted
Merge origin/main into strict cert ASN.1 tags
Co-authored-by: OpenCode <opencode-noreply@coinbase.com>
2 parents b8941c3 + a25cae2 commit 1bbd94a

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 InvalidAsn1Tag();
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.
@@ -173,13 +183,13 @@ contract CertManager is ICertManager {
173183
}
174184

175185
function transferOwnership(address newOwner) external onlyOwner {
176-
require(newOwner != address(0), "invalid owner");
186+
if (newOwner == address(0)) revert InvalidOwner();
177187
emit OwnershipTransferred(owner, newOwner);
178188
owner = newOwner;
179189
}
180190

181191
function setRevoker(address newRevoker) external onlyOwner {
182-
require(newRevoker != address(0), "invalid revoker");
192+
if (newRevoker == address(0)) revert InvalidRevoker();
183193
emit RevokerUpdated(revoker, newRevoker);
184194
revoker = newRevoker;
185195
}
@@ -200,12 +210,12 @@ contract CertManager is ICertManager {
200210

201211
function unrevokeCert(bytes32 certId) external onlyOwner {
202212
revoked[certId] = false;
203-
emit CertUnrevoked(certId);
213+
emit CertUnrevoked(certId, msg.sender);
204214
}
205215

206216
function _revokeCert(bytes32 certId) internal {
207217
revoked[certId] = true;
208-
emit CertRevoked(certId);
218+
emit CertRevoked(certId, msg.sender);
209219
}
210220

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

246256
function _verifyCert(
@@ -419,17 +429,19 @@ contract CertManager is ICertManager {
419429
Asn1Ptr subjectPublicKeyPtr = certificate.nextSiblingOf(pubKeyAlgoPtr);
420430
Asn1Ptr subjectPubKeyPtr = certificate.bitstring(subjectPublicKeyPtr);
421431

422-
require(
423-
certificate.keccak(pubKeyAlgoIdPtr.content(), pubKeyAlgoIdPtr.length()) == EC_PUB_KEY_OID,
424-
"invalid cert algo id"
425-
);
426-
require(
427-
certificate.keccak(algoParamsPtr.content(), algoParamsPtr.length()) == SECP_384_R1_OID,
428-
"invalid cert algo param"
429-
);
432+
if (certificate.keccak(pubKeyAlgoIdPtr.content(), pubKeyAlgoIdPtr.length()) != EC_PUB_KEY_OID) {
433+
revert InvalidSubjectPublicKey();
434+
}
435+
if (certificate.keccak(algoParamsPtr.content(), algoParamsPtr.length()) != SECP_384_R1_OID) {
436+
revert InvalidSubjectPublicKey();
437+
}
430438

431-
uint256 end = subjectPubKeyPtr.content() + subjectPubKeyPtr.length();
432-
subjectPubKey = certificate.slice(end - 96, 96);
439+
uint256 keyStart = subjectPubKeyPtr.content();
440+
uint256 keyLength = subjectPubKeyPtr.length();
441+
if (keyLength != 97 || keyStart + keyLength > certificate.length || certificate[keyStart] != 0x04) {
442+
revert InvalidSubjectPublicKey();
443+
}
444+
subjectPubKey = certificate.slice(keyStart + 1, 96);
433445
}
434446

435447
function _verifyValidity(bytes memory certificate, Asn1Ptr validityPtr) internal view returns (uint64 notAfter) {
@@ -452,7 +464,7 @@ contract CertManager is ICertManager {
452464
pure
453465
returns (int64 maxPathLen)
454466
{
455-
require(certificate[extensionsPtr.header()] == 0xa3, "invalid extensions");
467+
if (certificate[extensionsPtr.header()] != 0xa3) revert InvalidExtension();
456468
extensionsPtr = certificate.firstChildOf(extensionsPtr);
457469
_requireAsn1Tag(certificate, extensionsPtr, 0x30);
458470
Asn1Ptr extensionPtr = certificate.firstChildOf(extensionsPtr);
@@ -465,16 +477,16 @@ contract CertManager is ICertManager {
465477
_requireAsn1Tag(certificate, extensionPtr, 0x30);
466478
Asn1Ptr oidPtr = certificate.firstChildOf(extensionPtr);
467479
bytes32 oid = certificate.keccak(oidPtr.content(), oidPtr.length());
480+
Asn1Ptr valuePtr = certificate.nextSiblingOf(oidPtr);
481+
bool recognized = oid == BASIC_CONSTRAINTS_OID || oid == KEY_USAGE_OID;
468482

469-
if (oid == BASIC_CONSTRAINTS_OID || oid == KEY_USAGE_OID) {
470-
Asn1Ptr valuePtr = certificate.nextSiblingOf(oidPtr);
471-
472-
if (certificate[valuePtr.header()] == 0x01) {
473-
// skip optional critical bool
474-
require(valuePtr.length() == 1, "invalid critical bool value");
475-
valuePtr = certificate.nextSiblingOf(valuePtr);
476-
}
483+
if (certificate[valuePtr.header()] == 0x01) {
484+
if (valuePtr.length() != 1) revert InvalidExtension();
485+
if (!recognized && certificate[valuePtr.content()] != 0x00) revert UnsupportedCriticalExtension();
486+
valuePtr = certificate.nextSiblingOf(valuePtr);
487+
}
477488

489+
if (recognized) {
478490
valuePtr = certificate.octetString(valuePtr);
479491

480492
if (oid == BASIC_CONSTRAINTS_OID) {
@@ -492,17 +504,15 @@ contract CertManager is ICertManager {
492504
extensionPtr = certificate.nextSiblingOf(extensionPtr);
493505
}
494506

495-
require(basicConstraintsFound, "basicConstraints not found");
496-
require(keyUsageFound, "keyUsage not found");
497-
require(ca || maxPathLen == -1, "maxPathLen must be undefined for client cert");
507+
if (!basicConstraintsFound || !keyUsageFound || (!ca && maxPathLen != -1)) revert InvalidExtension();
498508
}
499509

500510
function _verifyBasicConstraintsExtension(bytes memory certificate, Asn1Ptr valuePtr, bool ca)
501511
internal
502512
pure
503513
returns (int64 maxPathLen)
504514
{
505-
require(certificate[valuePtr.header()] == 0x30, "invalid basicConstraints");
515+
if (certificate[valuePtr.header()] != 0x30) revert InvalidBasicConstraints();
506516

507517
maxPathLen = -1;
508518
bool isCA;
@@ -514,37 +524,37 @@ contract CertManager is ICertManager {
514524
cursor = _requireAsn1ChildWithin(basicConstraintsPtr, end);
515525

516526
if (certificate[basicConstraintsPtr.header()] == 0x01) {
517-
require(basicConstraintsPtr.length() == 1, "invalid isCA bool value");
527+
if (basicConstraintsPtr.length() != 1) revert InvalidBasicConstraints();
518528
isCA = certificate[basicConstraintsPtr.content()] == 0xff;
519529

520530
if (cursor == end) {
521-
require(ca == isCA, "isCA must be true for CA certs");
531+
if (ca != isCA) revert InvalidBasicConstraints();
522532
return maxPathLen;
523533
}
524534

525535
basicConstraintsPtr = certificate.nextSiblingOf(basicConstraintsPtr);
526536
cursor = _requireAsn1ChildWithin(basicConstraintsPtr, end);
527537
}
528538

529-
require(ca == isCA, "isCA must be true for CA certs");
539+
if (ca != isCA) revert InvalidBasicConstraints();
530540

531541
if (certificate[basicConstraintsPtr.header()] == 0x02) {
532-
require(basicConstraintsPtr.length() > 0, "invalid pathLenConstraint");
542+
if (basicConstraintsPtr.length() == 0) revert InvalidBasicConstraints();
533543
maxPathLen = int64(uint64(certificate.uintAt(basicConstraintsPtr)));
534544
} else {
535-
revert("invalid basicConstraints field");
545+
revert InvalidBasicConstraints();
536546
}
537547

538-
require(cursor == end, "trailing basicConstraints fields");
548+
if (cursor != end) revert InvalidBasicConstraints();
539549
return maxPathLen;
540550
}
541551

542-
require(ca == isCA, "isCA must be true for CA certs");
552+
if (ca != isCA) revert InvalidBasicConstraints();
543553
}
544554

545555
function _requireAsn1ChildWithin(Asn1Ptr ptr, uint256 parentEnd) internal pure returns (uint256 childEnd) {
546556
childEnd = ptr.header() + ptr.totalLength();
547-
require(childEnd <= parentEnd, "basicConstraints out of bounds");
557+
if (childEnd > parentEnd) revert InvalidBasicConstraints();
548558
}
549559

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