diff --git a/sdk/keyvault/azure-security-keyvault-jca/pom.xml b/sdk/keyvault/azure-security-keyvault-jca/pom.xml index f497dcaf0550..eb23672914bd 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/pom.xml +++ b/sdk/keyvault/azure-security-keyvault-jca/pom.xml @@ -185,6 +185,12 @@ slf4j-nop 1.7.30 + + org.mockito + mockito-core + 3.6.28 + test + diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/AzureCertificates.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/AzureCertificates.java new file mode 100644 index 000000000000..7ff549725bf2 --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/AzureCertificates.java @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.security.keyvault.jca; + +import java.security.Key; +import java.security.cert.Certificate; +import java.util.List; +import java.util.Map; + +/** + * Store Azure Certificates + */ +public interface AzureCertificates { + + /** + * Get certificate aliases. + * @return certificate aliases + */ + List getAliases(); + + /** + * Get certificates. + * @return certificates + */ + Map getCertificates(); + + /** + * Get certificate keys. + * @return certificate keys + */ + Map getCertificateKeys(); + + /** + * Delete certificate info by alias if exits + * @param alias certificate alias + */ + void deleteEntry(String alias); +} diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/ClasspathCertificates.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/ClasspathCertificates.java new file mode 100644 index 000000000000..6e0ef8f6ea12 --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/ClasspathCertificates.java @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.security.keyvault.jca; + +import java.security.Key; +import java.security.cert.Certificate; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Store certificates loaded from classpath. + */ +public class ClasspathCertificates implements AzureCertificates { + + /** + * Store certificates' alias. + */ + private final List aliases = new ArrayList<>(); + + /** + * Stores the certificates by alias. + */ + private final Map certificates = new HashMap<>(); + + /** + * Stores the certificate keys by alias. + */ + private final Map certificateKeys = new HashMap<>(); + + /** + * Get certificate aliases. + * @return certificate aliases + */ + @Override + public List getAliases() { + return aliases; + } + + /** + * Get certificates. + * @return certificates + */ + @Override + public Map getCertificates() { + return certificates; + } + + /** + * Get certificate keys. + * @return certificate keys + */ + @Override + public Map getCertificateKeys() { + return certificateKeys; + } + + /** + * Remove alias if exist. + * @param alias certificate alias + */ + public void removeAlias(String alias) { + aliases.remove(alias); + } + + /** + * Remove certificate if exist. + * @param alias certificate alias + */ + public void removeCertificate(String alias) { + certificates.remove(alias); + } + + /** + * Add certificate. + * @param alias certificate alias + * @param certificate certificate + */ + public void setCertificateEntry(String alias, Certificate certificate) { + if (!aliases.contains(alias)) { + aliases.add(alias); + certificates.put(alias, certificate); + } + } + + /** + * Delete certificate info by alias if exits + * @param alias certificate alias + */ + @Override + public void deleteEntry(String alias) { + aliases.remove(alias); + certificates.remove(alias); + certificateKeys.remove(alias); + } + +} diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultCertificates.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultCertificates.java new file mode 100644 index 000000000000..850d3ee75809 --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultCertificates.java @@ -0,0 +1,161 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.security.keyvault.jca; + +import java.security.Key; +import java.security.cert.Certificate; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Date; +import java.util.Collections; +import java.util.Objects; + +/** + * Store certificates loaded from KeyVault. + */ +public class KeyVaultCertificates implements AzureCertificates { + + /** + * Stores the list of aliases. + */ + private List aliases = new ArrayList<>(); + + /** + * Stores the certificates by alias. + */ + private final Map certificates = new HashMap<>(); + + /** + * Stores the certificate keys by alias. + */ + private final Map certificateKeys = new HashMap<>(); + + /** + * Stores the last time refresh certificates and alias + */ + private Date lastRefreshTime; + + /** + * Stores the last force refresh time. + */ + private static volatile Date lastForceRefreshTime = new Date(); + + private KeyVaultClient keyVaultClient; + + private final long refreshInterval; + + void setKeyVaultClient(KeyVaultClient keyVaultClient) { + this.keyVaultClient = keyVaultClient; + } + + KeyVaultCertificates(long refreshInterval, KeyVaultClient keyVaultClient) { + this.refreshInterval = refreshInterval; + this.keyVaultClient = keyVaultClient; + } + + boolean certificatesNeedRefresh() { + if (lastRefreshTime == null || lastForceRefreshTime.after(lastRefreshTime)) { + return true; + } + if (refreshInterval > 0) { + return lastRefreshTime.getTime() + refreshInterval < new Date().getTime(); + } + return false; + } + + /** + * Get certificate aliases. + * @return certificate aliases + */ + @Override + public List getAliases() { + refreshCertificatesIfNeeded(); + return aliases; + } + + /** + * Get certificates. + * @return certificates + */ + @Override + public Map getCertificates() { + refreshCertificatesIfNeeded(); + return certificates; + } + + /** + * Get certificates. + * @return certificate keys + */ + @Override + public Map getCertificateKeys() { + refreshCertificatesIfNeeded(); + return certificateKeys; + } + + private void refreshCertificatesIfNeeded() { + if (certificatesNeedRefresh()) { + refreshCertificates(); + } + } + + private void refreshCertificates() { + aliases = keyVaultClient.getAliases(); + certificateKeys.clear(); + certificates.clear(); + Optional.ofNullable(aliases) + .orElse(Collections.emptyList()) + .forEach(alias -> { + Key key = keyVaultClient.getKey(alias, null); + if (!Objects.isNull(key)) { + certificateKeys.put(alias, key); + } + Certificate certificate = keyVaultClient.getCertificate(alias); + if (!Objects.isNull(certificate)) { + certificates.put(alias, certificate); + } + }); + lastRefreshTime = new Date(); + } + + /** + * Get latest alias by certificate which in portal + * @param certificate certificate got + * @return certificate' alias if exist. + */ + String refreshAndGetAliasByCertificate(Certificate certificate) { + updateLastForceRefreshTime(); + return getCertificates().entrySet() + .stream() + .filter(entry -> certificate.equals(entry.getValue())) + .findFirst() + .map(Map.Entry::getKey) + .orElse(null); + + } + + /** + * Delete certificate info by alias if exits + * @param alias deleted certificate + */ + @Override + public void deleteEntry(String alias) { + if (aliases != null) { + aliases.remove(alias); + } + certificates.remove(alias); + certificateKeys.remove(alias); + } + + /** + * Overall refresh certificates' info + */ + public static void updateLastForceRefreshTime() { + lastForceRefreshTime = new Date(); + } + +} diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultKeyStore.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultKeyStore.java index 4de4d7293aaf..a84597b0a3b4 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultKeyStore.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultKeyStore.java @@ -23,10 +23,14 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Date; +import java.util.Objects; import java.util.Enumeration; -import java.util.HashMap; import java.util.List; +import java.util.Collection; +import java.util.Optional; import java.util.logging.Logger; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; @@ -52,19 +56,14 @@ public final class KeyVaultKeyStore extends KeyStoreSpi { private static final Logger LOGGER = Logger.getLogger(KeyVaultKeyStore.class.getName()); /** - * Stores the list of aliases. + * Store certificates loaded from classpath. */ - private List aliases; + private final ClasspathCertificates classpathCertificates; /** - * Stores the certificates by alias. + * Store certificates loaded from KeyVault. */ - private final HashMap certificates = new HashMap<>(); - - /** - * Stores the certificate keys by alias. - */ - private final HashMap certificateKeys = new HashMap<>(); + private final KeyVaultCertificates keyVaultCertificates; /** * Stores the creation date. @@ -76,13 +75,15 @@ public final class KeyVaultKeyStore extends KeyStoreSpi { */ private KeyVaultClient keyVaultClient; + private final boolean refreshCertificatesWhenHaveUnTrustCertificate; + /** * Constructor. * *

* The constructor uses System.getProperty for - * azure.keyvault.uri, - * azure.keyvault.aadAuthenticationUrl, + * azure.keyvault.uri, + * azure.keyvault.aadAuthenticationUrl, * azure.keyvault.tenantId, * azure.keyvault.clientId, * azure.keyvault.clientSecret and @@ -102,14 +103,24 @@ public KeyVaultKeyStore() { } else { keyVaultClient = new KeyVaultClient(keyVaultUri, managedIdentity); } + long refreshInterval = Optional.ofNullable(System.getProperty("azure.keyvault.jca.certificates-refresh-interval")) + .map(Long::valueOf) + .orElse(0L); + refreshCertificatesWhenHaveUnTrustCertificate = Optional.ofNullable(System.getProperty("azure.keyvault.jca.refresh-certificates-when-have-un-trust-certificate")) + .map(Boolean::parseBoolean) + .orElse(false); + keyVaultCertificates = new KeyVaultCertificates(refreshInterval, keyVaultClient); + classpathCertificates = new ClasspathCertificates(); } @Override public Enumeration engineAliases() { - if (aliases == null) { - aliases = keyVaultClient.getAliases(); - } - return Collections.enumeration(aliases); + List aliasList = Stream.of(keyVaultCertificates, classpathCertificates) + .map(AzureCertificates::getAliases) + .flatMap(Collection::stream) + .distinct().collect(Collectors.toList()); + + return Collections.enumeration(aliasList); } @Override @@ -119,6 +130,8 @@ public boolean engineContainsAlias(String alias) { @Override public void engineDeleteEntry(String alias) { + keyVaultCertificates.deleteEntry(alias); + classpathCertificates.deleteEntry(alias); } @Override @@ -128,20 +141,16 @@ public boolean engineEntryInstanceOf(String alias, Class a.containsKey(alias)) + .findFirst() + .map(certificates -> certificates.get(alias)) + .orElse(null); + + if (refreshCertificatesWhenHaveUnTrustCertificate && certificate == null) { + KeyVaultCertificates.updateLastForceRefreshTime(); + certificate = keyVaultCertificates.getCertificates().get(alias); } return certificate; } @@ -150,10 +159,13 @@ public Certificate engineGetCertificate(String alias) { public String engineGetCertificateAlias(Certificate cert) { String alias = null; if (cert != null) { - if (aliases == null) { - aliases = keyVaultClient.getAliases(); - } - for (String candidateAlias : aliases) { + List aliasList = Stream.of(keyVaultCertificates, classpathCertificates) + .map(AzureCertificates::getAliases) + .flatMap(Collection::stream) + .distinct() + .collect(Collectors.toList()); + + for (String candidateAlias : aliasList) { Certificate certificate = engineGetCertificate(candidateAlias); if (certificate.equals(cert)) { alias = candidateAlias; @@ -161,6 +173,9 @@ public String engineGetCertificateAlias(Certificate cert) { } } } + if (refreshCertificatesWhenHaveUnTrustCertificate && alias == null) { + alias = keyVaultCertificates.refreshAndGetAliasByCertificate(cert); + } return alias; } @@ -187,30 +202,21 @@ public KeyStore.Entry engineGetEntry(String alias, KeyStore.ProtectionParameter @Override public Key engineGetKey(String alias, char[] password) { - Key key; - if (certificateKeys.containsKey(alias)) { - key = certificateKeys.get(alias); - } else { - key = keyVaultClient.getKey(alias, password); - if (key != null) { - certificateKeys.put(alias, key); - if (aliases == null) { - aliases = keyVaultClient.getAliases(); - } - if (!aliases.contains(alias)) { - aliases.add(alias); - } - } - } - return key; + return Stream.of(keyVaultCertificates, classpathCertificates) + .map(AzureCertificates::getCertificateKeys) + .filter(a -> a.containsKey(alias)) + .findFirst() + .map(certificateKeys -> certificateKeys.get(alias)) + .orElse(null); } @Override public boolean engineIsCertificateEntry(String alias) { - if (aliases == null) { - aliases = keyVaultClient.getAliases(); - } - return aliases.contains(alias); + return Stream.of(keyVaultCertificates, classpathCertificates) + .map(AzureCertificates::getAliases) + .flatMap(Collection::stream) + .distinct() + .anyMatch(a -> Objects.equals(a, alias)); } @Override @@ -236,24 +242,31 @@ public void engineLoad(KeyStore.LoadStoreParameter param) { } else { keyVaultClient = new KeyVaultClient(parameter.getUri()); } + keyVaultCertificates.setKeyVaultClient(keyVaultClient); } - sideLoad(); + loadCertificatesFromClasspath(); } @Override public void engineLoad(InputStream stream, char[] password) { - sideLoad(); + loadCertificatesFromClasspath(); } @Override public void engineSetCertificateEntry(String alias, Certificate certificate) { - if (aliases == null) { - aliases = keyVaultClient.getAliases(); - } - if (!aliases.contains(alias)) { - aliases.add(alias); - certificates.put(alias, certificate); + if (keyVaultCertificates.getAliases().contains(alias)) { + return; } + engineSetClasspathCertificateEntry(alias, certificate); + } + + /** + * Store alias and certificates to Classpath + * @param alias Classpath certificate's alias + * @param certificate Classpath certificate + */ + public void engineSetClasspathCertificateEntry(String alias, Certificate certificate) { + classpathCertificates.setCertificateEntry(alias, certificate); } @Override @@ -271,7 +284,12 @@ public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) { @Override public int engineSize() { - return aliases != null ? aliases.size() : 0; + return Stream.of(keyVaultCertificates, classpathCertificates) + .map(AzureCertificates::getAliases) + .flatMap(Collection::stream) + .distinct() + .collect(Collectors.toList()) + .size(); } @Override @@ -330,7 +348,7 @@ private byte[] readAllBytes(InputStream inputStream) throws IOException { /** * Side-load certificate from classpath. */ - private void sideLoad() { + private void loadCertificatesFromClasspath() { try { String[] filenames = getFilenames("/keyvault"); if (filenames.length > 0) { @@ -346,7 +364,7 @@ private void sideLoad() { CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509Certificate certificate = (X509Certificate) cf.generateCertificate( new ByteArrayInputStream(bytes)); - engineSetCertificateEntry(alias, certificate); + engineSetClasspathCertificateEntry(alias, certificate); LOGGER.log(INFO, "Side loaded certificate: {0} from: {1}", new Object[]{alias, filename}); } catch (CertificateException e) { diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultTrustManager.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultTrustManager.java index 8a40ed8c72cc..6c7083408812 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultTrustManager.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultTrustManager.java @@ -39,6 +39,13 @@ public class KeyVaultTrustManager extends X509ExtendedTrustManager { */ private KeyStore keyStore; + /** + * Constructor. + */ + public KeyVaultTrustManager() { + this(null); + } + /** * Constructor. * diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultTrustManagerFactory.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultTrustManagerFactory.java index 1ac87af72425..6aa7ee23246a 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultTrustManagerFactory.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultTrustManagerFactory.java @@ -33,8 +33,10 @@ protected void engineInit(KeyStore keystore) { } @Override + //TODO: enable create KeyVaultTrustManager with ManagerFactoryParameters protected void engineInit(ManagerFactoryParameters spec) { LOGGER.entering("KeyVaultKeyManagerFactory", "engineInit", spec); + trustManagers.add(new KeyVaultTrustManager()); } @Override diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/ClasspathCertificatesTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/ClasspathCertificatesTest.java new file mode 100644 index 000000000000..59b77205f040 --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/ClasspathCertificatesTest.java @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.security.keyvault.jca; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.security.Key; +import java.security.cert.Certificate; + +import static org.mockito.Mockito.mock; + +public class ClasspathCertificatesTest { + + private Key key = mock(Key.class); + + private Certificate certificate = mock(Certificate.class); + + private ClasspathCertificates classpathCertificates; + + @Test + public void testSetCertificateEntry() { + classpathCertificates = new ClasspathCertificates(); + classpathCertificates.setCertificateEntry("myalias", certificate); + Assertions.assertTrue(classpathCertificates.getAliases().contains("myalias")); + Assertions.assertEquals(classpathCertificates.getCertificates().get("myalias"), certificate); + } + + @Test + public void testRemoveCertificate() { + classpathCertificates = new ClasspathCertificates(); + classpathCertificates.setCertificateEntry("myalias", certificate); + classpathCertificates.removeCertificate("myalias"); + Assertions.assertNull(classpathCertificates.getCertificates().get("myalias")); + } + + @Test + public void testRemoveAlias() { + classpathCertificates = new ClasspathCertificates(); + classpathCertificates.setCertificateEntry("myalias", certificate); + classpathCertificates.removeAlias("myalias"); + Assertions.assertFalse(classpathCertificates.getAliases().contains("myalias")); + } + +} diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultCertificatesTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultCertificatesTest.java new file mode 100644 index 000000000000..98af68ebb76c --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultCertificatesTest.java @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.security.keyvault.jca; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.security.Key; +import java.security.cert.Certificate; +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class KeyVaultCertificatesTest { + + private final KeyVaultClient keyVaultClient = mock(KeyVaultClient.class); + + private Key key = mock(Key.class); + + private Certificate certificate = mock(Certificate.class); + + private KeyVaultCertificates keyVaultCertificates; + + @BeforeEach + public void beforeEach() { + List aliases = new ArrayList<>(); + aliases.add("myalias"); + when(keyVaultClient.getAliases()).thenReturn(aliases); + when(keyVaultClient.getKey("myalias", null)).thenReturn(key); + when(keyVaultClient.getCertificate("myalias")).thenReturn(certificate); + keyVaultCertificates = new KeyVaultCertificates(0, keyVaultClient); + } + + @Test + public void testGetAliases() { + Assertions.assertTrue(keyVaultCertificates.getAliases().contains("myalias")); + } + + @Test + public void testGetKey() { + Assertions.assertTrue(keyVaultCertificates.getCertificateKeys().containsValue(key)); + } + + @Test + public void testGetCertificate() { + Assertions.assertTrue(keyVaultCertificates.getCertificates().containsValue(certificate)); + } + + @Test + public void testRefreshAndGetAliasByCertificate() { + Assertions.assertEquals(keyVaultCertificates.refreshAndGetAliasByCertificate(certificate), "myalias"); + when(keyVaultClient.getAliases()).thenReturn(null); + Assertions.assertNotEquals(keyVaultCertificates.refreshAndGetAliasByCertificate(certificate), "myalias"); + } + + @Test + public void testDeleteAlias() { + Assertions.assertTrue(keyVaultCertificates.getAliases().contains("myalias")); + keyVaultCertificates.deleteEntry("myalias"); + Assertions.assertFalse(keyVaultCertificates.getAliases().contains("myalias")); + } + + @Test + public void testCertificatesNeedRefresh() throws InterruptedException { + keyVaultCertificates = new KeyVaultCertificates(1000, keyVaultClient); + Assertions.assertTrue(keyVaultCertificates.certificatesNeedRefresh()); + keyVaultCertificates.getAliases(); + Assertions.assertFalse(keyVaultCertificates.certificatesNeedRefresh()); + Thread.sleep(10); + KeyVaultCertificates.updateLastForceRefreshTime(); + Assertions.assertTrue(keyVaultCertificates.certificatesNeedRefresh()); + keyVaultCertificates.getAliases(); + Assertions.assertFalse(keyVaultCertificates.certificatesNeedRefresh()); + Thread.sleep(2000); + Assertions.assertTrue(keyVaultCertificates.certificatesNeedRefresh()); + } + +} diff --git a/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultCertificatesTest.java b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultCertificatesTest.java new file mode 100644 index 000000000000..fb9663f1b8a2 --- /dev/null +++ b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultCertificatesTest.java @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.security.keyvault.jca; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; + +import java.io.ByteArrayInputStream; +import java.security.KeyStore; +import java.security.ProviderException; +import java.security.Security; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Base64; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * The JUnit test for the KeyVaultCertificates. + */ +@EnabledIfEnvironmentVariable(named = "AZURE_KEYVAULT_CERTIFICATE_NAME", matches = "myalias") +public class KeyVaultCertificatesTest { + + private static String certificateName; + + /** + * Stores the CER test certificate (which is valid til 2120). + */ + private static final String TEST_CERTIFICATE + = "MIIDeDCCAmCgAwIBAgIQGghBu97rQJKNnUHPWU7xjDANBgkqhkiG9w0BAQsFADAk" + + "MSIwIAYDVQQDExlodW5kcmVkLXllYXJzLmV4YW1wbGUuY29tMCAXDTIwMDkwMjE3" + + "NDUyNFoYDzIxMjAwOTAyMTc1NTI0WjAkMSIwIAYDVQQDExlodW5kcmVkLXllYXJz" + + "LmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuU14" + + "btkN5wmcO2WKXqm1NUKXzi79EtqiFFkrLgPAwj5NNwMw2Akm3GpdEpwkJ8/q3l7d" + + "frDEVOO9gwZbz7xppyqutjxjllw8CCgjFdfK02btz56CGgh3X25ZZtzPbuMZJM0j" + + "o4mVEdaFNJ0eUeMppS0DcbbuTWCF7Jf1gvr8GVqx+E0IJUFkE+D4kdTbnJSaeK0A" + + "KEt94z88MPX18h8ud14uRVmUCYVZrZeswdE2tO1BpazrXELHuXCtrjGxsDDjDzeP" + + "98aFI9kblkqoJS4TsmloLEjwZLm80cyJDEmpXXMtR7C0FFXFI1BAtIa4mxSgBLsT" + + "L4GVPEGNANR8COYkHQIDAQABo4GjMIGgMA4GA1UdDwEB/wQEAwIFoDAJBgNVHRME" + + "AjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAkBgNVHREEHTAbghlo" + + "dW5kcmVkLXllYXJzLmV4YW1wbGUuY29tMB8GA1UdIwQYMBaAFOGTt4H3ho30O4e+" + + "hebwJjm2VMvIMB0GA1UdDgQWBBThk7eB94aN9DuHvoXm8CY5tlTLyDANBgkqhkiG" + + "9w0BAQsFAAOCAQEAGp8mCioVCmM+kZv6r+K2j2uog1k4HBwN1NfRoSsibDB8+QXF" + + "bmNf3M0imiuR/KJgODyuROwaa/AalxNFMOP8XTL2YmP7XsddBs9ONHHQXKjY/Ojl" + + "PsIPR7vZjwYPfEB+XEKl2fOIxDQQ921POBV7M6DdTC49T5X+FsLR1AIIfinVetT9" + + "QmNuvzulBX0T0rea/qpcPK4HTj7ToyImOaf8sXRv2s2ODLUrKWu5hhTNH2l6RIkQ" + + "U/aIAdQRfDaSE9jhtcVu5d5kCgBs7nz5AzeCisDPo5zIt4Mxej3iVaAJ79oEbHOE" + + "p192KLXLV/pscA4Wgb+PJ8AAEa5B6xq8p9JO+Q=="; + + @BeforeAll + public static void setEnvironmentProperty() { + PropertyConvertorUtils.putEnvironmentPropertyToSystemProperty( + Arrays.asList("AZURE_KEYVAULT_URI", + "AZURE_KEYVAULT_TENANT_ID", + "AZURE_KEYVAULT_CLIENT_ID", + "AZURE_KEYVAULT_CLIENT_SECRET") + ); + Security.insertProviderAt(new KeyVaultJcaProvider(), 1); + certificateName = System.getenv("AZURE_KEYVAULT_CERTIFICATE_NAME"); + } + + @Test + public void testGetKeyStore() throws Exception { + KeyStore keyStore = PropertyConvertorUtils.getKeyVaultKeyStore(); + assertNotNull(keyStore.getCertificate(certificateName)); + assertTrue(keyStore.containsAlias(certificateName)); + X509Certificate certificate = getTestCertificate(); + + keyStore.setCertificateEntry("setcert", certificate); + assertNotNull(keyStore.getCertificateAlias(certificate), "setcert"); + } + + private X509Certificate getTestCertificate() { + X509Certificate certificate; + + try { + byte[] certificateBytes = Base64.getDecoder().decode(TEST_CERTIFICATE); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + certificate = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(certificateBytes)); + } catch (CertificateException e) { + throw new ProviderException(e); + } + return certificate; + } + + @Test + public void testCertificatesRefreshInterval() throws Exception { + System.setProperty("azure.keyvault.jca.certificates-refresh-interval", "1000"); + KeyStore keyStore = PropertyConvertorUtils.getKeyVaultKeyStore(); + assertNotNull(keyStore.getCertificate(certificateName)); + keyStore.deleteEntry(certificateName); + assertNull(keyStore.getCertificate(certificateName)); + Thread.sleep(2000); + assertNotNull(keyStore.getCertificate(certificateName)); + } + + @Test + public void testUpdateLastForceRefreshTime() throws Exception { + KeyStore keyStore = PropertyConvertorUtils.getKeyVaultKeyStore(); + assertNotNull(keyStore.getCertificate(certificateName)); + keyStore.deleteEntry(certificateName); + assertNull(keyStore.getCertificate(certificateName)); + Thread.sleep(10); + KeyVaultCertificates.updateLastForceRefreshTime(); + assertNotNull(keyStore.getCertificate(certificateName)); + } +} diff --git a/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultJcaProviderTest.java b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultJcaProviderTest.java index 17ca799bd4cf..2d52b338574f 100644 --- a/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultJcaProviderTest.java +++ b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultJcaProviderTest.java @@ -33,13 +33,7 @@ public void testGetCertificate() throws Exception { "AZURE_KEYVAULT_CLIENT_SECRET") ); Security.addProvider(new KeyVaultJcaProvider()); - KeyStore keystore = KeyStore.getInstance("AzureKeyVault"); - KeyVaultLoadStoreParameter parameter = new KeyVaultLoadStoreParameter( - System.getenv("AZURE_KEYVAULT_URI"), - System.getenv("AZURE_KEYVAULT_TENANT_ID"), - System.getenv("AZURE_KEYVAULT_CLIENT_ID"), - System.getenv("AZURE_KEYVAULT_CLIENT_SECRET")); - keystore.load(parameter); + KeyStore keystore = PropertyConvertorUtils.getKeyVaultKeyStore(); assertNotNull(keystore.getCertificate(System.getenv("AZURE_KEYVAULT_CERTIFICATE_NAME"))); } } diff --git a/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultKeyManagerTest.java b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultKeyManagerTest.java index a79e16534862..dc3229b1dcc9 100644 --- a/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultKeyManagerTest.java +++ b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultKeyManagerTest.java @@ -33,13 +33,7 @@ public static void setEnvironmentProperty() throws KeyStoreException, NoSuchAlgo "AZURE_KEYVAULT_CLIENT_SECRET") ); Security.insertProviderAt(new KeyVaultJcaProvider(), 1); - KeyStore keyStore = KeyStore.getInstance("AzureKeyVault"); - KeyVaultLoadStoreParameter parameter = new KeyVaultLoadStoreParameter( - System.getenv("AZURE_KEYVAULT_URI"), - System.getenv("AZURE_KEYVAULT_TENANT_ID"), - System.getenv("AZURE_KEYVAULT_CLIENT_ID"), - System.getenv("AZURE_KEYVAULT_CLIENT_SECRET")); - keyStore.load(parameter); + KeyStore keyStore = PropertyConvertorUtils.getKeyVaultKeyStore(); manager = new KeyVaultKeyManager(keyStore, null); certificateName = System.getenv("AZURE_KEYVAULT_CERTIFICATE_NAME"); } diff --git a/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultKeyStoreTest.java b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultKeyStoreTest.java index 47b910102e62..f2a18a9dba6c 100644 --- a/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultKeyStoreTest.java +++ b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultKeyStoreTest.java @@ -8,15 +8,17 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import java.io.ByteArrayInputStream; +import java.security.KeyStore; import java.security.ProviderException; +import java.security.Security; +import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Base64; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; /** * The JUnit tests for the KeyVaultKeyStore class. @@ -141,7 +143,9 @@ public void testEngineGetCreationDate() { @Test public void testEngineDeleteEntry() { KeyVaultKeyStore keystore = new KeyVaultKeyStore(); + assertTrue(keystore.engineContainsAlias(certificateName)); keystore.engineDeleteEntry(certificateName); + assertFalse(keystore.engineContainsAlias(certificateName)); } @Test @@ -155,4 +159,27 @@ public void testEngineStore() { KeyVaultKeyStore keystore = new KeyVaultKeyStore(); keystore.engineStore(null, null); } + + @Test + public void testRefreshEngineGetCertificate() throws Exception { + System.setProperty("azure.keyvault.jca.refresh-certificates-when-have-un-trust-certificate", "true"); + KeyVaultJcaProvider provider = new KeyVaultJcaProvider(); + Security.addProvider(provider); + KeyStore ks = PropertyConvertorUtils.getKeyVaultKeyStore(); + Certificate certificate = ks.getCertificate(certificateName); + ks.deleteEntry(certificateName); + Thread.sleep(10); + assertEquals(ks.getCertificateAlias(certificate), certificateName); + } + + @Test + public void testNotRefreshEngineGetCertificate() throws Exception { + KeyVaultJcaProvider provider = new KeyVaultJcaProvider(); + Security.addProvider(provider); + KeyStore ks = PropertyConvertorUtils.getKeyVaultKeyStore(); + Certificate certificate = ks.getCertificate(certificateName); + ks.deleteEntry(certificateName); + assertNull(ks.getCertificateAlias(certificate)); + } + } diff --git a/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/PropertyConvertorUtils.java b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/PropertyConvertorUtils.java index d9da4bea8829..0e960acfb906 100644 --- a/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/PropertyConvertorUtils.java +++ b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/PropertyConvertorUtils.java @@ -3,6 +3,12 @@ package com.azure.security.keyvault.jca; +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.util.Arrays; import java.util.List; public class PropertyConvertorUtils { @@ -17,4 +23,21 @@ public static void putEnvironmentPropertyToSystemProperty(List key) { } ); } + + public static final List SYSTEM_PROPERTIES = Arrays.asList("AZURE_KEYVAULT_URI", + "AZURE_KEYVAULT_TENANT_ID", + "AZURE_KEYVAULT_CLIENT_ID", + "AZURE_KEYVAULT_CLIENT_SECRET"); + + public static KeyStore getKeyVaultKeyStore() throws CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException { + KeyStore keyStore = KeyStore.getInstance("AzureKeyVault"); + KeyVaultLoadStoreParameter parameter = new KeyVaultLoadStoreParameter( + System.getenv("AZURE_KEYVAULT_URI"), + System.getenv("AZURE_KEYVAULT_TENANT_ID"), + System.getenv("AZURE_KEYVAULT_CLIENT_ID"), + System.getenv("AZURE_KEYVAULT_CLIENT_SECRET")); + keyStore.load(parameter); + return keyStore; + } + } diff --git a/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/ServerSocketTest.java b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/ServerSocketTest.java index 86ce286199f0..7409c0856acf 100644 --- a/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/ServerSocketTest.java +++ b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/ServerSocketTest.java @@ -13,14 +13,15 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.ssl.PrivateKeyDetails; +import org.apache.http.ssl.PrivateKeyStrategy; import org.apache.http.ssl.SSLContexts; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLServerSocket; -import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.*; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; @@ -28,23 +29,25 @@ import java.security.Security; import java.security.cert.X509Certificate; import java.util.Arrays; +import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; /** * The unit test validating the ServerSocket is created using a certificate from Azure Key Vault. */ +@Disabled("Block live test, disable temporarily") @EnabledIfEnvironmentVariable(named = "AZURE_KEYVAULT_CERTIFICATE_NAME", matches = "myalias") public class ServerSocketTest { + private static KeyStore ks; - /** - * Test SSLServerSocket without client trust. - * - * @throws Exception when a serious error occurs. - */ - @Test - public void testServerSocket() throws Exception { + private static KeyManagerFactory kmf; + + private static String certificateName; + + @BeforeAll + public static void beforeEach() throws Exception { /* * Add JCA provider. @@ -52,37 +55,26 @@ public void testServerSocket() throws Exception { KeyVaultJcaProvider provider = new KeyVaultJcaProvider(); Security.addProvider(provider); - /* - * Setup server side. - * - * - Create an Azure Key Vault specific instance of a KeyStore. - * - Set the KeyManagerFactory to use that KeyStore. - * - Set the SSL context to use the KeyManagerFactory. - * - Create the SSLServerSocket using th SSL context. - */ PropertyConvertorUtils.putEnvironmentPropertyToSystemProperty( Arrays.asList("AZURE_KEYVAULT_URI", "AZURE_KEYVAULT_TENANT_ID", "AZURE_KEYVAULT_CLIENT_ID", "AZURE_KEYVAULT_CLIENT_SECRET") ); - KeyStore ks = KeyStore.getInstance("AzureKeyVault"); - KeyVaultLoadStoreParameter parameter = new KeyVaultLoadStoreParameter( - System.getenv("AZURE_KEYVAULT_URI"), - System.getenv("AZURE_KEYVAULT_TENANT_ID"), - System.getenv("AZURE_KEYVAULT_CLIENT_ID"), - System.getenv("AZURE_KEYVAULT_CLIENT_SECRET")); - ks.load(parameter); - - KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + + /** + * - Create an Azure Key Vault specific instance of a KeyStore. + * - Set the KeyManagerFactory to use that KeyStore. + */ + ks = PropertyConvertorUtils.getKeyVaultKeyStore(); + kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(ks, "".toCharArray()); - SSLContext context = SSLContext.getInstance("TLS"); - context.init(kmf.getKeyManagers(), null, null); + certificateName = System.getenv("AZURE_KEYVAULT_CERTIFICATE_NAME"); + } - SSLServerSocketFactory factory = context.getServerSocketFactory(); - SSLServerSocket serverSocket = (SSLServerSocket) factory.createServerSocket(8765); + private void startSocket(SSLServerSocket serverSocket) { Thread server = new Thread(() -> { while (true) { try { @@ -97,129 +89,124 @@ public void testServerSocket() throws Exception { } }); server.start(); + } - /* - * Setup client side - * - * - Create an SSL context. - * - Set SSL context to trust any certificate. - * - Create SSL connection factory. - * - Set hostname verifier to trust any hostname. - */ - + @Test + public void testHttpsConnectionWithoutClientTrust() throws Exception { SSLContext sslContext = SSLContexts .custom() .loadTrustMaterial((final X509Certificate[] chain, final String authType) -> true) .build(); + testHttpsConnection(8765, sslContext); - SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory( - sslContext, (hostname, session) -> true); - - PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager( - RegistryBuilder.create() - .register("https", sslConnectionSocketFactory) - .build()); + } - /* - * And now execute the test. - */ - String result = null; + @Test + public void testHttpsConnectionWithSelfSignedClientTrust() throws Exception { + SSLContext sslContext = SSLContexts + .custom() + .loadTrustMaterial(ks, new TrustSelfSignedStrategy()) + .build(); + testHttpsConnection(8766, sslContext); - try (CloseableHttpClient client = HttpClients.custom().setConnectionManager(manager).build()) { - HttpGet httpGet = new HttpGet("https://localhost:8765"); - ResponseHandler responseHandler = (HttpResponse response) -> { - int status = response.getStatusLine().getStatusCode(); - String result1 = null; - if (status == 204) { - result1 = "Success"; - } - return result1; - }; - result = client.execute(httpGet, responseHandler); - } catch (IOException ioe) { - ioe.printStackTrace(); - } + } - /* - * And verify all went well. - */ - assertEquals("Success", result); + @Test + public void testServerSocketWithDefaultTrustManager() throws Exception { + serverSocketWithTrustManager(8768); } + /** - * Test SSLServerSocket WITH self-signed client trust. + * Test SSLServerSocket with key vault trust manager. * * @throws Exception when a serious error occurs. */ @Test - public void testServerSocketWithSelfSignedClientTrust() throws Exception { + public void testServerSocketWithKeyVaultTrustManager() throws Exception { + Security.insertProviderAt(new KeyVaultTrustManagerFactoryProvider(), 1); + serverSocketWithTrustManager(8767); + } + + + private void testHttpsConnection(Integer port, SSLContext sslContext) throws Exception { /* - * Add JCA provider. + * Setup server side. + * + * - Set the SSL context to use the KeyManagerFactory. + * - Create the SSLServerSocket using th SSL context. */ - KeyVaultJcaProvider provider = new KeyVaultJcaProvider(); - Security.addProvider(provider); + SSLContext context = SSLContext.getInstance("TLS"); + context.init(kmf.getKeyManagers(), null, null); + + SSLServerSocketFactory factory = context.getServerSocketFactory(); + SSLServerSocket serverSocket = (SSLServerSocket) factory.createServerSocket(port); + + startSocket(serverSocket); + + /* + * And now execute the test. + */ + String result = sendRequest(sslContext, port); + + /* + * And verify all went well. + */ + assertEquals("Success", result); + } + + private void serverSocketWithTrustManager(Integer port) throws Exception { /* * Setup server side. * - * - Create an Azure Key Vault specific instance of a KeyStore. - * - Set the KeyManagerFactory to use that KeyStore. * - Set the SSL context to use the KeyManagerFactory. * - Create the SSLServerSocket using th SSL context. */ - PropertyConvertorUtils.putEnvironmentPropertyToSystemProperty( - Arrays.asList("AZURE_KEYVAULT_URI", - "AZURE_KEYVAULT_TENANT_ID", - "AZURE_KEYVAULT_CLIENT_ID", - "AZURE_KEYVAULT_CLIENT_SECRET") - ); - KeyStore ks = KeyStore.getInstance("AzureKeyVault"); - KeyVaultLoadStoreParameter parameter = new KeyVaultLoadStoreParameter( - System.getenv("AZURE_KEYVAULT_URI"), - System.getenv("AZURE_KEYVAULT_TENANT_ID"), - System.getenv("AZURE_KEYVAULT_CLIENT_ID"), - System.getenv("AZURE_KEYVAULT_CLIENT_SECRET")); - ks.load(parameter); - - KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - kmf.init(ks, "".toCharArray()); + + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(ks); SSLContext context = SSLContext.getInstance("TLS"); - context.init(kmf.getKeyManagers(), null, null); + context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); SSLServerSocketFactory factory = context.getServerSocketFactory(); - SSLServerSocket serverSocket = (SSLServerSocket) factory.createServerSocket(8766); + SSLServerSocket serverSocket = (SSLServerSocket) factory.createServerSocket(port); + serverSocket.setNeedClientAuth(true); - Thread server = new Thread(() -> { - while (true) { - try { - Socket socket = serverSocket.accept(); - try (OutputStream outputStream = socket.getOutputStream()) { - outputStream.write("HTTP/1.1 204\r\n".getBytes()); - outputStream.flush(); - } - } catch (IOException ioe) { - ioe.printStackTrace(); - } - } - }); - server.start(); + startSocket(serverSocket); /* * Setup client side * * - Create an SSL context. * - Set SSL context to trust any certificate. - * - Create SSL connection factory. - * - Set hostname verifier to trust any hostname. */ SSLContext sslContext = SSLContexts .custom() .loadTrustMaterial(ks, new TrustSelfSignedStrategy()) + .loadKeyMaterial(ks, "".toCharArray(), new ClientPrivateKeyStrategy()) .build(); + /* + * And now execute the test. + */ + String result = sendRequest(sslContext, port); + + /* + * And verify all went well. + */ + assertEquals("Success", result); + } + + private String sendRequest(SSLContext sslContext, Integer port) { + + /** + * - Create SSL connection factory. + * - Set hostname verifier to trust any hostname. + */ SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory( sslContext, (hostname, session) -> true); @@ -228,13 +215,11 @@ public void testServerSocketWithSelfSignedClientTrust() throws Exception { .register("https", sslConnectionSocketFactory) .build()); - /* - * And now execute the test. - */ + String result = null; try (CloseableHttpClient client = HttpClients.custom().setConnectionManager(manager).build()) { - HttpGet httpGet = new HttpGet("https://localhost:8766"); + HttpGet httpGet = new HttpGet("https://localhost:" + port); ResponseHandler responseHandler = (HttpResponse response) -> { int status = response.getStatusLine().getStatusCode(); String result1 = null; @@ -247,10 +232,15 @@ public void testServerSocketWithSelfSignedClientTrust() throws Exception { } catch (IOException ioe) { ioe.printStackTrace(); } + return result; + } - /* - * And verify all went well. - */ - assertEquals("Success", result); + + private static class ClientPrivateKeyStrategy implements PrivateKeyStrategy { + @Override + public String chooseAlias(Map map, Socket socket) { + return certificateName; // It should be your certificate alias used in client-side + } } + } diff --git a/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-keyvault-certificates-client-side/src/main/java/com/azure/spring/security/keyvault/certificates/sample/client/side/SampleApplicationConfiguration.java b/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-keyvault-certificates-client-side/src/main/java/com/azure/spring/security/keyvault/certificates/sample/client/side/SampleApplicationConfiguration.java index 5ba9a58d5d4a..bdb3d14db1e1 100644 --- a/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-keyvault-certificates-client-side/src/main/java/com/azure/spring/security/keyvault/certificates/sample/client/side/SampleApplicationConfiguration.java +++ b/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-keyvault-certificates-client-side/src/main/java/com/azure/spring/security/keyvault/certificates/sample/client/side/SampleApplicationConfiguration.java @@ -24,15 +24,15 @@ public class SampleApplicationConfiguration { @Bean public RestTemplate restTemplateWithTLS() throws Exception { - KeyStore trustStore = KeyStore.getInstance("AzureKeyVault"); + KeyStore azureKeyVaultKeyStore = KeyStore.getInstance("AzureKeyVault"); KeyVaultLoadStoreParameter parameter = new KeyVaultLoadStoreParameter( System.getProperty("azure.keyvault.uri"), System.getProperty("azure.keyvault.tenant-id"), System.getProperty("azure.keyvault.client-id"), System.getProperty("azure.keyvault.client-secret")); - trustStore.load(parameter); + azureKeyVaultKeyStore.load(parameter); SSLContext sslContext = SSLContexts.custom() - .loadTrustMaterial(trustStore, null) + .loadTrustMaterial(azureKeyVaultKeyStore, null) .build(); SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext, (hostname, session) -> true); @@ -46,16 +46,16 @@ public RestTemplate restTemplateWithTLS() throws Exception { @Bean public RestTemplate restTemplateWithMTLS() throws Exception { - KeyStore azuerKeyVaultKeyStore = KeyStore.getInstance("AzureKeyVault"); + KeyStore azureKeyVaultKeyStore = KeyStore.getInstance("AzureKeyVault"); KeyVaultLoadStoreParameter parameter = new KeyVaultLoadStoreParameter( System.getProperty("azure.keyvault.uri"), System.getProperty("azure.keyvault.tenant-id"), System.getProperty("azure.keyvault.client-id"), System.getProperty("azure.keyvault.client-secret")); - azuerKeyVaultKeyStore.load(parameter); + azureKeyVaultKeyStore.load(parameter); SSLContext sslContext = SSLContexts.custom() - .loadTrustMaterial(azuerKeyVaultKeyStore, null) - .loadKeyMaterial(azuerKeyVaultKeyStore, "".toCharArray(), new ClientPrivateKeyStrategy()) + .loadTrustMaterial(azureKeyVaultKeyStore, null) + .loadKeyMaterial(azureKeyVaultKeyStore, "".toCharArray(), new ClientPrivateKeyStrategy()) .build(); SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext, (hostname, session) -> true); diff --git a/sdk/spring/azure-spring-boot-starter-keyvault-certificates/README.md b/sdk/spring/azure-spring-boot-starter-keyvault-certificates/README.md index 43f2475a92dc..f51b1bcf7687 100644 --- a/sdk/spring/azure-spring-boot-starter-keyvault-certificates/README.md +++ b/sdk/spring/azure-spring-boot-starter-keyvault-certificates/README.md @@ -174,15 +174,15 @@ Configure a `RestTemplate` bean which set the `AzureKeyVault` as trust store: ```java @Bean public RestTemplate restTemplateWithTLS() throws Exception { - KeyStore trustStore = KeyStore.getInstance("AzureKeyVault"); + KeyStore azureKeyVaultKeyStore = KeyStore.getInstance("AzureKeyVault"); KeyVaultLoadStoreParameter parameter = new KeyVaultLoadStoreParameter( System.getProperty("azure.keyvault.uri"), System.getProperty("azure.keyvault.tenant-id"), System.getProperty("azure.keyvault.client-id"), System.getProperty("azure.keyvault.client-secret")); - trustStore.load(parameter); + azureKeyVaultKeyStore.load(parameter); SSLContext sslContext = SSLContexts.custom() - .loadTrustMaterial(trustStore, null) + .loadTrustMaterial(azureKeyVaultKeyStore, null) .build(); SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext, (hostname, session) -> true); @@ -249,16 +249,16 @@ Step 2. On the client side, update `RestTemplate`. Example: ```java @Bean public RestTemplate restTemplateWithMTLS() throws Exception { - KeyStore azuerKeyVaultKeyStore = KeyStore.getInstance("AzureKeyVault"); + KeyStore azureKeyVaultKeyStore = KeyStore.getInstance("AzureKeyVault"); KeyVaultLoadStoreParameter parameter = new KeyVaultLoadStoreParameter( System.getProperty("azure.keyvault.uri"), System.getProperty("azure.keyvault.tenant-id"), System.getProperty("azure.keyvault.client-id"), System.getProperty("azure.keyvault.client-secret")); - azuerKeyVaultKeyStore.load(parameter); + azureKeyVaultKeyStore.load(parameter); SSLContext sslContext = SSLContexts.custom() - .loadTrustMaterial(azuerKeyVaultKeyStore, null) - .loadKeyMaterial(azuerKeyVaultKeyStore, "".toCharArray(), new ClientPrivateKeyStrategy()) + .loadTrustMaterial(azureKeyVaultKeyStore, null) + .loadKeyMaterial(azureKeyVaultKeyStore, "".toCharArray(), new ClientPrivateKeyStrategy()) .build(); SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext, (hostname, session) -> true); @@ -316,6 +316,41 @@ spring: useInsecureTrustManager: true ``` +### Refresh certificates when have un trust certificate + +When the inbound certificate is not trusted, the KeyVaultKeyStore can fetch +certificates from KeyVault if the following property is configured: + +```yaml +azure: + keyvault: + jca: + refresh-certificates-when-have-un-trust-certificate: true +``` + +Note: If you set refresh-certificates-when-have-un-trust-certificate=true, your server will be vulnerable +to attack, because every untrusted certificate will cause your application to send a re-acquire certificate request. + +### Refresh certificate periodically + +KeyVaultKeyStore can fetch certificates from KeyVault periodically if following property is configured: + +```yaml +azure: + keyvault: + jca: + certificates-refresh-interval: 1800000 +``` + +Its value is 0(ms) by default, and certificate will not automatically refresh when its value <= 0. + +### Refresh certificate by java code + +You can also manually refresh the certificate by calling this method: +```java +KeyVaultCertificates.refreshCertsInfo(); +``` + ### Side-loading certificates This starter allows you to side-load certificates by supplying them as part of diff --git a/sdk/spring/azure-spring-boot-starter-keyvault-certificates/src/main/java/com/azure/spring/security/keyvault/certificates/starter/KeyVaultCertificatesEnvironmentPostProcessor.java b/sdk/spring/azure-spring-boot-starter-keyvault-certificates/src/main/java/com/azure/spring/security/keyvault/certificates/starter/KeyVaultCertificatesEnvironmentPostProcessor.java index 5c750615a621..6d20b6e68105 100644 --- a/sdk/spring/azure-spring-boot-starter-keyvault-certificates/src/main/java/com/azure/spring/security/keyvault/certificates/starter/KeyVaultCertificatesEnvironmentPostProcessor.java +++ b/sdk/spring/azure-spring-boot-starter-keyvault-certificates/src/main/java/com/azure/spring/security/keyvault/certificates/starter/KeyVaultCertificatesEnvironmentPostProcessor.java @@ -37,6 +37,8 @@ public void postProcessEnvironment(ConfigurableEnvironment environment, SpringAp putEnvironmentPropertyToSystemProperty(environment, "azure.keyvault.client-id"); putEnvironmentPropertyToSystemProperty(environment, "azure.keyvault.client-secret"); putEnvironmentPropertyToSystemProperty(environment, "azure.keyvault.managed-identity"); + putEnvironmentPropertyToSystemProperty(environment, "azure.keyvault.jca.certificates-refresh-interval"); + putEnvironmentPropertyToSystemProperty(environment, "azure.keyvault.jca.refresh-certificates-when-have-un-trust-certificate"); MutablePropertySources propertySources = environment.getPropertySources(); if (KeyVaultKeyStore.KEY_STORE_TYPE.equals(environment.getProperty("server.ssl.key-store-type"))) { diff --git a/sdk/spring/azure-spring-boot-starter-keyvault-certificates/src/main/java/com/azure/spring/security/keyvault/certificates/starter/KeyVaultProperties.java b/sdk/spring/azure-spring-boot-starter-keyvault-certificates/src/main/java/com/azure/spring/security/keyvault/certificates/starter/KeyVaultProperties.java index e37693550151..1c485c0a1ec1 100644 --- a/sdk/spring/azure-spring-boot-starter-keyvault-certificates/src/main/java/com/azure/spring/security/keyvault/certificates/starter/KeyVaultProperties.java +++ b/sdk/spring/azure-spring-boot-starter-keyvault-certificates/src/main/java/com/azure/spring/security/keyvault/certificates/starter/KeyVaultProperties.java @@ -91,11 +91,36 @@ public static class JcaProperties { * To configure Spring Cloud Gateway for outbound SSL, set overrideTrustManagerFactory = true. */ private String overrideTrustManagerFactory; + /** + * To configure refresh certificate when get untrusted certificate. + */ + private String refreshCertificatesWhenHaveUnTrustCertificate; + + public String getRefreshCertificatesWhenHaveUnTrustCertificate() { + return refreshCertificatesWhenHaveUnTrustCertificate; + } + + public void setRefreshCertificatesWhenHaveUnTrustCertificate(String refreshCertificatesWhenHaveUnTrustCertificate) { + this.refreshCertificatesWhenHaveUnTrustCertificate = refreshCertificatesWhenHaveUnTrustCertificate; + } + /** * If you are developing you can completely disable the certificate and hostname validation altogether by * setting disableHostnameVerification = true. Note: this is NOT recommended for production! */ private String disableHostnameVerification; + /** + * To enable auto refresh certificate, set certificatesRefreshInterval as refresh interval. The unit of time is milliseconds. + */ + private long certificatesRefreshInterval; + + public long getCertificatesRefreshInterval() { + return certificatesRefreshInterval; + } + + public void setCertificatesRefreshInterval(long certificatesRefreshInterval) { + this.certificatesRefreshInterval = certificatesRefreshInterval; + } public String getOverrideTrustManagerFactory() { return overrideTrustManagerFactory;