From c8c92fac47fcc82174548426f223d70487e65cb4 Mon Sep 17 00:00:00 2001 From: Mike Godenzi Date: Fri, 23 Sep 2022 09:36:08 +0200 Subject: [PATCH] Feat/unit tests (#3) * feat(unit-tests): started to add unit tests * feat(unit-tests): added more unit tests * feat(unit-tests): moved tests to their own file * feat(unit-tests): restructured source code * chore(): removed unused dependency --- pallets/acurast/Cargo.toml | 5 + pallets/acurast/README.md | 19 +- .../p384/ecdsa/elliptic-curve/tests/pkcs8.rs | 2 +- .../ecdsa/elliptic-curve/tests/secret_key.rs | 2 +- .../src/__root_certs__/00C36B7C44B9AE1831.cer | Bin 0 -> 1312 bytes .../src/__root_certs__/00D50FF25BA3F2D6B3.cer | Bin 0 -> 1312 bytes .../src/__root_certs__/00E8FA196314D2FA18.cer | Bin 0 -> 1380 bytes pallets/acurast/src/attestation.rs | 209 +--- pallets/acurast/src/attestation/asn.rs | 11 + pallets/acurast/src/lib.rs | 904 ++---------------- pallets/acurast/src/mock.rs | 225 +++++ pallets/acurast/src/tests.rs | 563 +++++++++++ pallets/acurast/src/types.rs | 725 ++++++++++++++ pallets/acurast/src/utils.rs | 109 +++ 14 files changed, 1725 insertions(+), 1049 deletions(-) create mode 100644 pallets/acurast/src/__root_certs__/00C36B7C44B9AE1831.cer create mode 100644 pallets/acurast/src/__root_certs__/00D50FF25BA3F2D6B3.cer create mode 100644 pallets/acurast/src/__root_certs__/00E8FA196314D2FA18.cer create mode 100644 pallets/acurast/src/mock.rs create mode 100644 pallets/acurast/src/tests.rs create mode 100644 pallets/acurast/src/types.rs create mode 100644 pallets/acurast/src/utils.rs diff --git a/pallets/acurast/Cargo.toml b/pallets/acurast/Cargo.toml index 746f6ef7..d1394a73 100644 --- a/pallets/acurast/Cargo.toml +++ b/pallets/acurast/Cargo.toml @@ -32,6 +32,11 @@ ecdsa-vendored = { package = "ecdsa_vendored", path = "p384/ecdsa", default-feat [dev-dependencies] base64 = { version = "0.13.0", default-features = false, features = ["alloc"] } +hex-literal = "0.3" + +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26" } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26" } [features] default = ["std"] diff --git a/pallets/acurast/README.md b/pallets/acurast/README.md index fc6dd67c..549e1c3d 100644 --- a/pallets/acurast/README.md +++ b/pallets/acurast/README.md @@ -3,7 +3,7 @@ ## Introduction -The Acurast Pallet allows a Parachain to integrate the Acurast functionality to be able to securly receive real world data posted by the Acurast Data Transmitters. +The Acurast Pallet allows a Parachain to integrate the Acurast functionality to be able to securly receive real world data posted by the Acurast Processors. The Pallet exposes a number of extrinsic. @@ -12,7 +12,7 @@ The Pallet exposes a number of extrinsic. Allows the registration of a job. A registration consists of: - An ipfs URL to a `script` (written in Javascript). - - The script will be run in the Acurast Trusted Virtual Machine that uses a Trusted Execution Environment (TEE) on the Acurast Data Transmitter. + - The script will be run in the Acurast Trusted Virtual Machine that uses a Trusted Execution Environment (TEE) on the Acurast Processor. - An optional `allowedSources` list of allowed sources. - A list of `AccountId`s that are allowed to `fulfill` the job. If no list is provided, all sources are accepted. - An `allowOnlyVerifiedSources` boolean indicating if only verified source can fulfill the job. @@ -38,6 +38,14 @@ Allows to post the fulfillment of a registered job. The fulfillment structure co In addition to the `fulfillment` structure, `fulfill` expects the `AccountId` of the `requester` of the job. +### submitAttestation + +Allows an Acurast Processor to submit a key attestation proving its integrity. The extrinsic parameter is a valid attestation certificate chain. + +### updateCertificateRevocationList + +Allows to update the certificate recovation list used during attestation validation. + ## Setup Add the following dependency to your Cargo manifest: @@ -71,10 +79,15 @@ impl pallet_acurast::FulfillmentRouter for AcurastRouter { } } +parameter_types! { + pub AllowedRevocationListUpdate: Vec = vec![]; +} + impl pallet_acurast::Config for Runtime { type Event = Event; type RegistrationExtra = AcurastRegistrationExtra; type FulfillmentRouter = AcurastRouter; + type AllowedRevocationListUpdate = AllowedRevocationListUpdate; } // Create the runtime by composing the FRAME pallets that were previously configured. @@ -219,7 +232,7 @@ contract SimpleFulfill { ## P256 signatures -Acurast Data Transmitters will sign extrinsics (the `fulfill` call) using a P256 (a.k.a secp256r1) private key. +Acurast Processors will sign extrinsics (the `fulfill` call) using a P256 (a.k.a secp256r1) private key. By default, Substrate does not support the P256 curve. Use the `acurast-p256-crypto` crate to add support for P256 signature verification. diff --git a/pallets/acurast/p384/ecdsa/elliptic-curve/tests/pkcs8.rs b/pallets/acurast/p384/ecdsa/elliptic-curve/tests/pkcs8.rs index cffb0680..6e8e70df 100644 --- a/pallets/acurast/p384/ecdsa/elliptic-curve/tests/pkcs8.rs +++ b/pallets/acurast/p384/ecdsa/elliptic-curve/tests/pkcs8.rs @@ -2,7 +2,7 @@ #![cfg(all(feature = "dev", feature = "pkcs8"))] -use elliptic_curve::{ +use elliptic_curve_vendored::{ dev::{PublicKey, SecretKey}, pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePrivateKey}, sec1::ToEncodedPoint, diff --git a/pallets/acurast/p384/ecdsa/elliptic-curve/tests/secret_key.rs b/pallets/acurast/p384/ecdsa/elliptic-curve/tests/secret_key.rs index 50e5a95c..5bdc1cc0 100644 --- a/pallets/acurast/p384/ecdsa/elliptic-curve/tests/secret_key.rs +++ b/pallets/acurast/p384/ecdsa/elliptic-curve/tests/secret_key.rs @@ -2,7 +2,7 @@ #![cfg(feature = "dev")] -use elliptic_curve::dev::SecretKey; +use elliptic_curve_vendored::dev::SecretKey; #[test] fn undersize_secret_key() { diff --git a/pallets/acurast/src/__root_certs__/00C36B7C44B9AE1831.cer b/pallets/acurast/src/__root_certs__/00C36B7C44B9AE1831.cer new file mode 100644 index 0000000000000000000000000000000000000000..189eb9598fa4b0c355e3b7c98d73de130d40807e GIT binary patch literal 1312 zcmXqLVwEvyVrE&u%*4pV#K~|tyT)bbItfDqUN%mxHjlRNyo`+8tPBRyhLQ&2Y|No7 ztil3mmPQ5!mZ=t|#z|&L1}3Hka^k#3hCpC$WNc_)ViYCLYitJO8bi7GH8(LSAzQ}C z%D~*j#Lr;R#Kgta#Kg$3e%tXT?TyPB-)L{q>pl}Amc07?41J^H^`(Z)Jkx``rQ=_4 z{ywg?(pA31X78+gVbcE_tf>-8qw<+-c=yzbk& z;;pvT**S4m+LGjBK4DxOU#AmDzi*FRhupQpWMmS@mo6*S;05;%*E|Tx;RK zxYDJ~jpgp<8%KUBESxPCcIczmwF@?3Z`e3yvdG9?T>3B9#9?dLu8S8I21U#Xej01e zp7cRT@BPF!X=%P3pN2^#GY`DncVLH|^}(MuMF%_+y!Yx0yFSyg?^C<|hV7(#+UJ8B z%5(jlg?`--vS@#h{pM?Wg!K>8sK$SHmXX$@IdR+N`kIRowmos*L3*t2tiQ7M~ zP3yYsx5fJ}xpGEc3ohZEzEIz+Tljt2)eB3#^7Lb8<-d2yFxr#e;;z~z&uM$%*=L8? z71NyG^ZRCQb7BbmyVJlk-Og+(Ys2NUUw&WcKHAbeRgEE;iJ6gsadEOiqJb0spy>~_g<|1afp@|C}kfjmfBnMJ}ttO2_MevkrTM#ldvtOm?L%770f zzz-5&VP;}tK~9FiG!IOMj10jh*W#X@p8u(1)i2YMt}DmRrY3q?oMZ7c6Xkw*J*a$f zWqP)k_1gP_B9&(^d+lh`o18T)%_KmT(`?pL_XDwxC(X`t*wucgzyJ&7zMe zziPNs^ZV78Ig_e)dA??6pBLwn?paXWLI4HJv$J?N>6*{o>1ywq=`2 zKQQ0zm|r=|HPh!&L3#JtAn)zh^Ce`ZWtomVeyF7p!I$>8Wx>BaFHE#@L$>aIpK@vL z#+(DDm!}+5@_W8)+pLelCUsR8we&trowT8(ewGQ(!rYRu@6zfPMs>}{TOZHeymv>J zRt1Ze)~P2O7ge<$jNcvk#n#P4@SEk{+Zt~S6w_VA!uKb{KYVSvp!AiLNy4iTsnhH8 pxA0wE(IFef<8eAF-gUN;$Vt!LGwk1sSQ?AJQG0u4+EOm3PyjrVHVOa$ literal 0 HcmV?d00001 diff --git a/pallets/acurast/src/__root_certs__/00D50FF25BA3F2D6B3.cer b/pallets/acurast/src/__root_certs__/00D50FF25BA3F2D6B3.cer new file mode 100644 index 0000000000000000000000000000000000000000..6482f0f48fa9ebf64e19cc3b47efe1ad7c202a67 GIT binary patch literal 1312 zcmXqLVwEvyVrE&u%*4pV#K~}#|5NnhPuDgZ@Un4gwRyCC=VfH%W@RvtHk33FXJZa! zVHFlgvota=uuQcuHBK^1GB7bUkQ3)Mv@|p{GBPqSHaE4166ZBGF*G!^fO7F`ZemhG zwv3ULfw_r^pTVGsiHoU;iIHLbw&P9O8<#V_(cYrheI`UKdG-4l`bNj=OAVQMrU!XT z$G_nGeOzm$t9*&g-dXv=rX^lWKD=b0$4A_XeuS8eJfnW-58X( z*1~^rrAwO|%iYa4j{H}k&(N&^k1%t!`84}7cVRfikK7p zG}fFw>4T8o`-yGR(tJ5S4UgFkJG4tOSb@6{J}eWqjIr*``d+e!Dd z&j&S>=lVMf{kkD!(f%O&&DZn@>mR03jsITl`Z=rl#CO3D4tty zFgx#E`OgmH4HCB&^0~^hEiSpr-uO9^Lo;o6dG}Br8>fA5lEv?smAUA0Y~)Aqu% z&knIGra8ao_s!hq#1Qy*r-5g>o!L~@hRbKa{JzkAw555f8bdM@Gb01z;$(wF16g4D zljUO(V-Yb+e8^DK!O9uf?ULjEU&`m?D}Nyad62X+i-dt#19k=cAO*sVjQ?3!4VZzH z0UtEpnse=GMm}Cok}XuS zo^S7R>^i-(i}lFKYt!DX+QxZd7th1R(l1*k+_(4PTY2~73;o~xr|stLy>WV~dgrH& zj`C~5PP94YZP!e2Dvn*sC%C3CUP9vh{5yWCUgpWB(pf4Fq8y28R(Xcp50~;jJ^tcd z2mf8~ryXAALcJ2r+?(CAzi7%eT|JhSGG+2S4iE0%2l#8dO0L)y7uW5J?ejY-YM|@Z znW^4zr_|zY-Ne#;iI&fOo!44)8wC8ny7FF75(CGK1MY z@$;AOyC3gvYM!>Z>al2P(;oAbofTZl^ZxLcE`0RtrsKKQYt-+)W_+}_MUiRtE0e2I z?{X5S)c zk(STOIj*|yOXnLgesP?BO6F``nf|7)hJoAuZTkOLJO9bQ=wo~59rpgmp*(lhpVxg` zSG?7>Iy)!Ms(ix3ts+j9hLNo^1lP{nv@(0|^`$kFSIRj4IjerH{@S;~Rosn1iEAzV z7gxHpxv|{ceB;PZg@v=l!VZ1(x^}@P>Aj!WCN0gE{u@Py2jO zLwT;hv(T>_LKf{0vfq47kFfq>8rAsk)vlkjnor!f(=yWfldkYaySjJlo=utL%-RBRMqFc9(Zg=9r>32k)-Z$Z3Zd|GchwVFfMLfX3)6UKo*z+ zW%*ddSVYVcA2QT*uyO`=yX3h4m-0FJ%3sJp9we>IB4HrbfL#GUNP#dT<9`-b17;v) zzy}iG2MMqMGeDbx1BfrrVrgJ*V7kC~fnl3|MoCFQv6a4lVqQv7erAeZdVYR-PHJL7 zX0cv!ey)CENl9vPNn%N6ex81EQI0-xQU>N5U{YpeP&je8*Za=OaA~)mPW}HytB(C} zSt#qc;zM!H;aah4PPu(`Q`lL2URgftWR>$AH~YZpY38$i)mG>l{GFrU_cr(7KK+($olkCxsCJ8Z?|P=P z@Hktcu%14f*ou}-ORg=+|JAvTY5%SpJVMTe@?ozN!oHcGSRf*E)M1KRcaKf&&MBK? zc+ci+T7Br&zehn#wz{u)t{u+UxA>W(N87iJPrIY9A2-;PapdvA-zh3P61?_Cju9 z4QFqa-fW2m>8dSj3L|PwV$U>lUfre@;8eBjg6(P6nK{#T@x&IsR7^S`cB*Cm=Xlc}t-t;=4EerpN5g{UHVb^G96Hx5{%(tH-2aH? z$7WwYdEQ#Hq;rz_ezm$7=haNFT*OqEi>#lm%G62RyKdeU&MV1BPS&Itq?W(?w5K#- zd${PCIkm@X+BkH#2dvLxpl{d(6kh$Helu9v{>-_tW*4aQiG#;(eoXYrE0x z4-$%1IXvYmQXUmgOb-}To^}=LJX)T_#x_g8H}!|ZLzkfTeK*QiuTy#CVk*`fYV!mD DZ>UTd literal 0 HcmV?d00001 diff --git a/pallets/acurast/src/attestation.rs b/pallets/acurast/src/attestation.rs index ca9618b4..5ce7a538 100644 --- a/pallets/acurast/src/attestation.rs +++ b/pallets/acurast/src/attestation.rs @@ -48,7 +48,7 @@ pub const KEY_ATTESTATION_OID: ObjectIdentifier = oid!(1, 3, 6, 1, 4, 1, 11129, pub fn extract_attestation<'a>( extensions: Option>>, -) -> Result { +) -> Result, ValidationError> { let extension = extensions .ok_or(ValidationError::ExtensionMissing)? .find(|e| e.extn_id == KEY_ATTESTATION_OID) @@ -165,7 +165,6 @@ fn validate<'a>( if cert.signature_algorithm.algorithm != cert.tbs_certificate.signature.algorithm { return Err(ValidationError::SignatureMismatch); } - match cert.signature_algorithm.algorithm { RSA_ALGORITHM => match pbk { PublicKey::RSA(pbk) => validate_rsa(&payload, &cert.signature_value, &pbk), @@ -306,211 +305,11 @@ pub fn validate_certificate_chain<'a>( /// The list of trusted root certificates, as decoded bytes arrays. [Source](https://developer.android.com/training/articles/security-key-attestation#root_certificate) const TRUSTED_ROOT_CERTS: &'static [&[u8]] = &[ // base64 equivalent: r"MIIFYDCCA0igAwIBAgIJAOj6GWMU0voYMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNVBAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMTYwNTI2MTYyODUyWhcNMjYwNTI0MTYyODUyWjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdSSxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggjnar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGqC4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQoVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+OJtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/EgsTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRiigHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+MRPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9EaDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5UmAGMCAwEAAaOBpjCBozAdBgNVHQ4EFgQUNmHhAHyIBQlRi0RsR/8aTMnqTxIwHwYDVR0jBBgwFoAUNmHhAHyIBQlRi0RsR/8aTMnqTxIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwQAYDVR0fBDkwNzA1oDOgMYYvaHR0cHM6Ly9hbmRyb2lkLmdvb2dsZWFwaXMuY29tL2F0dGVzdGF0aW9uL2NybC8wDQYJKoZIhvcNAQELBQADggIBACDIw41L3KlXG0aMiS//cqrG+EShHUGo8HNsw30W1kJtjn6UBwRM6jnmiwfBPb8VA91chb2vssAtX2zbTvqBJ9+LBPGCdw/E53Rbf86qhxKaiAHOjpvAy5Y3m00mqC0w/Zwvju1twb4vhLaJ5NkUJYsUS7rmJKHHBnETLi8GFqiEsqTWpG/6ibYCv7rYDBJDcR9W62BW9jfIoBQcxUCUJouMPH25lLNcDc1ssqvC2v7iUgI9LeoM1sNovqPmQUiG9rHli1vXxzCyaMTjwftkJLkf6724DFhuKug2jITV0QkXvaJWF4nUaHOTNA4uJU9WDvZLI1j83A+/xnAJUucIv/zGJ1AMH2boHqF8CY16LpsYgBt6tKxxWH00XcyDCdW2KlBCeqbQPcsFmWyWugxdcekhYsAWyoSf818NUsZdBWBaR/OukXrNLfkQ79IyZohZbvabO/X+MVT3rriAoKc8oE2Uws6DF+60PV7/WIPjNvXySdqspImSN78mflxDqwLqRBYkA3I75qppLGG9rp7UCdRjxMl8ZDBld+7yvHVgt1cVzJx9xnyGCC23UaicMDSXYrB4I4WHXPGjxhZuCuPBLTdOLU8YRvMYdEvYebWHMpvwGCF6bAx3JBpIeOQ1wDB5y0USicV3YgYGmi+NZfhA4URSh77Yd6uuJOJENRaNVTzk" - &[ - 48, 130, 5, 96, 48, 130, 3, 72, 160, 3, 2, 1, 2, 2, 9, 0, 232, 250, 25, 99, 20, 210, 250, - 24, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 11, 5, 0, 48, 27, 49, 25, 48, 23, 6, 3, - 85, 4, 5, 19, 16, 102, 57, 50, 48, 48, 57, 101, 56, 53, 51, 98, 54, 98, 48, 52, 53, 48, 30, - 23, 13, 49, 54, 48, 53, 50, 54, 49, 54, 50, 56, 53, 50, 90, 23, 13, 50, 54, 48, 53, 50, 52, - 49, 54, 50, 56, 53, 50, 90, 48, 27, 49, 25, 48, 23, 6, 3, 85, 4, 5, 19, 16, 102, 57, 50, - 48, 48, 57, 101, 56, 53, 51, 98, 54, 98, 48, 52, 53, 48, 130, 2, 34, 48, 13, 6, 9, 42, 134, - 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 2, 15, 0, 48, 130, 2, 10, 2, 130, 2, 1, 0, 175, - 182, 199, 130, 43, 177, 167, 1, 236, 43, 180, 46, 139, 204, 84, 22, 99, 171, 239, 152, 47, - 50, 199, 127, 117, 49, 3, 12, 151, 82, 75, 27, 95, 232, 9, 251, 199, 42, 169, 69, 31, 116, - 60, 189, 154, 111, 19, 53, 116, 74, 165, 94, 119, 246, 182, 172, 53, 53, 238, 23, 194, 94, - 99, 149, 23, 221, 156, 146, 230, 55, 74, 83, 203, 254, 37, 143, 143, 251, 182, 253, 18, - 147, 120, 162, 42, 76, 169, 156, 69, 45, 71, 165, 159, 50, 1, 244, 65, 151, 202, 28, 205, - 126, 118, 47, 178, 245, 49, 81, 182, 254, 178, 255, 253, 43, 111, 228, 254, 91, 198, 189, - 158, 195, 75, 254, 8, 35, 157, 170, 252, 235, 142, 181, 168, 237, 43, 58, 205, 156, 94, 58, - 119, 144, 225, 181, 20, 66, 121, 49, 89, 133, 152, 17, 173, 158, 178, 169, 107, 189, 215, - 165, 124, 147, 169, 28, 65, 252, 205, 39, 214, 127, 214, 246, 113, 170, 11, 129, 82, 97, - 173, 56, 79, 163, 121, 68, 134, 70, 4, 221, 179, 216, 196, 249, 32, 161, 155, 22, 86, 194, - 241, 74, 214, 208, 60, 86, 236, 6, 8, 153, 4, 28, 30, 209, 165, 254, 109, 52, 64, 181, 86, - 186, 209, 208, 161, 82, 88, 156, 83, 229, 93, 55, 7, 98, 240, 18, 46, 239, 145, 134, 27, - 27, 14, 108, 76, 128, 146, 116, 153, 192, 233, 190, 192, 184, 62, 59, 193, 249, 60, 114, - 192, 73, 96, 75, 189, 47, 19, 69, 230, 44, 63, 142, 38, 219, 236, 6, 201, 71, 102, 243, - 193, 40, 35, 157, 79, 67, 18, 250, 216, 18, 56, 135, 224, 107, 236, 245, 103, 88, 59, 248, - 53, 90, 129, 254, 234, 186, 249, 154, 131, 200, 223, 62, 42, 50, 42, 252, 103, 43, 241, 32, - 177, 53, 21, 139, 104, 33, 206, 175, 48, 155, 110, 238, 119, 249, 136, 51, 176, 24, 218, - 161, 14, 69, 31, 6, 163, 116, 213, 7, 129, 243, 89, 8, 41, 102, 187, 119, 139, 147, 8, 148, - 38, 152, 231, 78, 11, 205, 36, 98, 138, 1, 194, 204, 3, 229, 31, 11, 62, 91, 74, 193, 228, - 223, 158, 175, 159, 246, 164, 146, 167, 124, 20, 131, 136, 40, 133, 1, 91, 66, 44, 230, - 123, 128, 184, 140, 155, 72, 225, 59, 96, 122, 181, 69, 199, 35, 255, 140, 68, 248, 242, - 211, 104, 185, 246, 82, 13, 49, 20, 94, 191, 158, 134, 42, 215, 29, 246, 163, 191, 210, 69, - 9, 89, 214, 83, 116, 13, 151, 161, 47, 54, 139, 19, 239, 102, 213, 208, 165, 74, 110, 47, - 93, 154, 111, 239, 68, 104, 50, 188, 103, 132, 71, 37, 134, 31, 9, 61, 208, 230, 243, 64, - 93, 168, 150, 67, 239, 15, 77, 105, 182, 66, 0, 81, 253, 185, 48, 73, 103, 62, 54, 149, 5, - 128, 211, 205, 244, 251, 208, 139, 197, 132, 131, 149, 38, 0, 99, 2, 3, 1, 0, 1, 163, 129, - 166, 48, 129, 163, 48, 29, 6, 3, 85, 29, 14, 4, 22, 4, 20, 54, 97, 225, 0, 124, 136, 5, 9, - 81, 139, 68, 108, 71, 255, 26, 76, 201, 234, 79, 18, 48, 31, 6, 3, 85, 29, 35, 4, 24, 48, - 22, 128, 20, 54, 97, 225, 0, 124, 136, 5, 9, 81, 139, 68, 108, 71, 255, 26, 76, 201, 234, - 79, 18, 48, 15, 6, 3, 85, 29, 19, 1, 1, 255, 4, 5, 48, 3, 1, 1, 255, 48, 14, 6, 3, 85, 29, - 15, 1, 1, 255, 4, 4, 3, 2, 1, 134, 48, 64, 6, 3, 85, 29, 31, 4, 57, 48, 55, 48, 53, 160, - 51, 160, 49, 134, 47, 104, 116, 116, 112, 115, 58, 47, 47, 97, 110, 100, 114, 111, 105, - 100, 46, 103, 111, 111, 103, 108, 101, 97, 112, 105, 115, 46, 99, 111, 109, 47, 97, 116, - 116, 101, 115, 116, 97, 116, 105, 111, 110, 47, 99, 114, 108, 47, 48, 13, 6, 9, 42, 134, - 72, 134, 247, 13, 1, 1, 11, 5, 0, 3, 130, 2, 1, 0, 32, 200, 195, 141, 75, 220, 169, 87, 27, - 70, 140, 137, 47, 255, 114, 170, 198, 248, 68, 161, 29, 65, 168, 240, 115, 108, 195, 125, - 22, 214, 66, 109, 142, 126, 148, 7, 4, 76, 234, 57, 230, 139, 7, 193, 61, 191, 21, 3, 221, - 92, 133, 189, 175, 178, 192, 45, 95, 108, 219, 78, 250, 129, 39, 223, 139, 4, 241, 130, - 119, 15, 196, 231, 116, 91, 127, 206, 170, 135, 18, 154, 136, 1, 206, 142, 155, 192, 203, - 150, 55, 155, 77, 38, 168, 45, 48, 253, 156, 47, 142, 237, 109, 193, 190, 47, 132, 182, - 137, 228, 217, 20, 37, 139, 20, 75, 186, 230, 36, 161, 199, 6, 113, 19, 46, 47, 6, 22, 168, - 132, 178, 164, 214, 164, 111, 250, 137, 182, 2, 191, 186, 216, 12, 18, 67, 113, 31, 86, - 235, 96, 86, 246, 55, 200, 160, 20, 28, 197, 64, 148, 38, 139, 140, 60, 125, 185, 148, 179, - 92, 13, 205, 108, 178, 171, 194, 218, 254, 226, 82, 2, 61, 45, 234, 12, 214, 195, 104, 190, - 163, 230, 65, 72, 134, 246, 177, 229, 139, 91, 215, 199, 48, 178, 104, 196, 227, 193, 251, - 100, 36, 185, 31, 235, 189, 184, 12, 88, 110, 42, 232, 54, 140, 132, 213, 209, 9, 23, 189, - 162, 86, 23, 137, 212, 104, 115, 147, 52, 14, 46, 37, 79, 86, 14, 246, 75, 35, 88, 252, - 220, 15, 191, 198, 112, 9, 82, 231, 8, 191, 252, 198, 39, 80, 12, 31, 102, 232, 30, 161, - 124, 9, 141, 122, 46, 155, 24, 128, 27, 122, 180, 172, 113, 88, 125, 52, 93, 204, 131, 9, - 213, 182, 42, 80, 66, 122, 166, 208, 61, 203, 5, 153, 108, 150, 186, 12, 93, 113, 233, 33, - 98, 192, 22, 202, 132, 159, 243, 95, 13, 82, 198, 93, 5, 96, 90, 71, 243, 174, 145, 122, - 205, 45, 249, 16, 239, 210, 50, 102, 136, 89, 110, 246, 155, 59, 245, 254, 49, 84, 247, - 174, 184, 128, 160, 167, 60, 160, 77, 148, 194, 206, 131, 23, 238, 180, 61, 94, 255, 88, - 131, 227, 54, 245, 242, 73, 218, 172, 164, 137, 146, 55, 191, 38, 126, 92, 67, 171, 2, 234, - 68, 22, 36, 3, 114, 59, 230, 170, 105, 44, 97, 189, 174, 158, 212, 9, 212, 99, 196, 201, - 124, 100, 48, 101, 119, 238, 242, 188, 117, 96, 183, 87, 21, 204, 156, 125, 198, 124, 134, - 8, 45, 183, 81, 168, 156, 48, 52, 151, 98, 176, 120, 35, 133, 135, 92, 241, 163, 198, 22, - 110, 10, 227, 193, 45, 55, 78, 45, 79, 24, 70, 243, 24, 116, 75, 216, 121, 181, 135, 50, - 155, 240, 24, 33, 122, 108, 12, 119, 36, 26, 72, 120, 228, 53, 192, 48, 121, 203, 69, 18, - 137, 197, 119, 98, 6, 6, 154, 47, 141, 101, 248, 64, 225, 68, 82, 135, 190, 216, 119, 171, - 174, 36, 226, 68, 53, 22, 141, 85, 60, 228, - ], + include_bytes!("./__root_certs__/00E8FA196314D2FA18.cer"), // base64 equivalent: r"MIIFHDCCAwSgAwIBAgIJANUP8luj8tazMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNVBAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMTkxMTIyMjAzNzU4WhcNMzQxMTE4MjAzNzU4WjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdSSxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggjnar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGqC4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQoVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+OJtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/EgsTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRiigHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+MRPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9EaDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5UmAGMCAwEAAaNjMGEwHQYDVR0OBBYEFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMB8GA1UdIwQYMBaAFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgIEMA0GCSqGSIb3DQEBCwUAA4ICAQBOMaBc8oumXb2voc7XCWnuXKhBBK3e2KMGz39t7lA3XXRe2ZLLAkLM5y3J7tURkf5a1SutfdOyXAmeE6SRo83Uh6WszodmMkxK5GM4JGrnt4pBisu5igXEydaW7qq2CdC6DOGjG+mEkN8/TA6p3cnoL/sPyz6evdjLlSeJ8rFBH6xWyIZCbrcpYEJzXaUOEaxxXxgYz5/cTiVKN2M1G2okQBUIYSY6bjEL4aUN5cfo7ogP3UvliEo3Eo0YgwuzR2v0KR6C1cZqZJSTnghIC/vAD32KdNQ+c3N+vl2OTsUVMC1GiWkngNx1OO1+kXW+YTnnTUOtOIswUP/Vqd5SYgAImMAfY8U9/iIgkQj6T2W6FsScy94IN9fFhE1UtzmLoBIuUFsVXJMTz+Jucth+IqoWFua9v1R93/k98p41pjtFX+H8DslVgfP097vju4KDlqN64xV1grw3ZLl4CiOe/A91oeLm2UHOq6wn3esB4r2EIQKb6jTVGu5sYCcdWpXr0AUVqcABPdgL+H7qJguBw09ojm6xNIrw2OocrDKsudk/okr/AwqEyPKw9WnMlQgLIKw1rODG2NvU9oR3GVGdMkUBZutL8VuFkERQGt6vQ2OCw0sV47VMkuYbacK/xyZFiRcrPJPb41zgbQj9XAEyLKCHex0SdDrx+tWUDqG8At2JHA==" - &[ - 48, 130, 5, 28, 48, 130, 3, 4, 160, 3, 2, 1, 2, 2, 9, 0, 213, 15, 242, 91, 163, 242, 214, - 179, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 11, 5, 0, 48, 27, 49, 25, 48, 23, 6, 3, - 85, 4, 5, 19, 16, 102, 57, 50, 48, 48, 57, 101, 56, 53, 51, 98, 54, 98, 48, 52, 53, 48, 30, - 23, 13, 49, 57, 49, 49, 50, 50, 50, 48, 51, 55, 53, 56, 90, 23, 13, 51, 52, 49, 49, 49, 56, - 50, 48, 51, 55, 53, 56, 90, 48, 27, 49, 25, 48, 23, 6, 3, 85, 4, 5, 19, 16, 102, 57, 50, - 48, 48, 57, 101, 56, 53, 51, 98, 54, 98, 48, 52, 53, 48, 130, 2, 34, 48, 13, 6, 9, 42, 134, - 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 2, 15, 0, 48, 130, 2, 10, 2, 130, 2, 1, 0, 175, - 182, 199, 130, 43, 177, 167, 1, 236, 43, 180, 46, 139, 204, 84, 22, 99, 171, 239, 152, 47, - 50, 199, 127, 117, 49, 3, 12, 151, 82, 75, 27, 95, 232, 9, 251, 199, 42, 169, 69, 31, 116, - 60, 189, 154, 111, 19, 53, 116, 74, 165, 94, 119, 246, 182, 172, 53, 53, 238, 23, 194, 94, - 99, 149, 23, 221, 156, 146, 230, 55, 74, 83, 203, 254, 37, 143, 143, 251, 182, 253, 18, - 147, 120, 162, 42, 76, 169, 156, 69, 45, 71, 165, 159, 50, 1, 244, 65, 151, 202, 28, 205, - 126, 118, 47, 178, 245, 49, 81, 182, 254, 178, 255, 253, 43, 111, 228, 254, 91, 198, 189, - 158, 195, 75, 254, 8, 35, 157, 170, 252, 235, 142, 181, 168, 237, 43, 58, 205, 156, 94, 58, - 119, 144, 225, 181, 20, 66, 121, 49, 89, 133, 152, 17, 173, 158, 178, 169, 107, 189, 215, - 165, 124, 147, 169, 28, 65, 252, 205, 39, 214, 127, 214, 246, 113, 170, 11, 129, 82, 97, - 173, 56, 79, 163, 121, 68, 134, 70, 4, 221, 179, 216, 196, 249, 32, 161, 155, 22, 86, 194, - 241, 74, 214, 208, 60, 86, 236, 6, 8, 153, 4, 28, 30, 209, 165, 254, 109, 52, 64, 181, 86, - 186, 209, 208, 161, 82, 88, 156, 83, 229, 93, 55, 7, 98, 240, 18, 46, 239, 145, 134, 27, - 27, 14, 108, 76, 128, 146, 116, 153, 192, 233, 190, 192, 184, 62, 59, 193, 249, 60, 114, - 192, 73, 96, 75, 189, 47, 19, 69, 230, 44, 63, 142, 38, 219, 236, 6, 201, 71, 102, 243, - 193, 40, 35, 157, 79, 67, 18, 250, 216, 18, 56, 135, 224, 107, 236, 245, 103, 88, 59, 248, - 53, 90, 129, 254, 234, 186, 249, 154, 131, 200, 223, 62, 42, 50, 42, 252, 103, 43, 241, 32, - 177, 53, 21, 139, 104, 33, 206, 175, 48, 155, 110, 238, 119, 249, 136, 51, 176, 24, 218, - 161, 14, 69, 31, 6, 163, 116, 213, 7, 129, 243, 89, 8, 41, 102, 187, 119, 139, 147, 8, 148, - 38, 152, 231, 78, 11, 205, 36, 98, 138, 1, 194, 204, 3, 229, 31, 11, 62, 91, 74, 193, 228, - 223, 158, 175, 159, 246, 164, 146, 167, 124, 20, 131, 136, 40, 133, 1, 91, 66, 44, 230, - 123, 128, 184, 140, 155, 72, 225, 59, 96, 122, 181, 69, 199, 35, 255, 140, 68, 248, 242, - 211, 104, 185, 246, 82, 13, 49, 20, 94, 191, 158, 134, 42, 215, 29, 246, 163, 191, 210, 69, - 9, 89, 214, 83, 116, 13, 151, 161, 47, 54, 139, 19, 239, 102, 213, 208, 165, 74, 110, 47, - 93, 154, 111, 239, 68, 104, 50, 188, 103, 132, 71, 37, 134, 31, 9, 61, 208, 230, 243, 64, - 93, 168, 150, 67, 239, 15, 77, 105, 182, 66, 0, 81, 253, 185, 48, 73, 103, 62, 54, 149, 5, - 128, 211, 205, 244, 251, 208, 139, 197, 132, 131, 149, 38, 0, 99, 2, 3, 1, 0, 1, 163, 99, - 48, 97, 48, 29, 6, 3, 85, 29, 14, 4, 22, 4, 20, 54, 97, 225, 0, 124, 136, 5, 9, 81, 139, - 68, 108, 71, 255, 26, 76, 201, 234, 79, 18, 48, 31, 6, 3, 85, 29, 35, 4, 24, 48, 22, 128, - 20, 54, 97, 225, 0, 124, 136, 5, 9, 81, 139, 68, 108, 71, 255, 26, 76, 201, 234, 79, 18, - 48, 15, 6, 3, 85, 29, 19, 1, 1, 255, 4, 5, 48, 3, 1, 1, 255, 48, 14, 6, 3, 85, 29, 15, 1, - 1, 255, 4, 4, 3, 2, 2, 4, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 11, 5, 0, 3, 130, - 2, 1, 0, 78, 49, 160, 92, 242, 139, 166, 93, 189, 175, 161, 206, 215, 9, 105, 238, 92, 168, - 65, 4, 173, 222, 216, 163, 6, 207, 127, 109, 238, 80, 55, 93, 116, 94, 217, 146, 203, 2, - 66, 204, 231, 45, 201, 238, 213, 17, 145, 254, 90, 213, 43, 173, 125, 211, 178, 92, 9, 158, - 19, 164, 145, 163, 205, 212, 135, 165, 172, 206, 135, 102, 50, 76, 74, 228, 99, 56, 36, - 106, 231, 183, 138, 65, 138, 203, 185, 138, 5, 196, 201, 214, 150, 238, 170, 182, 9, 208, - 186, 12, 225, 163, 27, 233, 132, 144, 223, 63, 76, 14, 169, 221, 201, 232, 47, 251, 15, - 203, 62, 158, 189, 216, 203, 149, 39, 137, 242, 177, 65, 31, 172, 86, 200, 134, 66, 110, - 183, 41, 96, 66, 115, 93, 165, 14, 17, 172, 113, 95, 24, 24, 207, 159, 220, 78, 37, 74, 55, - 99, 53, 27, 106, 36, 64, 21, 8, 97, 38, 58, 110, 49, 11, 225, 165, 13, 229, 199, 232, 238, - 136, 15, 221, 75, 229, 136, 74, 55, 18, 141, 24, 131, 11, 179, 71, 107, 244, 41, 30, 130, - 213, 198, 106, 100, 148, 147, 158, 8, 72, 11, 251, 192, 15, 125, 138, 116, 212, 62, 115, - 115, 126, 190, 93, 142, 78, 197, 21, 48, 45, 70, 137, 105, 39, 128, 220, 117, 56, 237, 126, - 145, 117, 190, 97, 57, 231, 77, 67, 173, 56, 139, 48, 80, 255, 213, 169, 222, 82, 98, 0, 8, - 152, 192, 31, 99, 197, 61, 254, 34, 32, 145, 8, 250, 79, 101, 186, 22, 196, 156, 203, 222, - 8, 55, 215, 197, 132, 77, 84, 183, 57, 139, 160, 18, 46, 80, 91, 21, 92, 147, 19, 207, 226, - 110, 114, 216, 126, 34, 170, 22, 22, 230, 189, 191, 84, 125, 223, 249, 61, 242, 158, 53, - 166, 59, 69, 95, 225, 252, 14, 201, 85, 129, 243, 244, 247, 187, 227, 187, 130, 131, 150, - 163, 122, 227, 21, 117, 130, 188, 55, 100, 185, 120, 10, 35, 158, 252, 15, 117, 161, 226, - 230, 217, 65, 206, 171, 172, 39, 221, 235, 1, 226, 189, 132, 33, 2, 155, 234, 52, 213, 26, - 238, 108, 96, 39, 29, 90, 149, 235, 208, 5, 21, 169, 192, 1, 61, 216, 11, 248, 126, 234, - 38, 11, 129, 195, 79, 104, 142, 110, 177, 52, 138, 240, 216, 234, 28, 172, 50, 172, 185, - 217, 63, 162, 74, 255, 3, 10, 132, 200, 242, 176, 245, 105, 204, 149, 8, 11, 32, 172, 53, - 172, 224, 198, 216, 219, 212, 246, 132, 119, 25, 81, 157, 50, 69, 1, 102, 235, 75, 241, 91, - 133, 144, 68, 80, 26, 222, 175, 67, 99, 130, 195, 75, 21, 227, 181, 76, 146, 230, 27, 105, - 194, 191, 199, 38, 69, 137, 23, 43, 60, 147, 219, 227, 92, 224, 109, 8, 253, 92, 1, 50, 44, - 160, 135, 123, 29, 18, 116, 58, 241, 250, 213, 148, 14, 161, 188, 2, 221, 137, 28, - ], + include_bytes!("./__root_certs__/00D50FF25BA3F2D6B3.cer"), // base64 equivalent: r"MIIFHDCCAwSgAwIBAgIJAMNrfES5rhgxMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNVBAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMjExMTE3MjMxMDQyWhcNMzYxMTEzMjMxMDQyWjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdSSxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggjnar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGqC4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQoVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+OJtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/EgsTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRiigHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+MRPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9EaDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5UmAGMCAwEAAaNjMGEwHQYDVR0OBBYEFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMB8GA1UdIwQYMBaAFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgIEMA0GCSqGSIb3DQEBCwUAA4ICAQBTNNZe5cuf8oiq+jV0itTGzWVhSTjOBEk2FQvh11J3o3lna0o7rd8RFHnN00q4hi6TapFhh4qaw/iG6Xg+xOan63niLWIC5GOPFgPeYXM9+nBb3zZzC8ABypYuCusWCmt6Tn3+Pjbz3MTVhRGXuT/TQH4KGFY4PhvzAyXwdjTOCXID+aHud4RLcSySr0Fq/L+R8TWalvM1wJJPhyRjqRCJerGtfBagiALzvhnmY7U1qFcS0NCnKjoO7oFedKdWlZz0YAfu3aGCJd4KHT0MsGiLZez9WP81xYSrKMNEsDK+zK5fVzw6jA7cxmpXcARTnmAuGUeI7VVDhDzKeVOctf3a0qQLwC+d0+xrETZ4r2fRGNw2YEs2W8Qj6oDcfPvq9JySe7pJ6wcHnl5EZ0lwc4xH7Y4Dx9RA1JlfooLMw3tOdJZH0enxPXaydfAD3YifeZpFaUzicHeLzVJLt9dvGB0bHQLE4+EqKFgOZv2EoP686DQqbVS1u+9k0p2xbMA105TBIk7npraa8VM0fnrRKi7wlZKwdH+aNAyhbXRW9xsnODJ+g8eF452zvbiKKngEKirK5LGieoXBX7tZ9D1GNBH2Ob3bKOwwIWdEFle/YF/h6zWgdeoaNGDqVBrLr2+0DtWoiB1aDEjLWl9FmyIUyUm7mD/vFDkzF+wm7cyWpQpCVQ==" - &[ - 48, 130, 5, 28, 48, 130, 3, 4, 160, 3, 2, 1, 2, 2, 9, 0, 195, 107, 124, 68, 185, 174, 24, - 49, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 11, 5, 0, 48, 27, 49, 25, 48, 23, 6, 3, - 85, 4, 5, 19, 16, 102, 57, 50, 48, 48, 57, 101, 56, 53, 51, 98, 54, 98, 48, 52, 53, 48, 30, - 23, 13, 50, 49, 49, 49, 49, 55, 50, 51, 49, 48, 52, 50, 90, 23, 13, 51, 54, 49, 49, 49, 51, - 50, 51, 49, 48, 52, 50, 90, 48, 27, 49, 25, 48, 23, 6, 3, 85, 4, 5, 19, 16, 102, 57, 50, - 48, 48, 57, 101, 56, 53, 51, 98, 54, 98, 48, 52, 53, 48, 130, 2, 34, 48, 13, 6, 9, 42, 134, - 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 2, 15, 0, 48, 130, 2, 10, 2, 130, 2, 1, 0, 175, - 182, 199, 130, 43, 177, 167, 1, 236, 43, 180, 46, 139, 204, 84, 22, 99, 171, 239, 152, 47, - 50, 199, 127, 117, 49, 3, 12, 151, 82, 75, 27, 95, 232, 9, 251, 199, 42, 169, 69, 31, 116, - 60, 189, 154, 111, 19, 53, 116, 74, 165, 94, 119, 246, 182, 172, 53, 53, 238, 23, 194, 94, - 99, 149, 23, 221, 156, 146, 230, 55, 74, 83, 203, 254, 37, 143, 143, 251, 182, 253, 18, - 147, 120, 162, 42, 76, 169, 156, 69, 45, 71, 165, 159, 50, 1, 244, 65, 151, 202, 28, 205, - 126, 118, 47, 178, 245, 49, 81, 182, 254, 178, 255, 253, 43, 111, 228, 254, 91, 198, 189, - 158, 195, 75, 254, 8, 35, 157, 170, 252, 235, 142, 181, 168, 237, 43, 58, 205, 156, 94, 58, - 119, 144, 225, 181, 20, 66, 121, 49, 89, 133, 152, 17, 173, 158, 178, 169, 107, 189, 215, - 165, 124, 147, 169, 28, 65, 252, 205, 39, 214, 127, 214, 246, 113, 170, 11, 129, 82, 97, - 173, 56, 79, 163, 121, 68, 134, 70, 4, 221, 179, 216, 196, 249, 32, 161, 155, 22, 86, 194, - 241, 74, 214, 208, 60, 86, 236, 6, 8, 153, 4, 28, 30, 209, 165, 254, 109, 52, 64, 181, 86, - 186, 209, 208, 161, 82, 88, 156, 83, 229, 93, 55, 7, 98, 240, 18, 46, 239, 145, 134, 27, - 27, 14, 108, 76, 128, 146, 116, 153, 192, 233, 190, 192, 184, 62, 59, 193, 249, 60, 114, - 192, 73, 96, 75, 189, 47, 19, 69, 230, 44, 63, 142, 38, 219, 236, 6, 201, 71, 102, 243, - 193, 40, 35, 157, 79, 67, 18, 250, 216, 18, 56, 135, 224, 107, 236, 245, 103, 88, 59, 248, - 53, 90, 129, 254, 234, 186, 249, 154, 131, 200, 223, 62, 42, 50, 42, 252, 103, 43, 241, 32, - 177, 53, 21, 139, 104, 33, 206, 175, 48, 155, 110, 238, 119, 249, 136, 51, 176, 24, 218, - 161, 14, 69, 31, 6, 163, 116, 213, 7, 129, 243, 89, 8, 41, 102, 187, 119, 139, 147, 8, 148, - 38, 152, 231, 78, 11, 205, 36, 98, 138, 1, 194, 204, 3, 229, 31, 11, 62, 91, 74, 193, 228, - 223, 158, 175, 159, 246, 164, 146, 167, 124, 20, 131, 136, 40, 133, 1, 91, 66, 44, 230, - 123, 128, 184, 140, 155, 72, 225, 59, 96, 122, 181, 69, 199, 35, 255, 140, 68, 248, 242, - 211, 104, 185, 246, 82, 13, 49, 20, 94, 191, 158, 134, 42, 215, 29, 246, 163, 191, 210, 69, - 9, 89, 214, 83, 116, 13, 151, 161, 47, 54, 139, 19, 239, 102, 213, 208, 165, 74, 110, 47, - 93, 154, 111, 239, 68, 104, 50, 188, 103, 132, 71, 37, 134, 31, 9, 61, 208, 230, 243, 64, - 93, 168, 150, 67, 239, 15, 77, 105, 182, 66, 0, 81, 253, 185, 48, 73, 103, 62, 54, 149, 5, - 128, 211, 205, 244, 251, 208, 139, 197, 132, 131, 149, 38, 0, 99, 2, 3, 1, 0, 1, 163, 99, - 48, 97, 48, 29, 6, 3, 85, 29, 14, 4, 22, 4, 20, 54, 97, 225, 0, 124, 136, 5, 9, 81, 139, - 68, 108, 71, 255, 26, 76, 201, 234, 79, 18, 48, 31, 6, 3, 85, 29, 35, 4, 24, 48, 22, 128, - 20, 54, 97, 225, 0, 124, 136, 5, 9, 81, 139, 68, 108, 71, 255, 26, 76, 201, 234, 79, 18, - 48, 15, 6, 3, 85, 29, 19, 1, 1, 255, 4, 5, 48, 3, 1, 1, 255, 48, 14, 6, 3, 85, 29, 15, 1, - 1, 255, 4, 4, 3, 2, 2, 4, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 11, 5, 0, 3, 130, - 2, 1, 0, 83, 52, 214, 94, 229, 203, 159, 242, 136, 170, 250, 53, 116, 138, 212, 198, 205, - 101, 97, 73, 56, 206, 4, 73, 54, 21, 11, 225, 215, 82, 119, 163, 121, 103, 107, 74, 59, - 173, 223, 17, 20, 121, 205, 211, 74, 184, 134, 46, 147, 106, 145, 97, 135, 138, 154, 195, - 248, 134, 233, 120, 62, 196, 230, 167, 235, 121, 226, 45, 98, 2, 228, 99, 143, 22, 3, 222, - 97, 115, 61, 250, 112, 91, 223, 54, 115, 11, 192, 1, 202, 150, 46, 10, 235, 22, 10, 107, - 122, 78, 125, 254, 62, 54, 243, 220, 196, 213, 133, 17, 151, 185, 63, 211, 64, 126, 10, 24, - 86, 56, 62, 27, 243, 3, 37, 240, 118, 52, 206, 9, 114, 3, 249, 161, 238, 119, 132, 75, 113, - 44, 146, 175, 65, 106, 252, 191, 145, 241, 53, 154, 150, 243, 53, 192, 146, 79, 135, 36, - 99, 169, 16, 137, 122, 177, 173, 124, 22, 160, 136, 2, 243, 190, 25, 230, 99, 181, 53, 168, - 87, 18, 208, 208, 167, 42, 58, 14, 238, 129, 94, 116, 167, 86, 149, 156, 244, 96, 7, 238, - 221, 161, 130, 37, 222, 10, 29, 61, 12, 176, 104, 139, 101, 236, 253, 88, 255, 53, 197, - 132, 171, 40, 195, 68, 176, 50, 190, 204, 174, 95, 87, 60, 58, 140, 14, 220, 198, 106, 87, - 112, 4, 83, 158, 96, 46, 25, 71, 136, 237, 85, 67, 132, 60, 202, 121, 83, 156, 181, 253, - 218, 210, 164, 11, 192, 47, 157, 211, 236, 107, 17, 54, 120, 175, 103, 209, 24, 220, 54, - 96, 75, 54, 91, 196, 35, 234, 128, 220, 124, 251, 234, 244, 156, 146, 123, 186, 73, 235, 7, - 7, 158, 94, 68, 103, 73, 112, 115, 140, 71, 237, 142, 3, 199, 212, 64, 212, 153, 95, 162, - 130, 204, 195, 123, 78, 116, 150, 71, 209, 233, 241, 61, 118, 178, 117, 240, 3, 221, 136, - 159, 121, 154, 69, 105, 76, 226, 112, 119, 139, 205, 82, 75, 183, 215, 111, 24, 29, 27, 29, - 2, 196, 227, 225, 42, 40, 88, 14, 102, 253, 132, 160, 254, 188, 232, 52, 42, 109, 84, 181, - 187, 239, 100, 210, 157, 177, 108, 192, 53, 211, 148, 193, 34, 78, 231, 166, 182, 154, 241, - 83, 52, 126, 122, 209, 42, 46, 240, 149, 146, 176, 116, 127, 154, 52, 12, 161, 109, 116, - 86, 247, 27, 39, 56, 50, 126, 131, 199, 133, 227, 157, 179, 189, 184, 138, 42, 120, 4, 42, - 42, 202, 228, 177, 162, 122, 133, 193, 95, 187, 89, 244, 61, 70, 52, 17, 246, 57, 189, 219, - 40, 236, 48, 33, 103, 68, 22, 87, 191, 96, 95, 225, 235, 53, 160, 117, 234, 26, 52, 96, - 234, 84, 26, 203, 175, 111, 180, 14, 213, 168, 136, 29, 90, 12, 72, 203, 90, 95, 69, 155, - 34, 20, 201, 73, 187, 152, 63, 239, 20, 57, 51, 23, 236, 38, 237, 204, 150, 165, 10, 66, - 85, - ], + include_bytes!("./__root_certs__/00C36B7C44B9AE1831.cer"), ]; #[cfg(test)] diff --git a/pallets/acurast/src/attestation/asn.rs b/pallets/acurast/src/attestation/asn.rs index a307629a..aef6851b 100644 --- a/pallets/acurast/src/attestation/asn.rs +++ b/pallets/acurast/src/attestation/asn.rs @@ -1,5 +1,7 @@ #![cfg_attr(all(feature = "alloc", not(feature = "std"), not(test)), no_std)] +use core::convert::TryInto; + use asn1::{ Asn1Read, Asn1Write, BitString, Enumerated, Null, ObjectIdentifier, SequenceOf, SetOf, Tlv, }; @@ -84,6 +86,15 @@ pub enum Time { GeneralizedTime(asn1::GeneralizedTime), } +impl Time { + pub fn timestamp_millis(&self) -> u64 { + match self { + Time::UTCTime(time) => time.as_chrono().timestamp_millis().try_into().unwrap(), + Time::GeneralizedTime(time) => time.as_chrono().timestamp_millis().try_into().unwrap(), + } + } +} + #[derive(Asn1Read, Asn1Write, Clone)] pub struct SubjectPublicKeyInfo<'a> { pub algorithm: AlgorithmIdentifier<'a>, diff --git a/pallets/acurast/src/lib.rs b/pallets/acurast/src/lib.rs index 00fd18af..678d54a3 100644 --- a/pallets/acurast/src/lib.rs +++ b/pallets/acurast/src/lib.rs @@ -1,26 +1,30 @@ #![cfg_attr(not(feature = "std"), no_std)] -pub mod attestation; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +mod attestation; +mod types; +mod utils; + pub use pallet::*; +pub use types::*; #[frame_support::pallet] pub mod pallet { - use core::convert::TryFrom; - use crate::attestation::{asn::KeyDescription, *}; - use codec::{Decode, Encode}; use frame_support::{ - dispatch::DispatchResultWithPostInfo, - ensure, - pallet_prelude::*, - sp_runtime::traits::{MaybeDisplay, StaticLookup}, - storage::bounded_vec::BoundedVec, - Blake2_128Concat, + dispatch::DispatchResultWithPostInfo, ensure, pallet_prelude::*, + sp_runtime::traits::StaticLookup, Blake2_128Concat, }; use frame_system::pallet_prelude::*; - use scale_info::TypeInfo; use sp_std::prelude::*; + use crate::types::*; + use crate::utils::*; + /// This trait provides the interface for a fulfillment router. pub trait FulfillmentRouter { fn received_fulfillment( @@ -52,71 +56,10 @@ pub mod pallet { #[pallet::without_storage_info] pub struct Pallet(_); - const SCRIPT_PREFIX: &'static [u8] = b"ipfs://"; - const SCRIPT_LENGTH: u32 = 53; - - /// Type representing the utf8 bytes of a string containing the value of an ipfs url. - /// The ipfs url is expected to point to a script. - pub type Script = BoundedVec>; - - /// Structure representing a job fulfillment. It contains the script that generated the payload and the actual payload. - #[derive(RuntimeDebug, Encode, Decode, TypeInfo, Clone, PartialEq)] - pub struct Fulfillment { - /// The script that generated the payload. - pub script: Script, - /// The output of a script. - pub payload: Vec, - } - - /// Structure representing a job registration. - #[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq)] - pub struct JobRegistration - where - A: Parameter + Member + MaybeSerializeDeserialize + MaybeDisplay + Ord + MaxEncodedLen, - T: Parameter + Member + MaxEncodedLen, - { - /// The script to execute. It is a vector of bytes representing a utf8 string. The string needs to be a ipfs url that points to the script. - pub script: Script, - /// An optional array of the [AccountId]s allowed to fulfill the job. If the array is [None], then all sources are allowed. - pub allowed_sources: Option>, - /// A boolean indicating if only verified sources can fulfill the job. A verified source is one that has provided a valid key attestation. - pub allow_only_verified_sources: bool, - /// Extra parameters. This type can be configured through [Config::RegistrationExtra]. - pub extra: T, - } - - /// Structure used to updated the allowed sources list of a [Registration]. - #[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq)] - pub struct AllowedSourcesUpdate - where - A: Parameter + Member + MaybeSerializeDeserialize + MaybeDisplay + Ord + MaxEncodedLen, - { - /// The update operation - pub operation: ListUpdateOperation, - /// The [AccountId] to add or remove. - pub account_id: A, - } - - /// Structure used to updated the certificate recovation list. - #[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq)] - pub struct CertificateRevocationListUpdate { - /// The update operation - pub operation: ListUpdateOperation, - /// The [AccountId] to add or remove. - pub cert_id: CertId, - } - - /// The allowed sources update operation. - #[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq, Copy)] - pub enum ListUpdateOperation { - Add, - Remove, - } - - /// The storage for [Registration]s. They are stored by [AccountId] and [Script]. + /// The storage for [JobRegistration]s. They are stored by [AccountId] and [Script]. #[pallet::storage] - #[pallet::getter(fn stored_registration)] - pub type StoredRegistration = StorageDoubleMap< + #[pallet::getter(fn stored_job_registration)] + pub type StoredJobRegistration = StorageDoubleMap< _, Blake2_128Concat, T::AccountId, @@ -125,6 +68,18 @@ pub mod pallet { JobRegistration, >; + /// The storage for [Attestation]s. They are stored by [AccountId]. + #[pallet::storage] + #[pallet::getter(fn stored_attestation)] + pub type StoredAttestation = + StorageMap<_, Blake2_128Concat, T::AccountId, Attestation>; + + /// Certificate revocation list storage. + #[pallet::storage] + #[pallet::getter(fn stored_revoked_certificate)] + pub type StoredRevokedCertificate = + StorageMap<_, Blake2_128Concat, SerialNumber, ()>; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { @@ -142,7 +97,7 @@ pub mod pallet { JobRegistration, T::AccountId, ), - /// The allowed sources have been updated. [who, old_registration, allowed_sources, operation] + /// The allowed sources have been updated. [who, old_registration, updates] AllowedSourcesUpdated( T::AccountId, JobRegistration, @@ -150,6 +105,8 @@ pub mod pallet { ), /// An attestation was successfully stored. [attestation, who] AttestationStored(Attestation, T::AccountId), + /// The certificate revocation list has been updated. [who, updates] + CertificateRecovationListUpdated(T::AccountId, Vec), } #[pallet::error] @@ -167,13 +124,15 @@ pub mod pallet { /// The provided script value is not valid. The value needs to be and ipfs:// url. InvalidScriptValue, /// The provided attestation could not be parsed or is invalid. - AttestationInvalid, + AttestationUsageExpired, /// The certificate chain provided in the submit_attestation call is not long enough. CertificateChainTooShort, /// The submitted attestation root certificate is not valid. RootCertificateValidationFailed, /// The submitted attestation certificate chain is not valid. CertificateChainValidationFailed, + /// The submitted attestation certificate is not valid + AttestationCertificateNotValid, /// Failed to extract the attestation. AttestationExtractionFailed, /// Cannot get the attestation issuer name. @@ -197,7 +156,7 @@ pub mod pallet { #[pallet::call] impl Pallet { - /// Registers a job by providing a [Registration]. If a job for the same script was previously registered, it will be overwritten. + /// Registers a job by providing a [JobRegistration]. If a job for the same script was previously registered, it will be overwritten. #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn register( origin: OriginFor, @@ -221,7 +180,7 @@ pub mod pallet { Error::::TooManyAllowedSources ); } - >::insert( + >::insert( who.clone(), (®istration).script.clone(), registration.clone(), @@ -234,12 +193,12 @@ pub mod pallet { #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn deregister(origin: OriginFor, script: Script) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - >::remove(who.clone(), script.clone()); + >::remove(who.clone(), script.clone()); Self::deposit_event(Event::JobRegistrationRemoved(script, who)); Ok(().into()) } - /// Updates the allowed sources list of a [Registration]. + /// Updates the allowed sources list of a [JobRegistration]. #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1, 1))] pub fn update_allowed_sources( origin: OriginFor, @@ -247,7 +206,7 @@ pub mod pallet { updates: Vec>, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - let registration = >::get(who.clone(), script.clone()) + let registration = >::get(who.clone(), script.clone()) .ok_or(Error::::JobRegistrationNotFound)?; let mut current_allowed_sources = @@ -266,12 +225,18 @@ pub mod pallet { _ => {} } } + let max_allowed_sources_len = T::MaxAllowedSources::get() as usize; + let allowed_sources_len = current_allowed_sources.len(); + ensure!( + allowed_sources_len <= max_allowed_sources_len, + Error::::TooManyAllowedSources + ); let allowed_sources = if current_allowed_sources.is_empty() { None } else { Some(current_allowed_sources) }; - >::insert( + >::insert( who.clone(), script.clone(), JobRegistration { @@ -298,7 +263,7 @@ pub mod pallet { let requester = T::Lookup::lookup(requester)?; let registration = - >::get(requester.clone(), (&fulfillment).script.clone()) + >::get(requester.clone(), (&fulfillment).script.clone()) .ok_or(Error::::JobRegistrationNotFound)?; ensure_source_allowed::(&who, ®istration)?; @@ -326,8 +291,6 @@ pub mod pallet { /// - If the represented chain is valid, the [Attestation] details are stored. An existing attestion for signing account gets overwritten. /// /// Revocation: Each atttestation is stored with the unique IDs of the certificates on the chain proofing the attestation's validity. - /// - /// TODO: implement revocation #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn submit_attestation( origin: OriginFor, @@ -339,35 +302,11 @@ pub mod pallet { Error::::CertificateChainTooShort, ); - validate_certificate_chain_root(&attestation_chain.certificate_chain) - .map_err(|_| Error::::RootCertificateValidationFailed)?; - - let (cert_ids, cert) = validate_certificate_chain(&attestation_chain.certificate_chain) - .map_err(|_| Error::::CertificateChainValidationFailed)?; - - let key_description = extract_attestation(cert.extensions) - .map_err(|_| Error::::AttestationExtractionFailed)?; + let attestation = validate_and_extract_attestation::(&attestation_chain)?; - let cert_ids_bounded = cert_ids - .into_iter() - .map(|cert_id| { - let (iss, sn) = cert_id; - let iss_bounded = IssuerName::try_from(iss) - .map_err(|_| Error::::CannotGetAttestationIssuerName)?; - let sn_bounded = SerialNumber::try_from(sn) - .map_err(|_| Error::::CannotGetAttestationSerialNumber)?; - Ok((iss_bounded, sn_bounded)) - }) - .collect::, Error>>()?; - let cert_ids_bounded_vec = ValidatingCertIds::try_from(cert_ids_bounded) - .map_err(|_| Error::::CannotGetCertificateId)?; + ensure_not_expired::(&attestation)?; + ensure_not_revoked::(&attestation)?; - let attestation = Attestation { - cert_ids: cert_ids_bounded_vec, - key_description: key_description - .try_into() - .map_err(|_| Error::::AttestationToBoundedTypeConversionFailed)?, - }; >::insert(who.clone(), attestation.clone()); Self::deposit_event(Event::AttestationStored(attestation, who)); Ok(().into()) @@ -376,740 +315,27 @@ pub mod pallet { #[pallet::weight(0)] pub fn update_certificate_revocation_list( origin: OriginFor, - update: CertificateRevocationListUpdate, + updates: Vec, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; if !T::AllowedRevocationListUpdate::get().contains(&who) { return Err(Error::::CertificateRevocationListUpdateNotAllowed)?; } - match &update.operation { - ListUpdateOperation::Add => { - >::insert(update.cert_id, ()); - } - ListUpdateOperation::Remove => { - >::remove(update.cert_id); + for update in &updates { + match &update.operation { + ListUpdateOperation::Add => { + >::insert( + update.cert_serial_number.clone(), + (), + ); + } + ListUpdateOperation::Remove => { + >::remove(&update.cert_serial_number); + } } } + Self::deposit_event(Event::CertificateRecovationListUpdated(who, updates)); Ok(().into()) } } - - fn ensure_source_allowed( - source: &T::AccountId, - registration: &JobRegistration, - ) -> Result<(), Error> { - registration - .allowed_sources - .as_ref() - .map(|allowed_sources| { - allowed_sources - .iter() - .position(|allowed_source| allowed_source == source) - .map(|_| ()) - .ok_or(Error::::FulfillSourceNotAllowed) - }) - .unwrap_or(Ok(()))?; - - if registration.allow_only_verified_sources { - let attestation = - >::get(source).ok_or(Error::::FulfillSourceNotVerified)?; - ensure_not_expired(&attestation)?; - ensure_not_revoked(&attestation)?; - } - - Ok(()) - } - - fn ensure_not_expired(attestation: &Attestation) -> Result<(), Error> { - let expire_date_time = (&attestation) - .key_description - .tee_enforced - .usage_expire_date_time - .or_else(|| { - (&attestation) - .key_description - .software_enforced - .usage_expire_date_time - }); - if let Some(expire_date_time) = expire_date_time { - let now: u64 = >::now() - .try_into() - .map_err(|_| Error::::FailedTimestampConversion)?; - if now >= expire_date_time { - return Err(Error::::FulfillSourceNotVerified); - } - } - Ok(()) - } - - fn ensure_not_revoked(attestation: &Attestation) -> Result<(), Error> { - let ids = &attestation.cert_ids; - for id in ids { - if >::get(id).is_some() { - return Err(Error::::RevokedCertificate); - } - } - Ok(()) - } - - /// The storage for [Attestation]s. They are stored by [AccountId]. - #[pallet::storage] - #[pallet::getter(fn stored_attestation)] - pub type StoredAttestation = - StorageMap<_, Blake2_128Concat, T::AccountId, Attestation>; - - #[pallet::storage] - #[pallet::getter(fn stored_revoked_certificate)] - pub type StoredRevokedCertificate = StorageMap<_, Blake2_128Concat, CertId, ()>; - - /// https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.2 - const ISSUER_NAME_MAX_LENGTH: u32 = 64; - const SERIAL_NUMBER_MAX_LENGTH: u32 = 20; - - pub type IssuerName = BoundedVec>; - pub type SerialNumber = BoundedVec>; - - const PURPOSE_MAX_LENGTH: u32 = 50; - const DIGEST_MAX_LENGTH: u32 = 32; - const PADDING_MAX_LENGTH: u32 = 32; - const MGF_DIGEST_MAX_LENGTH: u32 = 32; - const VERIFIED_BOOT_KEY_MAX_LENGTH: u32 = 32; - const VERIFIED_BOOT_HASH_MAX_LENGTH: u32 = 32; - const ATTESTATION_ID_MAX_LENGTH: u32 = 256; - const BOUDNED_SET_PROPERTY: u32 = 16; - - pub type Purpose = BoundedVec>; - pub type Digest = BoundedVec>; - pub type Padding = BoundedVec>; - pub type MgfDigest = BoundedVec>; - pub type VerifiedBootKey = BoundedVec>; - pub type VerifiedBootHash = BoundedVec>; - pub type AttestationIdProperty = BoundedVec>; - pub type CertId = (IssuerName, SerialNumber); - pub type ValidatingCertIds = BoundedVec>; - pub type BoundedSetProperty = BoundedVec>; - - /// Structure representing a submitted attestation chain. - #[derive(RuntimeDebug, Encode, Decode, TypeInfo, Clone, PartialEq)] - pub struct AttestationChain { - /// An ordered array of [CertificateInput]s describing a valid chain from known root certificate to attestation certificate. - pub certificate_chain: CertificateChainInput, - } - - /// Structure representing a stored attestation. - #[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq)] - pub struct Attestation { - pub cert_ids: ValidatingCertIds, - pub key_description: BoundedKeyDescription, - } - - #[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq)] - pub struct BoundedKeyDescription { - pub attestation_security_level: AttestationSecurityLevel, - pub key_mint_security_level: AttestationSecurityLevel, - pub software_enforced: BoundedAuthorizationList, - pub tee_enforced: BoundedAuthorizationList, - } - - impl TryFrom> for BoundedKeyDescription { - type Error = (); - - fn try_from(value: KeyDescription) -> Result { - match value { - KeyDescription::V1(kd) => kd.try_into(), - KeyDescription::V2(kd) => kd.try_into(), - KeyDescription::V3(kd) => kd.try_into(), - KeyDescription::V4(kd) => kd.try_into(), - KeyDescription::V100(kd) => kd.try_into(), - KeyDescription::V200(kd) => kd.try_into(), - } - } - } - - use crate::attestation::asn; - - impl TryFrom> for BoundedKeyDescription { - type Error = (); - - fn try_from(data: asn::KeyDescriptionV1) -> Result { - Ok(BoundedKeyDescription { - attestation_security_level: data.attestation_security_level.into(), - key_mint_security_level: data.key_mint_security_level.into(), - software_enforced: data.software_enforced.try_into()?, - tee_enforced: data.tee_enforced.try_into()?, - }) - } - } - - impl TryFrom> for BoundedKeyDescription { - type Error = (); - - fn try_from(data: asn::KeyDescriptionV2) -> Result { - Ok(BoundedKeyDescription { - attestation_security_level: data.attestation_security_level.into(), - key_mint_security_level: data.key_mint_security_level.into(), - software_enforced: data.software_enforced.try_into()?, - tee_enforced: data.tee_enforced.try_into()?, - }) - } - } - - impl TryFrom> for BoundedKeyDescription { - type Error = (); - - fn try_from(data: asn::KeyDescriptionV3) -> Result { - Ok(BoundedKeyDescription { - attestation_security_level: data.attestation_security_level.into(), - key_mint_security_level: data.key_mint_security_level.into(), - software_enforced: data.software_enforced.try_into()?, - tee_enforced: data.tee_enforced.try_into()?, - }) - } - } - - impl TryFrom> for BoundedKeyDescription { - type Error = (); - - fn try_from(data: asn::KeyDescriptionV4) -> Result { - Ok(BoundedKeyDescription { - attestation_security_level: data.attestation_security_level.into(), - key_mint_security_level: data.key_mint_security_level.into(), - software_enforced: data.software_enforced.try_into()?, - tee_enforced: data.tee_enforced.try_into()?, - }) - } - } - - impl TryFrom> for BoundedKeyDescription { - type Error = (); - - fn try_from(data: asn::KeyDescriptionV100V200) -> Result { - Ok(BoundedKeyDescription { - attestation_security_level: data.attestation_security_level.into(), - key_mint_security_level: data.key_mint_security_level.into(), - software_enforced: data.software_enforced.try_into()?, - tee_enforced: data.tee_enforced.try_into()?, - }) - } - } - - #[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq)] - pub enum AttestationSecurityLevel { - Software, - TrustedEnvironemnt, - StrongBox, - Unknown, - } - - impl From for AttestationSecurityLevel { - fn from(data: asn::SecurityLevel) -> Self { - match data.value() { - 0 => AttestationSecurityLevel::Software, - 1 => AttestationSecurityLevel::TrustedEnvironemnt, - 2 => AttestationSecurityLevel::StrongBox, - _ => AttestationSecurityLevel::Unknown, - } - } - } - - /// The Authorization List tags. [Tag descriptions](https://source.android.com/docs/security/keystore/tags) - #[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq)] - pub struct BoundedAuthorizationList { - pub purpose: Option, - pub algorithm: Option, - pub key_size: Option, - pub digest: Option, - pub padding: Option, - pub ec_curve: Option, - pub rsa_public_exponent: Option, - pub mgf_digest: Option, - pub rollback_resistance: Option, - pub early_boot_only: Option, - pub active_date_time: Option, - pub origination_expire_date_time: Option, - pub usage_expire_date_time: Option, - pub usage_count_limit: Option, - pub no_auth_required: bool, - pub user_auth_type: Option, - pub auth_timeout: Option, - pub allow_while_on_body: bool, - pub trusted_user_presence_required: Option, - pub trusted_confirmation_required: Option, - pub unlocked_device_required: Option, - pub all_applications: Option, - pub application_id: Option, - pub creation_date_time: Option, - pub origin: Option, - pub root_of_trust: Option, - pub os_version: Option, - pub os_patch_level: Option, - pub attestation_application_id: Option, - pub attestation_id_brand: Option, - pub attestation_id_device: Option, - pub attestation_id_product: Option, - pub attestation_id_serial: Option, - pub attestation_id_imei: Option, - pub attestation_id_meid: Option, - pub attestation_id_manufacturer: Option, - pub attestation_id_model: Option, - pub vendor_patch_level: Option, - pub boot_patch_level: Option, - pub device_unique_attestation: Option, - } - - macro_rules! try_bound_set { - ( $set:expr, $target_vec_type:ty, $target_type:ty ) => {{ - $set.map(|v| { - v.map(|i| <$target_type>::try_from(i)) - .collect::, _>>() - }) - .map_or(Ok(None), |r| r.map(Some)) - .map_err(|_| ())? - .map(|v| <$target_vec_type>::try_from(v)) - .map_or(Ok(None), |r| r.map(Some)) - }}; - } - - macro_rules! try_bound { - ( $v:expr, $target_type:ty ) => {{ - $v.map(|v| <$target_type>::try_from(v)) - .map_or(Ok(None), |r| r.map(Some)) - .map_err(|_| ()) - }}; - } - - impl TryFrom> for BoundedAuthorizationList { - type Error = (); - - fn try_from(data: asn::AuthorizationListV1) -> Result { - Ok(BoundedAuthorizationList { - purpose: try_bound_set!(data.purpose, Purpose, u8)?, - algorithm: try_bound!(data.algorithm, u8)?, - key_size: try_bound!(data.key_size, u16)?, - digest: try_bound_set!(data.digest, Digest, u8)?, - padding: try_bound_set!(data.padding, Padding, u8)?, - ec_curve: try_bound!(data.ec_curve, u8)?, - rsa_public_exponent: try_bound!(data.rsa_public_exponent, u64)?, - mgf_digest: None, - rollback_resistance: Some(data.rollback_resistance.is_some()), - early_boot_only: None, - active_date_time: try_bound!(data.active_date_time, u64)?, - origination_expire_date_time: try_bound!(data.origination_expire_date_time, u64)?, - usage_expire_date_time: try_bound!(data.usage_expire_date_time, u64)?, - usage_count_limit: None, - no_auth_required: data.no_auth_required.is_some(), - user_auth_type: try_bound!(data.user_auth_type, u8)?, - auth_timeout: try_bound!(data.user_auth_type, u32)?, - allow_while_on_body: data.allow_while_on_body.is_some(), - trusted_user_presence_required: None, - trusted_confirmation_required: None, - unlocked_device_required: None, - all_applications: Some(data.all_applications.is_some()), - application_id: data - .application_id - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - creation_date_time: try_bound!(data.creation_date_time, u64)?, - origin: try_bound!(data.origin, u8)?, - root_of_trust: data - .root_of_trust - .map(|v| v.try_into()) - .map_or(Ok(None), |r| r.map(Some))?, - os_version: try_bound!(data.os_version, u32)?, - os_patch_level: try_bound!(data.os_patch_level, u32)?, - vendor_patch_level: None, - attestation_application_id: None, - attestation_id_brand: None, - attestation_id_device: None, - attestation_id_product: None, - attestation_id_serial: None, - attestation_id_imei: None, - attestation_id_meid: None, - attestation_id_manufacturer: None, - attestation_id_model: None, - boot_patch_level: None, - device_unique_attestation: None, - }) - } - } - - impl TryFrom> for BoundedAuthorizationList { - type Error = (); - - fn try_from(data: asn::AuthorizationListV2) -> Result { - Ok(BoundedAuthorizationList { - purpose: try_bound_set!(data.purpose, Purpose, u8)?, - algorithm: try_bound!(data.algorithm, u8)?, - key_size: try_bound!(data.key_size, u16)?, - digest: try_bound_set!(data.digest, Digest, u8)?, - padding: try_bound_set!(data.padding, Padding, u8)?, - ec_curve: try_bound!(data.ec_curve, u8)?, - rsa_public_exponent: try_bound!(data.rsa_public_exponent, u64)?, - mgf_digest: None, - rollback_resistance: Some(data.rollback_resistance.is_some()), - early_boot_only: None, - active_date_time: try_bound!(data.active_date_time, u64)?, - origination_expire_date_time: try_bound!(data.origination_expire_date_time, u64)?, - usage_expire_date_time: try_bound!(data.usage_expire_date_time, u64)?, - usage_count_limit: None, - no_auth_required: data.no_auth_required.is_some(), - user_auth_type: try_bound!(data.user_auth_type, u8)?, - auth_timeout: try_bound!(data.user_auth_type, u32)?, - allow_while_on_body: data.allow_while_on_body.is_some(), - trusted_user_presence_required: None, - trusted_confirmation_required: None, - unlocked_device_required: None, - all_applications: Some(data.all_applications.is_some()), - application_id: data - .application_id - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - creation_date_time: try_bound!(data.creation_date_time, u64)?, - origin: try_bound!(data.origin, u8)?, - root_of_trust: data - .root_of_trust - .map(|v| v.try_into()) - .map_or(Ok(None), |r| r.map(Some))?, - os_version: try_bound!(data.os_version, u32)?, - os_patch_level: try_bound!(data.os_patch_level, u32)?, - attestation_application_id: data - .attestation_application_id - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_brand: data - .attestation_id_brand - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_device: data - .attestation_id_device - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_product: data - .attestation_id_product - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_serial: data - .attestation_id_serial - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_imei: data - .attestation_id_imei - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_meid: data - .attestation_id_meid - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_manufacturer: data - .attestation_id_manufacturer - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_model: data - .attestation_id_model - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - vendor_patch_level: None, - boot_patch_level: None, - device_unique_attestation: None, - }) - } - } - - impl TryFrom> for BoundedAuthorizationList { - type Error = (); - - fn try_from(data: asn::AuthorizationListV3) -> Result { - Ok(BoundedAuthorizationList { - purpose: try_bound_set!(data.purpose, Purpose, u8)?, - algorithm: try_bound!(data.algorithm, u8)?, - key_size: try_bound!(data.key_size, u16)?, - digest: try_bound_set!(data.digest, Digest, u8)?, - padding: try_bound_set!(data.padding, Padding, u8)?, - ec_curve: try_bound!(data.ec_curve, u8)?, - rsa_public_exponent: try_bound!(data.rsa_public_exponent, u64)?, - mgf_digest: None, - rollback_resistance: Some(data.rollback_resistance.is_some()), - early_boot_only: None, - active_date_time: try_bound!(data.active_date_time, u64)?, - origination_expire_date_time: try_bound!(data.origination_expire_date_time, u64)?, - usage_expire_date_time: try_bound!(data.usage_expire_date_time, u64)?, - usage_count_limit: None, - no_auth_required: data.no_auth_required.is_some(), - user_auth_type: try_bound!(data.user_auth_type, u8)?, - auth_timeout: try_bound!(data.user_auth_type, u32)?, - allow_while_on_body: data.allow_while_on_body.is_some(), - trusted_user_presence_required: None, - trusted_confirmation_required: None, - unlocked_device_required: None, - all_applications: Some(data.all_applications.is_some()), - application_id: data - .application_id - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - creation_date_time: try_bound!(data.creation_date_time, u64)?, - origin: try_bound!(data.origin, u8)?, - root_of_trust: data - .root_of_trust - .map(|v| v.try_into()) - .map_or(Ok(None), |r| r.map(Some))?, - os_version: try_bound!(data.os_version, u32)?, - os_patch_level: try_bound!(data.os_patch_level, u32)?, - attestation_application_id: data - .attestation_application_id - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_brand: data - .attestation_id_brand - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_device: data - .attestation_id_device - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_product: data - .attestation_id_product - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_serial: data - .attestation_id_serial - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_imei: data - .attestation_id_imei - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_meid: data - .attestation_id_meid - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_manufacturer: data - .attestation_id_manufacturer - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_model: data - .attestation_id_model - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - vendor_patch_level: try_bound!(data.vendor_patch_level, u32)?, - boot_patch_level: try_bound!(data.boot_patch_level, u32)?, - device_unique_attestation: None, - }) - } - } - - impl TryFrom> for BoundedAuthorizationList { - type Error = (); - - fn try_from(data: asn::AuthorizationListV4) -> Result { - Ok(BoundedAuthorizationList { - purpose: try_bound_set!(data.purpose, Purpose, u8)?, - algorithm: try_bound!(data.algorithm, u8)?, - key_size: try_bound!(data.key_size, u16)?, - digest: try_bound_set!(data.digest, Digest, u8)?, - padding: try_bound_set!(data.padding, Padding, u8)?, - ec_curve: try_bound!(data.ec_curve, u8)?, - rsa_public_exponent: try_bound!(data.rsa_public_exponent, u64)?, - mgf_digest: None, - rollback_resistance: Some(data.rollback_resistance.is_some()), - early_boot_only: Some(data.early_boot_only.is_some()), - active_date_time: try_bound!(data.active_date_time, u64)?, - origination_expire_date_time: try_bound!(data.origination_expire_date_time, u64)?, - usage_expire_date_time: try_bound!(data.usage_expire_date_time, u64)?, - usage_count_limit: None, - no_auth_required: data.no_auth_required.is_some(), - user_auth_type: try_bound!(data.user_auth_type, u8)?, - auth_timeout: try_bound!(data.user_auth_type, u32)?, - allow_while_on_body: data.allow_while_on_body.is_some(), - trusted_user_presence_required: Some(data.trusted_user_presence_required.is_some()), - trusted_confirmation_required: Some(data.trusted_confirmation_required.is_some()), - unlocked_device_required: Some(data.unlocked_device_required.is_some()), - all_applications: Some(data.all_applications.is_some()), - application_id: data - .application_id - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - creation_date_time: try_bound!(data.creation_date_time, u64)?, - origin: try_bound!(data.origin, u8)?, - root_of_trust: data - .root_of_trust - .map(|v| v.try_into()) - .map_or(Ok(None), |r| r.map(Some))?, - os_version: try_bound!(data.os_version, u32)?, - os_patch_level: try_bound!(data.os_patch_level, u32)?, - attestation_application_id: data - .attestation_application_id - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_brand: data - .attestation_id_brand - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_device: data - .attestation_id_device - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_product: data - .attestation_id_product - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_serial: data - .attestation_id_serial - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_imei: data - .attestation_id_imei - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_meid: data - .attestation_id_meid - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_manufacturer: data - .attestation_id_manufacturer - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_model: data - .attestation_id_model - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - vendor_patch_level: try_bound!(data.vendor_patch_level, u32)?, - boot_patch_level: try_bound!(data.boot_patch_level, u32)?, - device_unique_attestation: Some(data.device_unique_attestation.is_some()), - }) - } - } - - impl TryFrom> for BoundedAuthorizationList { - type Error = (); - - fn try_from(data: asn::AuthorizationListV100V200) -> Result { - Ok(BoundedAuthorizationList { - purpose: try_bound_set!(data.purpose, Purpose, u8)?, - algorithm: try_bound!(data.algorithm, u8)?, - key_size: try_bound!(data.key_size, u16)?, - digest: try_bound_set!(data.digest, Digest, u8)?, - padding: try_bound_set!(data.padding, Padding, u8)?, - ec_curve: try_bound!(data.ec_curve, u8)?, - rsa_public_exponent: try_bound!(data.rsa_public_exponent, u64)?, - mgf_digest: try_bound_set!(data.mgf_digest, MgfDigest, u8)?, - rollback_resistance: Some(data.rollback_resistance.is_some()), - early_boot_only: Some(data.early_boot_only.is_some()), - active_date_time: try_bound!(data.active_date_time, u64)?, - origination_expire_date_time: try_bound!(data.origination_expire_date_time, u64)?, - usage_expire_date_time: try_bound!(data.usage_expire_date_time, u64)?, - usage_count_limit: try_bound!(data.usage_count_limit, u64)?, - no_auth_required: data.no_auth_required.is_some(), - user_auth_type: try_bound!(data.user_auth_type, u8)?, - auth_timeout: try_bound!(data.user_auth_type, u32)?, - allow_while_on_body: data.allow_while_on_body.is_some(), - trusted_user_presence_required: Some(data.trusted_user_presence_required.is_some()), - trusted_confirmation_required: Some(data.trusted_confirmation_required.is_some()), - unlocked_device_required: Some(data.unlocked_device_required.is_some()), - all_applications: None, - application_id: None, - creation_date_time: try_bound!(data.creation_date_time, u64)?, - origin: try_bound!(data.origin, u8)?, - root_of_trust: data - .root_of_trust - .map(|v| v.try_into()) - .map_or(Ok(None), |r| r.map(Some))?, - os_version: try_bound!(data.os_version, u32)?, - os_patch_level: try_bound!(data.os_patch_level, u32)?, - attestation_application_id: data - .attestation_application_id - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_brand: data - .attestation_id_brand - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_device: data - .attestation_id_device - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_product: data - .attestation_id_product - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_serial: data - .attestation_id_serial - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_imei: data - .attestation_id_imei - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_meid: data - .attestation_id_meid - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_manufacturer: data - .attestation_id_manufacturer - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - attestation_id_model: data - .attestation_id_model - .map(|v| AttestationIdProperty::try_from(v.to_vec())) - .map_or(Ok(None), |r| r.map(Some))?, - vendor_patch_level: try_bound!(data.vendor_patch_level, u32)?, - boot_patch_level: try_bound!(data.boot_patch_level, u32)?, - device_unique_attestation: Some(data.device_unique_attestation.is_some()), - }) - } - } - - #[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq)] - pub struct BoundedRootOfTrust { - pub verified_boot_key: VerifiedBootKey, - pub device_locked: bool, - pub verified_boot_state: VerifiedBootState, - pub verified_boot_hash: Option, - } - - impl TryFrom> for BoundedRootOfTrust { - type Error = (); - - fn try_from(data: asn::RootOfTrustV1V2) -> Result { - Ok(BoundedRootOfTrust { - verified_boot_key: VerifiedBootKey::try_from(data.verified_boot_key.to_vec())?, - device_locked: data.device_locked, - verified_boot_state: data.verified_boot_state.into(), - verified_boot_hash: None, - }) - } - } - - impl TryFrom> for BoundedRootOfTrust { - type Error = (); - - fn try_from(data: asn::RootOfTrust) -> Result { - Ok(BoundedRootOfTrust { - verified_boot_key: VerifiedBootKey::try_from(data.verified_boot_key.to_vec())?, - device_locked: data.device_locked, - verified_boot_state: data.verified_boot_state.into(), - verified_boot_hash: Some(VerifiedBootHash::try_from( - data.verified_boot_hash.to_vec(), - )?), - }) - } - } - - #[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq)] - pub enum VerifiedBootState { - Verified, - SelfSigned, - Unverified, - Failed, - } - - impl From for VerifiedBootState { - fn from(data: asn::VerifiedBootState) -> Self { - match data.value() { - 0 => VerifiedBootState::Verified, - 1 => VerifiedBootState::SelfSigned, - 2 => VerifiedBootState::Unverified, - _ => VerifiedBootState::Failed, - } - } - } } diff --git a/pallets/acurast/src/mock.rs b/pallets/acurast/src/mock.rs new file mode 100644 index 00000000..2909a7af --- /dev/null +++ b/pallets/acurast/src/mock.rs @@ -0,0 +1,225 @@ +use hex_literal::hex; +use sp_io; +use sp_runtime::{testing::Header, traits::IdentityLookup}; + +use crate::{AttestationChain, Fulfillment, JobRegistration, Script, SerialNumber}; + +type AccountId = u64; +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + Acurast: crate::{Pallet, Call, Storage, Event}, + } +); + +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = BlockWeights; + type BlockLength = (); + type DbWeight = (); + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Call = Call; + type Hash = sp_core::H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = frame_support::traits::ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +frame_support::parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max(1024); + pub const MinimumPeriod: u64 = 6000; + pub AllowedRevocationListUpdate: Vec = vec![1]; + pub static ExistentialDeposit: u64 = 0; +} + +impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +impl crate::Config for Test { + type Event = Event; + type RegistrationExtra = (); + type FulfillmentRouter = Router; + type MaxAllowedSources = frame_support::traits::ConstU16<4>; + type AllowedRevocationListUpdate = AllowedRevocationListUpdate; +} + +pub struct Router; + +impl crate::FulfillmentRouter for Router { + fn received_fulfillment( + _origin: frame_system::pallet_prelude::OriginFor, + _from: ::AccountId, + _fulfillment: crate::Fulfillment, + _registration: crate::JobRegistration< + ::AccountId, + ::RegistrationExtra, + >, + _requester: <::Lookup as sp_runtime::traits::StaticLookup>::Target, + ) -> frame_support::pallet_prelude::DispatchResultWithPostInfo { + Ok(().into()) + } +} + +pub struct ExtBuilder; + +impl ExtBuilder { + pub fn build(self) -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext + } +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self {} + } +} + +pub fn events() -> Vec { + let evt = System::events() + .into_iter() + .map(|evt| evt.event) + .collect::>(); + + System::reset_events(); + + evt +} + +const SCRIPT_BYTES: [u8; 53] = hex!("697066733A2F2F00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + +pub fn script() -> Script { + SCRIPT_BYTES.to_vec().try_into().unwrap() +} + +pub fn invalid_script_1() -> Script { + let end = SCRIPT_BYTES.len() - 2; + SCRIPT_BYTES[0..end].to_vec().try_into().unwrap() +} + +pub fn invalid_script_2() -> Script { + let mut bytes = SCRIPT_BYTES.to_vec(); + bytes[0] = 0; + bytes.try_into().unwrap() +} + +pub fn job_registration( + allowed_sources: Option>, + allow_only_verified_sources: bool, +) -> JobRegistration { + JobRegistration { + script: script(), + allowed_sources, + allow_only_verified_sources, + extra: (), + } +} + +pub fn invalid_job_registration_1() -> JobRegistration { + JobRegistration { + script: invalid_script_1(), + allowed_sources: None, + allow_only_verified_sources: false, + extra: (), + } +} + +pub fn invalid_job_registration_2() -> JobRegistration { + JobRegistration { + script: invalid_script_2(), + allowed_sources: None, + allow_only_verified_sources: false, + extra: (), + } +} + +pub fn fulfillment_for(registration: &JobRegistration) -> Fulfillment { + Fulfillment { + script: registration.script.clone(), + payload: hex!("00").to_vec(), + } +} + +const ROOT_CERT: [u8; 1380] = hex!("3082056030820348a003020102020900e8fa196314d2fa18300d06092a864886f70d01010b0500301b311930170603550405131066393230303965383533623662303435301e170d3136303532363136323835325a170d3236303532343136323835325a301b31193017060355040513106639323030396538353362366230343530820222300d06092a864886f70d01010105000382020f003082020a0282020100afb6c7822bb1a701ec2bb42e8bcc541663abef982f32c77f7531030c97524b1b5fe809fbc72aa9451f743cbd9a6f1335744aa55e77f6b6ac3535ee17c25e639517dd9c92e6374a53cbfe258f8ffbb6fd129378a22a4ca99c452d47a59f3201f44197ca1ccd7e762fb2f53151b6feb2fffd2b6fe4fe5bc6bd9ec34bfe08239daafceb8eb5a8ed2b3acd9c5e3a7790e1b51442793159859811ad9eb2a96bbdd7a57c93a91c41fccd27d67fd6f671aa0b815261ad384fa37944864604ddb3d8c4f920a19b1656c2f14ad6d03c56ec060899041c1ed1a5fe6d3440b556bad1d0a152589c53e55d370762f0122eef91861b1b0e6c4c80927499c0e9bec0b83e3bc1f93c72c049604bbd2f1345e62c3f8e26dbec06c94766f3c128239d4f4312fad8123887e06becf567583bf8355a81feeabaf99a83c8df3e2a322afc672bf120b135158b6821ceaf309b6eee77f98833b018daa10e451f06a374d50781f359082966bb778b9308942698e74e0bcd24628a01c2cc03e51f0b3e5b4ac1e4df9eaf9ff6a492a77c1483882885015b422ce67b80b88c9b48e13b607ab545c723ff8c44f8f2d368b9f6520d31145ebf9e862ad71df6a3bfd2450959d653740d97a12f368b13ef66d5d0a54a6e2f5d9a6fef446832bc67844725861f093dd0e6f3405da89643ef0f4d69b6420051fdb93049673e36950580d3cdf4fbd08bc58483952600630203010001a381a63081a3301d0603551d0e041604143661e1007c880509518b446c47ff1a4cc9ea4f12301f0603551d230418301680143661e1007c880509518b446c47ff1a4cc9ea4f12300f0603551d130101ff040530030101ff300e0603551d0f0101ff04040302018630400603551d1f043930373035a033a031862f68747470733a2f2f616e64726f69642e676f6f676c65617069732e636f6d2f6174746573746174696f6e2f63726c2f300d06092a864886f70d01010b0500038202010020c8c38d4bdca9571b468c892fff72aac6f844a11d41a8f0736cc37d16d6426d8e7e9407044cea39e68b07c13dbf1503dd5c85bdafb2c02d5f6cdb4efa8127df8b04f182770fc4e7745b7fceaa87129a8801ce8e9bc0cb96379b4d26a82d30fd9c2f8eed6dc1be2f84b689e4d914258b144bbae624a1c70671132e2f0616a884b2a4d6a46ffa89b602bfbad80c1243711f56eb6056f637c8a0141cc54094268b8c3c7db994b35c0dcd6cb2abc2dafee252023d2dea0cd6c368bea3e6414886f6b1e58b5bd7c730b268c4e3c1fb6424b91febbdb80c586e2ae8368c84d5d10917bda2561789d4687393340e2e254f560ef64b2358fcdc0fbfc6700952e708bffcc627500c1f66e81ea17c098d7a2e9b18801b7ab4ac71587d345dcc8309d5b62a50427aa6d03dcb05996c96ba0c5d71e92162c016ca849ff35f0d52c65d05605a47f3ae917acd2df910efd2326688596ef69b3bf5fe3154f7aeb880a0a73ca04d94c2ce8317eeb43d5eff5883e336f5f249daaca4899237bf267e5c43ab02ea44162403723be6aa692c61bdae9ed409d463c4c97c64306577eef2bc7560b75715cc9c7dc67c86082db751a89c30349762b0782385875cf1a3c6166e0ae3c12d374e2d4f1846f318744bd879b587329bf018217a6c0c77241a4878e435c03079cb451289c5776206069a2f8d65f840e1445287bed877abae24e24435168d553ce4"); +const INT_CERT_1: [u8; 987] = hex!("308203d7308201bfa003020102020a038826676065899685f5300d06092a864886f70d01010b0500301b311930170603550405131066393230303965383533623662303435301e170d3139303830393233303332335a170d3239303830363233303332335a302f31193017060355040513103534663539333730353432663561393531123010060355040c0c095374726f6e67426f783076301006072a8648ce3d020106052b8104002203620004e352276f9bfcea4301a5f0427fa6478e573209ae44fd762cfbc57cbbd4713631509e802ea0e940536e54fa2570ca2846154698075509293b3100b3955b4317768b286bf6fe2651c59af6c6b0db3360090a4647c7860e76ecc3b8a7db5ce57acca381b63081b3301d0603551d0e041604146990b10c3b088aee2af88c3387b42c12dadfc3a6301f0603551d230418301680143661e1007c880509518b446c47ff1a4cc9ea4f12300f0603551d130101ff040530030101ff300e0603551d0f0101ff04040302020430500603551d1f044930473045a043a041863f68747470733a2f2f616e64726f69642e676f6f676c65617069732e636f6d2f6174746573746174696f6e2f63726c2f38463637333443394641353034373839300d06092a864886f70d01010b050003820201005c591327a0b0249ecadc949184c9651ed1f2a617a17516439875429e9bd21f87fd2365d0dcde747022c19410f23ab380fe1cef0f47aebc443c2a4531df3eca4101bf96d6bc30dfd878ed6734653111b5e782a03350cc2605e128b48a57e7ff1fe4bf4104de3f7ca9ace6afb01bdd9205fa10b91837a337257afb8290afa456fa629cfae5477b172b009bf28d43dcd4d31edcbf3dc1b6fcfcca5c38a79773d38b5a9d3ccd8152d51f25f9900701d9fb4fbf1307e17fcf5ddc759409863d2f0fb2e6c24468c9c5d85154e104318cb10ae60ba27bb252080e072645681c39e560e8586a64550867162f4bde9db75645882cb9eaff4efe1b0a312f5bd40224298c91f135061b8e04e8fa4c618c33f7b942c028f00d18113bfb6e55a952ccb5d71ee046f9bfdc85aa083e26d94be354545954b70c812ac4e326fdf07703bb79e536d429ff1d099c81722d81714593c7c2bb56740ccbc801332bb548695e28f2c8ac1452a260cfe57f311adc132e8dda01d638f9a4a31288a623a917f5b6c87e1c8316927129a0d11f384251d2df26b942a76844ab91968f4953e7484f2ecd2d6e187f9772d3b4584ac986e2079bc75f20773f8814ba2d16c7266761d6a3505f939fc316efda8787085a5d4f479df944f9d061d2c99acce73ed31770659297113f94140500306887be1b88082b96b18e123cabfcffbd79b68782a0408748cbf4f02f42"); +const INT_CERT_2: [u8; 564] = hex!("30820230308201b7a003020102020a15905857467176635834300a06082a8648ce3d040302302f31193017060355040513103534663539333730353432663561393531123010060355040c0c095374726f6e67426f78301e170d3139303732373031353231395a170d3239303732343031353231395a302f31193017060355040513103937333533373739333664306464373431123010060355040c0c095374726f6e67426f783059301306072a8648ce3d020106082a8648ce3d030107034200047639963abb7d336b5f238d8b355efdb395a22b2ccde67bda24328e4bbf802fefa97f204dd8bdb450332cb5e566f759bdc6ffafb9f3bc78e3747dfce8278e5f02a381ba3081b7301d0603551d0e04160414413e3ca9b34bc7a51cbb0125c0421be651ad7ad8301f0603551d230418301680146990b10c3b088aee2af88c3387b42c12dadfc3a6300f0603551d130101ff040530030101ff300e0603551d0f0101ff04040302020430540603551d1f044d304b3049a047a045864368747470733a2f2f616e64726f69642e676f6f676c65617069732e636f6d2f6174746573746174696f6e2f63726c2f3135393035383537343637313736363335383334300a06082a8648ce3d0403020367003064023017a0df3880a22ea1d4b3dfbdb6c04a4e5655d0ba70bdc8a5ac483b270c1e6d520cda9800b3ad775bae8dfccc7a86ecf802302898f95f24867bb3112f440db5dad27769e42be7db8dc51cf0b2af55aa43c11002e340a24f3965032f9a3a7c83c6bbdb"); +const LEAF_CERT: [u8; 672] = hex!("3082029c30820241a003020102020101300c06082a8648ce3d0403020500302f31193017060355040513103937333533373739333664306464373431123010060355040c0c095374726f6e67426f783022180f32303232303730393130353135355a180f32303238303532333233353935395a301f311d301b06035504030c14416e64726f6964204b657973746f7265204b65793059301306072a8648ce3d020106082a8648ce3d03010703420004b20c1d15477662623ecf430104898006e0f81c0db1bae87cb96a87c7777404659e585d3d9057b8a2ff8ae61f401a078fc75cf52c8c4268e810f93798c729e862a382015630820152300e0603551d0f0101ff0404030207803082013e060a2b06010401d6790201110482012e3082012a0201040a01020201290a0102040874657374617364660400306cbf853d0802060181e296611fbf85455c045a305831323030042b636f6d2e7562696e657469632e61747465737465642e6578656375746f722e746573742e746573746e657402010e31220420bdcb4560f6b3c41dad920668169c28be1ef9ea49f23d98cd8eb2f37ae4488ff93081a1a1053103020102a203020103a30402020100a5053103020100aa03020101bf8377020500bf853e03020100bf85404c304a0420879cd3f18ea76e244d4d4ac3bcb9c337c13b4667190b19035afe2536550050f10101ff0a010004203f4136ee3581e6aba8ea337a6b43d703de1eca241f9b7f277ecdfafff7a8dcf1bf854105020301d4c0bf85420502030315debf854e06020401348abdbf854f06020401348abd300c06082a8648ce3d04030205000347003044022033a613cce9a6ed25026a492b651f0ac67c3c0289d4e4743168c6903e2faa0bda0220324cd35c4bf2695d71ad12a28868e69232112922eaf0e3699f6add8133d528d9"); + +pub fn attestation_chain() -> AttestationChain { + AttestationChain { + certificate_chain: vec![ + ROOT_CERT.to_vec().try_into().unwrap(), + INT_CERT_1.to_vec().try_into().unwrap(), + INT_CERT_2.to_vec().try_into().unwrap(), + LEAF_CERT.to_vec().try_into().unwrap(), + ] + .try_into() + .unwrap(), + } +} + +pub fn invalid_attestation_chain_1() -> AttestationChain { + AttestationChain { + certificate_chain: vec![LEAF_CERT.to_vec().try_into().unwrap()] + .try_into() + .unwrap(), + } +} + +pub fn invalid_attestation_chain_2() -> AttestationChain { + AttestationChain { + certificate_chain: vec![ + INT_CERT_2.to_vec().try_into().unwrap(), + LEAF_CERT.to_vec().try_into().unwrap(), + ] + .try_into() + .unwrap(), + } +} + +pub fn invalid_attestation_chain_3() -> AttestationChain { + AttestationChain { + certificate_chain: vec![ + ROOT_CERT.to_vec().try_into().unwrap(), + INT_CERT_1.to_vec().try_into().unwrap(), + LEAF_CERT.to_vec().try_into().unwrap(), + ] + .try_into() + .unwrap(), + } +} + +pub fn cert_serial_number() -> SerialNumber { + hex!("15905857467176635834").to_vec().try_into().unwrap() +} diff --git a/pallets/acurast/src/tests.rs b/pallets/acurast/src/tests.rs new file mode 100644 index 00000000..e84ed3a8 --- /dev/null +++ b/pallets/acurast/src/tests.rs @@ -0,0 +1,563 @@ +#![cfg(test)] + +use crate::{ + mock::*, utils::validate_and_extract_attestation, AllowedSourcesUpdate, + CertificateRevocationListUpdate, Error, Fulfillment, ListUpdateOperation, SerialNumber, +}; +use frame_support::{assert_err, assert_ok}; +use hex_literal::hex; + +#[test] +fn test_job_registration() { + ExtBuilder::default().build().execute_with(|| { + let registration = job_registration(None, false); + assert_ok!(Acurast::register( + Origin::signed(1).into(), + registration.clone(), + )); + + assert_eq!( + Some(registration.clone()), + Acurast::stored_job_registration(1, registration.script.clone()) + ); + + assert_ok!(Acurast::deregister( + Origin::signed(1).into(), + registration.script.clone() + )); + + assert_eq!( + None, + Acurast::stored_job_registration(1, registration.script.clone()) + ); + + assert_eq!( + events(), + [ + Event::Acurast(crate::Event::JobRegistrationStored(registration.clone(), 1)), + Event::Acurast(crate::Event::JobRegistrationRemoved(registration.script, 1)) + ] + ); + }); +} + +#[test] +fn test_job_registration_failure_1() { + ExtBuilder::default().build().execute_with(|| { + let registration = invalid_job_registration_1(); + assert_err!( + Acurast::register(Origin::signed(1).into(), registration.clone()), + Error::::InvalidScriptValue + ); + + assert_eq!( + None, + Acurast::stored_job_registration(1, registration.script) + ); + + assert_eq!(events(), []); + }); +} + +#[test] +fn test_job_registration_failure_2() { + ExtBuilder::default().build().execute_with(|| { + let registration = invalid_job_registration_2(); + assert_err!( + Acurast::register(Origin::signed(1).into(), registration.clone()), + Error::::InvalidScriptValue + ); + + assert_eq!( + None, + Acurast::stored_job_registration(1, registration.script) + ); + + assert_eq!(events(), []); + }); +} + +#[test] +fn test_job_registration_failure_3() { + ExtBuilder::default().build().execute_with(|| { + let registration_1 = job_registration(Some(vec![1, 2, 3, 4, 12]), false); + let registration_2 = job_registration(Some(vec![]), false); + assert_err!( + Acurast::register(Origin::signed(1).into(), registration_1.clone()), + Error::::TooManyAllowedSources + ); + + assert_eq!( + None, + Acurast::stored_job_registration(1, registration_1.script) + ); + + assert_err!( + Acurast::register(Origin::signed(1).into(), registration_2.clone()), + Error::::TooFewAllowedSources + ); + + assert_eq!( + None, + Acurast::stored_job_registration(1, registration_2.script) + ); + + assert_eq!(events(), []); + }); +} + +#[test] +fn test_update_allowed_sources() { + ExtBuilder::default().build().execute_with(|| { + let registration_1 = job_registration(None, false); + let registration_2 = job_registration(Some(vec![1, 2]), false); + let updates_1 = vec![ + AllowedSourcesUpdate { + operation: ListUpdateOperation::Add, + account_id: 1, + }, + AllowedSourcesUpdate { + operation: ListUpdateOperation::Add, + account_id: 2, + }, + ]; + let updates_2 = vec![ + AllowedSourcesUpdate { + operation: ListUpdateOperation::Remove, + account_id: 1, + }, + AllowedSourcesUpdate { + operation: ListUpdateOperation::Remove, + account_id: 2, + }, + ]; + assert_ok!(Acurast::register( + Origin::signed(1).into(), + registration_1.clone(), + )); + + assert_ok!(Acurast::update_allowed_sources( + Origin::signed(1).into(), + registration_1.script.clone(), + updates_1.clone() + )); + + assert_eq!( + Some(registration_2.clone()), + Acurast::stored_job_registration(1, ®istration_1.script) + ); + + assert_ok!(Acurast::update_allowed_sources( + Origin::signed(1).into(), + registration_1.script.clone(), + updates_2.clone() + )); + + assert_eq!( + Some(registration_1.clone()), + Acurast::stored_job_registration(1, ®istration_1.script) + ); + + assert_eq!( + events(), + [ + Event::Acurast(crate::Event::JobRegistrationStored( + registration_1.clone(), + 1 + )), + Event::Acurast(crate::Event::AllowedSourcesUpdated( + 1, + registration_1, + updates_1 + )), + Event::Acurast(crate::Event::AllowedSourcesUpdated( + 1, + registration_2, + updates_2 + )) + ] + ); + }); +} + +#[test] +fn test_update_allowed_sources_failure() { + let registration = job_registration(Some(vec![1, 2, 3, 4]), false); + let updates = vec![AllowedSourcesUpdate { + operation: ListUpdateOperation::Add, + account_id: 12, + }]; + ExtBuilder::default().build().execute_with(|| { + assert_ok!(Acurast::register( + Origin::signed(1).into(), + registration.clone(), + )); + + assert_err!( + Acurast::update_allowed_sources( + Origin::signed(1).into(), + registration.script.clone(), + updates.clone() + ), + Error::::TooManyAllowedSources + ); + + assert_eq!( + Some(registration.clone()), + Acurast::stored_job_registration(1, ®istration.script) + ); + + assert_eq!( + events(), + [Event::Acurast(crate::Event::JobRegistrationStored( + registration.clone(), + 1 + )),] + ); + }); +} + +#[test] +fn test_fulfill() { + let registration = job_registration(None, false); + let fulfillment = Fulfillment { + script: registration.script.clone(), + payload: hex!("00").to_vec(), + }; + ExtBuilder::default().build().execute_with(|| { + assert_ok!(Acurast::register( + Origin::signed(1).into(), + registration.clone(), + )); + assert_ok!(Acurast::fulfill( + Origin::signed(2).into(), + fulfillment.clone(), + 1 + )); + + assert_eq!( + events(), + [ + Event::Acurast(crate::Event::JobRegistrationStored(registration.clone(), 1)), + Event::Acurast(crate::Event::ReceivedFulfillment( + 2, + fulfillment, + registration, + 1 + )), + ] + ); + }); +} + +#[test] +fn test_fulfill_failure_1() { + let fulfillment = Fulfillment { + script: script(), + payload: hex!("00").to_vec(), + }; + ExtBuilder::default().build().execute_with(|| { + assert_err!( + Acurast::fulfill(Origin::signed(2).into(), fulfillment.clone(), 1), + Error::::JobRegistrationNotFound + ); + + assert_eq!(events(), []); + }); +} + +#[test] +fn test_fulfill_failure_2() { + let registration = job_registration(None, true); + let fulfillment = fulfillment_for(®istration); + ExtBuilder::default().build().execute_with(|| { + assert_ok!(Acurast::register( + Origin::signed(1).into(), + registration.clone(), + )); + assert_err!( + Acurast::fulfill(Origin::signed(2).into(), fulfillment.clone(), 1), + Error::::FulfillSourceNotVerified + ); + + assert_eq!( + events(), + [Event::Acurast(crate::Event::JobRegistrationStored( + registration.clone(), + 1 + ))] + ); + }); +} + +#[test] +fn test_fulfill_failure_3() { + let registration = job_registration(Some(vec![3]), false); + let fulfillment = fulfillment_for(®istration); + ExtBuilder::default().build().execute_with(|| { + assert_ok!(Acurast::register( + Origin::signed(1).into(), + registration.clone(), + )); + assert_err!( + Acurast::fulfill(Origin::signed(2).into(), fulfillment.clone(), 1), + Error::::FulfillSourceNotAllowed + ); + + assert_eq!( + events(), + [Event::Acurast(crate::Event::JobRegistrationStored( + registration.clone(), + 1 + ))] + ); + }); +} + +#[test] +fn test_submit_attestation() { + ExtBuilder::default().build().execute_with(|| { + let chain = attestation_chain(); + _ = Timestamp::set(Origin::none(), 1657363915001); + assert_ok!(Acurast::submit_attestation( + Origin::signed(1).into(), + chain.clone() + )); + + let attestation = validate_and_extract_attestation::(&chain).unwrap(); + + assert_eq!(Some(attestation.clone()), Acurast::stored_attestation(1)); + + assert_eq!( + events(), + [Event::Acurast(crate::Event::AttestationStored( + attestation, + 1 + ))] + ); + }); +} + +#[test] +fn test_submit_attestation_register_fulfill() { + ExtBuilder::default().build().execute_with(|| { + let chain = attestation_chain(); + let registration = job_registration(None, true); + let fulfillment = fulfillment_for(®istration); + + _ = Timestamp::set(Origin::none(), 1657363915001); + assert_ok!(Acurast::submit_attestation( + Origin::signed(1).into(), + chain.clone() + )); + assert_ok!(Acurast::register( + Origin::signed(2).into(), + registration.clone() + )); + assert_ok!(Acurast::fulfill(Origin::signed(1), fulfillment.clone(), 2)); + + let attestation = validate_and_extract_attestation::(&chain).unwrap(); + + assert_eq!( + events(), + [ + Event::Acurast(crate::Event::AttestationStored(attestation, 1)), + Event::Acurast(crate::Event::JobRegistrationStored(registration.clone(), 2)), + Event::Acurast(crate::Event::ReceivedFulfillment( + 1, + fulfillment, + registration, + 2 + )), + ] + ); + }); +} + +#[test] +fn test_submit_attestation_failure_1() { + ExtBuilder::default().build().execute_with(|| { + let chain = invalid_attestation_chain_1(); + + assert_err!( + Acurast::submit_attestation(Origin::signed(1).into(), chain.clone()), + Error::::CertificateChainTooShort + ); + + assert_eq!(None, Acurast::stored_attestation(1)); + + let chain = invalid_attestation_chain_2(); + + assert_err!( + Acurast::submit_attestation(Origin::signed(1).into(), chain.clone()), + Error::::RootCertificateValidationFailed + ); + + assert_eq!(None, Acurast::stored_attestation(1)); + + let chain = invalid_attestation_chain_3(); + + assert_err!( + Acurast::submit_attestation(Origin::signed(1).into(), chain.clone()), + Error::::CertificateChainValidationFailed + ); + + assert_eq!(None, Acurast::stored_attestation(1)); + + assert_eq!(events(), []); + }); +} + +#[test] +fn test_submit_attestation_failure_2() { + ExtBuilder::default().build().execute_with(|| { + let chain = attestation_chain(); + + _ = Timestamp::set(Origin::none(), 1657363914000); + assert_err!( + Acurast::submit_attestation(Origin::signed(1).into(), chain.clone()), + Error::::AttestationCertificateNotValid + ); + + assert_eq!(None, Acurast::stored_attestation(1)); + + assert_eq!(events(), []); + }); +} + +#[test] +fn test_submit_attestation_failure_3() { + ExtBuilder::default().build().execute_with(|| { + let chain = attestation_chain(); + + _ = Timestamp::set(Origin::none(), 1842739199001); + assert_err!( + Acurast::submit_attestation(Origin::signed(1).into(), chain.clone()), + Error::::AttestationCertificateNotValid + ); + + assert_eq!(None, Acurast::stored_attestation(1)); + + assert_eq!(events(), []); + }); +} + +#[test] +fn test_update_revocation_list() { + ExtBuilder::default().build().execute_with(|| { + let updates_1 = vec![CertificateRevocationListUpdate { + operation: ListUpdateOperation::Add, + cert_serial_number: cert_serial_number(), + }]; + assert_ok!(Acurast::update_certificate_revocation_list( + Origin::signed(1).into(), + updates_1.clone(), + )); + assert_eq!( + Some(()), + Acurast::stored_revoked_certificate::(cert_serial_number()) + ); + + let updates_2 = vec![CertificateRevocationListUpdate { + operation: ListUpdateOperation::Remove, + cert_serial_number: cert_serial_number(), + }]; + assert_ok!(Acurast::update_certificate_revocation_list( + Origin::signed(1).into(), + updates_2.clone(), + )); + assert_eq!( + None, + Acurast::stored_revoked_certificate::(cert_serial_number()) + ); + + assert_err!( + Acurast::update_certificate_revocation_list( + Origin::signed(2).into(), + updates_1.clone(), + ), + Error::::CertificateRevocationListUpdateNotAllowed + ); + assert_eq!( + None, + Acurast::stored_revoked_certificate::(cert_serial_number()) + ); + + assert_eq!( + events(), + [ + Event::Acurast(crate::Event::CertificateRecovationListUpdated(1, updates_1)), + Event::Acurast(crate::Event::CertificateRecovationListUpdated(1, updates_2)) + ] + ); + }); +} + +#[test] +fn test_update_revocation_list_submit_attestation() { + ExtBuilder::default().build().execute_with(|| { + let updates = vec![CertificateRevocationListUpdate { + operation: ListUpdateOperation::Add, + cert_serial_number: cert_serial_number(), + }]; + assert_ok!(Acurast::update_certificate_revocation_list( + Origin::signed(1).into(), + updates.clone(), + )); + + let chain = attestation_chain(); + _ = Timestamp::set(Origin::none(), 1657363915001); + assert_err!( + Acurast::submit_attestation(Origin::signed(1).into(), chain.clone()), + Error::::RevokedCertificate + ); + + assert_eq!( + events(), + [Event::Acurast( + crate::Event::CertificateRecovationListUpdated(1, updates) + ),] + ); + }); +} + +#[test] +fn test_update_revocation_list_fulfill() { + ExtBuilder::default().build().execute_with(|| { + let updates = vec![CertificateRevocationListUpdate { + operation: ListUpdateOperation::Add, + cert_serial_number: cert_serial_number(), + }]; + let chain = attestation_chain(); + let registration = job_registration(None, true); + let fulfillment = fulfillment_for(®istration); + _ = Timestamp::set(Origin::none(), 1657363915001); + assert_ok!(Acurast::submit_attestation( + Origin::signed(1).into(), + chain.clone() + )); + assert_ok!(Acurast::update_certificate_revocation_list( + Origin::signed(1).into(), + updates.clone(), + )); + assert_ok!(Acurast::register( + Origin::signed(2).into(), + registration.clone() + )); + assert_err!( + Acurast::fulfill(Origin::signed(1), fulfillment.clone(), 2), + Error::::RevokedCertificate + ); + + let attestation = validate_and_extract_attestation::(&chain).unwrap(); + + assert_eq!( + events(), + [ + Event::Acurast(crate::Event::AttestationStored(attestation, 1)), + Event::Acurast(crate::Event::CertificateRecovationListUpdated(1, updates)), + Event::Acurast(crate::Event::JobRegistrationStored(registration.clone(), 2)), + ] + ); + }); +} diff --git a/pallets/acurast/src/types.rs b/pallets/acurast/src/types.rs new file mode 100644 index 00000000..4ebe1ab2 --- /dev/null +++ b/pallets/acurast/src/types.rs @@ -0,0 +1,725 @@ +use frame_support::{ + pallet_prelude::*, sp_runtime::traits::MaybeDisplay, storage::bounded_vec::BoundedVec, +}; + +use crate::attestation::{ + asn::{self, KeyDescription}, + CertificateChainInput, CHAIN_MAX_LENGTH, +}; + +pub(crate) const SCRIPT_PREFIX: &'static [u8] = b"ipfs://"; +pub(crate) const SCRIPT_LENGTH: u32 = 53; + +/// Type representing the utf8 bytes of a string containing the value of an ipfs url. +/// The ipfs url is expected to point to a script. +pub type Script = BoundedVec>; + +/// https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.2 +const ISSUER_NAME_MAX_LENGTH: u32 = 64; +const SERIAL_NUMBER_MAX_LENGTH: u32 = 20; + +pub type IssuerName = BoundedVec>; +pub type SerialNumber = BoundedVec>; + +/// Structure representing a job fulfillment. It contains the script that generated the payload and the actual payload. +#[derive(RuntimeDebug, Encode, Decode, TypeInfo, Clone, PartialEq)] +pub struct Fulfillment { + /// The script that generated the payload. + pub script: Script, + /// The output of a script. + pub payload: Vec, +} + +/// Structure used to updated the allowed sources list of a [Registration]. +#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq)] +pub struct AllowedSourcesUpdate +where + A: Parameter + Member + MaybeSerializeDeserialize + MaybeDisplay + Ord + MaxEncodedLen, +{ + /// The update operation + pub operation: ListUpdateOperation, + /// The [AccountId] to add or remove. + pub account_id: A, +} + +/// Structure used to updated the certificate recovation list. +#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq)] +pub struct CertificateRevocationListUpdate { + /// The update operation + pub operation: ListUpdateOperation, + /// The [AccountId] to add or remove. + pub cert_serial_number: SerialNumber, +} + +/// The allowed sources update operation. +#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq, Copy)] +pub enum ListUpdateOperation { + Add, + Remove, +} + +/// Structure representing a job registration. +#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq)] +pub struct JobRegistration +where + A: Parameter + Member + MaybeSerializeDeserialize + MaybeDisplay + Ord + MaxEncodedLen, + T: Parameter + Member + MaxEncodedLen, +{ + /// The script to execute. It is a vector of bytes representing a utf8 string. The string needs to be a ipfs url that points to the script. + pub script: Script, + /// An optional array of the [AccountId]s allowed to fulfill the job. If the array is [None], then all sources are allowed. + pub allowed_sources: Option>, + /// A boolean indicating if only verified sources can fulfill the job. A verified source is one that has provided a valid key attestation. + pub allow_only_verified_sources: bool, + /// Extra parameters. This type can be configured through [Config::RegistrationExtra]. + pub extra: T, +} + +pub(crate) const PURPOSE_MAX_LENGTH: u32 = 50; +pub(crate) const DIGEST_MAX_LENGTH: u32 = 32; +pub(crate) const PADDING_MAX_LENGTH: u32 = 32; +pub(crate) const MGF_DIGEST_MAX_LENGTH: u32 = 32; +pub(crate) const VERIFIED_BOOT_KEY_MAX_LENGTH: u32 = 32; +pub(crate) const VERIFIED_BOOT_HASH_MAX_LENGTH: u32 = 32; +pub(crate) const ATTESTATION_ID_MAX_LENGTH: u32 = 256; +pub(crate) const BOUDNED_SET_PROPERTY: u32 = 16; + +pub type Purpose = BoundedVec>; +pub type Digest = BoundedVec>; +pub type Padding = BoundedVec>; +pub type MgfDigest = BoundedVec>; +pub type VerifiedBootKey = BoundedVec>; +pub type VerifiedBootHash = BoundedVec>; +pub type AttestationIdProperty = BoundedVec>; +pub type CertId = (IssuerName, SerialNumber); +pub type ValidatingCertIds = BoundedVec>; +pub type BoundedSetProperty = BoundedVec>; + +/// Structure representing a submitted attestation chain. +#[derive(RuntimeDebug, Encode, Decode, TypeInfo, Clone, PartialEq)] +pub struct AttestationChain { + /// An ordered array of [CertificateInput]s describing a valid chain from known root certificate to attestation certificate. + pub certificate_chain: CertificateChainInput, +} + +/// Structure representing a stored attestation. +#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq)] +pub struct Attestation { + pub cert_ids: ValidatingCertIds, + pub key_description: BoundedKeyDescription, + pub validity: AttestationValidity, +} + +#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, Copy, PartialEq)] +pub struct AttestationValidity { + pub not_before: u64, + pub not_after: u64, +} + +#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq)] +pub struct BoundedKeyDescription { + pub attestation_security_level: AttestationSecurityLevel, + pub key_mint_security_level: AttestationSecurityLevel, + pub software_enforced: BoundedAuthorizationList, + pub tee_enforced: BoundedAuthorizationList, +} + +impl TryFrom> for BoundedKeyDescription { + type Error = (); + + fn try_from(value: KeyDescription) -> Result { + match value { + KeyDescription::V1(kd) => kd.try_into(), + KeyDescription::V2(kd) => kd.try_into(), + KeyDescription::V3(kd) => kd.try_into(), + KeyDescription::V4(kd) => kd.try_into(), + KeyDescription::V100(kd) => kd.try_into(), + KeyDescription::V200(kd) => kd.try_into(), + } + } +} + +impl TryFrom> for BoundedKeyDescription { + type Error = (); + + fn try_from(data: asn::KeyDescriptionV1) -> Result { + Ok(BoundedKeyDescription { + attestation_security_level: data.attestation_security_level.into(), + key_mint_security_level: data.key_mint_security_level.into(), + software_enforced: data.software_enforced.try_into()?, + tee_enforced: data.tee_enforced.try_into()?, + }) + } +} + +impl TryFrom> for BoundedKeyDescription { + type Error = (); + + fn try_from(data: asn::KeyDescriptionV2) -> Result { + Ok(BoundedKeyDescription { + attestation_security_level: data.attestation_security_level.into(), + key_mint_security_level: data.key_mint_security_level.into(), + software_enforced: data.software_enforced.try_into()?, + tee_enforced: data.tee_enforced.try_into()?, + }) + } +} + +impl TryFrom> for BoundedKeyDescription { + type Error = (); + + fn try_from(data: asn::KeyDescriptionV3) -> Result { + Ok(BoundedKeyDescription { + attestation_security_level: data.attestation_security_level.into(), + key_mint_security_level: data.key_mint_security_level.into(), + software_enforced: data.software_enforced.try_into()?, + tee_enforced: data.tee_enforced.try_into()?, + }) + } +} + +impl TryFrom> for BoundedKeyDescription { + type Error = (); + + fn try_from(data: asn::KeyDescriptionV4) -> Result { + Ok(BoundedKeyDescription { + attestation_security_level: data.attestation_security_level.into(), + key_mint_security_level: data.key_mint_security_level.into(), + software_enforced: data.software_enforced.try_into()?, + tee_enforced: data.tee_enforced.try_into()?, + }) + } +} + +impl TryFrom> for BoundedKeyDescription { + type Error = (); + + fn try_from(data: asn::KeyDescriptionV100V200) -> Result { + Ok(BoundedKeyDescription { + attestation_security_level: data.attestation_security_level.into(), + key_mint_security_level: data.key_mint_security_level.into(), + software_enforced: data.software_enforced.try_into()?, + tee_enforced: data.tee_enforced.try_into()?, + }) + } +} + +#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq)] +pub enum AttestationSecurityLevel { + Software, + TrustedEnvironemnt, + StrongBox, + Unknown, +} + +impl From for AttestationSecurityLevel { + fn from(data: asn::SecurityLevel) -> Self { + match data.value() { + 0 => AttestationSecurityLevel::Software, + 1 => AttestationSecurityLevel::TrustedEnvironemnt, + 2 => AttestationSecurityLevel::StrongBox, + _ => AttestationSecurityLevel::Unknown, + } + } +} + +#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq)] +pub struct BoundedAuthorizationList { + pub purpose: Option, + pub algorithm: Option, + pub key_size: Option, + pub digest: Option, + pub padding: Option, + pub ec_curve: Option, + pub rsa_public_exponent: Option, + pub mgf_digest: Option, + pub rollback_resistance: Option, + pub early_boot_only: Option, + pub active_date_time: Option, + pub origination_expire_date_time: Option, + pub usage_expire_date_time: Option, + pub usage_count_limit: Option, + pub no_auth_required: bool, + pub user_auth_type: Option, + pub auth_timeout: Option, + pub allow_while_on_body: bool, + pub trusted_user_presence_required: Option, + pub trusted_confirmation_required: Option, + pub unlocked_device_required: Option, + pub all_applications: Option, + pub application_id: Option, + pub creation_date_time: Option, + pub origin: Option, + pub root_of_trust: Option, + pub os_version: Option, + pub os_patch_level: Option, + pub attestation_application_id: Option, + pub attestation_id_brand: Option, + pub attestation_id_device: Option, + pub attestation_id_product: Option, + pub attestation_id_serial: Option, + pub attestation_id_imei: Option, + pub attestation_id_meid: Option, + pub attestation_id_manufacturer: Option, + pub attestation_id_model: Option, + pub vendor_patch_level: Option, + pub boot_patch_level: Option, + pub device_unique_attestation: Option, +} + +macro_rules! try_bound_set { + ( $set:expr, $target_vec_type:ty, $target_type:ty ) => {{ + $set.map(|v| { + v.map(|i| <$target_type>::try_from(i)) + .collect::, _>>() + }) + .map_or(Ok(None), |r| r.map(Some)) + .map_err(|_| ())? + .map(|v| <$target_vec_type>::try_from(v)) + .map_or(Ok(None), |r| r.map(Some)) + }}; +} + +macro_rules! try_bound { + ( $v:expr, $target_type:ty ) => {{ + $v.map(|v| <$target_type>::try_from(v)) + .map_or(Ok(None), |r| r.map(Some)) + .map_err(|_| ()) + }}; +} + +/// The Authorization List tags. [Tag descriptions](https://source.android.com/docs/security/keystore/tags) +impl TryFrom> for BoundedAuthorizationList { + type Error = (); + + fn try_from(data: asn::AuthorizationListV1) -> Result { + Ok(BoundedAuthorizationList { + purpose: try_bound_set!(data.purpose, Purpose, u8)?, + algorithm: try_bound!(data.algorithm, u8)?, + key_size: try_bound!(data.key_size, u16)?, + digest: try_bound_set!(data.digest, Digest, u8)?, + padding: try_bound_set!(data.padding, Padding, u8)?, + ec_curve: try_bound!(data.ec_curve, u8)?, + rsa_public_exponent: try_bound!(data.rsa_public_exponent, u64)?, + mgf_digest: None, + rollback_resistance: Some(data.rollback_resistance.is_some()), + early_boot_only: None, + active_date_time: try_bound!(data.active_date_time, u64)?, + origination_expire_date_time: try_bound!(data.origination_expire_date_time, u64)?, + usage_expire_date_time: try_bound!(data.usage_expire_date_time, u64)?, + usage_count_limit: None, + no_auth_required: data.no_auth_required.is_some(), + user_auth_type: try_bound!(data.user_auth_type, u8)?, + auth_timeout: try_bound!(data.user_auth_type, u32)?, + allow_while_on_body: data.allow_while_on_body.is_some(), + trusted_user_presence_required: None, + trusted_confirmation_required: None, + unlocked_device_required: None, + all_applications: Some(data.all_applications.is_some()), + application_id: data + .application_id + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + creation_date_time: try_bound!(data.creation_date_time, u64)?, + origin: try_bound!(data.origin, u8)?, + root_of_trust: data + .root_of_trust + .map(|v| v.try_into()) + .map_or(Ok(None), |r| r.map(Some))?, + os_version: try_bound!(data.os_version, u32)?, + os_patch_level: try_bound!(data.os_patch_level, u32)?, + vendor_patch_level: None, + attestation_application_id: None, + attestation_id_brand: None, + attestation_id_device: None, + attestation_id_product: None, + attestation_id_serial: None, + attestation_id_imei: None, + attestation_id_meid: None, + attestation_id_manufacturer: None, + attestation_id_model: None, + boot_patch_level: None, + device_unique_attestation: None, + }) + } +} + +impl TryFrom> for BoundedAuthorizationList { + type Error = (); + + fn try_from(data: asn::AuthorizationListV2) -> Result { + Ok(BoundedAuthorizationList { + purpose: try_bound_set!(data.purpose, Purpose, u8)?, + algorithm: try_bound!(data.algorithm, u8)?, + key_size: try_bound!(data.key_size, u16)?, + digest: try_bound_set!(data.digest, Digest, u8)?, + padding: try_bound_set!(data.padding, Padding, u8)?, + ec_curve: try_bound!(data.ec_curve, u8)?, + rsa_public_exponent: try_bound!(data.rsa_public_exponent, u64)?, + mgf_digest: None, + rollback_resistance: Some(data.rollback_resistance.is_some()), + early_boot_only: None, + active_date_time: try_bound!(data.active_date_time, u64)?, + origination_expire_date_time: try_bound!(data.origination_expire_date_time, u64)?, + usage_expire_date_time: try_bound!(data.usage_expire_date_time, u64)?, + usage_count_limit: None, + no_auth_required: data.no_auth_required.is_some(), + user_auth_type: try_bound!(data.user_auth_type, u8)?, + auth_timeout: try_bound!(data.user_auth_type, u32)?, + allow_while_on_body: data.allow_while_on_body.is_some(), + trusted_user_presence_required: None, + trusted_confirmation_required: None, + unlocked_device_required: None, + all_applications: Some(data.all_applications.is_some()), + application_id: data + .application_id + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + creation_date_time: try_bound!(data.creation_date_time, u64)?, + origin: try_bound!(data.origin, u8)?, + root_of_trust: data + .root_of_trust + .map(|v| v.try_into()) + .map_or(Ok(None), |r| r.map(Some))?, + os_version: try_bound!(data.os_version, u32)?, + os_patch_level: try_bound!(data.os_patch_level, u32)?, + attestation_application_id: data + .attestation_application_id + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_brand: data + .attestation_id_brand + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_device: data + .attestation_id_device + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_product: data + .attestation_id_product + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_serial: data + .attestation_id_serial + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_imei: data + .attestation_id_imei + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_meid: data + .attestation_id_meid + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_manufacturer: data + .attestation_id_manufacturer + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_model: data + .attestation_id_model + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + vendor_patch_level: None, + boot_patch_level: None, + device_unique_attestation: None, + }) + } +} + +impl TryFrom> for BoundedAuthorizationList { + type Error = (); + + fn try_from(data: asn::AuthorizationListV3) -> Result { + Ok(BoundedAuthorizationList { + purpose: try_bound_set!(data.purpose, Purpose, u8)?, + algorithm: try_bound!(data.algorithm, u8)?, + key_size: try_bound!(data.key_size, u16)?, + digest: try_bound_set!(data.digest, Digest, u8)?, + padding: try_bound_set!(data.padding, Padding, u8)?, + ec_curve: try_bound!(data.ec_curve, u8)?, + rsa_public_exponent: try_bound!(data.rsa_public_exponent, u64)?, + mgf_digest: None, + rollback_resistance: Some(data.rollback_resistance.is_some()), + early_boot_only: None, + active_date_time: try_bound!(data.active_date_time, u64)?, + origination_expire_date_time: try_bound!(data.origination_expire_date_time, u64)?, + usage_expire_date_time: try_bound!(data.usage_expire_date_time, u64)?, + usage_count_limit: None, + no_auth_required: data.no_auth_required.is_some(), + user_auth_type: try_bound!(data.user_auth_type, u8)?, + auth_timeout: try_bound!(data.user_auth_type, u32)?, + allow_while_on_body: data.allow_while_on_body.is_some(), + trusted_user_presence_required: None, + trusted_confirmation_required: None, + unlocked_device_required: None, + all_applications: Some(data.all_applications.is_some()), + application_id: data + .application_id + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + creation_date_time: try_bound!(data.creation_date_time, u64)?, + origin: try_bound!(data.origin, u8)?, + root_of_trust: data + .root_of_trust + .map(|v| v.try_into()) + .map_or(Ok(None), |r| r.map(Some))?, + os_version: try_bound!(data.os_version, u32)?, + os_patch_level: try_bound!(data.os_patch_level, u32)?, + attestation_application_id: data + .attestation_application_id + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_brand: data + .attestation_id_brand + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_device: data + .attestation_id_device + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_product: data + .attestation_id_product + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_serial: data + .attestation_id_serial + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_imei: data + .attestation_id_imei + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_meid: data + .attestation_id_meid + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_manufacturer: data + .attestation_id_manufacturer + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_model: data + .attestation_id_model + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + vendor_patch_level: try_bound!(data.vendor_patch_level, u32)?, + boot_patch_level: try_bound!(data.boot_patch_level, u32)?, + device_unique_attestation: None, + }) + } +} + +impl TryFrom> for BoundedAuthorizationList { + type Error = (); + + fn try_from(data: asn::AuthorizationListV4) -> Result { + Ok(BoundedAuthorizationList { + purpose: try_bound_set!(data.purpose, Purpose, u8)?, + algorithm: try_bound!(data.algorithm, u8)?, + key_size: try_bound!(data.key_size, u16)?, + digest: try_bound_set!(data.digest, Digest, u8)?, + padding: try_bound_set!(data.padding, Padding, u8)?, + ec_curve: try_bound!(data.ec_curve, u8)?, + rsa_public_exponent: try_bound!(data.rsa_public_exponent, u64)?, + mgf_digest: None, + rollback_resistance: Some(data.rollback_resistance.is_some()), + early_boot_only: Some(data.early_boot_only.is_some()), + active_date_time: try_bound!(data.active_date_time, u64)?, + origination_expire_date_time: try_bound!(data.origination_expire_date_time, u64)?, + usage_expire_date_time: try_bound!(data.usage_expire_date_time, u64)?, + usage_count_limit: None, + no_auth_required: data.no_auth_required.is_some(), + user_auth_type: try_bound!(data.user_auth_type, u8)?, + auth_timeout: try_bound!(data.user_auth_type, u32)?, + allow_while_on_body: data.allow_while_on_body.is_some(), + trusted_user_presence_required: Some(data.trusted_user_presence_required.is_some()), + trusted_confirmation_required: Some(data.trusted_confirmation_required.is_some()), + unlocked_device_required: Some(data.unlocked_device_required.is_some()), + all_applications: Some(data.all_applications.is_some()), + application_id: data + .application_id + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + creation_date_time: try_bound!(data.creation_date_time, u64)?, + origin: try_bound!(data.origin, u8)?, + root_of_trust: data + .root_of_trust + .map(|v| v.try_into()) + .map_or(Ok(None), |r| r.map(Some))?, + os_version: try_bound!(data.os_version, u32)?, + os_patch_level: try_bound!(data.os_patch_level, u32)?, + attestation_application_id: data + .attestation_application_id + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_brand: data + .attestation_id_brand + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_device: data + .attestation_id_device + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_product: data + .attestation_id_product + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_serial: data + .attestation_id_serial + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_imei: data + .attestation_id_imei + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_meid: data + .attestation_id_meid + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_manufacturer: data + .attestation_id_manufacturer + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_model: data + .attestation_id_model + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + vendor_patch_level: try_bound!(data.vendor_patch_level, u32)?, + boot_patch_level: try_bound!(data.boot_patch_level, u32)?, + device_unique_attestation: Some(data.device_unique_attestation.is_some()), + }) + } +} + +impl TryFrom> for BoundedAuthorizationList { + type Error = (); + + fn try_from(data: asn::AuthorizationListV100V200) -> Result { + Ok(BoundedAuthorizationList { + purpose: try_bound_set!(data.purpose, Purpose, u8)?, + algorithm: try_bound!(data.algorithm, u8)?, + key_size: try_bound!(data.key_size, u16)?, + digest: try_bound_set!(data.digest, Digest, u8)?, + padding: try_bound_set!(data.padding, Padding, u8)?, + ec_curve: try_bound!(data.ec_curve, u8)?, + rsa_public_exponent: try_bound!(data.rsa_public_exponent, u64)?, + mgf_digest: try_bound_set!(data.mgf_digest, MgfDigest, u8)?, + rollback_resistance: Some(data.rollback_resistance.is_some()), + early_boot_only: Some(data.early_boot_only.is_some()), + active_date_time: try_bound!(data.active_date_time, u64)?, + origination_expire_date_time: try_bound!(data.origination_expire_date_time, u64)?, + usage_expire_date_time: try_bound!(data.usage_expire_date_time, u64)?, + usage_count_limit: try_bound!(data.usage_count_limit, u64)?, + no_auth_required: data.no_auth_required.is_some(), + user_auth_type: try_bound!(data.user_auth_type, u8)?, + auth_timeout: try_bound!(data.user_auth_type, u32)?, + allow_while_on_body: data.allow_while_on_body.is_some(), + trusted_user_presence_required: Some(data.trusted_user_presence_required.is_some()), + trusted_confirmation_required: Some(data.trusted_confirmation_required.is_some()), + unlocked_device_required: Some(data.unlocked_device_required.is_some()), + all_applications: None, + application_id: None, + creation_date_time: try_bound!(data.creation_date_time, u64)?, + origin: try_bound!(data.origin, u8)?, + root_of_trust: data + .root_of_trust + .map(|v| v.try_into()) + .map_or(Ok(None), |r| r.map(Some))?, + os_version: try_bound!(data.os_version, u32)?, + os_patch_level: try_bound!(data.os_patch_level, u32)?, + attestation_application_id: data + .attestation_application_id + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_brand: data + .attestation_id_brand + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_device: data + .attestation_id_device + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_product: data + .attestation_id_product + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_serial: data + .attestation_id_serial + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_imei: data + .attestation_id_imei + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_meid: data + .attestation_id_meid + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_manufacturer: data + .attestation_id_manufacturer + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + attestation_id_model: data + .attestation_id_model + .map(|v| AttestationIdProperty::try_from(v.to_vec())) + .map_or(Ok(None), |r| r.map(Some))?, + vendor_patch_level: try_bound!(data.vendor_patch_level, u32)?, + boot_patch_level: try_bound!(data.boot_patch_level, u32)?, + device_unique_attestation: Some(data.device_unique_attestation.is_some()), + }) + } +} + +#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq)] +pub struct BoundedRootOfTrust { + pub verified_boot_key: VerifiedBootKey, + pub device_locked: bool, + pub verified_boot_state: VerifiedBootState, + pub verified_boot_hash: Option, +} + +impl TryFrom> for BoundedRootOfTrust { + type Error = (); + + fn try_from(data: asn::RootOfTrustV1V2) -> Result { + Ok(BoundedRootOfTrust { + verified_boot_key: VerifiedBootKey::try_from(data.verified_boot_key.to_vec())?, + device_locked: data.device_locked, + verified_boot_state: data.verified_boot_state.into(), + verified_boot_hash: None, + }) + } +} + +impl TryFrom> for BoundedRootOfTrust { + type Error = (); + + fn try_from(data: asn::RootOfTrust) -> Result { + Ok(BoundedRootOfTrust { + verified_boot_key: VerifiedBootKey::try_from(data.verified_boot_key.to_vec())?, + device_locked: data.device_locked, + verified_boot_state: data.verified_boot_state.into(), + verified_boot_hash: Some(VerifiedBootHash::try_from( + data.verified_boot_hash.to_vec(), + )?), + }) + } +} + +#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq)] +pub enum VerifiedBootState { + Verified, + SelfSigned, + Unverified, + Failed, +} + +impl From for VerifiedBootState { + fn from(data: asn::VerifiedBootState) -> Self { + match data.value() { + 0 => VerifiedBootState::Verified, + 1 => VerifiedBootState::SelfSigned, + 2 => VerifiedBootState::Unverified, + _ => VerifiedBootState::Failed, + } + } +} diff --git a/pallets/acurast/src/utils.rs b/pallets/acurast/src/utils.rs new file mode 100644 index 00000000..06f94c32 --- /dev/null +++ b/pallets/acurast/src/utils.rs @@ -0,0 +1,109 @@ +use crate::attestation::{ + extract_attestation, validate_certificate_chain, validate_certificate_chain_root, +}; +use crate::{ + Attestation, AttestationChain, AttestationValidity, CertId, Config, Error, IssuerName, + JobRegistration, SerialNumber, StoredAttestation, StoredRevokedCertificate, ValidatingCertIds, +}; + +pub(crate) fn validate_and_extract_attestation( + attestation_chain: &AttestationChain, +) -> Result> { + validate_certificate_chain_root(&attestation_chain.certificate_chain) + .map_err(|_| Error::::RootCertificateValidationFailed)?; + + let (cert_ids, cert) = validate_certificate_chain(&attestation_chain.certificate_chain) + .map_err(|_| Error::::CertificateChainValidationFailed)?; + + let attestation_validity = AttestationValidity { + not_before: cert.validity.not_before.timestamp_millis(), + not_after: cert.validity.not_after.timestamp_millis(), + }; + + let key_description = extract_attestation(cert.extensions) + .map_err(|_| Error::::AttestationExtractionFailed)?; + + let cert_ids_bounded = cert_ids + .into_iter() + .map(|cert_id| { + let (iss, sn) = cert_id; + let iss_bounded = IssuerName::try_from(iss) + .map_err(|_| Error::::CannotGetAttestationIssuerName)?; + let sn_bounded = SerialNumber::try_from(sn) + .map_err(|_| Error::::CannotGetAttestationSerialNumber)?; + Ok((iss_bounded, sn_bounded)) + }) + .collect::, Error>>()?; + let cert_ids_bounded_vec = ValidatingCertIds::try_from(cert_ids_bounded) + .map_err(|_| Error::::CannotGetCertificateId)?; + + Ok(Attestation { + cert_ids: cert_ids_bounded_vec, + key_description: key_description + .try_into() + .map_err(|_| Error::::AttestationToBoundedTypeConversionFailed)?, + validity: attestation_validity, + }) +} + +pub(crate) fn ensure_source_allowed( + source: &T::AccountId, + registration: &JobRegistration, +) -> Result<(), Error> { + registration + .allowed_sources + .as_ref() + .map(|allowed_sources| { + allowed_sources + .iter() + .position(|allowed_source| allowed_source == source) + .map(|_| ()) + .ok_or(Error::::FulfillSourceNotAllowed) + }) + .unwrap_or(Ok(()))?; + + if registration.allow_only_verified_sources { + let attestation = + >::get(source).ok_or(Error::::FulfillSourceNotVerified)?; + ensure_not_expired(&attestation)?; + ensure_not_revoked(&attestation)?; + } + + Ok(()) +} + +pub(crate) fn ensure_not_expired(attestation: &Attestation) -> Result<(), Error> { + let now: u64 = >::now() + .try_into() + .map_err(|_| Error::::FailedTimestampConversion)?; + + if now >= attestation.validity.not_after || now < attestation.validity.not_before { + return Err(Error::::AttestationCertificateNotValid); + } + let expire_date_time = (&attestation) + .key_description + .tee_enforced + .usage_expire_date_time + .or_else(|| { + (&attestation) + .key_description + .software_enforced + .usage_expire_date_time + }); + if let Some(expire_date_time) = expire_date_time { + if now >= expire_date_time { + return Err(Error::::AttestationUsageExpired); + } + } + Ok(()) +} + +pub(crate) fn ensure_not_revoked(attestation: &Attestation) -> Result<(), Error> { + let ids = &attestation.cert_ids; + for id in ids { + if >::get(&id.1).is_some() { + return Err(Error::::RevokedCertificate); + } + } + Ok(()) +}