org.xbib.net.security.ssl.util.CertificateUtils Maven / Gradle / Ivy
The newest version!
package org.xbib.net.security.ssl.util;
import org.xbib.net.security.ssl.exception.GenericCertificateException;
import org.xbib.net.security.ssl.exception.GenericIOException;
import javax.net.ssl.X509TrustManager;
import javax.security.auth.x500.X500Principal;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public final class CertificateUtils {
private static final String CERTIFICATE_TYPE = "X.509";
private static final String P7B_HEADER = "-----BEGIN PKCS7-----";
private static final String P7B_FOOTER = "-----END PKCS7-----";
private static final String PEM_HEADER = "-----BEGIN CERTIFICATE-----";
private static final String PEM_FOOTER = "-----END CERTIFICATE-----";
private static final Pattern PEM_PATTERN = Pattern.compile(PEM_HEADER + "(.*?)" + PEM_FOOTER, Pattern.DOTALL);
private static final Pattern P7B_PATTERN = Pattern.compile(P7B_HEADER + "(.*?)" + P7B_FOOTER, Pattern.DOTALL);
private static final String EMPTY = "";
private CertificateUtils() {}
public static String generateAlias(Certificate certificate) {
if (certificate instanceof X509Certificate) {
return ((X509Certificate) certificate)
.getSubjectX500Principal()
.getName()
.toLowerCase(Locale.ENGLISH);
} else {
return UUID.randomUUID().toString().toLowerCase(Locale.ENGLISH);
}
}
/**
* Loads certificates from the classpath and maps it into a list of {@link Certificate}.
*
* Supported input format: PEM, P7B and DER
*/
public static List loadCertificate(String... certificatePaths) {
return loadCertificate(certificatePath ->
ValidationUtils.requireNotNull(
CertificateUtils.class.getClassLoader().getResourceAsStream(certificatePath),
String.format("Failed to load the certificate from the classpath for the given path: [%s]", certificatePath)),
certificatePaths
);
}
/**
* Loads certificates from the filesystem and maps it into a list of {@link Certificate}.
*
* Supported input format: PEM, P7B and DER
*/
public static List loadCertificate(Path... certificatePaths) {
return loadCertificate(certificatePath -> {
try {
return Files.newInputStream(certificatePath, StandardOpenOption.READ);
} catch (IOException exception) {
throw new GenericIOException(exception);
}
}, certificatePaths);
}
/**
* Loads certificates from multiple InputStreams and maps it into a list of {@link Certificate}.
*
* Supported input format: PEM, P7B and DER
*/
public static List loadCertificate(InputStream... certificateStreams) {
return loadCertificate(certificateStream ->
ValidationUtils.requireNotNull(certificateStream, "Failed to load the certificate from the provided InputStream because it is null"),
certificateStreams
);
}
private static List loadCertificate(Function resourceMapper, T[] resources) {
List certificates = new ArrayList<>();
for (T resource : resources) {
try (InputStream certificateStream = resourceMapper.apply(resource)) {
certificates.addAll(parseCertificate(certificateStream));
} catch (Exception e) {
throw new GenericIOException(e);
}
}
return Collections.unmodifiableList(certificates);
}
/**
* Tries to map the InputStream to a list of {@link Certificate}.
* It assumes that the content of the InputStream is either PEM, P7B or DER.
* The InputStream will copied into an OutputStream so it can be read multiple times.
*/
private static List parseCertificate(InputStream certificateStream) {
List certificates;
byte[] certificateData = IOUtils.copyToByteArray(certificateStream);
String certificateContent = new String(certificateData, StandardCharsets.UTF_8);
if (isPemFormatted(certificateContent)) {
certificates = parsePemCertificate(certificateContent);
} else if(isP7bFormatted(certificateContent)) {
certificates = parseP7bCertificate(certificateContent);
} else {
certificates = parseDerCertificate(new ByteArrayInputStream(certificateData));
}
return certificates;
}
private static boolean isPemFormatted(String certificateContent) {
return PEM_PATTERN.matcher(certificateContent).find();
}
private static boolean isP7bFormatted(String certificateContent) {
return P7B_PATTERN.matcher(certificateContent).find();
}
/**
* Parses PEM formatted certificates containing a
* header as -----BEGIN CERTIFICATE----- and footer as -----END CERTIFICATE-----
* or header as -----BEGIN PKCS7----- and footer as -----END PKCS7-----
* with a base64 encoded data between the header and footer.
*/
public static List parsePemCertificate(String certificateContent) {
Matcher pemMatcher = PEM_PATTERN.matcher(certificateContent);
return parseCertificate(pemMatcher);
}
/**
* Parses P7B formatted certificates containing a
* header as -----BEGIN PKCS7----- and footer as -----END PKCS7-----
* with a base64 encoded data between the header and footer.
*/
public static List parseP7bCertificate(String certificateContent) {
Matcher p7bMatcher = P7B_PATTERN.matcher(certificateContent);
return parseCertificate(p7bMatcher);
}
private static List parseCertificate(Matcher certificateMatcher) {
List certificates = new ArrayList<>();
while (certificateMatcher.find()) {
String certificate = certificateMatcher.group(1);
String sanitizedCertificate = certificate.replaceAll("[\\n|\\r]+", EMPTY).trim();
byte[] decodedCertificate = Base64.getDecoder().decode(sanitizedCertificate);
ByteArrayInputStream certificateAsInputStream = new ByteArrayInputStream(decodedCertificate);
List parsedCertificates = CertificateUtils.parseDerCertificate(certificateAsInputStream);
certificates.addAll(parsedCertificates);
IOUtils.closeSilently(certificateAsInputStream);
}
return Collections.unmodifiableList(certificates);
}
public static List parseDerCertificate(InputStream certificateStream) {
try(BufferedInputStream bufferedCertificateStream = new BufferedInputStream(certificateStream)) {
CertificateFactory certificateFactory = CertificateFactory.getInstance(CERTIFICATE_TYPE);
return certificateFactory.generateCertificates(bufferedCertificateStream).stream()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
} catch (CertificateException | IOException e) {
throw new GenericCertificateException("There is no valid certificate present to parse. Please make sure to supply a valid der formatted certificate", e);
}
}
public static List getJdkTrustedCertificates() {
return Collections.unmodifiableList(
Arrays.asList(
TrustManagerUtils.createTrustManagerWithJdkTrustedCertificates().getAcceptedIssuers()
)
);
}
public static List getSystemTrustedCertificates() {
return TrustManagerUtils.createTrustManagerWithSystemTrustedCertificates()
.map(X509TrustManager::getAcceptedIssuers)
.map(Arrays::asList)
.map(Collections::unmodifiableList)
.orElseGet(Collections::emptyList);
}
public static Map> getCertificateAsPem(String... urls) {
return getCertificateAsPem(Arrays.asList(urls));
}
public static Map> getCertificateAsPem(List urls) {
Map> certificates = CertificateUtils.getCertificate(urls)
.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, entry -> CertificateUtils.convertToPem(entry.getValue())));
return Collections.unmodifiableMap(certificates);
}
public static Map> getCertificate(String... urls) {
return CertificateUtils.getCertificate(Arrays.asList(urls));
}
public static Map> getCertificate(List urls) {
return urls.stream()
.map(url -> new AbstractMap.SimpleEntry<>(url, CertificateExtractorUtils.getInstance().getCertificateFromExternalSource(url)))
.collect(Collectors.collectingAndThen(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue), Collections::unmodifiableMap));
}
public static List convertToPem(List certificates) {
return certificates.stream()
.map(CertificateUtils::convertToPem)
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
}
public static String convertToPem(Certificate certificate) {
try {
byte[] encodedCertificate = certificate.getEncoded();
byte[] base64EncodedCertificate = Base64.getEncoder().encode(encodedCertificate);
String parsedCertificate = new String(base64EncodedCertificate);
List certificateContainer = Stream.of(parsedCertificate.split("(?<=\\G.{64})"))
.collect(Collectors.toCollection(ArrayList::new));
certificateContainer.add(0, PEM_HEADER);
certificateContainer.add(PEM_FOOTER);
if (certificate instanceof X509Certificate) {
X509Certificate x509Certificate = (X509Certificate) certificate;
X500Principal issuer = x509Certificate.getIssuerX500Principal();
certificateContainer.add(0, String.format("issuer=%s", issuer.getName()));
X500Principal subject = x509Certificate.getSubjectX500Principal();
certificateContainer.add(0, String.format("subject=%s", subject.getName()));
}
return String.join(System.lineSeparator(), certificateContainer);
} catch (CertificateEncodingException e) {
throw new GenericCertificateException(e);
}
}
}