Skip to content

Commit

Permalink
Load cert from file system in key vault (#21947)
Browse files Browse the repository at this point in the history
  • Loading branch information
zhichengliu12581 authored Jun 15, 2021
1 parent 14518ff commit 04fbb4f
Show file tree
Hide file tree
Showing 19 changed files with 529 additions and 201 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -57,22 +78,6 @@ public Map<String, Key> 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
Expand All @@ -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<String> 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]);
}
}
Original file line number Diff line number Diff line change
@@ -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<String> aliases = new ArrayList<>();

/**
* Stores the file system certificates by alias.
*/
private final Map<String, Certificate> certificates = new HashMap<>();

/**
* Stores the file system certificate keys by alias.
*/
private final Map<String, Key> certificateKeys = new HashMap<>();

private final String certificatePath;

@Override
public List<String> getAliases() {
return aliases;
}

@Override
public Map<String, Certificate> getCertificates() {
return certificates;
}

@Override
public Map<String, Key> 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<File> 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<File> getFiles() {
List<File> 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;
}
}

}
Loading

0 comments on commit 04fbb4f

Please sign in to comment.