@@ -175,12 +175,23 @@ else if ("interactive".equals(modeFromResponse))
175175 response .preferredClientMode = modeFromResponse ;
176176 }
177177 response .message = getString (detail , MESSAGE );
178+ response .username = getString (detail , USERNAME );
178179 response .image = getString (detail , IMAGE );
179180 response .serial = getString (detail , SERIAL );
180181 response .transactionID = getString (detail , TRANSACTION_ID );
181182 response .type = getString (detail , TYPE );
182183 response .otpLength = getInt (detail , OTPLEN );
183-
184+ JsonObject passkeyChallenge = detail .getAsJsonObject (PASSKEY );
185+ if (passkeyChallenge != null && !passkeyChallenge .isJsonNull ())
186+ {
187+ response .passkeyChallenge = passkeyChallenge .toString ();
188+ // The passkey challenge can contain a transaction id, use that if none was set prior
189+ // This will happen if the passkey challenge was requested via /validate/initialize
190+ if (response .transactionID == null || response .transactionID .isEmpty ())
191+ {
192+ response .transactionID = getString (passkeyChallenge , TRANSACTION_ID );
193+ }
194+ }
184195 String r = getString (detail , CHALLENGE_STATUS );
185196 for (ChallengeStatus cs : ChallengeStatus .values ())
186197 {
@@ -194,12 +205,12 @@ else if ("interactive".equals(modeFromResponse))
194205 if (arrMessages != null )
195206 {
196207 arrMessages .forEach (val ->
197- {
208+ {
198209 if (val != null )
199210 {
200211 response .messages .add (val .getAsString ());
201212 }
202- });
213+ });
203214 }
204215
205216 JsonArray arrChallenges = detail .getAsJsonArray (MULTI_CHALLENGE );
@@ -215,6 +226,17 @@ else if ("interactive".equals(modeFromResponse))
215226 String transactionID = getString (challenge , TRANSACTION_ID );
216227 String type = getString (challenge , TYPE );
217228
229+ if (challenge .has (PASSKEY_REGISTRATION ))
230+ {
231+ response .passkeyRegistration = challenge .get (PASSKEY_REGISTRATION ).toString ();
232+ // TODO for passkey registration with enroll_via_multichallenge, the txid is probably in the wrong place
233+ // as of 3.11.0
234+ if (response .transactionID == null || response .transactionID .isEmpty ())
235+ {
236+ response .transactionID = transactionID ;
237+ }
238+ }
239+
218240 if (TOKEN_TYPE_WEBAUTHN .equals (type ))
219241 {
220242 String webauthnSignRequest = getItemFromAttributes (challenge );
@@ -359,24 +381,24 @@ private TokenInfo parseSingleTokenInfo(String json)
359381 if (joInfo != null )
360382 {
361383 joInfo .entrySet ().forEach (entry ->
362- {
384+ {
363385 if (entry .getKey () != null && entry .getValue () != null )
364386 {
365387 info .info .put (entry .getKey (), entry .getValue ().getAsString ());
366388 }
367- });
389+ });
368390 }
369391
370392 JsonArray arrRealms = obj .getAsJsonArray (REALMS );
371393 if (arrRealms != null )
372394 {
373395 arrRealms .forEach (val ->
374- {
396+ {
375397 if (val != null )
376398 {
377399 info .realms .add (val .getAsString ());
378400 }
379- });
401+ });
380402 }
381403 return info ;
382404 }
@@ -472,7 +494,7 @@ Map<String, String> parseWebAuthnSignResponse(String json)
472494 }
473495 catch (JsonSyntaxException e )
474496 {
475- privacyIDEA .error ("WebAuthn sign response has the wrong format: " + e .getLocalizedMessage ());
497+ privacyIDEA .error ("FIDO2 sign response has the wrong format: " + e .getLocalizedMessage ());
476498 return null ;
477499 }
478500
@@ -495,6 +517,40 @@ Map<String, String> parseWebAuthnSignResponse(String json)
495517 return params ;
496518 }
497519
520+ Map <String , String > parseFIDO2AuthenticationResponse (String json )
521+ {
522+ Map <String , String > params = new LinkedHashMap <>();
523+ JsonObject obj ;
524+ try
525+ {
526+ obj = JsonParser .parseString (json ).getAsJsonObject ();
527+ }
528+ catch (JsonSyntaxException e )
529+ {
530+ privacyIDEA .error ("FIDO2 sign response has the wrong format: " + e .getLocalizedMessage ());
531+ return null ;
532+ }
533+
534+ params .put (CREDENTIAL_ID , getString (obj , CREDENTIAL_ID ));
535+ params .put (CLIENTDATAJSON , getString (obj , CLIENTDATAJSON ));
536+ params .put (SIGNATURE , getString (obj , SIGNATURE ));
537+ params .put (AUTHENTICATOR_DATA , getString (obj , AUTHENTICATOR_DATA ));
538+
539+ // The userhandle and assertionclientextension fields are optional
540+ String userhandle = getString (obj , USERHANDLE );
541+ if (!userhandle .isEmpty ())
542+ {
543+ params .put (USERHANDLE , userhandle );
544+ }
545+ String extensions = getString (obj , ASSERTIONCLIENTEXTENSIONS );
546+ if (!extensions .isEmpty ())
547+ {
548+ params .put (ASSERTIONCLIENTEXTENSIONS , extensions );
549+ }
550+ return params ;
551+ }
552+
553+
498554 private boolean getBoolean (JsonObject obj , String name )
499555 {
500556 JsonPrimitive primitive = getPrimitiveOrNull (obj , name );
@@ -528,4 +584,26 @@ private JsonPrimitive getPrimitiveOrNull(JsonObject obj, String name)
528584 }
529585 return primitive ;
530586 }
587+
588+ public Map <String , String > parseFIDO2RegistrationResponse (String registrationResponse )
589+ {
590+ Map <String , String > params = new LinkedHashMap <>();
591+ JsonObject obj ;
592+ try
593+ {
594+ obj = JsonParser .parseString (registrationResponse ).getAsJsonObject ();
595+ }
596+ catch (JsonSyntaxException e )
597+ {
598+ privacyIDEA .error ("Passkey registration response is not JSON: " + e .getLocalizedMessage ());
599+ return null ;
600+ }
601+
602+ params .put (CREDENTIAL_ID , getString (obj , CREDENTIAL_ID ));
603+ params .put (CLIENTDATAJSON , getString (obj , CLIENTDATAJSON ));
604+ params .put (ATTESTATION_OBJECT , getString (obj , ATTESTATION_OBJECT ));
605+ params .put (AUTHENTICATOR_ATTACHMENT , getString (obj , AUTHENTICATOR_ATTACHMENT ));
606+ params .put (RAW_ID , getString (obj , RAW_ID ));
607+ return params ;
608+ }
531609}
0 commit comments