diff --git a/src/Asn1Decode.sol b/src/Asn1Decode.sol index f6cb620..6e4f27f 100644 --- a/src/Asn1Decode.sol +++ b/src/Asn1Decode.sol @@ -170,10 +170,8 @@ library Asn1Decode { * @return Uint value of node */ function uintAt(bytes memory der, Asn1Ptr ptr) internal pure returns (uint256) { - if (der[ptr.header()] != 0x02) revert InvalidAsn1Type(); - if (der[ptr.content()] & 0x80 != 0) revert InvalidAsn1Value(); - uint256 len = ptr.length(); - return uint256(readBytesN(der, ptr.content(), len) >> (32 - len) * 8); + (uint256 start, uint256 len) = positiveIntegerContent(der, ptr, 32); + return uint256(readBytesN(der, start, len) >> (32 - len) * 8); } /* @@ -183,19 +181,40 @@ library Asn1Decode { * @return 384-bit uint encoded in uint128 and uint256 */ function uint384At(bytes memory der, Asn1Ptr ptr) internal pure returns (uint128, uint256) { + (uint256 start, uint256 valueLength) = positiveIntegerContent(der, ptr, 48); + + if (valueLength > 32) { + uint256 hiLen = valueLength - 32; + return ( + uint128(uint256(readBytesN(der, start, hiLen) >> (32 - hiLen) * 8)), + uint256(readBytesN(der, start + hiLen, 32)) + ); + } + + return (0, uint256(readBytesN(der, start, valueLength) >> (32 - valueLength) * 8)); + } + + function positiveIntegerContent(bytes memory der, Asn1Ptr ptr, uint256 maxValueLength) + private + pure + returns (uint256 start, uint256 valueLength) + { if (der[ptr.header()] != 0x02) revert InvalidAsn1Type(); - if (der[ptr.content()] & 0x80 != 0) revert InvalidAsn1Value(); - uint256 valueLength = ptr.length(); - uint256 start = ptr.content(); + valueLength = ptr.length(); + if (valueLength == 0) revert InvalidAsn1Length(); + start = ptr.content(); + if (der[start] == 0) { - start++; - valueLength--; + if (valueLength > 1) { + if (der[start + 1] & 0x80 != 0x80) revert InvalidAsn1Value(); + start++; + valueLength--; + } + } else { + if (der[start] & 0x80 != 0) revert InvalidAsn1Value(); } - uint256 shift = 48 - valueLength; - return ( - uint128(uint256(readBytesN(der, start, 16 - shift) >> (128 + shift * 8))), - uint256(readBytesN(der, start + 16 - shift, 32)) - ); + + if (valueLength > maxValueLength) revert InvalidAsn1Length(); } /* diff --git a/test/Asn1Decode.t.sol b/test/Asn1Decode.t.sol index 8eb667b..d5577db 100644 --- a/test/Asn1Decode.t.sol +++ b/test/Asn1Decode.t.sol @@ -20,6 +20,10 @@ contract Asn1Harness { return der.uintAt(der.root()); } + function uint384AtRoot(bytes memory der) external pure returns (uint128 hi, uint256 lo) { + return der.uint384At(der.root()); + } + function timestampAtRoot(bytes memory der) external pure returns (uint256) { return der.timestampAt(der.root()); } @@ -90,6 +94,20 @@ contract Asn1DecodeTest is Test { assertEq(h.uintAtRoot(hex"0203012345"), 0x012345); // INTEGER 0x012345 } + function test_uintAt_requiredLeadingZero() public view { + assertEq(h.uintAtRoot(hex"02020080"), 0x80); + } + + function test_uintAt_unnecessaryLeadingZero_reverts() public { + vm.expectRevert(Asn1Decode.InvalidAsn1Value.selector); + h.uintAtRoot(hex"0202007f"); + } + + function test_uintAt_empty_reverts() public { + vm.expectRevert(Asn1Decode.InvalidAsn1Length.selector); + h.uintAtRoot(hex"0200"); + } + function test_uintAt_notInteger_reverts() public { vm.expectRevert(Asn1Decode.InvalidAsn1Type.selector); h.uintAtRoot(hex"0401ff"); // OCTET STRING, not INTEGER @@ -106,6 +124,22 @@ contract Asn1DecodeTest is Test { h.uintAtRoot(hex"02050000"); // claims 5 content bytes, only 2 present } + function test_uint384At_requiredLeadingZero() public view { + (uint128 hi, uint256 lo) = h.uint384AtRoot(hex"02020080"); + assertEq(hi, 0); + assertEq(lo, 0x80); + } + + function test_uint384At_unnecessaryLeadingZero_reverts() public { + vm.expectRevert(Asn1Decode.InvalidAsn1Value.selector); + h.uint384AtRoot(hex"0202007f"); + } + + function test_uint384At_empty_reverts() public { + vm.expectRevert(Asn1Decode.InvalidAsn1Length.selector); + h.uint384AtRoot(hex"0200"); + } + // --- timestampAt --- function test_timestamp_utcEpoch() public view { @@ -209,11 +243,36 @@ contract Asn1DecodeTest is Test { } function testFuzz_uintAt_positive(uint64 v) public view { - // INTEGER with an explicit 0x00 sign byte so the value is always positive - bytes memory der = abi.encodePacked(bytes1(0x02), bytes1(0x09), bytes1(0x00), bytes8(v)); + bytes memory der = _derEncodeUint64(v); assertEq(h.uintAtRoot(der), v); } + function _derEncodeUint64(uint64 v) internal pure returns (bytes memory) { + if (v == 0) { + return hex"020100"; + } + + bytes8 raw = bytes8(v); + uint256 offset; + while (offset < 8 && raw[offset] == 0) { + offset++; + } + + uint256 len = 8 - offset; + bool needsPad = uint8(raw[offset]) >= 0x80; + bytes memory der = new bytes(2 + len + (needsPad ? 1 : 0)); + der[0] = 0x02; + der[1] = bytes1(uint8(der.length - 2)); + uint256 dst = 2; + if (needsPad) { + der[dst++] = 0x00; + } + for (uint256 i = offset; i < 8; ++i) { + der[dst++] = raw[i]; + } + return der; + } + function _utcTime(string memory s) internal pure returns (bytes memory) { bytes memory b = bytes(s); require(b.length == 13, "test: UTCTime must be 13 chars");