Skip to content

Commit a63c28c

Browse files
committed
age: Return Box<dyn Identity + Send + Sync> from read_identities
This exposes `Send + Sync` (which is always present for `Identity` types that we parse, including `age::encrypted::Identity` after this commit) to users that require it, without adding bounds to `Identity` itself. Users who do not require these bounds can map the output of `read_identities` to drop them.
1 parent 44db419 commit a63c28c

File tree

5 files changed

+37
-26
lines changed

5 files changed

+37
-26
lines changed

age/src/cli_common.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,14 @@ pub fn read_identities<E, G, H>(
2525
file_not_found: G,
2626
identity_encrypted_without_passphrase: H,
2727
#[cfg(feature = "ssh")] unsupported_ssh: impl Fn(String, crate::ssh::UnsupportedKey) -> E,
28-
) -> Result<Vec<Box<dyn Identity>>, E>
28+
) -> Result<Vec<Box<dyn Identity + Send + Sync>>, E>
2929
where
3030
E: From<crate::DecryptError>,
3131
E: From<io::Error>,
3232
G: Fn(String) -> E,
3333
H: Fn(String) -> E,
3434
{
35-
let mut identities: Vec<Box<dyn Identity>> = vec![];
35+
let mut identities: Vec<Box<dyn Identity + Send + Sync>> = vec![];
3636

3737
for filename in filenames {
3838
// Try parsing as an encrypted age identity.

age/src/encrypted.rs

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! The "encrypted age identity file" identity type.
22
3-
use std::{cell::Cell, io};
3+
use std::{io, ops::DerefMut, sync::Mutex};
44

55
use i18n_embed_fl::fl;
66

@@ -77,12 +77,12 @@ impl<R: io::Read> IdentityState<R> {
7777

7878
/// An encrypted age identity file.
7979
pub struct Identity<R: io::Read, C: Callbacks> {
80-
state: Cell<IdentityState<R>>,
80+
state: Mutex<Option<IdentityState<R>>>,
8181
filename: Option<String>,
8282
callbacks: C,
8383
}
8484

85-
impl<R: io::Read, C: Callbacks + Clone + 'static> Identity<R, C> {
85+
impl<R: io::Read, C: Callbacks + Clone + Send + Sync + 'static> Identity<R, C> {
8686
/// Parses an encrypted identity from an input containing valid UTF-8.
8787
///
8888
/// `filename` is the path to the file that the input is reading from, if any.
@@ -98,10 +98,10 @@ impl<R: io::Read, C: Callbacks + Clone + 'static> Identity<R, C> {
9898
match Decryptor::new(data)? {
9999
Decryptor::Recipients(_) => Ok(None),
100100
Decryptor::Passphrase(decryptor) => Ok(Some(Identity {
101-
state: Cell::new(IdentityState::Encrypted {
101+
state: Mutex::new(Some(IdentityState::Encrypted {
102102
decryptor,
103103
max_work_factor,
104-
}),
104+
})),
105105
filename,
106106
callbacks,
107107
})),
@@ -113,9 +113,11 @@ impl<R: io::Read, C: Callbacks + Clone + 'static> Identity<R, C> {
113113
/// If this encrypted identity has not been decrypted yet, calling this method will
114114
/// trigger a passphrase request.
115115
pub fn recipients(&self) -> Result<Vec<Box<dyn crate::Recipient>>, EncryptError> {
116-
match self
117-
.state
116+
let mut state = self.state.lock().unwrap();
117+
match state
118+
.deref_mut()
118119
.take()
120+
.expect("We never leave this set to None")
119121
.decrypt(self.filename.as_deref(), self.callbacks.clone())
120122
{
121123
Ok((identities, _)) => {
@@ -124,11 +126,11 @@ impl<R: io::Read, C: Callbacks + Clone + 'static> Identity<R, C> {
124126
.map(|entry| entry.to_recipient(self.callbacks.clone()))
125127
.collect::<Result<Vec<_>, _>>();
126128

127-
self.state.set(IdentityState::Decrypted(identities));
129+
*state = Some(IdentityState::Decrypted(identities));
128130
recipients
129131
}
130132
Err(e) => {
131-
self.state.set(IdentityState::Poisoned(Some(e.clone())));
133+
*state = Some(IdentityState::Poisoned(Some(e.clone())));
132134
Err(EncryptError::EncryptedIdentities(e))
133135
}
134136
}
@@ -151,12 +153,14 @@ impl<R: io::Read, C: Callbacks + Clone + 'static> Identity<R, C> {
151153
) -> Option<Result<age_core::format::FileKey, DecryptError>>
152154
where
153155
F: Fn(
154-
Result<Box<dyn crate::Identity>, DecryptError>,
156+
Result<Box<dyn crate::Identity + Send + Sync>, DecryptError>,
155157
) -> Option<Result<age_core::format::FileKey, DecryptError>>,
156158
{
157-
match self
158-
.state
159+
let mut state = self.state.lock().unwrap();
160+
match state
161+
.deref_mut()
159162
.take()
163+
.expect("We never leave this set to None")
160164
.decrypt(self.filename.as_deref(), self.callbacks.clone())
161165
{
162166
Ok((identities, requested_passphrase)) => {
@@ -175,18 +179,18 @@ impl<R: io::Read, C: Callbacks + Clone + 'static> Identity<R, C> {
175179
));
176180
}
177181

178-
self.state.set(IdentityState::Decrypted(identities));
182+
*state = Some(IdentityState::Decrypted(identities));
179183
result
180184
}
181185
Err(e) => {
182-
self.state.set(IdentityState::Poisoned(Some(e.clone())));
186+
*state = Some(IdentityState::Poisoned(Some(e.clone())));
183187
Some(Err(e))
184188
}
185189
}
186190
}
187191
}
188192

