@@ -84,27 +84,48 @@ 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 ( ) . fold ( String :: with_capacity ( 16 ) , |mut acc, b| {
96+ use std:: fmt:: Write ;
97+ let _ = write ! ( acc, "{:02x}" , b) ;
98+ acc
99+ } )
100+ }
101+
91102/// Extracts authentication parameters from request headers.
92- /// Returns (timestamp, hmac_hex) if valid format, or error.
103+ /// Returns (key_id, timestamp, hmac_hex) if valid format, or error.
93104fn extract_auth_params < B > ( req : & Request < B > ) -> Result < AuthParams , LdkServerError > {
94105 let auth_header = req
95106 . headers ( )
96107 . get ( "X-Auth" )
97108 . and_then ( |v| v. to_str ( ) . ok ( ) )
98109 . ok_or_else ( || LdkServerError :: new ( AuthError , "Missing X-Auth header" ) ) ?;
99110
100- // Format: "HMAC <timestamp>:<hmac_hex>"
111+ // Format: "HMAC <key_id>:< timestamp>:<hmac_hex>"
101112 let auth_data = auth_header
102113 . strip_prefix ( "HMAC " )
103114 . ok_or_else ( || LdkServerError :: new ( AuthError , "Invalid X-Auth header format" ) ) ?;
104115
105- let ( timestamp_str, hmac_hex) = auth_data
106- . split_once ( ':' )
107- . ok_or_else ( || LdkServerError :: new ( AuthError , "Invalid X-Auth header format" ) ) ?;
116+ let parts: Vec < & str > = auth_data. splitn ( 3 , ':' ) . collect ( ) ;
117+ if parts. len ( ) != 3 {
118+ return Err ( LdkServerError :: new ( AuthError , "Invalid X-Auth header format" ) ) ;
119+ }
120+
121+ let key_id = parts[ 0 ] ;
122+ let timestamp_str = parts[ 1 ] ;
123+ let hmac_hex = parts[ 2 ] ;
124+
125+ // Validate key_id is 16 hex chars
126+ if key_id. len ( ) != 16 || !key_id. chars ( ) . all ( |c| c. is_ascii_hexdigit ( ) ) {
127+ return Err ( LdkServerError :: new ( AuthError , "Invalid key_id in X-Auth header" ) ) ;
128+ }
108129
109130 let timestamp = timestamp_str
110131 . parse :: < u64 > ( )
@@ -115,13 +136,19 @@ fn extract_auth_params<B>(req: &Request<B>) -> Result<AuthParams, LdkServerError
115136 return Err ( LdkServerError :: new ( AuthError , "Invalid HMAC in X-Auth header" ) ) ;
116137 }
117138
118- Ok ( AuthParams { timestamp, hmac_hex : hmac_hex. to_string ( ) } )
139+ Ok ( AuthParams { key_id : key_id . to_string ( ) , timestamp, hmac_hex : hmac_hex. to_string ( ) } )
119140}
120141
121142/// Validates the HMAC authentication after the request body has been read.
122143fn validate_hmac_auth (
123- timestamp : u64 , provided_hmac_hex : & str , body : & [ u8 ] , api_key : & str ,
144+ key_id : & str , timestamp : u64 , provided_hmac_hex : & str , body : & [ u8 ] , api_key : & str ,
124145) -> Result < ( ) , LdkServerError > {
146+ // Verify the key_id matches the api_key
147+ let expected_key_id = compute_key_id ( api_key) ;
148+ if key_id != expected_key_id {
149+ return Err ( LdkServerError :: new ( AuthError , "Invalid credentials" ) ) ;
150+ }
151+
125152 // Validate timestamp is within acceptable window
126153 let now = std:: time:: SystemTime :: now ( )
127154 . duration_since ( std:: time:: UNIX_EPOCH )
@@ -406,9 +433,13 @@ async fn handle_request<
406433 } ;
407434
408435 // 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- {
436+ if let Err ( e) = validate_hmac_auth (
437+ & auth_params. key_id ,
438+ auth_params. timestamp ,
439+ & auth_params. hmac_hex ,
440+ & bytes,
441+ & api_key,
442+ ) {
412443 let ( error_response, status_code) = to_error_response ( e) ;
413444 return Ok ( Response :: builder ( )
414445 . status ( status_code)
@@ -468,13 +499,15 @@ mod tests {
468499 let timestamp =
469500 std:: time:: SystemTime :: now ( ) . duration_since ( std:: time:: UNIX_EPOCH ) . unwrap ( ) . as_secs ( ) ;
470501 let hmac = "8f5a33c2c68fb253899a588308fd13dcaf162d2788966a1fb6cc3aa2e0c51a93" ;
471- let auth_header = format ! ( "HMAC {timestamp}:{hmac}" ) ;
502+ let key_id = "abcdef0123456789" ;
503+ let auth_header = format ! ( "HMAC {key_id}:{timestamp}:{hmac}" ) ;
472504
473505 let req = create_test_request ( Some ( auth_header) ) ;
474506
475507 let result = extract_auth_params ( & req) ;
476508 assert ! ( result. is_ok( ) ) ;
477- let AuthParams { timestamp : ts, hmac_hex } = result. unwrap ( ) ;
509+ let AuthParams { key_id : kid, timestamp : ts, hmac_hex } = result. unwrap ( ) ;
510+ assert_eq ! ( kid, key_id) ;
478511 assert_eq ! ( ts, timestamp) ;
479512 assert_eq ! ( hmac_hex, hmac) ;
480513 }
@@ -501,47 +534,65 @@ mod tests {
501534 #[ test]
502535 fn test_validate_hmac_auth_success ( ) {
503536 let api_key = "test_api_key" . to_string ( ) ;
537+ let key_id = compute_key_id ( & api_key) ;
504538 let body = b"test request body" ;
505539 let timestamp =
506540 std:: time:: SystemTime :: now ( ) . duration_since ( std:: time:: UNIX_EPOCH ) . unwrap ( ) . as_secs ( ) ;
507541 let hmac = compute_hmac ( & api_key, timestamp, body) ;
508542
509- let result = validate_hmac_auth ( timestamp, & hmac, body, & api_key) ;
543+ let result = validate_hmac_auth ( & key_id , timestamp, & hmac, body, & api_key) ;
510544 assert ! ( result. is_ok( ) ) ;
511545 }
512546
513547 #[ test]
514548 fn test_validate_hmac_auth_wrong_key ( ) {
515549 let api_key = "test_api_key" . to_string ( ) ;
550+ let key_id = compute_key_id ( & api_key) ;
516551 let body = b"test request body" ;
517552 let timestamp =
518553 std:: time:: SystemTime :: now ( ) . duration_since ( std:: time:: UNIX_EPOCH ) . unwrap ( ) . as_secs ( ) ;
519554 // Compute HMAC with wrong key
520555 let hmac = compute_hmac ( "wrong_key" , timestamp, body) ;
521556
522- let result = validate_hmac_auth ( timestamp, & hmac, body, & api_key) ;
557+ let result = validate_hmac_auth ( & key_id, timestamp, & hmac, body, & api_key) ;
558+ assert ! ( result. is_err( ) ) ;
559+ assert_eq ! ( result. unwrap_err( ) . error_code, AuthError ) ;
560+ }
561+
562+ #[ test]
563+ fn test_validate_hmac_auth_wrong_key_id ( ) {
564+ let api_key = "test_api_key" . to_string ( ) ;
565+ let wrong_key_id = "0000000000000000" ;
566+ let body = b"test request body" ;
567+ let timestamp =
568+ std:: time:: SystemTime :: now ( ) . duration_since ( std:: time:: UNIX_EPOCH ) . unwrap ( ) . as_secs ( ) ;
569+ let hmac = compute_hmac ( & api_key, timestamp, body) ;
570+
571+ let result = validate_hmac_auth ( wrong_key_id, timestamp, & hmac, body, & api_key) ;
523572 assert ! ( result. is_err( ) ) ;
524573 assert_eq ! ( result. unwrap_err( ) . error_code, AuthError ) ;
525574 }
526575
527576 #[ test]
528577 fn test_validate_hmac_auth_expired_timestamp ( ) {
529578 let api_key = "test_api_key" . to_string ( ) ;
579+ let key_id = compute_key_id ( & api_key) ;
530580 let body = b"test request body" ;
531581 // Use a timestamp from 10 minutes ago
532582 let timestamp =
533583 std:: time:: SystemTime :: now ( ) . duration_since ( std:: time:: UNIX_EPOCH ) . unwrap ( ) . as_secs ( )
534584 - 600 ;
535585 let hmac = compute_hmac ( & api_key, timestamp, body) ;
536586
537- let result = validate_hmac_auth ( timestamp, & hmac, body, & api_key) ;
587+ let result = validate_hmac_auth ( & key_id , timestamp, & hmac, body, & api_key) ;
538588 assert ! ( result. is_err( ) ) ;
539589 assert_eq ! ( result. unwrap_err( ) . error_code, AuthError ) ;
540590 }
541591
542592 #[ test]
543593 fn test_validate_hmac_auth_tampered_body ( ) {
544594 let api_key = "test_api_key" . to_string ( ) ;
595+ let key_id = compute_key_id ( & api_key) ;
545596 let original_body = b"test request body" ;
546597 let tampered_body = b"tampered body" ;
547598 let timestamp =
@@ -550,7 +601,7 @@ mod tests {
550601 let hmac = compute_hmac ( & api_key, timestamp, original_body) ;
551602
552603 // Try to validate with tampered body
553- let result = validate_hmac_auth ( timestamp, & hmac, tampered_body, & api_key) ;
604+ let result = validate_hmac_auth ( & key_id , timestamp, & hmac, tampered_body, & api_key) ;
554605 assert ! ( result. is_err( ) ) ;
555606 assert_eq ! ( result. unwrap_err( ) . error_code, AuthError ) ;
556607 }
0 commit comments