47

Is it possible to sanely create an X509 Certificate in Java code without using the Bouncy Castle X509V*CertificateGenerator classes?

Ahmed Ashour
  • 5,179
  • 10
  • 35
  • 56
Yuliy
  • 17,381
  • 6
  • 41
  • 47

5 Answers5

88

Yes, but not with publicly documented classes. I've documented the process in this article.

import sun.security.x509.*;
import java.security.cert.*;
import java.security.*;
import java.math.BigInteger;
import java.util.Date;
import java.io.IOException

/** 
 * Create a self-signed X.509 Certificate
 * @param dn the X.509 Distinguished Name, eg "CN=Test, L=London, C=GB"
 * @param pair the KeyPair
 * @param days how many days from now the Certificate is valid for
 * @param algorithm the signing algorithm, eg "SHA1withRSA"
 */ 
X509Certificate generateCertificate(String dn, KeyPair pair, int days, String algorithm)
  throws GeneralSecurityException, IOException
{
  PrivateKey privkey = pair.getPrivate();
  X509CertInfo info = new X509CertInfo();
  Date from = new Date();
  Date to = new Date(from.getTime() + days * 86400000l);
  CertificateValidity interval = new CertificateValidity(from, to);
  BigInteger sn = new BigInteger(64, new SecureRandom());
  X500Name owner = new X500Name(dn);
 
  info.set(X509CertInfo.VALIDITY, interval);
  info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(sn));
  info.set(X509CertInfo.SUBJECT, new CertificateSubjectName(owner));
  info.set(X509CertInfo.ISSUER, new CertificateIssuerName(owner));
  info.set(X509CertInfo.KEY, new CertificateX509Key(pair.getPublic()));
  info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
  AlgorithmId algo = new AlgorithmId(AlgorithmId.md5WithRSAEncryption_oid);
  info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algo));
 
  // Sign the cert to identify the algorithm that's used.
  X509CertImpl cert = new X509CertImpl(info);
  cert.sign(privkey, algorithm);
 
  // Update the algorith, and resign.
  algo = (AlgorithmId)cert.get(X509CertImpl.SIG_ALG);
  info.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, algo);
  cert = new X509CertImpl(info);
  cert.sign(privkey, algorithm);
  return cert;
}   

Edit 2021 - unfortunately this approach won't work under Java 17, as the sun.* hierarchy can't be accessed. So it's back to BouncyCastle or a roll-your own ASN.1 serializer.

Mike B
  • 1,600
  • 1
  • 12
  • 8
  • 7
    A very good tip. Saved me from importing the dreaded (and beloved) Bouncycastle lib. Bloat begone! – Robert Foss Jul 08 '11 at 11:29
  • 22
    Is there a way to do it that DOESN'T involve calling in to sun.security.x509.*? Given that it is, in fact, not something you're supposed to use. – Markus Jevring Nov 26 '11 at 20:23
  • 1
    Excellent. Saved me a lot of work. Code is nice and clean. I am editing in the code to make sure it does not disappear is the blog goes down. – Suma Mar 20 '12 at 13:58
  • 1
    how do you add extensions? – maximz101 Jan 03 '13 at 07:14
  • 20
    With JDK 8 this should be: info.set(X509CertInfo.SUBJECT, owner); info.set(X509CertInfo.ISSUER, owner); – Axel Fontaine Oct 22 '14 at 23:31
  • 3
    The change @AxelFontaine mentioned is described here https://bugs.openjdk.java.net/browse/JDK-7198416 – Bozho Dec 26 '15 at 21:45
  • @meno Check line 2512 on https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/sun/security/tools/KeyTool.java – felipeptcho Aug 14 '17 at 22:09
  • 1
    How does this code set the BasicConstraint to true for the self-siged cert? – musterjunk Mar 29 '18 at 20:25
  • To set the basicConstrains (CA flag, path length) check: http://www.docjar.com/docs/api/sun/security/x509/BasicConstraintsExtension.html – Sajed Almorsy Nov 17 '20 at 21:07
  • X509CertInfo from which dependency is that? – Jacek Kaczmarek Jan 09 '22 at 22:26
  • X509CertInfo was sun.security.cert. But it's worth noting that this will no longer work under Java 17 as the sun.* hierarchy is locked down. – Mike B Jan 11 '22 at 08:23
  • @MikeB eh, I think the whole community is struggling with this issue. IMHO it's quite terrible that they blocked X509CertInfo without providing alternative. I made a general question about that: https://stackoverflow.com/questions/71441994/how-to-generate-self-signed-certificate-in-java-with-jdk17 It would be awesome if there would be slim BouncyCastle that would allow only certificate generation. – Wojtek Mar 11 '22 at 17:23
22

The ability to sign certificates is not part of a standard Java library or extension.

A lot of the code that is needed to do it yourself is part of the core. There are classes to encode and decode X.500 names, X.509 certificate extensions, public keys for various algorithms, and of course, for actually performing the digital signature.

Implementing this yourself is not trivial, but it is definitely doable—I probably spent 4 or 5 full days the first time I made a working prototype for certificate signing. It was a fantastic learning exercise for me, but it's hard to justify that expense when there are usable libraries available for free.

