Self signed X509 Certificate with Bouncy Castle in Java
Asked Answered
O

5

28

I need to create a self signed X509 Certificate with Bouncy Castle in Java, but every class I try to include is deprecated. How can I solve this? Is there some other class to include? Thanks

Omnivorous answered 24/4, 2015 at 16:4 Comment(0)
L
39

Using Bouncycastle latest version - 1.55 1.66

Update to the answer by @Bewusstsein. The bouncycastle classes are deprecated in the latest version as of this answer (5/11/2017). If you are using version 1.55 or later:

public static Certificate selfSign(KeyPair keyPair, String subjectDN) throws OperatorCreationException, CertificateException, IOException
{
    Provider bcProvider = new BouncyCastleProvider();
    Security.addProvider(bcProvider);

    long now = System.currentTimeMillis();
    Date startDate = new Date(now);

    X500Name dnName = new X500Name(subjectDN);
    BigInteger certSerialNumber = new BigInteger(Long.toString(now)); // <-- Using the current timestamp as the certificate serial number

    Calendar calendar = Calendar.getInstance();
    calendar.setTime(startDate);
    calendar.add(Calendar.YEAR, 1); // <-- 1 Yr validity

    Date endDate = calendar.getTime();

    String signatureAlgorithm = "SHA256WithRSA"; // <-- Use appropriate signature algorithm based on your keyPair algorithm.

    ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithm).build(keyPair.getPrivate());

    JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(dnName, certSerialNumber, startDate, endDate, dnName, keyPair.getPublic());

    // Extensions --------------------------

    // Basic Constraints
    BasicConstraints basicConstraints = new BasicConstraints(true); // <-- true for CA, false for EndEntity

    certBuilder.addExtension(new ASN1ObjectIdentifier("2.5.29.19"), true, basicConstraints); // Basic Constraints is usually marked as critical.

    // -------------------------------------

    return new JcaX509CertificateConverter().setProvider(bcProvider).getCertificate(certBuilder.build(contentSigner));
}
Larochelle answered 11/5, 2017 at 14:29 Comment(5)
Learning experience: use SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()); or your X.509 certificate won't be compatible with Java keystore. The issue is that the key generation algorithm of the public key is different from the signature algorithm itself. Funny enough, trying AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE) directly failed.Postconsonantal
Reused your code here. Note that it fails with your original code. Nevertheless, thanks for making sure I did not have to figure this out from base myself. Requested upvotes for this answer, hoping you'll get some more rep for it.Postconsonantal
@Bodewes Your are right, I didn't test it by storing in the keystore. Thanks for testing it out. The corrected code should now work with Java Keystore. You could use the corrected code in your other answer.Larochelle
Aren't there any constants avaliable for those ASN1 ID's? (like "2.5.29.19")Aleut
ok, I found that here: org.bouncycastle.asn1.x509.Extension.basicConstraintsAleut
S
11

BEWARE: This answer uses an old version of the library with 11 CVEs.

Here's what i'm using (with BouncyCastle v1.38):

import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.SignatureException;
import java.util.Date;

import javax.security.auth.x500.X500Principal;

import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;

import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.x509.X509V3CertificateGenerator;

public class BouncyCastle {

    public static void main(String[] args) throws CertificateEncodingException, InvalidKeyException, IllegalStateException, NoSuchProviderException, NoSuchAlgorithmException, SignatureException {
        X509Certificate selfSignedX509Certificate = new BouncyCastle().generateSelfSignedX509Certificate();
        System.out.println(selfSignedX509Certificate);
    }

    public X509Certificate generateSelfSignedX509Certificate() throws CertificateEncodingException, InvalidKeyException, IllegalStateException,
            NoSuchProviderException, NoSuchAlgorithmException, SignatureException {
        addBouncyCastleAsSecurityProvider();

        // generate a key pair
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
        keyPairGenerator.initialize(4096, new SecureRandom());
        KeyPair keyPair = keyPairGenerator.generateKeyPair();

        // build a certificate generator
        X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
        X500Principal dnName = new X500Principal("cn=example");

        // add some options
        certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
        certGen.setSubjectDN(new X509Name("dc=name"));
        certGen.setIssuerDN(dnName); // use the same
        // yesterday
        certGen.setNotBefore(new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000));
        // in 2 years
        certGen.setNotAfter(new Date(System.currentTimeMillis() + 2 * 365 * 24 * 60 * 60 * 1000));
        certGen.setPublicKey(keyPair.getPublic());
        certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
        certGen.addExtension(X509Extensions.ExtendedKeyUsage, true,
                new ExtendedKeyUsage(KeyPurposeId.id_kp_timeStamping));

