May 30, 2018

How to Create a X509 Certificate in Java without BouncyCastle?

Introduction

The Java Keytool project has most of the code to create x509 certificates in java, but it has dependency to sun class, which are deprecated, which means that they can change. So be carefully to test this code for different JRE before going into production.

How to Create a X509 Certificate in Java without BouncyCastle?

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Vector;

import sun.security.util.ObjectIdentifier;
import sun.security.x509.AccessDescription;
import sun.security.x509.AlgorithmId;
import sun.security.x509.AuthorityInfoAccessExtension;
import sun.security.x509.AuthorityKeyIdentifierExtension;
import sun.security.x509.BasicConstraintsExtension;
import sun.security.x509.CRLDistributionPointsExtension;
import sun.security.x509.CertificateAlgorithmId;
import sun.security.x509.CertificateExtensions;
import sun.security.x509.CertificateSerialNumber;
import sun.security.x509.CertificateValidity;
import sun.security.x509.CertificateVersion;
import sun.security.x509.CertificateX509Key;
import sun.security.x509.DNSName;
import sun.security.x509.DistributionPoint;
import sun.security.x509.ExtendedKeyUsageExtension;
import sun.security.x509.GeneralName;
import sun.security.x509.GeneralNames;
import sun.security.x509.KeyIdentifier;
import sun.security.x509.KeyUsageExtension;
import sun.security.x509.SerialNumber;
import sun.security.x509.SubjectAlternativeNameExtension;
import sun.security.x509.SubjectKeyIdentifierExtension;
import sun.security.x509.URIName;
import sun.security.x509.X500Name;
import sun.security.x509.X509CertImpl;
import sun.security.x509.X509CertInfo;

@SuppressWarnings("restriction")
public class X509CertificateTest {

