From 3b8937a162ad5ed187c218dad80c80ba37e78a4a Mon Sep 17 00:00:00 2001 From: Leopold Joy Date: Fri, 26 Jun 2026 12:46:43 +0100 Subject: [PATCH 1/2] fix: require exact cert signature wrapper Co-authored-by: OpenCode --- src/CertManager.sol | 9 +++++++ test/CertManager.t.sol | 60 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/src/CertManager.sol b/src/CertManager.sol index d1d9515..d61df10 100644 --- a/src/CertManager.sol +++ b/src/CertManager.sol @@ -557,8 +557,17 @@ contract CertManager is ICertManager { function _certSignature(bytes memory certificate, Asn1Ptr sigPtr) internal pure returns (bytes memory sigPacked) { Asn1Ptr sigBPtr = certificate.bitstring(sigPtr); Asn1Ptr sigRoot = certificate.rootOf(sigBPtr); + require(certificate[sigRoot.header()] == 0x30, "invalid cert signature"); + require( + sigRoot.header() + sigRoot.totalLength() == sigBPtr.content() + sigBPtr.length(), "invalid cert signature" + ); Asn1Ptr sigRPtr = certificate.firstChildOf(sigRoot); + require(certificate[sigRPtr.header()] == 0x02, "invalid cert signature"); Asn1Ptr sigSPtr = certificate.nextSiblingOf(sigRPtr); + require(certificate[sigSPtr.header()] == 0x02, "invalid cert signature"); + require( + sigSPtr.header() + sigSPtr.totalLength() == sigRoot.content() + sigRoot.length(), "invalid cert signature" + ); (uint128 rhi, uint256 rlo) = certificate.uint384At(sigRPtr); (uint128 shi, uint256 slo) = certificate.uint384At(sigSPtr); sigPacked = abi.encodePacked(rhi, rlo, shi, slo); diff --git a/test/CertManager.t.sol b/test/CertManager.t.sol index cc40cfd..7758e00 100644 --- a/test/CertManager.t.sol +++ b/test/CertManager.t.sol @@ -166,6 +166,38 @@ contract CertManagerTest is Test { cm.verifyCACertWithHints(rootTwin, rootHash, hints); } + function test_VerifyCACertWithHints_RejectsSignatureWrapperTagSubstitution() public { + vm.warp(1775145600); + CertManager cm = new CertManager(new P384Verifier()); + P384HintCollector collector = new P384HintCollector(); + + bytes32 rootHash = keccak256(CB0); + bytes memory parentPubKey = cm.loadVerified(rootHash).pubKey; + bytes memory hints = collector.collectCertSignatureHints(CB1, parentPubKey); + + bytes memory mutated = bytes.concat(CB1); + (,, Asn1Ptr sigRoot,) = _certSignaturePtrs(mutated); + mutated[sigRoot.header()] = 0x31; // constructed SET with the same r/s children. + + vm.expectRevert("invalid cert signature"); + cm.verifyCACertWithHints(mutated, rootHash, hints); + } + + function test_VerifyCACertWithHints_RejectsTrailingSignatureFields() public { + vm.warp(1775145600); + CertManager cm = new CertManager(new P384Verifier()); + P384HintCollector collector = new P384HintCollector(); + + bytes32 rootHash = keccak256(CB0); + bytes memory parentPubKey = cm.loadVerified(rootHash).pubKey; + bytes memory hints = collector.collectCertSignatureHints(CB1, parentPubKey); + + bytes memory mutated = _appendSignatureTrailingField(CB1); + + vm.expectRevert("invalid cert signature"); + cm.verifyCACertWithHints(mutated, rootHash, hints); + } + function _verifyCA(CertManager cm, P384HintCollector collector, bytes memory cert, bytes32 parentHash) internal returns (bytes32) @@ -212,6 +244,34 @@ contract CertManagerTest is Test { _writeDerLength(result, sigRoot, _addDelta(sigRoot.length(), delta)); } + function _appendSignatureTrailingField(bytes memory certificate) internal pure returns (bytes memory result) { + (Asn1Ptr root, Asn1Ptr sigPtr, Asn1Ptr sigRoot,) = _certSignaturePtrs(certificate); + bytes memory extraInteger = hex"020100"; + int256 delta = int256(extraInteger.length); + result = _insertBytes(certificate, sigRoot.content() + sigRoot.length(), extraInteger); + + _writeDerLength(result, root, _addDelta(root.length(), delta)); + _writeDerLength(result, sigPtr, _addDelta(sigPtr.length(), delta)); + _writeDerLength(result, sigRoot, _addDelta(sigRoot.length(), delta)); + } + + function _insertBytes(bytes memory input, uint256 offset, bytes memory inserted) + internal + pure + returns (bytes memory result) + { + result = new bytes(input.length + inserted.length); + for (uint256 i = 0; i < offset; ++i) { + result[i] = input[i]; + } + for (uint256 i = 0; i < inserted.length; ++i) { + result[offset + i] = inserted[i]; + } + for (uint256 i = offset; i < input.length; ++i) { + result[i + inserted.length] = input[i]; + } + } + function _certSignaturePtrs(bytes memory certificate) internal pure From 53f42887e87c453e880b937b65b3a561667d829b Mon Sep 17 00:00:00 2001 From: Leopold Joy Date: Fri, 26 Jun 2026 23:19:37 +0100 Subject: [PATCH 2/2] refactor: use custom cert signature error Co-authored-by: OpenCode --- src/CertManager.sol | 20 +++++++++++--------- test/CertManager.t.sol | 4 ++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/CertManager.sol b/src/CertManager.sol index d61df10..f6b07ba 100644 --- a/src/CertManager.sol +++ b/src/CertManager.sol @@ -17,6 +17,8 @@ contract CertManager is ICertManager { using LibAsn1Ptr for Asn1Ptr; using LibBytes for bytes; + error InvalidCertSignature(); + event CertVerified(bytes32 indexed certHash); event CertRevoked(bytes32 indexed certHash); event CertUnrevoked(bytes32 indexed certHash); @@ -557,17 +559,17 @@ contract CertManager is ICertManager { function _certSignature(bytes memory certificate, Asn1Ptr sigPtr) internal pure returns (bytes memory sigPacked) { Asn1Ptr sigBPtr = certificate.bitstring(sigPtr); Asn1Ptr sigRoot = certificate.rootOf(sigBPtr); - require(certificate[sigRoot.header()] == 0x30, "invalid cert signature"); - require( - sigRoot.header() + sigRoot.totalLength() == sigBPtr.content() + sigBPtr.length(), "invalid cert signature" - ); + if (certificate[sigRoot.header()] != 0x30) revert InvalidCertSignature(); + if (sigRoot.header() + sigRoot.totalLength() != sigBPtr.content() + sigBPtr.length()) { + revert InvalidCertSignature(); + } Asn1Ptr sigRPtr = certificate.firstChildOf(sigRoot); - require(certificate[sigRPtr.header()] == 0x02, "invalid cert signature"); + if (certificate[sigRPtr.header()] != 0x02) revert InvalidCertSignature(); Asn1Ptr sigSPtr = certificate.nextSiblingOf(sigRPtr); - require(certificate[sigSPtr.header()] == 0x02, "invalid cert signature"); - require( - sigSPtr.header() + sigSPtr.totalLength() == sigRoot.content() + sigRoot.length(), "invalid cert signature" - ); + if (certificate[sigSPtr.header()] != 0x02) revert InvalidCertSignature(); + if (sigSPtr.header() + sigSPtr.totalLength() != sigRoot.content() + sigRoot.length()) { + revert InvalidCertSignature(); + } (uint128 rhi, uint256 rlo) = certificate.uint384At(sigRPtr); (uint128 shi, uint256 slo) = certificate.uint384At(sigSPtr); sigPacked = abi.encodePacked(rhi, rlo, shi, slo); diff --git a/test/CertManager.t.sol b/test/CertManager.t.sol index 7758e00..1582daf 100644 --- a/test/CertManager.t.sol +++ b/test/CertManager.t.sol @@ -179,7 +179,7 @@ contract CertManagerTest is Test { (,, Asn1Ptr sigRoot,) = _certSignaturePtrs(mutated); mutated[sigRoot.header()] = 0x31; // constructed SET with the same r/s children. - vm.expectRevert("invalid cert signature"); + vm.expectRevert(CertManager.InvalidCertSignature.selector); cm.verifyCACertWithHints(mutated, rootHash, hints); } @@ -194,7 +194,7 @@ contract CertManagerTest is Test { bytes memory mutated = _appendSignatureTrailingField(CB1); - vm.expectRevert("invalid cert signature"); + vm.expectRevert(CertManager.InvalidCertSignature.selector); cm.verifyCACertWithHints(mutated, rootHash, hints); }