        // finally, sign the certificate with the private key of the same KeyPair
        X509Certificate cert = certGen.generate(keyPair.getPrivate(), "BC");
        return cert;
    }

    public void addBouncyCastleAsSecurityProvider() {
        Security.addProvider(new BouncyCastleProvider());
    }
}

For certGen.generate(keyPair.getPrivate(), "BC"); to work, BouncyCastle has to be added as a Security Provider.

I confirmed that it works with this maven dependency:

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk16</artifactId>
    <version>1.38</version>
</dependency>
Secretin answered 24/4, 2015 at 16:45 Comment(3)
Depending on the intended purpose of the certificate, you might want to add further extensions (using addExtension as shown in the answer). The current March 2015 version of Bouncycastle for Java is 1.52Lytic
Can you convert this code to SSCCE? I am unable to find the dependencies you usedPenile
@Penile Done, I've just tested it and it works for me.Secretin
T
7

Here's a complete self-signed ECDSA certificate generator that creates certificates usable in TLS connections on both client and server side. It was tested with BouncyCastle 1.57. Similar code can be used to create RSA certificates.

SecureRandom random = new SecureRandom();

// create keypair
KeyPairGenerator keypairGen = KeyPairGenerator.getInstance("EC");
keypairGen.initialize(256, random);
KeyPair keypair = keypairGen.generateKeyPair();

// fill in certificate fields
X500Name subject = new X500NameBuilder(BCStyle.INSTANCE)
    .addRDN(BCStyle.CN, "stackoverflow.com")
    .build();
byte[] id = new byte[20];
random.nextBytes(id);
BigInteger serial = new BigInteger(160, random);
X509v3CertificateBuilder certificate = new JcaX509v3CertificateBuilder(
    subject,
    serial,
    Date.from(LocalDate.of(2000, 1, 1).atStartOfDay(ZoneOffset.UTC).toInstant()),
    Date.from(LocalDate.of(2035, 1, 1).atStartOfDay(ZoneOffset.UTC).toInstant()),
    subject,
    keypair.getPublic());
certificate.addExtension(Extension.subjectKeyIdentifier, false, id);
certificate.addExtension(Extension.authorityKeyIdentifier, false, id);
BasicConstraints constraints = new BasicConstraints(true);
certificate.addExtension(
    Extension.basicConstraints,
    true,
    constraints.getEncoded());
KeyUsage usage = new KeyUsage(KeyUsage.keyCertSign | KeyUsage.digitalSignature);
certificate.addExtension(Extension.keyUsage, false, usage.getEncoded());
ExtendedKeyUsage usageEx = new ExtendedKeyUsage(new KeyPurposeId[] {
    KeyPurposeId.id_kp_serverAuth,
    KeyPurposeId.id_kp_clientAuth
});
certificate.addExtension(
    Extension.extendedKeyUsage,
    false,
    usageEx.getEncoded());

// build BouncyCastle certificate
ContentSigner signer = new JcaContentSignerBuilder("SHA256withECDSA")
    .build(keypair.getPrivate());
X509CertificateHolder holder = certificate.build(signer);

// convert to JRE certificate
JcaX509CertificateConverter converter = new JcaX509CertificateConverter();
converter.setProvider(new BouncyCastleProvider());
X509Certificate x509 = converter.getCertificate(holder);

// serialize in DER format
byte[] serialized = x509.getEncoded();
Tuberous answered 8/7, 2017 at 1:20 Comment(2)
It works! I had to replace "EC" with "RSA" with keypairGen.initialize(512, random); and "SHA256withECDSA" with "SHA256withRSA" however.Hap
@EvgeniyPhilippov Please do not post insecure parameters (RSA requires a key size of 2048 bits or more) without warning.Postconsonantal
A
4

Using Bouncycastle version 1.76 (July 2023)

Here's an update for the latest BC Version.
This code was tested with JDK 17.
Also, where possible, I've tried to use available constants, rather than Strings.

package uk.co.bc;

