net.ripe.rpki.commons.crypto.x509cert.X509CertificateBuilderHelper.java Source code

Java tutorial

Introduction

Here is the source code for net.ripe.rpki.commons.crypto.x509cert.X509CertificateBuilderHelper.java

Source

/**
 * The BSD License
 *
 * Copyright (c) 2010-2012 RIPE NCC
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *   - Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *   - Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *   - Neither the name of the RIPE NCC nor the names of its contributors may be
 *     used to endorse or promote products derived from this software without
 *     specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package net.ripe.rpki.commons.crypto.x509cert;

import net.ripe.ipresource.IpResourceSet;
import net.ripe.ipresource.IpResourceType;
import net.ripe.rpki.commons.crypto.ValidityPeriod;
import net.ripe.rpki.commons.crypto.rfc3779.ResourceExtensionEncoder;
import net.ripe.rpki.commons.crypto.util.BouncyCastleUtil;
import org.apache.commons.lang.Validate;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.x509.AccessDescription;
import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.CRLDistPoint;
import org.bouncycastle.asn1.x509.DistributionPoint;
import org.bouncycastle.asn1.x509.DistributionPointName;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.PolicyInformation;
import org.bouncycastle.asn1.x509.X509Extension;
import org.bouncycastle.cert.CertIOException;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;

import javax.security.auth.x500.X500Principal;
import java.math.BigInteger;
import java.net.URI;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.EnumSet;

/**
 * Fairly generic helper for X509CertificateBuilders. Intended to be used by
 * (delegated to, not extended) specific certificate builders.
 * <p/>
 * Because we want to maintain the pattern where a specific Certificate builder
 * can be chained like: builder.withValidity(val).withSubjectDn(subject) etc...
 * dynamic typing would be required.. hence delegation.
 */
public final class X509CertificateBuilderHelper {

    public static final String DEFAULT_SIGNATURE_ALGORITHM = "SHA256withRSA";

    public static final String DEFAULT_SIGNATURE_PROVIDER = "SunRsaSign";

    private String signatureProvider = DEFAULT_SIGNATURE_PROVIDER;

    private String signatureAlgorithm = DEFAULT_SIGNATURE_ALGORITHM;

    private BigInteger serial;

    private X500Principal subjectDN;

    private X500Principal issuerDN;

    private ValidityPeriod validityPeriod;

    private IpResourceSet resources;

    private PublicKey publicKey;

    private KeyPair signingKeyPair;

    private int keyUsage;

    private boolean ca;

    private boolean addSubjectKeyIdentifier = true;

    private boolean addAuthorityKeyIdentifier = true;

    private URI[] crlDistributionPoints;

    private AccessDescription[] authorityInformationAccess;

    private AccessDescription[] subjectInformationAccess;

    private PolicyInformation[] policies;

    private EnumSet<IpResourceType> inheritedResourceTypes = EnumSet.noneOf(IpResourceType.class);

    public X509CertificateBuilderHelper withSignatureProvider(String signatureProvider) {
        this.signatureProvider = signatureProvider;
        return this;
    }

    public X509CertificateBuilderHelper withSerial(BigInteger serial) {
        this.serial = serial;
        return this;
    }

    public X509CertificateBuilderHelper withSubjectDN(X500Principal subjectDN) {
        this.subjectDN = subjectDN;
        return this;
    }

    public X509CertificateBuilderHelper withIssuerDN(X500Principal issuerDN) {
        this.issuerDN = issuerDN;
        return this;
    }

    public X509CertificateBuilderHelper withValidityPeriod(ValidityPeriod validityPeriod) {
        this.validityPeriod = validityPeriod;
        return this;
    }

    public X509CertificateBuilderHelper withResources(IpResourceSet resources) {
        this.resources = resources;
        return this;
    }

    public X509CertificateBuilderHelper withPublicKey(PublicKey publicKey) {
        this.publicKey = publicKey;
        return this;
    }

    public X509CertificateBuilderHelper withSigningKeyPair(KeyPair signingKey) {
        this.signingKeyPair = signingKey;
        return this;
    }

    /**
     * Careful! You probably want to stick to the default. This method is here
     * mainly to allow for testing the parser -> it should reject sig algos not
     * allowed by RFC
     */
    public X509CertificateBuilderHelper withSignatureAlgorithm(String signatureAlgorithm) {
        this.signatureAlgorithm = signatureAlgorithm;
        return this;
    }

