@@ -84,27 +84,44 @@ const AUTH_TIMESTAMP_TOLERANCE_SECS: u64 = 60;
8484
8585#[ derive( Debug , Clone ) ]
8686pub ( crate ) struct AuthParams {
87+ key_id : String ,
8788 timestamp : u64 ,
8889 hmac_hex : String ,
8990}
9091
92+ /// Computes the key_id for an API key: first 8 bytes of SHA256(api_key), hex-encoded (16 chars).
93+ fn compute_key_id ( api_key : & str ) -> String {
94+ let hash = sha256:: Hash :: hash ( api_key. as_bytes ( ) ) ;
95+ hash[ ..8 ] . iter ( ) . map ( |b| format ! ( "{:02x}" , b) ) . collect :: < String > ( )
96+ }
97+
9198/// Extracts authentication parameters from request headers.
92- /// Returns (timestamp, hmac_hex) if valid format, or error.
99+ /// Returns (key_id, timestamp, hmac_hex) if valid format, or error.
93100fn extract_auth_params < B > ( req : & Request < B > ) -> Result < AuthParams , LdkServerError > {
94101 let auth_header = req
95102 . headers ( )
96103 . get ( "X-Auth" )
97104 . and_then ( |v| v. to_str ( ) . ok ( ) )
98105 . ok_or_else ( || LdkServerError :: new ( AuthError , "Missing X-Auth header" ) ) ?;
99106
100- // Format: "HMAC <timestamp>:<hmac_hex>"
107+ // Format: "HMAC <key_id>:< timestamp>:<hmac_hex>"
101108 let auth_data = auth_header
102109 . strip_prefix ( "HMAC " )
103110 . ok_or_else ( || LdkServerError :: new ( AuthError , "Invalid X-Auth header format" ) ) ?;
104111
105- let ( timestamp_str, hmac_hex) = auth_data
106- . split_once ( ':' )
107- . ok_or_else ( || LdkServerError :: new ( AuthError , "Invalid X-Auth header format" ) ) ?;
112+ let parts: Vec < & str > = auth_data. splitn ( 3 , ':' ) . collect ( ) ;
113+ if parts. len ( ) != 3 {
114+ return Err ( LdkServerError :: new ( AuthError , "Invalid X-Auth header format" ) ) ;
115+ }
116+
117+ let key_id = parts[ 0 ] ;
118+ let timestamp_str = parts[ 1 ] ;
119+ let hmac_hex = parts[ 2 ] ;
120+
121+ // Validate key_id is 16 hex chars
122+ if key_id. len ( ) != 16 || !key_id. chars ( ) . all ( |c| c. is_ascii_hexdigit ( ) ) {
123+ return Err ( LdkServerError :: new ( AuthError , "Invalid key_id in X-Auth header" ) ) ;
124+ }
108125
109126 let timestamp = timestamp_str
110127 . parse :: < u64 > ( )
@@ -115,13 +132,19 @@ fn extract_auth_params<B>(req: &Request<B>) -> Result<AuthParams, LdkServerError
115132 return Err ( LdkServerError :: new ( AuthError , "Invalid HMAC in X-Auth header" ) ) ;
116133 }
117134
118- Ok ( AuthParams { timestamp, hmac_hex : hmac_hex. to_string ( ) } )
135+ Ok ( AuthParams { key_id : key_id . to_string ( ) , timestamp, hmac_hex : hmac_hex. to_string ( ) } )
119136}
120137
121138/// Validates the HMAC authentication after the request body has been read.
122139fn validate_hmac_auth (
123- timestamp : u64 , provided_hmac_hex : & str , body : & [ u8 ] , api_key : & str ,
140+ key_id : & str , timestamp : u64 , provided_hmac_hex : & str , body : & [ u8 ] , api_key : & str ,
124141) -> Result < ( ) , LdkServerError > {
142+ // Verify the key_id matches the api_key
143+ let expected_key_id = compute_key_id ( api_key) ;
144+ if key_id != expected_key_id {
145+ return Err ( LdkServerError :: new ( AuthError , "Invalid credentials" ) ) ;
146+ }
147+
125148 // Validate timestamp is within acceptable window
126149 let now = std:: time:: SystemTime :: now ( )
127150 . duration_since ( std:: time:: UNIX_EPOCH )
@@ -406,9 +429,13 @@ async fn handle_request<
406429 } ;
407430
408431 // Validate HMAC authentication with the request body
409- if let Err ( e) =
410- validate_hmac_auth ( auth_params. timestamp , & auth_params. hmac_hex , & bytes, & api_key)
411- {
432+ if let Err ( e) = validate_hmac_auth (
433+ & auth_params. key_id ,
434+ auth_params. timestamp ,
435+ & auth_params. hmac_hex ,
436+ & bytes,
437+ & api_key,
438+ ) {
412439 let ( error_response, status_code) = to_error_response ( e) ;
413440 return Ok ( Response :: builder ( )
414441 . status ( status_code)
@@ -468,13 +495,15 @@ mod tests {
468495 let timestamp =
469496 std:: time:: SystemTime :: now ( ) . duration_since ( std:: time:: UNIX_EPOCH ) . unwrap ( ) . as_secs ( ) ;
470497 let hmac = "8f5a33c2c68fb253899a588308fd13dcaf162d2788966a1fb6cc3aa2e0c51a93" ;
471- let auth_header = format ! ( "HMAC {timestamp}:{hmac}" ) ;
498+ let key_id = "abcdef0123456789" ;
499+ let auth_header = format ! ( "HMAC {key_id}:{timestamp}:{hmac}" ) ;
472500
473501 let req = create_test_request ( Some ( auth_header) ) ;
474502
475503 let result = extract_auth_params ( & req) ;
476504 assert ! ( result. is_ok( ) ) ;
477- let AuthParams { timestamp : ts, hmac_hex } = result. unwrap ( ) ;
505+ let AuthParams { key_id : kid, timestamp : ts, hmac_hex } = result. unwrap ( ) ;
506+ assert_eq ! ( kid, key_id) ;
478507 assert_eq ! ( ts, timestamp) ;
479508 assert_eq ! ( hmac_hex, hmac) ;
480509 }
@@ -501,47 +530,65 @@ mod tests {
501530 #[ test]
502531 fn test_validate_hmac_auth_success ( ) {
503532 let api_key = "test_api_key" . to_string ( ) ;
533+ let key_id = compute_key_id ( & api_key) ;
504534 let body = b"test request body" ;
505535 let timestamp =
506536 std:: time:: SystemTime :: now ( ) . duration_since ( std:: time:: UNIX_EPOCH ) . unwrap ( ) . as_secs ( ) ;
507537 let hmac = compute_hmac ( & api_key, timestamp, body) ;
508538
509- let result = validate_hmac_auth ( timestamp, & hmac, body, & api_key) ;
539+ let result = validate_hmac_auth ( & key_id , timestamp, & hmac, body, & api_key) ;
510540 assert ! ( result. is_ok( ) ) ;
511541 }
512542
513543 #[ test]
514544 fn test_validate_hmac_auth_wrong_key ( ) {
515545 let api_key = "test_api_key" . to_string ( ) ;
546+ let key_id = compute_key_id ( & api_key) ;
516547 let body = b"test request body" ;
517548 let timestamp =
518549 std:: time:: SystemTime :: now ( ) . duration_since ( std:: time:: UNIX_EPOCH ) . unwrap ( ) . as_secs ( ) ;
519550 // Compute HMAC with wrong key
520551 let hmac = compute_hmac ( "wrong_key" , timestamp, body) ;
521552
522- let result = validate_hmac_auth ( timestamp, & hmac, body, & api_key) ;
553+ let result = validate_hmac_auth ( & key_id, timestamp, & hmac, body, & api_key) ;
554+ assert ! ( result. is_err( ) ) ;
555+ assert_eq ! ( result. unwrap_err( ) . error_code, AuthError ) ;
556+ }
557+
558+ #[ test]
559+ fn test_validate_hmac_auth_wrong_key_id ( ) {
560+ let api_key = "test_api_key" . to_string ( ) ;
561+ let wrong_key_id = "0000000000000000" ;
562+ let body = b"test request body" ;
563+ let timestamp =
564+ std:: time:: SystemTime :: now ( ) . duration_since ( std:: time:: UNIX_EPOCH ) . unwrap ( ) . as_secs ( ) ;
565+ let hmac = compute_hmac ( & api_key, timestamp, body) ;
566+
567+ let result = validate_hmac_auth ( wrong_key_id, timestamp, & hmac, body, & api_key) ;
523568 assert ! ( result. is_err( ) ) ;
524569 assert_eq ! ( result. unwrap_err( ) . error_code, AuthError ) ;
525570 }
526571
527572 #[ test]
528573 fn test_validate_hmac_auth_expired_timestamp ( ) {
529574 let api_key = "test_api_key" . to_string ( ) ;
575+ let key_id = compute_key_id ( & api_key) ;
530576 let body = b"test request body" ;
531577 // Use a timestamp from 10 minutes ago
532578 let timestamp =
533579 std:: time:: SystemTime :: now ( ) . duration_since ( std:: time:: UNIX_EPOCH ) . unwrap ( ) . as_secs ( )
534580 - 600 ;
535581 let hmac = compute_hmac ( & api_key, timestamp, body) ;
536582
537- let result = validate_hmac_auth ( timestamp, & hmac, body, & api_key) ;
583+ let result = validate_hmac_auth ( & key_id , timestamp, & hmac, body, & api_key) ;
538584 assert ! ( result. is_err( ) ) ;
539585 assert_eq ! ( result. unwrap_err( ) . error_code, AuthError ) ;
540586 }
541587
542588 #[ test]
543589 fn test_validate_hmac_auth_tampered_body ( ) {
544590 let api_key = "test_api_key" . to_string ( ) ;
591+ let key_id = compute_key_id ( & api_key) ;
545592 let original_body = b"test request body" ;
546593 let tampered_body = b"tampered body" ;
547594 let timestamp =
@@ -550,7 +597,7 @@ mod tests {
550597 let hmac = compute_hmac ( & api_key, timestamp, original_body) ;
551598
552599 // Try to validate with tampered body
553- let result = validate_hmac_auth ( timestamp, & hmac, tampered_body, & api_key) ;
600+ let result = validate_hmac_auth ( & key_id , timestamp, & hmac, tampered_body, & api_key) ;
554601 assert ! ( result. is_err( ) ) ;
555602 assert_eq ! ( result. unwrap_err( ) . error_code, AuthError ) ;
556603 }
0 commit comments