import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.HexFormat;

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERUTF8String;
import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.bc.BcX509ExtensionUtils;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;

public class GenerateSelfSigned {

    private static final Provider      BC_PROVIDER = new BouncyCastleProvider();
    private static final SecureRandom  PRNG        = new SecureRandom();

    public  static void main(final String[] args) throws Exception {

        Security.insertProviderAt(BC_PROVIDER, 1);

        final var keyPair     = getKeyPair("RSA", 4096);

        final var x500subject = getSubject();
        final var x509Cert    = getSelfSignedCert(keyPair, x500subject, Validity.ofYears(100), "SHA256WithRSA");
        /*
         * Load Certificate into freshly created Keystore...
         */
        final var pwChars     = "password".toCharArray();
        final var keyStore    = getKeyStore("PKCS12", keyPair, pwChars, "alias", x509Cert);
        /*
         * Write Certificate & Keystore to disk...
         */
        final var fileName    = "self.signed.x509_" + HexFormat.of().toHexDigits(System.currentTimeMillis());

        Files.write   (Path.of(             fileName +".cer"), x509Cert.getEncoded());

        keyStore.store(new FileOutputStream(fileName +".p12"), pwChars);
    }

    private static KeyStore getKeyStore(final String keyStoreType, final KeyPair keyPair, final char[] pwChars, final String alias, final X509Certificate x509Cert) throws Exception {

        final var keyStore = KeyStore.getInstance(keyStoreType);
        ;         keyStore.load(null, pwChars);
        ;         keyStore.setKeyEntry(alias, keyPair.getPrivate(), pwChars, new X509Certificate[] {x509Cert});

        return    keyStore;
    }

    private static KeyPair getKeyPair(final String algorithm, final int keysize) throws NoSuchAlgorithmException {

        final var keyPairGenerator = KeyPairGenerator.getInstance(algorithm, BC_PROVIDER);
        ;         keyPairGenerator.initialize(keysize, PRNG);

        return    keyPairGenerator.generateKeyPair();
    }

    private static  X500Name getSubject() {

        return  new X500Name(new RDN[] {new RDN (
                new AttributeTypeAndValue[] {

                new AttributeTypeAndValue(BCStyle.CN, new DERUTF8String("Common Name")),
                new AttributeTypeAndValue(BCStyle.OU, new DERUTF8String("Organisational Unit name")),
                new AttributeTypeAndValue(BCStyle.O,  new DERUTF8String("Organisation")),
                new AttributeTypeAndValue(BCStyle.L,  new DERUTF8String("Locality name")),
                new AttributeTypeAndValue(BCStyle.ST, new DERUTF8String("State or Province name")),
                new AttributeTypeAndValue(BCStyle.C,  new DERUTF8String("uk"))
        }) });
    }

    private static X509Certificate getSelfSignedCert(final KeyPair keyPair, final X500Name subject, final Validity validity, final String signatureAlgorithm) throws Exception {

        final var sn               = new BigInteger(Long.SIZE, PRNG);

        final var issuer           = subject;

        final var keyPublic        = keyPair  .getPublic();
        final var keyPublicEncoded = keyPublic.getEncoded();
        final var keyPublicInfo    = SubjectPublicKeyInfo.getInstance(keyPublicEncoded);
        /*
         * First, some fiendish trickery to generate the Subject (Public-) Key Identifier...
         */
        try(final var ist = new ByteArrayInputStream(keyPublicEncoded);
            final var ais = new      ASN1InputStream(ist))
        {
            final var asn1Sequence         = (ASN1Sequence) ais.readObject();

            final var subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(asn1Sequence);
            final var subjectPublicKeyId   = new BcX509ExtensionUtils().createSubjectKeyIdentifier(subjectPublicKeyInfo);

            /*
             * Now build the Certificate, add some Extensions & sign it with our own Private Key...
             */
            final var certBuilder          = new X509v3CertificateBuilder(issuer, sn, validity.notBefore, validity.notAfter, subject, keyPublicInfo);
            final var contentSigner        = new  JcaContentSignerBuilder(signatureAlgorithm).build(keyPair.getPrivate());
            /*
             * BasicConstraints instantiated with "CA=true"
             * The BasicConstraints Extension is usually marked "critical=true"
             * 
             * The Subject Key Identifier extension identifies the public key certified by this certificate.
             * This extension provides a way of distinguishing public keys if more than one is available for
             * a given subject name.
             */
            final var certHolder           = certBuilder
                    .addExtension(Extension.basicConstraints,     true,  new BasicConstraints(true))
                    .addExtension(Extension.subjectKeyIdentifier, false, subjectPublicKeyId)
                    .build(contentSigner);

            return new JcaX509CertificateConverter().setProvider(BC_PROVIDER).getCertificate(certHolder);
        }
    }

