Can I create a cert with the Java API only?
I'm building a client/server app that will use SSL and client certs for authenticating the client to the server. I'd like for each user to be able to create a keypair and an associated self-signed cert that they can provide to the server through some other means, to be included in the server's trust store.
I know how to generate a key pair with an associated self-signed cert via keytool, but I'd prefer to do it directly with the Java APIs. From looking at the Javadocs, I can see how to generate a keypair and how to generate a cert object using an encoded representation of the cert ( e.g. java.security.cert.CertificateFactory.generateCertififcate() ).
But how can I create this encoded representation of the certificate that I need to provide to generateCertificate()? I could do it with keytool and export the cert to a file, but is there no Java API that can accomplish the same thing?
I want to avoid having the user use keytool. Perhaps I can execute the appropriate keytool command from the java code, using Runtime.exec(), but again a pure java API approach would be better. Is there a way to do this all with Java? If not, is executing keytool via Runtime.exec() the best approach?
# 2
There is no solution available with the JDK. It's rather deficient wrt certificate management, as java.security.cert.CertificateFactory is a factory that only deals in re-treads. That is, it doesn't really create certs. Rather it converts a DER encoded byte stream into a Java Certificate object.
I found two ways to create a certificate from scratch. The first one is an all Java implementation of what keytool does. The second is to use Runtime.exec(), which you don't want to do.
1. Use BouncyCastle, a free open source cryptography library that you can find here: http://www.bouncycastle.org/ There are examples in the documentation that show you how to do just about anything you want to do. I chose not to use it, because my need was satisfied with a lighter approach, and I didn't want to add a dependency unnecessarily. Also Bouncy Castle requires you to use a distinct version with each version of the JDK. So if I wanted my app to work with JDK 1.4 or later, I would have to actually create three different versions, each bundled with the version of BouncyCastle that matches the version of the target JDK.
2. I created my cert by using Runtime.exec() to invoke the keytool program, which you say you don't want to do. This seemed like a hack to me, so I tried to avoid it; but actually I think it was the better choice for me, and I've been happy with how it works. It may have some backward compatibility issues. I tested it on Windows XP and Mac 10.4.9 with JDK 1.6. Some keytool arguments changed with JDK versions, but I think they maintained backward compatibility. I haven't checked it, and I don't know if I'm using the later or earlier version of the keytool arguments.
Here's my code.
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import javax.security.auth.x500.X500Principal;
import javax.swing.JOptionPane;
public class CreateCertDemo {
private static void createKey() throws IOException,
KeyStoreException, NoSuchAlgorithmException, CertificateException{
X500Principal principal;
String storeName = ".keystore";
String alias = "keyAlias";
principal = PrincipalInfo.getInstance().getPrincipal();
String validity = "10000";
String[] cmd = new String[]{ "keytool", "-genKey", "-alias", alias, "-keyalg", "RSA",
"-sigalg", "SHA256WithRSA", "-dname", principal.getName(), "-validity",
validity, "-keypass", "keyPassword", "-keystore",
storeName, "-storepass", "storePassword"};
int result = doExecCommand(cmd);
if (result != 0){
String msg = "An error occured while trying to generate\n" +
"the private key. The error code returned by\n" +
"the keytool command was " + result + ".";
JOptionPane.showMessageDialog(null, msg, "Key Generation Error", JOptionPane.WARNING_MESSAGE);
}
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(new FileInputStream(storeName), "storePassword".toCharArray());
//return ks from the method if needed
}
public static int doExecCommand(String[] cmd) throws IOException{
Runtime r = Runtime.getRuntime();
Process p = null;
p = r.exec(cmd);
FileOutputStream outFos = null;
FileOutputStream errFos = null;
File out = new File("keytool_exe.out");
out.createNewFile();
File err = new File("keytool_exe.err");
err.createNewFile();
outFos = new FileOutputStream(out);
errFos = new FileOutputStream(err);
StreamSink outSink = new StreamSink(p.getInputStream(),"Output", outFos );
StreamSink errSink = new StreamSink(p.getErrorStream(),"Error", errFos );
outSink.start();
errSink.start();
int exitVal = 0;;
try {
exitVal = p.waitFor();
} catch (InterruptedException e) {
return -100;
}
System.out.println (exitVal==0 ? "certificate created" :
"A problem occured during certificate creation");
outFos.flush();
outFos.close();
errFos.flush();
errFos.close();
out.delete();
err.delete();
return exitVal;
}
public static void main (String[] args) throws
KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException{
createKey();
}
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
//Adapted from Mike Daconta's StreamGobbler at
//http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html?page=4
public class StreamSink extends Thread
{
InputStream is;
String type;
OutputStream os;
public StreamSink(InputStream is, String type)
{
this(is, type, null);
}
public StreamSink(InputStream is, String type, OutputStream redirect)
{
this.is = is;
this.type = type;
this.os = redirect;
}
public void run()
{
try
{
PrintWriter pw = null;
if (os != null)
pw = new PrintWriter(os);
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line=null;
while ( (line = br.readLine()) != null)
{
if (pw != null)
pw.println(line);
System.out.println(type + ">" + line);
}
if (pw != null)
pw.flush();
} catch (IOException ioe)
{
ioe.printStackTrace();
}
}
}
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.security.auth.x500.X500Principal;
public class PrincipalInfo {
private static String defInfoString = "CN=Name, O=Organization";
//make it a singleton.
private static class PrincipalInfoHolder{
private static PrincipalInfo instance = new PrincipalInfo();
}
public static PrincipalInfo getInstance(){
return PrincipalInfoHolder.instance;
}
private PrincipalInfo(){
}
public X500Principal getPrincipal(){
String fileName = "principal.der";
File file = new File(fileName);
if (file.exists()){
try {
return new X500Principal(new FileInputStream(file));
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}else{
return new X500Principal(defInfoString);
}
}
public void savePrincipal(X500Principal p) throws IOException{
FileOutputStream fos = new FileOutputStream("principal.der");
fos.write(p.getEncoded());
fos.close();
}
}
Message was edited by:
MidnightJava
Message was edited by:
MidnightJava