Skip to content

Commit cb5a4b6

Browse files
leopoldjoyOpenCode
andcommitted
fix: require exact cert ASN.1 tags
Co-authored-by: OpenCode <opencode-noreply@coinbase.com>
1 parent c0a9f21 commit cb5a4b6

2 files changed

Lines changed: 55 additions & 1 deletion

File tree

src/CertManager.sol

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ contract CertManager is ICertManager {
1717
using LibAsn1Ptr for Asn1Ptr;
1818
using LibBytes for bytes;
1919

20+
error InvalidAsn1Tag();
21+
2022
event CertVerified(bytes32 indexed certHash);
2123
event CertRevoked(bytes32 indexed certHash);
2224
event CertUnrevoked(bytes32 indexed certHash);
@@ -163,7 +165,10 @@ contract CertManager is ICertManager {
163165
/// {revokeCerts}; the same value is recorded on-chain when the cert is first verified, so a
164166
/// revocation applies to every byte-encoding of that certificate. Reverts on malformed DER.
165167
function computeCertId(bytes memory cert) external pure returns (bytes32) {
166-
Asn1Ptr tbsCertPtr = cert.firstChildOf(cert.root());
168+
Asn1Ptr root = cert.root();
169+
_requireAsn1Tag(cert, root, 0x30);
170+
Asn1Ptr tbsCertPtr = cert.firstChildOf(root);
171+
_requireAsn1Tag(cert, tbsCertPtr, 0x30);
167172
return _certIdentity(cert, tbsCertPtr);
168173
}
169174

@@ -290,11 +295,17 @@ contract CertManager is ICertManager {
290295
}
291296

292297
Asn1Ptr root = certificate.root();
298+
_requireAsn1Tag(certificate, root, 0x30);
293299
require(root.totalLength() == certificate.length, "invalid cert length");
294300
Asn1Ptr tbsCertPtr = certificate.firstChildOf(root);
301+
_requireAsn1Tag(certificate, tbsCertPtr, 0x30);
295302
return certificate.keccak(tbsCertPtr.header(), tbsCertPtr.totalLength());
296303
}
297304

305+
function _requireAsn1Tag(bytes memory der, Asn1Ptr ptr, bytes1 tag) internal pure {
306+
if (der[ptr.header()] != tag) revert InvalidAsn1Tag();
307+
}
308+
298309
function _isPinnedRootAlias(bytes32 certHash, VerifiedCert memory cert) internal pure returns (bool) {
299310
return certHash != ROOT_CA_CERT_HASH && cert.ca && cert.subjectHash == ROOT_CA_CERT_SUBJECT_HASH
300311
&& cert.pubKey.length == ROOT_CA_CERT_PUB_KEY.length
@@ -308,8 +319,10 @@ contract CertManager is ICertManager {
308319
bytes memory signatureHints
309320
) internal view returns (VerifiedCert memory cert, bytes32 identity) {
310321
Asn1Ptr root = certificate.root();
322+
_requireAsn1Tag(certificate, root, 0x30);
311323
require(root.totalLength() == certificate.length, "invalid cert length");
312324
Asn1Ptr tbsCertPtr = certificate.firstChildOf(root);
325+
_requireAsn1Tag(certificate, tbsCertPtr, 0x30);
313326
(uint64 notAfter, int64 maxPathLen, bytes32 issuerHash, bytes32 subjectHash, bytes memory pubKey) =
314327
_parseTbs(certificate, tbsCertPtr, ca);
315328

@@ -349,10 +362,13 @@ contract CertManager is ICertManager {
349362
view
350363
returns (uint64 notAfter, int64 maxPathLen, bytes32 issuerHash, bytes32 subjectHash, bytes memory pubKey)
351364
{
365+
_requireAsn1Tag(certificate, ptr, 0x30);
352366
Asn1Ptr versionPtr = certificate.firstChildOf(ptr);
367+
_requireAsn1Tag(certificate, versionPtr, 0xa0);
353368
Asn1Ptr vPtr = certificate.firstChildOf(versionPtr);
354369
Asn1Ptr serialPtr = certificate.nextSiblingOf(versionPtr);
355370
Asn1Ptr sigAlgoPtr = certificate.nextSiblingOf(serialPtr);
371+
_requireAsn1Tag(certificate, sigAlgoPtr, 0x30);
356372

357373
require(certificate.keccak(sigAlgoPtr.content(), sigAlgoPtr.length()) == CERT_ALGO_OID, "invalid cert sig algo");
358374
uint256 version = certificate.uintAt(vPtr);
@@ -368,11 +384,15 @@ contract CertManager is ICertManager {
368384
returns (uint64 notAfter, int64 maxPathLen, bytes32 issuerHash, bytes32 subjectHash, bytes memory pubKey)
369385
{
370386
Asn1Ptr issuerPtr = certificate.nextSiblingOf(sigAlgoPtr);
387+
_requireAsn1Tag(certificate, issuerPtr, 0x30);
371388
issuerHash = certificate.keccak(issuerPtr.content(), issuerPtr.length());
372389
Asn1Ptr validityPtr = certificate.nextSiblingOf(issuerPtr);
390+
_requireAsn1Tag(certificate, validityPtr, 0x30);
373391
Asn1Ptr subjectPtr = certificate.nextSiblingOf(validityPtr);
392+
_requireAsn1Tag(certificate, subjectPtr, 0x30);
374393
subjectHash = certificate.keccak(subjectPtr.content(), subjectPtr.length());
375394
Asn1Ptr subjectPublicKeyInfoPtr = certificate.nextSiblingOf(subjectPtr);
395+
_requireAsn1Tag(certificate, subjectPublicKeyInfoPtr, 0x30);
376396
Asn1Ptr extensionsPtr = certificate.nextSiblingOf(subjectPublicKeyInfoPtr);
377397

378398
if (certificate[extensionsPtr.header()] == 0x81) {
@@ -394,7 +414,9 @@ contract CertManager is ICertManager {
394414
pure
395415
returns (bytes memory subjectPubKey)
396416
{
417+
_requireAsn1Tag(certificate, subjectPublicKeyInfoPtr, 0x30);
397418
Asn1Ptr pubKeyAlgoPtr = certificate.firstChildOf(subjectPublicKeyInfoPtr);
419+
_requireAsn1Tag(certificate, pubKeyAlgoPtr, 0x30);
398420
Asn1Ptr pubKeyAlgoIdPtr = certificate.firstChildOf(pubKeyAlgoPtr);
399421
Asn1Ptr algoParamsPtr = certificate.nextSiblingOf(pubKeyAlgoIdPtr);
400422
Asn1Ptr subjectPublicKeyPtr = certificate.nextSiblingOf(pubKeyAlgoPtr);
@@ -435,13 +457,15 @@ contract CertManager is ICertManager {
435457
{
436458
require(certificate[extensionsPtr.header()] == 0xa3, "invalid extensions");
437459
extensionsPtr = certificate.firstChildOf(extensionsPtr);
460+
_requireAsn1Tag(certificate, extensionsPtr, 0x30);
438461
Asn1Ptr extensionPtr = certificate.firstChildOf(extensionsPtr);
439462
uint256 end = extensionsPtr.content() + extensionsPtr.length();
440463
bool basicConstraintsFound = false;
441464
bool keyUsageFound = false;
442465
maxPathLen = -1;
443466

444467
while (true) {
468+
_requireAsn1Tag(certificate, extensionPtr, 0x30);
445469
Asn1Ptr oidPtr = certificate.firstChildOf(extensionPtr);
446470
bytes32 oid = certificate.keccak(oidPtr.content(), oidPtr.length());
447471

@@ -544,6 +568,7 @@ contract CertManager is ICertManager {
544568
bytes memory signatureHints
545569
) internal view {
546570
Asn1Ptr sigAlgoPtr = certificate.nextSiblingOf(ptr);
571+
_requireAsn1Tag(certificate, sigAlgoPtr, 0x30);
547572
require(certificate.keccak(sigAlgoPtr.content(), sigAlgoPtr.length()) == CERT_ALGO_OID, "invalid cert sig algo");
548573
Asn1Ptr sigPtr = certificate.nextSiblingOf(sigAlgoPtr);
549574
require(sigPtr.header() + sigPtr.totalLength() == certificate.length, "trailing cert fields");

test/CertManager.t.sol

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,35 @@ contract CertManagerTest is Test {
166166
cm.verifyCACertWithHints(rootTwin, rootHash, hints);
167167
}
168168

169+
function test_VerifyCACertWithHints_RejectsOuterTagSubstitution() public {
170+
vm.warp(1775145600);
171+
CertManager cm = new CertManager(new P384Verifier());
172+
173+
bytes32 rootHash = keccak256(CB0);
174+
bytes memory mutated = bytes.concat(CB1);
175+
mutated[0] = 0x31; // constructed SET with the same children is not an X.509 Certificate SEQUENCE.
176+
177+
vm.expectRevert(CertManager.InvalidAsn1Tag.selector);
178+
cm.verifyCACertWithHints(mutated, rootHash, "");
179+
}
180+
181+
function test_VerifyCACertWithHints_RejectsTbsAlgorithmTagSubstitution() public {
182+
vm.warp(1775145600);
183+
CertManager cm = new CertManager(new P384Verifier());
184+
185+
bytes32 rootHash = keccak256(CB0);
186+
bytes memory mutated = bytes.concat(CB1);
187+
Asn1Ptr root = mutated.root();
188+
Asn1Ptr tbsPtr = mutated.firstChildOf(root);
189+
Asn1Ptr versionPtr = mutated.firstChildOf(tbsPtr);
190+
Asn1Ptr serialPtr = mutated.nextSiblingOf(versionPtr);
191+
Asn1Ptr sigAlgoPtr = mutated.nextSiblingOf(serialPtr);
192+
mutated[sigAlgoPtr.header()] = 0x31; // constructed, but not AlgorithmIdentifier SEQUENCE.
193+
194+
vm.expectRevert(CertManager.InvalidAsn1Tag.selector);
195+
cm.verifyCACertWithHints(mutated, rootHash, "");
196+
}
197+
169198
function _verifyCA(CertManager cm, P384HintCollector collector, bytes memory cert, bytes32 parentHash)
170199
internal
171200
returns (bytes32)

0 commit comments

Comments
 (0)