    private static final record Validity(Date notBefore, Date notAfter) {

        private static Validity ofYears(final int count) {

            final var zdtNotBefore = ZonedDateTime.now();
            final var zdtNotAfter  = zdtNotBefore.plusYears(count);

            return              of(zdtNotBefore.toInstant(), zdtNotAfter.toInstant());
        }
        private static Validity of(final Instant notBefore,  final Instant notAfter) {
            return new Validity   (Date.from    (notBefore), Date.from    (notAfter));
        }
    }
}
Aleut answered 14/10, 2023 at 13:6 Comment(0)
A
3

This is the code used from BouncyCastle it self to generate X.509 Certificates. You will need this and this library from BC to use it. For further details on how to use it, maybe take a look at this question (Main class).

    public class BCCertGen {
    public static String _country = "Westeros",
                         _organisation = "Targaryen",
                         _location = "Valyria",
                         _state = "Essos",
                         _issuer = "Some Trusted CA";

    public BCCertGen(String country, String organisation, String location, String state, String issuer){
        _country = country;
        _organisation = organisation;
        _location = location;
        _state = state;
        _issuer = issuer;
    }
    public static X509Certificate generate(PrivateKey privKey, PublicKey pubKey, int duration, String signAlg, boolean isSelfSigned) throws Exception{
        Provider BC = new BouncyCastleProvider();

        // distinguished name table.
        X500NameBuilder builder = createStdBuilder();

        // create the certificate
        ContentSigner sigGen = new JcaContentSignerBuilder(signAlg).build(privKey);
        X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(
                new X500Name("cn="+_issuer),    //Issuer
                BigInteger.valueOf(1),      //Serial
                new Date(System.currentTimeMillis() - 50000),   //Valid from
                new Date((long)(System.currentTimeMillis() + duration*8.65*Math.pow(10,7))),    //Valid to
                builder.build(),    //Subject
                pubKey              //Publickey to be associated with the certificate
        );

        X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));

        cert.checkValidity(new Date());

        if (isSelfSigned) {
            // check verifies in general
            cert.verify(pubKey);
            // check verifies with contained key
            cert.verify(cert.getPublicKey());
        }

        ByteArrayInputStream bIn = new ByteArrayInputStream(cert.getEncoded());
        CertificateFactory fact = CertificateFactory.getInstance("X.509", BC);

        return (X509Certificate) fact.generateCertificate(bIn);
    }

    private static X500NameBuilder createStdBuilder() {
        X500NameBuilder builder = new X500NameBuilder(RFC4519Style.INSTANCE);

        builder.addRDN(RFC4519Style.c, _country);
        builder.addRDN(RFC4519Style.o, _organisation);
        builder.addRDN(RFC4519Style.l, _location);
        builder.addRDN(RFC4519Style.st, _state);

        return builder;
    }
}

EDIT: I can't remember from which BC test I took it exactly, but here is something similar https://github.com/bcgit/bc-java/blob/master/prov/src/test/java/org/bouncycastle/pqc/jcajce/provider/test/KeyStoreTest.java

Arsphenamine answered 10/12, 2018 at 21:37 Comment(3)
I just noticed that I downvoted - I don't know how that occurred it must have been a mis-click. I can't undo this unless the question gets edited - maybe you could edit it with say, a comment to say where you sourced that code from - then I can undo my downvote. Sorry about this, never happened before!Pineal
@Pineal No problem, thank you for the answer, I edit itArsphenamine
Sadly this is nonconforming. Most (X500)Name attributes can be any text up to certain size limits, but id-at-countryName (2.5.4.6) must be a two-letter country code from ISO 3166, which except some special cases goes by United Nations membership. See appendix A to rfc5280 at page 115 -- or more directly rfc4519 2.2 and rfc4517 3.3.4. Although a lot of software -- including Bouncy -- doesn't check/enforce this.Tonitonia

© 2022 - 2024 — McMap. All rights reserved.