1+ // Helpers return Result<T, Response> so the orchestrator can fan out early-return
2+ // HTTP responses; the Err size is dominated by axum::Response (~128 bytes) which
3+ // is fine for non-hot-path handler control flow.
4+ #![ allow( clippy:: result_large_err) ]
5+
16use crate :: {
27 bindings:: soap:: { send_soap_request, wrap_soap} ,
8+ config:: AuthConfig ,
39 saml:: {
4- messages:: create_artifact_resolve,
10+ idp_metadata:: IdpMetadata ,
11+ messages:: { CreatedMessage , create_artifact_resolve} ,
512 validation:: {
6- MINIMUM_LOA , ValidateAssertionOpts , validate_artifact_response, validate_assertion ,
7- validate_response,
13+ Claims , MINIMUM_LOA , ValidateAssertionOpts , validate_artifact_response,
14+ validate_assertion , validate_response,
815 } ,
916 } ,
1017 state:: { AuthServiceState , AuthState } ,
@@ -64,77 +71,141 @@ where
6471 ) ;
6572
6673 // 1. Create signed ArtifactResolve (eID §7.5)
74+ let resolve = match build_artifact_resolve ( artifact, cfg, rd, & dv_keys. signing [ 0 ] ) {
75+ Ok ( r) => r,
76+ Err ( resp) => return resp,
77+ } ;
78+
79+ // 2. Wrap in SOAP and send to ARS over mTLS (eID §9.4)
80+ let soap_response = match send_artifact_resolve ( & resolve. xml , rd, cfg) . await {
81+ Ok ( r) => r,
82+ Err ( resp) => return resp,
83+ } ;
84+
85+ // 3. Validate ArtifactResponse (eID §7.6.1) using RD signing certs from metadata
86+ let response_xml = match resolve_artifact_response ( & soap_response, rd, & resolve. id ) {
87+ Ok ( xml) => xml,
88+ Err ( resp) => return resp,
89+ } ;
90+
91+ // 4. Validate Response (eID §7.6.2) — handle cancellation / IdP errors
92+ let assertion_xml = match resolve_response ( & response_xml) {
93+ Ok ( xml) => xml,
94+ Err ( resp) => return resp,
95+ } ;
96+
97+ // 5. Validate Assertion (eID §7.6.3, §7.6.3.5).
98+ let claims = match resolve_assertion ( & assertion_xml, & auth_state, cfg, rd) {
99+ Ok ( c) => c,
100+ Err ( resp) => return resp,
101+ } ;
102+
103+ // SECURITY: never log decrypted SubjectID values — they are PII (BSN /
104+ // pseudonym per eID §7.6.3.4). Log only non-PII metadata for tracing.
105+ info ! (
106+ "[DV] Authentication successful. acting_subject_present={}, \
107+ legal_subject_present={}, loa={:?}, authenticating_authority={:?}, \
108+ service_uuid_present={}",
109+ claims. acting_subject_id. is_some( ) ,
110+ claims. legal_subject_id. is_some( ) ,
111+ claims. authn_context_class_ref. as_deref( ) ,
112+ claims. authenticating_authority. as_deref( ) ,
113+ claims. service_uuid. is_some( ) ,
114+ ) ;
115+
116+ // eID §9.7: consume the matched pending request ID to prevent replay.
117+ if let Some ( ref irt) = claims. in_response_to {
118+ debug ! ( "[DV][ACS] Consuming pending AuthnRequest id={irt}" ) ;
119+ auth_state. consume_pending_request ( irt. as_str ( ) ) ;
120+ }
121+
122+ // 6. Hand off to the embedding application to create its session.
123+ debug ! ( "[DV][ACS] Step 6: handing off to AuthState::on_authenticated" ) ;
124+ state. on_authenticated ( claims, jar, & headers) . await
125+ }
126+
127+ fn build_artifact_resolve (
128+ artifact : & str ,
129+ cfg : & AuthConfig ,
130+ rd : & IdpMetadata ,
131+ signing_key : & crate :: keys:: KeyPair ,
132+ ) -> Result < CreatedMessage , Response > {
67133 debug ! ( "[DV][ACS] Step 1: building signed ArtifactResolve" ) ;
68- let resolve = match create_artifact_resolve (
69- artifact,
70- & cfg. dv . entity_id ,
71- & rd. ars_url ,
72- & dv_keys. signing [ 0 ] ,
73- ) {
134+ match create_artifact_resolve ( artifact, & cfg. dv . entity_id , & rd. ars_url , signing_key) {
74135 Ok ( m) => {
75136 debug ! (
76137 "[DV][ACS] ArtifactResolve built: id={}, xml_len={}" ,
77138 m. id,
78139 m. xml. len( )
79140 ) ;
80- m
141+ Ok ( m )
81142 }
82143 Err ( e) => {
83144 error ! ( "[DV] Failed to create ArtifactResolve: {e}" ) ;
84- return (
145+ Err ( (
85146 StatusCode :: INTERNAL_SERVER_ERROR ,
86147 "ArtifactResolve build failed" ,
87148 )
88- . into_response ( ) ;
149+ . into_response ( ) )
89150 }
90- } ;
151+ }
152+ }
91153
92- // 2. Wrap in SOAP and send to ARS over mTLS (eID §9.4)
154+ async fn send_artifact_resolve (
155+ resolve_xml : & str ,
156+ rd : & IdpMetadata ,
157+ cfg : & AuthConfig ,
158+ ) -> Result < String , Response > {
93159 debug ! ( "[DV][ACS] Step 2: sending ArtifactResolve over mTLS SOAP back-channel" ) ;
94- let soap_xml = wrap_soap ( & resolve . xml ) ;
95- let soap_response = match send_soap_request ( & rd. ars_url , & soap_xml, & cfg. tls ) . await {
160+ let soap_xml = wrap_soap ( resolve_xml ) ;
161+ match send_soap_request ( & rd. ars_url , & soap_xml, & cfg. tls ) . await {
96162 Ok ( r) => {
97163 debug ! (
98164 "[DV][ACS] SOAP back-channel returned response (len={})" ,
99165 r. len( )
100166 ) ;
101- r
167+ Ok ( r )
102168 }
103169 Err ( e) => {
104170 error ! ( "[DV] SOAP back-channel failed: {e}" ) ;
105- return ( StatusCode :: BAD_GATEWAY , "ArtifactResolve failed" ) . into_response ( ) ;
171+ Err ( ( StatusCode :: BAD_GATEWAY , "ArtifactResolve failed" ) . into_response ( ) )
106172 }
107- } ;
173+ }
174+ }
108175
109- // 3. Validate ArtifactResponse (eID §7.6.1) using RD signing certs from metadata
176+ fn resolve_artifact_response (
177+ soap_response : & str ,
178+ rd : & IdpMetadata ,
179+ expected_id : & str ,
180+ ) -> Result < String , Response > {
110181 debug ! (
111182 "[DV][ACS] Step 3: validating ArtifactResponse against {} RD signing key(s), \
112183 expected InResponseTo={}",
113184 rd. signing_keys. len( ) ,
114- resolve . id
185+ expected_id
115186 ) ;
116- let rd_signing = & rd. signing_keys ;
117- let art_result = validate_artifact_response ( & soap_response, rd_signing, & resolve. id ) ;
187+ let art_result = validate_artifact_response ( soap_response, & rd. signing_keys , expected_id) ;
118188 if !art_result. valid {
119189 error ! (
120190 "[DV] ArtifactResponse validation failed: {:?}" ,
121191 art_result. errors
122192 ) ;
123- return (
193+ return Err ( (
124194 StatusCode :: BAD_REQUEST ,
125195 format ! (
126196 "ArtifactResponse validation failed: {}" ,
127197 art_result. errors. join( "; " )
128198 ) ,
129199 )
130- . into_response ( ) ;
200+ . into_response ( ) ) ;
131201 }
132202 debug ! ( "[DV][ACS] Step 3: ArtifactResponse OK" ) ;
203+ Ok ( art_result. response_xml . unwrap ( ) )
204+ }
133205
134- // 4. Validate Response (eID §7.6.2) — handle cancellation / IdP errors
206+ fn resolve_response ( response_xml : & str ) -> Result < String , Response > {
135207 debug ! ( "[DV][ACS] Step 4: validating inner Response status" ) ;
136- let response_xml = art_result. response_xml . unwrap ( ) ;
137- let resp_result = validate_response ( & response_xml) ;
208+ let resp_result = validate_response ( response_xml) ;
138209
139210 if !resp_result. valid {
140211 let errors_str = resp_result. errors . join ( "; " ) ;
@@ -144,21 +215,27 @@ where
144215 // TVS "Checklist Testen" v2.1 T3: user cancelled — take them home.
145216 if is_authn_failed || is_cancelled {
146217 warn ! ( "[DV] Authentication cancelled by user" ) ;
147- return Redirect :: to ( "/" ) . into_response ( ) ;
218+ return Err ( Redirect :: to ( "/" ) . into_response ( ) ) ;
148219 }
149220
150221 // TVS "Checklist Testen" v2.1 L10: DigiD error — terminate the flow.
151222 warn ! ( "[DV] Authentication failed: {errors_str}" ) ;
152- return ( StatusCode :: BAD_REQUEST , "Authentication failed" ) . into_response ( ) ;
223+ return Err ( ( StatusCode :: BAD_REQUEST , "Authentication failed" ) . into_response ( ) ) ;
153224 }
154225 debug ! ( "[DV][ACS] Step 4: Response status Success" ) ;
155226
156- let Some ( assertion_xml ) = resp_result. assertion_xml else {
227+ resp_result. assertion_xml . ok_or_else ( || {
157228 warn ! ( "[DV][ACS] No Assertion element extracted from successful Response" ) ;
158- return ( StatusCode :: BAD_REQUEST , "No assertion in response" ) . into_response ( ) ;
159- } ;
229+ ( StatusCode :: BAD_REQUEST , "No assertion in response" ) . into_response ( )
230+ } )
231+ }
160232
161- // 5. Validate Assertion (eID §7.6.3, §7.6.3.5).
233+ fn resolve_assertion (
234+ assertion_xml : & str ,
235+ auth_state : & AuthServiceState ,
236+ cfg : & AuthConfig ,
237+ rd : & IdpMetadata ,
238+ ) -> Result < Claims , Response > {
162239 // Rule 4: snapshot of pending request IDs for InResponseTo verification.
163240 let pending_ids = auth_state. pending_request_ids ( ) ;
164241 let pending_refs: Vec < & str > = pending_ids. iter ( ) . map ( |s| s. as_str ( ) ) . collect ( ) ;
@@ -169,6 +246,7 @@ where
169246 pending_refs. len( )
170247 ) ;
171248
249+ let dv_keys = auth_state. dv_keys ( ) ;
172250 let priv_keys: Vec < ( & str , & str , & str ) > = dv_keys
173251 . encryption
174252 . iter ( )
@@ -182,11 +260,11 @@ where
182260 . collect ( ) ;
183261
184262 let assert_result = validate_assertion ( ValidateAssertionOpts {
185- assertion_xml : & assertion_xml ,
263+ assertion_xml,
186264 dv_entity_id : & cfg. dv . entity_id ,
187265 expected_recipient : & cfg. dv . acs_url ,
188266 pending_request_ids : & pending_refs,
189- trusted_keys : rd_signing ,
267+ trusted_keys : & rd . signing_keys ,
190268 private_keys : & priv_keys,
191269 minimum_loa : Some ( MINIMUM_LOA ) ,
192270 } ) ;
@@ -196,37 +274,15 @@ where
196274 "[DV] Assertion validation failed: {:?}" ,
197275 assert_result. errors
198276 ) ;
199- return (
277+ return Err ( (
200278 StatusCode :: BAD_REQUEST ,
201279 format ! (
202280 "Assertion validation failed: {}" ,
203281 assert_result. errors. join( "; " )
204282 ) ,
205283 )
206- . into_response ( ) ;
284+ . into_response ( ) ) ;
207285 }
208286
209- let claims = assert_result. claims . unwrap ( ) ;
210- // SECURITY: never log decrypted SubjectID values — they are PII (BSN /
211- // pseudonym per eID §7.6.3.4). Log only non-PII metadata for tracing.
212- info ! (
213- "[DV] Authentication successful. acting_subject_present={}, \
214- legal_subject_present={}, loa={:?}, authenticating_authority={:?}, \
215- service_uuid_present={}",
216- claims. acting_subject_id. is_some( ) ,
217- claims. legal_subject_id. is_some( ) ,
218- claims. authn_context_class_ref. as_deref( ) ,
219- claims. authenticating_authority. as_deref( ) ,
220- claims. service_uuid. is_some( ) ,
221- ) ;
222-
223- // eID §9.7: consume the matched pending request ID to prevent replay.
224- if let Some ( ref irt) = claims. in_response_to {
225- debug ! ( "[DV][ACS] Consuming pending AuthnRequest id={irt}" ) ;
226- auth_state. consume_pending_request ( irt. as_str ( ) ) ;
227- }
228-
229- // 6. Hand off to the embedding application to create its session.
230- debug ! ( "[DV][ACS] Step 6: handing off to AuthState::on_authenticated" ) ;
231- state. on_authenticated ( claims, jar, & headers) . await
287+ Ok ( assert_result. claims . unwrap ( ) )
232288}
0 commit comments