    public X509Certificate createX509Certificate(X500Name subject, X500Name issuer, Date validityFrom, Date validityTo,
            PublicKey publicKey, PrivateKey signingPrivateKey, PublicKey signingPublicKey, String algorithm)
            throws CertificateException, IOException, InvalidKeyException, NoSuchAlgorithmException,
            NoSuchProviderException, SignatureException {

        X509CertInfo info = new X509CertInfo();
        info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
        info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(1));
        info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(AlgorithmId.get(algorithm)));
        info.set(X509CertInfo.SUBJECT, subject);
        info.set(X509CertInfo.KEY, new CertificateX509Key(publicKey));
        info.set(X509CertInfo.VALIDITY, new CertificateValidity(validityFrom, validityTo));
        info.set(X509CertInfo.ISSUER, issuer);

        // X509v3 extensions
        CertificateExtensions exts = new CertificateExtensions();

        // Extensions[1]: Authority Information Access
        List accDescr = new ArrayList<>();
        String urlCA = "http://magnuskkarlsson.se/ca.cer";
        String urlOCSP = "http://magnuskkarlsson.se/ocsp";
        accDescr.add(new AccessDescription(AccessDescription.Ad_CAISSUERS_Id, new GeneralName(new URIName(urlCA))));
        accDescr.add(new AccessDescription(AccessDescription.Ad_OCSP_Id, new GeneralName(new URIName(urlOCSP))));
        exts.set(AuthorityInfoAccessExtension.NAME, new AuthorityInfoAccessExtension(accDescr));

        // Extensions[2]: X509v3 Authority Key Identifier
        exts.set(AuthorityKeyIdentifierExtension.NAME,
                new AuthorityKeyIdentifierExtension(new KeyIdentifier(signingPublicKey),
                        new GeneralNames().add(new GeneralName(subject)), new SerialNumber(1)));

        // Extensions[3]: X509v3 Basic Constraints: critical=true, ca=false, pathLen=-1=undefined
        exts.set(BasicConstraintsExtension.NAME, new BasicConstraintsExtension(true, false, -1));

        // Extensions[4]: X509v3 CRL Distribution Points
        List cdp = new ArrayList<>();
        String urlCRL = "http://magnuskkarlsson.se/ca.crl";
        cdp.add(new DistributionPoint(new GeneralNames().add(new GeneralName(new URIName(urlCRL))), null, null));
        exts.set(CRLDistributionPointsExtension.NAME, new CRLDistributionPointsExtension(cdp));

        // Extensions[5]: X509v3 Extended Key Usage
        Vector keyUsages = new Vector<>();
        // OID defined in RFC 3280 Sections 4.2.1.13
        // more from http://www.alvestrand.no/objectid/1.3.6.1.5.5.7.3.html
        // serverAuth
        keyUsages.addElement(ObjectIdentifier.newInternal(new int[] { 1, 3, 6, 1, 5, 5, 7, 3, 1 }));
        // clientAuth
        keyUsages.addElement(ObjectIdentifier.newInternal(new int[] { 1, 3, 6, 1, 5, 5, 7, 3, 2 }));
        exts.set(ExtendedKeyUsageExtension.NAME, new ExtendedKeyUsageExtension(keyUsages));

        // Extensions[6]: X509v3 Key Usage
        KeyUsageExtension keyUsage = new KeyUsageExtension();
        keyUsage.set(KeyUsageExtension.DIGITAL_SIGNATURE, Boolean.TRUE);
        keyUsage.set(KeyUsageExtension.NON_REPUDIATION, Boolean.FALSE);
        keyUsage.set(KeyUsageExtension.KEY_ENCIPHERMENT, Boolean.TRUE);
        keyUsage.set(KeyUsageExtension.DATA_ENCIPHERMENT, Boolean.FALSE);
        keyUsage.set(KeyUsageExtension.KEY_AGREEMENT, Boolean.FALSE);
        keyUsage.set(KeyUsageExtension.KEY_CERTSIGN, Boolean.FALSE);
        keyUsage.set(KeyUsageExtension.CRL_SIGN, Boolean.FALSE);
        keyUsage.set(KeyUsageExtension.ENCIPHER_ONLY, Boolean.FALSE);
        keyUsage.set(KeyUsageExtension.DECIPHER_ONLY, Boolean.FALSE);
        exts.set(KeyUsageExtension.NAME, keyUsage);

        // Extensions[7]: Subject Alternative Name
        String urlSAN = "magnuskkarlsson.com";
        exts.set(SubjectAlternativeNameExtension.NAME,
                new SubjectAlternativeNameExtension(new GeneralNames().add(new GeneralName(new DNSName(urlSAN)))));

        // Extensions[8]: X509v3 Subject Key Identifier
        exts.set(SubjectKeyIdentifierExtension.NAME,
                new SubjectKeyIdentifierExtension(new KeyIdentifier(publicKey).getIdentifier()));

        info.set(X509CertInfo.EXTENSIONS, exts);

        // Sign the certificate with the CA private key
        X509CertImpl cert = new X509CertImpl(info);
        cert.sign(signingPrivateKey, algorithm);

        return cert;
    }

    public X500Name createX500Name(String commonName, String organizationUnit, String organizationName,
            String localityName, String stateName, String country) throws IOException {

        return new X500Name(commonName, organizationUnit, organizationName, localityName, stateName, country);
    }

    public Date[] createValidity(int days) {
        Date from = new Date();
        Date to = new Date(from.getTime() + days * 86400000l);
        return new Date[] { from, to };
    }

    public KeyPair generateKeyPair() throws NoSuchAlgorithmException {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        keyGen.initialize(2048, new SecureRandom());
        return keyGen.generateKeyPair();
    }

    public static void main(String[] args) throws Exception {
        //        CertificateFactory factory = CertificateFactory.getInstance("X.509");
        //        InputStream input = ClassLoader.getSystemResourceAsStream("server-v3.cert.pem");
        //        X509Certificate certDemo = (X509Certificate) factory.generateCertificate(input);
        //        System.out.println(certDemo);

        X509CertificateTest test = new X509CertificateTest();
        X500Name subject = test.createX500Name("localhost", "Purch", "Onizuka, Inc.", "Palo Alto", "California", "CH");
        Date[] validity = test.createValidity(730);
        KeyPair keyPair = test.generateKeyPair();
        X509Certificate cert = test.createX509Certificate(subject, subject, validity[0], validity[1],
                keyPair.getPublic(), keyPair.getPrivate(), keyPair.getPublic(), "SHA256withRSA");
        System.out.println(cert);
    }
}

And when run

