From 60fe22733f75f6b2927e959b77c952a4b9a1a333 Mon Sep 17 00:00:00 2001 From: shekhar16 Date: Tue, 27 Aug 2024 02:18:00 +0530 Subject: [PATCH] feat(jans-fido): changes to refactor metadataservers #9111 Signed-off-by: shekhar16 --- .../fido2/model/conf/Fido2Configuration.java | 18 ++-- .../jans/fido2/model/conf/MetadataServer.java | 43 ++++++++ .../io/jans/fido2/service/Fido2Service.java | 56 ++++++++++ .../service/mds/FetchMdsProviderService.java | 28 ++--- .../io/jans/fido2/service/mds/TocService.java | 101 +++++++++++++++--- .../mds/FetchMdsProviderServiceTest.java | 46 +------- 6 files changed, 205 insertions(+), 87 deletions(-) create mode 100644 jans-fido2/model/src/main/java/io/jans/fido2/model/conf/MetadataServer.java create mode 100644 jans-fido2/server/src/main/java/io/jans/fido2/service/Fido2Service.java diff --git a/jans-fido2/model/src/main/java/io/jans/fido2/model/conf/Fido2Configuration.java b/jans-fido2/model/src/main/java/io/jans/fido2/model/conf/Fido2Configuration.java index 3602caf65a8..8e9ce9f9a6e 100644 --- a/jans-fido2/model/src/main/java/io/jans/fido2/model/conf/Fido2Configuration.java +++ b/jans-fido2/model/src/main/java/io/jans/fido2/model/conf/Fido2Configuration.java @@ -46,7 +46,7 @@ public class Fido2Configuration { private List requestedParties = new ArrayList(); @DocProperty(description = "String value to provide source of URLs with external metadata") - private String metadataUrlsProvider; + private List metadataServers = new ArrayList(); @DocProperty(description = "Boolean value indicating whether the MDS download should be omitted") private boolean disableMetadataService = false; @DocProperty(description = "Boolean value indicating whether MDS validation should be omitted during attestation") @@ -117,14 +117,6 @@ public void setRequestedParties(List requestedParties) { this.requestedParties = requestedParties; } - public String getMetadataUrlsProvider() { - return metadataUrlsProvider; - } - - public void setMetadataUrlsProvider(String metadataUrlsProvider) { - this.metadataUrlsProvider = metadataUrlsProvider; - } - public boolean isSkipValidateMdsInAttestationEnabled() { return skipValidateMdsInAttestationEnabled; } @@ -172,4 +164,12 @@ public boolean isDisableMetadataService() { public void setDisableMetadataService(boolean disableMetadataService) { this.disableMetadataService = disableMetadataService; } + + public List getMetadataServers() { + return metadataServers; + } + + public void setMetadataServers(List metadataServers) { + this.metadataServers = metadataServers; + } } diff --git a/jans-fido2/model/src/main/java/io/jans/fido2/model/conf/MetadataServer.java b/jans-fido2/model/src/main/java/io/jans/fido2/model/conf/MetadataServer.java new file mode 100644 index 00000000000..80948d01a31 --- /dev/null +++ b/jans-fido2/model/src/main/java/io/jans/fido2/model/conf/MetadataServer.java @@ -0,0 +1,43 @@ +/* + * 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.conf; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.util.ArrayList; +import java.util.List; + +/** + * Supported MetadataServer + * + * @author Shekhar L. on 06/08/2024 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class MetadataServer { + + private String url; + + private List certificateDocumentInum = new ArrayList();; + + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + + public List getCertificateDocumentInum() { + return certificateDocumentInum; + } + + public void setCertificateDocumentInum(List certificateDocumentInum) { + this.certificateDocumentInum = certificateDocumentInum; + } +} diff --git a/jans-fido2/server/src/main/java/io/jans/fido2/service/Fido2Service.java b/jans-fido2/server/src/main/java/io/jans/fido2/service/Fido2Service.java new file mode 100644 index 00000000000..88b11c65405 --- /dev/null +++ b/jans-fido2/server/src/main/java/io/jans/fido2/service/Fido2Service.java @@ -0,0 +1,56 @@ +/* + * 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; + +import io.jans.fido2.model.conf.AppConfiguration; +import io.jans.fido2.model.conf.Conf; +import io.jans.fido2.service.app.ConfigurationFactory; +import io.jans.orm.PersistenceEntryManager; +import io.jans.orm.exception.BasePersistenceException; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.slf4j.Logger; + +@ApplicationScoped +public class Fido2Service { + + @Inject + Logger logger; + + @Inject + PersistenceEntryManager persistenceManager; + + @Inject + ConfigurationFactory configurationFactory; + + public Conf findConf() { + try { + String configurationDn = configurationFactory.getBaseConfiguration() + .getString("fido2_ConfigurationEntryDN"); + return persistenceManager.find(Conf.class, configurationDn); + } catch (BasePersistenceException var3) { + logger.error("Failed to load Fido2 configuration from LDAP"); + return null; + } + } + + public AppConfiguration find() { + final Conf conf = findConf(); + return conf.getDynamicConf(); + } + + public void mergeConf(Conf conf) { + conf.setRevision(conf.getRevision() + 1); + persistenceManager.merge(conf); + } + + public void merge(AppConfiguration fido2ConfigJson) { + Conf conf = this.findConf(); + conf.setDynamicConf(fido2ConfigJson); + mergeConf(conf); + } +} diff --git a/jans-fido2/server/src/main/java/io/jans/fido2/service/mds/FetchMdsProviderService.java b/jans-fido2/server/src/main/java/io/jans/fido2/service/mds/FetchMdsProviderService.java index b64545df5ec..cc43b870356 100644 --- a/jans-fido2/server/src/main/java/io/jans/fido2/service/mds/FetchMdsProviderService.java +++ b/jans-fido2/server/src/main/java/io/jans/fido2/service/mds/FetchMdsProviderService.java @@ -1,25 +1,18 @@ package io.jans.fido2.service.mds; -import com.fasterxml.jackson.core.JsonProcessingException; import io.jans.fido2.exception.mds.MdsClientException; import io.jans.fido2.model.conf.AppConfiguration; -import io.jans.fido2.model.mds.MdsGetEndpointResponse; import io.jans.fido2.service.DataMapperService; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.ws.rs.client.Client; import jakarta.ws.rs.client.ClientBuilder; -import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.client.WebTarget; -import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.apache.commons.lang3.StringUtils; import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; import org.slf4j.Logger; -import java.util.Collections; -import java.util.Map; - @ApplicationScoped public class FetchMdsProviderService { @@ -40,23 +33,16 @@ public class FetchMdsProviderService { * @return MetadataTestResponse class * @throws MdsClientException When an attempt is made to process the json or the status returns other than 200 */ - public MdsGetEndpointResponse fetchMdsV3Endpoints(String endpoint) throws MdsClientException { + public String fetchMdsV3Endpoints(String endpoint) throws MdsClientException { Client client = clientBuilder.build(); - WebTarget target = client.target(endpoint + "/getEndpoints"); - Map body = Collections.singletonMap("endpoint", appConfiguration.getBaseEndpoint()); + WebTarget target = client.target(endpoint); try { - log.debug("Fetch mds getEndpoints request, body: {}", dataMapperService.writeValueAsString(body)); - Response response = target.request().post(Entity.entity(body, MediaType.APPLICATION_JSON_TYPE)); - String responseBody = response.readEntity(String.class); - log.debug("Fetch mds getEndpoints response, body: {}", responseBody); - MdsGetEndpointResponse responseEntity = dataMapperService.readValueString(responseBody, MdsGetEndpointResponse.class); - if (!responseEntity.getStatus().equalsIgnoreCase("ok")) { - throw new MdsClientException(String.format("Error getting endpoints from mds test, status: %s, errorMessage: '%s'", responseEntity.getStatus(), responseEntity.getErrorMessage())); + Response response = target.request().get(); + if (response.getStatus() != 200) { + throw new MdsClientException(String.format("Error getting endpoints from mds test, status: %s, errorMessage: '%s'", response.getStatus(), response.getStatusInfo().getReasonPhrase())); } - return responseEntity; - } catch (JsonProcessingException e) { - log.error("Error when processing json: {}", e.getMessage(), e); - throw new MdsClientException("Error when processing json: " + e.getMessage()); + String responseBody = response.readEntity(String.class); + return responseBody; } finally { client.close(); } diff --git a/jans-fido2/server/src/main/java/io/jans/fido2/service/mds/TocService.java b/jans-fido2/server/src/main/java/io/jans/fido2/service/mds/TocService.java index 8736cca8751..e45490dc4a0 100644 --- a/jans-fido2/server/src/main/java/io/jans/fido2/service/mds/TocService.java +++ b/jans-fido2/server/src/main/java/io/jans/fido2/service/mds/TocService.java @@ -7,6 +7,7 @@ package io.jans.fido2.service.mds; import com.fasterxml.jackson.databind.JsonNode; +import com.google.api.client.util.ArrayMap; import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSObject; @@ -20,12 +21,16 @@ import io.jans.fido2.exception.mds.MdsClientException; import io.jans.fido2.model.conf.AppConfiguration; import io.jans.fido2.model.conf.Fido2Configuration; -import io.jans.fido2.model.mds.MdsGetEndpointResponse; +import io.jans.fido2.model.conf.MetadataServer; import io.jans.fido2.service.Base64Service; import io.jans.fido2.service.CertificateService; import io.jans.fido2.service.DataMapperService; +import io.jans.fido2.service.Fido2Service; +import io.jans.fido2.service.app.ConfigurationFactory; import io.jans.fido2.service.verifier.CertificateVerifier; import io.jans.service.cdi.event.ApplicationInitialized; +import io.jans.service.document.store.service.DBDocumentService; +import io.jans.service.document.store.service.Document; import io.jans.util.Pair; import io.jans.util.StringHelper; import jakarta.enterprise.context.ApplicationScoped; @@ -76,10 +81,19 @@ public class TocService { @Inject private AppConfiguration appConfiguration; + + @Inject + private ConfigurationFactory configurationFactory; @Inject private FetchMdsProviderService fetchMdsProviderService; + @Inject + private DBDocumentService dbDocumentService; + + @Inject + private Fido2Service fido2Service; + private Map tocEntries; private LocalDate nextUpdate; @@ -286,28 +300,40 @@ public boolean downloadMdsFromServer(URL metadataUrl) { } private void loadMetadataServiceExternalProvider() { - String metadataUrlsProvider = appConfiguration.getFido2Configuration().getMetadataUrlsProvider(); - if (metadataUrlsProvider != null && !metadataUrlsProvider.trim().isEmpty()) { - log.debug("MetadataUrlsProvider found: {}", metadataUrlsProvider); + List metadataServers = appConfiguration.getFido2Configuration().getMetadataServers(); + Map> updatedmetadataServers = new HashMap<>(); + if (metadataServers != null && !metadataServers.isEmpty()) { + log.debug("metadataServers found: {}", metadataServers.size()); try { - MdsGetEndpointResponse mdsGetEndpointResponse = fetchMdsProviderService.fetchMdsV3Endpoints(metadataUrlsProvider); - Fido2Configuration fido2Configuration = appConfiguration.getFido2Configuration(); - String mdsTocRootCertsFolder = fido2Configuration.getMdsCertsFolder(); - List> entryList = new ArrayList<>(); - for (String mdsUrl : mdsGetEndpointResponse.getResult()) { - String blobJwt = fetchMdsProviderService.fetchMetadataBlob(mdsUrl); - if (blobJwt == null) { - continue; - } + for(MetadataServer metadataServer : metadataServers) { + String blobJWT = fetchMdsProviderService.fetchMdsV3Endpoints(metadataServer.getUrl()); + Fido2Configuration fido2Configuration = appConfiguration.getFido2Configuration(); + String mdsTocRootCertsFolder = fido2Configuration.getMdsCertsFolder(); + List documentsId = saveMetadataServerCertsInDB(metadataServer.getUrl(), blobJWT); + updatedmetadataServers.put(metadataServer.getUrl(),documentsId); + List> entryList = new ArrayList<>(); try { - Pair> dateMapPair = readEntriesFromTocJWT(blobJwt, mdsTocRootCertsFolder, false); + Pair> dateMapPair = readEntriesFromTocJWT(blobJWT, mdsTocRootCertsFolder, false); entryList.add(dateMapPair.getSecond()); } catch (Fido2RuntimeException e) { log.error(e.getMessage()); } + this.tocEntries.putAll(mergeAndResolveDuplicateEntries(entryList)); + log.info("🔐 MedataUrlsProvider successfully loaded"); } - this.tocEntries.putAll(mergeAndResolveDuplicateEntries(entryList)); - log.info("🔐 MedataUrlsProvider successfully loaded"); + + List metadataServerList = new ArrayList<>(); + + for(String metadataserverurl : updatedmetadataServers.keySet()){ + MetadataServer metadataServer = new MetadataServer(); + metadataServer.setUrl(metadataserverurl); + metadataServer.setCertificateDocumentInum(updatedmetadataServers.get(metadataserverurl)); + metadataServerList.add(metadataServer); + } + + AppConfiguration updateAppConfiguration = configurationFactory.getAppConfiguration(); + updateAppConfiguration.getFido2Configuration().setMetadataServers(metadataServerList); + fido2Service.merge(updateAppConfiguration); } catch (MdsClientException e) { log.error(e.getMessage()); @@ -317,6 +343,49 @@ private void loadMetadataServiceExternalProvider() { } } + public List saveMetadataServerCertsInDB(String metadataServer, String blobJWT) { + List result = new ArrayList<>(); + log.debug("Attempting reading entries from JWT: {}", StringUtils.abbreviateMiddle(blobJWT, "...", 100)); + JWSObject blobDecoded; + try { + blobDecoded = JWSObject.parse(blobJWT); + } catch (ParseException e) { + throw new Fido2RuntimeException("Error when parsing TOC JWT: " + e.getMessage(), e); + } + List headerCertificatesX5c = blobDecoded.getHeader().getX509CertChain().stream() + .map(c -> base64Service.encodeToString(c.decode())) + .collect(Collectors.toList()); + int index = 0; + if (!headerCertificatesX5c.isEmpty()){ + List oldCerts = dbDocumentService.searchDocuments(metadataServer, 100); + for (Document certDoc : oldCerts) { + try { + dbDocumentService.removeDocument(certDoc); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + for (String cert : headerCertificatesX5c) { + Document document = new Document(); + document.setDisplayName(metadataServer + "_" + (index++)); + document.setDescription("metadata certificate for " + metadataServer); + document.setJansService(new ArrayList<>(Arrays.asList("Fido2 MDS"))); + try { + document.setDocument(cert); + document.setInum(dbDocumentService.generateInumForNewDocument()); + document.setDn(dbDocumentService.getDnForDocument(document.getInum())); + document.setJansEnabled(true); + dbDocumentService.addDocument(document); + result.add(document.getInum()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + return result; + } + private Pair> readEntriesFromTocJWT(String tocJwt, String mdsTocRootCertsFolder, boolean loadGlobalVariables) { log.debug("Attempting reading entries from JWT: {}", StringUtils.abbreviateMiddle(tocJwt, "...", 100)); JWSObject blobDecoded; diff --git a/jans-fido2/server/src/test/java/io/jans/fido2/service/mds/FetchMdsProviderServiceTest.java b/jans-fido2/server/src/test/java/io/jans/fido2/service/mds/FetchMdsProviderServiceTest.java index f543913f887..1dc7cb68e3d 100644 --- a/jans-fido2/server/src/test/java/io/jans/fido2/service/mds/FetchMdsProviderServiceTest.java +++ b/jans-fido2/server/src/test/java/io/jans/fido2/service/mds/FetchMdsProviderServiceTest.java @@ -7,7 +7,9 @@ import io.jans.fido2.model.mds.MdsGetEndpointResponse; import io.jans.fido2.service.DataMapperService; import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.client.WebTarget; import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import org.apache.commons.lang3.StringUtils; import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; import org.junit.jupiter.api.Assertions; @@ -40,51 +42,13 @@ class FetchMdsProviderServiceTest { @Mock private DataMapperService dataMapperService; - @Test - void fetchMdsV3Endpoints_withValidEndpoint_valid() throws MdsClientException, JsonProcessingException { - String endpoint = "https://mds3.fido.tools/"; - String baseEndpoint = "https://base-fido-serverfido.test.url"; - when(appConfiguration.getBaseEndpoint()).thenReturn(baseEndpoint); - when(dataMapperService.writeValueAsString(any())).thenReturn("{\"endpoint\":\"" + baseEndpoint + "\"}"); - MdsGetEndpointResponse mdsGetEndpointResponse = new MdsGetEndpointResponse(); - mdsGetEndpointResponse.setStatus("ok"); - mdsGetEndpointResponse.setResult(Arrays.asList( - "https://mds3.fido.tools/execute/5c5f5c14e804288bead66d5fd5c54fbb7c773b8d6786279e708e842554e70a29", - "https://mds3.fido.tools/execute/0cb1551242b4a32d5e7e7cbac3db8d175f03c5ca71f2374417eb9ebee922523a", - "https://mds3.fido.tools/execute/a4ab3537c91c901902f2b7f9ea6540723a7c068382bb79acb3ed310a51831e36", - "https://mds3.fido.tools/execute/3798ae5a0d2cd99a928604a462d475cd555014c244a44ba0cb1b61fc644b7b2b", - "https://mds3.fido.tools/execute/02f495ce54cc6ffcbd4b50f0c539bf8b76c8feee3e17c28445c52cfc3baf0852" - )); - when(dataMapperService.readValueString(any(), eq(MdsGetEndpointResponse.class))).thenReturn(mdsGetEndpointResponse); - - MdsGetEndpointResponse response = fetchMdsProviderService.fetchMdsV3Endpoints(endpoint); - assertNotNull(response); - assertNotNull(response.getStatus()); - assertNotNull(response.getResult()); - assertEquals(response.getStatus(), "ok"); - assertFalse(response.getResult().isEmpty()); - response.getResult().forEach(Assertions::assertNotNull); - - verify(log, times(2)).debug(contains("Fetch mds getEndpoints"), anyString()); - } @Test - void fetchMdsV3Endpoints_withEmptyEndpoint_mdsClientException() throws JsonProcessingException { + void fetchMdsV3Endpoints_withValidEndpoint_valid() throws MdsClientException, JsonProcessingException { String endpoint = "https://mds3.fido.tools/"; - String baseEndpoint = "https://base-fido-serverfido.test.url"; - when(appConfiguration.getBaseEndpoint()).thenReturn(baseEndpoint); - when(dataMapperService.writeValueAsString(any())).thenReturn("{\"endpoint\":\"" + baseEndpoint + "\"}"); - MdsGetEndpointResponse mdsGetEndpointResponse = new MdsGetEndpointResponse(); - mdsGetEndpointResponse.setStatus("failed"); - mdsGetEndpointResponse.setErrorMessage("Request missing endpoint field!"); - when(dataMapperService.readValueString(any(), eq(MdsGetEndpointResponse.class))).thenReturn(mdsGetEndpointResponse); - - MdsClientException response = assertThrows(MdsClientException.class, () -> fetchMdsProviderService.fetchMdsV3Endpoints(endpoint)); + String response = fetchMdsProviderService.fetchMdsV3Endpoints(endpoint); assertNotNull(response); - assertNotNull(response.getMessage()); - assertTrue(response.getMessage().contains("Error getting endpoints")); - - verify(log, times(2)).debug(contains("Fetch mds getEndpoints"), anyString()); + verify(log, times(1)).debug(contains("Fetch mds getEndpoints response, body:"), anyString()); } @Test