Search code examples
javasslx509certificatecsr

Add SAN names while generating a CSR in java Programmatically


I am using the below code to generate a CSR in java:

package demo;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;

import sun.security.pkcs10.PKCS10;
import sun.security.util.DerInputStream;
import sun.security.util.DerValue;
import sun.security.x509.CertificateExtensions;
import sun.security.x509.GeneralName;
import sun.security.x509.GeneralNames;
import sun.security.x509.SubjectAlternativeNameExtension;
import sun.security.x509.X500Name;

/**
 * This class generates PKCS10 certificate signing request
 *
 * @author [email protected]
 * @version 1.0
 */
public class GenerateCSR {
    private static PublicKey publicKey = null;
    private static PrivateKey privateKey = null;
    private static KeyPairGenerator keyGen = null;
    private static GenerateCSR gcsr = null;

    private GenerateCSR() {
        try {
            keyGen = KeyPairGenerator.getInstance("RSA");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        keyGen.initialize(2048, new SecureRandom());
        KeyPair keypair = keyGen.generateKeyPair();
        publicKey = keypair.getPublic();
        privateKey = keypair.getPrivate();
    }

    public static GenerateCSR getInstance() {
        if (gcsr == null)
            gcsr = new GenerateCSR();
        return gcsr;
    }

    public String getCSR(String cn) throws Exception {
        byte[] csr = generatePKCS10(cn, "Java", "JournalDev", "Cupertino", "California", "USA");
        return new String(csr);
    }

    /**
     *
     * @param CN Common Name, is X.509 speak for the name that distinguishes the Certificate best, and ties it to your
     *        Organization
     * @param OU Organizational unit
     * @param O Organization NAME
     * @param L Location
     * @param S State
     * @param C Country
     * @return
     * @throws Exception
     */
    private static byte[] generatePKCS10(String CN, String OU, String O, String L, String S, String C)
                    throws Exception {
        GeneralNames generalNames = new GeneralNames();
        generalNames.add(new GeneralName(new DerValue("b")));
        generalNames.add(new GeneralName(new DerValue("a")));

        CertificateExtensions ext = new CertificateExtensions();

        ext.set(SubjectAlternativeNameExtension.NAME, new SubjectAlternativeNameExtension(generalNames));

        // generate PKCS10 certificate request
        String sigAlg = "MD5WithRSA";
        PKCS10 pkcs10 = new PKCS10(publicKey);
        Signature signature = Signature.getInstance(sigAlg);
        signature.initSign(privateKey);
        // common, orgUnit, org, locality, state, country
        X500Name x500Name = new X500Name(CN, OU, O, L, S, C);
        pkcs10.encodeAndSign(x500Name, signature);
        ByteArrayOutputStream bs = new ByteArrayOutputStream();
        PrintStream ps = new PrintStream(bs);
        pkcs10.print(ps);
        byte[] c = bs.toByteArray();
        try {
            if (ps != null)
                ps.close();
            if (bs != null)
                bs.close();
        } catch (Throwable th) {
        }
        return c;
    }

    public PublicKey getPublicKey() {
        return publicKey;
    }

    public PrivateKey getPrivateKey() {
        return privateKey;
    }

    public static void main(String[] args) throws Exception {
        GenerateCSR gcsr = GenerateCSR.getInstance();

        System.out.println("Public Key:\n" + gcsr.getPublicKey().toString());

        System.out.println("Private Key:\n" + gcsr.getPrivateKey().toString());
        String csr = gcsr.getCSR("journaldev.com <https://www.journaldev.com>");
        System.out.println("CSR Request Generated!!");
        System.out.println(csr);
    }

}

as you can see I am using below code to add SAN names

      GeneralNames generalNames = new GeneralNames();
        generalNames.add(new GeneralName(new DerValue("b")));
        generalNames.add(new GeneralName(new DerValue("a")));

        CertificateExtensions ext = new CertificateExtensions();

        ext.set(SubjectAlternativeNameExtension.NAME, new SubjectAlternativeNameExtension(generalNames));

My questions are:

  1. IS this the correct way?
  2. If it is how to use CertificateExtensions's object and where to pass it.

I am referring to this question, it is mentioned there I have to pass it in my certificate's constructor, but I am not able to X500Name's constructor only allow string values.


Solution

  • As I stated in the comments, the sun.* classes are not meant to be used by other programmers, and I would personally never use them. The Bouncycastle libraries can do all this and more. But if you insist on using these classes then you'll need to use a few more to get your intended effect. NOTE: Since the classes aren't documented what I have here is mostly the result of experiment.

    Note that MD5 cannot be used for signatures, it is completely insecure for such an application. I have replaced it with SHA256.

    Consider this fragment of your code that I have modified. The names themselves are just examples:

    // ...
    
    import sun.security.pkcs10.PKCS10Attribute;
    import sun.security.pkcs10.PKCS10Attributes;
    import sun.security.x509.*;
    import sun.security.pkcs10.PKCS10;
    import sun.security.pkcs.PKCS9Attribute;
    
    // ....
    
    GeneralNames generalNames = new GeneralNames();
    generalNames.add(new GeneralName(new DNSName("a.example.com")));
    generalNames.add(new GeneralName(new DNSName("never.ever.example.com")));
    generalNames.add(new GeneralName(new IPAddressName("192.168.1.250")));
    CertificateExtensions ext = new CertificateExtensions();
    
    ext.set(SubjectAlternativeNameExtension.NAME, new SubjectAlternativeNameExtension(generalNames));
    var pkcs9Attr = new PKCS9Attribute(PKCS9Attribute.EXTENSION_REQUEST_OID, ext);
    var pkcs10Attrs = new PKCS10Attributes(new PKCS10Attribute[] {
        new PKCS10Attribute(pkcs9Attr)
    });
    
    // generate PKCS10 certificate request
    String sigAlg = "SHA256WithRSA";
    PKCS10 pkcs10 = new PKCS10(publicKey, pkcs10Attrs);
    Signature signature = Signature.getInstance(sigAlg);
    signature.initSign(privateKey);
    // common, orgUnit, org, locality, state, country
    X500Name x500Name = new X500Name(CN, OU, O, L, S, C);
    pkcs10.encodeAndSign(x500Name, signature);
    ByteArrayOutputStream bs = new ByteArrayOutputStream();
    PrintStream ps = new PrintStream(bs);
    pkcs10.print(ps);
    

    This should produce a PKCS10 certificate request with the proper subjectAltName extensions.