erickson
  • 265,237
  • 58
  • 395
  • 493
  • 4
    Is this still accurate as of 2017? – user674669 Oct 31 '17 at 20:32
  • No, the code snipped uses obsolete cryptography (MD5withRSA or SHA1withRSA in the Javadoc) for signing the certificate. Instead, I would recommend using either SHA256withRSA, SHA384withRSA, SHA512withRSA or SHA256withECDSA, SHA384withECDSA and SHA512withECDSA depending on the level of security you are designing for. – Daniel Gartmann Oct 23 '18 at 16:57
  • 1
    @DanielGartmann It's not clear what "code snipped" you are talking about. Is your "no" a reply to the previous commenter? – erickson Oct 23 '18 at 17:20
  • @erickson yes, I am obviously answering user674669's question. What I meant with "code snippet" is the code displayed in the first 2 answers below (in the grey area containing the source code). – Daniel Gartmann Oct 24 '18 at 18:36
  • 2
    @DanielGartmann The commenter is asking if my answer is still true in 2017. I think it is; you can't sign certificates out of the box, you have to build it yourself. So, if you are saying that's wrong, can you explain why? But your second comment makes it sound more like you are saying the other answers are wrong in the way they've applied the APIs that are provided (and, with that, I agree). – erickson Oct 24 '18 at 22:04
  • So, there is no class in the JCA (Java Criptography Arquitecture) that allows this, right? – Carlitos Way Dec 17 '19 at 21:24
  • @CarlitosWay Right. No supported API for it. – erickson Dec 17 '19 at 21:26
13
import sun.security.x509.*;

import java.security.cert.*;
import java.security.*;
import java.math.BigInteger;
import java.security.cert.Certificate;
import java.util.Date;
import java.io.IOException;

public class Example {
    /**
     * Create a self-signed X.509 Example
     *
     * @param dn        the X.509 Distinguished Name, eg "CN=Test, L=London, C=GB"
     * @param pair      the KeyPair
     * @param days      how many days from now the Example is valid for
     * @param algorithm the signing algorithm, eg "SHA1withRSA"
     */
    public X509Certificate generateCertificate(String dn, KeyPair pair, int days, String algorithm)
            throws GeneralSecurityException, IOException {
        PrivateKey privkey = pair.getPrivate();
        X509CertInfo info = new X509CertInfo();
        Date from = new Date();
        Date to = new Date(from.getTime() + days * 86400000l);
        CertificateValidity interval = new CertificateValidity(from, to);
        BigInteger sn = new BigInteger(64, new SecureRandom());
        X500Name owner = new X500Name(dn);

        info.set(X509CertInfo.VALIDITY, interval);
        info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(sn));
        info.set(X509CertInfo.SUBJECT, owner);
        info.set(X509CertInfo.ISSUER, owner);
        info.set(X509CertInfo.KEY, new CertificateX509Key(pair.getPublic()));
        info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
        AlgorithmId algo = new AlgorithmId(AlgorithmId.md5WithRSAEncryption_oid);
        info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algo));

        // Sign the cert to identify the algorithm that's used.
        X509CertImpl cert = new X509CertImpl(info);
        cert.sign(privkey, algorithm);

        // Update the algorith, and resign.
        algo = (AlgorithmId) cert.get(X509CertImpl.SIG_ALG);
        info.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, algo);
        cert = new X509CertImpl(info);
        cert.sign(privkey, algorithm);
        return cert;
    }

    public static void main (String[] argv) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        Example example = new Example();
        String distinguishedName = "CN=Test, L=London, C=GB";
        Certificate certificate = example.generateCertificateOriginal(distinguishedName, keyPair, 365, "SHA256withRSA");
        System.out.println("it worked!");
    }
}

I liked vbence's answer, but I kept getting the following exception:

java.security.cert.CertificateException: Subject class type invalid.

After lots of attempts to find out was a valid subject class I found out that X509CerInfo wanted an instance of X500Name.

1 info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(sn));
2 info.set(X509CertInfo.SUBJECT, new CertificateSubjectName(owner));
3 info.set(X509CertInfo.ISSUER, new CertificateIssuerName(owner));
4 info.set(X509CertInfo.KEY, new CertificateX509Key(pair.getPublic()));

So lines 2 & 3 needed to change to

2 info.set(X509CertInfo.SUBJECT, owner);
3 info.set(X509CertInfo.ISSUER, owner);
Clark Hobbie
  • 171
  • 1
  • 6
4

All the basic components to make a self-signed certificate (signing, X509 encoding etc) are available in JRE. Unlike BC, Sun's JCE doesn't provide any public calls to sign a certificate. However, all the functions are available in Keytool. You can simply copy the code from keytool to do this. The method you need to copy is doSelfCert().

ZZ Coder
  • 74,484
  • 29
  • 137
  • 169
  • 4
    Unfortunately, Keytool uses `sun.*` classes for this. So this won't work with every JRE. However here is the [source code](https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/sun/security/tools/KeyTool.java) – Pith Sep 14 '15 at 07:30
1

Depends on what exactly you want to do (and probably your definition of "Sanely"). As ZZ Coder pointed out, you can create a self-signed certificate directly by copying keytool. But I don't believe you can create a PKCS10 certificate request object with the standard JCE, which you probably need to do if you want to create standard CA-signed EECs.

Von
  • 4,365
  • 2
  • 29
  • 29
  • Hm, why not? Keytool can convert a self signed to a csr, you just need to copy that code as well. – eckes May 22 '15 at 20:27