From b1f67b13f96e18cb083e62a493282c359fbecaa6 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Thu, 28 Apr 2022 13:52:00 -0400 Subject: [PATCH] Fix a weird little bug (actually incompatibility with the old code) in x25519 handling in identity, and more cleanup. --- rustfmt.toml | 2 +- zerotier-core-crypto/src/c25519.rs | 52 +++-- zerotier-core-crypto/src/secret.rs | 2 +- zerotier-network-hypervisor/src/util/gate.rs | 9 +- zerotier-network-hypervisor/src/util/mod.rs | 10 +- .../src/vl1/identity.rs | 198 +++++++++++------- 6 files changed, 169 insertions(+), 104 deletions(-) diff --git a/rustfmt.toml b/rustfmt.toml index b8b4a48b8..a18fd9a3d 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,4 +1,4 @@ -max_width = 300 +max_width = 256 use_small_heuristics = "Max" tab_spaces = 4 newline_style = "Unix" diff --git a/zerotier-core-crypto/src/c25519.rs b/zerotier-core-crypto/src/c25519.rs index 32f486cb4..478f56858 100644 --- a/zerotier-core-crypto/src/c25519.rs +++ b/zerotier-core-crypto/src/c25519.rs @@ -22,23 +22,39 @@ pub const ED25519_SECRET_KEY_SIZE: usize = 32; pub const ED25519_SIGNATURE_SIZE: usize = 64; /// Curve25519 key pair for ECDH key agreement. -pub struct C25519KeyPair(x25519_dalek::StaticSecret, x25519_dalek::PublicKey); +pub struct C25519KeyPair(x25519_dalek::StaticSecret, Secret<32>, x25519_dalek::PublicKey); impl C25519KeyPair { #[inline(always)] pub fn generate() -> C25519KeyPair { let sk = x25519_dalek::StaticSecret::new(SecureRandom::get()); + let sk2 = Secret(sk.to_bytes()); let pk = x25519_dalek::PublicKey::from(&sk); - C25519KeyPair(sk, pk) + C25519KeyPair(sk, sk2, pk) } pub fn from_bytes(public_key: &[u8], secret_key: &[u8]) -> Option { if public_key.len() == 32 && secret_key.len() == 32 { + // NOTE: we keep the original secret separately from x25519_dalek's StaticSecret + // due to how "clamping" is done in the old C++ code vs x25519_dalek. Clamping + // is explained here: + // + // https://www.jcraige.com/an-explainer-on-ed25519-clamping + // + // In the old C++ code clamping is done when the secret key is actually used. + // In x25519_dalek it's done when the key is loaded into one of the secret + // containers. Unfortunately this means that identities' secret keys won't look + // the same in the actual identity structure vs. what you would get from the C++ + // v0 ZeroTier implementation. The cryptographic results are identical but we + // still need to have our identity spit out identical bits when exported. + // + // Newly generated keys will be clamped at generation time, which will also yield + // identical results in both cases. let pk: [u8; 32] = public_key.try_into().unwrap(); - let sk: [u8; 32] = secret_key.try_into().unwrap(); + let sk_orig: Secret<32> = Secret(secret_key.try_into().unwrap()); let pk = x25519_dalek::PublicKey::from(pk); - let sk = x25519_dalek::StaticSecret::from(sk); - Some(C25519KeyPair(sk, pk)) + let sk = x25519_dalek::StaticSecret::from(sk_orig.0.clone()); + Some(C25519KeyPair(sk, sk_orig, pk)) } else { None } @@ -46,12 +62,12 @@ impl C25519KeyPair { #[inline(always)] pub fn public_bytes(&self) -> [u8; C25519_PUBLIC_KEY_SIZE] { - self.1.to_bytes() + self.2.to_bytes() } #[inline(always)] - pub fn secret_bytes(&self) -> Secret<{ C25519_SECRET_KEY_SIZE }> { - Secret(self.0.to_bytes()) + pub fn secret_bytes(&self) -> &Secret<32> { + &self.1 } /// Execute ECDH agreement and return a raw (un-hashed) shared secret key. @@ -65,18 +81,20 @@ impl C25519KeyPair { impl Clone for C25519KeyPair { fn clone(&self) -> Self { - Self(x25519_dalek::StaticSecret::from(self.0.to_bytes()), x25519_dalek::PublicKey::from(self.1.to_bytes())) + Self(x25519_dalek::StaticSecret::from(self.0.to_bytes()), self.1.clone(), x25519_dalek::PublicKey::from(self.1 .0.clone())) } } /// Ed25519 key pair for EDDSA signatures. -pub struct Ed25519KeyPair(ed25519_dalek::Keypair); +pub struct Ed25519KeyPair(ed25519_dalek::Keypair, Secret<32>); impl Ed25519KeyPair { #[inline(always)] pub fn generate() -> Ed25519KeyPair { let mut rng = SecureRandom::get(); - Ed25519KeyPair(ed25519_dalek::Keypair::generate(&mut rng)) + let kp = ed25519_dalek::Keypair::generate(&mut rng); + let sk2 = Secret(kp.secret.to_bytes()); + Ed25519KeyPair(kp, sk2) } pub fn from_bytes(public_bytes: &[u8], secret_bytes: &[u8]) -> Option { @@ -84,7 +102,11 @@ impl Ed25519KeyPair { let pk = ed25519_dalek::PublicKey::from_bytes(public_bytes); let sk = ed25519_dalek::SecretKey::from_bytes(secret_bytes); if pk.is_ok() && sk.is_ok() { - Some(Ed25519KeyPair(ed25519_dalek::Keypair { public: pk.unwrap(), secret: sk.unwrap() })) + // See comment in from_bytes() in C25519KeyPair for an explanation of the copy of the secret here. + let pk = pk.unwrap(); + let sk = sk.unwrap(); + let sk2 = Secret(sk.to_bytes()); + Some(Ed25519KeyPair(ed25519_dalek::Keypair { public: pk, secret: sk }, sk2)) } else { None } @@ -99,8 +121,8 @@ impl Ed25519KeyPair { } #[inline(always)] - pub fn secret_bytes(&self) -> Secret<{ ED25519_SECRET_KEY_SIZE }> { - Secret(self.0.secret.to_bytes()) + pub fn secret_bytes(&self) -> &Secret<32> { + &self.1 } pub fn sign(&self, msg: &[u8]) -> [u8; ED25519_SIGNATURE_SIZE] { @@ -126,7 +148,7 @@ impl Ed25519KeyPair { impl Clone for Ed25519KeyPair { fn clone(&self) -> Self { - Self(ed25519_dalek::Keypair::from_bytes(&self.0.to_bytes()).unwrap()) + Self(ed25519_dalek::Keypair::from_bytes(&self.0.to_bytes()).unwrap(), self.1.clone()) } } diff --git a/zerotier-core-crypto/src/secret.rs b/zerotier-core-crypto/src/secret.rs index ac5329653..787fd931e 100644 --- a/zerotier-core-crypto/src/secret.rs +++ b/zerotier-core-crypto/src/secret.rs @@ -36,7 +36,7 @@ impl Secret { #[inline(always)] pub fn as_bytes(&self) -> &[u8; L] { - return &self.0; + &self.0 } /// Get a clone of the first N bytes of this secret. diff --git a/zerotier-network-hypervisor/src/util/gate.rs b/zerotier-network-hypervisor/src/util/gate.rs index 0825068a9..f0fcb7b88 100644 --- a/zerotier-network-hypervisor/src/util/gate.rs +++ b/zerotier-network-hypervisor/src/util/gate.rs @@ -61,14 +61,7 @@ impl AtomicIntervalGate { if (time - prev_time) < FREQ { false } else { - loop { - let pt = self.0.swap(time, Ordering::AcqRel); - if pt <= time { - break; - } else { - time = pt; - } - } + self.0.store(time, Ordering::Release); true } } diff --git a/zerotier-network-hypervisor/src/util/mod.rs b/zerotier-network-hypervisor/src/util/mod.rs index 502b02063..7023583f5 100644 --- a/zerotier-network-hypervisor/src/util/mod.rs +++ b/zerotier-network-hypervisor/src/util/mod.rs @@ -23,11 +23,11 @@ pub(crate) fn byte_array_range(a: &mut [u8; A]) -> &mut [u8; LEN] { - assert!((START + LEN) <= A); - unsafe { &mut *a.as_mut_ptr().add(START).cast::<[u8; LEN]>() } -} +//#[inline(always)] +//pub(crate) fn byte_array_range_mut(a: &mut [u8; A]) -> &mut [u8; LEN] { +// assert!((START + LEN) <= A); +// unsafe { &mut *a.as_mut_ptr().add(START).cast::<[u8; LEN]>() } +//} /// Non-cryptographic 64-bit bit mixer for things like local hashing. #[inline(always)] diff --git a/zerotier-network-hypervisor/src/vl1/identity.rs b/zerotier-network-hypervisor/src/vl1/identity.rs index 5bf477858..a9abce0b6 100644 --- a/zerotier-network-hypervisor/src/vl1/identity.rs +++ b/zerotier-network-hypervisor/src/vl1/identity.rs @@ -41,9 +41,20 @@ pub const IDENTITY_ALGORITHM_EC_NIST_P384: u8 = 0x02; /// Bit mask to include all algorithms. pub const IDENTITY_ALGORITHM_ALL: u8 = 0xff; -/// Current sanity limit for the size of a marshaled Identity (can be increased if needed). -pub const MAX_MARSHAL_SIZE: usize = - ADDRESS_SIZE + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_SECRET_KEY_SIZE + P384_SECRET_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE + 16; +/// Current sanity limit for the size of a marshaled Identity +/// This is padded just a little up to 512 and can be increased if new key types are ever added. +pub const MAX_MARSHAL_SIZE: usize = 25 + + ADDRESS_SIZE + + C25519_PUBLIC_KEY_SIZE + + ED25519_PUBLIC_KEY_SIZE + + C25519_SECRET_KEY_SIZE + + ED25519_SECRET_KEY_SIZE + + P384_PUBLIC_KEY_SIZE + + P384_PUBLIC_KEY_SIZE + + P384_SECRET_KEY_SIZE + + P384_SECRET_KEY_SIZE + + P384_ECDSA_SIGNATURE_SIZE + + ED25519_SIGNATURE_SIZE; /// Secret keys associated with NIST P-384 public keys. #[derive(Clone)] @@ -110,6 +121,7 @@ fn concat_arrays_4 Self { + // First generate an identity with just x25519 keys. let mut sha = SHA512::new(); let ed25519 = Ed25519KeyPair::generate(); let ed25519_pub = ed25519.public_bytes(); @@ -137,7 +149,6 @@ impl Identity { sha.reset(); } drop(genmem_pool_obj); - let mut id = Self { address, c25519: c25519_pub, @@ -146,8 +157,11 @@ impl Identity { secret: Some(IdentitySecret { c25519, ed25519, p384: None }), fingerprint: [0_u8; 64], // replaced in upgrade() }; + + // Then "upgrade" to add NIST P-384 keys and compute fingerprint. assert!(id.upgrade().is_ok()); assert!(id.p384.is_some() && id.secret.as_ref().unwrap().p384.is_some()); + id } @@ -194,6 +208,7 @@ impl Identity { return Ok(false); } + /// Get a bit mask of algorithms present in this identity. #[inline(always)] pub fn algorithms(&self) -> u8 { if self.p384.is_some() { @@ -248,8 +263,7 @@ impl Identity { /// NIST P-384 and the result is HMAC(Curve25519 secret, NIST P-384 secret). /// /// Nothing actually uses a 512-bit secret directly, but if the base secret is 512 bits then - /// no entropy is lost when deriving secrets with a KDF. Ciphers like AES use the first 256 bits - /// of these keys. + /// no entropy is lost when deriving smaller secrets with a KDF. pub fn agree(&self, other: &Identity) -> Option> { self.secret.as_ref().and_then(|secret| { let c25519_secret = Secret(SHA512::hash(&secret.c25519.agree(&other.c25519).0)); @@ -267,27 +281,33 @@ impl Identity { /// Sign a message with this identity. /// - /// If legacy_compatibility is true this generates only an ed25519 signature. Otherwise it - /// will generate a signature using both the ed25519 key and the P-384 key if the latter - /// is present in the identity. + /// If legacy_compatibility is true this generates only an ed25519 signature and uses the old + /// format that also includes part of the plaintext hash at the end. The include_algorithms mask + /// will be ignored. Otherwise it will generate a signature for every algorithm with a secret + /// in this identity and that is specified in the include_algorithms bit mask. /// /// A return of None happens if we don't have our secret key(s) or some other error occurs. - pub fn sign(&self, msg: &[u8], legacy_compatibility: bool) -> Option> { + pub fn sign(&self, msg: &[u8], include_algorithms: u8, legacy_compatibility: bool) -> Option> { if self.secret.is_some() { let secret = self.secret.as_ref().unwrap(); if legacy_compatibility { Some(secret.ed25519.sign_zt(msg).to_vec()) - } else if secret.p384.is_some() { - let mut tmp: Vec = Vec::with_capacity(1 + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE); - tmp.push(IDENTITY_ALGORITHM_X25519 | IDENTITY_ALGORITHM_EC_NIST_P384); - let _ = tmp.write_all(&secret.p384.as_ref().unwrap().ecdsa.sign(msg)); - let _ = tmp.write_all(&secret.ed25519.sign(msg)); - Some(tmp) } else { - let mut tmp: Vec = Vec::with_capacity(1 + ED25519_SIGNATURE_SIZE); - tmp.push(IDENTITY_ALGORITHM_X25519); - let _ = tmp.write_all(&secret.ed25519.sign(msg)); - Some(tmp) + let mut tmp: Vec = Vec::with_capacity(1 + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE); + tmp.push(0); + if secret.p384.is_some() && (include_algorithms & IDENTITY_ALGORITHM_EC_NIST_P384) != 0 { + *tmp.first_mut().unwrap() |= IDENTITY_ALGORITHM_EC_NIST_P384; + let _ = tmp.write_all(&secret.p384.as_ref().unwrap().ecdsa.sign(msg)); + } + if (include_algorithms & IDENTITY_ALGORITHM_X25519) != 0 { + *tmp.first_mut().unwrap() |= IDENTITY_ALGORITHM_X25519; + let _ = tmp.write_all(&secret.ed25519.sign(msg)); + } + if tmp.len() > 1 { + Some(tmp) + } else { + None + } } } else { None @@ -297,33 +317,42 @@ impl Identity { /// Verify a signature against this identity. pub fn verify(&self, msg: &[u8], mut signature: &[u8]) -> bool { if signature.len() == 96 { - // legacy ed25519-only signature with hash included - ed25519_verify(&self.ed25519, signature, msg) + // legacy ed25519-only signature with hash included detected by their unique size. + return ed25519_verify(&self.ed25519, signature, msg); } else if signature.len() > 1 { + // Otherwise we support compound signatures. Note that it's possible for there to be + // unknown algorithms here if we ever add e.g. a PQ signature scheme and older nodes + // don't support it, and therefore it's valid if all algorithms that are present and + // understood pass signature check. The 'passed' variable makes sure we can't pass without + // verifying at least one signature. If any present and understood algorithm fails the + // whole check fails, so you can't have one good and one bad signature. let algorithms = signature[0]; signature = &signature[1..]; - let mut ok = true; - let mut checked = false; - if ok && (algorithms & IDENTITY_ALGORITHM_EC_NIST_P384) != 0 && signature.len() >= P384_ECDSA_SIGNATURE_SIZE && self.p384.is_some() { - ok = self.p384.as_ref().unwrap().ecdsa.verify(msg, &signature[..P384_ECDSA_SIGNATURE_SIZE]); + let mut passed = false; // makes sure we can't pass with an empty signature! + if (algorithms & IDENTITY_ALGORITHM_EC_NIST_P384) != 0 && signature.len() >= P384_ECDSA_SIGNATURE_SIZE && self.p384.is_some() { + if !self.p384.as_ref().unwrap().ecdsa.verify(msg, &signature[..P384_ECDSA_SIGNATURE_SIZE]) { + return false; + } signature = &signature[P384_ECDSA_SIGNATURE_SIZE..]; - checked = true; + passed = true; } - if ok && (algorithms & IDENTITY_ALGORITHM_X25519) != 0 && signature.len() >= ED25519_SIGNATURE_SIZE { - ok = ed25519_verify(&self.ed25519, &signature[..ED25519_SIGNATURE_SIZE], msg); - signature = &signature[ED25519_SIGNATURE_SIZE..]; - checked = true; + if (algorithms & IDENTITY_ALGORITHM_X25519) != 0 && signature.len() >= ED25519_SIGNATURE_SIZE { + if !ed25519_verify(&self.ed25519, &signature[..ED25519_SIGNATURE_SIZE], msg) { + return false; + } + //signature = &signature[ED25519_SIGNATURE_SIZE..]; + passed = true; } - checked && ok + return passed; } else { - false + return false; } } #[inline(always)] pub fn to_bytes(&self, include_algorithms: u8, include_private: bool) -> Buffer { let mut b: Buffer = Buffer::new(); - self.marshal(&mut b, include_algorithms, include_private).expect("internal error marshaling Identity"); + assert!(self.marshal(&mut b, include_algorithms, include_private).is_ok()); b } @@ -335,16 +364,19 @@ impl Identity { let secret = self.secret.as_ref(); buf.append_bytes_fixed(&self.address.to_bytes())?; - buf.append_u8(0x00)?; // LEGACY: 0x00 here for backward compatibility - buf.append_bytes_fixed(&self.c25519)?; - buf.append_bytes_fixed(&self.ed25519)?; - if include_private && secret.is_some() { - let secret = secret.unwrap(); - buf.append_u8((C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE) as u8)?; - buf.append_bytes_fixed(&secret.c25519.secret_bytes().0)?; - buf.append_bytes_fixed(&secret.ed25519.secret_bytes().0)?; - } else { - buf.append_u8(0)?; + + if (algorithms & IDENTITY_ALGORITHM_X25519) != 0 { + buf.append_u8(0x00)?; // 0x00 is used for X25519 for backward compatibility with v0 identities + buf.append_bytes_fixed(&self.c25519)?; + buf.append_bytes_fixed(&self.ed25519)?; + if include_private && secret.is_some() { + let secret = secret.unwrap(); + buf.append_u8((C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE) as u8)?; + buf.append_bytes_fixed(&secret.c25519.secret_bytes().0)?; + buf.append_bytes_fixed(&secret.ed25519.secret_bytes().0)?; + } else { + buf.append_u8(0)?; + } } if (algorithms & IDENTITY_ALGORITHM_EC_NIST_P384) != 0 && self.p384.is_some() { @@ -529,37 +561,48 @@ impl Identity { }) } - /// Marshal this identity as a string with options to control which ciphers are included and whether private keys are included. + /// Marshal this identity as a string. + /// + /// The include_algorithms bitmap controls which algorithms will be included, provided we have them. + /// If include_private is true private keys will be included, again if we have them. pub fn to_string_with_options(&self, include_algorithms: u8, include_private: bool) -> String { - if include_private && self.secret.is_some() { - let secret = self.secret.as_ref().unwrap(); - if (include_algorithms & IDENTITY_ALGORITHM_EC_NIST_P384) == IDENTITY_ALGORITHM_EC_NIST_P384 && secret.p384.is_some() && self.p384.is_some() { - let p384_secret = secret.p384.as_ref().unwrap(); - let p384 = self.p384.as_ref().unwrap(); - let p384_secret_joined: [u8; P384_SECRET_KEY_SIZE + P384_SECRET_KEY_SIZE] = concat_arrays_2(p384_secret.ecdh.secret_key_bytes().as_bytes(), p384_secret.ecdsa.secret_key_bytes().as_bytes()); - let p384_joined: [u8; P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE] = concat_arrays_4(p384.ecdh.as_bytes(), p384.ecdsa.as_bytes(), &p384.ecdsa_self_signature, &p384.ed25519_self_signature); - format!( - "{}:0:{}{}:{}{}:2:{}:{}", - self.address.to_string(), - hex::to_string(&self.c25519), - hex::to_string(&self.ed25519), - hex::to_string(&secret.c25519.secret_bytes().0), - hex::to_string(&secret.ed25519.secret_bytes().0), - base64::encode_config(p384_joined, base64::URL_SAFE_NO_PAD), - base64::encode_config(p384_secret_joined, base64::URL_SAFE_NO_PAD) - ) - } else { - format!("{}:0:{}{}:{}{}", self.address.to_string(), hex::to_string(&self.c25519), hex::to_string(&self.ed25519), hex::to_string(&secret.c25519.secret_bytes().0), hex::to_string(&secret.ed25519.secret_bytes().0)) + let include_p384 = self.p384.is_some() && ((include_algorithms & IDENTITY_ALGORITHM_EC_NIST_P384) != 0); + + let mut s = String::with_capacity(MAX_MARSHAL_SIZE * 2); + s.push_str(self.address.to_string().as_str()); + + if (include_algorithms & IDENTITY_ALGORITHM_X25519) != 0 { + s.push_str(":0:"); // 0 used for x25519 for legacy reasons just like in marshal() + s.push_str(hex::to_string(&self.c25519).as_str()); + s.push_str(hex::to_string(&self.ed25519).as_str()); + if self.secret.is_some() && include_private { + let secret = self.secret.as_ref().unwrap(); + s.push(':'); + s.push_str(hex::to_string(secret.c25519.secret_bytes().as_bytes()).as_str()); + s.push_str(hex::to_string(secret.ed25519.secret_bytes().as_bytes()).as_str()); + } else if include_p384 { + s.push(':'); } - } else { - self.p384.as_ref().map_or_else( - || format!("{}:0:{}{}", self.address.to_string(), hex::to_string(&self.c25519), hex::to_string(&self.ed25519)), - |p384| { - let p384_joined: [u8; P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE] = concat_arrays_4(p384.ecdh.as_bytes(), p384.ecdsa.as_bytes(), &p384.ecdsa_self_signature, &p384.ed25519_self_signature); - format!("{}:0:{}{}::2:{}", self.address.to_string(), hex::to_string(&self.c25519), hex::to_string(&self.ed25519), base64::encode_config(p384_joined, base64::URL_SAFE_NO_PAD)) - }, - ) } + + if include_p384 { + let p384 = self.p384.as_ref().unwrap(); + + s.push_str(":2:"); // 2 == IDENTITY_ALGORITHM_EC_NIST_P384 + let p384_joined: [u8; P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE] = concat_arrays_4(p384.ecdh.as_bytes(), p384.ecdsa.as_bytes(), &p384.ecdsa_self_signature, &p384.ed25519_self_signature); + s.push_str(base64::encode_config(p384_joined, base64::URL_SAFE_NO_PAD).as_str()); + if self.secret.is_some() && include_private { + let secret = self.secret.as_ref().unwrap(); + if secret.p384.is_some() { + let p384_secret = secret.p384.as_ref().unwrap(); + let p384_secret_joined: [u8; P384_SECRET_KEY_SIZE + P384_SECRET_KEY_SIZE] = concat_arrays_2(p384_secret.ecdh.secret_key_bytes().as_bytes(), p384_secret.ecdsa.secret_key_bytes().as_bytes()); + s.push(':'); + s.push_str(base64::encode_config(p384_secret_joined, base64::URL_SAFE_NO_PAD).as_str()); + } + } + } + + s } /// Get this identity in string form with all ciphers and with secrets (if present) @@ -618,8 +661,12 @@ impl FromStr for Identity { ptr += 1; } - let keys = - [hex::from_string(keys[0].unwrap_or("")), hex::from_string(keys[1].unwrap_or("")), base64::decode_config(keys[2].unwrap_or(""), base64::URL_SAFE_NO_PAD).unwrap_or_else(|_| Vec::new()), base64::decode_config(keys[3].unwrap_or(""), base64::URL_SAFE_NO_PAD).unwrap_or_else(|_| Vec::new())]; + let keys = [ + hex::from_string(keys[0].unwrap_or("")), + hex::from_string(keys[1].unwrap_or("")), + base64::decode_config(keys[2].unwrap_or(""), base64::URL_SAFE_NO_PAD).unwrap_or_else(|_| Vec::new()), + base64::decode_config(keys[3].unwrap_or(""), base64::URL_SAFE_NO_PAD).unwrap_or_else(|_| Vec::new()), + ]; if keys[0].len() != C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE { return Err(InvalidFormatError); } @@ -868,6 +915,7 @@ lazy_static! { /// Purge the memory pool used to verify identities. This can be called periodically /// from the maintenance function to prevent memory buildup from bursts of identity /// verification. +#[allow(unused)] #[inline(always)] pub(crate) fn purge_verification_memory_pool() { ADDRESS_DERVIATION_MEMORY_POOL.purge(); @@ -926,6 +974,7 @@ mod tests { for id_str in GOOD_V0_IDENTITIES { let mut id = Identity::from_str(id_str).unwrap(); + assert_eq!(id.to_string_with_options(IDENTITY_ALGORITHM_ALL, true).as_str(), id_str); assert!(id.validate_identity()); assert!(id.p384.is_none()); @@ -956,6 +1005,7 @@ mod tests { } for id_str in GOOD_V1_IDENTITIES { let id = Identity::from_str(id_str).unwrap(); + assert_eq!(id.to_string_with_options(IDENTITY_ALGORITHM_ALL, false).as_str(), id_str); assert!(id.validate_identity()); assert!(id.p384.is_some());