1

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 Pankaj@JournalDev.com
 * @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.

Neeraj
  • 80
  • 1
  • 9
  • Using any classes from `sun.security`, or indeed anything from `sun.*`, is officially **incorrect**. Those are internal classes that are not documented as being available for others. They may disappear at any time, or the behavior may change. And it may not be available at all in other compliant implementations of the Java library. Everything you need is available in the Bouncycastle library APIs. – President James K. Polk Aug 26 '21 at 16:52
  • However, even if you go with the `sun.*` classes it's incorrect. The GeneralName you use must be either a DNSName or an IPAddressName. `new DerValue("b")` is neither of those. Something like `generalNames.add(new GeneralName(new DNSName("a.example.com")));` should be correct. There is no connection to the PKCS10 request though, so something more is needed. – President James K. Polk Aug 26 '21 at 17:15

2 Answers2

2

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.

President James K. Polk
  • 40,516
  • 21
  • 95
  • 125
  • 1
    I agree that indeed the classes within the sun packages should be avoided. I have used it in the past and faced breaking changes between java 8 - 11 - 15. So please note that any class within the sun package may not work on newer java versions as it is not part of the public api – Hakan54 Aug 26 '21 at 23:41
  • Thank you for guiding me in the right direction, I am exploring bouncy castle to generate a CSR. – Neeraj Aug 27 '21 at 09:52
2

Posting code snippet using bouncy castle if someone wants to refer it in future. It includes adding SAN names to the certificate.

Also Creating a CSR includes the following steps:

  1. Generate an asymmetric key pair(private+public), usually, you keep a private key at a secure location at your server.

  2. Create a Certificate adding information about your domain name(can use wildcard or SAN names as well), info about your organization, city, state, etc.(you can do this by the code below or command-line tools such OpenSSL or java keytool).

  3. Typically your CSR will also include your public key and it is signed by your private key.

  4. Once you have generated CSR you will submit it to a trusted and well-known Certificate Authority to sign it or if you are on a private network or in a dev environment you can sign it by yourself also.

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.util.ArrayList;
import java.util.List;

import javax.security.auth.x500.X500Principal;

import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.ExtensionsGenerator;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
import org.bouncycastle.util.IPAddress;

public class BouncyCastle {

    public static void main(String[] args)
                    throws OperatorCreationException, NoSuchAlgorithmException, NoSuchProviderException, IOException {
        Security.addProvider(new BouncyCastleProvider());
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
        keyPairGenerator.initialize(4096);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();

        PKCS10CertificationRequestBuilder p10Builder = new JcaPKCS10CertificationRequestBuilder(
                        new X500Principal(
                                        "CN=my.example.com, C=US, ST=Texas, L=Austin, O=Example ORG, OU=Engineering"),
                        keyPair.getPublic());
        List<String> extensions = new ArrayList<>();
        extensions.add("tx9918-otr01.my.example.com");
        extensions.add("tx9918-otr02.my.example.com");
        extensions.add("tx9918-otr03.my.example.com");
        extensions.add("tx9918-otr04.my.example.com");

        p10Builder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest,
                        createDomainAlternativeNamesExtensions(extensions));

        JcaContentSignerBuilder csBuilder = new JcaContentSignerBuilder("SHA256withRSA");
        ContentSigner signer = csBuilder.build(keyPair.getPrivate());
        PKCS10CertificationRequest csr = p10Builder.build(signer);
        JcaPEMWriter jcaPEMWriter = new JcaPEMWriter(new FileWriter(new File("D:\\Titans\\cert\\test.csr")));
        jcaPEMWriter.writeObject(csr);
        jcaPEMWriter.close();
    }

    public static Extensions createDomainAlternativeNamesExtensions(List<String> domainAlternativeNames)
                    throws IOException {
        List<GeneralName> namesList = new ArrayList<>();
        if (domainAlternativeNames != null) {
            for (String alternativeName : domainAlternativeNames) {
                namesList.add(new GeneralName(
                                IPAddress.isValid(alternativeName) ? GeneralName.iPAddress : GeneralName.dNSName,
                                alternativeName));
            }
        }
        GeneralNames subjectAltNames = new GeneralNames(namesList.toArray(new GeneralName[] {}));
        ExtensionsGenerator extGen = new ExtensionsGenerator();
        extGen.addExtension(Extension.subjectAlternativeName, false, subjectAltNames);
        return extGen.generate();
    }
}
Neeraj
  • 80
  • 1
  • 9