    public X509CertificateBuilderHelper withKeyUsage(int keyUsage) {
        this.keyUsage = keyUsage;
        return this;
    }

    public X509CertificateBuilderHelper withCa(boolean ca) {
        this.ca = ca;
        return this;
    }

    public X509CertificateBuilderHelper withSubjectKeyIdentifier(boolean add) {
        this.addSubjectKeyIdentifier = add;
        return this;
    }

    public X509CertificateBuilderHelper withAuthorityKeyIdentifier(boolean add) {
        this.addAuthorityKeyIdentifier = add;
        return this;
    }

    public X509CertificateBuilderHelper withCrlDistributionPoints(URI... uris) {
        this.crlDistributionPoints = uris;
        return this;
    }

    public X509CertificateBuilderHelper withAuthorityInformationAccess(
            X509CertificateInformationAccessDescriptor... descriptors) {
        authorityInformationAccess = X509CertificateInformationAccessDescriptor
                .convertAccessDescriptors(descriptors);
        return this;
    }

    public X509CertificateBuilderHelper withSubjectInformationAccess(
            X509CertificateInformationAccessDescriptor... descriptors) {
        subjectInformationAccess = X509CertificateInformationAccessDescriptor.convertAccessDescriptors(descriptors);
        return this;
    }

    public X509CertificateBuilderHelper withPolicies(PolicyInformation... policies) {
        this.policies = policies;
        return this;
    }

    public X509CertificateBuilderHelper withInheritedResourceTypes(EnumSet<IpResourceType> resourceTypes) {
        this.inheritedResourceTypes = EnumSet.copyOf(resourceTypes);
        return this;
    }

    public X509Certificate generateCertificate() {
        X509v3CertificateBuilder certificateGenerator = createCertificateGenerator();
        try {
            ContentSigner signer = new JcaContentSignerBuilder(signatureAlgorithm).setProvider(signatureProvider)
                    .build(signingKeyPair.getPrivate());
            return new JcaX509CertificateConverter().getCertificate(certificateGenerator.build(signer));
        } catch (CertificateEncodingException e) {
            throw new X509ResourceCertificateBuilderException(e);
        } catch (IllegalStateException e) {
            throw new X509ResourceCertificateBuilderException(e);
        } catch (OperatorCreationException e) {
            throw new X509ResourceCertificateBuilderException(e);
        } catch (CertificateException e) {
            throw new X509ResourceCertificateBuilderException(e);
        }
    }

    /**
     * Override this to add your extensions to the certificate generator
     */
    protected X509v3CertificateBuilder createCertificateGenerator() {
        try {
            X509v3CertificateBuilder generator = createX509V3CertificateGenerator();

            if (addSubjectKeyIdentifier) {
                addSubjectKeyIdentifier(generator);
            }
            if (addAuthorityKeyIdentifier) {
                addAuthorityKeyIdentifier(generator);
            }
            if (ca) {
                addCaBit(generator);
            }
            if (keyUsage != 0) {
                addKeyUsage(generator);
            }
            if (authorityInformationAccess != null) {
                addAIA(generator);
            }
            if (subjectInformationAccess != null) {
                addSIA(generator);
            }
            if (crlDistributionPoints != null) {
                Validate.noNullElements(crlDistributionPoints);
                addCrlDistributionPoints(generator);
            }
            if (policies != null && policies.length > 0) {
                addPolicies(generator);
            }
            if (resources != null) {
                addResourceExtensions(generator);
            }
            return generator;
        } catch (CertIOException e) {
            throw new X509ResourceCertificateBuilderException(e);
        } catch (InvalidKeyException e) {
            throw new X509ResourceCertificateBuilderException(e);
        } catch (NoSuchAlgorithmException e) {
            throw new X509ResourceCertificateBuilderException(e);
        }
    }

    private X509v3CertificateBuilder createX509V3CertificateGenerator() {
        validateCertificateFields();

        PublicKey key = publicKey;
        X509v3CertificateBuilder generator = new X509v3CertificateBuilder(
                BouncyCastleUtil.principalToName(issuerDN), serial,
                new Date(validityPeriod.getNotValidBefore().getMillis()),
                new Date(validityPeriod.getNotValidAfter().getMillis()),
                BouncyCastleUtil.principalToName(subjectDN), BouncyCastleUtil.createSubjectPublicKeyInfo(key));
        return generator;
    }

