mirror of
https://github.com/str4d/rage.git
synced 2025-04-02 02:17:42 +03:00
Migrate to secrecy 0.10
This commit is contained in:
parent
a59f0479d0
commit
93fa28ad78
27 changed files with 155 additions and 93 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -1844,9 +1844,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
|||
|
||||
[[package]]
|
||||
name = "pinentry"
|
||||
version = "0.5.1"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72268b7db3a2075ea65d4b93b755d086e99196e327837e690db6559b393a8d69"
|
||||
checksum = "c1ecb857a7b11a03e8872c704d0a1ae1efc20533b3be98338008527a1928ffa6"
|
||||
dependencies = [
|
||||
"log",
|
||||
"nom",
|
||||
|
@ -2344,9 +2344,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "secrecy"
|
||||
version = "0.8.0"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e"
|
||||
checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a"
|
||||
dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
@ -2979,7 +2979,7 @@ version = "0.1.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -48,8 +48,8 @@ cookie-factory = "0.3.1"
|
|||
nom = { version = "7", default-features = false, features = ["alloc"] }
|
||||
|
||||
# Secret management
|
||||
pinentry = "0.5"
|
||||
secrecy = "0.8"
|
||||
pinentry = "0.6"
|
||||
secrecy = "0.10"
|
||||
subtle = "2"
|
||||
zeroize = "1"
|
||||
|
||||
|
|
|
@ -8,9 +8,14 @@ to 1.0.0 are beta releases.
|
|||
|
||||
## [Unreleased]
|
||||
### Added
|
||||
- `age_core::format::is_arbitrary_string`
|
||||
- `age_core::format`:
|
||||
- `FileKey::new`
|
||||
- `FileKey::init_with_mut`
|
||||
- `FileKey::try_init_with_mut`
|
||||
- `is_arbitrary_string`
|
||||
|
||||
### Changed
|
||||
- Migrated to `secrecy 0.10`.
|
||||
- `age::plugin::Connection::unidir_receive` now takes an additional argument to
|
||||
enable handling an optional fourth command.
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ use rand::{
|
|||
distributions::{Distribution, Uniform},
|
||||
thread_rng, RngCore,
|
||||
};
|
||||
use secrecy::{ExposeSecret, Secret};
|
||||
use secrecy::{ExposeSecret, ExposeSecretMut, SecretBox};
|
||||
|
||||
/// The prefix identifying an age stanza.
|
||||
const STANZA_TAG: &str = "-> ";
|
||||
|
@ -14,11 +14,26 @@ const STANZA_TAG: &str = "-> ";
|
|||
pub const FILE_KEY_BYTES: usize = 16;
|
||||
|
||||
/// A file key for encrypting or decrypting an age file.
|
||||
pub struct FileKey(Secret<[u8; FILE_KEY_BYTES]>);
|
||||
pub struct FileKey(SecretBox<[u8; FILE_KEY_BYTES]>);
|
||||
|
||||
impl From<[u8; FILE_KEY_BYTES]> for FileKey {
|
||||
fn from(file_key: [u8; FILE_KEY_BYTES]) -> Self {
|
||||
FileKey(Secret::new(file_key))
|
||||
impl FileKey {
|
||||
/// Creates a file key using a pre-boxed key.
|
||||
pub fn new(file_key: Box<[u8; FILE_KEY_BYTES]>) -> Self {
|
||||
Self(SecretBox::new(file_key))
|
||||
}
|
||||
|
||||
/// Creates a file key using a function that can initialize the key in-place.
|
||||
pub fn init_with_mut(ctr: impl FnOnce(&mut [u8; FILE_KEY_BYTES])) -> Self {
|
||||
Self(SecretBox::init_with_mut(ctr))
|
||||
}
|
||||
|
||||
/// Same as [`Self::init_with_mut`], but the constructor can be fallible.
|
||||
pub fn try_init_with_mut<E>(
|
||||
ctr: impl FnOnce(&mut [u8; FILE_KEY_BYTES]) -> Result<(), E>,
|
||||
) -> Result<Self, E> {
|
||||
let mut file_key = SecretBox::new(Box::new([0; FILE_KEY_BYTES]));
|
||||
ctr(file_key.expose_secret_mut())?;
|
||||
Ok(Self(file_key))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
//! implementations built around the `age-plugin` crate.
|
||||
|
||||
use rand::{thread_rng, Rng};
|
||||
use secrecy::Zeroize;
|
||||
use secrecy::zeroize::Zeroize;
|
||||
use std::env;
|
||||
use std::fmt;
|
||||
use std::io::{self, BufRead, BufReader, Read, Write};
|
||||
|
|
|
@ -175,9 +175,14 @@ impl IdentityPluginV1 for IdentityPlugin {
|
|||
// identities.
|
||||
let _ = callbacks.message("This identity does nothing!")?;
|
||||
file_keys.entry(file_index).or_insert_with(|| {
|
||||
Ok(FileKey::from(
|
||||
TryInto::<[u8; 16]>::try_into(&stanza.body[..]).unwrap(),
|
||||
))
|
||||
FileKey::try_init_with_mut(|file_key| {
|
||||
if stanza.body.len() == file_key.len() {
|
||||
file_key.copy_from_slice(&stanza.body);
|
||||
Ok(())
|
||||
} else {
|
||||
panic!("File key is wrong length")
|
||||
}
|
||||
})
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -135,7 +135,7 @@ impl<'a, 'b, R: io::Read, W: io::Write> Callbacks<Error> for BidirCallbacks<'a,
|
|||
.and_then(|res| match res {
|
||||
Ok(s) => String::from_utf8(s.body)
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "secret is not UTF-8"))
|
||||
.map(|s| Ok(SecretString::new(s))),
|
||||
.map(|s| Ok(SecretString::from(s))),
|
||||
Err(e) => Ok(Err(e)),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! Recipient plugin helpers.
|
||||
|
||||
use age_core::{
|
||||
format::{is_arbitrary_string, FileKey, Stanza, FILE_KEY_BYTES},
|
||||
format::{is_arbitrary_string, FileKey, Stanza},
|
||||
plugin::{self, BidirSend, Connection},
|
||||
secrecy::SecretString,
|
||||
};
|
||||
|
@ -183,7 +183,7 @@ impl<'a, 'b, R: io::Read, W: io::Write> Callbacks<Error> for BidirCallbacks<'a,
|
|||
.and_then(|res| match res {
|
||||
Ok(s) => String::from_utf8(s.body)
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "secret is not UTF-8"))
|
||||
.map(|s| Ok(SecretString::new(s))),
|
||||
.map(|s| Ok(SecretString::from(s))),
|
||||
Err(e) => Ok(Err(e)),
|
||||
})
|
||||
}
|
||||
|
@ -281,11 +281,16 @@ pub(crate) fn run_v1<P: RecipientPluginV1>(mut plugin: P) -> io::Result<()> {
|
|||
}),
|
||||
(Some(WRAP_FILE_KEY), |s| {
|
||||
// TODO: Should we ignore file key commands with unexpected metadata args?
|
||||
TryInto::<[u8; FILE_KEY_BYTES]>::try_into(&s.body[..])
|
||||
.map_err(|_| Error::Internal {
|
||||
message: "invalid file key length".to_owned(),
|
||||
})
|
||||
.map(FileKey::from)
|
||||
FileKey::try_init_with_mut(|file_key| {
|
||||
if s.body.len() == file_key.len() {
|
||||
file_key.copy_from_slice(&s.body);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::Internal {
|
||||
message: "invalid file key length".to_owned(),
|
||||
})
|
||||
}
|
||||
})
|
||||
}),
|
||||
(Some(EXTENSION_LABELS), |_| Ok(())),
|
||||
)?;
|
||||
|
|
|
@ -26,7 +26,7 @@ to 1.0.0 are beta releases.
|
|||
- Partial French translation!
|
||||
|
||||
### Changed
|
||||
- Migrated to `i18n-embed 0.15`.
|
||||
- Migrated to `i18n-embed 0.15`, `secrecy 0.10`.
|
||||
- `age::Encryptor::with_recipients` now takes recipients by reference instead of
|
||||
by value. This aligns it with `age::Decryptor` (which takes identities by
|
||||
reference), and also means that errors with recipients are reported earlier.
|
||||
|
|
|
@ -37,7 +37,7 @@ futures = { version = "0.3", optional = true }
|
|||
pin-project = "1"
|
||||
|
||||
# Common CLI dependencies
|
||||
pinentry = { version = "0.5", optional = true }
|
||||
pinentry = { workspace = true, optional = true }
|
||||
|
||||
# Dependencies used internally:
|
||||
# (Breaking upgrades to these are usually backwards-compatible, but check MSRVs.)
|
||||
|
|
|
@ -125,10 +125,10 @@ pub fn read_secret(
|
|||
input.interact()
|
||||
} else {
|
||||
// Fall back to CLI interface.
|
||||
let passphrase = prompt_password(format!("{}: ", description)).map(SecretString::new)?;
|
||||
let passphrase = prompt_password(format!("{}: ", description)).map(SecretString::from)?;
|
||||
if let Some(confirm_prompt) = confirm {
|
||||
let confirm_passphrase =
|
||||
prompt_password(format!("{}: ", confirm_prompt)).map(SecretString::new)?;
|
||||
prompt_password(format!("{}: ", confirm_prompt)).map(SecretString::from)?;
|
||||
|
||||
if !bool::from(
|
||||
passphrase
|
||||
|
@ -199,7 +199,7 @@ impl Passphrase {
|
|||
acc + "-" + s
|
||||
}
|
||||
});
|
||||
Passphrase::Generated(SecretString::new(new_passphrase))
|
||||
Passphrase::Generated(SecretString::from(new_passphrase))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -239,7 +239,7 @@ fOrxrKTj7xCdNS3+OrCdnBC8Z9cKDxjCGWW3fkjLsYha0Jo=
|
|||
|
||||
/// This intentionally panics if called twice.
|
||||
fn request_passphrase(&self, _: &str) -> Option<SecretString> {
|
||||
Some(SecretString::new(
|
||||
Some(SecretString::from(
|
||||
self.0.lock().unwrap().take().unwrap().to_owned(),
|
||||
))
|
||||
}
|
||||
|
@ -248,8 +248,10 @@ fOrxrKTj7xCdNS3+OrCdnBC8Z9cKDxjCGWW3fkjLsYha0Jo=
|
|||
#[test]
|
||||
#[cfg(feature = "armor")]
|
||||
fn round_trip() {
|
||||
use age_core::format::FileKey;
|
||||
|
||||
let pk: x25519::Recipient = TEST_RECIPIENT.parse().unwrap();
|
||||
let file_key = [12; 16].into();
|
||||
let file_key = FileKey::new(Box::new([12; 16]));
|
||||
let (wrapped, labels) = pk.wrap_file_key(&file_key).unwrap();
|
||||
assert!(labels.is_empty());
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
use age_core::{
|
||||
format::FileKey,
|
||||
primitives::hkdf,
|
||||
secrecy::{ExposeSecret, Secret},
|
||||
secrecy::{ExposeSecret, SecretBox},
|
||||
};
|
||||
use rand::{rngs::OsRng, RngCore};
|
||||
|
||||
|
@ -18,17 +18,15 @@ const HEADER_KEY_LABEL: &[u8] = b"header";
|
|||
const PAYLOAD_KEY_LABEL: &[u8] = b"payload";
|
||||
|
||||
pub(crate) fn new_file_key() -> FileKey {
|
||||
let mut file_key = [0; 16];
|
||||
OsRng.fill_bytes(&mut file_key);
|
||||
file_key.into()
|
||||
FileKey::init_with_mut(|file_key| OsRng.fill_bytes(file_key))
|
||||
}
|
||||
|
||||
pub(crate) fn mac_key(file_key: &FileKey) -> HmacKey {
|
||||
HmacKey(Secret::new(hkdf(
|
||||
HmacKey(SecretBox::new(Box::new(hkdf(
|
||||
&[],
|
||||
HEADER_KEY_LABEL,
|
||||
file_key.expose_secret(),
|
||||
)))
|
||||
))))
|
||||
}
|
||||
|
||||
pub(crate) fn v1_payload_key(
|
||||
|
|
|
@ -63,10 +63,10 @@
|
|||
//! ## Passphrase-based encryption
|
||||
//!
|
||||
//! ```
|
||||
//! use age::secrecy::Secret;
|
||||
//! use age::secrecy::SecretString;
|
||||
//!
|
||||
//! # fn run_main() -> Result<(), ()> {
|
||||
//! let passphrase = Secret::new("this is not a good passphrase".to_owned());
|
||||
//! let passphrase = SecretString::from("this is not a good passphrase".to_owned());
|
||||
//! let recipient = age::scrypt::Recipient::new(passphrase.clone());
|
||||
//! let identity = age::scrypt::Identity::new(passphrase);
|
||||
//!
|
||||
|
@ -152,16 +152,16 @@
|
|||
//! ## Passphrase-based encryption
|
||||
//!
|
||||
//! ```
|
||||
//! use age::secrecy::Secret;
|
||||
//! use age::secrecy::SecretString;
|
||||
//! use std::io::{Read, Write};
|
||||
//! use std::iter;
|
||||
//!
|
||||
//! # fn run_main() -> Result<(), ()> {
|
||||
//! let plaintext = b"Hello world!";
|
||||
//! let passphrase = Secret::new("this is not a good passphrase".to_owned());
|
||||
//! let passphrase = SecretString::from("this is not a good passphrase".to_owned());
|
||||
//!
|
||||
//! // Encrypt the plaintext to a ciphertext using the passphrase...
|
||||
//! # fn encrypt(passphrase: Secret<String>, plaintext: &[u8]) -> Result<Vec<u8>, age::EncryptError> {
|
||||
//! # fn encrypt(passphrase: SecretString, plaintext: &[u8]) -> Result<Vec<u8>, age::EncryptError> {
|
||||
//! let encrypted = {
|
||||
//! let encryptor = age::Encryptor::with_user_passphrase(passphrase.clone());
|
||||
//!
|
||||
|
@ -176,7 +176,7 @@
|
|||
//! # }
|
||||
//!
|
||||
//! // ... and decrypt the ciphertext to the plaintext again using the same passphrase.
|
||||
//! # fn decrypt(passphrase: Secret<String>, encrypted: Vec<u8>) -> Result<Vec<u8>, age::DecryptError> {
|
||||
//! # fn decrypt(passphrase: SecretString, encrypted: Vec<u8>) -> Result<Vec<u8>, age::DecryptError> {
|
||||
//! let decrypted = {
|
||||
//! let decryptor = age::Decryptor::new(&encrypted[..])?;
|
||||
//!
|
||||
|
|
|
@ -649,11 +649,14 @@ impl<C: Callbacks> IdentityPluginV1<C> {
|
|||
// We only support a single file.
|
||||
assert!(command.args[0] == "0");
|
||||
assert!(file_key.is_none());
|
||||
file_key = Some(
|
||||
TryInto::<[u8; 16]>::try_into(&command.body[..])
|
||||
.map_err(|_| DecryptError::DecryptionFailed)
|
||||
.map(FileKey::from),
|
||||
);
|
||||
file_key = Some(FileKey::try_init_with_mut(|file_key| {
|
||||
if command.body.len() == file_key.len() {
|
||||
file_key.copy_from_slice(&command.body);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(DecryptError::DecryptionFailed)
|
||||
}
|
||||
}));
|
||||
reply.ok(None)
|
||||
}
|
||||
CMD_ERROR => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! Primitive cryptographic operations used by `age`.
|
||||
|
||||
use age_core::secrecy::{ExposeSecret, Secret};
|
||||
use age_core::secrecy::{ExposeSecret, SecretBox};
|
||||
use hmac::{
|
||||
digest::{CtOutput, MacError},
|
||||
Hmac, Mac,
|
||||
|
@ -15,7 +15,7 @@ pub mod armor;
|
|||
|
||||
pub mod stream;
|
||||
|
||||
pub(crate) struct HmacKey(pub(crate) Secret<[u8; 32]>);
|
||||
pub(crate) struct HmacKey(pub(crate) SecretBox<[u8; 32]>);
|
||||
|
||||
/// `HMAC[key](message)`
|
||||
///
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! I/O helper structs for age file encryption and decryption.
|
||||
|
||||
use age_core::secrecy::{ExposeSecret, SecretVec};
|
||||
use age_core::secrecy::{ExposeSecret, SecretSlice};
|
||||
use chacha20poly1305::{
|
||||
aead::{generic_array::GenericArray, Aead, KeyInit, KeySizeUser},
|
||||
ChaCha20Poly1305,
|
||||
|
@ -194,7 +194,7 @@ impl Stream {
|
|||
Ok(encrypted)
|
||||
}
|
||||
|
||||
fn decrypt_chunk(&mut self, chunk: &[u8], last: bool) -> io::Result<SecretVec<u8>> {
|
||||
fn decrypt_chunk(&mut self, chunk: &[u8], last: bool) -> io::Result<SecretSlice<u8>> {
|
||||
assert!(chunk.len() <= ENCRYPTED_CHUNK_SIZE);
|
||||
|
||||
self.nonce.set_last(last).map_err(|_| {
|
||||
|
@ -204,7 +204,7 @@ impl Stream {
|
|||
let decrypted = self
|
||||
.aead
|
||||
.decrypt(&self.nonce.to_bytes().into(), chunk)
|
||||
.map(SecretVec::new)
|
||||
.map(SecretSlice::from)
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "decryption error"))?;
|
||||
self.nonce.increment_counter();
|
||||
|
||||
|
@ -407,7 +407,7 @@ pub struct StreamReader<R> {
|
|||
start: StartPos,
|
||||
plaintext_len: Option<u64>,
|
||||
cur_plaintext_pos: u64,
|
||||
chunk: Option<SecretVec<u8>>,
|
||||
chunk: Option<SecretSlice<u8>>,
|
||||
}
|
||||
|
||||
impl<R> StreamReader<R> {
|
||||
|
|
|
@ -477,7 +477,7 @@ mod tests {
|
|||
fn scrypt_round_trip() {
|
||||
let test_msg = b"This is a test message. For testing.";
|
||||
|
||||
let mut recipient = scrypt::Recipient::new(SecretString::new("passphrase".to_string()));
|
||||
let mut recipient = scrypt::Recipient::new(SecretString::from("passphrase".to_string()));
|
||||
// Override to something very fast for testing.
|
||||
recipient.set_work_factor(2);
|
||||
|
||||
|
@ -492,7 +492,7 @@ mod tests {
|
|||
let d = Decryptor::new(&encrypted[..]).unwrap();
|
||||
let mut r = d
|
||||
.decrypt(
|
||||
Some(&scrypt::Identity::new(SecretString::new("passphrase".to_string())) as _)
|
||||
Some(&scrypt::Identity::new(SecretString::from("passphrase".to_string())) as _)
|
||||
.into_iter(),
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -549,7 +549,8 @@ mod tests {
|
|||
#[test]
|
||||
fn mixed_recipient_and_passphrase() {
|
||||
let pk: x25519::Recipient = crate::x25519::tests::TEST_PK.parse().unwrap();
|
||||
let passphrase = crate::scrypt::Recipient::new(SecretString::new("passphrase".to_string()));
|
||||
let passphrase =
|
||||
crate::scrypt::Recipient::new(SecretString::from("passphrase".to_string()));
|
||||
|
||||
let recipients = [&pk as &dyn Recipient, &passphrase as _];
|
||||
|
||||
|
|
|
@ -260,9 +260,10 @@ impl crate::Identity for Identity {
|
|||
aead_decrypt(&enc_key, FILE_KEY_BYTES, &stanza.body)
|
||||
.map(|mut pt| {
|
||||
// It's ours!
|
||||
let file_key: [u8; FILE_KEY_BYTES] = pt[..].try_into().unwrap();
|
||||
pt.zeroize();
|
||||
file_key.into()
|
||||
FileKey::init_with_mut(|file_key| {
|
||||
file_key.copy_from_slice(&pt);
|
||||
pt.zeroize();
|
||||
})
|
||||
})
|
||||
.map_err(DecryptError::from),
|
||||
)
|
||||
|
|
|
@ -194,7 +194,7 @@ mod decrypt {
|
|||
}
|
||||
|
||||
mod read_ssh {
|
||||
use age_core::secrecy::Secret;
|
||||
use age_core::secrecy::SecretBox;
|
||||
use curve25519_dalek::edwards::{CompressedEdwardsY, EdwardsPoint};
|
||||
use nom::{
|
||||
branch::alt,
|
||||
|
@ -349,14 +349,14 @@ mod read_ssh {
|
|||
/// Internal OpenSSH encoding of an Ed25519 private key.
|
||||
///
|
||||
/// - [OpenSSH serialization code](https://github.com/openssh/openssh-portable/blob/4103a3ec7c68493dbc4f0994a229507e943a86d3/sshkey.c#L3277-L3283)
|
||||
fn openssh_ed25519_privkey(input: &[u8]) -> IResult<&[u8], Secret<[u8; 64]>> {
|
||||
fn openssh_ed25519_privkey(input: &[u8]) -> IResult<&[u8], SecretBox<[u8; 64]>> {
|
||||
delimited(
|
||||
string_tag(SSH_ED25519_KEY_PREFIX),
|
||||
map_opt(tuple((string, string)), |(pubkey_bytes, privkey_bytes)| {
|
||||
if privkey_bytes.len() == 64 && pubkey_bytes == &privkey_bytes[32..64] {
|
||||
let mut privkey = [0; 64];
|
||||
let mut privkey = Box::new([0; 64]);
|
||||
privkey.copy_from_slice(privkey_bytes);
|
||||
Some(Secret::new(privkey))
|
||||
Some(SecretBox::new(privkey))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use age_core::{
|
||||
format::{FileKey, Stanza, FILE_KEY_BYTES},
|
||||
primitives::{aead_decrypt, hkdf},
|
||||
secrecy::{ExposeSecret, Secret},
|
||||
secrecy::{ExposeSecret, SecretBox},
|
||||
};
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use nom::{
|
||||
|
@ -32,12 +32,27 @@ use crate::{
|
|||
};
|
||||
|
||||
/// An SSH private key for decrypting an age file.
|
||||
#[derive(Clone)]
|
||||
pub enum UnencryptedKey {
|
||||
/// An ssh-rsa private key.
|
||||
SshRsa(Vec<u8>, Box<rsa::RsaPrivateKey>),
|
||||
/// An ssh-ed25519 key pair.
|
||||
SshEd25519(Vec<u8>, Secret<[u8; 64]>),
|
||||
SshEd25519(Vec<u8>, SecretBox<[u8; 64]>),
|
||||
}
|
||||
|
||||
impl Clone for UnencryptedKey {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Self::SshRsa(ssh_key, sk) => Self::SshRsa(ssh_key.clone(), sk.clone()),
|
||||
Self::SshEd25519(ssh_key, privkey) => Self::SshEd25519(
|
||||
ssh_key.clone(),
|
||||
SecretBox::new({
|
||||
let mut cloned_privkey = Box::new([0; 64]);
|
||||
cloned_privkey.copy_from_slice(privkey.expose_secret());
|
||||
cloned_privkey
|
||||
}),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UnencryptedKey {
|
||||
|
@ -64,11 +79,18 @@ impl UnencryptedKey {
|
|||
&stanza.body,
|
||||
)
|
||||
.map_err(DecryptError::from)
|
||||
.map(|mut pt| {
|
||||
.and_then(|mut pt| {
|
||||
// It's ours!
|
||||
let file_key: [u8; 16] = pt[..].try_into().unwrap();
|
||||
pt.zeroize();
|
||||
file_key.into()
|
||||
FileKey::try_init_with_mut(|file_key| {
|
||||
let ret = if pt.len() == file_key.len() {
|
||||
file_key.copy_from_slice(&pt);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(DecryptError::DecryptionFailed)
|
||||
};
|
||||
pt.zeroize();
|
||||
ret
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
@ -115,9 +137,10 @@ impl UnencryptedKey {
|
|||
.map_err(DecryptError::from)
|
||||
.map(|mut pt| {
|
||||
// It's ours!
|
||||
let file_key: [u8; FILE_KEY_BYTES] = pt[..].try_into().unwrap();
|
||||
pt.zeroize();
|
||||
file_key.into()
|
||||
FileKey::init_with_mut(|file_key| {
|
||||
file_key.copy_from_slice(&pt);
|
||||
pt.zeroize();
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
@ -354,7 +377,10 @@ pub(crate) fn ssh_identity(input: &str) -> IResult<&str, Identity> {
|
|||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use age_core::secrecy::{ExposeSecret, SecretString};
|
||||
use age_core::{
|
||||
format::FileKey,
|
||||
secrecy::{ExposeSecret, SecretString},
|
||||
};
|
||||
use std::io::BufReader;
|
||||
|
||||
use super::{Identity, UnsupportedKey};
|
||||
|
@ -491,7 +517,7 @@ AwQFBg==
|
|||
}
|
||||
|
||||
fn request_passphrase(&self, _: &str) -> Option<SecretString> {
|
||||
Some(SecretString::new(self.0.to_owned()))
|
||||
Some(SecretString::from(self.0.to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -505,7 +531,7 @@ AwQFBg==
|
|||
};
|
||||
let pk: Recipient = TEST_SSH_RSA_PK.parse().unwrap();
|
||||
|
||||
let file_key = [12; 16].into();
|
||||
let file_key = FileKey::new(Box::new([12; 16]));
|
||||
|
||||
let (wrapped, labels) = pk.wrap_file_key(&file_key).unwrap();
|
||||
assert!(labels.is_empty());
|
||||
|
@ -532,7 +558,7 @@ AwQFBg==
|
|||
let identity = identity.with_callbacks(TestPassphrase("passphrase"));
|
||||
let pk: Recipient = TEST_SSH_ED25519_PK.parse().unwrap();
|
||||
|
||||
let file_key = [12; 16].into();
|
||||
let file_key = FileKey::new(Box::new([12; 16]));
|
||||
|
||||
let (wrapped, labels) = pk.wrap_file_key(&file_key).unwrap();
|
||||
assert!(labels.is_empty());
|
||||
|
|
|
@ -68,7 +68,7 @@ impl Identity {
|
|||
let sk_base32 = sk_bytes.to_base32();
|
||||
let mut encoded =
|
||||
bech32::encode(SECRET_KEY_PREFIX, sk_base32, Variant::Bech32).expect("HRP is valid");
|
||||
let ret = SecretString::new(encoded.to_uppercase());
|
||||
let ret = SecretString::from(encoded.to_uppercase());
|
||||
|
||||
// Clear intermediates
|
||||
sk_bytes.zeroize();
|
||||
|
@ -136,9 +136,10 @@ impl crate::Identity for Identity {
|
|||
.ok()
|
||||
.map(|mut pt| {
|
||||
// It's ours!
|
||||
let file_key: [u8; FILE_KEY_BYTES] = pt[..].try_into().unwrap();
|
||||
pt.zeroize();
|
||||
Ok(file_key.into())
|
||||
Ok(FileKey::init_with_mut(|file_key| {
|
||||
file_key.copy_from_slice(&pt);
|
||||
pt.zeroize();
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -238,7 +239,7 @@ impl crate::Recipient for Recipient {
|
|||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use age_core::secrecy::ExposeSecret;
|
||||
use age_core::{format::FileKey, secrecy::ExposeSecret};
|
||||
use proptest::prelude::*;
|
||||
use x25519_dalek::{PublicKey, StaticSecret};
|
||||
|
||||
|
@ -265,7 +266,7 @@ pub(crate) mod tests {
|
|||
proptest! {
|
||||
#[test]
|
||||
fn wrap_and_unwrap(sk_bytes in proptest::collection::vec(any::<u8>(), ..=32)) {
|
||||
let file_key = [7; 16].into();
|
||||
let file_key = FileKey::new(Box::new([7; 16]));
|
||||
let sk = {
|
||||
let mut tmp = [0; 32];
|
||||
tmp[..sk_bytes.len()].copy_from_slice(&sk_bytes);
|
||||
|
|
|
@ -44,7 +44,7 @@ fn age_test_vectors() -> Result<(), Box<dyn std::error::Error>> {
|
|||
name
|
||||
))?
|
||||
.read_to_string(&mut passphrase)?;
|
||||
let passphrase = SecretString::new(passphrase);
|
||||
let passphrase = SecretString::from(passphrase);
|
||||
let identity = scrypt::Identity::new(passphrase);
|
||||
d.decrypt(Some(&identity as _).into_iter())
|
||||
};
|
||||
|
|
4
fuzz-afl/Cargo.lock
generated
4
fuzz-afl/Cargo.lock
generated
|
@ -879,9 +879,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "secrecy"
|
||||
version = "0.8.0"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e"
|
||||
checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a"
|
||||
dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
|
4
fuzz/Cargo.lock
generated
4
fuzz/Cargo.lock
generated
|
@ -884,9 +884,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "secrecy"
|
||||
version = "0.8.0"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e"
|
||||
checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a"
|
||||
dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
|
|
@ -654,7 +654,7 @@ version = "0.11.0"
|
|||
criteria = "safe-to-deploy"
|
||||
|
||||
[[exemptions.secrecy]]
|
||||
version = "0.8.0"
|
||||
version = "0.10.3"
|
||||
criteria = "safe-to-deploy"
|
||||
|
||||
[[exemptions.self_cell]]
|
||||
|
|
|
@ -16,8 +16,8 @@ user-login = "jrmuizel"
|
|||
user-name = "Jeff Muizelaar"
|
||||
|
||||
[[publisher.pinentry]]
|
||||
version = "0.5.1"
|
||||
when = "2024-08-31"
|
||||
version = "0.6.0"
|
||||
when = "2024-11-03"
|
||||
user-id = 6289
|
||||
user-login = "str4d"
|
||||
user-name = "Jack Grigg"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue