14
14
//! - storing PIN for one of the cards: `SSH_AUTH_SOCK=/tmp/sock ssh-add -s 0006:15422467` (the agent will validate the PIN before storing it)
15
15
//! - and that's it! You can use the agent to login to your SSH servers.
16
16
17
- use std:: {
18
- collections:: HashMap ,
19
- sync:: { Arc , Mutex } ,
20
- } ;
17
+ use std:: { sync:: Arc , time:: Duration } ;
21
18
22
19
use card_backend_pcsc:: PcscBackend ;
23
20
use clap:: Parser ;
@@ -26,11 +23,13 @@ use openpgp_card::{
26
23
crypto_data:: { EccType , PublicKeyMaterial } ,
27
24
Card , KeyType ,
28
25
} ;
26
+ use retainer:: { Cache , CacheExpiration } ;
27
+ use secrecy:: { ExposeSecret , SecretString } ;
29
28
use service_binding:: Binding ;
30
29
use ssh_agent_lib:: {
31
30
agent:: Session ,
32
31
error:: AgentError ,
33
- proto:: { Identity , SignRequest , SmartcardKey } ,
32
+ proto:: { AddSmartcardKeyConstrained , Identity , KeyConstraint , SignRequest , SmartcardKey } ,
34
33
Agent ,
35
34
} ;
36
35
use ssh_key:: {
@@ -39,9 +38,17 @@ use ssh_key::{
39
38
} ;
40
39
use testresult:: TestResult ;
41
40
42
- #[ derive( Default ) ]
43
41
struct CardAgent {
44
- pwds : Arc < Mutex < HashMap < String , String > > > ,
42
+ pwds : Arc < Cache < String , SecretString > > ,
43
+ }
44
+
45
+ impl CardAgent {
46
+ pub fn new ( ) -> Self {
47
+ let pwds: Arc < Cache < String , SecretString > > = Arc :: new ( Default :: default ( ) ) ;
48
+ let clone = Arc :: clone ( & pwds) ;
49
+ tokio:: spawn ( async move { clone. monitor ( 4 , 0.25 , Duration :: from_secs ( 3 ) ) . await } ) ;
50
+ Self { pwds }
51
+ }
45
52
}
46
53
47
54
impl Agent for CardAgent {
@@ -53,7 +60,74 @@ impl Agent for CardAgent {
53
60
}
54
61
55
62
struct CardSession {
56
- pwds : Arc < Mutex < HashMap < String , String > > > ,
63
+ pwds : Arc < Cache < String , SecretString > > ,
64
+ }
65
+
66
+ impl CardSession {
67
+ async fn handle_sign (
68
+ & self ,
69
+ request : SignRequest ,
70
+ ) -> Result < ssh_key:: Signature , Box < dyn std:: error:: Error + Send + Sync > > {
71
+ let cards = PcscBackend :: cards ( None ) . map_err ( AgentError :: other) ?;
72
+ for card in cards {
73
+ let mut card = Card :: new ( card?) ?;
74
+ let mut tx = card. transaction ( ) ?;
75
+ let ident = tx. application_identifier ( ) ?. ident ( ) ;
76
+ if let PublicKeyMaterial :: E ( e) = tx. public_key ( KeyType :: Authentication ) ? {
77
+ if let AlgorithmAttributes :: Ecc ( ecc) = e. algo ( ) {
78
+ if ecc. ecc_type ( ) == EccType :: EdDSA {
79
+ let pubkey = KeyData :: Ed25519 ( Ed25519PublicKey ( e. data ( ) . try_into ( ) ?) ) ;
80
+ if pubkey == request. pubkey {
81
+ let pin = self . pwds . get ( & ident) . await ;
82
+ return if let Some ( pin) = pin {
83
+ tx. verify_pw1_user ( pin. expose_secret ( ) . as_bytes ( ) ) ?;
84
+ let signature = tx. internal_authenticate ( request. data . clone ( ) ) ?;
85
+
86
+ Ok ( Signature :: new ( Algorithm :: Ed25519 , signature) ?)
87
+ } else {
88
+ // no pin saved, use "ssh-add -s ..."
89
+ Err ( std:: io:: Error :: other ( "no pin saved" ) . into ( ) )
90
+ } ;
91
+ }
92
+ }
93
+ }
94
+ }
95
+ }
96
+ Err ( std:: io:: Error :: other ( "no applicable card found" ) . into ( ) )
97
+ }
98
+
99
+ async fn handle_add_smartcard_key (
100
+ & mut self ,
101
+ key : SmartcardKey ,
102
+ expiration : impl Into < CacheExpiration > ,
103
+ ) -> Result < ( ) , AgentError > {
104
+ match PcscBackend :: cards ( None ) {
105
+ Ok ( cards) => {
106
+ let card_pin_matches = cards
107
+ . flat_map ( |card| {
108
+ let mut card = Card :: new ( card?) ?;
109
+ let mut tx = card. transaction ( ) ?;
110
+ let ident = tx. application_identifier ( ) ?. ident ( ) ;
111
+ if ident == key. id {
112
+ tx. verify_pw1_user ( key. pin . as_bytes ( ) ) ?;
113
+ Ok :: < _ , Box < dyn std:: error:: Error > > ( true )
114
+ } else {
115
+ Ok ( false )
116
+ }
117
+ } )
118
+ . any ( |x| x) ;
119
+ if card_pin_matches {
120
+ self . pwds . insert ( key. id , key. pin . into ( ) , expiration) . await ;
121
+ Ok ( ( ) )
122
+ } else {
123
+ Err ( AgentError :: IO ( std:: io:: Error :: other (
124
+ "Card/PIN combination is not valid" ,
125
+ ) ) )
126
+ }
127
+ }
128
+ Err ( error) => Err ( AgentError :: other ( error) ) ,
129
+ }
130
+ }
57
131
}
58
132
59
133
#[ ssh_agent_lib:: async_trait]
@@ -87,70 +161,32 @@ impl Session for CardSession {
87
161
}
88
162
89
163
async fn add_smartcard_key ( & mut self , key : SmartcardKey ) -> Result < ( ) , AgentError > {
90
- match PcscBackend :: cards ( None ) {
91
- Ok ( cards) => {
92
- let card_pin_matches = cards
93
- . flat_map ( |card| {
94
- let mut card = Card :: new ( card?) ?;
95
- let mut tx = card. transaction ( ) ?;
96
- let ident = tx. application_identifier ( ) ?. ident ( ) ;
97
- if ident == key. id {
98
- tx. verify_pw1_user ( key. pin . as_bytes ( ) ) ?;
99
- Ok :: < _ , Box < dyn std:: error:: Error > > ( true )
100
- } else {
101
- Ok ( false )
102
- }
103
- } )
104
- . any ( |x| x) ;
105
- if card_pin_matches {
106
- self . pwds
107
- . lock ( )
108
- . expect ( "lock not to be poisoned" )
109
- . insert ( key. id , key. pin ) ;
110
- Ok ( ( ) )
111
- } else {
112
- Err ( AgentError :: IO ( std:: io:: Error :: other (
113
- "Card/PIN combination is not valid" ,
114
- ) ) )
115
- }
116
- }
117
- Err ( error) => Err ( AgentError :: other ( error) ) ,
164
+ self . handle_add_smartcard_key ( key, CacheExpiration :: none ( ) )
165
+ . await
166
+ }
167
+
168
+ async fn add_smartcard_key_constrained (
169
+ & mut self ,
170
+ key : AddSmartcardKeyConstrained ,
171
+ ) -> Result < ( ) , AgentError > {
172
+ if key. constraints . len ( ) > 1 {
173
+ return Err ( AgentError :: other ( std:: io:: Error :: other (
174
+ "Only one lifetime constraint supported." ,
175
+ ) ) ) ;
118
176
}
177
+ let expiration_in_seconds = if let KeyConstraint :: Lifetime ( seconds) = key. constraints [ 0 ] {
178
+ Duration :: from_secs ( seconds as u64 )
179
+ } else {
180
+ return Err ( AgentError :: other ( std:: io:: Error :: other (
181
+ "Only one lifetime constraint supported." ,
182
+ ) ) ) ;
183
+ } ;
184
+ self . handle_add_smartcard_key ( key. key , expiration_in_seconds)
185
+ . await
119
186
}
120
187
121
188
async fn sign ( & mut self , request : SignRequest ) -> Result < Signature , AgentError > {
122
- let cards = PcscBackend :: cards ( None ) . map_err ( AgentError :: other) ?;
123
- cards
124
- . flat_map ( |card| -> Result < _ , Box < dyn std:: error:: Error > > {
125
- let mut card = Card :: new ( card?) ?;
126
- let mut tx = card. transaction ( ) ?;
127
- let ident = tx. application_identifier ( ) ?. ident ( ) ;
128
- if let PublicKeyMaterial :: E ( e) = tx. public_key ( KeyType :: Authentication ) ? {
129
- if let AlgorithmAttributes :: Ecc ( ecc) = e. algo ( ) {
130
- if ecc. ecc_type ( ) == EccType :: EdDSA {
131
- let pubkey = KeyData :: Ed25519 ( Ed25519PublicKey ( e. data ( ) . try_into ( ) ?) ) ;
132
- if pubkey == request. pubkey {
133
- let pwds = self . pwds . lock ( ) . expect ( "mutex not to be poisoned" ) ;
134
- let pin = pwds. get ( & ident) ;
135
- return if let Some ( pin) = pin {
136
- tx. verify_pw1_user ( pin. as_bytes ( ) ) ?;
137
- let signature =
138
- tx. internal_authenticate ( request. data . clone ( ) ) ?;
139
-
140
- Ok ( Signature :: new ( Algorithm :: Ed25519 , signature) )
141
- } else {
142
- // no pin saved, use "ssh-add -s ..."
143
- Err ( std:: io:: Error :: other ( "no pin saved" ) . into ( ) )
144
- } ;
145
- }
146
- }
147
- }
148
- }
149
- Err ( std:: io:: Error :: other ( "no applicable card found" ) . into ( ) )
150
- } )
151
- . flatten ( )
152
- . next ( )
153
- . ok_or ( std:: io:: Error :: other ( "no applicable card found" ) . into ( ) )
189
+ self . handle_sign ( request) . await . map_err ( AgentError :: Other )
154
190
}
155
191
}
156
192
@@ -162,7 +198,9 @@ struct Args {
162
198
163
199
#[ tokio:: main]
164
200
async fn main ( ) -> TestResult {
201
+ env_logger:: init ( ) ;
202
+
165
203
let args = Args :: parse ( ) ;
166
- CardAgent :: default ( ) . bind ( args. host . try_into ( ) ?) . await ?;
204
+ CardAgent :: new ( ) . bind ( args. host . try_into ( ) ?) . await ?;
167
205
Ok ( ( ) )
168
206
}
0 commit comments