189-
impl<R: io::Read, C: Callbacks + Clone + 'static> crate::Identity for Identity<R, C> {
193+
impl<R: io::Read, C: Callbacks + Clone + Send + Sync + 'static> crate::Identity for Identity<R, C> {
190194
fn unwrap_stanza(
191195
&self,
192196
stanza: &age_core::format::Stanza,
@@ -210,7 +214,7 @@ impl<R: io::Read, C: Callbacks + Clone + 'static> crate::Identity for Identity<R
210214

211215
#[cfg(test)]
212216
mod tests {
213-
use std::{cell::Cell, io::BufReader};
217+
use std::{io::BufReader, sync::Mutex};
214218

215219
use secrecy::{ExposeSecret, SecretString};
216220

@@ -235,12 +239,17 @@ fOrxrKTj7xCdNS3+OrCdnBC8Z9cKDxjCGWW3fkjLsYha0Jo=
235239

236240
const TEST_RECIPIENT: &str = "age1ysxuaeqlk7xd8uqsh8lsnfwt9jzzjlqf49ruhpjrrj5yatlcuf7qke4pqe";
237241

238-
#[derive(Clone)]
239-
struct MockCallbacks(Cell<Option<&'static str>>);
242+
struct MockCallbacks(Mutex<Option<&'static str>>);
243+
244+
impl Clone for MockCallbacks {
245+
fn clone(&self) -> Self {
246+
Self(Mutex::new(self.0.lock().unwrap().clone()))
247+
}
248+
}
240249

241250
impl MockCallbacks {
242251
fn new(passphrase: &'static str) -> Self {
243-
MockCallbacks(Cell::new(Some(passphrase)))
252+
MockCallbacks(Mutex::new(Some(passphrase)))
244253
}
245254
}
246255

@@ -255,7 +264,9 @@ fOrxrKTj7xCdNS3+OrCdnBC8Z9cKDxjCGWW3fkjLsYha0Jo=
255264

256265
/// This intentionally panics if called twice.
257266
fn request_passphrase(&self, _: &str) -> Option<secrecy::SecretString> {
258-
Some(SecretString::new(self.0.take().unwrap().to_owned()))
267+
Some(SecretString::new(
268+
self.0.lock().unwrap().take().unwrap().to_owned(),
269+
))
259270
}
260271
}
261272

age/src/identity.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ pub enum IdentityFileEntry {
1919
impl IdentityFileEntry {
2020
pub(crate) fn into_identity(
2121
self,
22-
callbacks: impl Callbacks + 'static,
23-
) -> Result<Box<dyn crate::Identity>, DecryptError> {
22+
callbacks: impl Callbacks + Send + Sync + 'static,
23+
) -> Result<Box<dyn crate::Identity + Send + Sync>, DecryptError> {
2424
match self {
2525
IdentityFileEntry::Native(i) => Ok(Box::new(i)),
2626
#[cfg(feature = "plugin")]

rage/src/bin/rage-mount/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ fn main() -> Result<(), Error> {
281281
}
282282

283283
decryptor
284-
.decrypt(identities.iter().map(|i| &**i))
284+
.decrypt(identities.iter().map(|i| i.as_ref() as &dyn age::Identity))
285285
.map_err(|e| e.into())
286286
.and_then(|stream| mount_stream(stream, types, mountpoint))
287287
}

rage/src/bin/rage/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ fn decrypt(opts: AgeOptions) -> Result<(), error::DecryptError> {
422422
&opts.plugin_name,
423423
&[plugin::Identity::default_for_plugin(&opts.plugin_name)],
424424
UiCallbacks,
425-
)?) as Box<dyn Identity>]
425+
)?) as Box<dyn Identity + Send + Sync>]
426426
};
427427

428428
if identities.is_empty() {

0 commit comments

Comments
 (0)