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?

[1224 byte] By [MidnightJavaa] at [2007-11-27 0:37:13]
# 1
Any answer for this question? I am looking for the Java approach to implement keytool instead of running script via Runtime.exec().
jspganga at 2007-7-11 22:47:07 > top of Java-index,Security,Other Security APIs, Tools, and Issues...
# 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

MidnightJavaa at 2007-7-11 22:47:07 > top of Java-index,Security,Other Security APIs, Tools, and Issues...
# 3

MidnightJava, Thanks for sharing your thoughtful solutions. My previous solution is very similar to your second approach. The only difference is I write a batch script on WinXP, and my Java program read it from a location and execute it in Java by invoking Runtime.exec(). Your solution is more pure Java approach.

jspganga at 2007-7-11 22:47:07 > top of Java-index,Security,Other Security APIs, Tools, and Issues...