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 index 6e0ef8f6ea129..0db138bb371c8 100644 --- 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 @@ -3,17 +3,38 @@ package com.azure.security.keyvault.jca; +import java.io.InputStream; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.ByteArrayOutputStream; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.security.Key; import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.logging.Logger; + +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; /** * Store certificates loaded from classpath. */ -public class ClasspathCertificates implements AzureCertificates { +public final class ClasspathCertificates implements AzureCertificates { + + + /** + * Stores the logger. + */ + private static final Logger LOGGER = Logger.getLogger(ClasspathCertificates.class.getName()); /** * Store certificates' alias. @@ -57,22 +78,6 @@ 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 @@ -96,4 +101,81 @@ public void deleteEntry(String alias) { certificateKeys.remove(alias); } + /** + * Side-load certificate from classpath. + */ + void loadCertificatesFromClasspath() { + try { + String[] filenames = getFilenames("/keyvault"); + for (String filename : filenames) { + try (InputStream inputStream = getClass().getResourceAsStream("/keyvault/" + filename)) { + String alias = filename; + if (alias != null) { + if (alias.lastIndexOf('.') != -1) { + alias = alias.substring(0, alias.lastIndexOf('.')); + } + byte[] bytes = readAllBytes(inputStream); + try { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate certificate = (X509Certificate) cf.generateCertificate( + new ByteArrayInputStream(bytes)); + setCertificateEntry(alias, certificate); + LOGGER.log(INFO, "Side loaded certificate: {0} from: {1}", + new Object[]{alias, filename}); + } catch (CertificateException e) { + LOGGER.log(WARNING, "Unable to side-load certificate from: " + filename, e); + } + } + } + } + } catch (IOException ioe) { + LOGGER.log(WARNING, "Unable to determine certificates to side-load", ioe); + } + } + + + /** + * Read all the bytes for a given input stream. + * + * @param inputStream the input stream. + * @return the byte-array. + * @throws IOException when an I/O error occurs. + */ + private byte[] readAllBytes(InputStream inputStream) throws IOException { + byte[] bytes; + try (ByteArrayOutputStream byteOutput = new ByteArrayOutputStream()) { + byte[] buffer = new byte[1024]; + while (true) { + int r = inputStream.read(buffer); + if (r == -1) { + break; + } + byteOutput.write(buffer, 0, r); + } + bytes = byteOutput.toByteArray(); + } + return bytes; + } + + /** + * Get the filenames. + * + * @param path the path. + * @return the filenames. + * @throws IOException when an I/O error occurs. + */ + private String[] getFilenames(String path) throws IOException { + List filenames = new ArrayList<>(); + try (InputStream in = getClass().getResourceAsStream(path)) { + if (!Objects.isNull(in)) { + try (BufferedReader br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) { + String resource; + while ((resource = br.readLine()) != null) { + filenames.add(resource); + } + } + } + } + return filenames.toArray(new String[0]); + } } diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/FileSystemCertificates.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/FileSystemCertificates.java new file mode 100644 index 0000000000000..0eec1cb0f360f --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/FileSystemCertificates.java @@ -0,0 +1,214 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.security.keyvault.jca; + +import java.io.InputStream; +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.FileInputStream; +import java.io.File; +import java.security.Key; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.List; +import java.util.Objects; +import java.util.HashMap; +import java.util.Map; +import java.util.ArrayList; +import java.util.Optional; +import java.util.Arrays; +import java.util.logging.Logger; +import java.util.stream.Stream; + +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; + +/** + * Store certificates loaded from file system. + */ +public final class FileSystemCertificates implements AzureCertificates { + + /** + * Stores the logger. + */ + private static final Logger LOGGER = Logger.getLogger(FileSystemCertificates.class.getName()); + + /** + * Stores the jre key store aliases. + */ + private final List aliases = new ArrayList<>(); + + /** + * Stores the file system certificates by alias. + */ + private final Map certificates = new HashMap<>(); + + /** + * Stores the file system certificate keys by alias. + */ + private final Map certificateKeys = new HashMap<>(); + + private final String certificatePath; + + @Override + public List getAliases() { + return aliases; + } + + @Override + public Map getCertificates() { + return certificates; + } + + @Override + public Map getCertificateKeys() { + return certificateKeys; + } + + @Override + public void deleteEntry(String alias) { + aliases.remove(alias); + certificates.remove(alias); + certificateKeys.remove(alias); + } + + /** + * Constructor. + * + * @param certificatePath Store the file path where certificates are placed + */ + private FileSystemCertificates(String certificatePath) { + this.certificatePath = certificatePath; + } + + /** + * Add alias and certificate + * + * @param alias certificate alias + * @param certificate certificate value + */ + public void setCertificateEntry(String alias, Certificate certificate) { + //Add verification to avoid certificate files with the same file name but different suffixes + if (aliases.contains(alias)) { + LOGGER.log(WARNING, "Cannot load certificates with the same alias in file system", alias); + return; + } + aliases.add(alias); + certificates.put(alias, certificate); + } + + /** + * If the file can be parsed into a certificate, add it to the list + * + * @param file file which try to parsed into a certificate + * @throws IOException Exception thrown when there is an error in reading all the bytes from the File. + */ + private void setCertificateByFile(File file) throws IOException { + X509Certificate certificate; + try (InputStream inputStream = new FileInputStream(file); + BufferedInputStream bytes = new BufferedInputStream(inputStream)) { + String alias = toCertificateAlias(file); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + certificate = (X509Certificate) cf.generateCertificate(bytes); + if (certificate != null) { + setCertificateEntry(alias, certificate); + LOGGER.log(INFO, "Load file system certificate: {0} from: {1}", + new Object[]{alias, file.getName()}); + } + } catch (CertificateException e) { + LOGGER.log(WARNING, "Unable to load file system certificate from: " + file.getName(), e); + } + } + + /** + * Load certificates in the file directory + */ + void loadCertificatesFromFileSystem() { + try { + List files = getFiles(); + for (File file : files) { + setCertificateByFile(file); + } + } catch (IOException ioe) { + LOGGER.log(WARNING, "Unable to determine certificates to file system", ioe); + } + } + + /** + * Get alias from file + * @param file File containing certificate information + * @return certificate alias + */ + public static String toCertificateAlias(File file) { + String fileName = file.getName(); + int lastIndexOfDot = fileName.lastIndexOf('.'); + if (lastIndexOfDot == -1) { + return fileName; + } + return fileName.substring(0, lastIndexOfDot); + } + + /** + * Load all certificates in the folder, to avoid alias duplication, do not read files in subdirectories. + */ + private List getFiles() { + List files = new ArrayList<>(); + File filePackage = new File(certificatePath); + File[] array = filePackage.listFiles(); + Optional.ofNullable(array) + .map(Arrays::stream) + .orElseGet(Stream::empty) + .filter(Objects::nonNull) + .filter(File::isFile) + .filter(File::exists) + .filter(File::canRead) + .forEach(files::add); + return files; + } + + /** + * Factory of FileSystemCertificate, to avoid loading files multiple times + */ + public static class FileSystemCertificatesFactory { + + private static volatile FileSystemCertificates customFileSystemCertificates; + + /** + * Get Singleton custom file system certificates + * @param path custom certificate path, which works only in first time + * @return custom file certificate + */ + public static FileSystemCertificates getCustomFileSystemCertificates(String path) { + if (customFileSystemCertificates == null) { + synchronized (FileSystemCertificatesFactory.class) { + if (customFileSystemCertificates == null) { + customFileSystemCertificates = new FileSystemCertificates(path); + } + } + } + return customFileSystemCertificates; + } + + private static volatile FileSystemCertificates wellKnownFileSystemCertificates; + + /** + * Get Singleton well known file system certificates + * @param path well known certificate path, which works only in first time + * @return well known file certificate + */ + public static FileSystemCertificates getWellKnownFileSystemCertificates(String path) { + if (wellKnownFileSystemCertificates == null) { + synchronized (FileSystemCertificatesFactory.class) { + if (wellKnownFileSystemCertificates == null) { + wellKnownFileSystemCertificates = new FileSystemCertificates(path); + } + } + } + return wellKnownFileSystemCertificates; + } + } + +} 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 3704aaaae1c97..8d625441a071e 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 @@ -2,14 +2,8 @@ // Licensed under the MIT License. package com.azure.security.keyvault.jca; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.io.OutputStream; -import java.nio.charset.StandardCharsets; import java.security.Key; import java.security.KeyStore; import java.security.KeyStoreException; @@ -17,22 +11,17 @@ import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableEntryException; import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.ArrayList; import java.util.Collections; import java.util.Date; -import java.util.Objects; import java.util.Enumeration; import java.util.List; -import java.util.Collection; import java.util.Optional; import java.util.Arrays; +import java.util.ArrayList; +import java.util.Map; +import java.util.HashMap; import java.util.logging.Logger; -import java.util.stream.Collectors; -import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; /** @@ -56,9 +45,19 @@ public final class KeyVaultKeyStore extends KeyStoreSpi { private static final Logger LOGGER = Logger.getLogger(KeyVaultKeyStore.class.getName()); /** - * Store certificates loaded from classpath. + * Stores the Jre key store certificates. */ - private final ClasspathCertificates classpathCertificates; + private final JreCertificates jreCertificates; + + /** + * Store well Know certificates loaded from file system. + */ + private final FileSystemCertificates wellKnowCertificates; + + /** + * Store custom certificates loaded from file system. + */ + private final FileSystemCertificates customCertificates; /** * Store certificates loaded from KeyVault. @@ -66,9 +65,9 @@ public final class KeyVaultKeyStore extends KeyStoreSpi { private final KeyVaultCertificates keyVaultCertificates; /** - * Stores the Jre key store certificates. + * Store certificates loaded from classpath. */ - private final JreCertificates jreCertificates; + private final ClasspathCertificates classpathCertificates; /** * Stores all the certificates. @@ -87,6 +86,18 @@ public final class KeyVaultKeyStore extends KeyStoreSpi { private final boolean refreshCertificatesWhenHaveUnTrustCertificate; + /** + * Store the path where the well know certificate is placed + */ + final String wellKnowPath = Optional.ofNullable(System.getProperty("azure.cert-path.well-known")) + .orElse("/etc/certs/well-known/"); + + /** + * Store the path where the custom certificate is placed + */ + final String customPath = Optional.ofNullable(System.getProperty("azure.cert-path.custom")) + .orElse("/etc/certs/custom/"); + /** * Constructor. * @@ -119,21 +130,17 @@ public KeyVaultKeyStore() { refreshCertificatesWhenHaveUnTrustCertificate = Optional.ofNullable(System.getProperty("azure.keyvault.jca.refresh-certificates-when-have-un-trust-certificate")) .map(Boolean::parseBoolean) .orElse(false); + jreCertificates = JreCertificates.getInstance(); + wellKnowCertificates = FileSystemCertificates.FileSystemCertificatesFactory.getWellKnownFileSystemCertificates(wellKnowPath); + customCertificates = FileSystemCertificates.FileSystemCertificatesFactory.getCustomFileSystemCertificates(customPath); keyVaultCertificates = new KeyVaultCertificates(refreshInterval, keyVaultClient); classpathCertificates = new ClasspathCertificates(); - jreCertificates = JreCertificates.getInstance(); - allCertificates = Arrays.asList(keyVaultCertificates, classpathCertificates, jreCertificates); + allCertificates = Arrays.asList(jreCertificates, wellKnowCertificates, customCertificates, keyVaultCertificates, classpathCertificates); } @Override public Enumeration engineAliases() { - List aliasList = allCertificates.stream() - .map(AzureCertificates::getAliases) - .flatMap(Collection::stream) - .distinct() - .collect(Collectors.toList()); - - return Collections.enumeration(aliasList); + return Collections.enumeration(getAllAliases()); } @Override @@ -143,8 +150,7 @@ public boolean engineContainsAlias(String alias) { @Override public void engineDeleteEntry(String alias) { - keyVaultCertificates.deleteEntry(alias); - classpathCertificates.deleteEntry(alias); + allCertificates.forEach(a -> a.deleteEntry(alias)); } @Override @@ -172,11 +178,7 @@ public Certificate engineGetCertificate(String alias) { public String engineGetCertificateAlias(Certificate cert) { String alias = null; if (cert != null) { - List aliasList = allCertificates.stream() - .map(AzureCertificates::getAliases) - .flatMap(Collection::stream) - .distinct() - .collect(Collectors.toList()); + List aliasList = getAllAliases(); for (String candidateAlias : aliasList) { Certificate certificate = engineGetCertificate(candidateAlias); @@ -225,11 +227,7 @@ public Key engineGetKey(String alias, char[] password) { @Override public boolean engineIsCertificateEntry(String alias) { - return allCertificates.stream() - .map(AzureCertificates::getAliases) - .flatMap(Collection::stream) - .distinct() - .anyMatch(a -> Objects.equals(a, alias)); + return getAllAliases().contains(alias); } @Override @@ -257,28 +255,48 @@ public void engineLoad(KeyStore.LoadStoreParameter param) { } keyVaultCertificates.setKeyVaultClient(keyVaultClient); } - loadCertificatesFromClasspath(); + loadCertificates(); } @Override public void engineLoad(InputStream stream, char[] password) { - loadCertificatesFromClasspath(); + loadCertificates(); } + private void loadCertificates() { + wellKnowCertificates.loadCertificatesFromFileSystem(); + customCertificates.loadCertificatesFromFileSystem(); + classpathCertificates.loadCertificatesFromClasspath(); + } + + private List getAllAliases() { + List allAliases = new ArrayList<>(); + allAliases.addAll(jreCertificates.getAliases()); + Map> aliasLists = new HashMap<>(); + aliasLists.put("well known certificates", wellKnowCertificates.getAliases()); + aliasLists.put("custom certificates", customCertificates.getAliases()); + aliasLists.put("key vault certificates", keyVaultCertificates.getAliases()); + aliasLists.put("class path certificates", classpathCertificates.getAliases()); + + aliasLists.forEach((key, value) -> { + value.forEach(a -> { + if (allAliases.contains(a)) { + LOGGER.log(WARNING, String.format("The certificate with alias %s under %s already exists", a, key)); + } else { + allAliases.add(a); + } + }); + }); + return allAliases; + } + + @Override public void engineSetCertificateEntry(String alias, Certificate certificate) { - if (keyVaultCertificates.getAliases().contains(alias)) { + if (getAllAliases().contains(alias)) { + LOGGER.log(WARNING, "Cannot overwrite own certificate"); 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); } @@ -297,12 +315,7 @@ public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) { @Override public int engineSize() { - return allCertificates.stream() - .map(AzureCertificates::getAliases) - .flatMap(Collection::stream) - .distinct() - .collect(Collectors.toList()) - .size(); + return getAllAliases().size(); } @Override @@ -313,82 +326,5 @@ public void engineStore(OutputStream stream, char[] password) { public void engineStore(KeyStore.LoadStoreParameter param) { } - /** - * Get the filenames. - * - * @param path the path. - * @return the filenames. - * @throws IOException when an I/O error occurs. - */ - private String[] getFilenames(String path) throws IOException { - List filenames = new ArrayList<>(); - try (InputStream in = getClass().getResourceAsStream(path)) { - if (in != null) { - try (BufferedReader br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) { - String resource; - while ((resource = br.readLine()) != null) { - filenames.add(resource); - } - } - } - } - return filenames.toArray(new String[0]); - } - /** - * Read all the bytes for a given input stream. - * - * @param inputStream the input stream. - * @return the byte-array. - * @throws IOException when an I/O error occurs. - */ - private byte[] readAllBytes(InputStream inputStream) throws IOException { - byte[] bytes; - try (ByteArrayOutputStream byteOutput = new ByteArrayOutputStream()) { - byte[] buffer = new byte[1024]; - while (true) { - int r = inputStream.read(buffer); - if (r == -1) { - break; - } - byteOutput.write(buffer, 0, r); - } - bytes = byteOutput.toByteArray(); - } - return bytes; - } - - /** - * Side-load certificate from classpath. - */ - private void loadCertificatesFromClasspath() { - try { - String[] filenames = getFilenames("/keyvault"); - if (filenames.length > 0) { - for (String filename : filenames) { - try (InputStream inputStream = getClass().getResourceAsStream("/keyvault/" + filename)) { - String alias = filename; - if (alias != null) { - if (alias.lastIndexOf('.') != -1) { - alias = alias.substring(0, alias.lastIndexOf('.')); - } - byte[] bytes = readAllBytes(inputStream); - try { - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - X509Certificate certificate = (X509Certificate) cf.generateCertificate( - new ByteArrayInputStream(bytes)); - engineSetClasspathCertificateEntry(alias, certificate); - LOGGER.log(INFO, "Side loaded certificate: {0} from: {1}", - new Object[]{alias, filename}); - } catch (CertificateException e) { - LOGGER.log(WARNING, "Unable to side-load certificate from: " + filename, e); - } - } - } - } - } - } catch (IOException ioe) { - LOGGER.log(WARNING, "Unable to determine certificates to side-load", ioe); - } - } } 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 index 59b77205f040d..ba43f191dadc5 100644 --- 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 @@ -27,20 +27,4 @@ public void testSetCertificateEntry() { 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/FileSystemCertificatesTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/FileSystemCertificatesTest.java new file mode 100644 index 0000000000000..20170115a4cbe --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/FileSystemCertificatesTest.java @@ -0,0 +1,64 @@ +// 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.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.security.ProviderException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +public class FileSystemCertificatesTest { + + FileSystemCertificates fileSystemCertificates; + + public static String getFilePath(String packageName) { + String filepath = "\\src\\test\\resources\\" + packageName; + return System.getProperty("user.dir") + filepath.replace("\\", System.getProperty("file.separator")); + } + + @Test + public void testSetCertificateEntry() { + fileSystemCertificates = FileSystemCertificates.FileSystemCertificatesFactory.getCustomFileSystemCertificates(getFilePath("custom\\")); + fileSystemCertificates.loadCertificatesFromFileSystem(); + Assertions.assertTrue(fileSystemCertificates.getAliases().contains("sideload")); + } + + @Test + public void testFileSystemCertificatePath() { + // Test them together cause System.setProperty will interdependent + System.setProperty("azure.keyvault.uri", "https://fake-vaule.vault.azure.net/"); + System.setProperty("azure.cert-path.well-known", getFilePath("well-known\\")); + System.setProperty("azure.cert-path.custom", getFilePath("custom\\")); + KeyVaultKeyStore ks = new KeyVaultKeyStore(); + ks.engineLoad(null); + X509Certificate customCertificate = getCertificateByFile(new File(getFilePath("custom\\sideload.x509"))); + X509Certificate wellKnownCertificate = getCertificateByFile(new File(getFilePath("well-known\\sideload.pem"))); + assertEquals(wellKnownCertificate, ks.engineGetCertificate("sideload")); + assertNotEquals(customCertificate, ks.engineGetCertificate("sideload")); + + } + + + public X509Certificate getCertificateByFile(File file) { + X509Certificate certificate; + try (InputStream inputStream = new FileInputStream(file); + BufferedInputStream bytes = new BufferedInputStream(inputStream)) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + certificate = (X509Certificate) cf.generateCertificate(bytes); + } catch (Exception e) { + throw new ProviderException(e); + } + return certificate; + } + +} 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 index 98af68ebb76ce..0d875dd67faf0 100644 --- 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 @@ -12,8 +12,7 @@ import java.util.ArrayList; import java.util.List; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; public class KeyVaultCertificatesTest { @@ -27,6 +26,7 @@ public class KeyVaultCertificatesTest { @BeforeEach public void beforeEach() { + List aliases = new ArrayList<>(); aliases.add("myalias"); when(keyVaultClient.getAliases()).thenReturn(aliases); diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/resources/custom/sideload.x509 b/sdk/keyvault/azure-security-keyvault-jca/src/test/resources/custom/sideload.x509 new file mode 100644 index 0000000000000..7c9f48aae658e Binary files /dev/null and b/sdk/keyvault/azure-security-keyvault-jca/src/test/resources/custom/sideload.x509 differ diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/resources/well-known/sideload.pem b/sdk/keyvault/azure-security-keyvault-jca/src/test/resources/well-known/sideload.pem new file mode 100644 index 0000000000000..9ea2d0a855793 --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/resources/well-known/sideload.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/FileSystemCertificatesTest.java b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/FileSystemCertificatesTest.java new file mode 100644 index 0000000000000..c4fb2041df4f7 --- /dev/null +++ b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/FileSystemCertificatesTest.java @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.security.keyvault.jca.test; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; + +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; + +@EnabledIfEnvironmentVariable(named = "AZURE_KEYVAULT_CERTIFICATE_NAME", matches = "myalias") +public class FileSystemCertificatesTest { + + @BeforeAll + public static void setEnvironmentProperty() { + System.setProperty("azure.cert-path.custom", getFilePath()); + PropertyConvertorUtils.putEnvironmentPropertyToSystemPropertyForKeyVaultJca(PropertyConvertorUtils.SYSTEM_PROPERTIES); + PropertyConvertorUtils.addKeyVaultJcaProvider(); + } + + public static String getFilePath() { + String filepath = "\\src\\test\\resources\\custom\\"; + return System.getProperty("user.dir") + filepath.replace("\\", System.getProperty("file.separator")); + } + + @Test + public void testGetFileSystemCertificate() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException { + KeyStore keyStore = PropertyConvertorUtils.getKeyVaultKeyStore(); + Assertions.assertNotNull(keyStore.getCertificate("sideload")); + } + +} diff --git a/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/JreKeyStoreTest.java b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/JreKeyStoreTest.java index 4227eabccbb8c..840c9cea8cda2 100644 --- a/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/JreKeyStoreTest.java +++ b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/JreKeyStoreTest.java @@ -4,7 +4,6 @@ package com.azure.security.keyvault.jca.test; import com.azure.security.keyvault.jca.JreCertificates; -import com.azure.security.keyvault.jca.KeyVaultJcaProvider; import org.apache.http.HttpResponse; import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.HttpGet; @@ -22,9 +21,7 @@ import javax.net.ssl.SSLContext; import java.io.IOException; import java.security.KeyStore; -import java.security.Security; import java.security.cert.Certificate; -import java.util.Arrays; import java.util.Map; import static org.junit.jupiter.api.Assertions.*; @@ -36,18 +33,11 @@ public static void init() { /* * Add JCA provider. */ - KeyVaultJcaProvider provider = new KeyVaultJcaProvider(); - Security.addProvider(provider); + PropertyConvertorUtils.addKeyVaultJcaProvider(); /* * Set system properties. */ - - PropertyConvertorUtils.putEnvironmentPropertyToSystemProperty( - Arrays.asList("AZURE_KEYVAULT_URI", - "AZURE_KEYVAULT_TENANT_ID", - "AZURE_KEYVAULT_CLIENT_ID", - "AZURE_KEYVAULT_CLIENT_SECRET") - ); + PropertyConvertorUtils.putEnvironmentPropertyToSystemPropertyForKeyVaultJca(PropertyConvertorUtils.SYSTEM_PROPERTIES); } @Test diff --git a/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/KeyVaultCertificatesTest.java b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/KeyVaultCertificatesTest.java index eb24818db85f3..dbdebe4ebed7b 100644 --- a/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/KeyVaultCertificatesTest.java +++ b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/KeyVaultCertificatesTest.java @@ -4,7 +4,6 @@ package com.azure.security.keyvault.jca.test; import com.azure.security.keyvault.jca.KeyVaultCertificates; -import com.azure.security.keyvault.jca.KeyVaultJcaProvider; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; @@ -12,7 +11,6 @@ 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; @@ -56,9 +54,8 @@ public class KeyVaultCertificatesTest { @BeforeAll public static void setEnvironmentProperty() { - PropertyConvertorUtils.putEnvironmentPropertyToSystemProperty(PropertyConvertorUtils.SYSTEM_PROPERTIES); - KeyVaultJcaProvider provider = new KeyVaultJcaProvider(); - Security.addProvider(provider); + PropertyConvertorUtils.putEnvironmentPropertyToSystemPropertyForKeyVaultJca(PropertyConvertorUtils.SYSTEM_PROPERTIES); + PropertyConvertorUtils.addKeyVaultJcaProvider(); 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/test/KeyVaultJcaProviderTest.java b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/KeyVaultJcaProviderTest.java index 178af1895cfce..c63050ae67d88 100644 --- a/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/KeyVaultJcaProviderTest.java +++ b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/KeyVaultJcaProviderTest.java @@ -3,14 +3,11 @@ package com.azure.security.keyvault.jca.test; -import com.azure.security.keyvault.jca.KeyVaultJcaProvider; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import java.security.KeyStore; -import java.security.Security; -import static com.azure.security.keyvault.jca.test.PropertyConvertorUtils.SYSTEM_PROPERTIES; import static org.junit.jupiter.api.Assertions.assertNotNull; /** @@ -27,8 +24,8 @@ public class KeyVaultJcaProviderTest { */ @Test public void testGetCertificate() throws Exception { - PropertyConvertorUtils.putEnvironmentPropertyToSystemProperty(SYSTEM_PROPERTIES); - Security.addProvider(new KeyVaultJcaProvider()); + PropertyConvertorUtils.putEnvironmentPropertyToSystemPropertyForKeyVaultJca(PropertyConvertorUtils.SYSTEM_PROPERTIES); + PropertyConvertorUtils.addKeyVaultJcaProvider(); 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/test/KeyVaultKeyManagerTest.java b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/KeyVaultKeyManagerTest.java index 8e4066e47581d..14b82160e15ff 100644 --- a/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/KeyVaultKeyManagerTest.java +++ b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/KeyVaultKeyManagerTest.java @@ -3,7 +3,6 @@ package com.azure.security.keyvault.jca.test; -import com.azure.security.keyvault.jca.KeyVaultJcaProvider; import com.azure.security.keyvault.jca.KeyVaultKeyManager; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -13,10 +12,8 @@ import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; -import java.security.Security; import java.security.cert.CertificateException; -import static com.azure.security.keyvault.jca.test.PropertyConvertorUtils.SYSTEM_PROPERTIES; import static org.junit.jupiter.api.Assertions.assertNotNull; @EnabledIfEnvironmentVariable(named = "AZURE_KEYVAULT_CERTIFICATE_NAME", matches = "myalias") @@ -28,9 +25,8 @@ public class KeyVaultKeyManagerTest { @BeforeAll public static void setEnvironmentProperty() throws KeyStoreException, NoSuchAlgorithmException, IOException, CertificateException { - PropertyConvertorUtils.putEnvironmentPropertyToSystemProperty(SYSTEM_PROPERTIES); - KeyVaultJcaProvider provider = new KeyVaultJcaProvider(); - Security.addProvider(provider); + PropertyConvertorUtils.putEnvironmentPropertyToSystemPropertyForKeyVaultJca(PropertyConvertorUtils.SYSTEM_PROPERTIES); + PropertyConvertorUtils.addKeyVaultJcaProvider(); 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/test/KeyVaultKeyStoreTest.java b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/KeyVaultKeyStoreTest.java index f3c42e1937712..46454daa4554c 100644 --- a/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/KeyVaultKeyStoreTest.java +++ b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/KeyVaultKeyStoreTest.java @@ -20,7 +20,6 @@ import java.security.cert.X509Certificate; import java.util.Base64; -import static com.azure.security.keyvault.jca.test.PropertyConvertorUtils.SYSTEM_PROPERTIES; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -64,7 +63,7 @@ public class KeyVaultKeyStoreTest { @BeforeAll public static void setEnvironmentProperty() { - PropertyConvertorUtils.putEnvironmentPropertyToSystemProperty(SYSTEM_PROPERTIES); + PropertyConvertorUtils.putEnvironmentPropertyToSystemPropertyForKeyVaultJca(PropertyConvertorUtils.SYSTEM_PROPERTIES); keystore = new KeyVaultKeyStore(); KeyVaultLoadStoreParameter parameter = new KeyVaultLoadStoreParameter( System.getenv("AZURE_KEYVAULT_URI"), diff --git a/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/PropertyConvertorUtils.java b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/PropertyConvertorUtils.java index e94f475699b29..47880ad7a9996 100644 --- a/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/PropertyConvertorUtils.java +++ b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/PropertyConvertorUtils.java @@ -3,19 +3,21 @@ package com.azure.security.keyvault.jca.test; +import com.azure.security.keyvault.jca.KeyVaultJcaProvider; import com.azure.security.keyvault.jca.KeyVaultLoadStoreParameter; import java.io.IOException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; +import java.security.Security; import java.security.cert.CertificateException; import java.util.Arrays; import java.util.List; public class PropertyConvertorUtils { - public static void putEnvironmentPropertyToSystemProperty(List key) { + public static void putEnvironmentPropertyToSystemPropertyForKeyVaultJca(List key) { key.forEach( environmentPropertyKey -> { String value = System.getenv(environmentPropertyKey); @@ -42,4 +44,9 @@ public static KeyStore getKeyVaultKeyStore() throws CertificateException, NoSuch return keyStore; } + public static void addKeyVaultJcaProvider() { + KeyVaultJcaProvider provider = new KeyVaultJcaProvider(); + Security.addProvider(provider); + } + } diff --git a/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/ServerSocketTest.java b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/ServerSocketTest.java index 02164bb42c93d..5d37631aa3ee2 100644 --- a/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/ServerSocketTest.java +++ b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/test/ServerSocketTest.java @@ -57,7 +57,7 @@ public static void beforeEach() throws Exception { */ KeyVaultJcaProvider provider = new KeyVaultJcaProvider(); Security.addProvider(provider); - PropertyConvertorUtils.putEnvironmentPropertyToSystemProperty(SYSTEM_PROPERTIES); + PropertyConvertorUtils.putEnvironmentPropertyToSystemPropertyForKeyVaultJca(SYSTEM_PROPERTIES); /** * - Create an Azure Key Vault specific instance of a KeyStore. diff --git a/sdk/keyvault/azure-security-test-keyvault-jca/src/test/resources/custom/sideload.x509 b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/resources/custom/sideload.x509 new file mode 100644 index 0000000000000..7c9f48aae658e Binary files /dev/null and b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/resources/custom/sideload.x509 differ diff --git a/sdk/keyvault/azure-security-test-keyvault-jca/src/test/resources/well-known/sideload.x509 b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/resources/well-known/sideload.x509 new file mode 100644 index 0000000000000..7c9f48aae658e Binary files /dev/null and b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/resources/well-known/sideload.x509 differ 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 6d20b6e681052..4035b33dc80dc 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 @@ -39,6 +39,8 @@ public void postProcessEnvironment(ConfigurableEnvironment environment, SpringAp 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"); + putEnvironmentPropertyToSystemProperty(environment, "azure.cert-path.well-known"); + putEnvironmentPropertyToSystemProperty(environment, "azure.cert-path.custom"); MutablePropertySources propertySources = environment.getPropertySources(); if (KeyVaultKeyStore.KEY_STORE_TYPE.equals(environment.getProperty("server.ssl.key-store-type"))) {