@@ -96,14 +96,32 @@ impl ChannelBinding {
96
96
}
97
97
}
98
98
99
+ /// A pair of keys for the SCRAM-SHA-256 mechanism.
100
+ /// See <https://datatracker.ietf.org/doc/html/rfc5802#section-3> for details.
101
+ #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
102
+ pub struct ScramKeys < const N : usize > {
103
+ /// Used by server to authenticate client.
104
+ pub client_key : [ u8 ; N ] ,
105
+ /// Used by client to verify server's signature.
106
+ pub server_key : [ u8 ; N ] ,
107
+ }
108
+
109
+ /// Password or keys which were derived from it.
110
+ enum Credentials < const N : usize > {
111
+ /// A regular password as a vector of bytes.
112
+ Password ( Vec < u8 > ) ,
113
+ /// A precomputed pair of keys.
114
+ Keys ( Box < ScramKeys < N > > ) ,
115
+ }
116
+
99
117
enum State {
100
118
Update {
101
119
nonce : String ,
102
- password : Vec < u8 > ,
120
+ password : Credentials < 32 > ,
103
121
channel_binding : ChannelBinding ,
104
122
} ,
105
123
Finish {
106
- salted_password : [ u8 ; 32 ] ,
124
+ server_key : [ u8 ; 32 ] ,
107
125
auth_message : String ,
108
126
} ,
109
127
Done ,
@@ -129,30 +147,43 @@ pub struct ScramSha256 {
129
147
state : State ,
130
148
}
131
149
150
+ fn nonce ( ) -> String {
151
+ // rand 0.5's ThreadRng is cryptographically secure
152
+ let mut rng = rand:: thread_rng ( ) ;
153
+ ( 0 ..NONCE_LENGTH )
154
+ . map ( |_| {
155
+ let mut v = rng. gen_range ( 0x21u8 ..0x7e ) ;
156
+ if v == 0x2c {
157
+ v = 0x7e
158
+ }
159
+ v as char
160
+ } )
161
+ . collect ( )
162
+ }
163
+
132
164
impl ScramSha256 {
133
165
/// Constructs a new instance which will use the provided password for authentication.
134
166
pub fn new ( password : & [ u8 ] , channel_binding : ChannelBinding ) -> ScramSha256 {
135
- // rand 0.5's ThreadRng is cryptographically secure
136
- let mut rng = rand:: thread_rng ( ) ;
137
- let nonce = ( 0 ..NONCE_LENGTH )
138
- . map ( |_| {
139
- let mut v = rng. gen_range ( 0x21u8 ..0x7e ) ;
140
- if v == 0x2c {
141
- v = 0x7e
142
- }
143
- v as char
144
- } )
145
- . collect :: < String > ( ) ;
167
+ let password = Credentials :: Password ( normalize ( password) ) ;
168
+ ScramSha256 :: new_inner ( password, channel_binding, nonce ( ) )
169
+ }
146
170
147
- ScramSha256 :: new_inner ( password, channel_binding, nonce)
171
+ /// Constructs a new instance which will use the provided key pair for authentication.
172
+ pub fn new_with_keys ( keys : ScramKeys < 32 > , channel_binding : ChannelBinding ) -> ScramSha256 {
173
+ let password = Credentials :: Keys ( keys. into ( ) ) ;
174
+ ScramSha256 :: new_inner ( password, channel_binding, nonce ( ) )
148
175
}
149
176
150
- fn new_inner ( password : & [ u8 ] , channel_binding : ChannelBinding , nonce : String ) -> ScramSha256 {
177
+ fn new_inner (
178
+ password : Credentials < 32 > ,
179
+ channel_binding : ChannelBinding ,
180
+ nonce : String ,
181
+ ) -> ScramSha256 {
151
182
ScramSha256 {
152
183
message : format ! ( "{}n=,r={}" , channel_binding. gs2_header( ) , nonce) ,
153
184
state : State :: Update {
154
185
nonce,
155
- password : normalize ( password ) ,
186
+ password,
156
187
channel_binding,
157
188
} ,
158
189
}
@@ -189,20 +220,32 @@ impl ScramSha256 {
189
220
return Err ( io:: Error :: new ( io:: ErrorKind :: InvalidInput , "invalid nonce" ) ) ;
190
221
}
191
222
192
- let salt = match base64:: decode ( parsed. salt ) {
193
- Ok ( salt) => salt,
194
- Err ( e) => return Err ( io:: Error :: new ( io:: ErrorKind :: InvalidInput , e) ) ,
195
- } ;
223
+ let ( client_key, server_key) = match password {
224
+ Credentials :: Password ( password) => {
225
+ let salt = match base64:: decode ( parsed. salt ) {
226
+ Ok ( salt) => salt,
227
+ Err ( e) => return Err ( io:: Error :: new ( io:: ErrorKind :: InvalidInput , e) ) ,
228
+ } ;
196
229
197
- let salted_password = hi ( & password, & salt, parsed. iteration_count ) ;
230
+ let salted_password = hi ( & password, & salt, parsed. iteration_count ) ;
198
231
199
- let mut hmac = Hmac :: < Sha256 > :: new_from_slice ( & salted_password)
200
- . expect ( "HMAC is able to accept all key sizes" ) ;
201
- hmac. update ( b"Client Key" ) ;
202
- let client_key = hmac. finalize ( ) . into_bytes ( ) ;
232
+ let make_key = |name| {
233
+ let mut hmac = Hmac :: < Sha256 > :: new_from_slice ( & salted_password)
234
+ . expect ( "HMAC is able to accept all key sizes" ) ;
235
+ hmac. update ( name) ;
236
+
237
+ let mut key = [ 0u8 ; 32 ] ;
238
+ key. copy_from_slice ( hmac. finalize ( ) . into_bytes ( ) . as_slice ( ) ) ;
239
+ key
240
+ } ;
241
+
242
+ ( make_key ( b"Client Key" ) , make_key ( b"Server Key" ) )
243
+ }
244
+ Credentials :: Keys ( keys) => ( keys. client_key , keys. server_key ) ,
245
+ } ;
203
246
204
247
let mut hash = Sha256 :: default ( ) ;
205
- hash. update ( client_key. as_slice ( ) ) ;
248
+ hash. update ( client_key) ;
206
249
let stored_key = hash. finalize_fixed ( ) ;
207
250
208
251
let mut cbind_input = vec ! [ ] ;
@@ -225,10 +268,10 @@ impl ScramSha256 {
225
268
* proof ^= signature;
226
269
}
227
270
228
- write ! ( & mut self . message, ",p={}" , base64:: encode( & * client_proof) ) . unwrap ( ) ;
271
+ write ! ( & mut self . message, ",p={}" , base64:: encode( client_proof) ) . unwrap ( ) ;
229
272
230
273
self . state = State :: Finish {
231
- salted_password ,
274
+ server_key ,
232
275
auth_message,
233
276
} ;
234
277
Ok ( ( ) )
@@ -239,11 +282,11 @@ impl ScramSha256 {
239
282
/// This should be called when the backend sends an `AuthenticationSASLFinal` message.
240
283
/// Authentication has only succeeded if this method returns `Ok(())`.
241
284
pub fn finish ( & mut self , message : & [ u8 ] ) -> io:: Result < ( ) > {
242
- let ( salted_password , auth_message) = match mem:: replace ( & mut self . state , State :: Done ) {
285
+ let ( server_key , auth_message) = match mem:: replace ( & mut self . state , State :: Done ) {
243
286
State :: Finish {
244
- salted_password ,
287
+ server_key ,
245
288
auth_message,
246
- } => ( salted_password , auth_message) ,
289
+ } => ( server_key , auth_message) ,
247
290
_ => return Err ( io:: Error :: new ( io:: ErrorKind :: Other , "invalid SCRAM state" ) ) ,
248
291
} ;
249
292
@@ -267,11 +310,6 @@ impl ScramSha256 {
267
310
Err ( e) => return Err ( io:: Error :: new ( io:: ErrorKind :: InvalidInput , e) ) ,
268
311
} ;
269
312
270
- let mut hmac = Hmac :: < Sha256 > :: new_from_slice ( & salted_password)
271
- . expect ( "HMAC is able to accept all key sizes" ) ;
272
- hmac. update ( b"Server Key" ) ;
273
- let server_key = hmac. finalize ( ) . into_bytes ( ) ;
274
-
275
313
let mut hmac = Hmac :: < Sha256 > :: new_from_slice ( & server_key)
276
314
. expect ( "HMAC is able to accept all key sizes" ) ;
277
315
hmac. update ( auth_message. as_bytes ( ) ) ;
@@ -458,7 +496,7 @@ mod test {
458
496
let server_final = "v=U+ppxD5XUKtradnv8e2MkeupiA8FU87Sg8CXzXHDAzw=" ;
459
497
460
498
let mut scram = ScramSha256 :: new_inner (
461
- password. as_bytes ( ) ,
499
+ Credentials :: Password ( normalize ( password. as_bytes ( ) ) ) ,
462
500
ChannelBinding :: unsupported ( ) ,
463
501
nonce. to_string ( ) ,
464
502
) ;
0 commit comments