@@ -4,13 +4,80 @@ use std::{cell::Cell, io};
4
4
5
5
use crate :: { fl, scrypt, Callbacks , DecryptError , Decryptor , EncryptError , IdentityFile } ;
6
6
7
+ /// An encrypted age identity file.
8
+ ///
9
+ /// This type can be explicitly decrypted to obtain an [`IdentityFile`]. If you want a
10
+ /// type that can be used directly as an identity and caches the decryption result
11
+ /// internally, use [`Identity`].
12
+ pub struct EncryptedIdentity < R : io:: Read , C : Callbacks > {
13
+ decryptor : Decryptor < R > ,
14
+ max_work_factor : Option < u8 > ,
15
+ callbacks : C ,
16
+ }
17
+
18
+ impl < R : io:: Read , C : Callbacks > EncryptedIdentity < R , C > {
19
+ /// Parses an encrypted identity from an input containing valid UTF-8.
20
+ ///
21
+ /// Returns `Ok(None)` if the input contains an age ciphertext that is not encrypted
22
+ /// to a passphrase.
23
+ pub ( crate ) fn from_buffer (
24
+ data : R ,
25
+ callbacks : C ,
26
+ max_work_factor : Option < u8 > ,
27
+ ) -> Result < Option < Self > , DecryptError > {
28
+ let decryptor = Decryptor :: new ( data) ?;
29
+ Ok ( Self :: new ( decryptor, callbacks, max_work_factor) )
30
+ }
31
+
32
+ /// Constructs a new encrypted identity from a [`Decryptor`].
33
+ ///
34
+ /// Returns `Ok(None)` if the input contains an age ciphertext that is not encrypted
35
+ /// to a passphrase.
36
+ pub fn new ( decryptor : Decryptor < R > , callbacks : C , max_work_factor : Option < u8 > ) -> Option < Self > {
37
+ decryptor. is_scrypt ( ) . then_some ( EncryptedIdentity {
38
+ decryptor,
39
+ max_work_factor,
40
+ callbacks,
41
+ } )
42
+ }
43
+
44
+ /// Decrypts this encrypted identity.
45
+ ///
46
+ /// The provided filename (if any) will be used in the passphrase request message.
47
+ pub fn decrypt ( self , filename : Option < & str > ) -> Result < IdentityFile < C > , DecryptError > {
48
+ let passphrase = match self . callbacks . request_passphrase ( & fl ! (
49
+ "encrypted-passphrase-prompt" ,
50
+ filename = filename. unwrap_or_default( )
51
+ ) ) {
52
+ Some ( passphrase) => passphrase,
53
+ None => todo ! ( ) ,
54
+ } ;
55
+
56
+ let mut identity = scrypt:: Identity :: new ( passphrase) ;
57
+ if let Some ( max_work_factor) = self . max_work_factor {
58
+ identity. set_max_work_factor ( max_work_factor) ;
59
+ }
60
+
61
+ self . decryptor
62
+ . decrypt ( Some ( & identity as _ ) . into_iter ( ) )
63
+ . map_err ( |e| {
64
+ if matches ! ( e, DecryptError :: DecryptionFailed ) {
65
+ DecryptError :: KeyDecryptionFailed
66
+ } else {
67
+ e
68
+ }
69
+ } )
70
+ . and_then ( |stream| {
71
+ let file = IdentityFile :: from_buffer ( io:: BufReader :: new ( stream) ) ?
72
+ . with_callbacks ( self . callbacks ) ;
73
+ Ok ( file)
74
+ } )
75
+ }
76
+ }
77
+
7
78
/// The state of the encrypted age identity.
8
79
enum IdentityState < R : io:: Read , C : Callbacks > {
9
- Encrypted {
10
- decryptor : Decryptor < R > ,
11
- max_work_factor : Option < u8 > ,
12
- callbacks : C ,
13
- } ,
80
+ Encrypted ( EncryptedIdentity < R , C > ) ,
14
81
Decrypted ( IdentityFile < C > ) ,
15
82
16
83
/// The file was not correctly encrypted, or did not contain age identities. We cache
@@ -33,39 +100,7 @@ impl<R: io::Read, C: Callbacks> IdentityState<R, C> {
33
100
/// were not cached (and we just asked the user for a passphrase).
34
101
fn decrypt ( self , filename : Option < & str > ) -> Result < ( IdentityFile < C > , bool ) , DecryptError > {
35
102
match self {
36
- Self :: Encrypted {
37
- decryptor,
38
- max_work_factor,
39
- callbacks,
40
- } => {
41
- let passphrase = match callbacks. request_passphrase ( & fl ! (
42
- "encrypted-passphrase-prompt" ,
43
- filename = filename. unwrap_or_default( )
44
- ) ) {
45
- Some ( passphrase) => passphrase,
46
- None => todo ! ( ) ,
47
- } ;
48
-
49
- let mut identity = scrypt:: Identity :: new ( passphrase) ;
50
- if let Some ( max_work_factor) = max_work_factor {
51
- identity. set_max_work_factor ( max_work_factor) ;
52
- }
53
-
54
- decryptor
55
- . decrypt ( Some ( & identity as _ ) . into_iter ( ) )
56
- . map_err ( |e| {
57
- if matches ! ( e, DecryptError :: DecryptionFailed ) {
58
- DecryptError :: KeyDecryptionFailed
59
- } else {
60
- e
61
- }
62
- } )
63
- . and_then ( |stream| {
64
- let file = IdentityFile :: from_buffer ( io:: BufReader :: new ( stream) ) ?
65
- . with_callbacks ( callbacks) ;
66
- Ok ( ( file, true ) )
67
- } )
68
- }
103
+ Self :: Encrypted ( encrypted) => encrypted. decrypt ( filename) . map ( |file| ( file, true ) ) ,
69
104
Self :: Decrypted ( identity_file) => Ok ( ( identity_file, false ) ) ,
70
105
// `IdentityState::decrypt` is only ever called with `Some`.
71
106
Self :: Poisoned ( e) => Err ( e. unwrap ( ) ) ,
@@ -74,6 +109,10 @@ impl<R: io::Read, C: Callbacks> IdentityState<R, C> {
74
109
}
75
110
76
111
/// An encrypted age identity file.
112
+ ///
113
+ /// This type can be used directly as an identity and caches the decryption result
114
+ /// internally. If you want a type that can be explicitly decrypted to obtain an
115
+ /// [`IdentityFile`], use [`EncryptedIdentity`].
77
116
pub struct Identity < R : io:: Read , C : Callbacks > {
78
117
state : Cell < IdentityState < R , C > > ,
79
118
filename : Option < String > ,
@@ -92,13 +131,9 @@ impl<R: io::Read, C: Callbacks> Identity<R, C> {
92
131
callbacks : C ,
93
132
max_work_factor : Option < u8 > ,
94
133
) -> Result < Option < Self > , DecryptError > {
95
- let decryptor = Decryptor :: new ( data) ?;
96
- Ok ( decryptor. is_scrypt ( ) . then_some ( Identity {
97
- state : Cell :: new ( IdentityState :: Encrypted {
98
- decryptor,
99
- max_work_factor,
100
- callbacks,
101
- } ) ,
134
+ let encrypted = EncryptedIdentity :: from_buffer ( data, callbacks, max_work_factor) ?;
135
+ Ok ( encrypted. map ( |encrypted| Identity {
136
+ state : Cell :: new ( IdentityState :: Encrypted ( encrypted) ) ,
102
137
filename,
103
138
} ) )
104
139
}
@@ -138,7 +173,7 @@ impl<R: io::Read, C: Callbacks> Identity<R, C> {
138
173
) -> Option < Result < age_core:: format:: FileKey , DecryptError > >
139
174
where
140
175
F : Fn (
141
- Result < Box < dyn crate :: Identity > , DecryptError > ,
176
+ Result < Box < dyn crate :: Identity + Send + Sync > , DecryptError > ,
142
177
) -> Option < Result < age_core:: format:: FileKey , DecryptError > > ,
143
178
{
144
179
match self . state . take ( ) . decrypt ( self . filename . as_deref ( ) ) {
0 commit comments