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

io.spiffe.internal.CertificateUtils Maven / Gradle / Ivy

Go to download

Core functionality to fetch, process and validate X.509 and JWT SVIDs and Bundles from the Workload API.

There is a newer version: 0.8.11
Show newest version
package io.spiffe.internal;

import io.spiffe.spiffeid.SpiffeId;
import io.spiffe.spiffeid.TrustDomain;
import lombok.val;

import java.io.ByteArrayInputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateParsingException;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.security.spec.EncodedKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import static io.spiffe.internal.AsymmetricKeyAlgorithm.EC;
import static io.spiffe.internal.AsymmetricKeyAlgorithm.RSA;
import static io.spiffe.internal.KeyUsage.CRL_SIGN;
import static io.spiffe.internal.KeyUsage.DIGITAL_SIGNATURE;
import static io.spiffe.internal.KeyUsage.KEY_CERT_SIGN;
import static org.apache.commons.lang3.StringUtils.startsWith;

/**
 * Common certificate utility methods.
 */
public class CertificateUtils {

    private static final String SPIFFE_PREFIX = "spiffe://";
    private static final int SAN_VALUE_INDEX = 1;
    private static final String PUBLIC_KEY_INFRASTRUCTURE_ALGORITHM = "PKIX";
    private static final String X509_CERTIFICATE_TYPE = "X.509";

    private CertificateUtils() {
    }

    /**
     * Generates a list of X.509 certificates from a byte array.
     *
     * @param input as byte array representing a list of X.509 certificates, as a DER or PEM
     * @return a List of {@link X509Certificate}
     */
    public static List generateCertificates(final byte[] input) throws CertificateParsingException {
        if (input.length == 0) {
            throw new CertificateParsingException("No certificates found");
        }

        val certificateFactory = getCertificateFactory();

        Collection certificates;
        try {
            certificates = certificateFactory.generateCertificates(new ByteArrayInputStream(input));
        } catch (CertificateException e) {
            throw new CertificateParsingException("Certificate could not be parsed from cert bytes", e);
        }

        return certificates.stream()
                .map(X509Certificate.class::cast)
                .collect(Collectors.toList());
    }

    /**
     * Generates a private key from an array of bytes.
     *
     * @param privateKeyBytes is a PEM or DER PKCS#8 private key.
     * @return a instance of {@link PrivateKey}
     * @throws InvalidKeySpecException
     * @throws NoSuchAlgorithmException
     */
    public static PrivateKey generatePrivateKey(final byte[] privateKeyBytes, AsymmetricKeyAlgorithm algorithm, KeyFileFormat keyFileFormat) throws InvalidKeySpecException, NoSuchAlgorithmException, InvalidKeyException {
        EncodedKeySpec kspec = getEncodedKeySpec(privateKeyBytes, keyFileFormat);
        return generatePrivateKeyWithSpec(kspec, algorithm);
    }

