From e934b820b92124947c4a443908815db254b30bb2 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Fri, 17 Nov 2023 20:34:03 -0700 Subject: [PATCH] Replace `secrecy` with `zeroize`; MSRV 1.60 (#54) The `secrecy` version is out-of-date and represents another dependency which can be eliminated by using a zeroize-on-drop approach. --- .github/workflows/cryptouri.yml | 2 +- Cargo.lock | 13 ++------ Cargo.toml | 4 +-- README.md | 4 +-- src/encoding.rs | 6 ++-- src/hash/sha2.rs | 1 - src/lib.rs | 25 +++++--------- src/parts.rs | 19 +++++++---- src/secret_key.rs | 2 -- src/secret_key/aesgcm.rs | 29 +++++++++------- src/secret_key/chacha20poly1305.rs | 27 ++++++++------- src/secret_key/ed25519.rs | 27 ++++++++------- src/secret_key/hkdf.rs | 53 ++++++++++++++++-------------- src/signature/ed25519.rs | 1 - tests/secret_key_test.rs | 11 ++----- 15 files changed, 111 insertions(+), 113 deletions(-) diff --git a/.github/workflows/cryptouri.yml b/.github/workflows/cryptouri.yml index 7539d7b..90137b5 100644 --- a/.github/workflows/cryptouri.yml +++ b/.github/workflows/cryptouri.yml @@ -20,7 +20,7 @@ jobs: strategy: matrix: include: - - rust: 1.56.0 # MSRV + - rust: 1.60.0 # MSRV - rust: stable steps: diff --git a/Cargo.lock b/Cargo.lock index c0687f1..fc5724a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,16 +6,7 @@ version = 3 name = "cryptouri" version = "0.5.0-pre" dependencies = [ - "secrecy", "subtle-encoding", -] - -[[package]] -name = "secrecy" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9182278ed645df3477a9c27bfee0621c621aa16f6972635f7f795dae3d81070f" -dependencies = [ "zeroize", ] @@ -30,6 +21,6 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.1.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/Cargo.toml b/Cargo.toml index 496a27c..49fef50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,11 +10,11 @@ readme = "README.md" categories = ["cryptography", "encoding"] keywords = ["bech32", "cryptography", "keys", "security", "uri"] edition = "2021" -rust-version = "1.56" +rust-version = "1.60" [badges] travis-ci = { repository = "cryptouri/cryptouri.rs" } [dependencies] -secrecy = "0.6" subtle-encoding = { version = "0.5.1", features = ["bech32-preview"] } +zeroize = "1.7" diff --git a/README.md b/README.md index be58e25..0f9d138 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ CryptoURIs which have been mis-transcribed will fail to decode. ## Minimum Supported Rust Version -- Rust **1.56+** +- Rust **1.60+** ## Code of Conduct @@ -74,7 +74,7 @@ The **cryptouri** Rust crate is dual licensed under your choice of either of: [safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg [safety-link]: https://github.com/rust-secure-code/safety-dance/ [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[msrv-image]: https://img.shields.io/badge/rustc-1.56+-blue.svg +[msrv-image]: https://img.shields.io/badge/rustc-1.60+-blue.svg [//]: # (links) diff --git a/src/encoding.rs b/src/encoding.rs index 3ca5ac1..b9113ec 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -107,7 +107,6 @@ macro_rules! impl_encodable_secret_key { impl crate::encoding::Encodable for $name { #[inline] fn to_uri_string(&self) -> String { - use secrecy::ExposeSecret; use subtle_encoding::bech32::{self, Bech32}; Bech32::new( bech32::DEFAULT_CHARSET, @@ -115,13 +114,12 @@ macro_rules! impl_encodable_secret_key { ) .encode( $crate::encoding::URI_ENCODING.secret_key_scheme.to_owned() + $alg, - &self.expose_secret()[..], + &self.as_ref()[..], ) } #[inline] fn to_dasherized_string(&self) -> String { - use secrecy::ExposeSecret; use subtle_encoding::bech32::{self, Bech32}; Bech32::new( bech32::DEFAULT_CHARSET, @@ -132,7 +130,7 @@ macro_rules! impl_encodable_secret_key { .secret_key_scheme .to_owned() + $alg, - &self.expose_secret()[..], + &self.as_ref()[..], ) } } diff --git a/src/hash/sha2.rs b/src/hash/sha2.rs index 2c490ac..6242a79 100644 --- a/src/hash/sha2.rs +++ b/src/hash/sha2.rs @@ -1,7 +1,6 @@ //! SHA2 hash types use crate::{algorithm::SHA256_ALG_ID, error::Error}; -use std::convert::{TryFrom, TryInto}; /// Size of a SHA-256 hash pub const SHA256_HASH_SIZE: usize = 32; diff --git a/src/lib.rs b/src/lib.rs index 844a68c..6471597 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,8 +2,7 @@ //! with Bech32 encoding/checksums #![doc( - html_logo_url = "https://avatars3.githubusercontent.com/u/40766087?u=0267cf8b7fe892bbf35b6114d9eb48adc057d6ff", - html_root_url = "https://docs.rs/cryptouri/0.4.0" + html_logo_url = "https://avatars3.githubusercontent.com/u/40766087?u=0267cf8b7fe892bbf35b6114d9eb48adc057d6ff" )] #![forbid(unsafe_code)] #![warn(missing_docs, rust_2018_idioms, unused_qualifications)] @@ -29,7 +28,6 @@ use crate::{ encoding::{Encoding, DASHERIZED_ENCODING, URI_ENCODING}, parts::Parts, }; -use secrecy::{ExposeSecret, SecretString}; /// `CryptoUri`: URI-based format for encoding cryptographic objects pub struct CryptoUri { @@ -37,7 +35,7 @@ pub struct CryptoUri { kind: CryptoUriKind, /// URI fragment (i.e. everything after `#`) - fragment: Option, + fragment: Option, } /// Kinds of `CryptoUri`s @@ -64,12 +62,12 @@ impl CryptoUri { let kind = if parts.prefix.starts_with(encoding.hash_scheme) { CryptoUriKind::Hash(Hash::new( &parts.prefix[encoding.hash_scheme.len()..], - parts.data.expose_secret(), + parts.data.as_ref(), )?) } else if parts.prefix.starts_with(encoding.public_key_scheme) { CryptoUriKind::PublicKey(PublicKey::new( &parts.prefix[encoding.public_key_scheme.len()..], - parts.data.expose_secret(), + parts.data.as_ref(), )?) } else if parts.prefix.starts_with(encoding.secret_key_scheme) { let alg_id = &parts.prefix[encoding.secret_key_scheme.len()..]; @@ -77,17 +75,14 @@ impl CryptoUri { if alg_id.contains(encoding.combine) { // Multi-algorithm combination (e.g. KDF) let alg_ids = alg_id.split(encoding.combine).collect::>(); - CryptoUriKind::SecretKey(SecretKey::new_combination( - &alg_ids, - parts.data.expose_secret(), - )?) + CryptoUriKind::SecretKey(SecretKey::new_combination(&alg_ids, parts.data.as_ref())?) } else { - CryptoUriKind::SecretKey(SecretKey::new(alg_id, parts.data.expose_secret())?) + CryptoUriKind::SecretKey(SecretKey::new(alg_id, parts.data.as_ref())?) } } else if parts.prefix.starts_with(encoding.signature_scheme) { CryptoUriKind::Signature(Signature::new( &parts.prefix[encoding.signature_scheme.len()..], - parts.data.expose_secret(), + parts.data.as_ref(), )?) } else { return Err(Error::Scheme(parts.prefix.to_owned())); @@ -95,7 +90,7 @@ impl CryptoUri { Ok(Self { kind, - fragment: parts.fragment, + fragment: parts.fragment.clone(), }) } @@ -168,9 +163,7 @@ impl CryptoUri { /// Obtain the fragment for this URI (i.e. everything after `#`) pub fn fragment(&self) -> Option<&str> { - self.fragment - .as_ref() - .map(|fragment| fragment.expose_secret().as_ref()) + self.fragment.as_ref().map(|fragment| fragment.as_ref()) } } diff --git a/src/parts.rs b/src/parts.rs index 556139f..fecfed6 100644 --- a/src/parts.rs +++ b/src/parts.rs @@ -1,8 +1,8 @@ //! CryptoURI parts use crate::{encoding::Encoding, error::Error}; -use secrecy::{Secret, SecretString, SecretVec}; use subtle_encoding::bech32::{self, Bech32}; +use zeroize::Zeroize; /// Parts of a CryptoURI pub(crate) struct Parts { @@ -10,10 +10,10 @@ pub(crate) struct Parts { pub(crate) prefix: String, /// Data (i.e. public or private key or key fingerprint) - pub(crate) data: SecretVec, + pub(crate) data: Vec, /// URI fragment (i.e. comment) - pub(crate) fragment: Option, + pub(crate) fragment: Option, } impl Parts { @@ -30,8 +30,8 @@ impl Parts { return Ok(Self { prefix, - data: Secret::new(data), - fragment: Some(Secret::new(fragment)), + data, + fragment: Some(fragment), }); } } @@ -42,8 +42,15 @@ impl Parts { Ok(Self { prefix, - data: Secret::new(data), + data, fragment: None, }) } } + +impl Drop for Parts { + fn drop(&mut self) { + self.data.zeroize(); + self.fragment.zeroize(); + } +} diff --git a/src/secret_key.rs b/src/secret_key.rs index 028fe9c..bb59b07 100644 --- a/src/secret_key.rs +++ b/src/secret_key.rs @@ -11,7 +11,6 @@ pub use self::{ ed25519::Ed25519SecretKey, hkdf::HkdfSha256Key, }; -pub use secrecy::ExposeSecret; use crate::{ algorithm::{ @@ -22,7 +21,6 @@ use crate::{ error::Error, }; use std::{ - convert::TryInto, fmt::{self, Display}, str::FromStr, }; diff --git a/src/secret_key/aesgcm.rs b/src/secret_key/aesgcm.rs index 506323a..3db69a6 100644 --- a/src/secret_key/aesgcm.rs +++ b/src/secret_key/aesgcm.rs @@ -4,8 +4,7 @@ use crate::{ algorithm::{AES128GCM_ALG_ID, AES256GCM_ALG_ID}, error::Error, }; -use secrecy::{DebugSecret, ExposeSecret, Secret}; -use std::convert::{TryFrom, TryInto}; +use zeroize::{Zeroize, ZeroizeOnDrop}; /// Size of an AES-128 key in bytes pub const AES128_KEY_SIZE: usize = 16; @@ -15,21 +14,33 @@ pub const AES256_KEY_SIZE: usize = 32; /// AES-128 in Galois/Counter Mode (GCM) #[derive(Clone)] -pub struct Aes128GcmKey(Secret<[u8; AES128_KEY_SIZE]>); +pub struct Aes128GcmKey(Box<[u8; AES128_KEY_SIZE]>); /// AES-256 in Galois/Counter Mode (GCM) #[derive(Clone)] -pub struct Aes256GcmKey(Secret<[u8; AES256_KEY_SIZE]>); +pub struct Aes256GcmKey(Box<[u8; AES256_KEY_SIZE]>); macro_rules! impl_aes_gcm_key { ($name:ident, $key_size:expr, $desc:expr) => { + impl AsRef<[u8; $key_size]> for $name { + fn as_ref(&self) -> &[u8; $key_size] { + &self.0 + } + } + + impl Drop for $name { + fn drop(&mut self) { + self.0.zeroize(); + } + } + impl TryFrom<&[u8]> for $name { type Error = Error; fn try_from(slice: &[u8]) -> Result { slice .try_into() - .map(|bytes| $name(Secret::new(bytes))) + .map(|bytes| $name(Box::new(bytes))) .map_err(|_| Error::Length { actual: slice.len(), expected: $key_size, @@ -37,13 +48,7 @@ macro_rules! impl_aes_gcm_key { } } - impl DebugSecret for $name {} - - impl ExposeSecret<[u8; $key_size]> for $name { - fn expose_secret(&self) -> &[u8; $key_size] { - self.0.expose_secret() - } - } + impl ZeroizeOnDrop for $name {} }; } diff --git a/src/secret_key/chacha20poly1305.rs b/src/secret_key/chacha20poly1305.rs index a1fdd02..4c7a48d 100644 --- a/src/secret_key/chacha20poly1305.rs +++ b/src/secret_key/chacha20poly1305.rs @@ -1,15 +1,26 @@ //! ChaCha20Poly1305 AEAD (RFC 8439) use crate::{algorithm::CHACHA20POLY1305_ALG_ID, error::Error}; -use secrecy::{DebugSecret, ExposeSecret, Secret}; -use std::convert::{TryFrom, TryInto}; +use zeroize::{Zeroize, ZeroizeOnDrop}; /// Size of a ChaCha20Poly1305 key in bytes pub const CHACHA20POLY1305_KEY_SIZE: usize = 32; /// ChaCha20Poly1305 encryption key #[derive(Clone)] -pub struct ChaCha20Poly1305Key(Secret<[u8; CHACHA20POLY1305_KEY_SIZE]>); +pub struct ChaCha20Poly1305Key(Box<[u8; CHACHA20POLY1305_KEY_SIZE]>); + +impl AsRef<[u8; CHACHA20POLY1305_KEY_SIZE]> for ChaCha20Poly1305Key { + fn as_ref(&self) -> &[u8; CHACHA20POLY1305_KEY_SIZE] { + &self.0 + } +} + +impl Drop for ChaCha20Poly1305Key { + fn drop(&mut self) { + self.0.zeroize(); + } +} impl TryFrom<&[u8]> for ChaCha20Poly1305Key { type Error = Error; @@ -17,7 +28,7 @@ impl TryFrom<&[u8]> for ChaCha20Poly1305Key { fn try_from(slice: &[u8]) -> Result { slice .try_into() - .map(|bytes| ChaCha20Poly1305Key(Secret::new(bytes))) + .map(|bytes| ChaCha20Poly1305Key(Box::new(bytes))) .map_err(|_| Error::Length { actual: slice.len(), expected: 32, @@ -25,12 +36,6 @@ impl TryFrom<&[u8]> for ChaCha20Poly1305Key { } } -impl DebugSecret for ChaCha20Poly1305Key {} - -impl ExposeSecret<[u8; CHACHA20POLY1305_KEY_SIZE]> for ChaCha20Poly1305Key { - fn expose_secret(&self) -> &[u8; CHACHA20POLY1305_KEY_SIZE] { - self.0.expose_secret() - } -} +impl ZeroizeOnDrop for ChaCha20Poly1305Key {} impl_encodable_secret_key!(ChaCha20Poly1305Key, CHACHA20POLY1305_ALG_ID); diff --git a/src/secret_key/ed25519.rs b/src/secret_key/ed25519.rs index c9e59d3..60822d1 100644 --- a/src/secret_key/ed25519.rs +++ b/src/secret_key/ed25519.rs @@ -1,15 +1,26 @@ //! The Ed25519 digital signature algorithm use crate::{algorithm::ED25519_ALG_ID, error::Error}; -use secrecy::{DebugSecret, ExposeSecret, Secret}; -use std::convert::{TryFrom, TryInto}; +use zeroize::{Zeroize, ZeroizeOnDrop}; /// Size of an Ed25519 secret key pub const ED25519_SEC_KEY_SIZE: usize = 32; /// Ed25519 secret key (i.e. private scalar) #[derive(Clone)] -pub struct Ed25519SecretKey(Secret<[u8; ED25519_SEC_KEY_SIZE]>); +pub struct Ed25519SecretKey(Box<[u8; ED25519_SEC_KEY_SIZE]>); + +impl AsRef<[u8; ED25519_SEC_KEY_SIZE]> for Ed25519SecretKey { + fn as_ref(&self) -> &[u8; ED25519_SEC_KEY_SIZE] { + &self.0 + } +} + +impl Drop for Ed25519SecretKey { + fn drop(&mut self) { + self.0.zeroize(); + } +} impl TryFrom<&[u8]> for Ed25519SecretKey { type Error = Error; @@ -17,7 +28,7 @@ impl TryFrom<&[u8]> for Ed25519SecretKey { fn try_from(slice: &[u8]) -> Result { slice .try_into() - .map(|bytes| Ed25519SecretKey(Secret::new(bytes))) + .map(|bytes| Ed25519SecretKey(Box::new(bytes))) .map_err(|_| Error::Length { actual: slice.len(), expected: 32, @@ -25,12 +36,6 @@ impl TryFrom<&[u8]> for Ed25519SecretKey { } } -impl DebugSecret for Ed25519SecretKey {} - -impl ExposeSecret<[u8; ED25519_SEC_KEY_SIZE]> for Ed25519SecretKey { - fn expose_secret(&self) -> &[u8; ED25519_SEC_KEY_SIZE] { - self.0.expose_secret() - } -} +impl ZeroizeOnDrop for Ed25519SecretKey {} impl_encodable_secret_key!(Ed25519SecretKey, ED25519_ALG_ID); diff --git a/src/secret_key/hkdf.rs b/src/secret_key/hkdf.rs index 52ef8aa..a8c4a2e 100644 --- a/src/secret_key/hkdf.rs +++ b/src/secret_key/hkdf.rs @@ -6,8 +6,7 @@ use crate::{ encoding::{Encodable, DASHERIZED_ENCODING, URI_ENCODING}, error::Error, }; -use secrecy::{DebugSecret, ExposeSecret, Secret}; -use std::convert::{TryFrom, TryInto}; +use zeroize::{Zeroize, ZeroizeOnDrop}; /// Size of an HKDF-SHA-256 secret key pub const HKDFSHA256_KEY_SIZE: usize = 32; @@ -16,7 +15,7 @@ pub const HKDFSHA256_KEY_SIZE: usize = 32; #[derive(Clone)] pub struct HkdfSha256Key { /// HKDF input key material - ikm: Secret<[u8; HKDFSHA256_KEY_SIZE]>, + ikm: Box<[u8; HKDFSHA256_KEY_SIZE]>, /// Key type to derive (if specified) derived_alg: Option, @@ -40,28 +39,15 @@ impl HkdfSha256Key { } } -impl TryFrom<&[u8]> for HkdfSha256Key { - type Error = Error; - - fn try_from(slice: &[u8]) -> Result { - slice - .try_into() - .map(|bytes| HkdfSha256Key { - ikm: Secret::new(bytes), - derived_alg: None, - }) - .map_err(|_| Error::Length { - actual: slice.len(), - expected: 32, - }) +impl AsRef<[u8; HKDFSHA256_KEY_SIZE]> for HkdfSha256Key { + fn as_ref(&self) -> &[u8; HKDFSHA256_KEY_SIZE] { + &self.ikm } } -impl DebugSecret for HkdfSha256Key {} - -impl ExposeSecret<[u8; HKDFSHA256_KEY_SIZE]> for HkdfSha256Key { - fn expose_secret(&self) -> &[u8; HKDFSHA256_KEY_SIZE] { - self.ikm.expose_secret() +impl Drop for HkdfSha256Key { + fn drop(&mut self) { + self.ikm.zeroize(); } } @@ -78,7 +64,7 @@ impl Encodable for HkdfSha256Key { use subtle_encoding::bech32::{self, Bech32}; Bech32::new(bech32::DEFAULT_CHARSET, URI_ENCODING.delimiter).encode( URI_ENCODING.secret_key_scheme.to_owned() + &alg_id, - &self.expose_secret()[..], + &self.as_ref()[..], ) } @@ -94,7 +80,26 @@ impl Encodable for HkdfSha256Key { use subtle_encoding::bech32::{self, Bech32}; Bech32::new(bech32::DEFAULT_CHARSET, DASHERIZED_ENCODING.delimiter).encode( DASHERIZED_ENCODING.secret_key_scheme.to_owned() + &alg_id, - &self.expose_secret()[..], + &self.as_ref()[..], ) } } + +impl TryFrom<&[u8]> for HkdfSha256Key { + type Error = Error; + + fn try_from(slice: &[u8]) -> Result { + slice + .try_into() + .map(|bytes| HkdfSha256Key { + ikm: Box::new(bytes), + derived_alg: None, + }) + .map_err(|_| Error::Length { + actual: slice.len(), + expected: 32, + }) + } +} + +impl ZeroizeOnDrop for HkdfSha256Key {} diff --git a/src/signature/ed25519.rs b/src/signature/ed25519.rs index ce81068..7f91e43 100644 --- a/src/signature/ed25519.rs +++ b/src/signature/ed25519.rs @@ -1,7 +1,6 @@ //! Ed25519 signatures use crate::{algorithm::ED25519_ALG_ID, error::Error}; -use core::convert::TryFrom; /// Size of an Ed25519 signature pub const ED25519_SIGNATURE_SIZE: usize = 64; diff --git a/tests/secret_key_test.rs b/tests/secret_key_test.rs index aa5ff44..4cf136c 100644 --- a/tests/secret_key_test.rs +++ b/tests/secret_key_test.rs @@ -3,25 +3,18 @@ macro_rules! secret_key_test { mod $name { use cryptouri::secret_key::$keytype; use cryptouri::{CryptoUri, Encodable}; - use secrecy::ExposeSecret; use std::convert::TryFrom; #[test] fn parse_uri() { let key = CryptoUri::parse_uri($uri).unwrap(); - assert_eq!( - key.secret_key().unwrap().$name().unwrap().expose_secret(), - $bytes - ); + assert_eq!(key.secret_key().unwrap().$name().unwrap().as_ref(), $bytes); } #[test] fn parse_dasherized() { let key = CryptoUri::parse_dasherized($dasherized).unwrap(); - assert_eq!( - key.secret_key().unwrap().$name().unwrap().expose_secret(), - $bytes - ); + assert_eq!(key.secret_key().unwrap().$name().unwrap().as_ref(), $bytes); } #[test]