@@ -3,13 +3,29 @@ pragma solidity ^0.8.13;
33
44import {Test, console} from "forge-std/Test.sol " ;
55import {CertManager} from "../src/CertManager.sol " ;
6+ import {Asn1Decode, LibAsn1Ptr, Asn1Ptr} from "../src/Asn1Decode.sol " ;
7+
8+ contract Asn1DecodeHarness {
9+ using Asn1Decode for bytes ;
10+
11+ function uint384At (bytes calldata der , uint256 header , uint256 content , uint256 length )
12+ external
13+ pure
14+ returns (uint128 hi , uint256 lo )
15+ {
16+ Asn1Ptr ptr = LibAsn1Ptr.toAsn1Ptr (header, content, length);
17+ return der.uint384At (ptr);
18+ }
19+ }
620
721contract CertManagerTest is Test {
822 CertManager public certManager;
23+ Asn1DecodeHarness public harness;
924
1025 function setUp () public {
1126 vm.warp (1732580000 );
1227 certManager = new CertManager ();
28+ harness = new Asn1DecodeHarness ();
1329 }
1430
1531 function test_VerifyCertBundle () public {
@@ -38,4 +54,94 @@ contract CertManagerTest is Test {
3854 hex"308202bf30820244a00302010202100b93e39c65609c59e8144a2ad34ba3a0300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3234313132333036333235355a170d3234313231333037333235355a3064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d353133623665666332313639303264372e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004ee78108039725a03e0b63a5d7d1244f6294eb7631f305e360997c8e5c06c779f23cfaeb64cb9aeac8a031bfac9f4dafc3621b4367f003c08c0ce410c2118396cc5d56ec4e92e1b17f9709b2bffcef462f7bcb97d6ca11325c4a30156c9720de7a381d53081d230120603551d130101ff040830060101ff020102301f0603551d230418301680149025b50dd90547e796c396fa729dcf99a9df4b96301d0603551d0e041604142b3d75d274a3cdd61b2c13f539e08c960ce757dd300e0603551d0f0101ff040403020186306c0603551d1f046530633061a05fa05d865b687474703a2f2f6177732d6e6974726f2d656e636c617665732d63726c2e73332e616d617a6f6e6177732e636f6d2f63726c2f61623439363063632d376436332d343262642d396539662d3539333338636236376638342e63726c300a06082a8648ce3d0403030369003066023100fce7a6c2b38e0a8ebf0d28348d74463458b84bfe8b2b95315dd4da665e8e83d4ab911852a4e92a8263ecf571d2df3b89023100ab92be511136be76aa313018f9f4825eaad602d0342d268e6da632767f68f55f761fa9fd2a7ee716c481c67f26e3f8f4";
3955 certManager.verifyCACert (cert, keccak256 (parent));
4056 }
57+
58+ // 's' INTEGER from cabundle[3] (2026-04-02 attestation): DER-encoded with a 0x00
59+ // sign-padding byte, leaving valueLength=47. Verifies hi/lo are correctly zero-padded
60+ // to the full 48-byte scalar rather than packed flush against the stripped bytes.
61+ function test_uint384At_Short47Bytes () public view {
62+ bytes memory der =
63+ hex "023000caf59019bfbcc6f6ed365e5a892ceaa2eda9c549dc01460f5fe650814ebe0e7ee855d3bcffde95afd2e82e21df0eac " ;
64+ (uint128 hi , uint256 lo ) = harness.uint384At (der, 0 , 2 , 48 );
65+ assertEq (uint8 (hi >> 120 ), 0x00 , "hi[0]: expected 0x00 (unfixed: 0xca) " );
66+ assertEq (uint8 (lo >> 248 ), 0xa2 , "lo[0]: expected 0xa2 (unfixed: 0x00, byte absorbed into hi) " );
67+ assertEq (hi, uint128 (0x00caf59019bfbcc6f6ed365e5a892cea ));
68+ assertEq (lo, 0xa2eda9c549dc01460f5fe650814ebe0e7ee855d3bcffde95afd2e82e21df0eac );
69+ }
70+
71+ // Cert chain from the 2026-04-02 ~15:35 UTC dev attestation that produced the live revert.
72+ bytes constant CB0 =
73+ hex"3082021130820196a003020102021100f93175681b90afe11d46ccb4e4e7f856300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3139313032383133323830355a170d3439313032383134323830355a3049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004fc0254eba608c1f36870e29ada90be46383292736e894bfff672d989444b5051e534a4b1f6dbe3c0bc581a32b7b176070ede12d69a3fea211b66e752cf7dd1dd095f6f1370f4170843d9dc100121e4cf63012809664487c9796284304dc53ff4a3423040300f0603551d130101ff040530030101ff301d0603551d0e041604149025b50dd90547e796c396fa729dcf99a9df4b96300e0603551d0f0101ff040403020186300a06082a8648ce3d0403030369003066023100a37f2f91a1c9bd5ee7b8627c1698d255038e1f0343f95b63a9628c3d39809545a11ebcbf2e3b55d8aeee71b4c3d6adf3023100a2f39b1605b27028a5dd4ba069b5016e65b4fbde8fe0061d6a53197f9cdaf5d943bc61fc2beb03cb6fee8d2302f3dff6";
74+ bytes constant CB1 =
75+ hex"308202c030820245a003020102021100e9773f7085209425e1de229b000f01f1300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3236303333313035303734355a170d3236303432303036303734355a3064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d316262313063316530323564363536632e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004cd607e358d0c0d7fb6cb74700df27dd799f46a8b3e1e106c2ba4e962b8e1869fb8ee1929a0543e8cf5fe36eb8c85decb1a8e680fde0427d222cd2c43f6103d4f651b98770d9f52439963a83d65ebc45e17f8d16ccb76e343163429e4f4dca36da381d53081d230120603551d130101ff040830060101ff020102301f0603551d230418301680149025b50dd90547e796c396fa729dcf99a9df4b96301d0603551d0e0416041449ed5cf5770acf285a97690e5027f21b81c1bf0d300e0603551d0f0101ff040403020186306c0603551d1f046530633061a05fa05d865b687474703a2f2f6177732d6e6974726f2d656e636c617665732d63726c2e73332e616d617a6f6e6177732e636f6d2f63726c2f61623439363063632d376436332d343262642d396539662d3539333338636236376638342e63726c300a06082a8648ce3d0403030369003066023100fad8167b186ede6765e2fee311718e1c2dfde817a280a316bab19008eb15356795525d52458bdec9818ea7c1f26d054c023100de456629f7833285dc26f68e74b206b83bf39846a76271c7c364c6fc2c00db5da4b435a92cd85963eab5e1b58d049b49";
76+ bytes constant CB2 =
77+ hex"308203133082029aa003020102021038fed4b0c2167f2e947b381791999d87300a06082a8648ce3d0403033064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d316262313063316530323564363536632e75732d656173742d312e6177732e6e6974726f2d656e636c61766573301e170d3236303430323037303834365a170d3236303430383036303834365a308189313c303a06035504030c33396162323162376434663562373139622e7a6f6e616c2e75732d656173742d312e6177732e6e6974726f2d656e636c61766573310c300a060355040b0c03415753310f300d060355040a0c06416d617a6f6e310b3009060355040613025553310b300906035504080c0257413110300e06035504070c0753656174746c653076301006072a8648ce3d020106052b81040022036200041448bacb44a37937f8558d861442956c0c4482f28bd5ced8af0a2a7ec153cb22ab709ef75b37cf273ba2a3415dae125fa5aac4aa063e833c86df6cb68141b35284afb95279a301fadcad98e4edf5740a8461646b31ce52cbba81638395da26b7a381ea3081e730120603551d130101ff040830060101ff020101301f0603551d2304183016801449ed5cf5770acf285a97690e5027f21b81c1bf0d301d0603551d0e041604146a2a38eb4940a886ad9e57e4b7da9dde7f4eb700300e0603551d0f0101ff0404030201863081800603551d1f047930773075a073a071866f687474703a2f2f63726c2d75732d656173742d312d6177732d6e6974726f2d656e636c617665732e73332e75732d656173742d312e616d617a6f6e6177732e636f6d2f63726c2f63336535353365322d363936632d346635302d386233382d6435666165663036306633312e63726c300a06082a8648ce3d0403030367003064023012012108aa4f9678c965ca3746037a230d07c2bc39f3d7ebc9832fbad0e5277c9642d61d3245ba45d741bd88c04692fa02307a4a42535408ed65e5db2bea9aefff587d9187788b715843295efcbbf710d7f65301476ed4338b4755cd3c16b0e12d6a";
78+ bytes constant CB3 =
79+ hex"308202bf30820245a003020102021500f2a021cc6a8466d5fe18e8f487b975f35c7c5b05300a06082a8648ce3d040303308189313c303a06035504030c33396162323162376434663562373139622e7a6f6e616c2e75732d656173742d312e6177732e6e6974726f2d656e636c61766573310c300a060355040b0c03415753310f300d060355040a0c06416d617a6f6e310b3009060355040613025553310b300906035504080c0257413110300e06035504070c0753656174746c65301e170d3236303430323135303431355a170d3236303430333135303431355a30818e310b30090603550406130255533113301106035504080c0a57617368696e67746f6e3110300e06035504070c0753656174746c65310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533139303706035504030c30692d30396535656634623664666630323535622e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b810400220362000420ecb0a645c3e6b4437c1c4149c3f1d0af3e90f0343b0e1e3a47d8b314420266843c345a1e64b01b43331f911ab537b78b355a2419eeb00f1ecd6d4fbd7186ab41603ff2a0430cab7b597362a1054ed4cb8ff32dd76b2c108224b42648bbf026a366306430120603551d130101ff040830060101ff020100300e0603551d0f0101ff040403020204301d0603551d0e0416041499b2d026ad0212b670d2550fb292ca51fdbf8d66301f0603551d230418301680146a2a38eb4940a886ad9e57e4b7da9dde7f4eb700300a06082a8648ce3d040303036800306502310096ca96c46b5a05de3ad9ecdeaab8670916137461d306cf2fcd8a308885eb6063d96de2e28a1a4ad8c2214e1d1479b5b8023000caf59019bfbcc6f6ed365e5a892ceaa2eda9c549dc01460f5fe650814ebe0e7ee855d3bcffde95afd2e82e21df0eac";
80+
81+ // CB3's 's' component has a 47-byte DER encoding; unpatched code reverts with "invalid sig".
82+ function test_VerifyCACert_ShortS_Regression () public {
83+ vm.warp (1775145600 );
84+ CertManager cm = new CertManager ();
85+ cm.verifyCACert (CB0, 0 );
86+ cm.verifyCACert (CB1, keccak256 (CB0));
87+ cm.verifyCACert (CB2, keccak256 (CB1));
88+ cm.verifyCACert (CB3, keccak256 (CB2)); // reverts with "invalid sig" on unpatched code
89+ }
90+
91+ function testFuzz_uint384At_LeadingZeros (uint8 numZeros , uint128 hiSeed , uint256 loSeed ) public view {
92+ numZeros = uint8 (bound (numZeros, 0 , 16 ));
93+
94+ uint128 expectedHi;
95+ uint256 expectedLo;
96+
97+ if (numZeros == 16 ) {
98+ expectedHi = 0 ;
99+ expectedLo = loSeed | (uint256 (1 ) << 248 );
100+ } else {
101+ uint128 mask = type (uint128 ).max >> (numZeros * 8 );
102+ expectedHi = (hiSeed & mask) | (uint128 (1 ) << ((15 - numZeros) * 8 ));
103+ expectedLo = loSeed;
104+ }
105+
106+ bytes memory scalar48 = abi.encodePacked (expectedHi, expectedLo);
107+ bytes memory der = _derEncodeP384Integer (scalar48);
108+
109+ uint256 contentLen = uint256 (uint8 (der[1 ]));
110+ (uint128 hi , uint256 lo ) = harness.uint384At (der, 0 , 2 , contentLen);
111+
112+ assertEq (hi, expectedHi);
113+ assertEq (lo, expectedLo);
114+ }
115+
116+ function _derEncodeP384Integer (bytes memory scalar48 ) internal pure returns (bytes memory ) {
117+ require (scalar48.length == 48 );
118+
119+ uint256 i = 0 ;
120+ while (i < 48 && scalar48[i] == 0 ) {
121+ i++ ;
122+ }
123+
124+ if (i == 48 ) return hex "020100 " ;
125+
126+ uint256 minLen = 48 - i;
127+ bool needsPad = uint8 (scalar48[i]) >= 0x80 ;
128+ uint256 contentLen = needsPad ? minLen + 1 : minLen;
129+
130+ bytes memory der = new bytes (2 + contentLen);
131+ der[0 ] = 0x02 ;
132+ der[1 ] = bytes1 (uint8 (contentLen));
133+
134+ uint256 offset = 2 ;
135+ if (needsPad) {
136+ der[offset] = 0x00 ;
137+ offset++ ;
138+ }
139+
140+ for (uint256 j = i; j < 48 ; j++ ) {
141+ der[offset] = scalar48[j];
142+ offset++ ;
143+ }
144+
145+ return der;
146+ }
41147}
0 commit comments