    private void validateCertificateFields() {
        Validate.notNull(issuerDN, "no issuerDN");
        Validate.notNull(subjectDN, "no subjectDN");
        Validate.notNull(serial, "no serial");
        Validate.notNull(publicKey, "no publicKey");
        Validate.notNull(signingKeyPair, "no signingKeyPair");
        Validate.notNull(validityPeriod, "no validityPeriod");
        if (!ca) {
            Validate.isTrue((keyUsage & KeyUsage.keyCertSign) == 0, "keyCertSign only allowed for ca");
        }
    }

    private void addSubjectKeyIdentifier(X509v3CertificateBuilder generator)
            throws InvalidKeyException, CertIOException, NoSuchAlgorithmException {
        generator.addExtension(X509Extension.subjectKeyIdentifier, false,
                new JcaX509ExtensionUtils().createSubjectKeyIdentifier(publicKey));
    }

    private void addAuthorityKeyIdentifier(X509v3CertificateBuilder generator)
            throws InvalidKeyException, CertIOException {
        generator.addExtension(X509Extension.authorityKeyIdentifier, false,
                BouncyCastleUtil.createAuthorityKeyIdentifier(signingKeyPair.getPublic()));
    }

    private void addCaBit(X509v3CertificateBuilder generator) throws CertIOException {
        generator.addExtension(X509Extension.basicConstraints, true, new BasicConstraints(ca));
    }

    private void addKeyUsage(X509v3CertificateBuilder generator) throws CertIOException {
        generator.addExtension(X509Extension.keyUsage, true, new KeyUsage(keyUsage));
    }

    private void addAIA(X509v3CertificateBuilder generator) throws CertIOException {
        generator.addExtension(X509Extension.authorityInfoAccess, false,
                AuthorityInformationAccess.getInstance(new DERSequence(authorityInformationAccess)));
    }

    private void addSIA(X509v3CertificateBuilder generator) throws CertIOException {
        generator.addExtension(X509Extension.subjectInfoAccess, false,
                AuthorityInformationAccess.getInstance(new DERSequence(subjectInformationAccess)));
    }

    private void addCrlDistributionPoints(X509v3CertificateBuilder generator) throws CertIOException {
        CRLDistPoint crldp = convertToCrlDistributionPoint(crlDistributionPoints);
        generator.addExtension(X509Extension.cRLDistributionPoints, false, crldp);
    }

    private void addPolicies(X509v3CertificateBuilder generator) throws CertIOException {
        generator.addExtension(X509Extension.certificatePolicies, true, new DERSequence(policies));
    }

    private void addResourceExtensions(X509v3CertificateBuilder generator) throws CertIOException {
        ResourceExtensionEncoder encoder = new ResourceExtensionEncoder();

        for (IpResourceType inherited : inheritedResourceTypes) {
            if (resources.containsType(inherited)) {
                throw new IllegalArgumentException(
                        "resource set '" + resources + "' contains resources of inherited type " + inherited);
            }
        }

        ASN1Encodable encodedIPAddressBlocks = encoder.encodeIpAddressBlocks(
                inheritedResourceTypes.contains(IpResourceType.IPv4),
                inheritedResourceTypes.contains(IpResourceType.IPv6), resources);
        if (encodedIPAddressBlocks != null) {
            generator.addExtension(ResourceExtensionEncoder.OID_IP_ADDRESS_BLOCKS, true, encodedIPAddressBlocks);
        }

        ASN1Encodable encodedASNs = encoder.encodeAsIdentifiers(inheritedResourceTypes.contains(IpResourceType.ASN),
                resources);
        if (encodedASNs != null) {
            generator.addExtension(ResourceExtensionEncoder.OID_AUTONOMOUS_SYS_IDS, true, encodedASNs);
        }
    }

    /**
     * Generate a single distribution point where the names contains each URI.
     */
    private CRLDistPoint convertToCrlDistributionPoint(URI[] uris) {
        GeneralName[] seq = new GeneralName[uris.length];
        for (int i = 0; i < uris.length; ++i) {
            seq[i] = new GeneralName(GeneralName.uniformResourceIdentifier, uris[i].toString());
        }
        GeneralNames names = new GeneralNames(seq);
        DistributionPointName distributionPoint = new DistributionPointName(names);
        DistributionPoint[] dps = { new DistributionPoint(distributionPoint, null, null) };
        return new CRLDistPoint(dps);
    }
}