Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions src/NitroValidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -73,17 +73,20 @@ contract NitroValidator {
}

CborElement protectedPtr = attestation.byteStringAt(offset);
CborElement unprotectedPtr = attestation.nextMap(protectedPtr);
CborElement payloadPtr = attestation.nextByteString(unprotectedPtr);
// Map pointers end at their header; validate the type, then skip the full item.
attestation.nextMap(protectedPtr);
uint256 unprotectedEnd = attestation.skipValue(protectedPtr.end());
CborElement payloadPtr = attestation.byteStringAt(unprotectedEnd);
CborElement signaturePtr = attestation.nextByteString(payloadPtr);

uint256 rawProtectedLength = protectedPtr.end() - offset;
uint256 rawPayloadLength = payloadPtr.end() - unprotectedPtr.end();
uint256 rawPayloadLength = payloadPtr.end() - unprotectedEnd;
bytes memory rawProtectedBytes = attestation.slice(offset, rawProtectedLength);
bytes memory rawPayloadBytes = attestation.slice(unprotectedPtr.end(), rawPayloadLength);
bytes memory rawPayloadBytes = attestation.slice(unprotectedEnd, rawPayloadLength);
attestationTbs =
_constructAttestationTbs(rawProtectedBytes, rawProtectedLength, rawPayloadBytes, rawPayloadLength);
signature = attestation.slice(signaturePtr.start(), signaturePtr.length());
require(signaturePtr.end() == attestation.length, "trailing COSE data");
}

/// @notice DEPRECATED — always reverts. The fully on-chain (non-hinted) path is too expensive
Expand Down
30 changes: 30 additions & 0 deletions test/IndefiniteLengthCbor.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,36 @@ contract NitroValidatorIndefiniteLengthTest is Test {
validator = new NitroValidatorHarness(ICertManager(address(new StubCertManager())), new P384Verifier());
}

// ── COSE decoder tests ────────────────────────────────────

/// @dev AWS uses an empty unprotected header map; it must still be consumed before the payload.
function test_decode_emptyUnprotectedMap_preservesPayload() public view {
(bytes memory tbs, bytes memory signature) = validator.decodeAttestationTbs(hex"8440a043aabbcc42ddee");

assertEq(tbs, hex"846a5369676e617475726531404043aabbcc", "unexpected attestation TBS");
assertEq(signature, hex"ddee", "unexpected signature");
}

/// @dev A valid non-empty unprotected map must be skipped in full before locating the payload.
function test_decode_nonEmptyUnprotectedMap_preservesPayload() public view {
(bytes memory tbs, bytes memory signature) = validator.decodeAttestationTbs(hex"8440a1010243aabbcc42ddee");

assertEq(tbs, hex"846a5369676e617475726531404043aabbcc", "unexpected attestation TBS");
assertEq(signature, hex"ddee", "unexpected signature");
}

/// @dev A map declaring an entry without supplying its value must not be treated as an empty map.
function test_decode_truncatedUnprotectedMap_reverts() public {
vm.expectRevert();
validator.decodeAttestationTbs(hex"8440a101");
}

/// @dev The COSE Sign1 item is self-delimiting; bytes after its signature are not accepted.
function test_decode_trailingOuterBytes_reverts() public {
vm.expectRevert("trailing COSE data");
validator.decodeAttestationTbs(hex"8440a043aabbcc42ddee00");
}

// ── Shared assertion helpers ──────────────────────────────

/// @dev Asserts all fields of a Ptrs parsed from synthetic test data.
Expand Down
Loading