All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nl.altindag.ssl.util.CertificateUtils Maven / Gradle / Ivy

Go to download

High level library to configure a SSLContext and other properties to enable SSL/TLS connection

There is a newer version: 8.3.7
Show newest version
/*
 * Copyright 2019 Thunderberry.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package nl.altindag.ssl.util;

import nl.altindag.ssl.exception.GenericCertificateException;
import nl.altindag.ssl.exception.GenericIOException;
import nl.altindag.ssl.util.internal.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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.net.PasswordAuthentication;
import java.net.Proxy;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
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.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static nl.altindag.ssl.util.internal.CollectorsUtils.toModifiableList;
import static nl.altindag.ssl.util.internal.CollectorsUtils.toUnmodifiableList;
import static nl.altindag.ssl.util.internal.ValidationUtils.requireNotNull;

/**
 * @author Hakan Altindag
 */
public final class CertificateUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(CertificateUtils.class);

    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_INPUT_STREAM_EXCEPTION_MESSAGE = "Failed to load the certificate from the provided InputStream because it is null";
    private static final UnaryOperator CERTIFICATE_NOT_FOUND_EXCEPTION_MESSAGE = certificatePath -> String.format("Failed to load the certificate from the classpath for the given path: [%s]", certificatePath);
    private static final String MAX_64_CHARACTER_LINE_SPLITTER = "(?<=\\G.{64})";

    private static final String EMPTY = "";

    private CertificateUtils() {}

    public static  String generateAlias(T certificate) {
        if (certificate instanceof X509Certificate) {
            return ((X509Certificate) certificate)
                    .getSubjectX500Principal()
                    .getName(X500Principal.CANONICAL)
                    .replace(" ", "-")
                    .replace(",", "_")
                    .replace("'", "")
                    .replaceAll("[.*\\\\]+", "");
        } else {
            return UUID.randomUUID().toString().toLowerCase(Locale.US);
        }
    }

    public static  Map generateAliases(List certificates) {
        Map aliasToCertificate = new LinkedHashMap<>();
        for (T certificate : certificates) {
            String alias = generateUniqueAlias(certificate, aliasToCertificate::containsKey);
            aliasToCertificate.put(alias, certificate);
        }
        return Collections.unmodifiableMap(aliasToCertificate);
    }

    public static  String generateUniqueAlias(T certificate, Predicate aliasPredicate) {
        String initialAlias = generateAlias(certificate);
        String alias = initialAlias;

        int counter = 0;
        while (aliasPredicate.test(alias)) {
            alias = String.format("%s-%d", initialAlias, counter);
            counter++;
        }

        return alias;
    }

    public static  void write(Path destination, T certificate) {
        try {
            byte[] encodedCertificate = certificate.getEncoded();
            IOUtils.write(destination, encodedCertificate);
        } catch (CertificateEncodingException e) {
            throw new GenericCertificateException(e);
        }
    }

    /**
     * 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 -> requireNotNull( CertificateUtils.class.getClassLoader().getResourceAsStream(certificatePath), CERTIFICATE_NOT_FOUND_EXCEPTION_MESSAGE.apply(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 -> requireNotNull(certificateStream, EMPTY_INPUT_STREAM_EXCEPTION_MESSAGE), 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)) { return CertificateFactory.getInstance(CERTIFICATE_TYPE) .generateCertificates(bufferedCertificateStream).stream() .collect(toUnmodifiableList()); } catch (CertificateException | IOException e) { LOGGER.debug("There is no valid certificate present to parse. Please make sure to supply a valid der formatted certificate", e); return Collections.emptyList(); } } public static List getJdkTrustedCertificates() { return Stream.of(TrustManagerUtils.createTrustManagerWithJdkTrustedCertificates().getAcceptedIssuers()) .collect(toUnmodifiableList()); } public static List getSystemTrustedCertificates() { return TrustManagerUtils.createTrustManagerWithSystemTrustedCertificates() .map(X509TrustManager::getAcceptedIssuers) .map(Arrays::asList) .map(Collections::unmodifiableList) .orElseGet(Collections::emptyList); } public static List getCertificatesFromExternalSource(String url) { return CertificateExtractorUtils.getInstance().getCertificateFromExternalSource(url); } public static List getCertificatesFromExternalSource(Proxy proxy, String url) { return new CertificateExtractorUtils(proxy).getCertificateFromExternalSource(url); } public static List getCertificatesFromExternalSource(Proxy proxy, PasswordAuthentication passwordAuthentication, String url) { return new CertificateExtractorUtils(proxy, passwordAuthentication).getCertificateFromExternalSource(url); } public static List getCertificatesFromExternalSourceAsPem(String url) { return getCertificatesFromExternalSource(url).stream() .map(CertificateUtils::convertToPem) .collect(toUnmodifiableList()); } public static List getCertificatesFromExternalSourceAsPem(Proxy proxy, String url) { return getCertificatesFromExternalSource(proxy, url).stream() .map(CertificateUtils::convertToPem) .collect(toUnmodifiableList()); } public static List getCertificatesFromExternalSourceAsPem(Proxy proxy, PasswordAuthentication passwordAuthentication, String url) { return getCertificatesFromExternalSource(proxy, passwordAuthentication, url).stream() .map(CertificateUtils::convertToPem) .collect(toUnmodifiableList()); } public static Map> getCertificatesFromExternalSources(String... urls) { return getCertificatesFromExternalSources(Arrays.asList(urls)); } public static Map> getCertificatesFromExternalSources(Proxy proxy, String... urls) { return getCertificatesFromExternalSources(proxy, Arrays.asList(urls)); } public static Map> getCertificatesFromExternalSources(Proxy proxy, PasswordAuthentication passwordAuthentication, String... urls) { return getCertificatesFromExternalSources(proxy, passwordAuthentication, Arrays.asList(urls)); } public static Map> getCertificatesFromExternalSources(List urls) { return urls.stream() .distinct() .map(url -> new AbstractMap.SimpleEntry<>(url, getCertificatesFromExternalSource(url))) .collect(Collectors.collectingAndThen(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue, (key1, key2) -> key1, LinkedHashMap::new), Collections::unmodifiableMap)); } public static Map> getCertificatesFromExternalSources(Proxy proxy, List urls) { CertificateExtractorUtils certificateExtractorUtils = new CertificateExtractorUtils(proxy); return urls.stream() .distinct() .map(url -> new AbstractMap.SimpleEntry<>(url, certificateExtractorUtils.getCertificateFromExternalSource(url))) .collect(Collectors.collectingAndThen(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue, (key1, key2) -> key1, LinkedHashMap::new), Collections::unmodifiableMap)); } public static Map> getCertificatesFromExternalSources(Proxy proxy, PasswordAuthentication passwordAuthentication, List urls) { CertificateExtractorUtils certificateExtractorUtils = new CertificateExtractorUtils(proxy, passwordAuthentication); return urls.stream() .distinct() .map(url -> new AbstractMap.SimpleEntry<>(url, certificateExtractorUtils.getCertificateFromExternalSource(url))) .collect(Collectors.collectingAndThen(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue, (key1, key2) -> key1, LinkedHashMap::new), Collections::unmodifiableMap)); } public static Map> getCertificatesFromExternalSourcesAsPem(String... urls) { return getCertificatesFromExternalSourcesAsPem(Arrays.asList(urls)); } public static Map> getCertificatesFromExternalSourcesAsPem(Proxy proxy, String... urls) { return getCertificatesFromExternalSourcesAsPem(proxy, Arrays.asList(urls)); } public static Map> getCertificatesFromExternalSourcesAsPem(Proxy proxy, PasswordAuthentication passwordAuthentication, String... urls) { return getCertificatesFromExternalSourcesAsPem(proxy, passwordAuthentication, Arrays.asList(urls)); } public static Map> getCertificatesFromExternalSourcesAsPem(List urls) { return CertificateUtils.getCertificatesFromExternalSources(urls).entrySet().stream() .collect(Collectors.collectingAndThen(Collectors.toMap(Map.Entry::getKey, entry -> CertificateUtils.convertToPem(entry.getValue()), (key1, key2) -> key1, LinkedHashMap::new), Collections::unmodifiableMap)); } public static Map> getCertificatesFromExternalSourcesAsPem(Proxy proxy, List urls) { Map> certificates = CertificateUtils.getCertificatesFromExternalSources(proxy, urls).entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, entry -> CertificateUtils.convertToPem(entry.getValue()))); return Collections.unmodifiableMap(certificates); } public static Map> getCertificatesFromExternalSourcesAsPem(Proxy proxy, PasswordAuthentication passwordAuthentication, List urls) { return CertificateUtils.getCertificatesFromExternalSources(proxy, passwordAuthentication, urls).entrySet().stream() .collect(Collectors.collectingAndThen(Collectors.toMap(Map.Entry::getKey, entry -> CertificateUtils.convertToPem(entry.getValue()), (key1, key2) -> key1, LinkedHashMap::new), Collections::unmodifiableMap)); } public static List convertToPem(List certificates) { return certificates.stream() .map(CertificateUtils::convertToPem) .collect(toUnmodifiableList()); } 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(MAX_64_CHARACTER_LINE_SPLITTER)) .collect(toModifiableList()); 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); } } public static boolean isSelfSigned(T certificate) { try { certificate.verify(certificate.getPublicKey()); return true; } catch (SignatureException e) { return false; } catch (CertificateException | NoSuchAlgorithmException | InvalidKeyException | NoSuchProviderException e) { throw new GenericCertificateException(e); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy