/**
CertRequest.java
Copyright (C) 1999, Claymore Systems, Inc.
All Rights Reserved.
ekr@rtfm.com Sat Jul 24 19:06:19 1999
*/
package COM.claymoresystems.cert;
import COM.claymoresystems.util.Util;
import COM.claymoresystems.ptls.LoadProviders;
import COM.claymoresystems.crypto.EAYEncryptedPrivateKey;
import COM.claymoresystems.cert.EAYDSAPrivateKey;
import COM.claymoresystems.cert.DERUtils;
import COM.claymoresystems.cert.X509DSAPublicKey;
import COM.claymoresystems.cert.X509RSAPublicKey;
import java.io.*;
import java.math.BigInteger;
import java.security.KeyPairGenerator;
import java.security.interfaces.DSAKeyPairGenerator;
import java.security.SecureRandom;
import java.security.KeyPair;
import java.security.PublicKey;
import java.security.PrivateKey;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.DSAPublicKey;
import java.util.Vector;
import xjava.security.interfaces.CryptixRSAPublicKey;
import xjava.security.interfaces.CryptixRSAPrivateKey;
import java.security.Signature;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import cryptix.util.mime.Base64OutputStream;
/** Generate various kinds of certificate signing requests.
<p>
The three main interfaces are<br>
<code>
makeSPKACRequest()
<br>
makePKCS10Request()
<br>
makeSelfSignedCert()
</code>
<p>
You can access these in a primitive fashion through the
<code>main()</main> function but this gives you a mostly
hardcoded DN with PKCS10 and self-signed (you can choose
the CN). <code>makeSimpleDN()</code> offers a way to construct
a fairly simple DN from a simpler construct than the standard
X509Name. At some point we may expose simpler functionality
at the command line.
*/
public class CertRequest {
static {
LoadProviders.init();
}
/** Generate a key pair
@param type DSA or RSA
@param size the length
@param password the password to use to encrypted the key
@param keyfile the keyfile to store the key in
@param newParams generate new parameters if using DSA--by default Sun uses fixed precomputed params
@return the keypair
@exception NoSuchAlgorithmException if you choose a key we don't know about
@exception NoSuchProviderException internal errors
@exception IOException encoding errors
*/
public static KeyPair generateKey(String type,int size,
String password,BufferedWriter keyfile,boolean newParams)
throws NoSuchAlgorithmException, NoSuchProviderException, IOException {
PrivateKey priv;
KeyPairGenerator kg;
if(type.equals("DSA")){
kg=KeyPairGenerator.getInstance(type);
DSAKeyPairGenerator dsaGen=(DSAKeyPairGenerator)kg;
dsaGen.initialize(size,newParams,new SecureRandom());
}
else{
kg=KeyPairGenerator.getInstance(type,"Cryptix");
kg.initialize(size);
}
KeyPair pair=kg.generateKeyPair();
// We need to map the DSA keys given us by Sun into
// our own DSA keys because Sun gives us a bogus
// DER encoding
if(type.equals("DSA")){
priv=new EAYDSAPrivateKey((DSAPrivateKey)pair.getPrivate());
}
else {
priv=new X509RSAPrivateKey((CryptixRSAPrivateKey)pair.getPrivate());
}
EAYEncryptedPrivateKey.writePrivateKey(priv,password.getBytes(),keyfile);
return pair;
}
/** Make a Netscape Signed Public Key and Cert request
@param p the keypair to make it with
@return the SPKAC as a bytestring
@exception IOException for errors */
public static byte[] makeSPKACRequest(KeyPair p)
throws IOException {
PrivateKey priv=p.getPrivate();
PublicKey pub=p.getPublic();
String alg=priv.getAlgorithm();
byte[] DSA_algid={(byte)0x30,(byte)0x0b,(byte)0x06,(byte)0x07,(byte)0x2a,
(byte)0x86,(byte)0x48,(byte)0xce,(byte)0x38,(byte)0x04,
(byte)0x03,(byte)0x05,(byte)0x00};
byte[] RSA_algid={(byte)0x30,(byte)0x0d,(byte)0x06,(byte)0x09,
(byte)0x2a,(byte)0x86,(byte)0x48,(byte)0x86,(byte)0xf7,
(byte)0x0d,(byte)0x01,(byte)0x01,(byte)0x05,(byte)0x05,
(byte)0x00};
// We need to map the keys given us by Sun or Cryptix into
// our own DSA keys because they gives us a bogus
// DER encoding (or none at all)
byte[] algId;
String algorithm;
if(alg.equals("DSA")){
algId=DSA_algid;
algorithm="DSA";
pub=new X509DSAPublicKey((DSAPublicKey)pub);
}
else if(alg.equals("RSA")){
algId=RSA_algid;
algorithm="SHA-1/RSA/PKCS#1";
pub=new X509RSAPublicKey((CryptixRSAPublicKey)pub);
}
else throw new InternalError("Unknown algorithm "+alg);
byte[] key_enc=pub.getEncoded(); // SPKI
ByteArrayOutputStream os=new ByteArrayOutputStream();
os.write(key_enc);
DERUtils.encodeIA5String("Challenge",os);
byte[] tmp=os.toByteArray();
os.reset();
DERUtils.encodeSequence(tmp,os);
// At this point we have the public key and challenge sequence
byte[] toBeSigned=os.toByteArray();
byte[] signature;
try {
Signature sig=Signature.getInstance(algorithm);
sig.initSign(priv);
sig.update(toBeSigned);
signature=sig.sign();
} catch (Exception e){
throw new InternalError(e.toString());
}
os.reset();
os.write(toBeSigned);
os.write(algId);
// Make the signature the right size
if(alg.equals("RSA")){
signature=fitSignature(signature,pub);
}
DERUtils.encodeBitString(signature,os);
byte[] spkac_c=os.toByteArray();
// Now we've got the SPKAC as a byte array, but we need to wrap it in a sequence
os.reset();
DERUtils.encodeSequence(spkac_c,os);
byte[] spkac=os.toByteArray();
return spkac;
}
/** Make a PKCS10 CSR
@param p the keypair to make it with
@param name the subject name as an X509Name
@return the CSR as a bytestring
@exception IOException for errors */
public static byte[] makePKCS10Request(KeyPair p,X509Name name)
throws IOException {
PrivateKey priv=p.getPrivate();
PublicKey pub=p.getPublic();
String alg=priv.getAlgorithm();
byte[] DSA_algid={(byte)0x30,(byte)0x0b,(byte)0x06,(byte)0x07,(byte)0x2a,
(byte)0x86,(byte)0x48,(byte)0xce,(byte)0x38,(byte)0x04,
(byte)0x03,(byte)0x05,(byte)0x00};
byte[] RSA_algid={(byte)0x30,(byte)0x0d,(byte)0x06,(byte)0x09,
(byte)0x2a,(byte)0x86,(byte)0x48,(byte)0x86,(byte)0xf7,
(byte)0x0d,(byte)0x01,(byte)0x01,(byte)0x05,(byte)0x05,
(byte)0x00};
// We need to map the keys given us by Sun or Cryptix into
// our own DSA keys because they gives us a bogus
// DER encoding (or none at all)
byte[] algId;
String algorithm;
if(alg.equals("DSA")){
algId=DSA_algid;
algorithm="DSA";
pub=new X509DSAPublicKey((DSAPublicKey)pub);
}
else if(alg.equals("RSA")){
algId=RSA_algid;
algorithm="SHA-1/RSA/PKCS#1";
pub=new X509RSAPublicKey((CryptixRSAPublicKey)pub);
}
else throw new InternalError("Unknown algorithm "+alg);
byte[] key_enc=pub.getEncoded(); // SPKI
ByteArrayOutputStream os=new ByteArrayOutputStream();
DERUtils.encodeInteger(new BigInteger("0"),os); // Version
os.write(name.getNameDER()); // DN
os.write(key_enc); // SPKI
byte[] tmp=os.toByteArray();
os.reset();
DERUtils.encodeSequence(tmp,os);
// At this point we have the inner content to be signed
byte[] toBeSigned=os.toByteArray();
byte[] signature;
try {
Signature sig=Signature.getInstance(algorithm);
sig.initSign(priv);
sig.update(toBeSigned);
signature=sig.sign();
} catch (Exception e){
throw new InternalError(e.toString());
}
os.reset();
os.write(toBeSigned);
os.write(algId);
// Make the signature the right size
if(alg.equals("RSA")){
signature=fitSignature(signature,pub);
}
DERUtils.encodeBitString(signature,os);
byte[] pkcs10_c=os.toByteArray();
// Now we've got the PKCS10 as a byte array, but we need to wrap it in a sequence
os.reset();
DERUtils.encodeSequence(pkcs10_c,os);
byte[] pkcs10=os.toByteArray();
return pkcs10;
}
/** Make a Self-signed cert
@param p the keypair to make it with
@param the name to use
@param the lifetime in seconds
@return the CSR as a bytestring
@exception IOException for errors */
public static byte[] makeSelfSignedCert(KeyPair p,X509Name name,int lifetime)
throws IOException {
PrivateKey priv=p.getPrivate();
PublicKey pub=p.getPublic();
String alg=priv.getAlgorithm();
byte[] DSA_algid={(byte)0x30,(byte)0x0b,(byte)0x06,(byte)0x07,(byte)0x2a,
(byte)0x86,(byte)0x48,(byte)0xce,(byte)0x38,(byte)0x04,
(byte)0x03,(byte)0x05,(byte)0x00};
byte[] RSA_algid={(byte)0x30,(byte)0x0d,(byte)0x06,(byte)0x09,
(byte)0x2a,(byte)0x86,(byte)0x48,(byte)0x86,(byte)0xf7,
(byte)0x0d,(byte)0x01,(byte)0x01,(byte)0x05,(byte)0x05,
(byte)0x00};
// We need to map the keys given us by Sun or Cryptix into
// our own DSA keys because they gives us a bogus
// DER encoding (or none at all)
byte[] algId;
String algorithm;
if(alg.equals("DSA")){
algId=DSA_algid;
algorithm="DSA";
pub=new X509DSAPublicKey((DSAPublicKey)pub);
}
else if(alg.equals("RSA")){
algId=RSA_algid;
algorithm="SHA-1/RSA/PKCS#1";
pub=new X509RSAPublicKey((CryptixRSAPublicKey)pub);
}
else throw new InternalError("Unknown algorithm "+alg);
byte[] key_enc=pub.getEncoded(); // SPKI
ByteArrayOutputStream os=new ByteArrayOutputStream();
// Omit version to default to 0 (version 1)
DERUtils.encodeInteger(new BigInteger("0"),os); // Serial
os.write(algId); // signature alg
os.write(name.getNameDER()); // Issuer
// Validity
ByteArrayOutputStream os2=new ByteArrayOutputStream();
long notBefore=System.currentTimeMillis();
long increment=lifetime;
long notAfter=notBefore+(increment*1000);
DERUtils.encodeUTCTime(notBefore,os2);
DERUtils.encodeUTCTime(notAfter,os2);
DERUtils.encodeSequence(os2,os);
os.write(name.getNameDER()); // Subject
os.write(key_enc); // SPKI
byte[] tmp=os.toByteArray();
os.reset();
DERUtils.encodeSequence(tmp,os);
// At this point we have the inner content to be signed
byte[] toBeSigned=os.toByteArray();
byte[] signature;
try {
Signature sig=Signature.getInstance(algorithm);
sig.initSign(priv);
sig.update(toBeSigned);
signature=sig.sign();
} catch (Exception e){
throw new InternalError(e.toString());
}
os.reset();
os.write(toBeSigned);
os.write(algId);
// Make the signature the right size
if(alg.equals("RSA")){
signature=fitSignature(signature,pub);
}
DERUtils.encodeBitString(signature,os);
byte[] cert_c=os.toByteArray();
// Now we've got the CERT as a byte array, but we need to wrap it in a sequence
os.reset();
DERUtils.encodeSequence(cert_c,os);
byte[] cert=os.toByteArray();
return cert;
}
/* Make a DN from a vector of AVAs. This assumes
one AVA per RDN */
public static X509Name makeSimpleDN(Vector rdns){
Vector dn=new Vector();
for(int i=0;i<rdns.size();i++){
String[] ava=(String [])rdns.elementAt(i);
Vector nrdn=new Vector();
String[] nava=new String[2];
nava[0]=new String(ava[0]);
nava[1]=new String(ava[1]);
nrdn.addElement(nava);
dn.addElement(nrdn);
}
return new X509Name(dn);
}
protected static byte[] fitSignature(byte[] tmp,PublicKey pub){
CryptixRSAPublicKey rsa=(CryptixRSAPublicKey)pub;
int bitLength=rsa.getModulus().bitLength();
int length=bitLength/8;
length+=(bitLength%8>0)?1:0;
int i;
// Ensure that this is the right length
if(tmp.length==length)
return tmp;
// If it's too short, pad with zeros on the right
// If it's too long, remove zeros from the left.
// if it's too long without zeros, throw an error.
byte[] ntmp=new byte[length];
if(tmp.length<length){
for(i=0;i<(length-tmp.length);i++){
ntmp[i]=0;
}
System.arraycopy(tmp,0,ntmp,i,tmp.length);
}
else {
for(i=0;i<(tmp.length-length);i++){
if(tmp[i]!=0)
throw new InternalError("RSA signature error");
}
System.arraycopy(tmp,i,ntmp,0,length);
}
return ntmp;
}
private static X509Name makeSuperSimpleDN(String CN) {
Vector tdn=new Vector();
String[] c=new String[2];
String[] o=new String[2];
String[] ou=new String[2];
String[] cn=new String[2];
c[0]="C";
c[1]="US";
o[0]="O";
o[1]="Snake Oil, Inc.";
ou[0]="OU";
ou[1]="Test";
cn[0]="CN";
cn[1]=CN;
tdn.addElement(c);
tdn.addElement(o);
tdn.addElement(ou);
tdn.addElement(cn);
return makeSimpleDN(tdn);
}
public static void main(String[] args)
throws IOException, Exception {
String keyFileName=args[0];
String keyType;
String cn=null;
int type=0;
if(args.length>=2){
keyType=args[1];
}
else
keyType="DSA";
if(args.length>=3){
if(args[2].equals("SPKAC"))
type=0;
else if (args[2].equals("PKCS10"))
type=1;
else if (args[2].equals("X509"))
type=2;
else
throw new InternalError("Unknown type "+args[2]);
}
if(type==1 || type==2){
if(args.length!=4)
throw new InternalError("Must supply common name for type"+type);
cn=args[3];
}
int size=1024;
InputStreamReader ir=new InputStreamReader(System.in);
BufferedReader br=new BufferedReader(ir);
String password=br.readLine();
FileWriter fw=new FileWriter(keyFileName);
BufferedWriter bw=new BufferedWriter(fw);
KeyPair kp=generateKey(keyType,size,password,bw,true);
byte[] req;
switch(type){
case 0:
req=makeSPKACRequest(kp);
break;
case 1:
req=makePKCS10Request(kp,makeSuperSimpleDN(cn));
break;
case 2:
req=makeSelfSignedCert(kp,makeSuperSimpleDN(cn),30000000); // 30Msec is almost a year
break;
default:
throw new InternalError("Bad type");
}
ByteArrayOutputStream bos=new ByteArrayOutputStream();
Base64OutputStream b64os=new Base64OutputStream(bos);
b64os.write(req);
b64os.flush();
b64os.close();
ByteArrayInputStream bis=new ByteArrayInputStream(
bos.toByteArray());
InputStreamReader irr=new InputStreamReader(bis);
BufferedReader r=new BufferedReader(irr);
String line;
System.out.println(password);
switch(type){
case 1:
System.out.println("-----BEGIN CERTIFICATE REQUEST-----");
break;
case 2:
System.out.println("-----BEGIN CERTIFICATE-----");
break;
}
while((line=r.readLine())!=null){
System.out.println(line);
}
switch(type){
case 1:
System.out.println("-----END CERTIFICATE REQUEST-----");
break;
case 2:
System.out.println("-----END CERTIFICATE-----");
break;
}
}
}