From 4b1b674d28aa7fe34a72cc50f221326c0e977f01 Mon Sep 17 00:00:00 2001 From: Michael de Hoog Date: Fri, 29 Nov 2024 14:56:32 -1000 Subject: [PATCH] Split certificate management to separate contract --- src/CertManager.sol | 298 ++++++++++++++++++++++++++++++ src/NitroAttestation.sol | 126 ------------- src/NitroValidator.sol | 369 ++++++++++++-------------------------- test/CertManager.t.sol | 38 ++++ test/NitroValidator.t.sol | 27 +-- 5 files changed, 453 insertions(+), 405 deletions(-) create mode 100644 src/CertManager.sol delete mode 100644 src/NitroAttestation.sol create mode 100644 test/CertManager.t.sol diff --git a/src/CertManager.sol b/src/CertManager.sol new file mode 100644 index 0000000..59dfd89 --- /dev/null +++ b/src/CertManager.sol @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {Sha2Ext} from "./Sha2Ext.sol"; +import {Asn1Decode} from "./Asn1Decode.sol"; +import {ECDSA384} from "./ECDSA384.sol"; +import {LibBytes} from "./LibBytes.sol"; +import {NodePtr, LibNodePtr} from "./NodePtr.sol"; + +// adapted from https://github.com/marlinprotocol/NitroProver/blob/f1d368d1f172ad3a55cd2aaaa98ad6a6e7dcde9d/src/CertManager.sol + +contract CertManager { + using Asn1Decode for bytes; + using LibBytes for bytes; + using LibNodePtr for NodePtr; + + // @dev download the root CA cert for AWS nitro enclaves from https://aws-nitro-enclaves.amazonaws.com/AWS_NitroEnclaves_Root-G1.zip + // @dev convert the base64 encoded pub key into hex to get the cert below + bytes public constant ROOT_CA_CERT = + hex"3082021130820196a003020102021100f93175681b90afe11d46ccb4e4e7f856300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3139313032383133323830355a170d3439313032383134323830355a3049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004fc0254eba608c1f36870e29ada90be46383292736e894bfff672d989444b5051e534a4b1f6dbe3c0bc581a32b7b176070ede12d69a3fea211b66e752cf7dd1dd095f6f1370f4170843d9dc100121e4cf63012809664487c9796284304dc53ff4a3423040300f0603551d130101ff040530030101ff301d0603551d0e041604149025b50dd90547e796c396fa729dcf99a9df4b96300e0603551d0f0101ff040403020186300a06082a8648ce3d0403030369003066023100a37f2f91a1c9bd5ee7b8627c1698d255038e1f0343f95b63a9628c3d39809545a11ebcbf2e3b55d8aeee71b4c3d6adf3023100a2f39b1605b27028a5dd4ba069b5016e65b4fbde8fe0061d6a53197f9cdaf5d943bc61fc2beb03cb6fee8d2302f3dff6"; + bytes32 public constant ROOT_CA_CERT_HASH = keccak256(ROOT_CA_CERT); + // 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)} + // which essentially means the signature algorithm is Elliptic curve Digital Signature Algorithm (DSA) coupled with the Secure Hash Algorithm 384 (SHA384) algorithm + // @dev Sig algo is hardcoded here because the root cerificate's sig algorithm is known beforehand + // @dev reference article for encoding https://learn.microsoft.com/en-in/windows/win32/seccertenroll/about-object-identifier + bytes32 public constant CERT_ALGO_OID = keccak256(hex"06082a8648ce3d040303"); + // https://oid-rep.orange-labs.fr/get/1.2.840.10045.2.1 + // 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 + bytes32 public constant EC_PUB_KEY_OID = keccak256(hex"2a8648ce3d0201"); + // https://oid-rep.orange-labs.fr/get/1.3.132.0.34 + // 1.3.132.0.34 {iso(1) identified-organization(3) certicom(132) curve(0) ansip384r1(34)} represents NIST 384-bit elliptic curve + bytes32 public constant SECP_384_R1_OID = keccak256(hex"2b81040022"); + + // extension OID certificate constants + bytes32 public constant BASIC_CONSTRAINTS_OID = keccak256(hex"551d13"); + bytes32 public constant KEY_USAGE_OID = keccak256(hex"551d0f"); + + // ECDSA384 curve parameters (NIST P-384) + bytes public constant CURVE_A = + hex"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc"; + bytes public constant CURVE_B = + hex"b3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef"; + bytes public constant CURVE_GX = + hex"aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7"; + bytes public constant CURVE_GY = + hex"3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f"; + bytes public constant CURVE_P = + hex"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff"; + bytes public constant CURVE_N = + hex"ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973"; + bytes public constant CURVE_LOW_S_MAX = + hex"7fffffffffffffffffffffffffffffffffffffffffffffffe3b1a6c0fa1b96efac0d06d9245853bd76760cb5666294b9"; + + struct CachedCert { + uint256 notAfter; + int256 maxPathLen; + bytes pubKey; + } + + // certHash -> CachedCert + mapping(bytes32 => bytes) public verified; + + constructor() { + CachedCert memory empty; + _verifyCert(ROOT_CA_CERT, ROOT_CA_CERT_HASH, false, empty); + } + + function verifyCert(bytes memory cert, bool clientCert, bytes32 parentCertHash) + external + returns (CachedCert memory) + { + bytes memory parentCacheBytes = verified[parentCertHash]; + require(parentCacheBytes.length != 0, "parent cert unverified"); + CachedCert memory parentCache = abi.decode(parentCacheBytes, (CachedCert)); + require(parentCache.notAfter >= block.timestamp, "parent cert expired"); + bytes32 certHash = keccak256(cert); + require(verified[certHash].length == 0, "cert already verified"); + return _verifyCert(cert, certHash, clientCert, parentCache); + } + + function verifyCertBundle(bytes memory certificate, bytes[] calldata cabundle) + external + returns (CachedCert memory) + { + CachedCert memory parentCache; + for (uint256 i = 0; i < cabundle.length; i++) { + bytes32 certHash = keccak256(cabundle[i]); + require(i > 0 || certHash == ROOT_CA_CERT_HASH, "Root CA cert not matching"); + parentCache = _verifyCert(cabundle[i], certHash, false, parentCache); + } + return _verifyCert(certificate, keccak256(certificate), true, parentCache); + } + + function _verifyCert(bytes memory certificate, bytes32 certHash, bool clientCert, CachedCert memory parentCache) + internal + returns (CachedCert memory) + { + // skip verification if already verified + bytes memory cacheBytes = verified[certHash]; + CachedCert memory cache; + if (cacheBytes.length != 0) { + cache = abi.decode(cacheBytes, (CachedCert)); + require(cache.notAfter >= block.timestamp, "cert expired"); + return cache; + } + + NodePtr root = certificate.root(); + NodePtr tbsCertPtr = certificate.firstChildOf(root); + (uint256 notAfter, int256 maxPathLen, bytes memory pubKey) = _parseTbs(certificate, tbsCertPtr, clientCert); + + if (parentCache.pubKey.length != 0 || certHash != ROOT_CA_CERT_HASH) { + if (parentCache.maxPathLen > 0 && (maxPathLen < 0 || maxPathLen >= parentCache.maxPathLen)) { + maxPathLen = parentCache.maxPathLen - 1; + } + require((parentCache.maxPathLen == 0) == clientCert, "maxPathLen exceeded"); + _verifyCertSignature(certificate, tbsCertPtr, parentCache.pubKey); + } + + cache = CachedCert({notAfter: notAfter, maxPathLen: maxPathLen, pubKey: pubKey}); + verified[certHash] = abi.encode(cache); + return cache; + } + + function _parseTbs(bytes memory certificate, NodePtr ptr, bool clientCert) + internal + view + returns (uint256 notAfter, int256 maxPathLen, bytes memory pubKey) + { + NodePtr versionPtr = certificate.firstChildOf(ptr); + NodePtr vPtr = certificate.firstChildOf(versionPtr); + NodePtr serialPtr = certificate.nextSiblingOf(versionPtr); + NodePtr sigAlgoPtr = certificate.nextSiblingOf(serialPtr); + + require(certificate.keccak(sigAlgoPtr.content(), sigAlgoPtr.length()) == CERT_ALGO_OID, "invalid cert sig algo"); + uint256 version = certificate.uintAt(vPtr); + // 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 + require(version == 2, "version should be 3"); + + (notAfter, maxPathLen, pubKey) = _parseTbsInner(certificate, sigAlgoPtr, clientCert); + } + + function _parseTbsInner(bytes memory certificate, NodePtr sigAlgoPtr, bool clientCert) + internal + view + returns (uint256 notAfter, int256 maxPathLen, bytes memory pubKey) + { + NodePtr issuerPtr = certificate.nextSiblingOf(sigAlgoPtr); + NodePtr validityPtr = certificate.nextSiblingOf(issuerPtr); + NodePtr subjectPtr = certificate.nextSiblingOf(validityPtr); + NodePtr subjectPublicKeyInfoPtr = certificate.nextSiblingOf(subjectPtr); + NodePtr extensionsPtr = certificate.nextSiblingOf(subjectPublicKeyInfoPtr); + + notAfter = _verifyValidity(certificate, validityPtr); + maxPathLen = _verifyExtensions(certificate, extensionsPtr, clientCert); + pubKey = _parsePubKey(certificate, subjectPublicKeyInfoPtr); + } + + function _parsePubKey(bytes memory certificate, NodePtr subjectPublicKeyInfoPtr) + internal + pure + returns (bytes memory subjectPubKey) + { + NodePtr pubKeyAlgoPtr = certificate.firstChildOf(subjectPublicKeyInfoPtr); + NodePtr pubKeyAlgoIdPtr = certificate.firstChildOf(pubKeyAlgoPtr); + NodePtr algoParamsPtr = certificate.nextSiblingOf(pubKeyAlgoIdPtr); + NodePtr subjectPublicKeyPtr = certificate.nextSiblingOf(pubKeyAlgoPtr); + NodePtr subjectPubKeyPtr = certificate.bitstring(subjectPublicKeyPtr); + + require( + certificate.keccak(pubKeyAlgoIdPtr.content(), pubKeyAlgoIdPtr.length()) == EC_PUB_KEY_OID, + "invalid cert algo id" + ); + require( + certificate.keccak(algoParamsPtr.content(), algoParamsPtr.length()) == SECP_384_R1_OID, + "invalid cert algo param" + ); + + uint256 end = subjectPubKeyPtr.content() + subjectPubKeyPtr.length(); + subjectPubKey = certificate.slice(end - 96, end); + } + + function _verifyValidity(bytes memory certificate, NodePtr validityPtr) internal view returns (uint256 notAfter) { + NodePtr notBeforePtr = certificate.firstChildOf(validityPtr); + NodePtr notAfterPtr = certificate.nextSiblingOf(notBeforePtr); + + uint256 notBefore = certificate.timestampAt(notBeforePtr); + notAfter = certificate.timestampAt(notAfterPtr); + + require(notBefore <= block.timestamp, "certificate not valid yet"); + require(notAfter >= block.timestamp, "certificate not valid anymore"); + } + + function _verifyExtensions(bytes memory certificate, NodePtr extensionsPtr, bool clientCert) + internal + pure + returns (int256 maxPathLen) + { + require(certificate[extensionsPtr.header()] == 0xa3, "invalid extensions"); + extensionsPtr = certificate.firstChildOf(extensionsPtr); + NodePtr extensionPtr = certificate.firstChildOf(extensionsPtr); + uint256 end = extensionsPtr.content() + extensionsPtr.length(); + bool basicConstraintsFound = false; + bool keyUsageFound = false; + maxPathLen = -1; + + while (true) { + NodePtr oidPtr = certificate.firstChildOf(extensionPtr); + bytes32 oid = certificate.keccak(oidPtr.content(), oidPtr.length()); + + if (oid == BASIC_CONSTRAINTS_OID || oid == KEY_USAGE_OID) { + NodePtr valuePtr = certificate.nextSiblingOf(oidPtr); + + if (certificate[valuePtr.header()] == 0x01) { + // skip optional critical bool + require(valuePtr.length() == 1, "invalid critical bool value"); + valuePtr = certificate.nextSiblingOf(valuePtr); + } + + valuePtr = certificate.octetString(valuePtr); + + if (oid == BASIC_CONSTRAINTS_OID) { + basicConstraintsFound = true; + maxPathLen = _verifyBasicConstraintsExtension(certificate, valuePtr); + } else { + keyUsageFound = true; + _verifyKeyUsageExtension(certificate, valuePtr, clientCert); + } + } + + if (extensionPtr.content() + extensionPtr.length() == end) { + break; + } + extensionPtr = certificate.nextSiblingOf(extensionPtr); + } + + require(basicConstraintsFound, "basicConstraints not found"); + require(keyUsageFound, "keyUsage not found"); + require(!clientCert || maxPathLen == -1, "maxPathLen must be undefined for client cert"); + } + + function _verifyBasicConstraintsExtension(bytes memory certificate, NodePtr valuePtr) + internal + pure + returns (int256 maxPathLen) + { + maxPathLen = -1; + NodePtr basicConstraintsPtr = certificate.firstChildOf(valuePtr); + if (certificate[basicConstraintsPtr.header()] == 0x01) { + // skip optional isCA bool + require(basicConstraintsPtr.length() == 1, "invalid isCA bool value"); + basicConstraintsPtr = certificate.nextSiblingOf(basicConstraintsPtr); + } + if (certificate[basicConstraintsPtr.header()] == 0x02) { + maxPathLen = int256(certificate.uintAt(basicConstraintsPtr)); + } + } + + function _verifyKeyUsageExtension(bytes memory certificate, NodePtr valuePtr, bool clientCert) internal pure { + uint256 value = certificate.bitstringUintAt(valuePtr); + // bits are reversed (DigitalSignature 0x01 => 0x80, CertSign 0x32 => 0x04) + if (clientCert) { + require(value & 0x80 == 0x80, "DigitalSignature must be present"); + } else { + require(value & 0x04 == 0x04, "CertSign must be present"); + } + } + + function _verifyCertSignature(bytes memory certificate, NodePtr ptr, bytes memory pubKey) internal view { + NodePtr sigAlgoPtr = certificate.nextSiblingOf(ptr); + require(certificate.keccak(sigAlgoPtr.content(), sigAlgoPtr.length()) == CERT_ALGO_OID, "invalid cert sig algo"); + + bytes memory hash = Sha2Ext.sha384(certificate, ptr.header(), ptr.totalLength()); + + NodePtr sigPtr = certificate.nextSiblingOf(sigAlgoPtr); + NodePtr sigBPtr = certificate.bitstring(sigPtr); + NodePtr sigRoot = certificate.rootOf(sigBPtr); + NodePtr sigRPtr = certificate.firstChildOf(sigRoot); + NodePtr sigSPtr = certificate.nextSiblingOf(sigRPtr); + (uint128 rhi, uint256 rlo) = certificate.uint384At(sigRPtr); + (uint128 shi, uint256 slo) = certificate.uint384At(sigSPtr); + bytes memory sigPacked = abi.encodePacked(rhi, rlo, shi, slo); + + _verifySignature(pubKey, hash, sigPacked); + } + + function _verifySignature(bytes memory pubKey, bytes memory hash, bytes memory sig) internal view { + ECDSA384.Parameters memory CURVE_PARAMETERS = ECDSA384.Parameters({ + a: CURVE_A, + b: CURVE_B, + gx: CURVE_GX, + gy: CURVE_GY, + p: CURVE_P, + n: CURVE_N, + lowSmax: CURVE_LOW_S_MAX + }); + require(ECDSA384.verify(CURVE_PARAMETERS, hash, sig, pubKey), "invalid sig"); + } +} diff --git a/src/NitroAttestation.sol b/src/NitroAttestation.sol deleted file mode 100644 index 600f5b3..0000000 --- a/src/NitroAttestation.sol +++ /dev/null @@ -1,126 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.15; - -import {NodePtr, LibNodePtr} from "./NodePtr.sol"; -import {LibBytes} from "./LibBytes.sol"; - -library NitroAttestation { - using LibNodePtr for NodePtr; - using LibBytes for bytes; - - bytes32 public constant ATTESTATION_TBS_PREFIX = keccak256(hex"846a5369676e61747572653144a101382240"); - bytes32 public constant CERTIFICATE_KEY = keccak256(bytes("certificate")); - bytes32 public constant PUBLIC_KEY_KEY = keccak256(bytes("public_key")); - bytes32 public constant MODULE_ID_KEY = keccak256(bytes("module_id")); - bytes32 public constant TIMESTAMP_KEY = keccak256(bytes("timestamp")); - bytes32 public constant USER_DATA_KEY = keccak256(bytes("user_data")); - bytes32 public constant CABUNDLE_KEY = keccak256(bytes("cabundle")); - bytes32 public constant DIGEST_KEY = keccak256(bytes("digest")); - bytes32 public constant NONCE_KEY = keccak256(bytes("nonce")); - bytes32 public constant PCRS_KEY = keccak256(bytes("pcrs")); - - struct Ptrs { - NodePtr moduleID; - uint64 timestamp; - NodePtr digest; - NodePtr[] pcrs; - NodePtr cert; - NodePtr[] cabundle; - NodePtr publicKey; - NodePtr userData; - NodePtr nonce; - } - - function parseAttestation(bytes memory attestationTbs) internal pure returns (Ptrs memory) { - require(attestationTbs.keccak(0, 18) == ATTESTATION_TBS_PREFIX, "invalid attestation prefix"); - - NodePtr payload = readNextElement(attestationTbs, 18); - require(payload.header() == 0x40, "invalid attestation payload type"); - NodePtr payloadMap = readNextElement(attestationTbs, payload.content()); - require(payloadMap.header() == 0xa0, "invalid attestation payload map type"); - - Ptrs memory ptrs; - uint256 offset = payloadMap.content(); - uint256 end = payload.content() + payload.length(); - while (offset < end) { - NodePtr key = readNextElement(attestationTbs, offset); - require(key.header() == 0x60, "invalid attestation key type"); - bytes32 keyHash = attestationTbs.keccak(key.content(), key.length()); - NodePtr value = readNextElement(attestationTbs, key.content() + key.length()); - if (keyHash == MODULE_ID_KEY) { - require(value.header() == 0x60, "invalid module_id type"); - ptrs.moduleID = value; - offset = value.content() + value.length(); - } else if (keyHash == DIGEST_KEY) { - require(value.header() == 0x60, "invalid digest type"); - ptrs.digest = value; - offset = value.content() + value.length(); - } else if (keyHash == CERTIFICATE_KEY) { - require(value.header() == 0x40, "invalid cert type"); - ptrs.cert = value; - offset = value.content() + value.length(); - } else if (keyHash == PUBLIC_KEY_KEY) { - ptrs.publicKey = value; - offset = value.content() + value.length(); - } else if (keyHash == USER_DATA_KEY) { - ptrs.userData = value; - offset = value.content() + value.length(); - } else if (keyHash == NONCE_KEY) { - ptrs.nonce = value; - offset = value.content() + value.length(); - } else if (keyHash == TIMESTAMP_KEY) { - require(value.header() == 0x00, "invalid timestamp type"); - ptrs.timestamp = uint64(value.length()); - offset = value.content(); - } else if (keyHash == CABUNDLE_KEY) { - require(value.header() == 0x80, "invalid cabundle type"); - offset = value.content(); - ptrs.cabundle = new NodePtr[](value.length()); - for (uint256 i = 0; i < value.length(); i++) { - NodePtr cert = readNextElement(attestationTbs, offset); - require(cert.header() == 0x40, "invalid cert type"); - ptrs.cabundle[i] = cert; - offset = cert.content() + cert.length(); - } - } else if (keyHash == PCRS_KEY) { - require(value.header() == 0xa0, "invalid pcrs type"); - offset = value.content(); - ptrs.pcrs = new NodePtr[](value.length()); - for (uint256 i = 0; i < value.length(); i++) { - key = readNextElement(attestationTbs, offset); - require(key.header() == 0x00, "invalid pcr key type"); - require(key.length() < value.length(), "invalid pcr key value"); - require(NodePtr.unwrap(ptrs.pcrs[key.length()]) == 0, "duplicate pcr key"); - NodePtr pcr = readNextElement(attestationTbs, key.content()); - require(pcr.header() == 0x40, "invalid pcr type"); - ptrs.pcrs[key.length()] = pcr; - offset = pcr.content() + pcr.length(); - } - } else { - revert("invalid attestation key"); - } - } - - return ptrs; - } - - function readNextElement(bytes memory cbor, uint256 ix) private pure returns (NodePtr) { - uint256 _type = uint256(uint8(cbor[ix] & 0xe0)); - uint256 length = uint256(uint8(cbor[ix] & 0x1f)); - uint256 header = 1; - if (length == 24) { - length = uint8(cbor[ix + 1]); - header = 2; - } else if (length == 25) { - length = cbor.readUint16(ix + 1); - header = 3; - } else if (length == 26) { - length = cbor.readUint32(ix + 1); - header = 5; - } else if (length == 27) { - length = cbor.readUint64(ix + 1); - header = 9; - } - return LibNodePtr.toNodePtr(_type, ix + header, length); - } -} diff --git a/src/NitroValidator.sol b/src/NitroValidator.sol index 8fa2f9b..3c91b17 100644 --- a/src/NitroValidator.sol +++ b/src/NitroValidator.sol @@ -1,43 +1,32 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.15; +import {CertManager} from "./CertManager.sol"; import {Sha2Ext} from "./Sha2Ext.sol"; import {Asn1Decode} from "./Asn1Decode.sol"; -import {NitroAttestation} from "./NitroAttestation.sol"; import {ECDSA384} from "./ECDSA384.sol"; import {LibBytes} from "./LibBytes.sol"; import {NodePtr, LibNodePtr} from "./NodePtr.sol"; // adapted from https://github.com/marlinprotocol/NitroProver/blob/f1d368d1f172ad3a55cd2aaaa98ad6a6e7dcde9d/src/NitroProver.sol -// and https://github.com/marlinprotocol/NitroProver/blob/f1d368d1f172ad3a55cd2aaaa98ad6a6e7dcde9d/src/CertManager.sol contract NitroValidator { - using Asn1Decode for bytes; - using NitroAttestation for bytes; using LibBytes for bytes; using LibNodePtr for NodePtr; - // @dev download the root CA cert for AWS nitro enclaves from https://aws-nitro-enclaves.amazonaws.com/AWS_NitroEnclaves_Root-G1.zip - // @dev convert the base64 encoded pub key into hex to get the cert below - bytes public constant ROOT_CA_CERT = - hex"3082021130820196a003020102021100f93175681b90afe11d46ccb4e4e7f856300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3139313032383133323830355a170d3439313032383134323830355a3049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004fc0254eba608c1f36870e29ada90be46383292736e894bfff672d989444b5051e534a4b1f6dbe3c0bc581a32b7b176070ede12d69a3fea211b66e752cf7dd1dd095f6f1370f4170843d9dc100121e4cf63012809664487c9796284304dc53ff4a3423040300f0603551d130101ff040530030101ff301d0603551d0e041604149025b50dd90547e796c396fa729dcf99a9df4b96300e0603551d0f0101ff040403020186300a06082a8648ce3d0403030369003066023100a37f2f91a1c9bd5ee7b8627c1698d255038e1f0343f95b63a9628c3d39809545a11ebcbf2e3b55d8aeee71b4c3d6adf3023100a2f39b1605b27028a5dd4ba069b5016e65b4fbde8fe0061d6a53197f9cdaf5d943bc61fc2beb03cb6fee8d2302f3dff6"; - bytes32 public constant ROOT_CA_CERT_HASH = keccak256(ROOT_CA_CERT); - // 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)} - // which essentially means the signature algorithm is Elliptic curve Digital Signature Algorithm (DSA) coupled with the Secure Hash Algorithm 384 (SHA384) algorithm - // @dev Sig algo is hardcoded here because the root cerificate's sig algorithm is known beforehand - // @dev reference article for encoding https://learn.microsoft.com/en-in/windows/win32/seccertenroll/about-object-identifier - bytes32 public constant CERT_ALGO_OID = keccak256(hex"06082a8648ce3d040303"); - // https://oid-rep.orange-labs.fr/get/1.2.840.10045.2.1 - // 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 - bytes32 public constant EC_PUB_KEY_OID = keccak256(hex"2a8648ce3d0201"); - // https://oid-rep.orange-labs.fr/get/1.3.132.0.34 - // 1.3.132.0.34 {iso(1) identified-organization(3) certicom(132) curve(0) ansip384r1(34)} represents NIST 384-bit elliptic curve - bytes32 public constant SECP_384_R1_OID = keccak256(hex"2b81040022"); + bytes32 public constant ATTESTATION_TBS_PREFIX = keccak256(hex"846a5369676e61747572653144a101382240"); + bytes32 public constant CERTIFICATE_KEY = keccak256(bytes("certificate")); + bytes32 public constant PUBLIC_KEY_KEY = keccak256(bytes("public_key")); + bytes32 public constant MODULE_ID_KEY = keccak256(bytes("module_id")); + bytes32 public constant TIMESTAMP_KEY = keccak256(bytes("timestamp")); + bytes32 public constant USER_DATA_KEY = keccak256(bytes("user_data")); + bytes32 public constant CABUNDLE_KEY = keccak256(bytes("cabundle")); + bytes32 public constant DIGEST_KEY = keccak256(bytes("digest")); + bytes32 public constant NONCE_KEY = keccak256(bytes("nonce")); + bytes32 public constant PCRS_KEY = keccak256(bytes("pcrs")); // attestation / certificate constants bytes32 public constant ATTESTATION_DIGEST = keccak256("SHA384"); - bytes32 public constant BASIC_CONSTRAINTS_OID = keccak256(hex"551d13"); - bytes32 public constant KEY_USAGE_OID = keccak256(hex"551d0f"); // ECDSA384 curve parameters (NIST P-384) bytes public constant CURVE_A = @@ -55,54 +44,29 @@ contract NitroValidator { bytes public constant CURVE_LOW_S_MAX = hex"7fffffffffffffffffffffffffffffffffffffffffffffffe3b1a6c0fa1b96efac0d06d9245853bd76760cb5666294b9"; - struct CachedCert { - uint256 notAfter; - int256 maxPathLen; - bytes pubKey; + struct Ptrs { + NodePtr moduleID; + uint64 timestamp; + NodePtr digest; + NodePtr[] pcrs; + NodePtr cert; + NodePtr[] cabundle; + NodePtr publicKey; + NodePtr userData; + NodePtr nonce; } - // certHash -> CachedCert - mapping(bytes32 => bytes) public verified; + CertManager public immutable certManager; - constructor() { - CachedCert memory empty; - _verifyCert(ROOT_CA_CERT, LibNodePtr.toNodePtr(0, 0, ROOT_CA_CERT.length), ROOT_CA_CERT_HASH, false, empty); - } - - function verifyCert(bytes memory cert, bool clientCert, bytes32 parentCertHash) - external - returns (CachedCert memory) - { - bytes memory parentCacheBytes = verified[parentCertHash]; - require(parentCacheBytes.length != 0, "parent cert unverified"); - CachedCert memory parentCache = abi.decode(parentCacheBytes, (CachedCert)); - require(parentCache.notAfter >= block.timestamp, "parent cert expired"); - bytes32 certHash = keccak256(cert); - require(verified[certHash].length == 0, "cert already verified"); - return _verifyCert(cert, LibNodePtr.toNodePtr(0, 0, cert.length), certHash, clientCert, parentCache); - } - - function verifyCertBundle(bytes memory certificate, bytes[] calldata cabundle) - external - returns (CachedCert memory) - { - CachedCert memory parentCache; - for (uint256 i = 0; i < cabundle.length; i++) { - bytes32 certHash = keccak256(cabundle[i]); - require(i > 0 || certHash == ROOT_CA_CERT_HASH, "Root CA cert not matching"); - parentCache = - _verifyCert(cabundle[i], LibNodePtr.toNodePtr(0, 0, cabundle.length), certHash, false, parentCache); - } - return _verifyCert( - certificate, LibNodePtr.toNodePtr(0, 0, certificate.length), keccak256(certificate), true, parentCache - ); + constructor(CertManager _certManager) { + certManager = _certManager; } function validateAttestation(bytes memory attestationTbs, bytes memory signature) external - returns (NitroAttestation.Ptrs memory) + returns (Ptrs memory) { - NitroAttestation.Ptrs memory ptrs = attestationTbs.parseAttestation(); + Ptrs memory ptrs = _parseAttestation(attestationTbs); require(ptrs.moduleID.length() > 0, "no module id"); require(ptrs.timestamp > 0, "no timestamp"); @@ -125,215 +89,110 @@ contract NitroValidator { "invalid nonce" ); - CachedCert memory parentCache; - bytes32 certHash; + bytes memory cert = attestationTbs.slice(ptrs.cert.content(), ptrs.cert.content() + ptrs.cert.length()); + bytes[] memory cabundle = new bytes[](ptrs.cabundle.length); for (uint256 i = 0; i < ptrs.cabundle.length; i++) { - certHash = attestationTbs.keccak(ptrs.cabundle[i].content(), ptrs.cabundle[i].length()); - require(i > 0 || certHash == ROOT_CA_CERT_HASH, "Root CA cert not matching"); - parentCache = _verifyCert(attestationTbs, ptrs.cabundle[i], certHash, false, parentCache); + cabundle[i] = attestationTbs.slice(ptrs.cabundle[i].content(), ptrs.cabundle[i].content() + ptrs.cabundle[i].length()); } - certHash = attestationTbs.keccak(ptrs.cert.content(), ptrs.cert.length()); - parentCache = _verifyCert(attestationTbs, ptrs.cert, certHash, true, parentCache); + CertManager.CachedCert memory parent = certManager.verifyCertBundle(cert, cabundle); bytes memory hash = Sha2Ext.sha384(attestationTbs, 0, attestationTbs.length); - _verifySignature(parentCache.pubKey, hash, signature); + _verifySignature(parent.pubKey, hash, signature); return ptrs; } - function _verifyCert( - bytes memory certificate, - NodePtr ptr, - bytes32 certHash, - bool clientCert, - CachedCert memory parentCache - ) internal returns (CachedCert memory) { - // skip verification if already verified - bytes memory cacheBytes = verified[certHash]; - CachedCert memory cache; - if (cacheBytes.length != 0) { - cache = abi.decode(cacheBytes, (CachedCert)); - require(cache.notAfter >= block.timestamp, "cert expired"); - return cache; - } - - NodePtr root = certificate.rootOf(ptr); - NodePtr tbsCertPtr = certificate.firstChildOf(root); - (uint256 notAfter, int256 maxPathLen, bytes memory pubKey) = _parseTbs(certificate, tbsCertPtr, clientCert); - - if (parentCache.pubKey.length != 0 || certHash != ROOT_CA_CERT_HASH) { - if (parentCache.maxPathLen > 0 && (maxPathLen < 0 || maxPathLen >= parentCache.maxPathLen)) { - maxPathLen = parentCache.maxPathLen - 1; - } - require((parentCache.maxPathLen == 0) == clientCert, "maxPathLen exceeded"); - _verifyCertSignature(certificate, tbsCertPtr, parentCache.pubKey); - } - - cache = CachedCert({notAfter: notAfter, maxPathLen: maxPathLen, pubKey: pubKey}); - verified[certHash] = abi.encode(cache); - return cache; - } - - function _parseTbs(bytes memory certificate, NodePtr ptr, bool clientCert) - internal - view - returns (uint256 notAfter, int256 maxPathLen, bytes memory pubKey) - { - NodePtr versionPtr = certificate.firstChildOf(ptr); - NodePtr vPtr = certificate.firstChildOf(versionPtr); - NodePtr serialPtr = certificate.nextSiblingOf(versionPtr); - NodePtr sigAlgoPtr = certificate.nextSiblingOf(serialPtr); - - require(certificate.keccak(sigAlgoPtr.content(), sigAlgoPtr.length()) == CERT_ALGO_OID, "invalid cert sig algo"); - uint256 version = certificate.uintAt(vPtr); - // 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 - require(version == 2, "version should be 3"); - - (notAfter, maxPathLen, pubKey) = _parseTbsInner(certificate, sigAlgoPtr, clientCert); - } - - function _parseTbsInner(bytes memory certificate, NodePtr sigAlgoPtr, bool clientCert) - internal - view - returns (uint256 notAfter, int256 maxPathLen, bytes memory pubKey) - { - NodePtr issuerPtr = certificate.nextSiblingOf(sigAlgoPtr); - NodePtr validityPtr = certificate.nextSiblingOf(issuerPtr); - NodePtr subjectPtr = certificate.nextSiblingOf(validityPtr); - NodePtr subjectPublicKeyInfoPtr = certificate.nextSiblingOf(subjectPtr); - NodePtr extensionsPtr = certificate.nextSiblingOf(subjectPublicKeyInfoPtr); - - notAfter = _verifyValidity(certificate, validityPtr); - maxPathLen = _verifyExtensions(certificate, extensionsPtr, clientCert); - pubKey = _parsePubKey(certificate, subjectPublicKeyInfoPtr); - } - - function _parsePubKey(bytes memory certificate, NodePtr subjectPublicKeyInfoPtr) - internal - pure - returns (bytes memory subjectPubKey) - { - NodePtr pubKeyAlgoPtr = certificate.firstChildOf(subjectPublicKeyInfoPtr); - NodePtr pubKeyAlgoIdPtr = certificate.firstChildOf(pubKeyAlgoPtr); - NodePtr algoParamsPtr = certificate.nextSiblingOf(pubKeyAlgoIdPtr); - NodePtr subjectPublicKeyPtr = certificate.nextSiblingOf(pubKeyAlgoPtr); - NodePtr subjectPubKeyPtr = certificate.bitstring(subjectPublicKeyPtr); - - require( - certificate.keccak(pubKeyAlgoIdPtr.content(), pubKeyAlgoIdPtr.length()) == EC_PUB_KEY_OID, - "invalid cert algo id" - ); - require( - certificate.keccak(algoParamsPtr.content(), algoParamsPtr.length()) == SECP_384_R1_OID, - "invalid cert algo param" - ); - - uint256 end = subjectPubKeyPtr.content() + subjectPubKeyPtr.length(); - subjectPubKey = certificate.slice(end - 96, end); - } - - function _verifyValidity(bytes memory certificate, NodePtr validityPtr) internal view returns (uint256 notAfter) { - NodePtr notBeforePtr = certificate.firstChildOf(validityPtr); - NodePtr notAfterPtr = certificate.nextSiblingOf(notBeforePtr); - - uint256 notBefore = certificate.timestampAt(notBeforePtr); - notAfter = certificate.timestampAt(notAfterPtr); - - require(notBefore <= block.timestamp, "certificate not valid yet"); - require(notAfter >= block.timestamp, "certificate not valid anymore"); - } - - function _verifyExtensions(bytes memory certificate, NodePtr extensionsPtr, bool clientCert) - internal - pure - returns (int256 maxPathLen) - { - require(certificate[extensionsPtr.header()] == 0xa3, "invalid extensions"); - extensionsPtr = certificate.firstChildOf(extensionsPtr); - NodePtr extensionPtr = certificate.firstChildOf(extensionsPtr); - uint256 end = extensionsPtr.content() + extensionsPtr.length(); - bool basicConstraintsFound = false; - bool keyUsageFound = false; - maxPathLen = -1; - - while (true) { - NodePtr oidPtr = certificate.firstChildOf(extensionPtr); - bytes32 oid = certificate.keccak(oidPtr.content(), oidPtr.length()); - - if (oid == BASIC_CONSTRAINTS_OID || oid == KEY_USAGE_OID) { - NodePtr valuePtr = certificate.nextSiblingOf(oidPtr); - - if (certificate[valuePtr.header()] == 0x01) { - // skip optional critical bool - require(valuePtr.length() == 1, "invalid critical bool value"); - valuePtr = certificate.nextSiblingOf(valuePtr); + function _parseAttestation(bytes memory attestationTbs) internal pure returns (Ptrs memory) { + require(attestationTbs.keccak(0, 18) == ATTESTATION_TBS_PREFIX, "invalid attestation prefix"); + + NodePtr payload = _readNextElement(attestationTbs, 18); + require(payload.header() == 0x40, "invalid attestation payload type"); + NodePtr payloadMap = _readNextElement(attestationTbs, payload.content()); + require(payloadMap.header() == 0xa0, "invalid attestation payload map type"); + + Ptrs memory ptrs; + uint256 offset = payloadMap.content(); + uint256 end = payload.content() + payload.length(); + while (offset < end) { + NodePtr key = _readNextElement(attestationTbs, offset); + require(key.header() == 0x60, "invalid attestation key type"); + bytes32 keyHash = attestationTbs.keccak(key.content(), key.length()); + NodePtr value = _readNextElement(attestationTbs, key.content() + key.length()); + if (keyHash == MODULE_ID_KEY) { + require(value.header() == 0x60, "invalid module_id type"); + ptrs.moduleID = value; + offset = value.content() + value.length(); + } else if (keyHash == DIGEST_KEY) { + require(value.header() == 0x60, "invalid digest type"); + ptrs.digest = value; + offset = value.content() + value.length(); + } else if (keyHash == CERTIFICATE_KEY) { + require(value.header() == 0x40, "invalid cert type"); + ptrs.cert = value; + offset = value.content() + value.length(); + } else if (keyHash == PUBLIC_KEY_KEY) { + ptrs.publicKey = value; + offset = value.content() + value.length(); + } else if (keyHash == USER_DATA_KEY) { + ptrs.userData = value; + offset = value.content() + value.length(); + } else if (keyHash == NONCE_KEY) { + ptrs.nonce = value; + offset = value.content() + value.length(); + } else if (keyHash == TIMESTAMP_KEY) { + require(value.header() == 0x00, "invalid timestamp type"); + ptrs.timestamp = uint64(value.length()); + offset = value.content(); + } else if (keyHash == CABUNDLE_KEY) { + require(value.header() == 0x80, "invalid cabundle type"); + offset = value.content(); + ptrs.cabundle = new NodePtr[](value.length()); + for (uint256 i = 0; i < value.length(); i++) { + NodePtr cert = _readNextElement(attestationTbs, offset); + require(cert.header() == 0x40, "invalid cert type"); + ptrs.cabundle[i] = cert; + offset = cert.content() + cert.length(); } - - valuePtr = certificate.octetString(valuePtr); - - if (oid == BASIC_CONSTRAINTS_OID) { - basicConstraintsFound = true; - maxPathLen = _verifyBasicConstraintsExtension(certificate, valuePtr); - } else { - keyUsageFound = true; - _verifyKeyUsageExtension(certificate, valuePtr, clientCert); + } else if (keyHash == PCRS_KEY) { + require(value.header() == 0xa0, "invalid pcrs type"); + offset = value.content(); + ptrs.pcrs = new NodePtr[](value.length()); + for (uint256 i = 0; i < value.length(); i++) { + key = _readNextElement(attestationTbs, offset); + require(key.header() == 0x00, "invalid pcr key type"); + require(key.length() < value.length(), "invalid pcr key value"); + require(NodePtr.unwrap(ptrs.pcrs[key.length()]) == 0, "duplicate pcr key"); + NodePtr pcr = _readNextElement(attestationTbs, key.content()); + require(pcr.header() == 0x40, "invalid pcr type"); + ptrs.pcrs[key.length()] = pcr; + offset = pcr.content() + pcr.length(); } + } else { + revert("invalid attestation key"); } - - if (extensionPtr.content() + extensionPtr.length() == end) { - break; - } - extensionPtr = certificate.nextSiblingOf(extensionPtr); } - require(basicConstraintsFound, "basicConstraints not found"); - require(keyUsageFound, "keyUsage not found"); - require(!clientCert || maxPathLen == -1, "maxPathLen must be undefined for client cert"); - } - - function _verifyBasicConstraintsExtension(bytes memory certificate, NodePtr valuePtr) - internal - pure - returns (int256 maxPathLen) - { - maxPathLen = -1; - NodePtr basicConstraintsPtr = certificate.firstChildOf(valuePtr); - if (certificate[basicConstraintsPtr.header()] == 0x01) { - // skip optional isCA bool - require(basicConstraintsPtr.length() == 1, "invalid isCA bool value"); - basicConstraintsPtr = certificate.nextSiblingOf(basicConstraintsPtr); - } - if (certificate[basicConstraintsPtr.header()] == 0x02) { - maxPathLen = int256(certificate.uintAt(basicConstraintsPtr)); - } + return ptrs; } - function _verifyKeyUsageExtension(bytes memory certificate, NodePtr valuePtr, bool clientCert) internal pure { - uint256 value = certificate.bitstringUintAt(valuePtr); - // bits are reversed (DigitalSignature 0x01 => 0x80, CertSign 0x32 => 0x04) - if (clientCert) { - require(value & 0x80 == 0x80, "DigitalSignature must be present"); - } else { - require(value & 0x04 == 0x04, "CertSign must be present"); + function _readNextElement(bytes memory cbor, uint256 ix) internal pure returns (NodePtr) { + uint256 _type = uint256(uint8(cbor[ix] & 0xe0)); + uint256 length = uint256(uint8(cbor[ix] & 0x1f)); + uint256 header = 1; + if (length == 24) { + length = uint8(cbor[ix + 1]); + header = 2; + } else if (length == 25) { + length = cbor.readUint16(ix + 1); + header = 3; + } else if (length == 26) { + length = cbor.readUint32(ix + 1); + header = 5; + } else if (length == 27) { + length = cbor.readUint64(ix + 1); + header = 9; } - } - - function _verifyCertSignature(bytes memory certificate, NodePtr ptr, bytes memory pubKey) internal view { - NodePtr sigAlgoPtr = certificate.nextSiblingOf(ptr); - require(certificate.keccak(sigAlgoPtr.content(), sigAlgoPtr.length()) == CERT_ALGO_OID, "invalid cert sig algo"); - - bytes memory hash = Sha2Ext.sha384(certificate, ptr.header(), ptr.totalLength()); - - NodePtr sigPtr = certificate.nextSiblingOf(sigAlgoPtr); - NodePtr sigBPtr = certificate.bitstring(sigPtr); - NodePtr sigRoot = certificate.rootOf(sigBPtr); - NodePtr sigRPtr = certificate.firstChildOf(sigRoot); - NodePtr sigSPtr = certificate.nextSiblingOf(sigRPtr); - (uint128 rhi, uint256 rlo) = certificate.uint384At(sigRPtr); - (uint128 shi, uint256 slo) = certificate.uint384At(sigSPtr); - bytes memory sigPacked = abi.encodePacked(rhi, rlo, shi, slo); - - _verifySignature(pubKey, hash, sigPacked); + return LibNodePtr.toNodePtr(_type, ix + header, length); } function _verifySignature(bytes memory pubKey, bytes memory hash, bytes memory sig) internal view { diff --git a/test/CertManager.t.sol b/test/CertManager.t.sol new file mode 100644 index 0000000..14dbec7 --- /dev/null +++ b/test/CertManager.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console} from "forge-std/Test.sol"; +import {NitroValidator} from "../src/NitroValidator.sol"; +import {CertManager} from "../src/CertManager.sol"; + +contract CounterTest is Test { + CertManager public certManager; + + function setUp() public { + vm.warp(1732580000); + certManager = new CertManager(); + } + + function test_VerifyCertBundle() public { + bytes memory cert = + hex"3082027c30820201a0030201020210019332852aac84300000000067450121300a06082a8648ce3d04030330818e310b30090603550406130255533113301106035504080c0a57617368696e67746f6e3110300e06035504070c0753656174746c65310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533139303706035504030c30692d30666661626464383636323664616631662e75732d656173742d312e6177732e6e6974726f2d656e636c61766573301e170d3234313132353232353833385a170d3234313132363031353834315a308193310b30090603550406130255533113301106035504080c0a57617368696e67746f6e3110300e06035504070c0753656174746c65310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753313e303c06035504030c35692d30666661626464383636323664616631662d656e63303139333332383532616163383433302e75732d656173742d312e6177733076301006072a8648ce3d020106052b8104002203620004dcae821ff99b2d890039bb0ac16e729439d842ad713ffe2609f8bc3f7dc8909cfed78e39cb5583e350b2719d52f7109ee56c988f4081a5789940a3e591b43c3697bb4b79409fc9dda34dacfaff2594e55eeb15086e268d73cc392dc187499768a31d301b300c0603551d130101ff04023000300b0603551d0f0404030206c0300a06082a8648ce3d0403030369003066023100896c399489c267213e069bd73e1ec4ef201a0bb4032472acfda46b96b506862d19384667c6ede4a3fb8dbfe5f26595d9023100a71c8937ee835d489a99b3b24817982fa8f1034728ceed3deae88fb193d98588bf411d009904fbd7ac6b31b5b23eb2b6"; + bytes[] memory cabundle = new bytes[](4); + cabundle[0] = + hex"3082021130820196a003020102021100f93175681b90afe11d46ccb4e4e7f856300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3139313032383133323830355a170d3439313032383134323830355a3049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004fc0254eba608c1f36870e29ada90be46383292736e894bfff672d989444b5051e534a4b1f6dbe3c0bc581a32b7b176070ede12d69a3fea211b66e752cf7dd1dd095f6f1370f4170843d9dc100121e4cf63012809664487c9796284304dc53ff4a3423040300f0603551d130101ff040530030101ff301d0603551d0e041604149025b50dd90547e796c396fa729dcf99a9df4b96300e0603551d0f0101ff040403020186300a06082a8648ce3d0403030369003066023100a37f2f91a1c9bd5ee7b8627c1698d255038e1f0343f95b63a9628c3d39809545a11ebcbf2e3b55d8aeee71b4c3d6adf3023100a2f39b1605b27028a5dd4ba069b5016e65b4fbde8fe0061d6a53197f9cdaf5d943bc61fc2beb03cb6fee8d2302f3dff6"; + cabundle[1] = + hex"308202bf30820244a00302010202100b93e39c65609c59e8144a2ad34ba3a0300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3234313132333036333235355a170d3234313231333037333235355a3064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d353133623665666332313639303264372e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004ee78108039725a03e0b63a5d7d1244f6294eb7631f305e360997c8e5c06c779f23cfaeb64cb9aeac8a031bfac9f4dafc3621b4367f003c08c0ce410c2118396cc5d56ec4e92e1b17f9709b2bffcef462f7bcb97d6ca11325c4a30156c9720de7a381d53081d230120603551d130101ff040830060101ff020102301f0603551d230418301680149025b50dd90547e796c396fa729dcf99a9df4b96301d0603551d0e041604142b3d75d274a3cdd61b2c13f539e08c960ce757dd300e0603551d0f0101ff040403020186306c0603551d1f046530633061a05fa05d865b687474703a2f2f6177732d6e6974726f2d656e636c617665732d63726c2e73332e616d617a6f6e6177732e636f6d2f63726c2f61623439363063632d376436332d343262642d396539662d3539333338636236376638342e63726c300a06082a8648ce3d0403030369003066023100fce7a6c2b38e0a8ebf0d28348d74463458b84bfe8b2b95315dd4da665e8e83d4ab911852a4e92a8263ecf571d2df3b89023100ab92be511136be76aa313018f9f4825eaad602d0342d268e6da632767f68f55f761fa9fd2a7ee716c481c67f26e3f8f4"; + cabundle[2] = + hex"308203153082029aa003020102021020c20971680e956fc3c8ce925d784bc7300a06082a8648ce3d0403033064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d353133623665666332313639303264372e75732d656173742d312e6177732e6e6974726f2d656e636c61766573301e170d3234313132353032323230345a170d3234313230313030323230345a308189313c303a06035504030c33626635396531633335623630386133382e7a6f6e616c2e75732d656173742d312e6177732e6e6974726f2d656e636c61766573310c300a060355040b0c03415753310f300d060355040a0c06416d617a6f6e310b3009060355040613025553310b300906035504080c0257413110300e06035504070c0753656174746c653076301006072a8648ce3d020106052b8104002203620004df741cd0537abbbc37bb32b06c835f497df86933b6ac8b4ee15d1251cfde596a7953756bb2759896a4d50c7cfb7d50cfc62fd4010a8c0d4a58a6f38988de6707d5aeaef3e3ca523ffac31260cc7c33546dc667d52ba524c39bd0ed6b82c0652da381ea3081e730120603551d130101ff040830060101ff020101301f0603551d230418301680142b3d75d274a3cdd61b2c13f539e08c960ce757dd301d0603551d0e04160414e8b15a6bc0b83e3d9e50ab9b289fb5fa0c61eabf300e0603551d0f0101ff0404030201863081800603551d1f047930773075a073a071866f687474703a2f2f63726c2d75732d656173742d312d6177732d6e6974726f2d656e636c617665732e73332e75732d656173742d312e616d617a6f6e6177732e636f6d2f63726c2f39636665653133332d613562622d343431392d613462372d3730386661643563363866662e63726c300a06082a8648ce3d04030303690030660231009595351f7c4411011eb4cf1a18181c2ed6901e84c2971c781e2cdc2725d5135066fc8d96ac70c98fc27106cdb345a563023100f96927f5bc58f1c29f8ea06d9bb5eeae3a6e2e572aff9911a8c90ed6e00c1cc7c534b9fde367781807c35ba9427d05fe"; + cabundle[3] = + hex"308202bd30820244a00302010202142690c27f442c86646256455d3442f8998be152dc300a06082a8648ce3d040303308189313c303a06035504030c33626635396531633335623630386133382e7a6f6e616c2e75732d656173742d312e6177732e6e6974726f2d656e636c61766573310c300a060355040b0c03415753310f300d060355040a0c06416d617a6f6e310b3009060355040613025553310b300906035504080c0257413110300e06035504070c0753656174746c65301e170d3234313132353133353135375a170d3234313132363133353135375a30818e310b30090603550406130255533113301106035504080c0a57617368696e67746f6e3110300e06035504070c0753656174746c65310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533139303706035504030c30692d30666661626464383636323664616631662e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004c041c328653a7028db8060f3f19f589197b8c1a17dc755a5629c0f47c3cd3414412fd38b7f87fc70a6f22a0c2698fe1f748eff998f783add861a6b2373fba37a31f9bf0ab75fd2c4e17cc0df8124ddb0a4513483e40721ebb15a80619696747aa366306430120603551d130101ff040830060101ff020100300e0603551d0f0101ff040403020204301d0603551d0e041604145555580de23b6d83eb6a5be1b4dbdb376f69e444301f0603551d23041830168014e8b15a6bc0b83e3d9e50ab9b289fb5fa0c61eabf300a06082a8648ce3d0403030367003064023072c53164609cba5d7a16914d5d2102a9e70009288aae1215cc5e8d70f2d2d4b49bffb0119ec523e620275729f09e566e02302e0f2b7998eb25fa493dc1300329f7f142337b38e76df0a32b8660f41599c5febae120e4ed2c60efbbaa842ba6db8d91"; + certManager.verifyCertBundle(cert, cabundle); + } + + function test_VerifyCert() public { + bytes memory parent = + hex"3082021130820196a003020102021100f93175681b90afe11d46ccb4e4e7f856300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3139313032383133323830355a170d3439313032383134323830355a3049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004fc0254eba608c1f36870e29ada90be46383292736e894bfff672d989444b5051e534a4b1f6dbe3c0bc581a32b7b176070ede12d69a3fea211b66e752cf7dd1dd095f6f1370f4170843d9dc100121e4cf63012809664487c9796284304dc53ff4a3423040300f0603551d130101ff040530030101ff301d0603551d0e041604149025b50dd90547e796c396fa729dcf99a9df4b96300e0603551d0f0101ff040403020186300a06082a8648ce3d0403030369003066023100a37f2f91a1c9bd5ee7b8627c1698d255038e1f0343f95b63a9628c3d39809545a11ebcbf2e3b55d8aeee71b4c3d6adf3023100a2f39b1605b27028a5dd4ba069b5016e65b4fbde8fe0061d6a53197f9cdaf5d943bc61fc2beb03cb6fee8d2302f3dff6"; + bytes memory cert = + hex"308202bf30820244a00302010202100b93e39c65609c59e8144a2ad34ba3a0300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3234313132333036333235355a170d3234313231333037333235355a3064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d353133623665666332313639303264372e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004ee78108039725a03e0b63a5d7d1244f6294eb7631f305e360997c8e5c06c779f23cfaeb64cb9aeac8a031bfac9f4dafc3621b4367f003c08c0ce410c2118396cc5d56ec4e92e1b17f9709b2bffcef462f7bcb97d6ca11325c4a30156c9720de7a381d53081d230120603551d130101ff040830060101ff020102301f0603551d230418301680149025b50dd90547e796c396fa729dcf99a9df4b96301d0603551d0e041604142b3d75d274a3cdd61b2c13f539e08c960ce757dd300e0603551d0f0101ff040403020186306c0603551d1f046530633061a05fa05d865b687474703a2f2f6177732d6e6974726f2d656e636c617665732d63726c2e73332e616d617a6f6e6177732e636f6d2f63726c2f61623439363063632d376436332d343262642d396539662d3539333338636236376638342e63726c300a06082a8648ce3d0403030369003066023100fce7a6c2b38e0a8ebf0d28348d74463458b84bfe8b2b95315dd4da665e8e83d4ab911852a4e92a8263ecf571d2df3b89023100ab92be511136be76aa313018f9f4825eaad602d0342d268e6da632767f68f55f761fa9fd2a7ee716c481c67f26e3f8f4"; + certManager.verifyCert(cert, false, keccak256(parent)); + } +} diff --git a/test/NitroValidator.t.sol b/test/NitroValidator.t.sol index d503518..e57cf40 100644 --- a/test/NitroValidator.t.sol +++ b/test/NitroValidator.t.sol @@ -3,13 +3,15 @@ pragma solidity ^0.8.13; import {Test, console} from "forge-std/Test.sol"; import {NitroValidator} from "../src/NitroValidator.sol"; +import {CertManager} from "../src/CertManager.sol"; contract CounterTest is Test { NitroValidator public validator; function setUp() public { vm.warp(1732580000); - validator = new NitroValidator(); + CertManager certManager = new CertManager(); + validator = new NitroValidator(certManager); } function test_ValidateAttestation() public { @@ -19,27 +21,4 @@ contract CounterTest is Test { hex"a3dfa0e2c5fcbebc057b29544b1f43133e0153f8b7a711da749b3207acf9931660d4195a0cd912a7b720859321f8f0f472d9807d217935580857066fde8d2db62a946766cb5c0a383a1d55c7970ccb15c5700746a8ebe8c2a2b21a77a204765b"; validator.validateAttestation(attestationTbs, signature); } - - function test_VerifyCertBundle() public { - bytes memory cert = - hex"3082027c30820201a0030201020210019332852aac84300000000067450121300a06082a8648ce3d04030330818e310b30090603550406130255533113301106035504080c0a57617368696e67746f6e3110300e06035504070c0753656174746c65310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533139303706035504030c30692d30666661626464383636323664616631662e75732d656173742d312e6177732e6e6974726f2d656e636c61766573301e170d3234313132353232353833385a170d3234313132363031353834315a308193310b30090603550406130255533113301106035504080c0a57617368696e67746f6e3110300e06035504070c0753656174746c65310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753313e303c06035504030c35692d30666661626464383636323664616631662d656e63303139333332383532616163383433302e75732d656173742d312e6177733076301006072a8648ce3d020106052b8104002203620004dcae821ff99b2d890039bb0ac16e729439d842ad713ffe2609f8bc3f7dc8909cfed78e39cb5583e350b2719d52f7109ee56c988f4081a5789940a3e591b43c3697bb4b79409fc9dda34dacfaff2594e55eeb15086e268d73cc392dc187499768a31d301b300c0603551d130101ff04023000300b0603551d0f0404030206c0300a06082a8648ce3d0403030369003066023100896c399489c267213e069bd73e1ec4ef201a0bb4032472acfda46b96b506862d19384667c6ede4a3fb8dbfe5f26595d9023100a71c8937ee835d489a99b3b24817982fa8f1034728ceed3deae88fb193d98588bf411d009904fbd7ac6b31b5b23eb2b6"; - bytes[] memory cabundle = new bytes[](4); - cabundle[0] = - hex"3082021130820196a003020102021100f93175681b90afe11d46ccb4e4e7f856300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3139313032383133323830355a170d3439313032383134323830355a3049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004fc0254eba608c1f36870e29ada90be46383292736e894bfff672d989444b5051e534a4b1f6dbe3c0bc581a32b7b176070ede12d69a3fea211b66e752cf7dd1dd095f6f1370f4170843d9dc100121e4cf63012809664487c9796284304dc53ff4a3423040300f0603551d130101ff040530030101ff301d0603551d0e041604149025b50dd90547e796c396fa729dcf99a9df4b96300e0603551d0f0101ff040403020186300a06082a8648ce3d0403030369003066023100a37f2f91a1c9bd5ee7b8627c1698d255038e1f0343f95b63a9628c3d39809545a11ebcbf2e3b55d8aeee71b4c3d6adf3023100a2f39b1605b27028a5dd4ba069b5016e65b4fbde8fe0061d6a53197f9cdaf5d943bc61fc2beb03cb6fee8d2302f3dff6"; - cabundle[1] = - hex"308202bf30820244a00302010202100b93e39c65609c59e8144a2ad34ba3a0300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3234313132333036333235355a170d3234313231333037333235355a3064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d353133623665666332313639303264372e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004ee78108039725a03e0b63a5d7d1244f6294eb7631f305e360997c8e5c06c779f23cfaeb64cb9aeac8a031bfac9f4dafc3621b4367f003c08c0ce410c2118396cc5d56ec4e92e1b17f9709b2bffcef462f7bcb97d6ca11325c4a30156c9720de7a381d53081d230120603551d130101ff040830060101ff020102301f0603551d230418301680149025b50dd90547e796c396fa729dcf99a9df4b96301d0603551d0e041604142b3d75d274a3cdd61b2c13f539e08c960ce757dd300e0603551d0f0101ff040403020186306c0603551d1f046530633061a05fa05d865b687474703a2f2f6177732d6e6974726f2d656e636c617665732d63726c2e73332e616d617a6f6e6177732e636f6d2f63726c2f61623439363063632d376436332d343262642d396539662d3539333338636236376638342e63726c300a06082a8648ce3d0403030369003066023100fce7a6c2b38e0a8ebf0d28348d74463458b84bfe8b2b95315dd4da665e8e83d4ab911852a4e92a8263ecf571d2df3b89023100ab92be511136be76aa313018f9f4825eaad602d0342d268e6da632767f68f55f761fa9fd2a7ee716c481c67f26e3f8f4"; - cabundle[2] = - hex"308203153082029aa003020102021020c20971680e956fc3c8ce925d784bc7300a06082a8648ce3d0403033064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d353133623665666332313639303264372e75732d656173742d312e6177732e6e6974726f2d656e636c61766573301e170d3234313132353032323230345a170d3234313230313030323230345a308189313c303a06035504030c33626635396531633335623630386133382e7a6f6e616c2e75732d656173742d312e6177732e6e6974726f2d656e636c61766573310c300a060355040b0c03415753310f300d060355040a0c06416d617a6f6e310b3009060355040613025553310b300906035504080c0257413110300e06035504070c0753656174746c653076301006072a8648ce3d020106052b8104002203620004df741cd0537abbbc37bb32b06c835f497df86933b6ac8b4ee15d1251cfde596a7953756bb2759896a4d50c7cfb7d50cfc62fd4010a8c0d4a58a6f38988de6707d5aeaef3e3ca523ffac31260cc7c33546dc667d52ba524c39bd0ed6b82c0652da381ea3081e730120603551d130101ff040830060101ff020101301f0603551d230418301680142b3d75d274a3cdd61b2c13f539e08c960ce757dd301d0603551d0e04160414e8b15a6bc0b83e3d9e50ab9b289fb5fa0c61eabf300e0603551d0f0101ff0404030201863081800603551d1f047930773075a073a071866f687474703a2f2f63726c2d75732d656173742d312d6177732d6e6974726f2d656e636c617665732e73332e75732d656173742d312e616d617a6f6e6177732e636f6d2f63726c2f39636665653133332d613562622d343431392d613462372d3730386661643563363866662e63726c300a06082a8648ce3d04030303690030660231009595351f7c4411011eb4cf1a18181c2ed6901e84c2971c781e2cdc2725d5135066fc8d96ac70c98fc27106cdb345a563023100f96927f5bc58f1c29f8ea06d9bb5eeae3a6e2e572aff9911a8c90ed6e00c1cc7c534b9fde367781807c35ba9427d05fe"; - cabundle[3] = - hex"308202bd30820244a00302010202142690c27f442c86646256455d3442f8998be152dc300a06082a8648ce3d040303308189313c303a06035504030c33626635396531633335623630386133382e7a6f6e616c2e75732d656173742d312e6177732e6e6974726f2d656e636c61766573310c300a060355040b0c03415753310f300d060355040a0c06416d617a6f6e310b3009060355040613025553310b300906035504080c0257413110300e06035504070c0753656174746c65301e170d3234313132353133353135375a170d3234313132363133353135375a30818e310b30090603550406130255533113301106035504080c0a57617368696e67746f6e3110300e06035504070c0753656174746c65310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533139303706035504030c30692d30666661626464383636323664616631662e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004c041c328653a7028db8060f3f19f589197b8c1a17dc755a5629c0f47c3cd3414412fd38b7f87fc70a6f22a0c2698fe1f748eff998f783add861a6b2373fba37a31f9bf0ab75fd2c4e17cc0df8124ddb0a4513483e40721ebb15a80619696747aa366306430120603551d130101ff040830060101ff020100300e0603551d0f0101ff040403020204301d0603551d0e041604145555580de23b6d83eb6a5be1b4dbdb376f69e444301f0603551d23041830168014e8b15a6bc0b83e3d9e50ab9b289fb5fa0c61eabf300a06082a8648ce3d0403030367003064023072c53164609cba5d7a16914d5d2102a9e70009288aae1215cc5e8d70f2d2d4b49bffb0119ec523e620275729f09e566e02302e0f2b7998eb25fa493dc1300329f7f142337b38e76df0a32b8660f41599c5febae120e4ed2c60efbbaa842ba6db8d91"; - validator.verifyCertBundle(cert, cabundle); - } - - function test_VerifyCert() public { - bytes memory parent = - hex"3082021130820196a003020102021100f93175681b90afe11d46ccb4e4e7f856300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3139313032383133323830355a170d3439313032383134323830355a3049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004fc0254eba608c1f36870e29ada90be46383292736e894bfff672d989444b5051e534a4b1f6dbe3c0bc581a32b7b176070ede12d69a3fea211b66e752cf7dd1dd095f6f1370f4170843d9dc100121e4cf63012809664487c9796284304dc53ff4a3423040300f0603551d130101ff040530030101ff301d0603551d0e041604149025b50dd90547e796c396fa729dcf99a9df4b96300e0603551d0f0101ff040403020186300a06082a8648ce3d0403030369003066023100a37f2f91a1c9bd5ee7b8627c1698d255038e1f0343f95b63a9628c3d39809545a11ebcbf2e3b55d8aeee71b4c3d6adf3023100a2f39b1605b27028a5dd4ba069b5016e65b4fbde8fe0061d6a53197f9cdaf5d943bc61fc2beb03cb6fee8d2302f3dff6"; - bytes memory cert = - hex"308202bf30820244a00302010202100b93e39c65609c59e8144a2ad34ba3a0300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3234313132333036333235355a170d3234313231333037333235355a3064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d353133623665666332313639303264372e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004ee78108039725a03e0b63a5d7d1244f6294eb7631f305e360997c8e5c06c779f23cfaeb64cb9aeac8a031bfac9f4dafc3621b4367f003c08c0ce410c2118396cc5d56ec4e92e1b17f9709b2bffcef462f7bcb97d6ca11325c4a30156c9720de7a381d53081d230120603551d130101ff040830060101ff020102301f0603551d230418301680149025b50dd90547e796c396fa729dcf99a9df4b96301d0603551d0e041604142b3d75d274a3cdd61b2c13f539e08c960ce757dd300e0603551d0f0101ff040403020186306c0603551d1f046530633061a05fa05d865b687474703a2f2f6177732d6e6974726f2d656e636c617665732d63726c2e73332e616d617a6f6e6177732e636f6d2f63726c2f61623439363063632d376436332d343262642d396539662d3539333338636236376638342e63726c300a06082a8648ce3d0403030369003066023100fce7a6c2b38e0a8ebf0d28348d74463458b84bfe8b2b95315dd4da665e8e83d4ab911852a4e92a8263ecf571d2df3b89023100ab92be511136be76aa313018f9f4825eaad602d0342d268e6da632767f68f55f761fa9fd2a7ee716c481c67f26e3f8f4"; - validator.verifyCert(cert, false, keccak256(parent)); - } }