From 0841d83e88fc98827ce147d86aeff93778165e9c Mon Sep 17 00:00:00 2001 From: neithanmo Date: Thu, 11 Jul 2024 14:20:59 -0600 Subject: [PATCH] Improve tests and error handling, add C interface meant to parse certificates --- app/rust/src/constants.rs | 1 + app/rust/src/error.rs | 57 ++++++++++++- app/rust/src/parser.rs | 37 ++++++++ app/rust/src/parser/certificate.rs | 77 +++++++++-------- app/rust/src/parser/delegation.rs | 56 +++++++----- app/rust/src/parser/hash_tree.rs | 132 ++++++++++++----------------- app/rust/src/parser/subnet_id.rs | 7 ++ app/src/common/parser_common.h | 5 ++ 8 files changed, 233 insertions(+), 139 deletions(-) diff --git a/app/rust/src/constants.rs b/app/rust/src/constants.rs index b8e6d6bf..bb6c2529 100644 --- a/app/rust/src/constants.rs +++ b/app/rust/src/constants.rs @@ -1,2 +1,3 @@ pub const BLS_PUBLIC_KEY_SIZE: usize = 96; pub const BLS_SIGNATURE_SIZE: usize = 48; +pub const CBOR_CERTIFICATE_TAG: u64 = 55799; diff --git a/app/rust/src/error.rs b/app/rust/src/error.rs index 1fe24052..c7d6485d 100644 --- a/app/rust/src/error.rs +++ b/app/rust/src/error.rs @@ -1,8 +1,57 @@ +use minicbor::decode::Error; + #[repr(u32)] +#[derive(Debug, PartialEq)] pub enum ParserError { + // Generic errors Ok = 0, - CborError = 7, - InvalidLabel = 27, - InvalidDelegation = 28, - InvalidCert = 29, + NoData, + InitContextEmpty, + DisplayIdxOutOfRange, + DisplayPageOutOfRange, + UnexpectedError, + NotImplemented, + // Cbor + CborUnexpected, + CborUnexpectedEOF, + CborNotCanonical, + // Coin specific + UnexpectedTxVersion, + UnexpectedType, + UnexpectedMethod, + UnexpectedBufferEnd, + UnexpectedValue, + UnexpectedNumberItems, + UnexpectedCharacters, + UnexpectedField, + ValueOutOfRange, + InvalidAddress, + // Context related errors + ContextMismatch, + ContextUnexpectedSize, + ContextInvalidChars, + ContextUnknownPrefix, + // Required fields + RequiredNonce, + RequiredMethod, + // Special codes + TypeNotFound, + InvalidLabel, + InvalidDelegation, + InvalidCertificate, + InvalidTree, + MiniCborError, + RecursionLimitReached, } + +// minicibor error provides a reach +// error context and also the index +// at which the error occurred, +// we can not handle it here, as ParserError +// is send to C callers +impl From for ParserError { + fn from(_: Error) -> Self { + Self::MiniCborError + } +} + diff --git a/app/rust/src/parser.rs b/app/rust/src/parser.rs index 6aefaefb..32d76104 100644 --- a/app/rust/src/parser.rs +++ b/app/rust/src/parser.rs @@ -1,3 +1,5 @@ +use core::mem::MaybeUninit; + pub mod certificate; pub mod delegation; pub mod hash_tree; @@ -6,3 +8,38 @@ pub mod pubkey; pub mod raw_value; pub mod signature; pub mod subnet_id; + +///This trait defines an useful interface to parse +///objects from bytes. +///this gives different objects in a transaction +///a way to define their own deserilization implementation, allowing higher level objects to generalize the +///parsing of their inner types +pub trait FromBytes<'b>: Sized { + /// this method is avaliable for testing only, as the preferable + /// option is to save stack by passing the memory where the object should + /// store itself + #[cfg(test)] + fn from_bytes(input: &'b [u8]) -> Result<(), crate::error::ParserError> { + use core::mem::MaybeUninit; + + let mut out = MaybeUninit::uninit(); + Self::from_bytes_into(input, &mut out) + } + + ///Main deserialization method + ///`input` the input data that contains the serialized form in bytes of this object. + ///`out` the memory where this object would be stored + /// + /// returns the remaining bytes on success + /// + /// `Safety` Dealing with uninitialize memory is undefine behavior + /// even in rust, so implementors should follow the rust documentation + /// for MaybeUninit and unsafe guidelines. + /// + /// It's a good idea to always put `#[inline(never)]` on top of this + /// function's implementation + fn from_bytes_into( + input: &'b [u8], + out: &mut MaybeUninit, + ) -> Result<(), crate::error::ParserError>; +} diff --git a/app/rust/src/parser/certificate.rs b/app/rust/src/parser/certificate.rs index 911a8830..8c4afd21 100644 --- a/app/rust/src/parser/certificate.rs +++ b/app/rust/src/parser/certificate.rs @@ -1,7 +1,12 @@ +use core::mem::MaybeUninit; + use bls_signature::verify_bls_signature; -use minicbor::{decode::Error, Decode, Decoder}; +use minicbor::{data::Type, decode::Error, Decode, Decoder}; -use crate::{hash_tree::hash_with_domain_sep, signature::Signature}; +use crate::{ + constants::CBOR_CERTIFICATE_TAG, error::ParserError, hash_tree::hash_with_domain_sep, + signature::Signature, FromBytes, +}; use super::{delegation::Delegation, hash_tree::HashTree, raw_value::RawValue}; @@ -12,14 +17,25 @@ pub struct Certificate<'a> { delegation: Option>, } +impl<'a> FromBytes<'a> for Certificate<'a> { + fn from_bytes_into( + input: &'a [u8], + out: &mut MaybeUninit, + ) -> Result<(), crate::error::ParserError> { + let cert = Certificate::parse(input)?; + out.write(cert); + Ok(()) + } +} + impl<'a> Certificate<'a> { - pub fn parse(data: &[u8]) -> Result { + pub fn parse(data: &[u8]) -> Result { let mut decoder = Decoder::new(data); // Check for and skip the self-describing CBOR tag if present if let Ok(tag) = decoder.tag() { - if tag.as_u64() != 55799 { - return Err(Error::message("Unexpected tag")); + if tag.as_u64() != CBOR_CERTIFICATE_TAG { + return Err(ParserError::CborUnexpected); } } @@ -49,13 +65,13 @@ impl<'a> Certificate<'a> { self.signature } - pub fn hash(&self) -> Result<[u8; 32], Error> { + pub fn hash(&self) -> Result<[u8; 32], ParserError> { let tree: HashTree = self.tree.try_into()?; tree.reconstruct() } // The root_public_key is now a parameter to the verify method - pub fn verify(&self, root_public_key: &[u8]) -> Result { + pub fn verify(&self, root_public_key: &[u8]) -> Result { // Step 1: Compute root hash of the outer certificate's tree let tree: HashTree = self.tree.try_into()?; let root_hash = tree.reconstruct()?; @@ -79,7 +95,7 @@ impl<'a> Certificate<'a> { // verify the inner certificate // the one that comes in the delegation - fn check_delegation(&self, root_key: &[u8]) -> Result { + fn check_delegation(&self, root_key: &[u8]) -> Result { match &self.delegation { None => Ok(true), Some(delegation) => { @@ -103,18 +119,11 @@ impl<'a> Certificate<'a> { } } - fn delegation_key(&self, root_public_key: &'a [u8]) -> Result<&'a [u8], Error> { - #[cfg(test)] - std::println!("delegation: {:?}", self.delegation); - + fn delegation_key(&self, root_public_key: &'a [u8]) -> Result<&'a [u8], ParserError> { match &self.delegation { None => Ok(root_public_key), // Use root_public_key if no delegation Some(d) => { - #[cfg(test)] - std::println!("delegation"); - let key = d - .public_key()? - .ok_or(Error::message("Missing public key"))?; + let key = d.public_key()?.ok_or(ParserError::UnexpectedValue)?; Ok(key.as_bytes()) } } @@ -124,11 +133,11 @@ impl<'a> Certificate<'a> { impl<'b, C> Decode<'b, C> for Certificate<'b> { fn decode(d: &mut Decoder<'b>, ctx: &mut C) -> Result { // Expect a map with 2/3 entries - let len = d.map()?.ok_or(Error::message("Expected a map"))?; + let len = d.map()?.ok_or(Error::type_mismatch(Type::Map))?; // A certificate could have either 2(delegation cert) or 3 entries(root cert) if len != 2 && len != 3 { - return Err(Error::message("Expected a map with 3 entries")); + return Err(Error::type_mismatch(Type::Map)); } let mut tree = None; @@ -158,11 +167,11 @@ mod test_certificate { use ic_certification::Certificate as IcpCertificate; const REAL_CERT: &str = "D9D9F7A3647472656583018301820458200BBCC71092DA3CE262B8154D398B9A6114BEE87F1C0B72E16912757AA023626A8301820458200628A8E00432E8657AD99C4D1BF167DD54ACE9199609BFC5D57D89F48D97565F83024E726571756573745F737461747573830258204EA057C46292FEDB573D35319DD1CCAB3FB5D6A2B106B785D1F7757CFA5A254283018302457265706C79820358B44449444C0B6B02BC8A0101C5FED201086C02EFCEE7800402E29FDCC806036C01D880C6D007716B02D9E5B0980404FCDFD79A0F716C01C4D6B4EA0B056D066C01FFBB87A807076D716B04D1C4987C09A3F2EFE6020A9A8597E6030AE3C581900F0A6C02FC91F4F80571C498B1B50D7D6C01FC91F4F8057101000002656E0001021E50726F647563652074686520666F6C6C6F77696E67206772656574696E6714746578743A202248656C6C6F2C20746F626921228302467374617475738203477265706C696564830182045820891AF3E8982F1AC3D295C29B9FDFEDC52301C03FBD4979676C01059184060B0583024474696D65820349CBF7DD8CA1A2A7E217697369676E6174757265583088078C6FE75F32594BF4E322B14D47E5C849CF24A370E3BAB0CAB5DAFFB7AB6A2C49DE18B7F2D631893217D0C716CD656A64656C65676174696F6EA2697375626E65745F6964581D2C55B347ECF2686C83781D6C59D1B43E7B4CBA8DEB6C1B376107F2CD026B6365727469666963617465590294D9D9F7A264747265658301820458200B0D62DC7B9D7DE735BB9A6393B59C9F32FF7C4D2AACDFC9E6FFC70E341FB6F783018301820458204468514CA4AF8224C055C386E3F7B0BFE018C2D9CFD5837E427B43E1AB0934F98302467375626E65748301830183018301820458208739FBBEDD3DEDAA8FEF41870367C0905BDE376B63DD37E2B176FB08B582052F830182045820F8C3EAE0377EE00859223BF1C6202F5885C4DCDC8FD13B1D48C3C838688919BC83018302581D2C55B347ECF2686C83781D6C59D1B43E7B4CBA8DEB6C1B376107F2CD02830183024F63616E69737465725F72616E67657382035832D9D9F782824A000000000060000001014A00000000006000AE0101824A00000000006000B001014A00000000006FFFFF010183024A7075626C69635F6B657982035885308182301D060D2B0601040182DC7C0503010201060C2B0601040182DC7C0503020103610090075120778EB21A530A02BCC763E7F4A192933506966AF7B54C10A4D2B24DE6A86B200E3440BAE6267BF4C488D9A11D0472C38C1B6221198F98E4E6882BA38A5A4E3AA5AFCE899B7F825ED95ADFA12629688073556F2747527213E8D73E40CE8204582036F3CD257D90FB38E42597F193A5E031DBD585B6292793BB04DB4794803CE06E82045820028FC5E5F70868254E7215E7FC630DBD29EEFC3619AF17CE231909E1FAF97E9582045820696179FCEB777EAED283265DD690241999EB3EDE594091748B24456160EDC1278204582081398069F9684DA260CFB002EAC42211D0DBF22C62D49AEE61617D62650E793183024474696D65820349A5948992AAA195E217697369676E6174757265583094E5F544A7681B0C2C3C5DBF97950C96FD837F2D19342F1050D94D3068371B0A95A5EE20C36C4395C2DBB4204F2B4742"; - // Same as above but we change the inner cbor typo to something invalid(different to a map) - const INVALID_DATA: &str = "A9D9F7A3647472656583018301820458200BBCC71092DA3CE262B8154D398B9A6114BEE87F1C0B72E16912757AA023626A8301820458200628A8E00432E8657AD99C4D1BF167DD54ACE9199609BFC5D57D89F48D97565F83024E726571756573745F737461747573830258204EA057C46292FEDB573D35319DD1CCAB3FB5D6A2B106B785D1F7757CFA5A254283018302457265706C79820358B44449444C0B6B02BC8A0101C5FED201086C02EFCEE7800402E29FDCC806036C01D880C6D007716B02D9E5B0980404FCDFD79A0F716C01C4D6B4EA0B056D066C01FFBB87A807076D716B04D1C4987C09A3F2EFE6020A9A8597E6030AE3C581900F0A6C02FC91F4F80571C498B1B50D7D6C01FC91F4F8057101000002656E0001021E50726F647563652074686520666F6C6C6F77696E67206772656574696E6714746578743A202248656C6C6F2C20746F626921228302467374617475738203477265706C696564830182045820891AF3E8982F1AC3D295C29B9FDFEDC52301C03FBD4979676C01059184060B0583024474696D65820349CBF7DD8CA1A2A7E217697369676E6174757265583088078C6FE75F32594BF4E322B14D47E5C849CF24A370E3BAB0CAB5DAFFB7AB6A2C49DE18B7F2D631893217D0C716CD656A64656C65676174696F6EA2697375626E65745F6964581D2C55B347ECF2686C83781D6C59D1B43E7B4CBA8DEB6C1B376107F2CD026B6365727469666963617465590294D9D9F7A264747265658301820458200B0D62DC7B9D7DE735BB9A6393B59C9F32FF7C4D2AACDFC9E6FFC70E341FB6F783018301820458204468514CA4AF8224C055C386E3F7B0BFE018C2D9CFD5837E427B43E1AB0934F98302467375626E65748301830183018301820458208739FBBEDD3DEDAA8FEF41870367C0905BDE376B63DD37E2B176FB08B582052F830182045820F8C3EAE0377EE00859223BF1C6202F5885C4DCDC8FD13B1D48C3C838688919BC83018302581D2C55B347ECF2686C83781D6C59D1B43E7B4CBA8DEB6C1B376107F2CD02830183024F63616E69737465725F72616E67657382035832D9D9F782824A000000000060000001014A00000000006000AE0101824A00000000006000B001014A00000000006FFFFF010183024A7075626C69635F6B657982035885308182301D060D2B0601040182DC7C0503010201060C2B0601040182DC7C0503020103610090075120778EB21A530A02BCC763E7F4A192933506966AF7B54C10A4D2B24DE6A86B200E3440BAE6267BF4C488D9A11D0472C38C1B6221198F98E4E6882BA38A5A4E3AA5AFCE899B7F825ED95ADFA12629688073556F2747527213E8D73E40CE8204582036F3CD257D90FB38E42597F193A5E031DBD585B6292793BB04DB4794803CE06E82045820028FC5E5F70868254E7215E7FC630DBD29EEFC3619AF17CE231909E1FAF97E9582045820696179FCEB777EAED283265DD690241999EB3EDE594091748B24456160EDC1278204582081398069F9684DA260CFB002EAC42211D0DBF22C62D49AEE61617D62650E793183024474696D65820349A5948992AAA195E217697369676E6174757265583094E5F544A7681B0C2C3C5DBF97950C96FD837F2D19342F1050D94D3068371B0A95A5EE20C36C4395C2DBB4204F2B4742"; + const CERT_HASH: &str = "bcedf2eab3980aedd4d0d9f2159efebd5597cbad5f49217e0c9686b93d30d503"; + const DEL_CERT_HASH: &str = "04a94256c02e83aab4f203cb0784340279d7902f9b09305c978be1746e19b742"; #[test] - fn parser_cert() { + fn parse_cert() { let data = hex::decode(REAL_CERT).unwrap(); let cert = Certificate::parse(&data).unwrap(); @@ -171,20 +180,14 @@ mod test_certificate { std::println!("sign: {:?} - len: {}", sign, sign.len()); } - #[test] - fn error_invalid_certificate() { - let data = hex::decode(INVALID_DATA).unwrap(); - assert!(Certificate::parse(&data).is_err()); - } - #[test] fn verify_cert() { let data = hex::decode(REAL_CERT).unwrap(); let cert = Certificate::parse(&data).unwrap(); let root_hash = hex::encode(cert.hash().unwrap()); - // HashTree::parse_and_print_hash_tree(cert.tree(), 0); - // let del = cert.delegation.unwrap(); - // HashTree::parse_and_print_hash_tree(del.cert().tree(), 0); + // Verify delegation.cert root_hash + let del_cert = cert.delegation.unwrap().cert(); + let del_cert_hash = hex::encode(del_cert.hash().unwrap()); // compare our root hash with the hash icp library computes let icp_cert: IcpCertificate = serde_cbor::from_slice(&data).unwrap(); @@ -193,8 +196,14 @@ mod test_certificate { let icp_hash = hex::encode(icp_hash); assert_eq!(root_hash, icp_hash); - // TODO: enable later - let root_key = [0u8; 96]; - assert!(cert.verify(&root_key).unwrap()); + assert_eq!(root_hash, CERT_HASH); + // compare our root hash with the hash icp library computes + let icp_cert: IcpCertificate = serde_cbor::from_slice(&data).unwrap(); + let icp_del = icp_cert.delegation.unwrap(); + let icp_hash: IcpCertificate = serde_cbor::from_slice(&icp_del.certificate).unwrap(); + let icp_hash = hex::encode(icp_hash.tree.digest()); + + assert_eq!(del_cert_hash, icp_hash); + assert_eq!(del_cert_hash, DEL_CERT_HASH); } } diff --git a/app/rust/src/parser/delegation.rs b/app/rust/src/parser/delegation.rs index 5aafdcda..31a4fcb5 100644 --- a/app/rust/src/parser/delegation.rs +++ b/app/rust/src/parser/delegation.rs @@ -1,9 +1,10 @@ -use minicbor::{data::Type, decode::Error, Decode, Decoder}; +use minicbor::{decode::Error, Decode, Decoder}; + +use crate::error::ParserError; use super::{ certificate::Certificate, hash_tree::{HashTree, LookupResult}, - label::Label, pubkey::PublicKey, raw_value::RawValue, subnet_id::SubnetId, @@ -37,7 +38,7 @@ impl<'a> Delegation<'a> { self.subnet_id.id() } - pub fn verify(&self, root_key: &[u8]) -> Result { + pub fn verify(&self, root_key: &[u8]) -> Result { let cert = self.cert(); cert.verify(root_key) @@ -45,7 +46,7 @@ impl<'a> Delegation<'a> { // Why an option? // It is not clear if delegation would always contain a key - pub fn public_key(&self) -> Result>, Error> { + pub fn public_key(&self) -> Result>, ParserError> { let value = self.subnet_public_key()?; let Some(value) = value.value() else { return Ok(None); @@ -53,26 +54,24 @@ impl<'a> Delegation<'a> { Ok(Some(PublicKey::try_from(*value)?)) } - // 1. root_hash: We need to compute this for the inner certificate. - // 2. subnet_id: This is available in the Delegation structure. - // 3. public_key: We need to lookup ["subnet", subnet_id, "public_key"] in the inner certificate. - // 4. signature: This is available in the inner certificate structure. - fn subnet_public_key(&self) -> Result, Error> { + // 1. subnet_id: This is available in the Delegation structure. + // 2. public_key: We need to lookup ["subnet", subnet_id, "public_key"] in the inner certificate. + fn subnet_public_key(&self) -> Result, ParserError> { // Step 1: Look up "subnet" in the root of the tree let cert = self.cert(); - let subnet_result = HashTree::lookup_path(&Label::String("subnet"), *cert.tree())?; + let subnet_result = HashTree::lookup_path(&"subnet".into(), *cert.tree())?; match subnet_result { LookupResult::Found(subnet_value) => { // Step 2: Look up the specific subnet_id in the subnet subtree let subnet_id_result = - HashTree::lookup_path(&Label::Blob(self.subnet_id.id()), subnet_value)?; + HashTree::lookup_path(&self.subnet_id.id().into(), subnet_value)?; match subnet_id_result { LookupResult::Found(subnet_id_tree) => { // Step 3: Look up "public_key" in the subnet_id subtree - HashTree::lookup_path(&Label::String("public_key"), subnet_id_tree) + HashTree::lookup_path(&"public_key".into(), subnet_id_tree) } _ => Ok(LookupResult::Absent), } @@ -85,8 +84,11 @@ impl<'a> Delegation<'a> { impl<'b, C> Decode<'b, C> for Delegation<'b> { fn decode(d: &mut Decoder<'b>, ctx: &mut C) -> Result { // Expect a map with 2 entries - let len = d.map()?.ok_or(Error::message("Expected a map"))?; - if len != 2 { + let len = d + .map()? + .ok_or(Error::type_mismatch(minicbor::data::Type::Map))?; + + if len != DELEGATION_MAP_ENTRIES { return Err(Error::message("Expected a map with 2 entries")); } @@ -101,16 +103,12 @@ impl<'b, C> Decode<'b, C> for Delegation<'b> { subnet_id = Some(SubnetId::decode(d, ctx)?); } "certificate" => { - if d.datatype()? == Type::Bytes { - let bytes = d.bytes()?; - let mut dec = Decoder::new(bytes); - let raw = RawValue::decode(&mut dec, ctx)?; - certificate = Some(raw); - } else { - return Err(Error::message("Expected byte string for certificate")); - } + let bytes = d.bytes()?; + let mut dec = Decoder::new(bytes); + let raw = RawValue::decode(&mut dec, ctx)?; + certificate = Some(raw); } - _ => return Err(Error::message("Unexpected key in delegation")), + _ => return Err(Error::message("Unexpected key")), } } @@ -127,6 +125,10 @@ mod test_delegation { use super::*; const DATA: &str = "D9D9F7A3647472656583018301820458200BBCC71092DA3CE262B8154D398B9A6114BEE87F1C0B72E16912757AA023626A8301820458200628A8E00432E8657AD99C4D1BF167DD54ACE9199609BFC5D57D89F48D97565F83024E726571756573745F737461747573830258204EA057C46292FEDB573D35319DD1CCAB3FB5D6A2B106B785D1F7757CFA5A254283018302457265706C79820358B44449444C0B6B02BC8A0101C5FED201086C02EFCEE7800402E29FDCC806036C01D880C6D007716B02D9E5B0980404FCDFD79A0F716C01C4D6B4EA0B056D066C01FFBB87A807076D716B04D1C4987C09A3F2EFE6020A9A8597E6030AE3C581900F0A6C02FC91F4F80571C498B1B50D7D6C01FC91F4F8057101000002656E0001021E50726F647563652074686520666F6C6C6F77696E67206772656574696E6714746578743A202248656C6C6F2C20746F626921228302467374617475738203477265706C696564830182045820891AF3E8982F1AC3D295C29B9FDFEDC52301C03FBD4979676C01059184060B0583024474696D65820349CBF7DD8CA1A2A7E217697369676E6174757265583088078C6FE75F32594BF4E322B14D47E5C849CF24A370E3BAB0CAB5DAFFB7AB6A2C49DE18B7F2D631893217D0C716CD656A64656C65676174696F6EA2697375626E65745F6964581D2C55B347ECF2686C83781D6C59D1B43E7B4CBA8DEB6C1B376107F2CD026B6365727469666963617465590294D9D9F7A264747265658301820458200B0D62DC7B9D7DE735BB9A6393B59C9F32FF7C4D2AACDFC9E6FFC70E341FB6F783018301820458204468514CA4AF8224C055C386E3F7B0BFE018C2D9CFD5837E427B43E1AB0934F98302467375626E65748301830183018301820458208739FBBEDD3DEDAA8FEF41870367C0905BDE376B63DD37E2B176FB08B582052F830182045820F8C3EAE0377EE00859223BF1C6202F5885C4DCDC8FD13B1D48C3C838688919BC83018302581D2C55B347ECF2686C83781D6C59D1B43E7B4CBA8DEB6C1B376107F2CD02830183024F63616E69737465725F72616E67657382035832D9D9F782824A000000000060000001014A00000000006000AE0101824A00000000006000B001014A00000000006FFFFF010183024A7075626C69635F6B657982035885308182301D060D2B0601040182DC7C0503010201060C2B0601040182DC7C0503020103610090075120778EB21A530A02BCC763E7F4A192933506966AF7B54C10A4D2B24DE6A86B200E3440BAE6267BF4C488D9A11D0472C38C1B6221198F98E4E6882BA38A5A4E3AA5AFCE899B7F825ED95ADFA12629688073556F2747527213E8D73E40CE8204582036F3CD257D90FB38E42597F193A5E031DBD585B6292793BB04DB4794803CE06E82045820028FC5E5F70868254E7215E7FC630DBD29EEFC3619AF17CE231909E1FAF97E9582045820696179FCEB777EAED283265DD690241999EB3EDE594091748B24456160EDC1278204582081398069F9684DA260CFB002EAC42211D0DBF22C62D49AEE61617D62650E793183024474696D65820349A5948992AAA195E217697369676E6174757265583094E5F544A7681B0C2C3C5DBF97950C96FD837F2D19342F1050D94D3068371B0A95A5EE20C36C4395C2DBB4204F2B4742"; + const SUBNET_ID: &[u8] = &[ + 44, 85, 179, 71, 236, 242, 104, 108, 131, 120, 29, 108, 89, 209, 180, 62, 123, 76, 186, + 141, 235, 108, 27, 55, 97, 7, 242, 205, 2, + ]; #[test] fn test_pubkey() { @@ -135,4 +137,12 @@ mod test_delegation { let delegation = cert.delegation().expect("root cert must have a delegation"); delegation.public_key().unwrap().unwrap(); } + + #[test] + fn test_subnet() { + let data = hex::decode(DATA).unwrap(); + let cert = Certificate::parse(&data).unwrap(); + let delegation = cert.delegation().expect("root cert must have a delegation"); + assert_eq!(delegation.subnet(), SUBNET_ID); + } } diff --git a/app/rust/src/parser/hash_tree.rs b/app/rust/src/parser/hash_tree.rs index 1c4ca0dc..33e6f613 100644 --- a/app/rust/src/parser/hash_tree.rs +++ b/app/rust/src/parser/hash_tree.rs @@ -1,8 +1,10 @@ -use minicbor::{decode::Error, Decode, Decoder}; +use minicbor::{data::Type, decode::Error, Decode, Decoder}; use sha2::Digest; // use core::cmp::Ordering; +use crate::error::ParserError; + use super::{label::Label, raw_value::RawValue}; const MAX_TREE_DEPTH: usize = 32; @@ -71,9 +73,9 @@ impl<'a> HashTree<'a> { // Traverse all the tree ensuring // at parsing that we can handle its length // without reaching overflows, otherwise we error - fn check_integrity(&self, depth: usize) -> Result<(), Error> { + fn check_integrity(&self, depth: usize) -> Result<(), ParserError> { if depth >= MAX_TREE_DEPTH { - return Err(Error::message("Maximum tree depth exceeded")); + return Err(ParserError::RecursionLimitReached); } match self { @@ -135,14 +137,17 @@ impl<'a> HashTree<'a> { Ok(()) } - pub fn lookup_path(label: &Label<'a>, tree: RawValue<'a>) -> Result, Error> { + pub fn lookup_path( + label: &Label<'a>, + tree: RawValue<'a>, + ) -> Result, ParserError> { fn inner_lookup<'a>( label: &Label<'a>, tree: RawValue<'a>, depth: usize, - ) -> Result, Error> { + ) -> Result, ParserError> { if depth >= MAX_TREE_DEPTH { - return Err(Error::message("Maximum tree depth exceeded")); + return Err(ParserError::RecursionLimitReached); } let current_tree = tree.try_into()?; @@ -175,7 +180,9 @@ impl<'a> HashTree<'a> { inner_lookup(label, tree, 1) } - pub fn reconstruct(&self) -> Result<[u8; 32], Error> { + /// Reconstruct the root hash of this tree, following the rules in: + /// https://internetcomputer.org/docs/current/references/ic-interface-spec/#certificate + pub fn reconstruct(&self) -> Result<[u8; 32], ParserError> { let hash = match self { HashTree::Empty => hash_with_domain_sep("ic-hashtree-empty", &[]), HashTree::Fork(left, right) => { @@ -184,19 +191,24 @@ impl<'a> HashTree<'a> { let left_hash = left.reconstruct()?; let right_hash = right.reconstruct()?; + let mut concat = [0; 64]; concat[..32].copy_from_slice(&left_hash); concat[32..].copy_from_slice(&right_hash); + hash_with_domain_sep("ic-hashtree-fork", &concat) } HashTree::Labeled(label, subtree) => { let subtree: HashTree = subtree.try_into()?; let subtree_hash = subtree.reconstruct()?; + // domain.len() + label_max_len + hash_len let mut concat = [0; Label::MAX_LEN + 32]; let label_len = label.as_bytes().len(); + concat[..label_len].copy_from_slice(label.as_bytes()); concat[label_len..label_len + 32].copy_from_slice(&subtree_hash); + hash_with_domain_sep("ic-hashtree-labeled", &concat[..label_len + 32]) } HashTree::Leaf(_) => { @@ -207,8 +219,7 @@ impl<'a> HashTree<'a> { HashTree::Pruned(_) => { let hash = self.value().unwrap(); if hash.len() != 32 { - // FIXME: Do not panic - panic!("Pruned node hash must be 32 bytes"); + return Err(ParserError::UnexpectedValue); } let mut result = [0; 32]; @@ -221,17 +232,17 @@ impl<'a> HashTree<'a> { } impl<'a> TryFrom> for HashTree<'a> { - type Error = Error; + type Error = ParserError; fn try_from(value: RawValue<'a>) -> Result { let mut d = Decoder::new(value.bytes()); - let tree = HashTree::decode(&mut d, &mut ())?; + let tree = HashTree::decode(&mut d, &mut ()).map_err(|_| ParserError::InvalidTree)?; tree.check_integrity(0)?; Ok(tree) } } impl<'a> TryFrom<&RawValue<'a>> for HashTree<'a> { - type Error = Error; + type Error = ParserError; fn try_from(value: &RawValue<'a>) -> Result { (*value).try_into() } @@ -239,33 +250,29 @@ impl<'a> TryFrom<&RawValue<'a>> for HashTree<'a> { impl<'b, C> Decode<'b, C> for HashTree<'b> { fn decode(d: &mut Decoder<'b>, ctx: &mut C) -> Result { - match d.datatype()? { - minicbor::data::Type::Array => { - let len = d.array()?.ok_or(Error::message("Expected array"))?; - match len { - 0 => Ok(HashTree::Empty), - _ => { - let tag = u8::decode(d, ctx)?; - match tag { - 0 => Ok(HashTree::Empty), - 1 => { - let left = RawValue::decode(d, ctx)?; - let right = RawValue::decode(d, ctx)?; - Ok(HashTree::Fork(left, right)) - } - 2 => { - let label = Label::decode(d, ctx)?; - let subtree = RawValue::decode(d, ctx)?; - Ok(HashTree::Labeled(label, subtree)) - } - 3 => Ok(HashTree::Leaf(RawValue::decode(d, ctx)?)), - 4 => Ok(HashTree::Pruned(RawValue::decode(d, ctx)?)), - _ => Err(Error::message("Invalid HashTree tag")), - } - } - } + // Every tree is encoded as an array: + // [tag(u8), data(CBOR blob)] + // where tag tells the tree type + let len = d.array()?.ok_or(Error::type_mismatch(Type::Array))?; + if len == 0 { + return Ok(HashTree::Empty); + } + let tag = u8::decode(d, ctx)?; + match tag { + 0 => Ok(HashTree::Empty), + 1 => { + let left = RawValue::decode(d, ctx)?; + let right = RawValue::decode(d, ctx)?; + Ok(HashTree::Fork(left, right)) + } + 2 => { + let label = Label::decode(d, ctx)?; + let subtree = RawValue::decode(d, ctx)?; + Ok(HashTree::Labeled(label, subtree)) } - _ => Err(Error::message("Expected array for HashTree")), + 3 => Ok(HashTree::Leaf(RawValue::decode(d, ctx)?)), + 4 => Ok(HashTree::Pruned(RawValue::decode(d, ctx)?)), + _ => Err(Error::message("Invalid HashTree tag")), } } } @@ -330,27 +337,14 @@ mod hash_tree_tests { let cert = Certificate::parse(&data).unwrap(); // Create the path for "time" - let path = Label::String("time"); + let path = "time".into(); // Perform the lookup - match HashTree::lookup_path(&path, *cert.tree()).unwrap() { - LookupResult::Found(value) => { - match value.try_into() { - Ok(HashTree::Leaf(leaf_data)) => { - // The expected leaf data - let expected = [73, 203, 247, 221, 140, 161, 162, 167, 226, 23]; - assert_eq!( - leaf_data.bytes(), - expected, - "Leaf data does not match expected value" - ); - std::println!("Successfully found and matched 'time' leaf data"); - } - _ => panic!("Expected Leaf, found {:?}", value), - } - } - _ => panic!("'{:?}' path not found in the tree", path), - } + let found = HashTree::lookup_path(&path, *cert.tree()).unwrap(); + let time: HashTree = found.value().unwrap().try_into().unwrap(); + let time = time.value().unwrap(); + let expected = [203, 247, 221, 140, 161, 162, 167, 226, 23]; + assert_eq!(time, expected, "Leaf data does not match expected value"); } #[test] @@ -359,28 +353,10 @@ mod hash_tree_tests { let data = hex::decode(DATA).unwrap(); let cert = Certificate::parse(&data).unwrap(); - let path = Label::String("reply"); - - HashTree::parse_and_print_hash_tree(cert.tree(), 0).unwrap(); + let path = "reply".into(); - // Perform the lookup - match HashTree::lookup_path(&path, *cert.tree()).unwrap() { - LookupResult::Found(value) => { - let tree = value.try_into().unwrap(); - match tree { - HashTree::Leaf(leaf_data) => { - // The expected leaf data - assert_eq!( - leaf_data.bytes(), - REPLY, - "Leaf data does not match expected value" - ); - } - _ => panic!("Expected Leaf, found {:?}", value), - } - } - _ => panic!("'{:?}' path not found in the tree", path), - } + let found = HashTree::lookup_path(&path, *cert.tree()).unwrap(); + assert!(found.value().is_some()); } #[test] diff --git a/app/rust/src/parser/subnet_id.rs b/app/rust/src/parser/subnet_id.rs index c3386581..94efb3f9 100644 --- a/app/rust/src/parser/subnet_id.rs +++ b/app/rust/src/parser/subnet_id.rs @@ -12,6 +12,13 @@ impl<'a> SubnetId<'a> { d.bytes().unwrap() } } +impl<'a> TryFrom<&RawValue<'a>> for SubnetId<'a> { + type Error = Error; + + fn try_from(value: &RawValue<'a>) -> Result { + (*value).try_into() + } +} impl<'a> TryFrom> for SubnetId<'a> { type Error = Error; diff --git a/app/src/common/parser_common.h b/app/src/common/parser_common.h index b596fb9f..a3e43f7f 100644 --- a/app/src/common/parser_common.h +++ b/app/src/common/parser_common.h @@ -65,6 +65,11 @@ typedef enum { parser_invalid_label, parser_invalid_delegation, parser_invalid_certificate + parser_invalid_tree, + parser_minicbor_error, + parser_recursion_limit_reached, + + } parser_error_t; typedef struct {