11// SPDX-License-Identifier: Apache-2.0
22// Copyright 2025 Keylime Authors
33
4- //! Challenge-Response Authentication Module
4+ //! Authentication types and utilities
55//!
6- //! This module implements the challenge-response authentication protocol
7- //! as described in Keylime Enhancement 103. It provides a standalone
8- //! authentication client that can be used independently or integrated
9- //! with existing HTTP clients.
6+ //! This module provides common types and utilities for authentication
7+ //! that are shared between different components of the Keylime system.
108
11- use anyhow:: { anyhow, Result } ;
12- use chrono:: { DateTime , Duration , Utc } ;
13- use keylime:: structures:: {
9+ use crate :: structures:: {
1410 ProofOfPossession , SessionIdResponse , SessionRequest ,
1511 SessionRequestAttributes , SessionRequestData , SessionResponse ,
1612 SupportedAuthMethod ,
1713} ;
14+ use anyhow:: { anyhow, Result } ;
15+ use chrono:: { DateTime , Duration , Utc } ;
1816use log:: { debug, info, warn} ;
19- use reqwest:: { Client , Method , StatusCode } ;
17+ use reqwest:: Client ;
2018use std:: sync:: Arc ;
2119use tokio:: sync:: Mutex ;
2220
23- /// Configuration for the authentication client
21+ /// Configuration for authentication
2422#[ derive( Debug , Clone ) ]
25- #[ allow( dead_code) ] // TODO: Remove when auth client is integrated
2623pub struct AuthConfig {
2724 /// Base URL of the verifier (e.g., "https://verifier.example.com")
2825 pub verifier_base_url : String ,
@@ -53,34 +50,37 @@ impl Default for AuthConfig {
5350
5451/// Session token with expiration information
5552#[ derive( Debug , Clone ) ]
56- #[ allow( dead_code) ] // TODO: Remove when auth client is integrated
57- struct SessionToken {
58- token : String ,
59- expires_at : DateTime < Utc > ,
60- session_id : u64 ,
53+ pub struct SessionToken {
54+ pub token : String ,
55+ pub expires_at : DateTime < Utc > ,
56+ pub session_id : u64 ,
6157}
6258
6359impl SessionToken {
64- #[ allow( dead_code) ] // TODO: Remove when auth client is integrated
65- fn is_valid ( & self , buffer_minutes : i64 ) -> bool {
60+ pub fn is_valid ( & self , buffer_minutes : i64 ) -> bool {
6661 let buffer = Duration :: minutes ( buffer_minutes) ;
6762 Utc :: now ( ) + buffer < self . expires_at
6863 }
6964}
7065
7166/// Mock TPM operations for testing
72- #[ allow( dead_code) ] // TODO: Remove when auth client is integrated
7367pub trait TpmOperations : Send + Sync {
74- fn generate_proof ( & self , challenge : & str ) -> Result < ProofOfPossession > ;
68+ fn generate_proof (
69+ & self ,
70+ challenge : & str ,
71+ ) -> Result < crate :: structures:: ProofOfPossession > ;
7572}
7673
7774/// Default mock TPM implementation
7875#[ derive( Debug , Clone ) ]
79- #[ allow( dead_code) ] // TODO: Remove when auth client is integrated
8076pub struct MockTpmOperations ;
8177
8278impl TpmOperations for MockTpmOperations {
83- fn generate_proof ( & self , challenge : & str ) -> Result < ProofOfPossession > {
79+ fn generate_proof (
80+ & self ,
81+ challenge : & str ,
82+ ) -> Result < crate :: structures:: ProofOfPossession > {
83+ use log:: debug;
8484 debug ! ( "Generating mock TPM proof for challenge: {challenge}" ) ;
8585
8686 // Create a deterministic but unique proof based on the challenge
@@ -89,23 +89,32 @@ impl TpmOperations for MockTpmOperations {
8989
9090 use base64:: { engine:: general_purpose, Engine as _} ;
9191
92- Ok ( ProofOfPossession {
92+ Ok ( crate :: structures :: ProofOfPossession {
9393 message : general_purpose:: STANDARD . encode ( message) ,
9494 signature : general_purpose:: STANDARD . encode ( signature) ,
9595 } )
9696 }
9797}
9898
9999/// Standalone authentication client implementing the challenge-response protocol
100- #[ allow( dead_code) ] // TODO: Remove when auth client is integrated
101100pub struct AuthenticationClient {
102101 config : AuthConfig ,
103102 http_client : Client ,
104103 session_token : Arc < Mutex < Option < SessionToken > > > ,
105104 tpm_ops : Box < dyn TpmOperations > ,
106105}
107106
108- #[ allow( dead_code) ] // TODO: Remove when auth client is integrated
107+ impl std:: fmt:: Debug for AuthenticationClient {
108+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
109+ f. debug_struct ( "AuthenticationClient" )
110+ . field ( "config" , & self . config )
111+ . field ( "http_client" , & "<Client>" )
112+ . field ( "session_token" , & "<Arc<Mutex<SessionToken>>>" )
113+ . field ( "tpm_ops" , & "<Box<dyn TpmOperations>>" )
114+ . finish ( )
115+ }
116+ }
117+
109118impl AuthenticationClient {
110119 /// Create a new authentication client with the given configuration
111120 pub fn new ( config : AuthConfig ) -> Result < Self > {
@@ -178,45 +187,9 @@ impl AuthenticationClient {
178187 } )
179188 }
180189
181- /// Get a valid authentication token, performing authentication if necessary
182- pub async fn get_auth_token ( & self ) -> Result < String > {
183- let token_guard = self . session_token . lock ( ) . await ;
184-
185- // Check if we have a valid token
186- if let Some ( ref token) = * token_guard {
187- if token. is_valid ( self . config . token_refresh_buffer_minutes ) {
188- debug ! ( "Using existing valid token" ) ;
189- return Ok ( token. token . clone ( ) ) ;
190- } else {
191- debug ! (
192- "Token expired or expiring soon, need to re-authenticate"
193- ) ;
194- }
195- } else {
196- debug ! ( "No token available, need to authenticate" ) ;
197- }
198-
199- drop ( token_guard) ; // Release lock before authentication
200-
201- // Perform authentication
202- self . authenticate ( ) . await
203- }
204-
205- /// Check if we currently have a valid token
206- pub async fn has_valid_token ( & self ) -> bool {
207- let token_guard = self . session_token . lock ( ) . await ;
208- if let Some ( ref token) = * token_guard {
209- token. is_valid ( self . config . token_refresh_buffer_minutes )
210- } else {
211- false
212- }
213- }
214-
215- /// Clear the current token (e.g., after receiving 401)
216- pub async fn clear_token ( & self ) {
217- let mut token_guard = self . session_token . lock ( ) . await ;
218- * token_guard = None ;
219- debug ! ( "Authentication token cleared" ) ;
190+ /// Get the authentication configuration
191+ pub fn config ( & self ) -> & AuthConfig {
192+ & self . config
220193 }
221194
222195 /// Get a valid authentication token with metadata (token, expiration, session_id)
@@ -494,41 +467,12 @@ impl AuthenticationClient {
494467
495468 Ok ( token. clone ( ) )
496469 }
497-
498- /// Make an authenticated HTTP request (convenience method for testing)
499- pub async fn make_authenticated_request (
500- & self ,
501- method : Method ,
502- url : & str ,
503- body : Option < String > ,
504- ) -> Result < reqwest:: Response > {
505- let token = self . get_auth_token ( ) . await ?;
506-
507- let mut request = self . http_client . request ( method, url) ;
508- request = request. header ( "Authorization" , format ! ( "Bearer {token}" ) ) ;
509-
510- if let Some ( body) = body {
511- request = request
512- . header ( "Content-Type" , "application/vnd.api+json" )
513- . body ( body) ;
514- }
515-
516- let response = request. send ( ) . await ?;
517-
518- // Handle 401 responses by clearing token
519- if response. status ( ) == StatusCode :: UNAUTHORIZED {
520- warn ! ( "Received 401, clearing token" ) ;
521- self . clear_token ( ) . await ;
522- return Err ( anyhow ! ( "Authentication token was rejected (401)" ) ) ;
523- }
524-
525- Ok ( response)
526- }
527470}
528471
529472#[ cfg( test) ]
530473mod tests {
531474 use super :: * ;
475+ use serde_json;
532476 use wiremock:: matchers:: { header, method, path} ;
533477 use wiremock:: { Mock , MockServer , ResponseTemplate } ;
534478
@@ -544,7 +488,7 @@ mod tests {
544488 max_auth_retries : 2 ,
545489 } ;
546490
547- AuthenticationClient :: new ( config) . unwrap ( )
491+ AuthenticationClient :: new ( config) . unwrap ( ) //#[allow_ci]
548492 }
549493
550494 #[ tokio:: test]
@@ -615,16 +559,16 @@ mod tests {
615559
616560 let client = create_test_client ( & mock_server. uri ( ) ) . await ;
617561
618- // Test authentication
619- let token = client. get_auth_token ( ) . await . unwrap ( ) ;
562+ // Test authentication - get token with metadata since that's our main method
563+ let ( token, _expires_at, session_id) =
564+ client. get_auth_token_with_metadata ( ) . await . unwrap ( ) ; //#[allow_ci]
620565 assert_eq ! ( token, "test-token-456" ) ;
566+ assert_eq ! ( session_id, 1 ) ;
621567
622- // Test that token is cached
623- assert ! ( client. has_valid_token( ) . await ) ;
624-
625- // Test that subsequent calls use cached token
626- let token2 = client. get_auth_token ( ) . await . unwrap ( ) ;
627- assert_eq ! ( token2, "test-token-456" ) ;
568+ // Verify token is valid
569+ let token_guard = client. session_token . lock ( ) . await ;
570+ let session_token = token_guard. as_ref ( ) . unwrap ( ) ; //#[allow_ci]
571+ assert ! ( session_token. is_valid( 5 ) ) ;
628572 }
629573
630574 #[ tokio:: test]
@@ -691,10 +635,10 @@ mod tests {
691635
692636 let client = create_test_client ( & mock_server. uri ( ) ) . await ;
693637
694- let result = client. get_auth_token ( ) . await ;
638+ let result = client. get_auth_token_with_metadata ( ) . await ;
695639 assert ! ( result. is_err( ) ) ;
696640 assert ! ( result
697- . unwrap_err( )
641+ . unwrap_err( ) //#[allow_ci]
698642 . to_string( )
699643 . contains( "Authentication failed" ) ) ;
700644 }
@@ -726,7 +670,7 @@ mod tests {
726670 }
727671 } ) ,
728672 ) )
729- . expect ( 1 ..) // May be called multiple times
673+ . expect ( 1 ..) //#[allow_ci] // May be called multiple times
730674 . mount ( & mock_server)
731675 . await ;
732676
@@ -762,7 +706,7 @@ mod tests {
762706 }
763707 } ) ,
764708 ) )
765- . expect ( 1 ..) // May be called multiple times
709+ . expect ( 1 ..) //#[allow_ci] // May be called multiple times
766710 . mount ( & mock_server)
767711 . await ;
768712
@@ -775,37 +719,18 @@ mod tests {
775719 max_auth_retries : 2 ,
776720 } ;
777721
778- let client = AuthenticationClient :: new ( config) . unwrap ( ) ;
722+ let client = AuthenticationClient :: new ( config) . unwrap ( ) ; //#[allow_ci]
779723
780724 // Since token expires in 1 minute but we have 5 minute buffer,
781725 // it should be considered invalid and trigger re-authentication
782- let token = client. get_auth_token ( ) . await . unwrap ( ) ;
726+ let ( token, _, _) =
727+ client. get_auth_token_with_metadata ( ) . await . unwrap ( ) ; //#[allow_ci]
783728 assert_eq ! ( token, "short-lived-token" ) ;
784729
785730 // Check that token is considered invalid due to buffer
786- assert ! ( !client. has_valid_token( ) . await ) ;
787- }
788-
789- #[ tokio:: test]
790- async fn test_clear_token ( ) {
791- let mock_server = MockServer :: start ( ) . await ;
792- let client = create_test_client ( & mock_server. uri ( ) ) . await ;
793-
794- // Manually insert a token
795- {
796- let mut token_guard = client. session_token . lock ( ) . await ;
797- * token_guard = Some ( SessionToken {
798- token : "test-token" . to_string ( ) ,
799- expires_at : Utc :: now ( ) + Duration :: hours ( 1 ) ,
800- session_id : 1 ,
801- } ) ;
802- }
803-
804- assert ! ( client. has_valid_token( ) . await ) ;
805-
806- client. clear_token ( ) . await ;
807-
808- assert ! ( !client. has_valid_token( ) . await ) ;
731+ let token_guard = client. session_token . lock ( ) . await ;
732+ let session_token = token_guard. as_ref ( ) . unwrap ( ) ; //#[allow_ci]
733+ assert ! ( !session_token. is_valid( 5 ) ) ;
809734 }
810735
811736 #[ tokio:: test]
@@ -819,7 +744,7 @@ mod tests {
819744 max_auth_retries : 2 ,
820745 } ;
821746
822- let raw_client = AuthenticationClient :: new_raw ( config) . unwrap ( ) ;
747+ let raw_client = AuthenticationClient :: new_raw ( config) . unwrap ( ) ; //#[allow_ci]
823748
824749 // Verify the client was created successfully
825750 assert_eq ! ( raw_client. config. agent_id, "test-agent-raw" ) ;
@@ -843,7 +768,7 @@ mod tests {
843768 config,
844769 custom_tpm_ops,
845770 )
846- . unwrap ( ) ;
771+ . unwrap ( ) ; //#[allow_ci]
847772
848773 // Verify the client was created successfully
849774 assert_eq ! ( raw_client. config. agent_id, "test-agent-raw-tpm" ) ;
0 commit comments