├── .github └── workflows │ └── test.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── audits └── report-cantinacode-1212.pdf ├── foundry.toml ├── remappings.txt ├── src ├── Asn1Decode.sol ├── CborDecode.sol ├── CertManager.sol ├── ECDSA384Curve.sol ├── ICertManager.sol ├── LibBytes.sol ├── NitroValidator.sol └── Sha2Ext.sol └── test ├── CertManager.t.sol ├── NitroValidator.t.sol └── RootCertCheck.t.sol /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | env: 9 | FOUNDRY_PROFILE: ci 10 | 11 | jobs: 12 | check: 13 | strategy: 14 | fail-fast: true 15 | 16 | name: Foundry project 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | submodules: recursive 22 | 23 | - name: Install Foundry 24 | uses: foundry-rs/foundry-toolchain@v1 25 | with: 26 | version: nightly 27 | 28 | - name: Show Forge version 29 | run: | 30 | forge --version 31 | 32 | - name: Run Forge fmt 33 | run: | 34 | forge fmt --check 35 | id: fmt 36 | 37 | - name: Run Forge build 38 | run: | 39 | forge build --sizes 40 | id: build 41 | 42 | - name: Run Forge tests 43 | run: | 44 | forge test -vvv 45 | id: test 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | /.idea/ 16 | /.DS_Store 17 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/solidity-lib"] 5 | path = lib/solidity-lib 6 | url = https://github.com/dl-solarity/solidity-lib 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright 2020-2024 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Solidity AWS Nitro Attestation validator 2 | 3 | This repo provides solidity contracts for the verification of attestations generated by AWS Nitro Enclaves, as outlined in 4 | [this doc](https://github.com/aws/aws-nitro-enclaves-nsm-api/blob/4b851f3006c6fa98f23dcffb2cba03b39de9b8af/docs/attestation_process.md#3-attestation-document-validation). 5 | 6 | Note it costs around 63m gas to validate an attestation with no prior verified certs. 7 | You can break this up into smaller transactions by verifying each cert in the chain separately. 8 | You can call `CertManager.verifyCACert` for each cert in the attestation `cabundle`. 9 | 10 | This library does not currently support certificate revocation, which is disabled in AWS's attestation verification documentation 11 | [here](https://github.com/aws/aws-nitro-enclaves-nsm-api/blob/4b851f3006c6fa98f23dcffb2cba03b39de9b8af/docs/attestation_process.md#32-syntactical-validation). 12 | 13 | ## Usage 14 | 15 | 1. Deploy the `CertManager` separately. 16 | 2. Validate Nitro attestation in your contract: 17 | 18 | ```solidity 19 | pragma solidity ^0.8.0; 20 | 21 | import {NitroValidator} from "@nitro-validator/NitroValidator.sol"; 22 | import {CborDecode} from "@nitro-validator/CborDecode.sol"; 23 | import {CertManager} from "@nitro-validator/CertManager.sol"; 24 | 25 | contract Validator is NitroValidator { 26 | using CborDecode for bytes; 27 | 28 | uint256 public constant MAX_AGE = 60 minutes; 29 | bytes32 public constant PCR0 = keccak256("some PCR0 value"); 30 | 31 | constructor(CertManager certManager) NitroValidator(certManager) {} 32 | 33 | function registerSigner(bytes calldata attestationTbs, bytes calldata signature) external onlyOwner { 34 | Ptrs memory ptrs = validateAttestation(attestationTbs, signature); 35 | bytes32 pcr0 = attestationTbs.keccak(ptrs.pcrs[0]); 36 | require(pcr0 == PCR0, "invalid pcr0 in attestation"); 37 | require(ptrs.timestamp + MAX_AGE > block.timestamp, "attestation too old"); 38 | 39 | bytes memory publicKey = attestationTbs.slice(ptrs.publicKey); 40 | // do something with the public key, user data, etc 41 | } 42 | } 43 | ``` 44 | 3. Convert an attestation document to a attestationTbs / signature (TBS means to-be-signed). 45 | Note it's cheaper to perform this conversion offchain (e.g. using `cast call`). 46 | ```solidity 47 | bytes memory attestation = hex"84.."; 48 | (bytes memory attestationTbs, bytes memory signature) = validator.decodeAttestationTbs(attestation); 49 | validator.validateAttestation(attestationTbs, signature); 50 | ``` 51 | 52 | ## Build 53 | 54 | ```sh 55 | forge install 56 | forge build 57 | ``` 58 | 59 | ## Test 60 | 61 | ```sh 62 | forge test 63 | ``` 64 | -------------------------------------------------------------------------------- /audits/report-cantinacode-1212.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/base/nitro-validator/78759f7efa6d0dace5cdd0fa9ce8e6c0137e7a65/audits/report-cantinacode-1212.pdf -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | optimizer = true 6 | optimizer_runs = 999999 7 | solc_version = "0.8.26" 8 | 9 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 10 | -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | forge-std/=lib/forge-std/src/ 2 | @solarity/=lib/solidity-lib/contracts/ 3 | -------------------------------------------------------------------------------- /src/Asn1Decode.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // Copyright (c) 2019 Jonah Groendal 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | pragma solidity ^0.8.15; 24 | 25 | // adapted from https://github.com/JonahGroendal/asn1-decode/tree/master 26 | 27 | import {LibBytes} from "./LibBytes.sol"; 28 | 29 | type Asn1Ptr is uint256; 30 | 31 | library LibAsn1Ptr { 32 | using LibAsn1Ptr for Asn1Ptr; 33 | 34 | // First byte index of the header 35 | function header(Asn1Ptr self) internal pure returns (uint256) { 36 | return uint80(Asn1Ptr.unwrap(self)); 37 | } 38 | 39 | // First byte index of the content 40 | function content(Asn1Ptr self) internal pure returns (uint256) { 41 | return uint80(Asn1Ptr.unwrap(self) >> 80); 42 | } 43 | 44 | // Content length 45 | function length(Asn1Ptr self) internal pure returns (uint256) { 46 | return uint80(Asn1Ptr.unwrap(self) >> 160); 47 | } 48 | 49 | // Total length (header length + content length) 50 | function totalLength(Asn1Ptr self) internal pure returns (uint256) { 51 | return self.length() + self.content() - self.header(); 52 | } 53 | 54 | // Pack 3 uint80s into a uint256 55 | function toAsn1Ptr(uint256 _header, uint256 _content, uint256 _length) internal pure returns (Asn1Ptr) { 56 | return Asn1Ptr.wrap(_header | _content << 80 | _length << 160); 57 | } 58 | } 59 | 60 | library Asn1Decode { 61 | using LibAsn1Ptr for Asn1Ptr; 62 | using LibBytes for bytes; 63 | 64 | /* 65 | * @dev Get the root node. First step in traversing an ASN1 structure 66 | * @param der The DER-encoded ASN1 structure 67 | * @return A pointer to the outermost node 68 | */ 69 | function root(bytes memory der) internal pure returns (Asn1Ptr) { 70 | return readNodeLength(der, 0); 71 | } 72 | 73 | /* 74 | * @dev Get a child root of the current node 75 | * @param der The DER-encoded ASN1 structure 76 | * @param ptr Pointer to the current node 77 | * @return A pointer to the child root node 78 | */ 79 | function rootOf(bytes memory der, Asn1Ptr ptr) internal pure returns (Asn1Ptr) { 80 | return readNodeLength(der, ptr.content()); 81 | } 82 | 83 | /* 84 | * @dev Get the next sibling node 85 | * @param der The DER-encoded ASN1 structure 86 | * @param ptr Points to the indices of the current node 87 | * @return A pointer to the next sibling node 88 | */ 89 | function nextSiblingOf(bytes memory der, Asn1Ptr ptr) internal pure returns (Asn1Ptr) { 90 | return readNodeLength(der, ptr.content() + ptr.length()); 91 | } 92 | 93 | /* 94 | * @dev Get the first child node of the current node 95 | * @param der The DER-encoded ASN1 structure 96 | * @param ptr Points to the indices of the current node 97 | * @return A pointer to the first child node 98 | */ 99 | function firstChildOf(bytes memory der, Asn1Ptr ptr) internal pure returns (Asn1Ptr) { 100 | require(der[ptr.header()] & 0x20 == 0x20, "Not a constructed type"); 101 | return readNodeLength(der, ptr.content()); 102 | } 103 | 104 | /* 105 | * @dev Extract pointer of bitstring node from DER-encoded structure 106 | * @param der The DER-encoded ASN1 structure 107 | * @param ptr Points to the indices of the current node 108 | * @return A pointer to a bitstring 109 | */ 110 | function bitstring(bytes memory der, Asn1Ptr ptr) internal pure returns (Asn1Ptr) { 111 | require(der[ptr.header()] == 0x03, "Not type BIT STRING"); 112 | // Only 00 padded bitstr can be converted to bytestr! 113 | require(der[ptr.content()] == 0x00, "Non-0-padded BIT STRING"); 114 | return LibAsn1Ptr.toAsn1Ptr(ptr.header(), ptr.content() + 1, ptr.length() - 1); 115 | } 116 | 117 | /* 118 | * @dev Extract value of bitstring node from DER-encoded structure 119 | * @param der The DER-encoded ASN1 structure 120 | * @param ptr Points to the indices of the current node 121 | * @return A bitstring encoded in a uint256 122 | */ 123 | function bitstringUintAt(bytes memory der, Asn1Ptr ptr) internal pure returns (uint256) { 124 | require(der[ptr.header()] == 0x03, "Not type BIT STRING"); 125 | uint256 len = ptr.length() - 1; 126 | return uint256(readBytesN(der, ptr.content() + 1, len) >> ((32 - len) * 8)); 127 | } 128 | 129 | /* 130 | * @dev Extract value of octet string node from DER-encoded structure 131 | * @param der The DER-encoded ASN1 structure 132 | * @param ptr Points to the indices of the current node 133 | * @return A pointer to a octet string 134 | */ 135 | function octetString(bytes memory der, Asn1Ptr ptr) internal pure returns (Asn1Ptr) { 136 | require(der[ptr.header()] == 0x04, "Not type OCTET STRING"); 137 | return readNodeLength(der, ptr.content()); 138 | } 139 | 140 | /* 141 | * @dev Extract value of node from DER-encoded structure 142 | * @param der The der-encoded ASN1 structure 143 | * @param ptr Points to the indices of the current node 144 | * @return Uint value of node 145 | */ 146 | function uintAt(bytes memory der, Asn1Ptr ptr) internal pure returns (uint256) { 147 | require(der[ptr.header()] == 0x02, "Not type INTEGER"); 148 | require(der[ptr.content()] & 0x80 == 0, "Not positive"); 149 | uint256 len = ptr.length(); 150 | return uint256(readBytesN(der, ptr.content(), len) >> (32 - len) * 8); 151 | } 152 | 153 | /* 154 | * @dev Extract value of a positive integer node from DER-encoded structure 155 | * @param der The DER-encoded ASN1 structure 156 | * @param ptr Points to the indices of the current node 157 | * @return 384-bit uint encoded in uint128 and uint256 158 | */ 159 | function uint384At(bytes memory der, Asn1Ptr ptr) internal pure returns (uint128, uint256) { 160 | require(der[ptr.header()] == 0x02, "Not type INTEGER"); 161 | require(der[ptr.content()] & 0x80 == 0, "Not positive"); 162 | uint256 valueLength = ptr.length(); 163 | uint256 start = ptr.content(); 164 | if (der[start] == 0) { 165 | start++; 166 | valueLength--; 167 | } 168 | return ( 169 | uint128(uint256(readBytesN(der, start, 16) >> 128)), 170 | uint256(readBytesN(der, start + 16, valueLength - 16) >> (48 - valueLength) * 8) 171 | ); 172 | } 173 | 174 | /* 175 | * @dev Extract value of a timestamp from DER-encoded structure 176 | * @param der The DER-encoded ASN1 structure 177 | * @param ptr Points to the indices of the current node 178 | * @return UNIX timestamp (seconds since 1970/01/01) 179 | */ 180 | function timestampAt(bytes memory der, Asn1Ptr ptr) internal pure returns (uint256) { 181 | uint8 _type = uint8(der[ptr.header()]); 182 | uint256 offset = ptr.content(); 183 | uint256 length = ptr.length(); 184 | 185 | // content validation: 186 | require((_type == 0x17 && length == 13) || (_type == 0x18 && length == 15), "Invalid TIMESTAMP"); 187 | require(der[offset + length - 1] == 0x5A, "TIMESTAMP must be UTC"); // 0x5A == 'Z' 188 | for (uint256 i = 0; i < length - 1; i++) { 189 | // all other characters must be digits between 0 and 9 190 | uint8 v = uint8(der[offset + i]); 191 | require(48 <= v && v <= 57, "Invalid character in TIMESTAMP"); 192 | } 193 | 194 | uint16 _years; 195 | if (length == 13) { 196 | _years = (uint8(der[offset]) - 48 < 5) ? 2000 : 1900; 197 | } else { 198 | _years = (uint8(der[offset]) - 48) * 1000 + (uint8(der[offset + 1]) - 48) * 100; 199 | offset += 2; 200 | } 201 | _years += (uint8(der[offset]) - 48) * 10 + uint8(der[offset + 1]) - 48; 202 | uint8 _months = (uint8(der[offset + 2]) - 48) * 10 + uint8(der[offset + 3]) - 48; 203 | uint8 _days = (uint8(der[offset + 4]) - 48) * 10 + uint8(der[offset + 5]) - 48; 204 | uint8 _hours = (uint8(der[offset + 6]) - 48) * 10 + uint8(der[offset + 7]) - 48; 205 | uint8 _mins = (uint8(der[offset + 8]) - 48) * 10 + uint8(der[offset + 9]) - 48; 206 | uint8 _secs = (uint8(der[offset + 10]) - 48) * 10 + uint8(der[offset + 11]) - 48; 207 | 208 | return timestampFromDateTime(_years, _months, _days, _hours, _mins, _secs); 209 | } 210 | 211 | function readNodeLength(bytes memory der, uint256 ix) private pure returns (Asn1Ptr) { 212 | require(der[ix] & 0x1f != 0x1f, "ASN.1 tags longer than 1-byte are not supported"); 213 | uint256 length; 214 | uint256 ixFirstContentByte; 215 | if ((der[ix + 1] & 0x80) == 0) { 216 | length = uint8(der[ix + 1]); 217 | ixFirstContentByte = ix + 2; 218 | } else { 219 | uint8 lengthbytesLength = uint8(der[ix + 1] & 0x7F); 220 | if (lengthbytesLength == 1) { 221 | length = uint8(der[ix + 2]); 222 | } else if (lengthbytesLength == 2) { 223 | length = der.readUint16(ix + 2); 224 | } else { 225 | length = uint256(readBytesN(der, ix + 2, lengthbytesLength) >> (32 - lengthbytesLength) * 8); 226 | require(length <= 2 ** 64 - 1); // bound to max uint64 to be safe 227 | } 228 | ixFirstContentByte = ix + 2 + lengthbytesLength; 229 | } 230 | return LibAsn1Ptr.toAsn1Ptr(ix, ixFirstContentByte, length); 231 | } 232 | 233 | function readBytesN(bytes memory self, uint256 idx, uint256 len) private pure returns (bytes32 ret) { 234 | require(len <= 32); 235 | require(idx + len <= self.length); 236 | assembly { 237 | let mask := not(sub(exp(256, sub(32, len)), 1)) 238 | ret := and(mload(add(add(self, 32), idx)), mask) 239 | } 240 | } 241 | 242 | // Calculate the number of seconds from 1970/01/01 to 243 | // year/month/day/hour/minute/second using the date conversion 244 | // algorithm from https://aa.usno.navy.mil/faq/JD_formula.html 245 | // and subtracting the offset 2440588 so that 1970/01/01 is day 0 246 | function timestampFromDateTime( 247 | uint256 year, 248 | uint256 month, 249 | uint256 day, 250 | uint256 hour, 251 | uint256 minute, 252 | uint256 second 253 | ) private pure returns (uint256) { 254 | require(year >= 1970); 255 | require(1 <= month && month <= 12); 256 | require(1 <= day && day <= 31); 257 | require(hour <= 23); 258 | require(minute <= 59); 259 | require(second <= 59); 260 | 261 | int256 _year = int256(year); 262 | int256 _month = int256(month); 263 | int256 _day = int256(day); 264 | 265 | int256 _days = _day - 32075 + 1461 * (_year + 4800 + (_month - 14) / 12) / 4 266 | + 367 * (_month - 2 - (_month - 14) / 12 * 12) / 12 - 3 * ((_year + 4900 + (_month - 14) / 12) / 100) / 4 267 | - 2440588; 268 | 269 | return ((uint256(_days) * 24 + hour) * 60 + minute) * 60 + second; 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /src/CborDecode.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import {LibBytes} from "./LibBytes.sol"; 5 | 6 | type CborElement is uint256; 7 | 8 | library LibCborElement { 9 | // Cbor element type 10 | function cborType(CborElement self) internal pure returns (uint8) { 11 | return uint8(CborElement.unwrap(self)); 12 | } 13 | 14 | // First byte index of the content 15 | function start(CborElement self) internal pure returns (uint256) { 16 | return uint80(CborElement.unwrap(self) >> 80); 17 | } 18 | 19 | // First byte index of the next element (exclusive end of content) 20 | function end(CborElement self) internal pure returns (uint256) { 21 | return start(self) + length(self); 22 | } 23 | 24 | // Content length (0 for non-string types) 25 | function length(CborElement self) internal pure returns (uint256) { 26 | uint8 _type = cborType(self); 27 | if (_type == 0x40 || _type == 0x60) { 28 | // length is non-zero only for byte strings and text strings 29 | return value(self); 30 | } 31 | return 0; 32 | } 33 | 34 | // Value of the element (length for string/map/array types, value for others) 35 | function value(CborElement self) internal pure returns (uint64) { 36 | return uint64(CborElement.unwrap(self) >> 160); 37 | } 38 | 39 | // Returns true if the element is null 40 | function isNull(CborElement self) internal pure returns (bool) { 41 | uint8 _type = cborType(self); 42 | return _type == 0xf6 || _type == 0xf7; // null or undefined 43 | } 44 | 45 | // Pack 3 uint80s into a uint256 46 | function toCborElement(uint256 _type, uint256 _start, uint256 _length) internal pure returns (CborElement) { 47 | return CborElement.wrap(_type | _start << 80 | _length << 160); 48 | } 49 | } 50 | 51 | library CborDecode { 52 | using LibBytes for bytes; 53 | using LibCborElement for CborElement; 54 | 55 | // Calculate the keccak256 hash of the given cbor element 56 | function keccak(bytes memory cbor, CborElement ptr) internal pure returns (bytes32) { 57 | return cbor.keccak(ptr.start(), ptr.length()); 58 | } 59 | 60 | // Take a slice of the given cbor element 61 | function slice(bytes memory cbor, CborElement ptr) internal pure returns (bytes memory) { 62 | return cbor.slice(ptr.start(), ptr.length()); 63 | } 64 | 65 | function byteStringAt(bytes memory cbor, uint256 ix) internal pure returns (CborElement) { 66 | return elementAt(cbor, ix, 0x40, true); 67 | } 68 | 69 | function nextByteString(bytes memory cbor, CborElement ptr) internal pure returns (CborElement) { 70 | return elementAt(cbor, ptr.end(), 0x40, true); 71 | } 72 | 73 | function nextByteStringOrNull(bytes memory cbor, CborElement ptr) internal pure returns (CborElement) { 74 | return elementAt(cbor, ptr.end(), 0x40, false); 75 | } 76 | 77 | function nextTextString(bytes memory cbor, CborElement ptr) internal pure returns (CborElement) { 78 | return elementAt(cbor, ptr.end(), 0x60, true); 79 | } 80 | 81 | function nextPositiveInt(bytes memory cbor, CborElement ptr) internal pure returns (CborElement) { 82 | return elementAt(cbor, ptr.end(), 0x00, true); 83 | } 84 | 85 | function mapAt(bytes memory cbor, uint256 ix) internal pure returns (CborElement) { 86 | return elementAt(cbor, ix, 0xa0, true); 87 | } 88 | 89 | function nextMap(bytes memory cbor, CborElement ptr) internal pure returns (CborElement) { 90 | return mapAt(cbor, ptr.end()); 91 | } 92 | 93 | function nextArray(bytes memory cbor, CborElement ptr) internal pure returns (CborElement) { 94 | return elementAt(cbor, ptr.end(), 0x80, true); 95 | } 96 | 97 | function elementAt(bytes memory cbor, uint256 ix, uint8 expectedType, bool required) 98 | internal 99 | pure 100 | returns (CborElement) 101 | { 102 | uint8 _type = uint8(cbor[ix] & 0xe0); 103 | uint8 ai = uint8(cbor[ix] & 0x1f); 104 | if (_type == 0xe0) { 105 | // The primitive type can encode a float, bool, null, undefined, etc. 106 | // We only need support for null (and we treat undefined as null). 107 | require(ai == 22 || ai == 23, "only null primitive values are supported"); 108 | require(!required, "null value for required element"); 109 | // retain the additional information: 110 | return LibCborElement.toCborElement(_type | ai, ix + 1, 0); 111 | } 112 | require(_type == expectedType, "unexpected type"); 113 | require(ai < 28, "unsupported type"); 114 | if (ai == 24) { 115 | return LibCborElement.toCborElement(_type, ix + 2, uint8(cbor[ix + 1])); 116 | } else if (ai == 25) { 117 | return LibCborElement.toCborElement(_type, ix + 3, cbor.readUint16(ix + 1)); 118 | } else if (ai == 26) { 119 | return LibCborElement.toCborElement(_type, ix + 5, cbor.readUint32(ix + 1)); 120 | } else if (ai == 27) { 121 | return LibCborElement.toCborElement(_type, ix + 9, cbor.readUint64(ix + 1)); 122 | } 123 | return LibCborElement.toCborElement(_type, ix + 1, ai); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/CertManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.22; 3 | 4 | import {Sha2Ext} from "./Sha2Ext.sol"; 5 | import {Asn1Decode, Asn1Ptr, LibAsn1Ptr} from "./Asn1Decode.sol"; 6 | import {ECDSA384} from "@solarity/libs/crypto/ECDSA384.sol"; 7 | import {ECDSA384Curve} from "./ECDSA384Curve.sol"; 8 | import {LibBytes} from "./LibBytes.sol"; 9 | import {ICertManager} from "./ICertManager.sol"; 10 | 11 | // adapted from https://github.com/marlinprotocol/NitroProver/blob/f1d368d1f172ad3a55cd2aaaa98ad6a6e7dcde9d/src/CertManager.sol 12 | 13 | // Manages a mapping of verified certificates and their metadata. 14 | // The root of trust is the AWS Nitro root cert. 15 | // Certificate revocation is not currently supported. 16 | contract CertManager is ICertManager { 17 | using Asn1Decode for bytes; 18 | using LibAsn1Ptr for Asn1Ptr; 19 | using LibBytes for bytes; 20 | 21 | event CertVerified(bytes32 indexed certHash); 22 | 23 | // root CA certificate constants (don't store it to reduce contract size) 24 | bytes32 public constant ROOT_CA_CERT_HASH = 0x311d96fcd5c5e0ccf72ef548e2ea7d4c0cd53ad7c4cc49e67471aed41d61f185; 25 | uint64 public constant ROOT_CA_CERT_NOT_AFTER = 2519044085; 26 | int64 public constant ROOT_CA_CERT_MAX_PATH_LEN = -1; 27 | bytes32 public constant ROOT_CA_CERT_SUBJECT_HASH = 28 | 0x3c3e2e5f1dd14dee5db88341ba71521e939afdb7881aa24c9f1e1c007a2fa8b6; 29 | bytes public constant ROOT_CA_CERT_PUB_KEY = 30 | hex"fc0254eba608c1f36870e29ada90be46383292736e894bfff672d989444b5051e534a4b1f6dbe3c0bc581a32b7b176070ede12d69a3fea211b66e752cf7dd1dd095f6f1370f4170843d9dc100121e4cf63012809664487c9796284304dc53ff4"; 31 | 32 | // OID 1.2.840.10045.4.3.3 represents {iso(1) member-body(2) us(840) ansi-x962(10045) signatures(4) ecdsa-with-SHA2(3) ecdsa-with-SHA384(3)} 33 | // which essentially means the signature algorithm is Elliptic curve Digital Signature Algorithm (DSA) coupled with the Secure Hash Algorithm 384 (SHA384) algorithm 34 | // @dev Sig algo is hardcoded here because the root certificate's sig algorithm is known beforehand 35 | // @dev reference article for encoding https://learn.microsoft.com/en-in/windows/win32/seccertenroll/about-object-identifier 36 | bytes32 public constant CERT_ALGO_OID = 0x53ce037f0dfaa43ef13b095f04e68a6b5e3f1519a01a3203a1e6440ba915b87e; // keccak256(hex"06082a8648ce3d040303") 37 | // https://oid-rep.orange-labs.fr/get/1.2.840.10045.2.1 38 | // 1.2.840.10045.2.1 {iso(1) member-body(2) us(840) ansi-x962(10045) keyType(2) ecPublicKey(1)} represents Elliptic curve public key cryptography 39 | bytes32 public constant EC_PUB_KEY_OID = 0xb60fee1fd85f867dd7c8d16884a49a20287ebe4c0fb49294e9825988aa8e42b4; // keccak256(hex"2a8648ce3d0201") 40 | // https://oid-rep.orange-labs.fr/get/1.3.132.0.34 41 | // 1.3.132.0.34 {iso(1) identified-organization(3) certicom(132) curve(0) ansip384r1(34)} represents NIST 384-bit elliptic curve 42 | bytes32 public constant SECP_384_R1_OID = 0xbd74344bb507daeb9ed315bc535f24a236ccab72c5cd6945fb0efe5c037e2097; // keccak256(hex"2b81040022") 43 | 44 | // extension OID certificate constants 45 | bytes32 public constant BASIC_CONSTRAINTS_OID = 0x6351d72a43cb42fb9a2531a28608c278c89629f8f025b5f5dc705f3fe45e950a; // keccak256(hex"551d13") 46 | bytes32 public constant KEY_USAGE_OID = 0x45529d8772b07ebd6d507a1680da791f4a2192882bf89d518801579f7a5167d2; // keccak256(hex"551d0f") 47 | 48 | // certHash -> VerifiedCert 49 | mapping(bytes32 => bytes) public verified; 50 | 51 | constructor() { 52 | _saveVerified( 53 | ROOT_CA_CERT_HASH, 54 | VerifiedCert({ 55 | ca: true, 56 | notAfter: ROOT_CA_CERT_NOT_AFTER, 57 | maxPathLen: ROOT_CA_CERT_MAX_PATH_LEN, 58 | subjectHash: ROOT_CA_CERT_SUBJECT_HASH, 59 | pubKey: ROOT_CA_CERT_PUB_KEY 60 | }) 61 | ); 62 | } 63 | 64 | function verifyCACert(bytes memory cert, bytes32 parentCertHash) external returns (bytes32) { 65 | bytes32 certHash = keccak256(cert); 66 | _verifyCert(cert, certHash, true, _loadVerified(parentCertHash)); 67 | return certHash; 68 | } 69 | 70 | function verifyClientCert(bytes memory cert, bytes32 parentCertHash) external returns (VerifiedCert memory) { 71 | return _verifyCert(cert, keccak256(cert), false, _loadVerified(parentCertHash)); 72 | } 73 | 74 | function _verifyCert(bytes memory certificate, bytes32 certHash, bool ca, VerifiedCert memory parent) 75 | internal 76 | returns (VerifiedCert memory) 77 | { 78 | if (certHash != ROOT_CA_CERT_HASH) { 79 | require(parent.pubKey.length > 0, "parent cert unverified"); 80 | require(parent.notAfter >= block.timestamp, "parent cert expired"); 81 | require(parent.ca, "parent cert is not a CA"); 82 | require(!ca || parent.maxPathLen != 0, "maxPathLen exceeded"); 83 | } 84 | 85 | // skip verification if already verified 86 | VerifiedCert memory cert = _loadVerified(certHash); 87 | if (cert.pubKey.length != 0) { 88 | require(cert.notAfter >= block.timestamp, "cert expired"); 89 | require(cert.ca == ca, "cert is not a CA"); 90 | return cert; 91 | } 92 | 93 | Asn1Ptr root = certificate.root(); 94 | Asn1Ptr tbsCertPtr = certificate.firstChildOf(root); 95 | (uint64 notAfter, int64 maxPathLen, bytes32 issuerHash, bytes32 subjectHash, bytes memory pubKey) = 96 | _parseTbs(certificate, tbsCertPtr, ca); 97 | 98 | require(parent.subjectHash == issuerHash, "issuer / subject mismatch"); 99 | 100 | // constrain maxPathLen to parent's maxPathLen-1 101 | if (parent.maxPathLen > 0 && (maxPathLen < 0 || maxPathLen >= parent.maxPathLen)) { 102 | maxPathLen = parent.maxPathLen - 1; 103 | } 104 | 105 | _verifyCertSignature(certificate, tbsCertPtr, parent.pubKey); 106 | 107 | cert = 108 | VerifiedCert({ca: ca, notAfter: notAfter, maxPathLen: maxPathLen, subjectHash: subjectHash, pubKey: pubKey}); 109 | _saveVerified(certHash, cert); 110 | 111 | emit CertVerified(certHash); 112 | 113 | return cert; 114 | } 115 | 116 | function _parseTbs(bytes memory certificate, Asn1Ptr ptr, bool ca) 117 | internal 118 | view 119 | returns (uint64 notAfter, int64 maxPathLen, bytes32 issuerHash, bytes32 subjectHash, bytes memory pubKey) 120 | { 121 | Asn1Ptr versionPtr = certificate.firstChildOf(ptr); 122 | Asn1Ptr vPtr = certificate.firstChildOf(versionPtr); 123 | Asn1Ptr serialPtr = certificate.nextSiblingOf(versionPtr); 124 | Asn1Ptr sigAlgoPtr = certificate.nextSiblingOf(serialPtr); 125 | 126 | require(certificate.keccak(sigAlgoPtr.content(), sigAlgoPtr.length()) == CERT_ALGO_OID, "invalid cert sig algo"); 127 | uint256 version = certificate.uintAt(vPtr); 128 | // as extensions are used in cert, version should be 3 (value 2) as per https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.1 129 | require(version == 2, "version should be 3"); 130 | 131 | (notAfter, maxPathLen, issuerHash, subjectHash, pubKey) = _parseTbsInner(certificate, sigAlgoPtr, ca); 132 | } 133 | 134 | function _parseTbsInner(bytes memory certificate, Asn1Ptr sigAlgoPtr, bool ca) 135 | internal 136 | view 137 | returns (uint64 notAfter, int64 maxPathLen, bytes32 issuerHash, bytes32 subjectHash, bytes memory pubKey) 138 | { 139 | Asn1Ptr issuerPtr = certificate.nextSiblingOf(sigAlgoPtr); 140 | issuerHash = certificate.keccak(issuerPtr.content(), issuerPtr.length()); 141 | Asn1Ptr validityPtr = certificate.nextSiblingOf(issuerPtr); 142 | Asn1Ptr subjectPtr = certificate.nextSiblingOf(validityPtr); 143 | subjectHash = certificate.keccak(subjectPtr.content(), subjectPtr.length()); 144 | Asn1Ptr subjectPublicKeyInfoPtr = certificate.nextSiblingOf(subjectPtr); 145 | Asn1Ptr extensionsPtr = certificate.nextSiblingOf(subjectPublicKeyInfoPtr); 146 | 147 | if (certificate[extensionsPtr.header()] == 0x81) { 148 | // skip optional issuerUniqueID 149 | extensionsPtr = certificate.nextSiblingOf(extensionsPtr); 150 | } 151 | if (certificate[extensionsPtr.header()] == 0x82) { 152 | // skip optional subjectUniqueID 153 | extensionsPtr = certificate.nextSiblingOf(extensionsPtr); 154 | } 155 | 156 | notAfter = _verifyValidity(certificate, validityPtr); 157 | maxPathLen = _verifyExtensions(certificate, extensionsPtr, ca); 158 | pubKey = _parsePubKey(certificate, subjectPublicKeyInfoPtr); 159 | } 160 | 161 | function _parsePubKey(bytes memory certificate, Asn1Ptr subjectPublicKeyInfoPtr) 162 | internal 163 | pure 164 | returns (bytes memory subjectPubKey) 165 | { 166 | Asn1Ptr pubKeyAlgoPtr = certificate.firstChildOf(subjectPublicKeyInfoPtr); 167 | Asn1Ptr pubKeyAlgoIdPtr = certificate.firstChildOf(pubKeyAlgoPtr); 168 | Asn1Ptr algoParamsPtr = certificate.nextSiblingOf(pubKeyAlgoIdPtr); 169 | Asn1Ptr subjectPublicKeyPtr = certificate.nextSiblingOf(pubKeyAlgoPtr); 170 | Asn1Ptr subjectPubKeyPtr = certificate.bitstring(subjectPublicKeyPtr); 171 | 172 | require( 173 | certificate.keccak(pubKeyAlgoIdPtr.content(), pubKeyAlgoIdPtr.length()) == EC_PUB_KEY_OID, 174 | "invalid cert algo id" 175 | ); 176 | require( 177 | certificate.keccak(algoParamsPtr.content(), algoParamsPtr.length()) == SECP_384_R1_OID, 178 | "invalid cert algo param" 179 | ); 180 | 181 | uint256 end = subjectPubKeyPtr.content() + subjectPubKeyPtr.length(); 182 | subjectPubKey = certificate.slice(end - 96, 96); 183 | } 184 | 185 | function _verifyValidity(bytes memory certificate, Asn1Ptr validityPtr) internal view returns (uint64 notAfter) { 186 | Asn1Ptr notBeforePtr = certificate.firstChildOf(validityPtr); 187 | Asn1Ptr notAfterPtr = certificate.nextSiblingOf(notBeforePtr); 188 | 189 | uint256 notBefore = certificate.timestampAt(notBeforePtr); 190 | notAfter = uint64(certificate.timestampAt(notAfterPtr)); 191 | 192 | require(notBefore <= block.timestamp, "certificate not valid yet"); 193 | require(notAfter >= block.timestamp, "certificate not valid anymore"); 194 | } 195 | 196 | function _verifyExtensions(bytes memory certificate, Asn1Ptr extensionsPtr, bool ca) 197 | internal 198 | pure 199 | returns (int64 maxPathLen) 200 | { 201 | require(certificate[extensionsPtr.header()] == 0xa3, "invalid extensions"); 202 | extensionsPtr = certificate.firstChildOf(extensionsPtr); 203 | Asn1Ptr extensionPtr = certificate.firstChildOf(extensionsPtr); 204 | uint256 end = extensionsPtr.content() + extensionsPtr.length(); 205 | bool basicConstraintsFound = false; 206 | bool keyUsageFound = false; 207 | maxPathLen = -1; 208 | 209 | while (true) { 210 | Asn1Ptr oidPtr = certificate.firstChildOf(extensionPtr); 211 | bytes32 oid = certificate.keccak(oidPtr.content(), oidPtr.length()); 212 | 213 | if (oid == BASIC_CONSTRAINTS_OID || oid == KEY_USAGE_OID) { 214 | Asn1Ptr valuePtr = certificate.nextSiblingOf(oidPtr); 215 | 216 | if (certificate[valuePtr.header()] == 0x01) { 217 | // skip optional critical bool 218 | require(valuePtr.length() == 1, "invalid critical bool value"); 219 | valuePtr = certificate.nextSiblingOf(valuePtr); 220 | } 221 | 222 | valuePtr = certificate.octetString(valuePtr); 223 | 224 | if (oid == BASIC_CONSTRAINTS_OID) { 225 | basicConstraintsFound = true; 226 | maxPathLen = _verifyBasicConstraintsExtension(certificate, valuePtr, ca); 227 | } else { 228 | keyUsageFound = true; 229 | _verifyKeyUsageExtension(certificate, valuePtr, ca); 230 | } 231 | } 232 | 233 | if (extensionPtr.content() + extensionPtr.length() == end) { 234 | break; 235 | } 236 | extensionPtr = certificate.nextSiblingOf(extensionPtr); 237 | } 238 | 239 | require(basicConstraintsFound, "basicConstraints not found"); 240 | require(keyUsageFound, "keyUsage not found"); 241 | require(ca || maxPathLen == -1, "maxPathLen must be undefined for client cert"); 242 | } 243 | 244 | function _verifyBasicConstraintsExtension(bytes memory certificate, Asn1Ptr valuePtr, bool ca) 245 | internal 246 | pure 247 | returns (int64 maxPathLen) 248 | { 249 | maxPathLen = -1; 250 | Asn1Ptr basicConstraintsPtr = certificate.firstChildOf(valuePtr); 251 | bool isCA; 252 | if (certificate[basicConstraintsPtr.header()] == 0x01) { 253 | require(basicConstraintsPtr.length() == 1, "invalid isCA bool value"); 254 | isCA = certificate[basicConstraintsPtr.content()] == 0xff; 255 | basicConstraintsPtr = certificate.nextSiblingOf(basicConstraintsPtr); 256 | } 257 | require(ca == isCA, "isCA must be true for CA certs"); 258 | if (certificate[basicConstraintsPtr.header()] == 0x02) { 259 | maxPathLen = int64(uint64(certificate.uintAt(basicConstraintsPtr))); 260 | } 261 | } 262 | 263 | function _verifyKeyUsageExtension(bytes memory certificate, Asn1Ptr valuePtr, bool ca) internal pure { 264 | uint256 value = certificate.bitstringUintAt(valuePtr); 265 | // bits are reversed (DigitalSignature 0x01 => 0x80, CertSign 0x32 => 0x04) 266 | if (ca) { 267 | require(value & 0x04 == 0x04, "CertSign must be present"); 268 | } else { 269 | require(value & 0x80 == 0x80, "DigitalSignature must be present"); 270 | } 271 | } 272 | 273 | function _verifyCertSignature(bytes memory certificate, Asn1Ptr ptr, bytes memory pubKey) internal view { 274 | Asn1Ptr sigAlgoPtr = certificate.nextSiblingOf(ptr); 275 | require(certificate.keccak(sigAlgoPtr.content(), sigAlgoPtr.length()) == CERT_ALGO_OID, "invalid cert sig algo"); 276 | 277 | bytes memory hash = Sha2Ext.sha384(certificate, ptr.header(), ptr.totalLength()); 278 | 279 | Asn1Ptr sigPtr = certificate.nextSiblingOf(sigAlgoPtr); 280 | Asn1Ptr sigBPtr = certificate.bitstring(sigPtr); 281 | Asn1Ptr sigRoot = certificate.rootOf(sigBPtr); 282 | Asn1Ptr sigRPtr = certificate.firstChildOf(sigRoot); 283 | Asn1Ptr sigSPtr = certificate.nextSiblingOf(sigRPtr); 284 | (uint128 rhi, uint256 rlo) = certificate.uint384At(sigRPtr); 285 | (uint128 shi, uint256 slo) = certificate.uint384At(sigSPtr); 286 | bytes memory sigPacked = abi.encodePacked(rhi, rlo, shi, slo); 287 | 288 | _verifySignature(pubKey, hash, sigPacked); 289 | } 290 | 291 | function _verifySignature(bytes memory pubKey, bytes memory hash, bytes memory sig) internal view { 292 | require(ECDSA384.verify(ECDSA384Curve.p384(), hash, sig, pubKey), "invalid sig"); 293 | } 294 | 295 | function _saveVerified(bytes32 certHash, VerifiedCert memory cert) internal { 296 | verified[certHash] = abi.encodePacked(cert.ca, cert.notAfter, cert.maxPathLen, cert.subjectHash, cert.pubKey); 297 | } 298 | 299 | function _loadVerified(bytes32 certHash) internal view returns (VerifiedCert memory) { 300 | bytes memory packed = verified[certHash]; 301 | if (packed.length == 0) { 302 | return VerifiedCert({ca: false, notAfter: 0, maxPathLen: 0, subjectHash: 0, pubKey: ""}); 303 | } 304 | uint8 ca; 305 | uint64 notAfter; 306 | int64 maxPathLen; 307 | bytes32 subjectHash; 308 | assembly { 309 | ca := mload(add(packed, 0x1)) 310 | notAfter := mload(add(packed, 0x9)) 311 | maxPathLen := mload(add(packed, 0x11)) 312 | subjectHash := mload(add(packed, 0x31)) 313 | } 314 | bytes memory pubKey = packed.slice(0x31, packed.length - 0x31); 315 | return VerifiedCert({ 316 | ca: ca != 0, 317 | notAfter: notAfter, 318 | maxPathLen: maxPathLen, 319 | subjectHash: subjectHash, 320 | pubKey: pubKey 321 | }); 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /src/ECDSA384Curve.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import {ECDSA384} from "@solarity/libs/crypto/ECDSA384.sol"; 5 | 6 | library ECDSA384Curve { 7 | // ECDSA384 curve parameters (NIST P-384) 8 | bytes public constant CURVE_A = 9 | hex"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc"; 10 | bytes public constant CURVE_B = 11 | hex"b3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef"; 12 | bytes public constant CURVE_GX = 13 | hex"aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7"; 14 | bytes public constant CURVE_GY = 15 | hex"3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f"; 16 | bytes public constant CURVE_P = 17 | hex"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff"; 18 | bytes public constant CURVE_N = 19 | hex"ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973"; 20 | // use n-1 for lowSmax, which allows s-values above n/2 21 | bytes public constant CURVE_LOW_S_MAX = 22 | hex"ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52972"; 23 | 24 | function p384() internal pure returns (ECDSA384.Parameters memory) { 25 | return ECDSA384.Parameters({ 26 | a: CURVE_A, 27 | b: CURVE_B, 28 | gx: CURVE_GX, 29 | gy: CURVE_GY, 30 | p: CURVE_P, 31 | n: CURVE_N, 32 | lowSmax: CURVE_LOW_S_MAX 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ICertManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | interface ICertManager { 5 | struct VerifiedCert { 6 | bool ca; 7 | uint64 notAfter; 8 | int64 maxPathLen; 9 | bytes32 subjectHash; 10 | bytes pubKey; 11 | } 12 | 13 | function verifyCACert(bytes memory cert, bytes32 parentCertHash) external returns (bytes32); 14 | 15 | function verifyClientCert(bytes memory cert, bytes32 parentCertHash) external returns (VerifiedCert memory); 16 | } 17 | -------------------------------------------------------------------------------- /src/LibBytes.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | library LibBytes { 5 | function keccak(bytes memory data, uint256 offset, uint256 length) internal pure returns (bytes32 result) { 6 | require(offset + length <= data.length, "index out of bounds"); 7 | assembly { 8 | result := keccak256(add(data, add(32, offset)), length) 9 | } 10 | } 11 | 12 | function slice(bytes memory b, uint256 offset, uint256 length) internal pure returns (bytes memory result) { 13 | require(offset + length <= b.length, "index out of bounds"); 14 | 15 | // Create a new bytes structure and copy contents 16 | result = new bytes(length); 17 | uint256 dest; 18 | uint256 src; 19 | assembly { 20 | dest := add(result, 32) 21 | src := add(b, add(32, offset)) 22 | } 23 | memcpy(dest, src, length); 24 | return result; 25 | } 26 | 27 | function readUint16(bytes memory b, uint256 index) internal pure returns (uint16) { 28 | require(b.length >= index + 2, "index out of bounds"); 29 | bytes2 result; 30 | assembly { 31 | result := mload(add(b, add(index, 32))) 32 | } 33 | return uint16(result); 34 | } 35 | 36 | function readUint32(bytes memory b, uint256 index) internal pure returns (uint32) { 37 | require(b.length >= index + 4, "index out of bounds"); 38 | bytes4 result; 39 | assembly { 40 | result := mload(add(b, add(index, 32))) 41 | } 42 | return uint32(result); 43 | } 44 | 45 | function readUint64(bytes memory b, uint256 index) internal pure returns (uint64) { 46 | require(b.length >= index + 8, "index out of bounds"); 47 | bytes8 result; 48 | assembly { 49 | result := mload(add(b, add(index, 32))) 50 | } 51 | return uint64(result); 52 | } 53 | 54 | function memcpy(uint256 dest, uint256 src, uint256 len) internal pure { 55 | // Copy word-length chunks while possible 56 | for (; len >= 32; len -= 32) { 57 | assembly { 58 | mstore(dest, mload(src)) 59 | } 60 | dest += 32; 61 | src += 32; 62 | } 63 | 64 | if (len > 0) { 65 | // Copy remaining bytes 66 | uint256 mask = 256 ** (32 - len) - 1; 67 | assembly { 68 | let srcpart := and(mload(src), not(mask)) 69 | let destpart := and(mload(dest), mask) 70 | mstore(dest, or(destpart, srcpart)) 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/NitroValidator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import {ICertManager} from "./ICertManager.sol"; 5 | import {Sha2Ext} from "./Sha2Ext.sol"; 6 | import {CborDecode, CborElement, LibCborElement} from "./CborDecode.sol"; 7 | import {ECDSA384} from "@solarity/libs/crypto/ECDSA384.sol"; 8 | import {ECDSA384Curve} from "./ECDSA384Curve.sol"; 9 | import {LibBytes} from "./LibBytes.sol"; 10 | 11 | // adapted from https://github.com/marlinprotocol/NitroProver/blob/f1d368d1f172ad3a55cd2aaaa98ad6a6e7dcde9d/src/NitroProver.sol 12 | 13 | contract NitroValidator { 14 | using LibBytes for bytes; 15 | using CborDecode for bytes; 16 | using LibCborElement for CborElement; 17 | 18 | bytes32 public constant ATTESTATION_TBS_PREFIX = 0x63ce814bd924c1ef12c43686e4cbf48ed1639a78387b0570c23ca921e8ce071c; // keccak256(hex"846a5369676e61747572653144a101382240") 19 | bytes32 public constant ATTESTATION_DIGEST = 0x501a3a7a4e0cf54b03f2488098bdd59bc1c2e8d741a300d6b25926d531733fef; // keccak256("SHA384") 20 | 21 | bytes32 public constant CERTIFICATE_KEY = 0x925cec779426f44d8d555e01d2683a3a765ce2fa7562ca7352aeb09dfc57ea6a; // keccak256(bytes("certificate")) 22 | bytes32 public constant PUBLIC_KEY_KEY = 0xc7b28019ccfdbd30ffc65951d94bb85c9e2b8434111a000b5afd533ce65f57a4; // keccak256(bytes("public_key")) 23 | bytes32 public constant MODULE_ID_KEY = 0x8ce577cf664c36ba5130242bf5790c2675e9f4e6986a842b607821bee25372ee; // keccak256(bytes("module_id")) 24 | bytes32 public constant TIMESTAMP_KEY = 0x4ebf727c48eac2c66272456b06a885c5cc03e54d140f63b63b6fd10c1227958e; // keccak256(bytes("timestamp")) 25 | bytes32 public constant USER_DATA_KEY = 0x5e4ea5393e4327b3014bc32f2264336b0d1ee84a4cfd197c8ad7e1e16829a16a; // keccak256(bytes("user_data")) 26 | bytes32 public constant CABUNDLE_KEY = 0x8a8cb7aa1da17ada103546ae6b4e13ccc2fafa17adf5f93925e0a0a4e5681a6a; // keccak256(bytes("cabundle")) 27 | bytes32 public constant DIGEST_KEY = 0x682a7e258d80bd2421d3103cbe71e3e3b82138116756b97b8256f061dc2f11fb; // keccak256(bytes("digest")) 28 | bytes32 public constant NONCE_KEY = 0x7ab1577440dd7bedf920cb6de2f9fc6bf7ba98c78c85a3fa1f8311aac95e1759; // keccak256(bytes("nonce")) 29 | bytes32 public constant PCRS_KEY = 0x61585f8bc67a4b6d5891a4639a074964ac66fc2241dc0b36c157dc101325367a; // keccak256(bytes("pcrs")) 30 | 31 | struct Ptrs { 32 | CborElement moduleID; 33 | uint64 timestamp; 34 | CborElement digest; 35 | CborElement[] pcrs; 36 | CborElement cert; 37 | CborElement[] cabundle; 38 | CborElement publicKey; 39 | CborElement userData; 40 | CborElement nonce; 41 | } 42 | 43 | ICertManager public immutable certManager; 44 | 45 | constructor(ICertManager _certManager) { 46 | certManager = _certManager; 47 | } 48 | 49 | function decodeAttestationTbs(bytes memory attestation) 50 | external 51 | pure 52 | returns (bytes memory attestationTbs, bytes memory signature) 53 | { 54 | uint256 offset = 1; 55 | if (attestation[0] == 0xD2) { 56 | offset = 2; 57 | } 58 | 59 | CborElement protectedPtr = attestation.byteStringAt(offset); 60 | CborElement unprotectedPtr = attestation.nextMap(protectedPtr); 61 | CborElement payloadPtr = attestation.nextByteString(unprotectedPtr); 62 | CborElement signaturePtr = attestation.nextByteString(payloadPtr); 63 | 64 | uint256 rawProtectedLength = protectedPtr.end() - offset; 65 | uint256 rawPayloadLength = payloadPtr.end() - unprotectedPtr.end(); 66 | bytes memory rawProtectedBytes = attestation.slice(offset, rawProtectedLength); 67 | bytes memory rawPayloadBytes = attestation.slice(unprotectedPtr.end(), rawPayloadLength); 68 | attestationTbs = 69 | _constructAttestationTbs(rawProtectedBytes, rawProtectedLength, rawPayloadBytes, rawPayloadLength); 70 | signature = attestation.slice(signaturePtr.start(), signaturePtr.length()); 71 | } 72 | 73 | function validateAttestation(bytes memory attestationTbs, bytes memory signature) public returns (Ptrs memory) { 74 | Ptrs memory ptrs = _parseAttestation(attestationTbs); 75 | 76 | require(ptrs.moduleID.length() > 0, "no module id"); 77 | require(ptrs.timestamp > 0, "no timestamp"); 78 | require(ptrs.cabundle.length > 0, "no cabundle"); 79 | require(attestationTbs.keccak(ptrs.digest) == ATTESTATION_DIGEST, "invalid digest"); 80 | require(1 <= ptrs.pcrs.length && ptrs.pcrs.length <= 32, "invalid pcrs"); 81 | require( 82 | ptrs.publicKey.isNull() || (1 <= ptrs.publicKey.length() && ptrs.publicKey.length() <= 1024), 83 | "invalid pub key" 84 | ); 85 | require(ptrs.userData.isNull() || (ptrs.userData.length() <= 512), "invalid user data"); 86 | require(ptrs.nonce.isNull() || (ptrs.nonce.length() <= 512), "invalid nonce"); 87 | 88 | for (uint256 i = 0; i < ptrs.pcrs.length; i++) { 89 | require( 90 | ptrs.pcrs[i].length() == 32 || ptrs.pcrs[i].length() == 48 || ptrs.pcrs[i].length() == 64, "invalid pcr" 91 | ); 92 | } 93 | 94 | bytes memory cert = attestationTbs.slice(ptrs.cert); 95 | bytes[] memory cabundle = new bytes[](ptrs.cabundle.length); 96 | for (uint256 i = 0; i < ptrs.cabundle.length; i++) { 97 | require(1 <= ptrs.cabundle[i].length() && ptrs.cabundle[i].length() <= 1024, "invalid cabundle cert"); 98 | cabundle[i] = attestationTbs.slice(ptrs.cabundle[i]); 99 | } 100 | 101 | ICertManager.VerifiedCert memory parent = verifyCertBundle(cert, cabundle); 102 | bytes memory hash = Sha2Ext.sha384(attestationTbs, 0, attestationTbs.length); 103 | _verifySignature(parent.pubKey, hash, signature); 104 | 105 | return ptrs; 106 | } 107 | 108 | function verifyCertBundle(bytes memory certificate, bytes[] memory cabundle) 109 | internal 110 | returns (ICertManager.VerifiedCert memory) 111 | { 112 | bytes32 parentHash; 113 | for (uint256 i = 0; i < cabundle.length; i++) { 114 | parentHash = certManager.verifyCACert(cabundle[i], parentHash); 115 | } 116 | return certManager.verifyClientCert(certificate, parentHash); 117 | } 118 | 119 | function _constructAttestationTbs( 120 | bytes memory rawProtectedBytes, 121 | uint256 rawProtectedLength, 122 | bytes memory rawPayloadBytes, 123 | uint256 rawPayloadLength 124 | ) internal pure returns (bytes memory attestationTbs) { 125 | attestationTbs = new bytes(13 + rawProtectedLength + rawPayloadLength); 126 | attestationTbs[0] = bytes1(uint8(4 << 5 | 4)); // Outer: 4-length array 127 | attestationTbs[1] = bytes1(uint8(3 << 5 | 10)); // Context: 10-length string 128 | attestationTbs[12 + rawProtectedLength] = bytes1(uint8(2 << 5)); // ExternalAAD: 0-length bytes 129 | 130 | string memory sig = "Signature1"; 131 | uint256 dest; 132 | uint256 sigSrc; 133 | uint256 protectedSrc; 134 | uint256 payloadSrc; 135 | assembly { 136 | dest := add(attestationTbs, 32) 137 | sigSrc := add(sig, 32) 138 | protectedSrc := add(rawProtectedBytes, 32) 139 | payloadSrc := add(rawPayloadBytes, 32) 140 | } 141 | 142 | LibBytes.memcpy(dest + 2, sigSrc, 10); 143 | LibBytes.memcpy(dest + 12, protectedSrc, rawProtectedLength); 144 | LibBytes.memcpy(dest + 13 + rawProtectedLength, payloadSrc, rawPayloadLength); 145 | } 146 | 147 | function _parseAttestation(bytes memory attestationTbs) internal pure returns (Ptrs memory) { 148 | require(attestationTbs.keccak(0, 18) == ATTESTATION_TBS_PREFIX, "invalid attestation prefix"); 149 | 150 | CborElement payload = attestationTbs.byteStringAt(18); 151 | CborElement current = attestationTbs.mapAt(payload.start()); 152 | 153 | Ptrs memory ptrs; 154 | uint256 end = payload.end(); 155 | while (current.end() < end) { 156 | current = attestationTbs.nextTextString(current); 157 | bytes32 keyHash = attestationTbs.keccak(current); 158 | if (keyHash == MODULE_ID_KEY) { 159 | current = attestationTbs.nextTextString(current); 160 | ptrs.moduleID = current; 161 | } else if (keyHash == DIGEST_KEY) { 162 | current = attestationTbs.nextTextString(current); 163 | ptrs.digest = current; 164 | } else if (keyHash == CERTIFICATE_KEY) { 165 | current = attestationTbs.nextByteString(current); 166 | ptrs.cert = current; 167 | } else if (keyHash == PUBLIC_KEY_KEY) { 168 | current = attestationTbs.nextByteStringOrNull(current); 169 | ptrs.publicKey = current; 170 | } else if (keyHash == USER_DATA_KEY) { 171 | current = attestationTbs.nextByteStringOrNull(current); 172 | ptrs.userData = current; 173 | } else if (keyHash == NONCE_KEY) { 174 | current = attestationTbs.nextByteStringOrNull(current); 175 | ptrs.nonce = current; 176 | } else if (keyHash == TIMESTAMP_KEY) { 177 | current = attestationTbs.nextPositiveInt(current); 178 | ptrs.timestamp = uint64(current.value()); 179 | } else if (keyHash == CABUNDLE_KEY) { 180 | current = attestationTbs.nextArray(current); 181 | ptrs.cabundle = new CborElement[](current.value()); 182 | for (uint256 i = 0; i < ptrs.cabundle.length; i++) { 183 | current = attestationTbs.nextByteString(current); 184 | ptrs.cabundle[i] = current; 185 | } 186 | } else if (keyHash == PCRS_KEY) { 187 | current = attestationTbs.nextMap(current); 188 | ptrs.pcrs = new CborElement[](current.value()); 189 | for (uint256 i = 0; i < ptrs.pcrs.length; i++) { 190 | current = attestationTbs.nextPositiveInt(current); 191 | uint256 key = current.value(); 192 | require(key < ptrs.pcrs.length, "invalid pcr key value"); 193 | require(CborElement.unwrap(ptrs.pcrs[key]) == 0, "duplicate pcr key"); 194 | current = attestationTbs.nextByteString(current); 195 | ptrs.pcrs[key] = current; 196 | } 197 | } else { 198 | revert("invalid attestation key"); 199 | } 200 | } 201 | 202 | return ptrs; 203 | } 204 | 205 | function _verifySignature(bytes memory pubKey, bytes memory hash, bytes memory sig) internal view { 206 | require(ECDSA384.verify(ECDSA384Curve.p384(), hash, sig, pubKey), "invalid sig"); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/Sha2Ext.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | // adapted from https://github.com/yangfh2004/SolSha2Ext/blob/main/contracts/lib/Sha2Ext.sol 5 | 6 | import {LibBytes} from "./LibBytes.sol"; 7 | 8 | library Sha2Ext { 9 | using LibBytes for bytes; 10 | 11 | function sha2(bytes memory message, uint256 offset, uint256 length, uint64[8] memory h) internal pure { 12 | uint64[80] memory k = [ 13 | 0x428a2f98d728ae22, 14 | 0x7137449123ef65cd, 15 | 0xb5c0fbcfec4d3b2f, 16 | 0xe9b5dba58189dbbc, 17 | 0x3956c25bf348b538, 18 | 0x59f111f1b605d019, 19 | 0x923f82a4af194f9b, 20 | 0xab1c5ed5da6d8118, 21 | 0xd807aa98a3030242, 22 | 0x12835b0145706fbe, 23 | 0x243185be4ee4b28c, 24 | 0x550c7dc3d5ffb4e2, 25 | 0x72be5d74f27b896f, 26 | 0x80deb1fe3b1696b1, 27 | 0x9bdc06a725c71235, 28 | 0xc19bf174cf692694, 29 | 0xe49b69c19ef14ad2, 30 | 0xefbe4786384f25e3, 31 | 0x0fc19dc68b8cd5b5, 32 | 0x240ca1cc77ac9c65, 33 | 0x2de92c6f592b0275, 34 | 0x4a7484aa6ea6e483, 35 | 0x5cb0a9dcbd41fbd4, 36 | 0x76f988da831153b5, 37 | 0x983e5152ee66dfab, 38 | 0xa831c66d2db43210, 39 | 0xb00327c898fb213f, 40 | 0xbf597fc7beef0ee4, 41 | 0xc6e00bf33da88fc2, 42 | 0xd5a79147930aa725, 43 | 0x06ca6351e003826f, 44 | 0x142929670a0e6e70, 45 | 0x27b70a8546d22ffc, 46 | 0x2e1b21385c26c926, 47 | 0x4d2c6dfc5ac42aed, 48 | 0x53380d139d95b3df, 49 | 0x650a73548baf63de, 50 | 0x766a0abb3c77b2a8, 51 | 0x81c2c92e47edaee6, 52 | 0x92722c851482353b, 53 | 0xa2bfe8a14cf10364, 54 | 0xa81a664bbc423001, 55 | 0xc24b8b70d0f89791, 56 | 0xc76c51a30654be30, 57 | 0xd192e819d6ef5218, 58 | 0xd69906245565a910, 59 | 0xf40e35855771202a, 60 | 0x106aa07032bbd1b8, 61 | 0x19a4c116b8d2d0c8, 62 | 0x1e376c085141ab53, 63 | 0x2748774cdf8eeb99, 64 | 0x34b0bcb5e19b48a8, 65 | 0x391c0cb3c5c95a63, 66 | 0x4ed8aa4ae3418acb, 67 | 0x5b9cca4f7763e373, 68 | 0x682e6ff3d6b2b8a3, 69 | 0x748f82ee5defb2fc, 70 | 0x78a5636f43172f60, 71 | 0x84c87814a1f0ab72, 72 | 0x8cc702081a6439ec, 73 | 0x90befffa23631e28, 74 | 0xa4506cebde82bde9, 75 | 0xbef9a3f7b2c67915, 76 | 0xc67178f2e372532b, 77 | 0xca273eceea26619c, 78 | 0xd186b8c721c0c207, 79 | 0xeada7dd6cde0eb1e, 80 | 0xf57d4f7fee6ed178, 81 | 0x06f067aa72176fba, 82 | 0x0a637dc5a2c898a6, 83 | 0x113f9804bef90dae, 84 | 0x1b710b35131c471b, 85 | 0x28db77f523047d84, 86 | 0x32caab7b40c72493, 87 | 0x3c9ebe0a15c9bebc, 88 | 0x431d67c49c100d4c, 89 | 0x4cc5d4becb3e42b6, 90 | 0x597f299cfc657e2a, 91 | 0x5fcb6fab3ad6faec, 92 | 0x6c44198c4a475817 93 | ]; 94 | 95 | require(offset + length <= message.length, "OUT_OF_BOUNDS"); 96 | bytes memory padding = padMessage(message, offset, length); 97 | require(padding.length % 128 == 0, "PADDING_ERROR"); 98 | uint64[80] memory w; 99 | uint64[8] memory temp; 100 | uint64[16] memory blocks; 101 | uint256 messageLength = (length / 128) * 128; 102 | unchecked { 103 | for (uint256 i = 0; i < (messageLength + padding.length); i += 128) { 104 | if (i < messageLength) { 105 | getBlock(message, blocks, offset + i); 106 | } else { 107 | getBlock(padding, blocks, i - messageLength); 108 | } 109 | for (uint256 j = 0; j < 16; ++j) { 110 | w[j] = blocks[j]; 111 | } 112 | for (uint256 j = 16; j < 80; ++j) { 113 | w[j] = gamma1(w[j - 2]) + w[j - 7] + gamma0(w[j - 15]) + w[j - 16]; 114 | } 115 | for (uint256 j = 0; j < 8; ++j) { 116 | temp[j] = h[j]; 117 | } 118 | for (uint256 j = 0; j < 80; ++j) { 119 | uint64 t1 = temp[7] + sigma1(temp[4]) + ch(temp[4], temp[5], temp[6]) + k[j] + w[j]; 120 | uint64 t2 = sigma0(temp[0]) + maj(temp[0], temp[1], temp[2]); 121 | temp[7] = temp[6]; 122 | temp[6] = temp[5]; 123 | temp[5] = temp[4]; 124 | temp[4] = temp[3] + t1; 125 | temp[3] = temp[2]; 126 | temp[2] = temp[1]; 127 | temp[1] = temp[0]; 128 | temp[0] = t1 + t2; 129 | } 130 | for (uint256 j = 0; j < 8; ++j) { 131 | h[j] += temp[j]; 132 | } 133 | } 134 | } 135 | } 136 | 137 | function sha384(bytes memory message, uint256 offset, uint256 length) internal pure returns (bytes memory) { 138 | uint64[8] memory h = [ 139 | 0xcbbb9d5dc1059ed8, 140 | 0x629a292a367cd507, 141 | 0x9159015a3070dd17, 142 | 0x152fecd8f70e5939, 143 | 0x67332667ffc00b31, 144 | 0x8eb44a8768581511, 145 | 0xdb0c2e0d64f98fa7, 146 | 0x47b5481dbefa4fa4 147 | ]; 148 | sha2(message, offset, length, h); 149 | return abi.encodePacked(bytes8(h[0]), bytes8(h[1]), bytes8(h[2]), bytes8(h[3]), bytes8(h[4]), bytes8(h[5])); 150 | } 151 | 152 | function sha512(bytes memory message, uint256 offset, uint256 length) internal pure returns (bytes memory) { 153 | uint64[8] memory h = [ 154 | 0x6a09e667f3bcc908, 155 | 0xbb67ae8584caa73b, 156 | 0x3c6ef372fe94f82b, 157 | 0xa54ff53a5f1d36f1, 158 | 0x510e527fade682d1, 159 | 0x9b05688c2b3e6c1f, 160 | 0x1f83d9abfb41bd6b, 161 | 0x5be0cd19137e2179 162 | ]; 163 | sha2(message, offset, length, h); 164 | return abi.encodePacked( 165 | bytes8(h[0]), 166 | bytes8(h[1]), 167 | bytes8(h[2]), 168 | bytes8(h[3]), 169 | bytes8(h[4]), 170 | bytes8(h[5]), 171 | bytes8(h[6]), 172 | bytes8(h[7]) 173 | ); 174 | } 175 | 176 | function padMessage(bytes memory message, uint256 offset, uint256 length) internal pure returns (bytes memory) { 177 | bytes8 bitLength = bytes8(uint64(length * 8)); 178 | uint256 mdi = length % 128; 179 | uint256 paddingLength; 180 | if (mdi < 112) { 181 | paddingLength = 119 - mdi; 182 | } else { 183 | paddingLength = 247 - mdi; 184 | } 185 | bytes memory padding = new bytes(paddingLength); 186 | bytes memory tail = message.slice(offset + length - mdi, mdi); 187 | return abi.encodePacked(tail, bytes1(0x80), padding, bitLength); 188 | } 189 | 190 | function getBlock(bytes memory message, uint64[16] memory blocks, uint256 index) internal pure { 191 | for (uint256 i = 0; i < 16; ++i) { 192 | blocks[i] = message.readUint64(index + i * 8); 193 | } 194 | } 195 | 196 | function ch(uint64 x, uint64 y, uint64 z) internal pure returns (uint64) { 197 | return (x & y) ^ (~x & z); 198 | } 199 | 200 | function maj(uint64 x, uint64 y, uint64 z) internal pure returns (uint64) { 201 | return (x & y) ^ (x & z) ^ (y & z); 202 | } 203 | 204 | function sigma0(uint64 x) internal pure returns (uint64) { 205 | return (rotateRight(x, 28) ^ rotateRight(x, 34) ^ rotateRight(x, 39)); 206 | } 207 | 208 | function sigma1(uint64 x) internal pure returns (uint64) { 209 | return (rotateRight(x, 14) ^ rotateRight(x, 18) ^ rotateRight(x, 41)); 210 | } 211 | 212 | function gamma0(uint64 x) internal pure returns (uint64) { 213 | return (rotateRight(x, 1) ^ rotateRight(x, 8) ^ (x >> 7)); 214 | } 215 | 216 | function gamma1(uint64 x) internal pure returns (uint64) { 217 | return (rotateRight(x, 19) ^ rotateRight(x, 61) ^ (x >> 6)); 218 | } 219 | 220 | function rotateRight(uint64 x, uint64 n) internal pure returns (uint64) { 221 | return (x << (64 - n)) | (x >> n); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /test/CertManager.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, console} from "forge-std/Test.sol"; 5 | import {CertManager} from "../src/CertManager.sol"; 6 | 7 | contract CertManagerTest is Test { 8 | CertManager public certManager; 9 | 10 | function setUp() public { 11 | vm.warp(1732580000); 12 | certManager = new CertManager(); 13 | } 14 | 15 | function test_VerifyCertBundle() public { 16 | bytes memory cert = 17 | hex"3082027c30820201a0030201020210019332852aac84300000000067450121300a06082a8648ce3d04030330818e310b30090603550406130255533113301106035504080c0a57617368696e67746f6e3110300e06035504070c0753656174746c65310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533139303706035504030c30692d30666661626464383636323664616631662e75732d656173742d312e6177732e6e6974726f2d656e636c61766573301e170d3234313132353232353833385a170d3234313132363031353834315a308193310b30090603550406130255533113301106035504080c0a57617368696e67746f6e3110300e06035504070c0753656174746c65310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753313e303c06035504030c35692d30666661626464383636323664616631662d656e63303139333332383532616163383433302e75732d656173742d312e6177733076301006072a8648ce3d020106052b8104002203620004dcae821ff99b2d890039bb0ac16e729439d842ad713ffe2609f8bc3f7dc8909cfed78e39cb5583e350b2719d52f7109ee56c988f4081a5789940a3e591b43c3697bb4b79409fc9dda34dacfaff2594e55eeb15086e268d73cc392dc187499768a31d301b300c0603551d130101ff04023000300b0603551d0f0404030206c0300a06082a8648ce3d0403030369003066023100896c399489c267213e069bd73e1ec4ef201a0bb4032472acfda46b96b506862d19384667c6ede4a3fb8dbfe5f26595d9023100a71c8937ee835d489a99b3b24817982fa8f1034728ceed3deae88fb193d98588bf411d009904fbd7ac6b31b5b23eb2b6"; 18 | bytes[] memory cabundle = new bytes[](4); 19 | cabundle[0] = 20 | hex"3082021130820196a003020102021100f93175681b90afe11d46ccb4e4e7f856300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3139313032383133323830355a170d3439313032383134323830355a3049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004fc0254eba608c1f36870e29ada90be46383292736e894bfff672d989444b5051e534a4b1f6dbe3c0bc581a32b7b176070ede12d69a3fea211b66e752cf7dd1dd095f6f1370f4170843d9dc100121e4cf63012809664487c9796284304dc53ff4a3423040300f0603551d130101ff040530030101ff301d0603551d0e041604149025b50dd90547e796c396fa729dcf99a9df4b96300e0603551d0f0101ff040403020186300a06082a8648ce3d0403030369003066023100a37f2f91a1c9bd5ee7b8627c1698d255038e1f0343f95b63a9628c3d39809545a11ebcbf2e3b55d8aeee71b4c3d6adf3023100a2f39b1605b27028a5dd4ba069b5016e65b4fbde8fe0061d6a53197f9cdaf5d943bc61fc2beb03cb6fee8d2302f3dff6"; 21 | cabundle[1] = 22 | hex"308202bf30820244a00302010202100b93e39c65609c59e8144a2ad34ba3a0300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3234313132333036333235355a170d3234313231333037333235355a3064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d353133623665666332313639303264372e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004ee78108039725a03e0b63a5d7d1244f6294eb7631f305e360997c8e5c06c779f23cfaeb64cb9aeac8a031bfac9f4dafc3621b4367f003c08c0ce410c2118396cc5d56ec4e92e1b17f9709b2bffcef462f7bcb97d6ca11325c4a30156c9720de7a381d53081d230120603551d130101ff040830060101ff020102301f0603551d230418301680149025b50dd90547e796c396fa729dcf99a9df4b96301d0603551d0e041604142b3d75d274a3cdd61b2c13f539e08c960ce757dd300e0603551d0f0101ff040403020186306c0603551d1f046530633061a05fa05d865b687474703a2f2f6177732d6e6974726f2d656e636c617665732d63726c2e73332e616d617a6f6e6177732e636f6d2f63726c2f61623439363063632d376436332d343262642d396539662d3539333338636236376638342e63726c300a06082a8648ce3d0403030369003066023100fce7a6c2b38e0a8ebf0d28348d74463458b84bfe8b2b95315dd4da665e8e83d4ab911852a4e92a8263ecf571d2df3b89023100ab92be511136be76aa313018f9f4825eaad602d0342d268e6da632767f68f55f761fa9fd2a7ee716c481c67f26e3f8f4"; 23 | cabundle[2] = 24 | hex"308203153082029aa003020102021020c20971680e956fc3c8ce925d784bc7300a06082a8648ce3d0403033064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d353133623665666332313639303264372e75732d656173742d312e6177732e6e6974726f2d656e636c61766573301e170d3234313132353032323230345a170d3234313230313030323230345a308189313c303a06035504030c33626635396531633335623630386133382e7a6f6e616c2e75732d656173742d312e6177732e6e6974726f2d656e636c61766573310c300a060355040b0c03415753310f300d060355040a0c06416d617a6f6e310b3009060355040613025553310b300906035504080c0257413110300e06035504070c0753656174746c653076301006072a8648ce3d020106052b8104002203620004df741cd0537abbbc37bb32b06c835f497df86933b6ac8b4ee15d1251cfde596a7953756bb2759896a4d50c7cfb7d50cfc62fd4010a8c0d4a58a6f38988de6707d5aeaef3e3ca523ffac31260cc7c33546dc667d52ba524c39bd0ed6b82c0652da381ea3081e730120603551d130101ff040830060101ff020101301f0603551d230418301680142b3d75d274a3cdd61b2c13f539e08c960ce757dd301d0603551d0e04160414e8b15a6bc0b83e3d9e50ab9b289fb5fa0c61eabf300e0603551d0f0101ff0404030201863081800603551d1f047930773075a073a071866f687474703a2f2f63726c2d75732d656173742d312d6177732d6e6974726f2d656e636c617665732e73332e75732d656173742d312e616d617a6f6e6177732e636f6d2f63726c2f39636665653133332d613562622d343431392d613462372d3730386661643563363866662e63726c300a06082a8648ce3d04030303690030660231009595351f7c4411011eb4cf1a18181c2ed6901e84c2971c781e2cdc2725d5135066fc8d96ac70c98fc27106cdb345a563023100f96927f5bc58f1c29f8ea06d9bb5eeae3a6e2e572aff9911a8c90ed6e00c1cc7c534b9fde367781807c35ba9427d05fe"; 25 | cabundle[3] = 26 | hex"308202bd30820244a00302010202142690c27f442c86646256455d3442f8998be152dc300a06082a8648ce3d040303308189313c303a06035504030c33626635396531633335623630386133382e7a6f6e616c2e75732d656173742d312e6177732e6e6974726f2d656e636c61766573310c300a060355040b0c03415753310f300d060355040a0c06416d617a6f6e310b3009060355040613025553310b300906035504080c0257413110300e06035504070c0753656174746c65301e170d3234313132353133353135375a170d3234313132363133353135375a30818e310b30090603550406130255533113301106035504080c0a57617368696e67746f6e3110300e06035504070c0753656174746c65310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533139303706035504030c30692d30666661626464383636323664616631662e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004c041c328653a7028db8060f3f19f589197b8c1a17dc755a5629c0f47c3cd3414412fd38b7f87fc70a6f22a0c2698fe1f748eff998f783add861a6b2373fba37a31f9bf0ab75fd2c4e17cc0df8124ddb0a4513483e40721ebb15a80619696747aa366306430120603551d130101ff040830060101ff020100300e0603551d0f0101ff040403020204301d0603551d0e041604145555580de23b6d83eb6a5be1b4dbdb376f69e444301f0603551d23041830168014e8b15a6bc0b83e3d9e50ab9b289fb5fa0c61eabf300a06082a8648ce3d0403030367003064023072c53164609cba5d7a16914d5d2102a9e70009288aae1215cc5e8d70f2d2d4b49bffb0119ec523e620275729f09e566e02302e0f2b7998eb25fa493dc1300329f7f142337b38e76df0a32b8660f41599c5febae120e4ed2c60efbbaa842ba6db8d91"; 27 | certManager.verifyCACert(cabundle[0], 0); 28 | certManager.verifyCACert(cabundle[1], keccak256(cabundle[0])); 29 | certManager.verifyCACert(cabundle[2], keccak256(cabundle[1])); 30 | certManager.verifyCACert(cabundle[3], keccak256(cabundle[2])); 31 | certManager.verifyClientCert(cert, keccak256(cabundle[3])); 32 | } 33 | 34 | function test_VerifyCACert() public { 35 | bytes memory parent = 36 | hex"3082021130820196a003020102021100f93175681b90afe11d46ccb4e4e7f856300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3139313032383133323830355a170d3439313032383134323830355a3049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004fc0254eba608c1f36870e29ada90be46383292736e894bfff672d989444b5051e534a4b1f6dbe3c0bc581a32b7b176070ede12d69a3fea211b66e752cf7dd1dd095f6f1370f4170843d9dc100121e4cf63012809664487c9796284304dc53ff4a3423040300f0603551d130101ff040530030101ff301d0603551d0e041604149025b50dd90547e796c396fa729dcf99a9df4b96300e0603551d0f0101ff040403020186300a06082a8648ce3d0403030369003066023100a37f2f91a1c9bd5ee7b8627c1698d255038e1f0343f95b63a9628c3d39809545a11ebcbf2e3b55d8aeee71b4c3d6adf3023100a2f39b1605b27028a5dd4ba069b5016e65b4fbde8fe0061d6a53197f9cdaf5d943bc61fc2beb03cb6fee8d2302f3dff6"; 37 | bytes memory cert = 38 | hex"308202bf30820244a00302010202100b93e39c65609c59e8144a2ad34ba3a0300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3234313132333036333235355a170d3234313231333037333235355a3064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d353133623665666332313639303264372e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004ee78108039725a03e0b63a5d7d1244f6294eb7631f305e360997c8e5c06c779f23cfaeb64cb9aeac8a031bfac9f4dafc3621b4367f003c08c0ce410c2118396cc5d56ec4e92e1b17f9709b2bffcef462f7bcb97d6ca11325c4a30156c9720de7a381d53081d230120603551d130101ff040830060101ff020102301f0603551d230418301680149025b50dd90547e796c396fa729dcf99a9df4b96301d0603551d0e041604142b3d75d274a3cdd61b2c13f539e08c960ce757dd300e0603551d0f0101ff040403020186306c0603551d1f046530633061a05fa05d865b687474703a2f2f6177732d6e6974726f2d656e636c617665732d63726c2e73332e616d617a6f6e6177732e636f6d2f63726c2f61623439363063632d376436332d343262642d396539662d3539333338636236376638342e63726c300a06082a8648ce3d0403030369003066023100fce7a6c2b38e0a8ebf0d28348d74463458b84bfe8b2b95315dd4da665e8e83d4ab911852a4e92a8263ecf571d2df3b89023100ab92be511136be76aa313018f9f4825eaad602d0342d268e6da632767f68f55f761fa9fd2a7ee716c481c67f26e3f8f4"; 39 | certManager.verifyCACert(cert, keccak256(parent)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/NitroValidator.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, console} from "forge-std/Test.sol"; 5 | import {NitroValidator} from "../src/NitroValidator.sol"; 6 | import {CertManager} from "../src/CertManager.sol"; 7 | 8 | contract NitroValidatorTest is Test { 9 | NitroValidator public validator; 10 | 11 | function setUp() public { 12 | vm.warp(1732990000); 13 | CertManager certManager = new CertManager(); 14 | validator = new NitroValidator(certManager); 15 | } 16 | 17 | function test_ValidateAttestation() public { 18 | bytes memory attestationTbs = 19 | hex"846a5369676e61747572653144a101382240591144a9696d6f64756c655f69647827692d30646533386232623638353363633965382d656e633031393336383565376665653764383566646967657374665348413338346974696d657374616d701b000001937de1c5436470637273b0005830ec74bfbe7f7445a6c7610e152935e028276f638042b74797b119648e13f7a3675796b721034c320f140ea001b41aeae2015830fa2593b59f3e4fc7daba5cbdddfd3449d67cd02d43bb1128885e8f38b914d081dccdb68fff6d5b7a76bcb866a18a74a302583056ba201a72e36cd051e95e5c4724c899039b711770f4d9d4fe7a1de007119a10b364badcd35e90f728a5bdc9109057230358303c9cadd84f0d027d6a5370c3de4af9179824fd6f3f02ebab723ee4439c75d8f5183e1c55f523415d44e9e6580b06655204583098bdf1bde262272618ccd73279e8ee00dd2c36974bd253de55413a25ceb2cd7221421207c2c09dde609f87481b6f6c940558300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000658300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000758300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000858300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000958300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a58300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b58300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c58300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d58300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e58300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f58300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006b63657274696669636174655902803082027c30820201a00302010202100193685e7fee7d8500000000674b3bd8300a06082a8648ce3d04030330818e310b30090603550406130255533113301106035504080c0a57617368696e67746f6e3110300e06035504070c0753656174746c65310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533139303706035504030c30692d30646533386232623638353363633965382e75732d656173742d312e6177732e6e6974726f2d656e636c61766573301e170d3234313133303136323234355a170d3234313133303139323234385a308193310b30090603550406130255533113301106035504080c0a57617368696e67746f6e3110300e06035504070c0753656174746c65310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753313e303c06035504030c35692d30646533386232623638353363633965382d656e63303139333638356537666565376438352e75732d656173742d312e6177733076301006072a8648ce3d020106052b810400220362000461d930c61be969237398264901d6a37282cfd42c0694d012d9143cc86a339d567913dae552bad2f10d47c50d4e670247f0344983cbdc2d2e0045d4ccbdff59ef7a26ebf1be83a81e24a651c92008fe9f465757792a0877fba02c8b5e1eb2ed90a31d301b300c0603551d130101ff04023000300b0603551d0f0404030206c0300a06082a8648ce3d0403030369003066023100e48f39a39b444a6e5ea7a38b808198a2318dd531ed62faf4a9223f71f27dff4a5e495e32dd10f250bbaf1f892a4d328f023100d09fc8e48e233b9e972eecb94798865664dbeb0d75b29041f482777a4b7cae133483dcc9d35509c4967be51db37a745468636162756e646c65845902153082021130820196a003020102021100f93175681b90afe11d46ccb4e4e7f856300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3139313032383133323830355a170d3439313032383134323830355a3049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004fc0254eba608c1f36870e29ada90be46383292736e894bfff672d989444b5051e534a4b1f6dbe3c0bc581a32b7b176070ede12d69a3fea211b66e752cf7dd1dd095f6f1370f4170843d9dc100121e4cf63012809664487c9796284304dc53ff4a3423040300f0603551d130101ff040530030101ff301d0603551d0e041604149025b50dd90547e796c396fa729dcf99a9df4b96300e0603551d0f0101ff040403020186300a06082a8648ce3d0403030369003066023100a37f2f91a1c9bd5ee7b8627c1698d255038e1f0343f95b63a9628c3d39809545a11ebcbf2e3b55d8aeee71b4c3d6adf3023100a2f39b1605b27028a5dd4ba069b5016e65b4fbde8fe0061d6a53197f9cdaf5d943bc61fc2beb03cb6fee8d2302f3dff65902c2308202be30820244a003020102021056bfc987fd05ac99c475061b1a65eedc300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3234313132383036303734355a170d3234313231383037303734355a3064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d636264383238303866646138623434642e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b81040022036200040713751f4391a24bf27d688c9fdde4b7eec0c4922af63f242186269602eca12354e79356170287baa07dd84fa89834726891f9b4b27032b3e86000d32471a79fbf1a30c1982ad4ed069ad96a7e11d9ae2b5cd6a93ad613ee559ed7f6385a9a89a381d53081d230120603551d130101ff040830060101ff020102301f0603551d230418301680149025b50dd90547e796c396fa729dcf99a9df4b96301d0603551d0e04160414bfbd54a168f57f7391b66ca60a2836f30acfb9a1300e0603551d0f0101ff040403020186306c0603551d1f046530633061a05fa05d865b687474703a2f2f6177732d6e6974726f2d656e636c617665732d63726c2e73332e616d617a6f6e6177732e636f6d2f63726c2f61623439363063632d376436332d343262642d396539662d3539333338636236376638342e63726c300a06082a8648ce3d0403030368003065023100c05dfd13378b1eecd926b0c3ba8da01eec89ec5502ae7ca73cb958557ca323057962fff2681993a0ab223b6eacf11033023035664252d7f9e2c89c988cc4164d390f898a5e8ac2e99dc58595aa4c624e93face7964026a99b4bcca7088b51250ccc459031a308203163082029ba003020102021100cb286a4a4a09207f8b0c14950dcd6861300a06082a8648ce3d0403033064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d636264383238303866646138623434642e75732d656173742d312e6177732e6e6974726f2d656e636c61766573301e170d3234313133303033313435345a170d3234313230363031313435345a308189313c303a06035504030c33343762313739376131663031386266302e7a6f6e616c2e75732d656173742d312e6177732e6e6974726f2d656e636c61766573310c300a060355040b0c03415753310f300d060355040a0c06416d617a6f6e310b3009060355040613025553310b300906035504080c0257413110300e06035504070c0753656174746c653076301006072a8648ce3d020106052b810400220362000423959f700ef87dcbdba686449d944f2a89ad22aa03d73cf93d28853f2fb6a80b0cc714d3090e34cda8234eef8f804e46c0dcb216062afba3e2b36a693660d9965e2370308b8e1ffad8542ddbe3e733077481b0cbc747d8c7beb7612820d4fe95a381ea3081e730120603551d130101ff040830060101ff020101301f0603551d23041830168014bfbd54a168f57f7391b66ca60a2836f30acfb9a1301d0603551d0e04160414bbf52a3a42fdc4f301f72536b90e65aaa1b70a99300e0603551d0f0101ff0404030201863081800603551d1f047930773075a073a071866f687474703a2f2f63726c2d75732d656173742d312d6177732d6e6974726f2d656e636c617665732e73332e75732d656173742d312e616d617a6f6e6177732e636f6d2f63726c2f30366434386638652d326330382d343738312d613634352d6231646534303261656662382e63726c300a06082a8648ce3d0403030369003066023100fa31509230632a002939201eb5686b52d79f0276db5c2b954bed324caa5c3271a60d25e2e05a5e6700e488a074af4ecd02310084770462c2ef86dcdb11fa8a31dcf770866cbd28822b682a112b98c09a30e35e94affd3482bf8b01b59a0a7775b4af185902c3308202bf30820245a003020102021500c8925d382506d820d93d2c704a7523c4ba2ddfaa300a06082a8648ce3d040303308189313c303a06035504030c33343762313739376131663031386266302e7a6f6e616c2e75732d656173742d312e6177732e6e6974726f2d656e636c61766573310c300a060355040b0c03415753310f300d060355040a0c06416d617a6f6e310b3009060355040613025553310b300906035504080c0257413110300e06035504070c0753656174746c65301e170d3234313133303132343133315a170d3234313230313132343133315a30818e310b30090603550406130255533113301106035504080c0a57617368696e67746f6e3110300e06035504070c0753656174746c65310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533139303706035504030c30692d30646533386232623638353363633965382e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004466754b5718024df3564bcd722361e7c65a4922eda7b1f826758e30afac40b04a281062897d085311fd509b70a6bbc5f8280f86ae2ff255ad147146fc97b7afb16064f0712d335c1d473b716be320be625e91c5870973084b3a0005bc020c7b2a366306430120603551d130101ff040830060101ff020100300e0603551d0f0101ff040403020204301d0603551d0e04160414345c86a9ec55bc30cafd923d6b73111d9c57abc0301f0603551d23041830168014bbf52a3a42fdc4f301f72536b90e65aaa1b70a99300a06082a8648ce3d0403030368003065023100aba82c02f40acb9846012bf070578217eeb2ebbfd16414948438cf67eeab6f64cdc5a152998766c88b2cdebd5a97ebd402307421611ed511567bc8e6a0a2805b981ef38dc3bd6a6c661522802b5c5d658cc4fcc9b5e8df148b161d366926896736836a7075626c69635f6b657958410433a4701fa871b188983d570e2c2d8cf98fd66eb19ba8ca7617bc8e20e152a5d7f0205eae76e608ce855077e4565be69db4471ef72857253742f9602c11ff04e569757365725f64617461f6656e6f6e6365f6"; 20 | bytes memory signature = 21 | hex"874e67088943e85654beb78443c747def2c3736bf93e2b52d033b3e936a04ead91f7b5a1229a1615f237f138f64399418b8046b6e40cd93e750b58f5e1aded45ebf3f103b9ea19a9b874142b576638dad2da142254ae913664649be22e0b83f9"; 22 | validator.validateAttestation(attestationTbs, signature); 23 | } 24 | 25 | function test_DecodeAttestationTbs() public { 26 | bytes memory attestation = 27 | hex"8444a1013822a0591144a9696d6f64756c655f69647827692d30646533386232623638353363633965382d656e633031393336383565376665653764383566646967657374665348413338346974696d657374616d701b000001937de1c5436470637273b0005830ec74bfbe7f7445a6c7610e152935e028276f638042b74797b119648e13f7a3675796b721034c320f140ea001b41aeae2015830fa2593b59f3e4fc7daba5cbdddfd3449d67cd02d43bb1128885e8f38b914d081dccdb68fff6d5b7a76bcb866a18a74a302583056ba201a72e36cd051e95e5c4724c899039b711770f4d9d4fe7a1de007119a10b364badcd35e90f728a5bdc9109057230358303c9cadd84f0d027d6a5370c3de4af9179824fd6f3f02ebab723ee4439c75d8f5183e1c55f523415d44e9e6580b06655204583098bdf1bde262272618ccd73279e8ee00dd2c36974bd253de55413a25ceb2cd7221421207c2c09dde609f87481b6f6c940558300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000658300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000758300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000858300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000958300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a58300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b58300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c58300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d58300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e58300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f58300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006b63657274696669636174655902803082027c30820201a00302010202100193685e7fee7d8500000000674b3bd8300a06082a8648ce3d04030330818e310b30090603550406130255533113301106035504080c0a57617368696e67746f6e3110300e06035504070c0753656174746c65310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533139303706035504030c30692d30646533386232623638353363633965382e75732d656173742d312e6177732e6e6974726f2d656e636c61766573301e170d3234313133303136323234355a170d3234313133303139323234385a308193310b30090603550406130255533113301106035504080c0a57617368696e67746f6e3110300e06035504070c0753656174746c65310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753313e303c06035504030c35692d30646533386232623638353363633965382d656e63303139333638356537666565376438352e75732d656173742d312e6177733076301006072a8648ce3d020106052b810400220362000461d930c61be969237398264901d6a37282cfd42c0694d012d9143cc86a339d567913dae552bad2f10d47c50d4e670247f0344983cbdc2d2e0045d4ccbdff59ef7a26ebf1be83a81e24a651c92008fe9f465757792a0877fba02c8b5e1eb2ed90a31d301b300c0603551d130101ff04023000300b0603551d0f0404030206c0300a06082a8648ce3d0403030369003066023100e48f39a39b444a6e5ea7a38b808198a2318dd531ed62faf4a9223f71f27dff4a5e495e32dd10f250bbaf1f892a4d328f023100d09fc8e48e233b9e972eecb94798865664dbeb0d75b29041f482777a4b7cae133483dcc9d35509c4967be51db37a745468636162756e646c65845902153082021130820196a003020102021100f93175681b90afe11d46ccb4e4e7f856300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3139313032383133323830355a170d3439313032383134323830355a3049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004fc0254eba608c1f36870e29ada90be46383292736e894bfff672d989444b5051e534a4b1f6dbe3c0bc581a32b7b176070ede12d69a3fea211b66e752cf7dd1dd095f6f1370f4170843d9dc100121e4cf63012809664487c9796284304dc53ff4a3423040300f0603551d130101ff040530030101ff301d0603551d0e041604149025b50dd90547e796c396fa729dcf99a9df4b96300e0603551d0f0101ff040403020186300a06082a8648ce3d0403030369003066023100a37f2f91a1c9bd5ee7b8627c1698d255038e1f0343f95b63a9628c3d39809545a11ebcbf2e3b55d8aeee71b4c3d6adf3023100a2f39b1605b27028a5dd4ba069b5016e65b4fbde8fe0061d6a53197f9cdaf5d943bc61fc2beb03cb6fee8d2302f3dff65902c2308202be30820244a003020102021056bfc987fd05ac99c475061b1a65eedc300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3234313132383036303734355a170d3234313231383037303734355a3064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d636264383238303866646138623434642e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b81040022036200040713751f4391a24bf27d688c9fdde4b7eec0c4922af63f242186269602eca12354e79356170287baa07dd84fa89834726891f9b4b27032b3e86000d32471a79fbf1a30c1982ad4ed069ad96a7e11d9ae2b5cd6a93ad613ee559ed7f6385a9a89a381d53081d230120603551d130101ff040830060101ff020102301f0603551d230418301680149025b50dd90547e796c396fa729dcf99a9df4b96301d0603551d0e04160414bfbd54a168f57f7391b66ca60a2836f30acfb9a1300e0603551d0f0101ff040403020186306c0603551d1f046530633061a05fa05d865b687474703a2f2f6177732d6e6974726f2d656e636c617665732d63726c2e73332e616d617a6f6e6177732e636f6d2f63726c2f61623439363063632d376436332d343262642d396539662d3539333338636236376638342e63726c300a06082a8648ce3d0403030368003065023100c05dfd13378b1eecd926b0c3ba8da01eec89ec5502ae7ca73cb958557ca323057962fff2681993a0ab223b6eacf11033023035664252d7f9e2c89c988cc4164d390f898a5e8ac2e99dc58595aa4c624e93face7964026a99b4bcca7088b51250ccc459031a308203163082029ba003020102021100cb286a4a4a09207f8b0c14950dcd6861300a06082a8648ce3d0403033064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d636264383238303866646138623434642e75732d656173742d312e6177732e6e6974726f2d656e636c61766573301e170d3234313133303033313435345a170d3234313230363031313435345a308189313c303a06035504030c33343762313739376131663031386266302e7a6f6e616c2e75732d656173742d312e6177732e6e6974726f2d656e636c61766573310c300a060355040b0c03415753310f300d060355040a0c06416d617a6f6e310b3009060355040613025553310b300906035504080c0257413110300e06035504070c0753656174746c653076301006072a8648ce3d020106052b810400220362000423959f700ef87dcbdba686449d944f2a89ad22aa03d73cf93d28853f2fb6a80b0cc714d3090e34cda8234eef8f804e46c0dcb216062afba3e2b36a693660d9965e2370308b8e1ffad8542ddbe3e733077481b0cbc747d8c7beb7612820d4fe95a381ea3081e730120603551d130101ff040830060101ff020101301f0603551d23041830168014bfbd54a168f57f7391b66ca60a2836f30acfb9a1301d0603551d0e04160414bbf52a3a42fdc4f301f72536b90e65aaa1b70a99300e0603551d0f0101ff0404030201863081800603551d1f047930773075a073a071866f687474703a2f2f63726c2d75732d656173742d312d6177732d6e6974726f2d656e636c617665732e73332e75732d656173742d312e616d617a6f6e6177732e636f6d2f63726c2f30366434386638652d326330382d343738312d613634352d6231646534303261656662382e63726c300a06082a8648ce3d0403030369003066023100fa31509230632a002939201eb5686b52d79f0276db5c2b954bed324caa5c3271a60d25e2e05a5e6700e488a074af4ecd02310084770462c2ef86dcdb11fa8a31dcf770866cbd28822b682a112b98c09a30e35e94affd3482bf8b01b59a0a7775b4af185902c3308202bf30820245a003020102021500c8925d382506d820d93d2c704a7523c4ba2ddfaa300a06082a8648ce3d040303308189313c303a06035504030c33343762313739376131663031386266302e7a6f6e616c2e75732d656173742d312e6177732e6e6974726f2d656e636c61766573310c300a060355040b0c03415753310f300d060355040a0c06416d617a6f6e310b3009060355040613025553310b300906035504080c0257413110300e06035504070c0753656174746c65301e170d3234313133303132343133315a170d3234313230313132343133315a30818e310b30090603550406130255533113301106035504080c0a57617368696e67746f6e3110300e06035504070c0753656174746c65310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533139303706035504030c30692d30646533386232623638353363633965382e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004466754b5718024df3564bcd722361e7c65a4922eda7b1f826758e30afac40b04a281062897d085311fd509b70a6bbc5f8280f86ae2ff255ad147146fc97b7afb16064f0712d335c1d473b716be320be625e91c5870973084b3a0005bc020c7b2a366306430120603551d130101ff040830060101ff020100300e0603551d0f0101ff040403020204301d0603551d0e04160414345c86a9ec55bc30cafd923d6b73111d9c57abc0301f0603551d23041830168014bbf52a3a42fdc4f301f72536b90e65aaa1b70a99300a06082a8648ce3d0403030368003065023100aba82c02f40acb9846012bf070578217eeb2ebbfd16414948438cf67eeab6f64cdc5a152998766c88b2cdebd5a97ebd402307421611ed511567bc8e6a0a2805b981ef38dc3bd6a6c661522802b5c5d658cc4fcc9b5e8df148b161d366926896736836a7075626c69635f6b657958410433a4701fa871b188983d570e2c2d8cf98fd66eb19ba8ca7617bc8e20e152a5d7f0205eae76e608ce855077e4565be69db4471ef72857253742f9602c11ff04e569757365725f64617461f6656e6f6e6365f65860874e67088943e85654beb78443c747def2c3736bf93e2b52d033b3e936a04ead91f7b5a1229a1615f237f138f64399418b8046b6e40cd93e750b58f5e1aded45ebf3f103b9ea19a9b874142b576638dad2da142254ae913664649be22e0b83f9"; 28 | (bytes memory attestationTbs, bytes memory signature) = validator.decodeAttestationTbs(attestation); 29 | validator.validateAttestation(attestationTbs, signature); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/RootCertCheck.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, console} from "forge-std/Test.sol"; 5 | import {CertManager} from "../src/CertManager.sol"; 6 | import {Asn1Decode, Asn1Ptr, LibAsn1Ptr} from "../src/Asn1Decode.sol"; 7 | 8 | contract RootCertCheckTest is Test, CertManager { 9 | using Asn1Decode for bytes; 10 | 11 | // @dev download the root CA cert for AWS nitro enclaves from https://aws-nitro-enclaves.amazonaws.com/AWS_NitroEnclaves_Root-G1.zip 12 | // @dev convert the base64 encoded pub key into hex to get the cert below 13 | bytes public constant ROOT_CA_CERT = 14 | hex"3082021130820196a003020102021100f93175681b90afe11d46ccb4e4e7f856300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3139313032383133323830355a170d3439313032383134323830355a3049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004fc0254eba608c1f36870e29ada90be46383292736e894bfff672d989444b5051e534a4b1f6dbe3c0bc581a32b7b176070ede12d69a3fea211b66e752cf7dd1dd095f6f1370f4170843d9dc100121e4cf63012809664487c9796284304dc53ff4a3423040300f0603551d130101ff040530030101ff301d0603551d0e041604149025b50dd90547e796c396fa729dcf99a9df4b96300e0603551d0f0101ff040403020186300a06082a8648ce3d0403030369003066023100a37f2f91a1c9bd5ee7b8627c1698d255038e1f0343f95b63a9628c3d39809545a11ebcbf2e3b55d8aeee71b4c3d6adf3023100a2f39b1605b27028a5dd4ba069b5016e65b4fbde8fe0061d6a53197f9cdaf5d943bc61fc2beb03cb6fee8d2302f3dff6"; 15 | 16 | function setUp() public { 17 | vm.warp(1732580000); 18 | } 19 | 20 | function test_ParseCert() public view { 21 | Asn1Ptr root = ROOT_CA_CERT.root(); 22 | Asn1Ptr tbsCertPtr = ROOT_CA_CERT.firstChildOf(root); 23 | (uint64 notAfter, int64 maxPathLen,, bytes32 subjectHash, bytes memory pubKey) = 24 | _parseTbs(ROOT_CA_CERT, tbsCertPtr, true); 25 | 26 | bytes32 certHash = keccak256(ROOT_CA_CERT); 27 | bytes memory cert = abi.encodePacked(true, notAfter, maxPathLen, subjectHash, pubKey); 28 | assertEq(verified[certHash], cert); 29 | } 30 | } 31 | --------------------------------------------------------------------------------