@@ -4,7 +4,8 @@ use std::{
4
4
} ;
5
5
6
6
use base64:: { engine:: general_purpose, Engine as _} ;
7
- use md5:: Digest ;
7
+ use hmac:: { Hmac , Mac } ;
8
+ use md5:: { Digest , Md5 } ;
8
9
use once_cell:: sync:: Lazy ;
9
10
use rand:: Rng ;
10
11
use regex:: Regex ;
@@ -16,6 +17,8 @@ use super::{
16
17
user:: { Token , UserInfo } ,
17
18
} ;
18
19
20
+ type HmacMD5 = Hmac < Md5 > ;
21
+
19
22
static CHALLENGE_PATTERN : Lazy < Regex > =
20
23
Lazy :: new ( || Regex :: new ( r#",?([a-zA-Z0-9]+)=("([^"]+)"|([^,]+)),?"# ) . unwrap ( ) ) ;
21
24
static RESPONSE_PATTERN : Lazy < Regex > = Lazy :: new ( || Regex :: new ( "rspauth=([a-f0-9]{32})" ) . unwrap ( ) ) ;
@@ -70,7 +73,7 @@ impl Qop {
70
73
}
71
74
}
72
75
73
- static SUPPORTED_QOPS : [ Qop ; 1 ] = [ Qop :: Auth ] ;
76
+ static SUPPORTED_QOPS : [ Qop ; 2 ] = [ Qop :: Auth , Qop :: AuthInt ] ;
74
77
75
78
fn choose_qop ( options : Vec < Qop > ) -> Result < Qop > {
76
79
options
@@ -82,11 +85,13 @@ fn choose_qop(options: Vec<Qop>) -> Result<Qop> {
82
85
) )
83
86
}
84
87
85
- fn h ( s : impl AsRef < [ u8 ] > ) -> Digest {
86
- md5:: compute ( s. as_ref ( ) )
88
+ fn h ( b : impl AsRef < [ u8 ] > ) -> Vec < u8 > {
89
+ let mut hasher = Md5 :: new ( ) ;
90
+ hasher. update ( b. as_ref ( ) ) ;
91
+ hasher. finalize ( ) . to_vec ( )
87
92
}
88
93
89
- fn kd ( k : impl AsRef < [ u8 ] > , v : impl AsRef < [ u8 ] > ) -> Digest {
94
+ fn kd ( k : impl AsRef < [ u8 ] > , v : impl AsRef < [ u8 ] > ) -> Vec < u8 > {
90
95
h ( [ k. as_ref ( ) , b":" , v. as_ref ( ) ] . concat ( ) )
91
96
}
92
97
@@ -119,7 +124,6 @@ impl TryFrom<Vec<u8>> for Challenge {
119
124
120
125
fn try_from ( value : Vec < u8 > ) -> core:: result:: Result < Self , Self :: Error > {
121
126
let decoded = String :: from_utf8 ( value) . unwrap ( ) ;
122
- println ! ( "Parsing {}" , decoded) ;
123
127
let mut options: HashMap < String , String > = HashMap :: new ( ) ;
124
128
for capture in CHALLENGE_PATTERN . captures_iter ( & decoded) {
125
129
let key = capture. get ( 1 ) . unwrap ( ) . as_str ( ) . to_string ( ) ;
@@ -172,10 +176,20 @@ struct DigestContext {
172
176
qop : Qop ,
173
177
}
174
178
179
+ struct IntegrityContext {
180
+ kic : Vec < u8 > ,
181
+ kis : Vec < u8 > ,
182
+ seq_num : u32 ,
183
+ }
184
+
185
+ enum SecurityContext {
186
+ Integrity ( IntegrityContext ) ,
187
+ }
188
+
175
189
enum DigestState {
176
190
Pending ,
177
191
Stepped ( DigestContext ) ,
178
- Completed ( DigestContext ) ,
192
+ Completed ( Option < SecurityContext > ) ,
179
193
Errored ,
180
194
}
181
195
@@ -199,17 +213,17 @@ impl DigestSaslSession {
199
213
}
200
214
201
215
fn compute ( & self , ctx : & DigestContext , initial : bool ) -> String {
202
- let x = format ! ( "{:x}" , h( self . a1( ctx) ) ) ;
216
+ let x = hex :: encode ( h ( self . a1 ( ctx) ) ) ;
203
217
let y = format ! (
204
- "{}:{:08x}:{}:{}:{:x }" ,
218
+ "{}:{:08x}:{}:{}:{}" ,
205
219
ctx. nonce,
206
220
1 ,
207
221
ctx. cnonce,
208
222
ctx. qop,
209
- h( self . a2( initial, & ctx. qop) )
223
+ hex :: encode ( h( self . a2( initial, & ctx. qop) ) )
210
224
) ;
211
225
212
- format ! ( "{:x}" , kd( x, y) )
226
+ hex :: encode ( kd ( x, y) )
213
227
}
214
228
215
229
fn a1 ( & self , ctx : & DigestContext ) -> Vec < u8 > {
@@ -294,7 +308,18 @@ impl SaslSession for DigestSaslSession {
294
308
) ) ;
295
309
}
296
310
297
- self . state = DigestState :: Completed ( ctx) ;
311
+ self . state = match ctx. qop {
312
+ Qop :: Auth => DigestState :: Completed ( None ) ,
313
+ Qop :: AuthInt => {
314
+ let int_ctx = IntegrityContext {
315
+ kic : h ( [ & h ( self . a1 ( & ctx) ) [ ..] , b"Digest session key to client-to-server signing key magic constant" ] . concat ( ) ) ,
316
+ kis : h ( [ & h ( self . a1 ( & ctx) ) [ ..] , b"Digest session key to server-to-client signing key magic constant" ] . concat ( ) ) ,
317
+ seq_num : 0
318
+ } ;
319
+ DigestState :: Completed ( Some ( SecurityContext :: Integrity ( int_ctx) ) )
320
+ }
321
+ _ => todo ! ( ) ,
322
+ } ;
298
323
Ok ( ( Vec :: new ( ) , true ) )
299
324
}
300
325
DigestState :: Completed ( _) => Err ( HdfsError :: SASLError (
@@ -308,17 +333,64 @@ impl SaslSession for DigestSaslSession {
308
333
309
334
fn has_security_layer ( & self ) -> bool {
310
335
match & self . state {
311
- DigestState :: Completed ( ctx) => ctx. qop . has_security_layer ( ) ,
336
+ DigestState :: Completed ( ctx) => ctx. is_some ( ) ,
312
337
_ => false ,
313
338
}
314
339
}
315
340
316
- fn encode ( & mut self , _buf : & [ u8 ] ) -> crate :: Result < Vec < u8 > > {
317
- todo ! ( )
341
+ fn encode ( & mut self , buf : & [ u8 ] ) -> crate :: Result < Vec < u8 > > {
342
+ match & mut self . state {
343
+ DigestState :: Completed ( ctx) => match ctx {
344
+ Some ( SecurityContext :: Integrity ( int_ctx) ) => {
345
+ let mut mac = HmacMD5 :: new_from_slice ( & int_ctx. kic ) . unwrap ( ) ;
346
+ mac. update ( & int_ctx. seq_num . to_be_bytes ( ) ) ;
347
+ mac. update ( buf) ;
348
+ let result = mac. finalize ( ) . into_bytes ( ) ;
349
+
350
+ let message =
351
+ [ buf, & result[ 0 ..10 ] , & [ 0 , 1 ] , & int_ctx. seq_num . to_be_bytes ( ) ] . concat ( ) ;
352
+
353
+ int_ctx. seq_num += 1 ;
354
+ Ok ( message)
355
+ }
356
+ None => Err ( HdfsError :: SASLError (
357
+ "QOP doesn't support security layer" . to_string ( ) ,
358
+ ) ) ,
359
+ } ,
360
+ _ => Err ( HdfsError :: SASLError (
361
+ "SASL negotiation not complete, can't encode message" . to_string ( ) ,
362
+ ) ) ,
363
+ }
318
364
}
319
365
320
- fn decode ( & mut self , _buf : & [ u8 ] ) -> crate :: Result < Vec < u8 > > {
321
- todo ! ( )
366
+ fn decode ( & mut self , buf : & [ u8 ] ) -> crate :: Result < Vec < u8 > > {
367
+ match & self . state {
368
+ DigestState :: Completed ( ctx) => match ctx {
369
+ Some ( SecurityContext :: Integrity ( int_ctx) ) => {
370
+ let message = & buf[ 0 ..buf. len ( ) - 16 ] ;
371
+ let hmac = & buf[ buf. len ( ) - 16 ..buf. len ( ) - 6 ] ;
372
+ let mut seq_num_bytes = [ 0u8 ; 4 ] ;
373
+ seq_num_bytes. copy_from_slice ( & buf[ buf. len ( ) - 4 ..] ) ;
374
+ let seq_num = u32:: from_be_bytes ( seq_num_bytes) ;
375
+
376
+ let mut mac = HmacMD5 :: new_from_slice ( & int_ctx. kis ) . unwrap ( ) ;
377
+ mac. update ( & seq_num. to_be_bytes ( ) ) ;
378
+ mac. update ( message) ;
379
+
380
+ mac. verify_truncated_left ( hmac) . map_err ( |_| {
381
+ HdfsError :: SASLError ( "Integrity HMAC check failed" . to_string ( ) )
382
+ } ) ?;
383
+
384
+ Ok ( message. to_vec ( ) )
385
+ }
386
+ None => Err ( HdfsError :: SASLError (
387
+ "QOP doesn't support security layer" . to_string ( ) ,
388
+ ) ) ,
389
+ } ,
390
+ _ => Err ( HdfsError :: SASLError (
391
+ "SASL negotiation not complete, can't decode message" . to_string ( ) ,
392
+ ) ) ,
393
+ }
322
394
}
323
395
324
396
fn get_user_info ( & self ) -> crate :: Result < super :: user:: UserInfo > {
0 commit comments