[
[
  Version: V3
  Subject: CN=localhost, OU=Purch, O="Onizuka, Inc.", L=Palo Alto, ST=California, C=CH
  Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11

  Key:  Sun RSA public key, 2048 bits
  modulus: 16430622413258256674241974707951685615738697152316908673861521954949249341917487321969963974880523193055015435475912265313655167640567326771003684773855144686669652477201074611376490251544635379047537527674776936600766762870067059060115972142637033253593758480242365775073879800299177911290829460272374213329378851325135207593915369295651052987402058272708938961270470497580241134442791917877474961958209709747980260907545128710289887887982830065299918209286621365319016851187102417429903048397749408363510478403654647568673004684653502196209388803336825940342089023016860111125056634847644257184487730090507480997167
  public exponent: 65537
  Validity: [From: Wed May 30 13:38:49 CEST 2018,
               To: Fri May 29 13:38:49 CEST 2020]
  Issuer: CN=localhost, OU=Purch, O="Onizuka, Inc.", L=Palo Alto, ST=California, C=CH
  SerialNumber: [    01]

Certificate Extensions: 8
[1]: ObjectId: 1.3.6.1.5.5.7.1.1 Criticality=false
AuthorityInfoAccess [
  [
   accessMethod: caIssuers
   accessLocation: URIName: http://magnuskkarlsson.se/ca.cer
, 
   accessMethod: ocsp
   accessLocation: URIName: http://magnuskkarlsson.se/ocsp
]
]

[2]: ObjectId: 2.5.29.35 Criticality=false
AuthorityKeyIdentifier [
KeyIdentifier [
0000: 18 B5 68 3F 35 5A 1B 48   5F 9F B3 C3 3E AB E3 CA  ..h?5Z.H_...>...
0010: 83 FB 9E 47                                        ...G
]
[CN=localhost, OU=Purch, O="Onizuka, Inc.", L=Palo Alto, ST=California, C=CH]
SerialNumber: [    01]
]

[3]: ObjectId: 2.5.29.19 Criticality=true
BasicConstraints:[
  CA:false
  PathLen: undefined
]

[4]: ObjectId: 2.5.29.31 Criticality=false
CRLDistributionPoints [
  [DistributionPoint:
     [URIName: http://magnuskkarlsson.se/ca.crl]
]]

[5]: ObjectId: 2.5.29.37 Criticality=false
ExtendedKeyUsages [
  serverAuth
  clientAuth
]

[6]: ObjectId: 2.5.29.15 Criticality=true
KeyUsage [
  DigitalSignature
  Key_Encipherment
]

[7]: ObjectId: 2.5.29.17 Criticality=false
SubjectAlternativeName [
  DNSName: magnuskkarlsson.com
]

[8]: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 18 B5 68 3F 35 5A 1B 48   5F 9F B3 C3 3E AB E3 CA  ..h?5Z.H_...>...
0010: 83 FB 9E 47                                        ...G
]
]

]
  Algorithm: [SHA256withRSA]
  Signature:
0000: 58 62 47 8E 55 6B 17 15   37 E9 E9 C3 2B E0 9A 71  XbG.Uk..7...+..q
0010: 11 29 04 F3 39 4E 87 DB   5B 12 73 79 2D 66 76 B3  .)..9N..[.sy-fv.
0020: E0 DD 4A F3 09 39 DF 5A   54 68 69 A7 18 44 F7 39  ..J..9.ZThi..D.9
0030: 21 02 52 6C 1B A8 87 F6   28 BD F7 B2 86 08 43 8C  !.Rl....(.....C.
0040: 62 E1 10 EB 7E 3B FB 4D   5B F3 B8 93 F9 EC 67 13  b....;.M[.....g.
0050: 11 EB 99 88 DC CA 8D 48   83 9B 4B B7 6C 4C 7E 99  .......H..K.lL..
0060: C7 9B 16 69 9E EF 20 A0   5D 55 09 EF 75 99 87 5A  ...i.. .]U..u..Z
0070: 33 26 56 E1 33 EB A7 83   54 9F 1B 4A 49 02 1E F8  3&V.3...T..JI...
0080: 95 91 99 C3 48 D2 1A C3   3A 3A 8F 99 61 77 7D 7F  ....H...::..aw..
0090: D2 0A 85 DA 86 F6 DD 06   32 D0 8D 8D 8E 76 AA 16  ........2....v..
00A0: 19 71 79 3A C1 2A 57 DD   E9 C4 AD 48 CB 87 29 EF  .qy:.*W....H..).
00B0: 13 99 BA 9E 8A 3F 2E 0C   64 22 3E 66 B3 A7 2C 69  .....?..d">f..,i
00C0: E4 94 63 BB 89 CF 98 45   DE 89 25 5C 0F 1E F1 44  ..c....E..%\...D
00D0: 99 B2 A9 9A 73 64 20 F6   B1 55 EE B7 4C 68 96 F3  ....sd ..U..Lh..
00E0: 84 47 C1 D6 F8 FB 76 11   44 71 43 01 72 F5 A0 C2  .G....v.DqC.r...
00F0: F1 86 F0 FC 6D 9D 05 4A   BB 0A 88 C7 ED 4B 5A 4B  ....m..J.....KZK

]

No comments: