diff --git a/Cargo.toml b/Cargo.toml index 10e9c87..4e67413 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,17 +16,20 @@ name = "clevis" path = "src/main.rs" [dependencies] -base64 = "0.21.0" -clap = { version = "4.2.1", features = ["derive"] } +clap = { version = "4.4.7", features = ["derive"] } env_logger = "0.10.0" -josekit = "0.8.2" +josekit = "0.8.4" log = "0.4.20" -serde = "1.0.160" -sha2 = "0.10.6" -serde_json = { version = "1.0.95", features = ["preserve_order"] } -ureq = { version = "2.6.2", features = ["json"] } +serde = "1.0.190" +sha2 = "0.10.8" +serde_json = { version = "1.0.108", features = ["preserve_order"] } +ureq = { version = "2.8.0", features = ["json"] } sha1 = "0.10.6" hex = "0.4.3" +p521 = { git = "https://github.com/RustCrypto/elliptic-curves.git" } +p256 = { git = "https://github.com/RustCrypto/elliptic-curves.git" } +base64ct = { version = "1.6.0", features = ["alloc"] } + # vsss-rs = "2.7.1" [features] diff --git a/src/error.rs b/src/error.rs index 60b354d..e3bd542 100644 --- a/src/error.rs +++ b/src/error.rs @@ -11,7 +11,7 @@ pub enum Error { JsonMissingKey(Box), JsonKeyType(Box), Utf8(Utf8Error), - Base64(base64::DecodeError), + Base64(base64ct::Error), Json(serde_json::Error), Jose(josekit::JoseError), VerifyKey, @@ -49,8 +49,8 @@ impl From for Error { } } -impl From for Error { - fn from(value: base64::DecodeError) -> Self { +impl From for Error { + fn from(value: base64ct::Error) -> Self { Self::Base64(value) } } diff --git a/src/jose.rs b/src/jose.rs index eda82b9..72d67e8 100644 --- a/src/jose.rs +++ b/src/jose.rs @@ -1,7 +1,7 @@ use crate::key_exchange::create_encryption_key; use crate::util::{b64_to_bytes, b64_to_str}; use crate::{Error, Result}; -use base64::{prelude::BASE64_URL_SAFE_NO_PAD, Engine}; +use base64ct::{Base64UrlUnpadded, Encoding}; use josekit::jwe::alg::ecdh_es::EcdhEsJweAlgorithm; use josekit::jwe::{self, JweHeader}; use josekit::jwk::Jwk; @@ -17,6 +17,8 @@ use std::ops::Deref; use std::{sync::OnceLock, time::Duration}; /// Representation of a tang advertisment response which is a JWS of available keys. +/// +/// This is what is produced when you GET `tang_url/adv`. #[derive(Deserialize)] pub struct Advertisment { #[serde(deserialize_with = "b64_to_str")] @@ -34,16 +36,25 @@ impl Advertisment { let verifier = get_verifier(verify_jwk)?; // B64 is 4/3 data length, plus a `.` - let verify_len = ((self.payload.len() + self.protected.len()) * 4 / 3) + 1; - let mut to_verify = String::with_capacity(verify_len); + let payload_b64_len = Base64UrlUnpadded::encoded_len(self.payload.as_bytes()); + let protected_b64_len = Base64UrlUnpadded::encoded_len(self.protected.as_bytes()); + let mut to_verify = vec![b'.'; payload_b64_len + 1 + protected_b64_len]; + dbg!(payload_b64_len, protected_b64_len); // The format `b64(HEADER).b64(PAYLOAD)` is used for validation - BASE64_URL_SAFE_NO_PAD.encode_string(&self.protected, &mut to_verify); - to_verify.push('.'); - BASE64_URL_SAFE_NO_PAD.encode_string(&self.payload, &mut to_verify); + Base64UrlUnpadded::encode( + &self.protected.as_bytes(), + &mut to_verify[..protected_b64_len], + ) + .unwrap(); + Base64UrlUnpadded::encode( + &self.payload.as_bytes(), + &mut to_verify[(protected_b64_len + 1)..], + ) + .unwrap(); verifier - .verify(to_verify.as_bytes(), &self.signature) + .verify(&to_verify, &self.signature) .map_err(Into::into) } @@ -67,7 +78,10 @@ impl fmt::Debug for Advertisment { f.debug_struct("Advertisment") .field("payload", &json_field(&self.payload)) .field("protected", &json_field(&self.protected)) - .field("signature", &BASE64_URL_SAFE_NO_PAD.encode(&self.signature)) + .field( + "signature", + &Base64UrlUnpadded::encode_string(&self.signature), + ) .finish() } } @@ -220,12 +234,12 @@ fn make_thumbprint(jwk: &Jwk, alg: ThpHashAlg) -> Result { ThpHashAlg::Sha1 => { let mut hasher = sha1::Sha1::new(); hasher.update(to_hash.as_bytes()); - Ok(BASE64_URL_SAFE_NO_PAD.encode(hasher.finalize())) + Ok(Base64UrlUnpadded::encode_string(&hasher.finalize())) } ThpHashAlg::Sha256 => { let mut hasher = Sha256::new(); hasher.update(to_hash.as_bytes()); - Ok(BASE64_URL_SAFE_NO_PAD.encode(hasher.finalize())) + Ok(Base64UrlUnpadded::encode_string(&hasher.finalize())) } } } diff --git a/src/tang_interface.rs b/src/tang_interface.rs index c4bdc80..212bd53 100644 --- a/src/tang_interface.rs +++ b/src/tang_interface.rs @@ -5,7 +5,7 @@ use std::{sync::OnceLock, time::Duration}; use crate::jose::{Advertisment, JwkSet}; use crate::util::{b64_to_bytes, b64_to_str}; use crate::{Error, Result}; -use base64::{prelude::BASE64_URL_SAFE_NO_PAD, Engine}; +use base64ct::{Base64UrlUnpadded, Encoding}; use josekit::jwk::Jwk; use josekit::jws::alg::ecdsa::EcdsaJwsAlgorithm; use josekit::jws::alg::eddsa::EddsaJwsAlgorithm; @@ -16,6 +16,7 @@ use serde_json::{json, Value}; const DEFAULT_URL: &str = "http://tang.local"; const DEFAULT_TIMEOUT: Duration = Duration::from_secs(120); +/// A tang server connection specification #[derive(Clone, Debug)] pub struct TangClient { url: String, diff --git a/src/util.rs b/src/util.rs index cd60435..07a2f84 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,7 +1,6 @@ use std::collections::BTreeMap; -use base64::prelude::BASE64_URL_SAFE_NO_PAD; -use base64::{prelude::BASE64_STANDARD_NO_PAD, Engine}; +use base64ct::{Base64UrlUnpadded, Encoding}; use serde::de::Error as DeError; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::{Map, Value as JsonValue}; @@ -20,8 +19,6 @@ where D: Deserializer<'de>, { String::deserialize(deserializer).and_then(|string| { - BASE64_URL_SAFE_NO_PAD - .decode(&string) - .map_err(|err| DeError::custom(dbg!(err.to_string()))) + Base64UrlUnpadded::decode_vec(&string).map_err(|err| DeError::custom(err.to_string())) }) }