    /**
     * Validate a certificate chain with a set of trusted certificates.
     *
     * @param chain        the certificate chain
     * @param trustedCerts to validate the certificate chain
     * @throws CertificateException
     * @throws CertPathValidatorException
     */
    public static void validate(final List chain, final Collection trustedCerts) throws CertificateException, CertPathValidatorException {
        if (chain == null || chain.size() == 0) {
            throw new IllegalArgumentException("Chain of certificates is empty");
        }
        val certificateFactory = getCertificateFactory();
        PKIXParameters pkixParameters;
        try {
            pkixParameters = toPkixParameters(trustedCerts);
            val certPath = certificateFactory.generateCertPath(chain);
            getCertPathValidator().validate(certPath, pkixParameters);
        } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException e) {
            throw new CertificateException(e);
        }
    }

    /**
     * Extracts the SPIFFE ID from an X.509 certificate.
     * 

* It iterates over the list of SubjectAlternativesNames, read each entry, takes the value from the index * defined in SAN_VALUE_INDEX and filters the entries that starts with the SPIFFE_PREFIX and returns the first. * * @param certificate a {@link X509Certificate} * @return an instance of a {@link SpiffeId} * @throws CertificateException if the certificate contains multiple SPIFFE IDs, or does not contain any, or * the SAN extension cannot be decoded */ public static SpiffeId getSpiffeId(final X509Certificate certificate) throws CertificateException { val spiffeIds = getSpiffeIds(certificate); if (spiffeIds.size() > 1) { throw new CertificateException("Certificate contains multiple SPIFFE IDs"); } if (spiffeIds.size() < 1) { throw new CertificateException("Certificate does not contain SPIFFE ID in the URI SAN"); } return SpiffeId.parse(spiffeIds.get(0)); } /** * Extracts the trust domain of a chain of certificates. * * @param chain a list of {@link X509Certificate} * @return a {@link TrustDomain} * @throws CertificateException */ public static TrustDomain getTrustDomain(final List chain) throws CertificateException { val spiffeId = getSpiffeId(chain.get(0)); return spiffeId.getTrustDomain(); } public static boolean isCA(final X509Certificate cert) { return cert.getBasicConstraints() != -1; } public static boolean hasKeyUsageCertSign(final X509Certificate cert) { boolean[] keyUsage = cert.getKeyUsage(); return keyUsage[KEY_CERT_SIGN.index()]; } public static boolean hasKeyUsageDigitalSignature(final X509Certificate cert) { boolean[] keyUsage = cert.getKeyUsage(); return keyUsage[DIGITAL_SIGNATURE.index()]; } public static boolean hasKeyUsageCRLSign(final X509Certificate cert) { boolean[] keyUsage = cert.getKeyUsage(); return keyUsage[CRL_SIGN.index()]; } private static EncodedKeySpec getEncodedKeySpec(final byte[] privateKeyBytes, KeyFileFormat keyFileFormat) throws InvalidKeyException { EncodedKeySpec keySpec; if (keyFileFormat == KeyFileFormat.PEM) { byte[] keyDer = toDerFormat(privateKeyBytes); keySpec = new PKCS8EncodedKeySpec(keyDer); } else { keySpec = new PKCS8EncodedKeySpec(privateKeyBytes); } return keySpec; } private static List getSpiffeIds(final X509Certificate certificate) throws CertificateParsingException { if (certificate.getSubjectAlternativeNames() == null) { return Collections.emptyList(); } return certificate.getSubjectAlternativeNames() .stream() .map(san -> (String) san.get(SAN_VALUE_INDEX)) .filter(uri -> startsWith(uri, SPIFFE_PREFIX)) .collect(Collectors.toList()); } private static PrivateKey generatePrivateKeyWithSpec(final EncodedKeySpec keySpec, AsymmetricKeyAlgorithm algorithm) throws NoSuchAlgorithmException, InvalidKeySpecException { PrivateKey privateKey = null; switch (algorithm) { case EC: privateKey = KeyFactory.getInstance(EC.value()).generatePrivate(keySpec); break; case RSA: privateKey = KeyFactory.getInstance(RSA.value()).generatePrivate(keySpec); } return privateKey; } // Create an instance of PKIXParameters used as input for the PKIX CertPathValidator private static PKIXParameters toPkixParameters(final Collection trustedCerts) throws CertificateException, InvalidAlgorithmParameterException { if (trustedCerts == null || trustedCerts.isEmpty()) { throw new CertificateException("No trusted Certs"); } val pkixParameters = new PKIXParameters(trustedCerts.stream() .map(c -> new TrustAnchor(c, null)) .collect(Collectors.toSet())); pkixParameters.setRevocationEnabled(false); return pkixParameters; } // Get the default PKIX CertPath Validator private static CertPathValidator getCertPathValidator() throws NoSuchAlgorithmException { return CertPathValidator.getInstance(PUBLIC_KEY_INFRASTRUCTURE_ALGORITHM); } // Get the X.509 Certificate Factory private static CertificateFactory getCertificateFactory() { try { return CertificateFactory.getInstance(X509_CERTIFICATE_TYPE); } catch (CertificateException e) { throw new IllegalStateException("Could not create Certificate Factory", e); } } // Given a private key in PEM format, encode it as DER private static byte[] toDerFormat(final byte[] privateKeyPem) throws InvalidKeyException { String privateKeyAsString = new String(privateKeyPem); privateKeyAsString = privateKeyAsString.replaceAll("(-+BEGIN PRIVATE KEY-+\\r?\\n|-+END PRIVATE KEY-+\\r?\\n?)", ""); privateKeyAsString = privateKeyAsString.replaceAll("\n", ""); val decoder = Base64.getDecoder(); try { return decoder.decode(privateKeyAsString); } catch (Exception e) { throw new InvalidKeyException(e); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy