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:
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.
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.