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/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/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 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"); + } +} 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: