Skip to content

Commit d7a37ce

Browse files
committed
fix: bound basic constraints parsing
1 parent 5e1584b commit d7a37ce

2 files changed

Lines changed: 82 additions & 8 deletions

File tree

src/CertManager.sol

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -453,18 +453,48 @@ contract CertManager is ICertManager {
453453
pure
454454
returns (int64 maxPathLen)
455455
{
456+
require(certificate[valuePtr.header()] == 0x30, "invalid basicConstraints");
457+
456458
maxPathLen = -1;
457-
Asn1Ptr basicConstraintsPtr = certificate.firstChildOf(valuePtr);
458459
bool isCA;
459-
if (certificate[basicConstraintsPtr.header()] == 0x01) {
460-
require(basicConstraintsPtr.length() == 1, "invalid isCA bool value");
461-
isCA = certificate[basicConstraintsPtr.content()] == 0xff;
462-
basicConstraintsPtr = certificate.nextSiblingOf(basicConstraintsPtr);
460+
uint256 end = valuePtr.content() + valuePtr.length();
461+
uint256 cursor = valuePtr.content();
462+
463+
if (cursor < end) {
464+
Asn1Ptr basicConstraintsPtr = certificate.firstChildOf(valuePtr);
465+
cursor = _requireAsn1ChildWithin(basicConstraintsPtr, end);
466+
467+
if (certificate[basicConstraintsPtr.header()] == 0x01) {
468+
require(basicConstraintsPtr.length() == 1, "invalid isCA bool value");
469+
isCA = certificate[basicConstraintsPtr.content()] == 0xff;
470+
471+
if (cursor == end) {
472+
require(ca == isCA, "isCA must be true for CA certs");
473+
return maxPathLen;
474+
}
475+
476+
basicConstraintsPtr = certificate.nextSiblingOf(basicConstraintsPtr);
477+
cursor = _requireAsn1ChildWithin(basicConstraintsPtr, end);
478+
}
479+
480+
require(ca == isCA, "isCA must be true for CA certs");
481+
482+
if (certificate[basicConstraintsPtr.header()] == 0x02) {
483+
maxPathLen = int64(uint64(certificate.uintAt(basicConstraintsPtr)));
484+
} else {
485+
revert("invalid basicConstraints field");
486+
}
487+
488+
require(cursor == end, "trailing basicConstraints fields");
489+
return maxPathLen;
463490
}
491+
464492
require(ca == isCA, "isCA must be true for CA certs");
465-
if (certificate[basicConstraintsPtr.header()] == 0x02) {
466-
maxPathLen = int64(uint64(certificate.uintAt(basicConstraintsPtr)));
467-
}
493+
}
494+
495+
function _requireAsn1ChildWithin(Asn1Ptr ptr, uint256 parentEnd) internal pure returns (uint256 childEnd) {
496+
childEnd = ptr.header() + ptr.totalLength();
497+
require(childEnd <= parentEnd, "basicConstraints out of bounds");
468498
}
469499

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

test/CertManager.t.sol

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,23 @@ contract Asn1DecodeHarness {
2121
}
2222
}
2323

24+
contract CertManagerHarness is CertManager {
25+
using Asn1Decode for bytes;
26+
27+
constructor() CertManager(new P384Verifier()) {}
28+
29+
function verifyBasicConstraints(bytes memory der, bool ca) external pure returns (int64) {
30+
return _verifyBasicConstraintsExtension(der, der.root(), ca);
31+
}
32+
}
33+
2434
contract CertManagerTest is Test {
2535
Asn1DecodeHarness public harness;
36+
CertManagerHarness public certManagerHarness;
2637

2738
function setUp() public {
2839
harness = new Asn1DecodeHarness();
40+
certManagerHarness = new CertManagerHarness();
2941
}
3042

3143
// 's' INTEGER from cabundle[3] (2026-04-02 attestation): DER-encoded with a 0x00
@@ -41,6 +53,38 @@ contract CertManagerTest is Test {
4153
assertEq(lo, 0xa2eda9c549dc01460f5fe650814ebe0e7ee855d3bcffde95afd2e82e21df0eac);
4254
}
4355

56+
function test_BasicConstraintsEmptySequenceIsClientCert() public view {
57+
assertEq(int256(certManagerHarness.verifyBasicConstraints(hex"3000", false)), -1);
58+
}
59+
60+
function test_BasicConstraintsEmptySequenceRejectsCACert() public {
61+
vm.expectRevert("isCA must be true for CA certs");
62+
certManagerHarness.verifyBasicConstraints(hex"3000", true);
63+
}
64+
65+
function test_BasicConstraintsAcceptsCAWithoutPathLen() public view {
66+
assertEq(int256(certManagerHarness.verifyBasicConstraints(hex"30030101ff", true)), -1);
67+
}
68+
69+
function test_BasicConstraintsAcceptsCAWithPathLen() public view {
70+
assertEq(int256(certManagerHarness.verifyBasicConstraints(hex"30060101ff020100", true)), 0);
71+
}
72+
73+
function test_BasicConstraintsRejectsOutOfBoundsChild() public {
74+
vm.expectRevert("basicConstraints out of bounds");
75+
certManagerHarness.verifyBasicConstraints(hex"3003020200", false);
76+
}
77+
78+
function test_BasicConstraintsRejectsTrailingFields() public {
79+
vm.expectRevert("trailing basicConstraints fields");
80+
certManagerHarness.verifyBasicConstraints(hex"30090101ff020100020100", true);
81+
}
82+
83+
function test_BasicConstraintsRejectsUnknownField() public {
84+
vm.expectRevert("invalid basicConstraints field");
85+
certManagerHarness.verifyBasicConstraints(hex"30020400", false);
86+
}
87+
4488
// Cert chain from the 2026-04-02 ~15:35 UTC dev attestation that produced the live revert.
4589
// CB0 is the AWS Nitro root (keccak256(CB0) == CertManager.ROOT_CA_CERT_HASH, pinned in the
4690
// constructor), so the chain is verified starting from CB1.

0 commit comments

Comments
 (0)