From 648346e8cca1c8a4403862b116363e219508f007 Mon Sep 17 00:00:00 2001 From: imran Date: Tue, 28 Jul 2026 12:53:21 +0500 Subject: [PATCH 1/3] feat(jans-fido2): expose effective attestation-mode status endpoint Add GET /jans-fido2/restv1/trust/attestation/config, a read-only view of the attestation policy the server is applying: effective mode, whether unattested authenticators are still accepted, enterprise attestation, whether the metadata service is disabled, and whether the Apple WebAuthn root CA was loaded. unattestedAuthenticatorsAllowed is derived as mode != enforced, not mode == disabled: AttestationCertificateService.isAttestationEnforced() applies the stricter MDS trust rules only for enforced, so the default monitor mode still accepts an authenticator that fails attestation. The configured mode is reported verbatim alongside a flag for whether it is recognised, since an unrecognised value silently leaves the server lenient. Mirrored through the Config API fido2 plugin for the Admin UI. Also strips trailing tab characters on two pre-existing lines of jansFido2Swagger.yaml that made the spec unparseable by strict YAML loaders. Signed-off-by: imran --- .../plugins/docs/fido2-plugin-swagger.yaml | 524 +++++++----------- .../plugin/fido2/rest/ApiApplication.java | 5 +- .../plugin/fido2/rest/Fido2TrustResource.java | 87 +++ .../fido2/service/Fido2TrustService.java | 88 +++ .../plugin/fido2/util/Constants.java | 4 +- .../fido2/trust/fido2-attestation-config.json | 13 + jans-fido2/docs/jansFido2Swagger.yaml | 74 ++- .../model/trust/AttestationTrustConfig.java | 134 +++++ .../service/app/ResteasyInitializer.java | 2 + .../mds/AttestationCertificateService.java | 8 + .../service/trust/TrustStatusService.java | 69 +++ .../rs/controller/Fido2TrustController.java | 88 +++ .../AttestationCertificateServiceTest.java | 44 ++ .../service/trust/TrustStatusServiceTest.java | 120 ++++ .../controller/Fido2TrustControllerTest.java | 69 +++ 15 files changed, 1002 insertions(+), 327 deletions(-) create mode 100644 jans-config-api/plugins/fido2-plugin/src/main/java/io/jans/configapi/plugin/fido2/rest/Fido2TrustResource.java create mode 100644 jans-config-api/plugins/fido2-plugin/src/main/java/io/jans/configapi/plugin/fido2/service/Fido2TrustService.java create mode 100644 jans-config-api/server/src/main/resources/example/fido2/trust/fido2-attestation-config.json create mode 100644 jans-fido2/model/src/main/java/io/jans/fido2/model/trust/AttestationTrustConfig.java create mode 100644 jans-fido2/server/src/main/java/io/jans/fido2/service/trust/TrustStatusService.java create mode 100644 jans-fido2/server/src/main/java/io/jans/fido2/ws/rs/controller/Fido2TrustController.java create mode 100644 jans-fido2/server/src/test/java/io/jans/fido2/service/trust/TrustStatusServiceTest.java create mode 100644 jans-fido2/server/src/test/java/io/jans/fido2/ws/rs/controller/Fido2TrustControllerTest.java diff --git a/jans-config-api/plugins/docs/fido2-plugin-swagger.yaml b/jans-config-api/plugins/docs/fido2-plugin-swagger.yaml index 066aca4f7b2..91ffb1ab529 100644 --- a/jans-config-api/plugins/docs/fido2-plugin-swagger.yaml +++ b/jans-config-api/plugins/docs/fido2-plugin-swagger.yaml @@ -16,6 +16,7 @@ tags: - name: Fido2 - Configuration - name: Fido2 - Registration - name: Fido2 - Metrics +- name: Fido2 - Trust paths: /fido2/fido2-config: get: @@ -108,13 +109,7 @@ paths: examples: Response example: description: Response example - value: | - { - "newUsers": 1, - "returningUsers": 0, - "adoptionRate": 1, - "totalUniqueUsers": 1 - } + value: "" "400": description: Bad Request "401": @@ -166,15 +161,7 @@ paths: examples: Response example: description: Response example - value: | - { - "operatingSystems": {}, - "deviceTypes": {}, - "authenticatorTypes": { - "cross-platform": 1 - }, - "browsers": {} - } + value: "" "400": description: Bad Request "401": @@ -226,9 +213,7 @@ paths: examples: Response example: description: Response example - value: "{\n \"errorCategories\": {},\n \"failureRate\": 0.75,\n\ - \ \"successRate\": 0.25,\n\t\"completionRate\": 0.48,\n\t\"\ - dropOffRate\": 0.52,\n \"topErrors\": {}\n}\n" + value: "" "400": description: Bad Request "401": @@ -301,51 +286,7 @@ paths: examples: Response example: description: Response example - value: | - { - "start": 0, - "totalEntriesCount": 1, - "entriesCount": 1, - "entries": [ - { - "dn": "jansId=HOURLY_2026-02-20-15,ou=fido2-aggregations,o=jans", - "id": "HOURLY_2026-02-20-15", - "aggregationType": "HOURLY", - "startTime": "2026-02-20T15:00:00", - "endTime": "2026-02-20T16:00:00", - "uniqueUsers": 1, - "lastUpdated": "2026-02-20T16:05:00", - "fallbackEvents": 0, - "registrationSuccessRate": 0.25, - "authenticationAttempts": 0, - "metricsData": { - "deviceTypes": { - "cross-platform": 1 - }, - "fallbackEvents": 0, - "registrationAttempts": 4, - "registrationFailures": 3, - "registrationSuccesses": 1, - "authenticationAttempts": 0, - "authenticationFailures": 0, - "authenticationSuccesses": 0, - "registrationAvgDuration": 471.0, - "registrationSuccessRate": 0.25 - }, - "period": "2026-02-20-15", - "registrationAttempts": 4, - "registrationSuccesses": 1, - "registrationFailures": 3, - "authenticationSuccesses": 0, - "authenticationFailures": 0, - "registrationAvgDuration": 471.0, - "deviceTypes": { - "cross-platform": 1 - }, - "baseDn": "jansId=HOURLY_2026-02-20-15,ou=fido2-aggregations,o=jans" - } - ] - } + value: "" "400": description: Bad Request "401": @@ -404,15 +345,7 @@ paths: examples: Response example: description: Response example - value: | - { - "totalRegistrations": 4, - "totalOperations": 4, - "totalFallbacks": 0, - "totalAuthentications": 0, - "avgRegistrationSuccessRate": 0.25, - "avgAuthenticationSuccessRate": 0 - } + value: "" "400": description: Bad Request "401": @@ -478,38 +411,7 @@ paths: examples: Response example: description: Response example - value: | - { - "start": 1, - "totalEntriesCount": 10, - "entriesCount": 2, - "entries": [ - { - "dn": "jansId=086088ff-7a03-4e63-94b9-f4ab760edce2,ou=fido2-metrics,o=jans", - "id": "086088ff-7a03-4e63-94b9-f4ab760edce2", - "timestamp": "2026-02-17T06:26:39", - "userId": "e9e63718-7909-49c6-9feb-eeadd0c839b9", - "username": "test", - "operationType": "AUTHENTICATION", - "status": "ATTEMPT", - "nodeId": "12-63-57-5A-C8-E1", - "baseDn": "jansId=086088ff-7a03-4e63-94b9-f4ab760edce2,ou=fido2-metrics,o=jans" - }, - { - "dn": "jansId=9655ee6b-eaf3-43e4-930f-b19478dc0dad,ou=fido2-metrics,o=jans", - "id": "9655ee6b-eaf3-43e4-930f-b19478dc0dad", - "timestamp": "2026-02-17T06:26:45", - "userId": "e9e63718-7909-49c6-9feb-eeadd0c839b9", - "username": "test", - "operationType": "AUTHENTICATION", - "status": "SUCCESS", - "durationMs": 109, - "authenticatorType": "platform", - "nodeId": "12-63-57-5A-C8-E1", - "baseDn": "jansId=9655ee6b-eaf3-43e4-930f-b19478dc0dad,ou=fido2-metrics,o=jans" - } - ] - } + value: "" "400": description: Bad Request "401": @@ -581,36 +483,7 @@ paths: examples: Response example: description: Response example - value: | - { - "start": 0, - "totalEntriesCount": 4, - "entriesCount": 2, - "entries": [ - { - "dn": "jansId=0053a54e-5bf9-414f-b436-fcfcc9b1b440,ou=fido2-metrics,o=jans", - "id": "0053a54e-5bf9-414f-b436-fcfcc9b1b440", - "timestamp": "2026-02-20T15:53:39", - "userId": "e1f32d71-2954-4248-a7c5-a2d68ce39f8d", - "username": "admin", - "operationType": "REGISTRATION", - "status": "ATTEMPT", - "nodeId": "12-63-57-5A-C8-E1", - "baseDn": "jansId=0053a54e-5bf9-414f-b436-fcfcc9b1b440,ou=fido2-metrics,o=jans" - }, - { - "dn": "jansId=ff17dfa5-aaa1-4395-817e-b1f537c2b5ce,ou=fido2-metrics,o=jans", - "id": "ff17dfa5-aaa1-4395-817e-b1f537c2b5ce", - "timestamp": "2026-02-20T15:53:59", - "userId": "e1f32d71-2954-4248-a7c5-a2d68ce39f8d", - "username": "admin", - "operationType": "REGISTRATION", - "status": "ATTEMPT", - "nodeId": "12-63-57-5A-C8-E1", - "baseDn": "jansId=ff17dfa5-aaa1-4395-817e-b1f537c2b5ce,ou=fido2-metrics,o=jans" - } - ] - } + value: "" "400": description: Bad Request "401": @@ -645,21 +518,7 @@ paths: examples: Response example: description: Response example - value: | - { - "metricsEnabled": true, - "supportedAggregationTypes": [ - "HOURLY", - "DAILY", - "WEEKLY", - "MONTHLY" - ], - "retentionDays": 90, - "performanceMetrics": true, - "deviceInfoCollection": true, - "aggregationEnabled": true, - "errorCategorization": true - } + value: "" "401": description: Unauthorized "500": @@ -729,60 +588,7 @@ paths: examples: Response example: description: Response example - value: | - { - "start": 0, - "totalEntriesCount": 4, - "entriesCount": 4, - "entries": [ - { - "dn": "jansId=0053a54e-5bf9-414f-b436-fcfcc9b1b440,ou=fido2-metrics,o=jans", - "id": "0053a54e-5bf9-414f-b436-fcfcc9b1b440", - "timestamp": "2026-02-20T15:53:39", - "userId": "e1f32d71-2954-4248-a7c5-a2d68ce39f8d", - "username": "admin", - "operationType": "REGISTRATION", - "status": "ATTEMPT", - "nodeId": "12-63-57-5A-C8-E1", - "baseDn": "jansId=0053a54e-5bf9-414f-b436-fcfcc9b1b440,ou=fido2-metrics,o=jans" - }, - { - "dn": "jansId=ff17dfa5-aaa1-4395-817e-b1f537c2b5ce,ou=fido2-metrics,o=jans", - "id": "ff17dfa5-aaa1-4395-817e-b1f537c2b5ce", - "timestamp": "2026-02-20T15:53:59", - "userId": "e1f32d71-2954-4248-a7c5-a2d68ce39f8d", - "username": "admin", - "operationType": "REGISTRATION", - "status": "ATTEMPT", - "nodeId": "12-63-57-5A-C8-E1", - "baseDn": "jansId=ff17dfa5-aaa1-4395-817e-b1f537c2b5ce,ou=fido2-metrics,o=jans" - }, - { - "dn": "jansId=eadd61b1-0ecf-43c8-863c-693470a001f7,ou=fido2-metrics,o=jans", - "id": "eadd61b1-0ecf-43c8-863c-693470a001f7", - "timestamp": "2026-02-20T15:54:26", - "userId": "e1f32d71-2954-4248-a7c5-a2d68ce39f8d", - "username": "admin", - "operationType": "REGISTRATION", - "status": "ATTEMPT", - "nodeId": "12-63-57-5A-C8-E1", - "baseDn": "jansId=eadd61b1-0ecf-43c8-863c-693470a001f7,ou=fido2-metrics,o=jans" - }, - { - "dn": "jansId=4000d3e0-a18b-4e1c-8184-d43263561a6a,ou=fido2-metrics,o=jans", - "id": "4000d3e0-a18b-4e1c-8184-d43263561a6a", - "timestamp": "2026-02-20T15:54:50", - "userId": "e1f32d71-2954-4248-a7c5-a2d68ce39f8d", - "username": "admin", - "operationType": "REGISTRATION", - "status": "SUCCESS", - "durationMs": 471, - "authenticatorType": "cross-platform", - "nodeId": "12-63-57-5A-C8-E1", - "baseDn": "jansId=4000d3e0-a18b-4e1c-8184-d43263561a6a,ou=fido2-metrics,o=jans" - } - ] - } + value: "" "400": description: Bad Request "401": @@ -817,14 +623,7 @@ paths: examples: Response example: description: Response example - value: | - { - "metricsEnabled": true, - "aggregationEnabled": true, - "serviceAvailable": true, - "status": "UP", - "timestamp": "2026-03-12T12:11:06.79409705" - } + value: "" "401": description: Unauthorized "500": @@ -874,12 +673,7 @@ paths: examples: Response example: description: Response example - value: | - { - "registrationAvgDuration": 420, - "registrationMaxDuration": 890, - "registrationMinDuration": 210 - } + value: "" "400": description: Bad Request "401": @@ -929,14 +723,7 @@ paths: examples: Response example: description: Response example - value: | - { - "previousPeriod": {}, - "comparison": { - "totalOperationsChange": 0 - }, - "currentPeriod": {} - } + value: "" "400": description: Bad Request "401": @@ -996,42 +783,7 @@ paths: examples: Response example: description: Response example - value: | - { - "insights": { - "avgRegistrationSuccessRate": 0.25, - "avgAuthenticationSuccessRate": 0.67, - "peakUsage": { - "period": "2026-02-20", - "totalOperations": 7 - } - }, - "growthRate": 0.1, - "trendDirection": "STABLE", - "dataPoints": [ - { - "period": "2026-02-20", - "metrics": { - "deviceTypes": { - "cross-platform": 1 - }, - "errorCounts": {}, - "fallbackEvents": 1, - "registrationAttempts": 4, - "registrationFailures": 3, - "registrationSuccesses": 1, - "authenticationAttempts": 3, - "authenticationFailures": 1, - "authenticationSuccesses": 2, - "registrationAvgDuration": 420, - "registrationMinDuration": 210, - "registrationMaxDuration": 880, - "registrationSuccessRate": 0.25 - }, - "timestamp": 1771545600000 - } - ] - } + value: "" "400": description: Bad Request "401": @@ -1075,39 +827,48 @@ paths: description: Response example value: | { - "dn": "jansId=6e196a5d-122b-4f06-8ff8-6f41b32bc2a6,ou=fido2_register,o=jans", - "id": "6e196a5d-122b-4f06-8ff8-6f41b32bc2a6", - "challange": "CCkCZgre81B-6SYCzzfLqSReF4b7enbibnp487AEPkU", - "challengeHash": 76195, - "creationDate": "2024-09-25T17:13:51", + "dn": "jansId=1c88c1cb-8e32-4b79-b244-c9f677ca5654,ou=fido2_register,o=jans", + "id": "1c88c1cb-8e32-4b79-b244-c9f677ca5654", + "challange": "2vCSaPMIt4tZpkO8_rDG-v3QoXBYMB1aqCAXFgA4q0Y", + "challengeHash": 72585, + "creationDate": "2024-09-25T16:48:28", "userInum": "1e73f801-22c2-4c19-a757-f96a87a5c713", - "rpId": "admin-ui-test.gluu.org", + "rpId": "https://admin-ui-test.gluu.org", "deletable": false, - "publicKeyId": "RaA1BFgMwgKXFSVYJ4qPab0Dz_Y4zntWH0Wq21Uz6c8", - "publicKeyIdHash": 75217, + "publicKeyId": "KdwKkzDJDf0o2fLUWk0NDLpSX0QXFXG3H3a46RBTfGzyx69wQ6YJMMzSiIJXXEAKtjtAvNyQ1bOCkeWXZJ3u7A", + "publicKeyIdHash": 306117, "registrationData": { - "createdDate": "2024-09-25T17:13:51", - "updatedDate": "2024-09-25T17:15:43", + "createdDate": "2024-09-25T16:48:28", + "updatedDate": "2024-09-25T16:49:14", "createdBy": "admin", "updatedBy": "admin", "username": "admin", "domain": "admin-ui-test.gluu.org", - "userId": "fEJVrzv_s5p5_SCUjiVofbS01eKJwybVIFoRcE-Vl6Y", - "challenge": "CCkCZgre81B-6SYCzzfLqSReF4b7enbibnp487AEPkU", - "attenstationRequest": "{\"attestation\":\"direct\",\"displayName\":\"admin\",\"authenticatorSelection\":{\"authenticatorAttachment\":\"platform\",\"userVerification\":\"discouraged\",\"requireResidentKey\":\"false\"},\"username\":\"admin\"}", - "attenstationResponse": "{\"type\":\"public-key\",\"id\":\"RaA1BFgMwgKXFSVYJ4qPab0Dz_Y4zntWH0Wq21Uz6c8\",\"response\":{\"attestationObject\":\"o2NmbXRmcGFja2VkZ2F0dFN0bXSiY2FsZyZjc2lnWEcwRQIhANoBaaelHNJkCSnrmxSeixWKyCsA-IYW19LAv7IQoRcnAiAaQw6Rjc1By7RdA00k6qzDtOwLCoPvBTFACAL9nr6tPWhhdXRoRGF0YVikjZHVp5tBCFVmGadN9klm_j50zRn-V4OMG9wljvmxv3dFAAAAAK3OAAI1vMYKZIsLJfHwVQMAIEWgNQRYDMIClxUlWCeKj2m9A8_2OM57Vh9FqttVM-nPpQECAyYgASFYIMyALzgwTY7Oe3rzssnlBfrwk5TuZ1a-cKkEuRIYE0-tIlgghEQR51yw4l1fQLjvYi4ydFdOvC5rHkVrk-xxcuwcotE\",\"clientDataJSON\":\"eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiQ0NrQ1pncmU4MUItNlNZQ3p6ZkxxU1JlRjRiN2VuYmlibnA0ODdBRVBrVSIsIm9yaWdpbiI6Imh0dHBzOi8vYWRtaW4tdWktdGVzdC5nbHV1Lm9yZyIsImNyb3NzT3JpZ2luIjpmYWxzZX0\"},\"clientExtensionResults\":{}}", - "uncompressedECPoint": "pQECAyYgASFYIMyALzgwTY7Oe3rzssnlBfrwk5TuZ1a-cKkEuRIYE0-tIlgghEQR51yw4l1fQLjvYi4ydFdOvC5rHkVrk-xxcuwcotE", - "publicKeyId": "RaA1BFgMwgKXFSVYJ4qPab0Dz_Y4zntWH0Wq21Uz6c8", + "userId": "dIGgDiZj-uD0qYE52rbKAqdI7wlBOeIWqc8Qrgmp-GA", + "challenge": "2vCSaPMIt4tZpkO8_rDG-v3QoXBYMB1aqCAXFgA4q0Y", + "attenstationRequest": "{\"super_gluu_request\":true,\"super_gluu_request_mode\":\"two_step\",\"super_gluu_app_id\":\"https://admin-ui-test.gluu.org\",\"username\":\"admin\",\"displayName\":\"admin\",\"session_id\":\"aa7659a7-ea95-4ccf-802b-e2ce1f77592d\",\"attestation\":\"direct\"}", + "attenstationResponse": "{\"super_gluu_request\":true,\"super_gluu_request_mode\":\"two_step\",\"type\":\"public-key\",\"response\":{\"deviceData\":\"eyJwdXNoX3Rva2VuIjoiNWI1OWU5YzY2YjZkYWFjNDg4ODQ0MGY2YTQ0ZDY5MDU1NGE3NTlmOTA4MjM5N2QwMzdhYTg3ZjViZGVmZTA4OSIsInBsYXRmb3JtIjoiaW9zIiwib3NfdmVyc2lvbiI6IjE4LjAiLCJ1dWlkIjoiMzNENkZEMkQtODVEQy00NjQ1LUI1MjctNEEzOUJBMzBCOUY3Iiwib3NfbmFtZSI6ImlPUyIsInR5cGUiOiJpUGhvbmUiLCJuYW1lIjoiaVBob25lIn0=\",\"clientDataJSON\":\"eyJjaGFsbGVuZ2UiOiIydkNTYVBNSXQ0dFpwa084X3JERy12M1FvWEJZTUIxYXFDQVhGZ0E0cTBZIiwib3JpZ2luIjoiaHR0cHM6Ly9hZG1pbi11aS10ZXN0LmdsdXUub3JnIiwidHlwZSI6Im5hdmlnYXRvci5pZC5maW5pc2hFbnJvbGxtZW50In0\",\"attestationObject\":\"v2NmbXRzZmlkby11MmYtc3VwZXItZ2x1dWdhdHRTdG10v2N4NWOBeQP_TUlJQyt6Q0NBcUdnQXdJQkFnSUNFQU13Q2dZSUtvWkl6ajBFQXdJd2dhQXhDekFKQmdOVkJBWVRBbFZUTVFzd0NRWURWUVFJREFKVVdERVJNQThHQTFVRUNnd0lSMngxZFNCSmJtTXhLakFvQmdOVkJBc01JVWRzZFhVZ1NXNWpJRk5ISUVObGNuUnBabWxqWVhSbElFRjFkR2h2Y21sMGVURWtNQ0lHQTFVRUF3d2JSMngxZFNCSmJtTWdVMGNnU1c1MFpYSnRaV1JwWVhSbElFTkJNUjh3SFFZSktvWklodmNOQVFrQkZoQnpkWEJ3YjNKMFFHZHNkWFV1YjNKbk1CNFhEVEl6TURNd056RTBNekl6TkZvWERUSTJNRFF3TlRFME16SXpORm93Z2EweEN6QUpCZ05WQkFZVEFsVlRNUXN3Q1FZRFZRUUlEQUpVV0RFUE1BMEdBMVVFQnd3R1FYVnpkR2x1TVJFd0R3WURWUVFLREFoSGJIVjFJRWx1WXpFcU1DZ0dBMVVFQ3d3aFIyeDFkU0JKYm1NZ1UwY2dRMlZ5ZEdsbWFXTmhkR1VnUVhWMGFHOXlhWFI1TVNBd0hnWURWUVFEREJkVFJ5QkhiSFYxSUVsdVl5QlRSeUJKVDFNZ1VISnZaREVmTUIwR0NTcUdTSWIzRFFFSkFSWVFjM1Z3Y0c5eWRFQm5iSFYxTG05eVp6QlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJJdUxkY1dRVGRXY2pIZ2pUM05FRnR0S2grUTByRXl3MVdzeUZJZUFsNUpCZUZPanZWTUNoMThyK1ZwbGNoWHVFc1BDQUJ2dXhId3dieDhBT1dvZVBjaWpnYnN3Z2Jnd0NRWURWUjBUQkFJd0FEQVJCZ2xnaGtnQmh2aENBUUVFQkFNQ0I0QXdNd1lKWUlaSUFZYjRRZ0VOQkNZV0pFOXdaVzVUVTB3Z1IyVnVaWEpoZEdWa0lFTnNhV1Z1ZENCRFpYSjBhV1pwWTJGMFpUQWRCZ05WSFE0RUZnUVVFM3pUVTRDU3NTL0JhTVZPZ2U0dHpwTGh2WVF3SHdZRFZSMGpCQmd3Rm9BVXQySEJpdFBxTVFKWXZiZ3hlaFlWYW9pdGJ6QXdEZ1lEVlIwUEFRSC9CQVFEQWdXZ01CTUdBMVVkSlFRTU1Bb0dDQ3NHQVFVRkJ3TUNNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJQVBrNlRDaTc5dUtVa2JJLzVmMzFYT29SU1JYV1RDV1VlditwZ3F2ZUxhUUFpRUFwRVZsMEpSZkpsZUticXB3SkxBNVBzRWg3bDl4SkU2SHlOMWtac2ZrbkJRY3NpZ1hHMEUCIBzkX5fZ8u_p5WSXYf9snYVJr0ToaWKudDan31Sq_oDUAiEAmJfQD0TIVklEHqhaa9nvpdF0ZNQxTkVeABILEkZPsa__aGF1dGhEYXRhWM2SPi_U7b70sMUreG0v60G8suJ_QwlpkBIn-AYLG5BcVkEAAAAAAAAAAAAAAAAAAAAAAAAAAABAKdwKkzDJDf0o2fLUWk0NDLpSX0QXFXG3H3a46RBTfGzyx69wQ6YJMMzSiIJXXEAKtjtAvNyQ1bOCkeWXZJ3u7L9hMQJhMyZiLTEBYi0yWCDnjQGBQ5FbprdTRgVOa154aIp7JSc7I2LQijIavd9Wa2ItM1ggX0CyMtueg9vtuehvTU9rXDmOA19vqychmvATEkXUG5r__w\"},\"super_gluu_request_cancel\":false,\"id\":\"KdwKkzDJDf0o2fLUWk0NDLpSX0QXFXG3H3a46RBTfGzyx69wQ6YJMMzSiIJXXEAKtjtAvNyQ1bOCkeWXZJ3u7A\"}", + "uncompressedECPoint": "v2ExAmEzJmItMQFiLTJYIOeNAYFDkVumt1NGBU5rXnhoinslJzsjYtCKMhq931ZrYi0zWCBfQLIy256D2-256G9NT2tcOY4DX2-rJyGa8BMSRdQbmv8", + "publicKeyId": "KdwKkzDJDf0o2fLUWk0NDLpSX0QXFXG3H3a46RBTfGzyx69wQ6YJMMzSiIJXXEAKtjtAvNyQ1bOCkeWXZJ3u7A", "type": "public-key", "status": "registered", - "counter": 0, - "attestationType": "packed", - "signatureAlgorithm": 0, - "applicationId": "admin-ui-test.gluu.org" + "counter": 1, + "attestationType": "fido-u2f-super-gluu", + "signatureAlgorithm": -7, + "applicationId": "https://admin-ui-test.gluu.org" }, - "counter": 0, + "counter": 1, "registrationStatus": "registered", - "baseDn": "jansId=6e196a5d-122b-4f06-8ff8-6f41b32bc2a6,ou=fido2_register,o=jans" + "deviceData": { + "uuid": "33D6FD2D-85DC-4645-B527-4A39BA30B9F7", + "type": "iPhone", + "platform": "ios", + "name": "iPhone", + "os_name": "iOS", + "os_version": "18.0", + "push_token": "5b59e9c66b6daac4888440f6a44d690554a759f9082397d037aa87f5bdefe089" + }, + "baseDn": "jansId=1c88c1cb-8e32-4b79-b244-c9f677ca5654,ou=fido2_register,o=jans" } "401": description: Unauthorized @@ -1194,9 +955,53 @@ paths: value: | { "start": 0, - "totalEntriesCount": 2, - "entriesCount": 2, + "totalEntriesCount": 3, + "entriesCount": 3, "entries": [ + { + "dn": "jansId=1c88c1cb-8e32-4b79-b244-c9f677ca5654,ou=fido2_register,inum=1e73f801-22c2-4c19-a757-f96a87a5c713,ou=people,o=jans", + "id": "1c88c1cb-8e32-4b79-b244-c9f677ca5654", + "challange": "2vCSaPMIt4tZpkO8_rDG-v3QoXBYMB1aqCAXFgA4q0Y", + "challengeHash": 72585, + "creationDate": "2024-09-25T16:48:28", + "userInum": "1e73f801-22c2-4c19-a757-f96a87a5c713", + "rpId": "https://admin-ui-test.gluu.org", + "deletable": false, + "publicKeyId": "KdwKkzDJDf0o2fLUWk0NDLpSX0QXFXG3H3a46RBTfGzyx69wQ6YJMMzSiIJXXEAKtjtAvNyQ1bOCkeWXZJ3u7A", + "publicKeyIdHash": 306117, + "registrationData": { + "createdDate": "2024-09-25T16:48:28", + "updatedDate": "2024-09-25T16:49:14", + "createdBy": "admin", + "updatedBy": "admin", + "username": "admin", + "domain": "admin-ui-test.gluu.org", + "userId": "dIGgDiZj-uD0qYE52rbKAqdI7wlBOeIWqc8Qrgmp-GA", + "challenge": "2vCSaPMIt4tZpkO8_rDG-v3QoXBYMB1aqCAXFgA4q0Y", + "attenstationRequest": "{\"super_gluu_request\":true,\"super_gluu_request_mode\":\"two_step\",\"super_gluu_app_id\":\"https://admin-ui-test.gluu.org\",\"username\":\"admin\",\"displayName\":\"admin\",\"session_id\":\"aa7659a7-ea95-4ccf-802b-e2ce1f77592d\",\"attestation\":\"direct\"}", + "attenstationResponse": "{\"super_gluu_request\":true,\"super_gluu_request_mode\":\"two_step\",\"type\":\"public-key\",\"response\":{\"deviceData\":\"eyJwdXNoX3Rva2VuIjoiNWI1OWU5YzY2YjZkYWFjNDg4ODQ0MGY2YTQ0ZDY5MDU1NGE3NTlmOTA4MjM5N2QwMzdhYTg3ZjViZGVmZTA4OSIsInBsYXRmb3JtIjoiaW9zIiwib3NfdmVyc2lvbiI6IjE4LjAiLCJ1dWlkIjoiMzNENkZEMkQtODVEQy00NjQ1LUI1MjctNEEzOUJBMzBCOUY3Iiwib3NfbmFtZSI6ImlPUyIsInR5cGUiOiJpUGhvbmUiLCJuYW1lIjoiaVBob25lIn0=\",\"clientDataJSON\":\"eyJjaGFsbGVuZ2UiOiIydkNTYVBNSXQ0dFpwa084X3JERy12M1FvWEJZTUIxYXFDQVhGZ0E0cTBZIiwib3JpZ2luIjoiaHR0cHM6Ly9hZG1pbi11aS10ZXN0LmdsdXUub3JnIiwidHlwZSI6Im5hdmlnYXRvci5pZC5maW5pc2hFbnJvbGxtZW50In0\",\"attestationObject\":\"v2NmbXRzZmlkby11MmYtc3VwZXItZ2x1dWdhdHRTdG10v2N4NWOBeQP_TUlJQyt6Q0NBcUdnQXdJQkFnSUNFQU13Q2dZSUtvWkl6ajBFQXdJd2dhQXhDekFKQmdOVkJBWVRBbFZUTVFzd0NRWURWUVFJREFKVVdERVJNQThHQTFVRUNnd0lSMngxZFNCSmJtTXhLakFvQmdOVkJBc01JVWRzZFhVZ1NXNWpJRk5ISUVObGNuUnBabWxqWVhSbElFRjFkR2h2Y21sMGVURWtNQ0lHQTFVRUF3d2JSMngxZFNCSmJtTWdVMGNnU1c1MFpYSnRaV1JwWVhSbElFTkJNUjh3SFFZSktvWklodmNOQVFrQkZoQnpkWEJ3YjNKMFFHZHNkWFV1YjNKbk1CNFhEVEl6TURNd056RTBNekl6TkZvWERUSTJNRFF3TlRFME16SXpORm93Z2EweEN6QUpCZ05WQkFZVEFsVlRNUXN3Q1FZRFZRUUlEQUpVV0RFUE1BMEdBMVVFQnd3R1FYVnpkR2x1TVJFd0R3WURWUVFLREFoSGJIVjFJRWx1WXpFcU1DZ0dBMVVFQ3d3aFIyeDFkU0JKYm1NZ1UwY2dRMlZ5ZEdsbWFXTmhkR1VnUVhWMGFHOXlhWFI1TVNBd0hnWURWUVFEREJkVFJ5QkhiSFYxSUVsdVl5QlRSeUJKVDFNZ1VISnZaREVmTUIwR0NTcUdTSWIzRFFFSkFSWVFjM1Z3Y0c5eWRFQm5iSFYxTG05eVp6QlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJJdUxkY1dRVGRXY2pIZ2pUM05FRnR0S2grUTByRXl3MVdzeUZJZUFsNUpCZUZPanZWTUNoMThyK1ZwbGNoWHVFc1BDQUJ2dXhId3dieDhBT1dvZVBjaWpnYnN3Z2Jnd0NRWURWUjBUQkFJd0FEQVJCZ2xnaGtnQmh2aENBUUVFQkFNQ0I0QXdNd1lKWUlaSUFZYjRRZ0VOQkNZV0pFOXdaVzVUVTB3Z1IyVnVaWEpoZEdWa0lFTnNhV1Z1ZENCRFpYSjBhV1pwWTJGMFpUQWRCZ05WSFE0RUZnUVVFM3pUVTRDU3NTL0JhTVZPZ2U0dHpwTGh2WVF3SHdZRFZSMGpCQmd3Rm9BVXQySEJpdFBxTVFKWXZiZ3hlaFlWYW9pdGJ6QXdEZ1lEVlIwUEFRSC9CQVFEQWdXZ01CTUdBMVVkSlFRTU1Bb0dDQ3NHQVFVRkJ3TUNNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJQVBrNlRDaTc5dUtVa2JJLzVmMzFYT29SU1JYV1RDV1VlditwZ3F2ZUxhUUFpRUFwRVZsMEpSZkpsZUticXB3SkxBNVBzRWg3bDl4SkU2SHlOMWtac2ZrbkJRY3NpZ1hHMEUCIBzkX5fZ8u_p5WSXYf9snYVJr0ToaWKudDan31Sq_oDUAiEAmJfQD0TIVklEHqhaa9nvpdF0ZNQxTkVeABILEkZPsa__aGF1dGhEYXRhWM2SPi_U7b70sMUreG0v60G8suJ_QwlpkBIn-AYLG5BcVkEAAAAAAAAAAAAAAAAAAAAAAAAAAABAKdwKkzDJDf0o2fLUWk0NDLpSX0QXFXG3H3a46RBTfGzyx69wQ6YJMMzSiIJXXEAKtjtAvNyQ1bOCkeWXZJ3u7L9hMQJhMyZiLTEBYi0yWCDnjQGBQ5FbprdTRgVOa154aIp7JSc7I2LQijIavd9Wa2ItM1ggX0CyMtueg9vtuehvTU9rXDmOA19vqychmvATEkXUG5r__w\"},\"super_gluu_request_cancel\":false,\"id\":\"KdwKkzDJDf0o2fLUWk0NDLpSX0QXFXG3H3a46RBTfGzyx69wQ6YJMMzSiIJXXEAKtjtAvNyQ1bOCkeWXZJ3u7A\"}", + "uncompressedECPoint": "v2ExAmEzJmItMQFiLTJYIOeNAYFDkVumt1NGBU5rXnhoinslJzsjYtCKMhq931ZrYi0zWCBfQLIy256D2-256G9NT2tcOY4DX2-rJyGa8BMSRdQbmv8", + "publicKeyId": "KdwKkzDJDf0o2fLUWk0NDLpSX0QXFXG3H3a46RBTfGzyx69wQ6YJMMzSiIJXXEAKtjtAvNyQ1bOCkeWXZJ3u7A", + "type": "public-key", + "status": "registered", + "counter": 1, + "attestationType": "fido-u2f-super-gluu", + "signatureAlgorithm": -7, + "applicationId": "https://admin-ui-test.gluu.org" + }, + "counter": 1, + "registrationStatus": "registered", + "deviceData": { + "uuid": "33D6FD2D-85DC-4645-B527-4A39BA30B9F7", + "type": "iPhone", + "platform": "ios", + "name": "iPhone", + "os_name": "iOS", + "os_version": "18.0", + "push_token": "5b59e9c66b6daac4888440f6a44d690554a759f9082397d037aa87f5bdefe089" + }, + "baseDn": "jansId=1c88c1cb-8e32-4b79-b244-c9f677ca5654,ou=fido2_register,inum=1e73f801-22c2-4c19-a757-f96a87a5c713,ou=people,o=jans" + }, { "dn": "jansId=50b1c565-cb63-4d1c-9e60-b25c7ed15807,ou=fido2_register,inum=4e138215-42c6-43b2-a138-e98d282421a3,ou=people,o=jans", "id": "50b1c565-cb63-4d1c-9e60-b25c7ed15807", @@ -1346,9 +1151,53 @@ paths: value: | { "start": 0, - "totalEntriesCount": 2, - "entriesCount": 2, + "totalEntriesCount": 3, + "entriesCount": 3, "entries": [ + { + "dn": "jansId=1c88c1cb-8e32-4b79-b244-c9f677ca5654,ou=fido2_register,inum=1e73f801-22c2-4c19-a757-f96a87a5c713,ou=people,o=jans", + "id": "1c88c1cb-8e32-4b79-b244-c9f677ca5654", + "challange": "2vCSaPMIt4tZpkO8_rDG-v3QoXBYMB1aqCAXFgA4q0Y", + "challengeHash": 72585, + "creationDate": "2024-09-25T16:48:28", + "userInum": "1e73f801-22c2-4c19-a757-f96a87a5c713", + "rpId": "https://admin-ui-test.gluu.org", + "deletable": false, + "publicKeyId": "KdwKkzDJDf0o2fLUWk0NDLpSX0QXFXG3H3a46RBTfGzyx69wQ6YJMMzSiIJXXEAKtjtAvNyQ1bOCkeWXZJ3u7A", + "publicKeyIdHash": 306117, + "registrationData": { + "createdDate": "2024-09-25T16:48:28", + "updatedDate": "2024-09-25T16:49:14", + "createdBy": "admin", + "updatedBy": "admin", + "username": "admin", + "domain": "admin-ui-test.gluu.org", + "userId": "dIGgDiZj-uD0qYE52rbKAqdI7wlBOeIWqc8Qrgmp-GA", + "challenge": "2vCSaPMIt4tZpkO8_rDG-v3QoXBYMB1aqCAXFgA4q0Y", + "attenstationRequest": "{\"super_gluu_request\":true,\"super_gluu_request_mode\":\"two_step\",\"super_gluu_app_id\":\"https://admin-ui-test.gluu.org\",\"username\":\"admin\",\"displayName\":\"admin\",\"session_id\":\"aa7659a7-ea95-4ccf-802b-e2ce1f77592d\",\"attestation\":\"direct\"}", + "attenstationResponse": "{\"super_gluu_request\":true,\"super_gluu_request_mode\":\"two_step\",\"type\":\"public-key\",\"response\":{\"deviceData\":\"eyJwdXNoX3Rva2VuIjoiNWI1OWU5YzY2YjZkYWFjNDg4ODQ0MGY2YTQ0ZDY5MDU1NGE3NTlmOTA4MjM5N2QwMzdhYTg3ZjViZGVmZTA4OSIsInBsYXRmb3JtIjoiaW9zIiwib3NfdmVyc2lvbiI6IjE4LjAiLCJ1dWlkIjoiMzNENkZEMkQtODVEQy00NjQ1LUI1MjctNEEzOUJBMzBCOUY3Iiwib3NfbmFtZSI6ImlPUyIsInR5cGUiOiJpUGhvbmUiLCJuYW1lIjoiaVBob25lIn0=\",\"clientDataJSON\":\"eyJjaGFsbGVuZ2UiOiIydkNTYVBNSXQ0dFpwa084X3JERy12M1FvWEJZTUIxYXFDQVhGZ0E0cTBZIiwib3JpZ2luIjoiaHR0cHM6Ly9hZG1pbi11aS10ZXN0LmdsdXUub3JnIiwidHlwZSI6Im5hdmlnYXRvci5pZC5maW5pc2hFbnJvbGxtZW50In0\",\"attestationObject\":\"v2NmbXRzZmlkby11MmYtc3VwZXItZ2x1dWdhdHRTdG10v2N4NWOBeQP_TUlJQyt6Q0NBcUdnQXdJQkFnSUNFQU13Q2dZSUtvWkl6ajBFQXdJd2dhQXhDekFKQmdOVkJBWVRBbFZUTVFzd0NRWURWUVFJREFKVVdERVJNQThHQTFVRUNnd0lSMngxZFNCSmJtTXhLakFvQmdOVkJBc01JVWRzZFhVZ1NXNWpJRk5ISUVObGNuUnBabWxqWVhSbElFRjFkR2h2Y21sMGVURWtNQ0lHQTFVRUF3d2JSMngxZFNCSmJtTWdVMGNnU1c1MFpYSnRaV1JwWVhSbElFTkJNUjh3SFFZSktvWklodmNOQVFrQkZoQnpkWEJ3YjNKMFFHZHNkWFV1YjNKbk1CNFhEVEl6TURNd056RTBNekl6TkZvWERUSTJNRFF3TlRFME16SXpORm93Z2EweEN6QUpCZ05WQkFZVEFsVlRNUXN3Q1FZRFZRUUlEQUpVV0RFUE1BMEdBMVVFQnd3R1FYVnpkR2x1TVJFd0R3WURWUVFLREFoSGJIVjFJRWx1WXpFcU1DZ0dBMVVFQ3d3aFIyeDFkU0JKYm1NZ1UwY2dRMlZ5ZEdsbWFXTmhkR1VnUVhWMGFHOXlhWFI1TVNBd0hnWURWUVFEREJkVFJ5QkhiSFYxSUVsdVl5QlRSeUJKVDFNZ1VISnZaREVmTUIwR0NTcUdTSWIzRFFFSkFSWVFjM1Z3Y0c5eWRFQm5iSFYxTG05eVp6QlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJJdUxkY1dRVGRXY2pIZ2pUM05FRnR0S2grUTByRXl3MVdzeUZJZUFsNUpCZUZPanZWTUNoMThyK1ZwbGNoWHVFc1BDQUJ2dXhId3dieDhBT1dvZVBjaWpnYnN3Z2Jnd0NRWURWUjBUQkFJd0FEQVJCZ2xnaGtnQmh2aENBUUVFQkFNQ0I0QXdNd1lKWUlaSUFZYjRRZ0VOQkNZV0pFOXdaVzVUVTB3Z1IyVnVaWEpoZEdWa0lFTnNhV1Z1ZENCRFpYSjBhV1pwWTJGMFpUQWRCZ05WSFE0RUZnUVVFM3pUVTRDU3NTL0JhTVZPZ2U0dHpwTGh2WVF3SHdZRFZSMGpCQmd3Rm9BVXQySEJpdFBxTVFKWXZiZ3hlaFlWYW9pdGJ6QXdEZ1lEVlIwUEFRSC9CQVFEQWdXZ01CTUdBMVVkSlFRTU1Bb0dDQ3NHQVFVRkJ3TUNNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJQVBrNlRDaTc5dUtVa2JJLzVmMzFYT29SU1JYV1RDV1VlditwZ3F2ZUxhUUFpRUFwRVZsMEpSZkpsZUticXB3SkxBNVBzRWg3bDl4SkU2SHlOMWtac2ZrbkJRY3NpZ1hHMEUCIBzkX5fZ8u_p5WSXYf9snYVJr0ToaWKudDan31Sq_oDUAiEAmJfQD0TIVklEHqhaa9nvpdF0ZNQxTkVeABILEkZPsa__aGF1dGhEYXRhWM2SPi_U7b70sMUreG0v60G8suJ_QwlpkBIn-AYLG5BcVkEAAAAAAAAAAAAAAAAAAAAAAAAAAABAKdwKkzDJDf0o2fLUWk0NDLpSX0QXFXG3H3a46RBTfGzyx69wQ6YJMMzSiIJXXEAKtjtAvNyQ1bOCkeWXZJ3u7L9hMQJhMyZiLTEBYi0yWCDnjQGBQ5FbprdTRgVOa154aIp7JSc7I2LQijIavd9Wa2ItM1ggX0CyMtueg9vtuehvTU9rXDmOA19vqychmvATEkXUG5r__w\"},\"super_gluu_request_cancel\":false,\"id\":\"KdwKkzDJDf0o2fLUWk0NDLpSX0QXFXG3H3a46RBTfGzyx69wQ6YJMMzSiIJXXEAKtjtAvNyQ1bOCkeWXZJ3u7A\"}", + "uncompressedECPoint": "v2ExAmEzJmItMQFiLTJYIOeNAYFDkVumt1NGBU5rXnhoinslJzsjYtCKMhq931ZrYi0zWCBfQLIy256D2-256G9NT2tcOY4DX2-rJyGa8BMSRdQbmv8", + "publicKeyId": "KdwKkzDJDf0o2fLUWk0NDLpSX0QXFXG3H3a46RBTfGzyx69wQ6YJMMzSiIJXXEAKtjtAvNyQ1bOCkeWXZJ3u7A", + "type": "public-key", + "status": "registered", + "counter": 1, + "attestationType": "fido-u2f-super-gluu", + "signatureAlgorithm": -7, + "applicationId": "https://admin-ui-test.gluu.org" + }, + "counter": 1, + "registrationStatus": "registered", + "deviceData": { + "uuid": "33D6FD2D-85DC-4645-B527-4A39BA30B9F7", + "type": "iPhone", + "platform": "ios", + "name": "iPhone", + "os_name": "iOS", + "os_version": "18.0", + "push_token": "5b59e9c66b6daac4888440f6a44d690554a759f9082397d037aa87f5bdefe089" + }, + "baseDn": "jansId=1c88c1cb-8e32-4b79-b244-c9f677ca5654,ou=fido2_register,inum=1e73f801-22c2-4c19-a757-f96a87a5c713,ou=people,o=jans" + }, { "dn": "jansId=50b1c565-cb63-4d1c-9e60-b25c7ed15807,ou=fido2_register,inum=4e138215-42c6-43b2-a138-e98d282421a3,ou=people,o=jans", "id": "50b1c565-cb63-4d1c-9e60-b25c7ed15807", @@ -1434,6 +1283,37 @@ paths: - https://jans.io/oauth/config/fido2.admin - oauth2: - https://jans.io/oauth/config/read-all + /fido2/trust/attestation/config: + get: + tags: + - Fido2 - Trust + summary: Get effective Fido2 attestation configuration. + description: Get effective Fido2 attestation configuration. + operationId: get-fido2-trust-attestation-config + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/JsonNode" + examples: + Response example: + description: Response example + value: "" + "401": + description: Unauthorized + "500": + description: InternalServerError + security: + - oauth2: + - https://jans.io/oauth/config/fido2.readonly + - oauth2: + - https://jans.io/oauth/config/fido2.write + - oauth2: + - https://jans.io/oauth/config/fido2.admin + - oauth2: + - https://jans.io/oauth/config/read-all components: schemas: AppConfiguration: @@ -1571,48 +1451,22 @@ components: lastUpdated: type: string format: date-time - deviceTypes: - type: object - additionalProperties: - type: integer - format: int64 - errorCounts: - type: object - additionalProperties: - type: integer - format: int64 - fallbackEvents: - type: integer - format: int64 - metricsData: - type: object - additionalProperties: - type: object - authenticationSuccessRate: - type: number - format: double - authenticationAvgDuration: - type: number - format: double period: type: string - performanceMetrics: - type: object - additionalProperties: - type: object - authenticationAttempts: - type: integer - format: int64 - registrationAttempts: + registrationSuccesses: type: integer format: int64 - authenticationSuccesses: + registrationFailures: type: integer format: int64 - registrationFailures: + registrationAttempts: type: integer format: int64 - registrationSuccesses: + performanceMetrics: + type: object + additionalProperties: + type: object + authenticationSuccesses: type: integer format: int64 authenticationFailures: @@ -1621,9 +1475,35 @@ components: registrationSuccessRate: type: number format: double + authenticationAttempts: + type: integer + format: int64 registrationAvgDuration: type: number format: double + metricsData: + type: object + additionalProperties: + type: object + authenticationSuccessRate: + type: number + format: double + authenticationAvgDuration: + type: number + format: double + deviceTypes: + type: object + additionalProperties: + type: integer + format: int64 + fallbackEvents: + type: integer + format: int64 + errorCounts: + type: object + additionalProperties: + type: integer + format: int64 baseDn: type: string Fido2MetricsAggregationPagedResult: diff --git a/jans-config-api/plugins/fido2-plugin/src/main/java/io/jans/configapi/plugin/fido2/rest/ApiApplication.java b/jans-config-api/plugins/fido2-plugin/src/main/java/io/jans/configapi/plugin/fido2/rest/ApiApplication.java index 561f235e11e..c32338704f5 100644 --- a/jans-config-api/plugins/fido2-plugin/src/main/java/io/jans/configapi/plugin/fido2/rest/ApiApplication.java +++ b/jans-config-api/plugins/fido2-plugin/src/main/java/io/jans/configapi/plugin/fido2/rest/ApiApplication.java @@ -17,8 +17,8 @@ @ApplicationPath("/fido2") @OpenAPIDefinition(info = @Info(title = "Jans Config API - Fido2", version = "1.0.0", contact = @Contact(name = "Gluu Support", url = "https://support.gluu.org", email = "xxx@gluu.org"), license = @License(name = "Apache 2.0", url = "https://github.com/JanssenProject/jans/blob/main/LICENSE")), - tags = { @Tag(name = "Fido2 - Configuration"), @Tag(name = "Fido2 - Registration") - , @Tag(name = "Fido2 - Metrics")}, + tags = { @Tag(name = "Fido2 - Configuration"), @Tag(name = "Fido2 - Registration") + , @Tag(name = "Fido2 - Metrics"), @Tag(name = "Fido2 - Trust")}, servers = { @Server(url = "https://jans.io/", description = "The Jans server") }) @SecurityScheme(name = "oauth2", type = SecuritySchemeType.OAUTH2, flows = @OAuthFlows(clientCredentials = @OAuthFlow(tokenUrl = "https://{op-hostname}/.../token", scopes = { @OAuthScope(name = Constants.FIDO2_CONFIG_READ_ACCESS, description = "View fido2 config related information"), @@ -38,6 +38,7 @@ public Set> getClasses() { classes.add(Fido2ConfigResource.class); classes.add(Fido2RegistrationResource.class); classes.add(Fido2MetricsResource.class); + classes.add(Fido2TrustResource.class); return classes; } diff --git a/jans-config-api/plugins/fido2-plugin/src/main/java/io/jans/configapi/plugin/fido2/rest/Fido2TrustResource.java b/jans-config-api/plugins/fido2-plugin/src/main/java/io/jans/configapi/plugin/fido2/rest/Fido2TrustResource.java new file mode 100644 index 00000000000..9578da91673 --- /dev/null +++ b/jans-config-api/plugins/fido2-plugin/src/main/java/io/jans/configapi/plugin/fido2/rest/Fido2TrustResource.java @@ -0,0 +1,87 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.configapi.plugin.fido2.rest; + +import com.fasterxml.jackson.databind.JsonNode; + +import io.jans.configapi.core.rest.BaseResource; +import io.jans.configapi.core.rest.ProtectedApi; +import io.jans.configapi.plugin.fido2.service.Fido2TrustService; +import io.jans.configapi.plugin.fido2.util.Constants; +import io.jans.configapi.util.ApiAccessConstants; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.*; + +import jakarta.inject.Inject; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +import org.slf4j.Logger; + +/** + * Read-only attestation and MDS diagnostics, surfaced through the Config API for the Admin UI. + *

+ * GitHub Issue #14602 + */ +@Path(Constants.TRUST) +@Consumes(MediaType.APPLICATION_JSON) +@Produces(MediaType.APPLICATION_JSON) +public class Fido2TrustResource extends BaseResource { + + private static final String ERR_MSG = "Exception while getting Fido2 trust data is - "; + + @Inject + Logger logger; + + @Inject + Fido2TrustService fido2TrustService; + + /** + * Retrieve the attestation policy the FIDO2 server is actually applying. + * + * @return a Response containing a JsonNode with the effective attestation configuration + */ + @Operation(summary = "Get effective Fido2 attestation configuration.", description = "Get effective Fido2 attestation configuration.", operationId = "get-fido2-trust-attestation-config", tags = { + "Fido2 - Trust" }, security = { + @SecurityRequirement(name = "oauth2", scopes = { Constants.FIDO2_CONFIG_READ_ACCESS }), + @SecurityRequirement(name = "oauth2", scopes = { Constants.FIDO2_CONFIG_WRITE_ACCESS }), + @SecurityRequirement(name = "oauth2", scopes = { Constants.FIDO2_ADMIN_ACCESS }), + @SecurityRequirement(name = "oauth2", scopes = { ApiAccessConstants.SUPER_ADMIN_READ_ACCESS }) }) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok", content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = JsonNode.class), examples = @ExampleObject(name = "Response example", value = "example/fido2/trust/fido2-attestation-config.json"))), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "InternalServerError") }) + @GET + @Path(Constants.ATTESTATION_CONFIG) + @ProtectedApi(scopes = { Constants.FIDO2_CONFIG_READ_ACCESS }, groupScopes = { + Constants.FIDO2_CONFIG_WRITE_ACCESS }, superScopes = { Constants.FIDO2_ADMIN_ACCESS, + ApiAccessConstants.SUPER_ADMIN_READ_ACCESS }) + public Response getAttestationConfig() { + logger.info("Fido2 attestation trust configuration"); + JsonNode jsonNode = null; + try { + jsonNode = fido2TrustService.getAttestationConfig(null); + + if (logger.isDebugEnabled()) { + logger.debug("Fido2 attestation trust configuration - jsonNode:{}", jsonNode); + } + + } catch (Exception ex) { + logger.error(ERR_MSG, ex); + throwInternalServerException(ex); + } + return Response.ok(jsonNode).build(); + } + +} diff --git a/jans-config-api/plugins/fido2-plugin/src/main/java/io/jans/configapi/plugin/fido2/service/Fido2TrustService.java b/jans-config-api/plugins/fido2-plugin/src/main/java/io/jans/configapi/plugin/fido2/service/Fido2TrustService.java new file mode 100644 index 00000000000..9500e1b9d50 --- /dev/null +++ b/jans-config-api/plugins/fido2-plugin/src/main/java/io/jans/configapi/plugin/fido2/service/Fido2TrustService.java @@ -0,0 +1,88 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.configapi.plugin.fido2.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; + +import io.jans.configapi.plugin.fido2.util.Constants; +import io.jans.configapi.plugin.fido2.util.Fido2Util; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; + +import java.util.HashMap; +import java.util.Map; + +/** + * Proxies the read-only attestation and MDS diagnostics exposed by the FIDO2 server, so the Admin UI + * can render an attestation and MDS status panel through the Config API. + *

+ * GitHub Issue #14602 + */ +@Singleton +public class Fido2TrustService { + + private static final String CONTENT_TYPE = "Content-Type"; + private static final String AUTHORIZATION = "Authorization"; + private static final String FIDO2_TRUST_BASE_URL = "/jans-fido2/restv1/trust"; + + @Inject + Logger log; + + @Inject + Fido2Util fido2Util; + + public String getFido2TrustUrl() { + return fido2Util.getIssuer() + FIDO2_TRUST_BASE_URL; + } + + public String getAttestationConfigUrl() { + return getFido2TrustUrl() + Constants.ATTESTATION_CONFIG; + } + + /** + * The attestation policy the FIDO2 server is applying. + * + * @param token bearer token to forward, may be null + * @return attestation trust configuration + */ + public JsonNode getAttestationConfig(String token) throws JsonProcessingException { + log.info("Get Fido2 attestation trust configuration"); + + return getTrustData(this.getAttestationConfigUrl(), buildHeaders(token)); + } + + private JsonNode getTrustData(String url, Map headers) throws JsonProcessingException { + log.debug("Fido2 trust data: url:{}, headers:{}", url, headers); + + if (StringUtils.isBlank(url)) { + throw new WebApplicationException("Error while getting Fido2 trust data", + Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } + + JsonNode jsonNode = fido2Util.executeGetRequest(url, headers, null); + log.debug("Fido2 trust data: jsonNode:{}", jsonNode); + + return jsonNode; + } + + private Map buildHeaders(String token) { + Map headers = new HashMap<>(); + headers.put(CONTENT_TYPE, MediaType.APPLICATION_JSON); + if (StringUtils.isNotBlank(token)) { + headers.put(AUTHORIZATION, token); + } + return headers; + } +} diff --git a/jans-config-api/plugins/fido2-plugin/src/main/java/io/jans/configapi/plugin/fido2/util/Constants.java b/jans-config-api/plugins/fido2-plugin/src/main/java/io/jans/configapi/plugin/fido2/util/Constants.java index 10f63a4e67e..dd9a3efc1ea 100644 --- a/jans-config-api/plugins/fido2-plugin/src/main/java/io/jans/configapi/plugin/fido2/util/Constants.java +++ b/jans-config-api/plugins/fido2-plugin/src/main/java/io/jans/configapi/plugin/fido2/util/Constants.java @@ -16,7 +16,9 @@ private Constants() {} public static final String DEVICE = "/device"; public static final String USER_DEVICE = "/userdevice"; public static final String METRICS = "/metrics"; - + public static final String TRUST = "/trust"; + public static final String ATTESTATION_CONFIG = "/attestation/config"; + public static final String USERNAME_PATH = "/{username}"; public static final String UUID_PATH = "/{uuid}"; diff --git a/jans-config-api/server/src/main/resources/example/fido2/trust/fido2-attestation-config.json b/jans-config-api/server/src/main/resources/example/fido2/trust/fido2-attestation-config.json new file mode 100644 index 00000000000..70be28da068 --- /dev/null +++ b/jans-config-api/server/src/main/resources/example/fido2/trust/fido2-attestation-config.json @@ -0,0 +1,13 @@ +{ + "attestationMode": "monitor", + "attestationModeRecognized": true, + "unattestedAuthenticatorsAllowed": true, + "enterpriseAttestation": false, + "metadataServiceDisabled": false, + "appleRootCaPresent": true, + "enabledFidoAlgorithms": [ + "RS256", + "ES256" + ], + "hints": [] +} diff --git a/jans-fido2/docs/jansFido2Swagger.yaml b/jans-fido2/docs/jansFido2Swagger.yaml index 88e50efb586..8a662a9125b 100644 --- a/jans-fido2/docs/jansFido2Swagger.yaml +++ b/jans-fido2/docs/jansFido2Swagger.yaml @@ -22,6 +22,8 @@ tags: - name: FIDO2 Attestation - name: FIDO2 Metrics description: Metrics and analytics API for FIDO2/Passkey operations (registrations, authentications, adoption, performance, devices, errors, trends). + - name: FIDO2 Trust + description: Read-only visibility into the attestation policy the server is applying. Diagnostics only — these endpoints never change attestation behaviour. paths: /jans-fido2/restv1/configuration: get: @@ -328,7 +330,7 @@ paths: type: type: string alg: - type: string + type: string rp: type: object description: RP credentials @@ -336,7 +338,7 @@ paths: name: type: string id: - type: string + type: string username: type: string displayName: @@ -1005,6 +1007,27 @@ paths: $ref: '#/components/schemas/MetricsHealth' 500: $ref: '#/components/responses/InternalServerError' + /jans-fido2/restv1/trust/attestation/config: + get: + tags: + - FIDO2 Trust + summary: Get effective attestation configuration + description: >- + Returns the attestation policy the server is actually enforcing, so an administrator can see + whether a strict mode is the reason authenticators are being rejected. Read-only. + operationId: get-trust-attestation-config + responses: + 200: + description: Effective attestation configuration. + content: + application/json: + schema: + $ref: '#/components/schemas/AttestationTrustConfig' + 403: + $ref: '#/components/responses/AccessDenied' + 500: + $ref: '#/components/responses/InternalServerError' + components: responses: @@ -1378,3 +1401,50 @@ components: serviceAvailable: type: boolean description: True if metrics backend (e.g. DB) is reachable. + AttestationTrustConfig: + type: object + description: Effective attestation policy. Read-only view of the FIDO2 configuration; changing it is out of scope for this endpoint. + properties: + attestationMode: + type: string + enum: + - disabled + - monitor + - enforced + description: >- + The configured attestation mode, reported verbatim (default "monitor"). Any other value is + returned as-is rather than normalised, so a typo is visible — see attestationModeRecognized. + attestationModeRecognized: + type: boolean + description: >- + Whether the configured value matches one of the supported modes. When false the server falls + back to lenient behaviour, which is otherwise invisible to an administrator. + unattestedAuthenticatorsAllowed: + type: boolean + description: >- + Whether an authenticator that fails attestation validation is still accepted. True for every + mode except "enforced" — only that mode applies the stricter MDS trust rules, so the default + "monitor" still accepts such an authenticator. + enterpriseAttestation: + type: boolean + description: Whether enterprise attestation is enabled. + metadataServiceDisabled: + type: boolean + description: Whether MDS download and validation are switched off. When true, attestation cannot be validated against FIDO metadata. + appleRootCaPresent: + type: boolean + description: >- + Whether the Apple WebAuthn root CA certificate was loaded at startup. When false, Apple + anonymous attestation cannot be validated. + enabledFidoAlgorithms: + type: array + items: + type: string + example: ["RS256", "ES256"] + description: Signature algorithms the server accepts. + hints: + type: array + items: + type: string + example: ["security-key", "client-device", "hybrid"] + description: Configured RP hints. diff --git a/jans-fido2/model/src/main/java/io/jans/fido2/model/trust/AttestationTrustConfig.java b/jans-fido2/model/src/main/java/io/jans/fido2/model/trust/AttestationTrustConfig.java new file mode 100644 index 00000000000..d02cbd77bba --- /dev/null +++ b/jans-fido2/model/src/main/java/io/jans/fido2/model/trust/AttestationTrustConfig.java @@ -0,0 +1,134 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.fido2.model.trust; + +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +/** + * Read-only view of the attestation policy the server is actually enforcing. Lets an administrator see + * whether a strict mode is the reason certain authenticators are being rejected, instead of that + * surfacing to end users as a generic registration failure. + * + * @author Janssen Project + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class AttestationTrustConfig { + + private String attestationMode; + + private boolean attestationModeRecognized; + + private boolean unattestedAuthenticatorsAllowed; + + private boolean enterpriseAttestation; + + private boolean metadataServiceDisabled; + + private boolean appleRootCaPresent; + + private List enabledFidoAlgorithms = new ArrayList<>(); + + private List hints = new ArrayList<>(); + + /** + * The configured attestation mode, verbatim. Reported as configured rather than normalized so an + * administrator can see a typo for what it is. + */ + public String getAttestationMode() { + return attestationMode; + } + + public void setAttestationMode(String attestationMode) { + this.attestationMode = attestationMode; + } + + /** + * Whether the configured value matches one of the supported modes (disabled / monitor / enforced). + * When false the server falls back to lenient behaviour, which is otherwise invisible. + */ + public boolean isAttestationModeRecognized() { + return attestationModeRecognized; + } + + public void setAttestationModeRecognized(boolean attestationModeRecognized) { + this.attestationModeRecognized = attestationModeRecognized; + } + + /** + * Whether an authenticator that fails attestation validation is still accepted. True for every mode + * except {@code enforced} — only that mode applies the stricter MDS trust rules. + */ + public boolean isUnattestedAuthenticatorsAllowed() { + return unattestedAuthenticatorsAllowed; + } + + public void setUnattestedAuthenticatorsAllowed(boolean unattestedAuthenticatorsAllowed) { + this.unattestedAuthenticatorsAllowed = unattestedAuthenticatorsAllowed; + } + + public boolean isEnterpriseAttestation() { + return enterpriseAttestation; + } + + public void setEnterpriseAttestation(boolean enterpriseAttestation) { + this.enterpriseAttestation = enterpriseAttestation; + } + + /** + * When true, MDS download and validation are switched off and attestation cannot be validated + * against FIDO metadata. + */ + public boolean isMetadataServiceDisabled() { + return metadataServiceDisabled; + } + + public void setMetadataServiceDisabled(boolean metadataServiceDisabled) { + this.metadataServiceDisabled = metadataServiceDisabled; + } + + /** + * Whether the Apple WebAuthn root CA was loaded at startup. When false, Apple anonymous attestation + * cannot be validated. + */ + public boolean isAppleRootCaPresent() { + return appleRootCaPresent; + } + + public void setAppleRootCaPresent(boolean appleRootCaPresent) { + this.appleRootCaPresent = appleRootCaPresent; + } + + public List getEnabledFidoAlgorithms() { + return enabledFidoAlgorithms; + } + + public void setEnabledFidoAlgorithms(List enabledFidoAlgorithms) { + this.enabledFidoAlgorithms = enabledFidoAlgorithms; + } + + public List getHints() { + return hints; + } + + public void setHints(List hints) { + this.hints = hints; + } + + @Override + public String toString() { + return "AttestationTrustConfig [attestationMode=" + attestationMode + ", attestationModeRecognized=" + + attestationModeRecognized + ", unattestedAuthenticatorsAllowed=" + unattestedAuthenticatorsAllowed + + ", enterpriseAttestation=" + enterpriseAttestation + ", metadataServiceDisabled=" + + metadataServiceDisabled + ", appleRootCaPresent=" + appleRootCaPresent + ", enabledFidoAlgorithms=" + + enabledFidoAlgorithms + ", hints=" + hints + "]"; + } +} diff --git a/jans-fido2/server/src/main/java/io/jans/fido2/service/app/ResteasyInitializer.java b/jans-fido2/server/src/main/java/io/jans/fido2/service/app/ResteasyInitializer.java index d1063463c45..a9fedbd1a0f 100644 --- a/jans-fido2/server/src/main/java/io/jans/fido2/service/app/ResteasyInitializer.java +++ b/jans-fido2/server/src/main/java/io/jans/fido2/service/app/ResteasyInitializer.java @@ -17,6 +17,7 @@ import io.jans.fido2.ws.rs.controller.AttestationController; import io.jans.fido2.ws.rs.controller.ConfigurationController; import io.jans.fido2.ws.rs.controller.Fido2MetricsController; +import io.jans.fido2.ws.rs.controller.Fido2TrustController; /** * Integration with Resteasy @@ -35,6 +36,7 @@ public Set> getClasses() { classes.add(AttestationController.class); classes.add(WebAuthnController.class); classes.add(Fido2MetricsController.class); + classes.add(Fido2TrustController.class); return classes; } diff --git a/jans-fido2/server/src/main/java/io/jans/fido2/service/mds/AttestationCertificateService.java b/jans-fido2/server/src/main/java/io/jans/fido2/service/mds/AttestationCertificateService.java index 81ff50e4553..75d28d8e8c4 100644 --- a/jans-fido2/server/src/main/java/io/jans/fido2/service/mds/AttestationCertificateService.java +++ b/jans-fido2/server/src/main/java/io/jans/fido2/service/mds/AttestationCertificateService.java @@ -97,6 +97,14 @@ public List getAppleRootCertificates() { return getRootCertificatesBySubjectDN(appleRootCaSubjectDn); } + /** + * Whether the Apple WebAuthn root CA was loaded at startup. When false, Apple anonymous attestation + * cannot be validated — a condition that is otherwise only visible as a startup log warning. #14602 + */ + public boolean isAppleRootCaPresent() { + return appleRootCaSubjectDn != null; + } + public List getAttestationRootCertificates(JsonNode metadataNode, List attestationCertificates) { JsonNode metaDataStatement = null; diff --git a/jans-fido2/server/src/main/java/io/jans/fido2/service/trust/TrustStatusService.java b/jans-fido2/server/src/main/java/io/jans/fido2/service/trust/TrustStatusService.java new file mode 100644 index 00000000000..3f183881772 --- /dev/null +++ b/jans-fido2/server/src/main/java/io/jans/fido2/service/trust/TrustStatusService.java @@ -0,0 +1,69 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.fido2.service.trust; + +import org.slf4j.Logger; + +import io.jans.fido2.model.conf.AppConfiguration; +import io.jans.fido2.model.conf.AttestationMode; +import io.jans.fido2.model.conf.Fido2Configuration; +import io.jans.fido2.model.trust.AttestationTrustConfig; +import io.jans.fido2.service.mds.AttestationCertificateService; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +/** + * Assembles the read-only attestation status reported by the trust endpoints. + *

+ * This service only reads state that the attestation service already holds; it never changes + * attestation behaviour. + * + * @author Janssen Project + */ +@ApplicationScoped +public class TrustStatusService { + + @Inject + private Logger log; + + @Inject + private AppConfiguration appConfiguration; + + @Inject + private AttestationCertificateService attestationCertificateService; + + /** + * The attestation policy the server is actually applying. + */ + public AttestationTrustConfig getAttestationTrustConfig() { + AttestationTrustConfig trustConfig = new AttestationTrustConfig(); + Fido2Configuration fido2Configuration = appConfiguration.getFido2Configuration(); + if (fido2Configuration == null) { + log.warn("Fido2 configuration is not available; reporting empty attestation trust config"); + return trustConfig; + } + + String configuredMode = fido2Configuration.getAttestationMode(); + trustConfig.setAttestationMode(configuredMode); + trustConfig.setAttestationModeRecognized(AttestationMode.getByValue(configuredMode) != null); + // Only "enforced" applies the stricter MDS trust rules (AttestationCertificateService + // .isAttestationEnforced) — "disabled" and "monitor" both stay lenient, so anything other than + // enforced still accepts an authenticator that fails attestation validation. + trustConfig.setUnattestedAuthenticatorsAllowed(!isAttestationEnforced(configuredMode)); + trustConfig.setEnterpriseAttestation(fido2Configuration.isEnterpriseAttestation()); + trustConfig.setMetadataServiceDisabled(fido2Configuration.isDisableMetadataService()); + trustConfig.setAppleRootCaPresent(attestationCertificateService.isAppleRootCaPresent()); + trustConfig.setEnabledFidoAlgorithms(fido2Configuration.getEnabledFidoAlgorithms()); + trustConfig.setHints(fido2Configuration.getHints()); + + return trustConfig; + } + + private boolean isAttestationEnforced(String configuredMode) { + return AttestationMode.ENFORCED.getValue().equalsIgnoreCase(configuredMode); + } +} diff --git a/jans-fido2/server/src/main/java/io/jans/fido2/ws/rs/controller/Fido2TrustController.java b/jans-fido2/server/src/main/java/io/jans/fido2/ws/rs/controller/Fido2TrustController.java new file mode 100644 index 00000000000..1ab205e7534 --- /dev/null +++ b/jans-fido2/server/src/main/java/io/jans/fido2/ws/rs/controller/Fido2TrustController.java @@ -0,0 +1,88 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.fido2.ws.rs.controller; + +import org.slf4j.Logger; + +import io.jans.fido2.model.error.ErrorResponseFactory; +import io.jans.fido2.service.DataMapperService; +import io.jans.fido2.service.trust.TrustStatusService; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +/** + * REST API controller exposing attestation trust diagnostics. + *

+ * Read-only: these endpoints surface configuration that already exists in the server, so an + * administrator can see why enrollments are failing instead of that appearing to end users as a + * generic registration failure. Nothing here changes attestation behaviour. + *

+ * SECURITY NOTE: as with the metrics endpoints, authentication and authorization are expected to be + * enforced at the infrastructure level (API gateway, OAuth interceptor, or reverse proxy) or via the + * Config API fido2 plugin, which applies the fido2 configuration scopes. + * + * @author Janssen Project + */ +@ApplicationScoped +@Path("/trust") +public class Fido2TrustController { + + @Inject + private Logger log; + + @Inject + private TrustStatusService trustStatusService; + + @Inject + private DataMapperService dataMapperService; + + @Inject + private ErrorResponseFactory errorResponseFactory; + + /** + * Effective attestation configuration: the mode in force, whether unattested authenticators are + * still accepted, and whether the trust anchors attestation depends on are present. + * + * @return attestation trust configuration + */ + @GET + @Path("/attestation/config") + @Produces(MediaType.APPLICATION_JSON) + public Response getAttestationConfig() { + return processRequest( + () -> Response.ok(dataMapperService.writeValueAsString(trustStatusService.getAttestationTrustConfig())) + .build()); + } + + private Response processRequest(RequestProcessor processor) { + try { + return processor.process(); + } catch (WebApplicationException e) { + // Already carries the right status code. + throw e; + } catch (Exception e) { + // Log the detail, return a generic message — the response must not leak internal paths or + // connection details. + log.error("Error processing trust status request: {}", e.getMessage(), e); + throw errorResponseFactory.unknownError("Failed to read trust status"); + } + } + + /** + * Functional interface for request processing; throws Exception to accommodate the checked + * exceptions raised while serializing the response. + */ + private interface RequestProcessor { + Response process() throws Exception; + } +} diff --git a/jans-fido2/server/src/test/java/io/jans/fido2/service/mds/AttestationCertificateServiceTest.java b/jans-fido2/server/src/test/java/io/jans/fido2/service/mds/AttestationCertificateServiceTest.java index dfdf67169db..302b884b8e6 100644 --- a/jans-fido2/server/src/test/java/io/jans/fido2/service/mds/AttestationCertificateServiceTest.java +++ b/jans-fido2/server/src/test/java/io/jans/fido2/service/mds/AttestationCertificateServiceTest.java @@ -16,12 +16,16 @@ import org.mockito.quality.Strictness; import org.slf4j.Logger; +import javax.security.auth.x500.X500Principal; + import java.security.cert.X509Certificate; import java.util.Collections; import java.util.List; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -82,4 +86,44 @@ void getAttestationRootCertificates_monitorAndMetadataFetchFails_fallsBack() { // Regression guard: monitor/disabled keep the previous lenient behavior — fall back, do not throw. assertDoesNotThrow(() -> attestationCertificateService.getAttestationRootCertificates(authData, certs)); } + + // #14602 — Apple root CA presence surfaced by the trust/attestation/config endpoint. + + private void configureAuthenticatorCertsFolder() { + Fido2Configuration cfg = mock(Fido2Configuration.class); + when(cfg.getAuthenticatorCertsFolder()).thenReturn("/etc/jans/conf/fido2/authenticator_cert"); + when(appConfiguration.getFido2Configuration()).thenReturn(cfg); + } + + @Test + void isAppleRootCaPresent_ifCertificateLoaded_returnsTrue() { + configureAuthenticatorCertsFolder(); + X509Certificate appleCert = mock(X509Certificate.class); + when(appleCert.getSubjectX500Principal()).thenReturn(new X500Principal("CN=Apple WebAuthn Root CA")); + when(certificateService.getCertificateByDisplayName("Apple_WebAuthn_Root_CA.pem")).thenReturn(appleCert); + + attestationCertificateService.init(new Object()); + + assertTrue(attestationCertificateService.isAppleRootCaPresent()); + } + + @Test + void isAppleRootCaPresent_ifCertificateMissing_returnsFalse() { + configureAuthenticatorCertsFolder(); + when(certificateService.getCertificateByDisplayName("Apple_WebAuthn_Root_CA.pem")).thenReturn(null); + + attestationCertificateService.init(new Object()); + + // Today this condition is only a startup log warning; the endpoint has to be able to report it. + assertFalse(attestationCertificateService.isAppleRootCaPresent()); + } + + @Test + void isAppleRootCaPresent_ifFido2ConfigurationMissing_returnsFalse() { + when(appConfiguration.getFido2Configuration()).thenReturn(null); + + attestationCertificateService.init(new Object()); + + assertFalse(attestationCertificateService.isAppleRootCaPresent()); + } } diff --git a/jans-fido2/server/src/test/java/io/jans/fido2/service/trust/TrustStatusServiceTest.java b/jans-fido2/server/src/test/java/io/jans/fido2/service/trust/TrustStatusServiceTest.java new file mode 100644 index 00000000000..5e9cbe2e035 --- /dev/null +++ b/jans-fido2/server/src/test/java/io/jans/fido2/service/trust/TrustStatusServiceTest.java @@ -0,0 +1,120 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.fido2.service.trust; + +import io.jans.fido2.model.conf.AppConfiguration; +import io.jans.fido2.model.conf.Fido2Configuration; +import io.jans.fido2.model.trust.AttestationTrustConfig; +import io.jans.fido2.service.mds.AttestationCertificateService; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.slf4j.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class TrustStatusServiceTest { + + @InjectMocks + private TrustStatusService trustStatusService; + + @Mock + private Logger log; + @Mock + private AppConfiguration appConfiguration; + @Mock + private AttestationCertificateService attestationCertificateService; + + private Fido2Configuration configure(String attestationMode, boolean metadataServiceDisabled) { + Fido2Configuration cfg = mock(Fido2Configuration.class); + when(cfg.getAttestationMode()).thenReturn(attestationMode); + when(cfg.isDisableMetadataService()).thenReturn(metadataServiceDisabled); + when(appConfiguration.getFido2Configuration()).thenReturn(cfg); + return cfg; + } + + @Test + void getAttestationTrustConfig_ifEnforced_disallowsUnattested() { + configure("enforced", false); + when(attestationCertificateService.isAppleRootCaPresent()).thenReturn(true); + + AttestationTrustConfig config = trustStatusService.getAttestationTrustConfig(); + + assertEquals("enforced", config.getAttestationMode()); + assertTrue(config.isAttestationModeRecognized()); + assertFalse(config.isUnattestedAuthenticatorsAllowed()); + assertTrue(config.isAppleRootCaPresent()); + } + + @Test + void getAttestationTrustConfig_ifMonitor_stillAllowsUnattested() { + configure("monitor", false); + + AttestationTrustConfig config = trustStatusService.getAttestationTrustConfig(); + + // Only "enforced" applies the stricter trust rules — monitor is lenient, which is exactly the + // thing administrators get wrong about the default. + assertTrue(config.isAttestationModeRecognized()); + assertTrue(config.isUnattestedAuthenticatorsAllowed()); + } + + @Test + void getAttestationTrustConfig_ifDisabled_allowsUnattested() { + configure("disabled", true); + + AttestationTrustConfig config = trustStatusService.getAttestationTrustConfig(); + + assertTrue(config.isAttestationModeRecognized()); + assertTrue(config.isUnattestedAuthenticatorsAllowed()); + assertTrue(config.isMetadataServiceDisabled()); + } + + @Test + void getAttestationTrustConfig_ifModeUnrecognized_reportsRawValueAndLenientBehaviour() { + configure("Enforce", false); + + AttestationTrustConfig config = trustStatusService.getAttestationTrustConfig(); + + // A typo silently makes the server lenient; report it rather than normalising it away. + assertEquals("Enforce", config.getAttestationMode()); + assertFalse(config.isAttestationModeRecognized()); + assertTrue(config.isUnattestedAuthenticatorsAllowed()); + } + + @Test + void getAttestationTrustConfig_ifAppleRootCaMissing_reportsAbsent() { + configure("monitor", false); + when(attestationCertificateService.isAppleRootCaPresent()).thenReturn(false); + + AttestationTrustConfig config = trustStatusService.getAttestationTrustConfig(); + + // Today this condition is only a startup log warning. + assertFalse(config.isAppleRootCaPresent()); + } + + @Test + void getAttestationTrustConfig_ifNoFido2Configuration_returnsEmptyConfig() { + when(appConfiguration.getFido2Configuration()).thenReturn(null); + + AttestationTrustConfig config = trustStatusService.getAttestationTrustConfig(); + + assertNotNull(config); + assertNull(config.getAttestationMode()); + } +} diff --git a/jans-fido2/server/src/test/java/io/jans/fido2/ws/rs/controller/Fido2TrustControllerTest.java b/jans-fido2/server/src/test/java/io/jans/fido2/ws/rs/controller/Fido2TrustControllerTest.java new file mode 100644 index 00000000000..f19716c0cf1 --- /dev/null +++ b/jans-fido2/server/src/test/java/io/jans/fido2/ws/rs/controller/Fido2TrustControllerTest.java @@ -0,0 +1,69 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.fido2.ws.rs.controller; + +import io.jans.fido2.model.error.ErrorResponseFactory; +import io.jans.fido2.model.trust.AttestationTrustConfig; +import io.jans.fido2.service.DataMapperService; +import io.jans.fido2.service.trust.TrustStatusService; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.Response; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.slf4j.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class Fido2TrustControllerTest { + + @InjectMocks + private Fido2TrustController fido2TrustController; + + @Mock + private Logger log; + @Mock + private TrustStatusService trustStatusService; + @Mock + private DataMapperService dataMapperService; + @Mock + private ErrorResponseFactory errorResponseFactory; + + @Test + void getAttestationConfig_returnsOk() throws Exception { + when(trustStatusService.getAttestationTrustConfig()).thenReturn(new AttestationTrustConfig()); + when(dataMapperService.writeValueAsString(any())).thenReturn("{}"); + + Response response = fido2TrustController.getAttestationConfig(); + + assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + } + + @Test + void getAttestationConfig_ifServiceFails_raisesSanitizedError() { + when(trustStatusService.getAttestationTrustConfig()) + .thenThrow(new IllegalStateException("jdbc://secret-host/db")); + when(errorResponseFactory.unknownError(anyString())) + .thenReturn(new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR)); + + assertThrows(WebApplicationException.class, () -> fido2TrustController.getAttestationConfig()); + + // The internal detail goes to the log, never into the response. + verify(errorResponseFactory).unknownError("Failed to read trust status"); + } +} From 64e2efb1043f0a5b98c87df1df589aca020e3ac8 Mon Sep 17 00:00:00 2001 From: imran Date: Tue, 28 Jul 2026 12:53:52 +0500 Subject: [PATCH 2/3] fix(jans-config-api): declare jans-config-api-common explicitly in fido2-plugin The plugin imports io.jans.configapi.util.ApiAccessConstants and ApiConstants directly but relied on them arriving transitively through jans-config-api-server. Declare the dependency it actually uses. Signed-off-by: imran --- jans-config-api/plugins/fido2-plugin/pom.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/jans-config-api/plugins/fido2-plugin/pom.xml b/jans-config-api/plugins/fido2-plugin/pom.xml index aca4cb0fc07..28e8a3945d1 100644 --- a/jans-config-api/plugins/fido2-plugin/pom.xml +++ b/jans-config-api/plugins/fido2-plugin/pom.xml @@ -21,6 +21,13 @@ jans-config-api-shared ${jans.version} + + + io.jans + jans-config-api-common + ${jans.version} + io.jans jans-fido2-model From 26e9e4b1a81fa68ead5aefbf7146ee76a85351f4 Mon Sep 17 00:00:00 2001 From: imran Date: Tue, 28 Jul 2026 12:53:53 +0500 Subject: [PATCH 3/3] docs(jans-fido2): document the attestation-mode status endpoint Adds a Trust Diagnostics page covering the attestation-mode endpoint, the three attestation modes, and the fact that only "enforced" rejects a failing attestation - the default "monitor" mode does not. Signed-off-by: imran --- docs/janssen-server/fido/trust-diagnostics.md | 91 +++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 92 insertions(+) create mode 100644 docs/janssen-server/fido/trust-diagnostics.md diff --git a/docs/janssen-server/fido/trust-diagnostics.md b/docs/janssen-server/fido/trust-diagnostics.md new file mode 100644 index 00000000000..dc01316d13c --- /dev/null +++ b/docs/janssen-server/fido/trust-diagnostics.md @@ -0,0 +1,91 @@ +--- +tags: + - administration + - fido2 + - attestation + - diagnostics +--- + +# Trust Diagnostics + +Trust misconfiguration is one of the harder FIDO2 problems to diagnose, because it reaches the end user +as a generic registration failure. An authenticator that was working yesterday starts being rejected, +and nothing in the response says why. + +The FIDO2 server exposes read-only endpoints that make the trust state visible. They are diagnostics +only — they never change attestation behaviour. + +## Attestation configuration + +=== "FIDO2 server" + + ``` + GET /jans-fido2/restv1/trust/attestation/config + ``` + +=== "Config API (fido2 plugin)" + + ``` + GET /fido2/trust/attestation/config + ``` + + Served under the Config API base path. Requires one of the + `https://jans.io/oauth/config/fido2.readonly`, `fido2.write`, `fido2.admin` or `read-all` scopes. + +```json +{ + "attestationMode": "monitor", + "attestationModeRecognized": true, + "unattestedAuthenticatorsAllowed": true, + "enterpriseAttestation": false, + "metadataServiceDisabled": false, + "appleRootCaPresent": true, + "enabledFidoAlgorithms": ["RS256", "ES256"], + "hints": [] +} +``` + +### Attestation modes + +`attestationMode` is set in the [FIDO2 configuration](./fido2-server-properties-config.md) and defaults +to `monitor`. + +| Mode | Behaviour | +| --- | --- | +| `disabled` | Attestation is not validated. | +| `monitor` | Attestation is validated and logged, but a failure does **not** reject the registration. | +| `enforced` | A failing attestation check rejects the registration. | + +!!! important + **Only `enforced` rejects anything.** Both `disabled` and `monitor` are lenient, so under the + default `monitor` mode an authenticator whose attestation cannot be validated — an unknown AAGUID, + an expired metadata blob, an untrusted root — is still registered successfully. If you are relying + on attestation to keep unapproved authenticators out, `monitor` will not do it. + + `unattestedAuthenticatorsAllowed` reports this directly: it is `true` for every mode except + `enforced`. + +`attestationModeRecognized` is `false` when the configured value is not one of the three modes above — +for example a typo such as `Enforce`. An unrecognised value leaves the server in lenient behaviour, so +this flag is worth checking when enforcement appears not to be taking effect. + +`metadataServiceDisabled` reflects the `disableMetadataService` setting. When it is `true`, attestation +cannot be validated against FIDO metadata at all. + +`appleRootCaPresent` reports whether the Apple WebAuthn root CA certificate was loaded at startup. When +it is `false`, Apple anonymous attestation cannot be validated. + +## Troubleshooting + +**Enforcement does not seem to apply.** Check `attestationMode` and `attestationModeRecognized`. An +unrecognised mode value silently leaves the server lenient. + +**Unapproved authenticators are being registered.** The default `monitor` mode does not reject them. +Check `unattestedAuthenticatorsAllowed`; only `enforced` will keep them out. + +**Certain authenticators are rejected while others succeed.** With `attestationMode` set to `enforced`, +an authenticator whose AAGUID is absent from the loaded metadata is rejected. See +[Vendor Metadata](./vendor-metadata.md) for supplying metadata locally. + +**Apple devices fail to register.** Check `appleRootCaPresent`. Without the Apple WebAuthn root CA, +Apple anonymous attestation cannot be validated. diff --git a/mkdocs.yml b/mkdocs.yml index 85a14fabcd7..f2ef5bd996a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -223,6 +223,7 @@ nav: - janssen-server/fido/README.md - Properties: janssen-server/fido/fido2-server-properties-config.md - Vendor Metadata: janssen-server/fido/vendor-metadata.md + - Trust Diagnostics: janssen-server/fido/trust-diagnostics.md - Conditional UI and Fallback: janssen-server/fido/conditional-ui-and-fallback.md - Passkey Telemetry: janssen-server/fido/passkey-telemetry.md - SAML: