@@ -4,13 +4,80 @@ use std::{cell::Cell, io};
44
55use crate :: { fl, scrypt, Callbacks , DecryptError , Decryptor , EncryptError , IdentityFile } ;
66
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+
778/// The state of the encrypted age identity.
879enum 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 > ) ,
1481 Decrypted ( IdentityFile < C > ) ,
1582
1683 /// 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> {
33100 /// were not cached (and we just asked the user for a passphrase).
34101 fn decrypt ( self , filename : Option < & str > ) -> Result < ( IdentityFile < C > , bool ) , DecryptError > {
35102 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 ) ) ,
69104 Self :: Decrypted ( identity_file) => Ok ( ( identity_file, false ) ) ,
70105 // `IdentityState::decrypt` is only ever called with `Some`.
71106 Self :: Poisoned ( e) => Err ( e. unwrap ( ) ) ,
@@ -74,6 +109,10 @@ impl<R: io::Read, C: Callbacks> IdentityState<R, C> {
74109}
75110
76111/// 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`].
77116pub struct Identity < R : io:: Read , C : Callbacks > {
78117 state : Cell < IdentityState < R , C > > ,
79118 filename : Option < String > ,
@@ -92,13 +131,9 @@ impl<R: io::Read, C: Callbacks> Identity<R, C> {
92131 callbacks : C ,
93132 max_work_factor : Option < u8 > ,
94133 ) -> 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) ) ,
102137 filename,
103138 } ) )
104139 }
@@ -138,7 +173,7 @@ impl<R: io::Read, C: Callbacks> Identity<R, C> {
138173 ) -> Option < Result < age_core:: format:: FileKey , DecryptError > >
139174 where
140175 F : Fn (
141- Result < Box < dyn crate :: Identity > , DecryptError > ,
176+ Result < Box < dyn crate :: Identity + Send + Sync > , DecryptError > ,
142177 ) -> Option < Result < age_core:: format:: FileKey , DecryptError > > ,
143178 {
144179 match self . state . take ( ) . decrypt ( self . filename . as_deref ( ) ) {
0 commit comments