package net.i2p.crypto; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.MessageDigest; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.cert.X509CRL; import java.security.spec.X509EncodedKeySpec; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TimeZone; import javax.crypto.interfaces.DHPublicKey; import javax.crypto.spec.DHParameterSpec; import javax.crypto.spec.DHPublicKeySpec; import javax.security.auth.x500.X500Principal; import static net.i2p.crypto.SigUtil.intToASN1; import net.i2p.data.DataHelper; import net.i2p.data.Signature; import net.i2p.data.SigningPrivateKey; import net.i2p.data.SigningPublicKey; import net.i2p.data.SimpleDataStructure; import net.i2p.util.HexDump; import net.i2p.util.RandomSource; import net.i2p.util.SecureFileOutputStream; import net.i2p.util.SystemVersion; /** * Generate keys and a selfsigned certificate, suitable for * storing in a Keystore with KeyStoreUtil.storePrivateKey(). * All done programatically, no keytool, no BC libs, no sun classes. * Ref: RFC 2459 * * This is coded to create a cert that matches what comes out of keytool * exactly, even if I don't understand all of it. * * @since 0.9.25 */ public final class SelfSignedGenerator { private static final boolean DEBUG = false; private static final String OID_CN = "2.5.4.3"; private static final String OID_C = "2.5.4.6"; private static final String OID_L = "2.5.4.7"; private static final String OID_ST = "2.5.4.8"; private static final String OID_O = "2.5.4.10"; private static final String OID_OU = "2.5.4.11"; // Subject Key Identifier private static final String OID_SKI = "2.5.29.14"; // Key Usage private static final String OID_USAGE = "2.5.29.15"; // Subject Alternative Name private static final String OID_SAN = "2.5.29.17"; // Basic Constraints private static final String OID_BASIC = "2.5.29.19"; // CRL number private static final String OID_CRLNUM = "2.5.29.20"; // Authority Key Identifier private static final String OID_AKI = "2.5.29.35"; private static final Map<String, String> OIDS; static { OIDS = new HashMap<String, String>(16); // only OIDs in the distinguished name need to be in here OIDS.put(OID_CN, "CN"); OIDS.put(OID_C, "C"); OIDS.put(OID_L, "L"); OIDS.put(OID_ST, "ST"); OIDS.put(OID_O, "O"); OIDS.put(OID_OU, "OU"); } /** * @param cname the common name, non-null * @param ou The OU (organizational unit) in the distinguished name, non-null before 0.9.28, may be null as of 0.9.28 * @param o The O (organization)in the distinguished name, non-null before 0.9.28, may be null as of 0.9.28 * @param l The L (city or locality) in the distinguished name, non-null before 0.9.28, may be null as of 0.9.28 * @param st The ST (state or province) in the distinguished name, non-null before 0.9.28, may be null as of 0.9.28 * @param c The C (country) in the distinguished name, non-null before 0.9.28, may be null as of 0.9.28 * * @return length 4 array: * rv[0] is a Java PublicKey * rv[1] is a Java PrivateKey * rv[2] is a Java X509Certificate * rv[3] is a Java X509CRL */ public static Object[] generate(String cname, String ou, String o, String l, String st, String c, int validDays, SigType type) throws GeneralSecurityException { SimpleDataStructure[] keys = KeyGenerator.getInstance().generateSigningKeys(type); SigningPublicKey pub = (SigningPublicKey) keys[0]; SigningPrivateKey priv = (SigningPrivateKey) keys[1]; PublicKey jpub = SigUtil.toJavaKey(pub); PrivateKey jpriv = SigUtil.toJavaKey(priv); String oid; switch (type) { case DSA_SHA1: case ECDSA_SHA256_P256: case ECDSA_SHA384_P384: case ECDSA_SHA512_P521: case RSA_SHA256_2048: case RSA_SHA384_3072: case RSA_SHA512_4096: case EdDSA_SHA512_Ed25519: case EdDSA_SHA512_Ed25519ph: oid = type.getOID(); break; default: throw new GeneralSecurityException("Unsupported: " + type); } byte[] sigoid = getEncodedOIDSeq(oid); byte[] tbs = genTBS(cname, ou, o, l, st, c, validDays, sigoid, jpub); int tbslen = tbs.length; Signature sig = DSAEngine.getInstance().sign(tbs, priv); if (sig == null) throw new GeneralSecurityException("sig failed"); byte[] sigbytes= SigUtil.toJavaSig(sig); int seqlen = tbslen + sigoid.length + spaceFor(sigbytes.length + 1); int totlen = spaceFor(seqlen); byte[] cb = new byte[totlen]; int idx = 0; // construct the whole encoded cert cb[idx++] = 0x30; idx = intToASN1(cb, idx, seqlen); // TBS cert System.arraycopy(tbs, 0, cb, idx, tbs.length); idx += tbs.length; // sig algo System.arraycopy(sigoid, 0, cb, idx, sigoid.length); idx += sigoid.length; // sig (bit string) cb[idx++] = 0x03; idx = intToASN1(cb, idx, sigbytes.length + 1); cb[idx++] = 0; System.arraycopy(sigbytes, 0, cb, idx, sigbytes.length); if (DEBUG) { System.out.println("Sig OID"); System.out.println(HexDump.dump(sigoid)); System.out.println("Signature"); System.out.println(HexDump.dump(sigbytes)); System.out.println("Whole cert"); System.out.println(HexDump.dump(cb)); } ByteArrayInputStream bais = new ByteArrayInputStream(cb); X509Certificate cert; try { CertificateFactory cf = CertificateFactory.getInstance("X.509"); cert = (X509Certificate)cf.generateCertificate(bais); cert.checkValidity(); } catch (IllegalArgumentException iae) { throw new GeneralSecurityException("cert error", iae); } X509CRL crl = generateCRL(cert, validDays, 1, sigoid, jpriv); // some simple tests PublicKey cpub = cert.getPublicKey(); cert.verify(cpub); if (!cpub.equals(jpub)) throw new GeneralSecurityException("pubkey mismatch"); // todo crl tests Object[] rv = { jpub, jpriv, cert, crl }; return rv; } /** * Generate a CRL for the given cert, signed with the given private key */ private static X509CRL generateCRL(X509Certificate cert, int validDays, int crlNum, byte[] sigoid, PrivateKey jpriv) throws GeneralSecurityException { SigningPrivateKey priv = SigUtil.fromJavaKey(jpriv); byte[] tbs = genTBSCRL(cert, validDays, crlNum, sigoid); int tbslen = tbs.length; Signature sig = DSAEngine.getInstance().sign(tbs, priv); if (sig == null) throw new GeneralSecurityException("sig failed"); byte[] sigbytes= SigUtil.toJavaSig(sig); int seqlen = tbslen + sigoid.length + spaceFor(sigbytes.length + 1); int totlen = spaceFor(seqlen); byte[] cb = new byte[totlen]; int idx = 0; // construct the whole encoded cert cb[idx++] = 0x30; idx = intToASN1(cb, idx, seqlen); // TBS cert System.arraycopy(tbs, 0, cb, idx, tbs.length); idx += tbs.length; // sig algo System.arraycopy(sigoid, 0, cb, idx, sigoid.length); idx += sigoid.length; // sig (bit string) cb[idx++] = 0x03; idx = intToASN1(cb, idx, sigbytes.length + 1); cb[idx++] = 0; System.arraycopy(sigbytes, 0, cb, idx, sigbytes.length); /**** if (DEBUG) { System.out.println("CRL Sig OID"); System.out.println(HexDump.dump(sigoid)); System.out.println("CRL Signature"); System.out.println(HexDump.dump(sigbytes)); System.out.println("Whole CRL"); System.out.println(HexDump.dump(cb)); } ****/ ByteArrayInputStream bais = new ByteArrayInputStream(cb); X509CRL rv; try { CertificateFactory cf = CertificateFactory.getInstance("X.509"); // wow, unlike for x509Certificates, there's no validation here at all // ASN.1 errors don't cause any exceptions rv = (X509CRL)cf.generateCRL(bais); } catch (IllegalArgumentException iae) { throw new GeneralSecurityException("cert error", iae); } return rv; } /** * @param cname the common name, non-null * @param ou The OU (organizational unit) in the distinguished name, non-null before 0.9.28, may be null as of 0.9.28 * @param o The O (organization)in the distinguished name, non-null before 0.9.28, may be null as of 0.9.28 * @param l The L (city or locality) in the distinguished name, non-null before 0.9.28, may be null as of 0.9.28 * @param st The ST (state or province) in the distinguished name, non-null before 0.9.28, may be null as of 0.9.28 * @param c The C (country) in the distinguished name, non-null before 0.9.28, may be null as of 0.9.28 */ private static byte[] genTBS(String cname, String ou, String o, String l, String st, String c, int validDays, byte[] sigoid, PublicKey jpub) throws GeneralSecurityException { // a0 ???, int = 2 byte[] version = { (byte) 0xa0, 3, 2, 1, 2 }; // positive serial number (long) byte[] serial = new byte[10]; serial[0] = 2; serial[1] = 8; RandomSource.getInstance().nextBytes(serial, 2, 8); serial[2] &= 0x7f; // going to use this for both issuer and subject StringBuilder buf = new StringBuilder(128); buf.append("CN=").append(cname); if (ou != null) buf.append(",OU=").append(ou); if (o != null) buf.append(",O=").append(o); if (l != null) buf.append(",L=").append(l); if (st != null) buf.append(",ST=").append(st); if (c != null) buf.append(",C=").append(c); String dname = buf.toString(); byte[] issuer = (new X500Principal(dname, OIDS)).getEncoded(); byte[] validity = getValidity(validDays); byte[] subject = issuer; byte[] pubbytes = jpub.getEncoded(); byte[] extbytes = getExtensions(pubbytes, cname); int len = version.length + serial.length + sigoid.length + issuer.length + validity.length + subject.length + pubbytes.length + extbytes.length; int totlen = spaceFor(len); byte[] rv = new byte[totlen]; int idx = 0; rv[idx++] = 0x30; idx = intToASN1(rv, idx, len); System.arraycopy(version, 0, rv, idx, version.length); idx += version.length; System.arraycopy(serial, 0, rv, idx, serial.length); idx += serial.length; System.arraycopy(sigoid, 0, rv, idx, sigoid.length); idx += sigoid.length; System.arraycopy(issuer, 0, rv, idx, issuer.length); idx += issuer.length; System.arraycopy(validity, 0, rv, idx, validity.length); idx += validity.length; System.arraycopy(subject, 0, rv, idx, subject.length); idx += subject.length; System.arraycopy(pubbytes, 0, rv, idx, pubbytes.length); idx += pubbytes.length; System.arraycopy(extbytes, 0, rv, idx, extbytes.length); if (DEBUG) { System.out.println(HexDump.dump(version)); System.out.println("serial"); System.out.println(HexDump.dump(serial)); System.out.println("oid"); System.out.println(HexDump.dump(sigoid)); System.out.println("issuer"); System.out.println(HexDump.dump(issuer)); System.out.println("valid"); System.out.println(HexDump.dump(validity)); System.out.println("subject"); System.out.println(HexDump.dump(subject)); System.out.println("pub"); System.out.println(HexDump.dump(pubbytes)); System.out.println("extensions"); System.out.println(HexDump.dump(extbytes)); System.out.println("TBS cert"); System.out.println(HexDump.dump(rv)); } return rv; } /** * * @param crlNum 0-255 because lazy * @return ASN.1 encoded object */ private static byte[] genTBSCRL(X509Certificate cert, int validDays, int crlNum, byte[] sigalg) throws GeneralSecurityException { // a0 ???, int = 2 byte[] version = { 2, 1, 1 }; byte[] issuer = cert.getIssuerX500Principal().getEncoded(); byte[] serial = cert.getSerialNumber().toByteArray(); if (serial.length > 255) throw new IllegalArgumentException(); long now = System.currentTimeMillis(); long then = now + (validDays * 24L * 60 * 60 * 1000); // used for CRL time and revocation time byte[] nowbytes = getDate(now); // used for next CRL time byte[] thenbytes = getDate(then); byte[] extbytes = getCRLExtensions(crlNum); int revlen = 2 + serial.length + nowbytes.length; int revseqlen = spaceFor(revlen); int revsseqlen = spaceFor(revseqlen); int len = version.length + sigalg.length + issuer.length + nowbytes.length + thenbytes.length + revsseqlen + extbytes.length; int totlen = spaceFor(len); byte[] rv = new byte[totlen]; int idx = 0; rv[idx++] = 0x30; idx = intToASN1(rv, idx, len); System.arraycopy(version, 0, rv, idx, version.length); idx += version.length; System.arraycopy(sigalg, 0, rv, idx, sigalg.length); idx += sigalg.length; System.arraycopy(issuer, 0, rv, idx, issuer.length); idx += issuer.length; System.arraycopy(nowbytes, 0, rv, idx, nowbytes.length); idx += nowbytes.length; System.arraycopy(thenbytes, 0, rv, idx, thenbytes.length); idx += thenbytes.length; // the certs rv[idx++] = 0x30; idx = intToASN1(rv, idx, revseqlen); // the cert rv[idx++] = 0x30; idx = intToASN1(rv, idx, revlen); rv[idx++] = 0x02; rv[idx++] = (byte) serial.length; System.arraycopy(serial, 0, rv, idx, serial.length); idx += serial.length; System.arraycopy(nowbytes, 0, rv, idx, nowbytes.length); idx += nowbytes.length; // extensions System.arraycopy(extbytes, 0, rv, idx, extbytes.length); if (DEBUG) { System.out.println("version"); System.out.println(HexDump.dump(version)); System.out.println("sigalg"); System.out.println(HexDump.dump(sigalg)); System.out.println("issuer"); System.out.println(HexDump.dump(issuer)); System.out.println("now"); System.out.println(HexDump.dump(nowbytes)); System.out.println("then"); System.out.println(HexDump.dump(thenbytes)); System.out.println("serial"); System.out.println(HexDump.dump(serial)); System.out.println("extensions"); System.out.println(HexDump.dump(extbytes)); System.out.println("TBS CRL"); System.out.println(HexDump.dump(rv)); } return rv; } /** * @param val the length of the value, 65535 max * @return the length of the TLV */ private static int spaceFor(int val) { int rv; if (val > 255) rv = 3; else if (val > 127) rv = 2; else rv = 1; return 1 + rv + val; } /** * Sequence of two UTCDates * @return 32 bytes ASN.1 encoded object */ private static byte[] getValidity(int validDays) { byte[] rv = new byte[32]; rv[0] = 0x30; rv[1] = 30; long now = System.currentTimeMillis(); long then = now + (validDays * 24L * 60 * 60 * 1000); byte[] nowbytes = getDate(now); byte[] thenbytes = getDate(then); System.arraycopy(nowbytes, 0, rv, 2, 15); System.arraycopy(thenbytes, 0, rv, 17, 15); return rv; } /** * A single UTCDate * @return 15 bytes ASN.1 encoded object */ private static byte[] getDate(long now) { // UTCDate format (HH 0-23) SimpleDateFormat fmt = new SimpleDateFormat("yyMMddHHmmss"); fmt.setTimeZone(TimeZone.getTimeZone("GMT")); byte[] nowbytes = DataHelper.getASCII(fmt.format(new Date(now))); if (nowbytes.length != 12) throw new IllegalArgumentException(); byte[] rv = new byte[15]; rv[0] = 0x17; rv[1] = 13; System.arraycopy(nowbytes, 0, rv, 2, 12); rv[14] = (byte) 'Z'; return rv; } /** * Add the following extensions: * 1) Subject Key Identifier * 2) Key Usage * 3) Basic Constraints * 4) Subject Alternative Name * 5) Authority Key Identifier * (not necessarily output in that order) * * Ref: RFC 5280 * * @param pubbytes bit string * @return ASN.1 encoded object */ private static byte[] getExtensions(byte[] pubbytes, String cname) { // RFC 2549 sec. 4.2.1.2 // subject public key identifier is the sha1 hash of the bit string of the public key // without the tag, length, and igore fields int pidx = 1; int skip = pubbytes[pidx++]; if ((skip & 0x80)!= 0) pidx += skip & 0x80; pidx++; // ignore MessageDigest md = SHA1.getInstance(); md.update(pubbytes, pidx, pubbytes.length - pidx); byte[] sha = md.digest(); byte[] oid1 = getEncodedOID(OID_SKI); byte[] oid2 = getEncodedOID(OID_USAGE); byte[] oid3 = getEncodedOID(OID_BASIC); byte[] oid4 = getEncodedOID(OID_SAN); byte[] oid5 = getEncodedOID(OID_AKI); byte[] TRUE = new byte[] { 1, 1, (byte) 0xff }; int wrap1len = spaceFor(sha.length); int ext1len = oid1.length + spaceFor(wrap1len); int wrap2len = 4; int ext2len = oid2.length + TRUE.length + spaceFor(wrap2len); int wrap3len = spaceFor(TRUE.length); int ext3len = oid3.length + TRUE.length + spaceFor(wrap3len); byte[] cnameBytes = DataHelper.getASCII(cname); int wrap41len = spaceFor(cnameBytes.length); int wrap4len = spaceFor(wrap41len); int ext4len = oid4.length + spaceFor(wrap4len); int wrap51len = wrap1len; int wrap5len = spaceFor(wrap51len); int ext5len = oid5.length + spaceFor(wrap5len); int extslen = spaceFor(ext1len) + spaceFor(ext2len) + spaceFor(ext4len) + spaceFor(ext5len); final boolean isCA = !cname.contains("@"); if (isCA) extslen += spaceFor(ext3len); int seqlen = spaceFor(extslen); int totlen = spaceFor(seqlen); byte[] rv = new byte[totlen]; int idx = 0; rv[idx++] = (byte) 0xa3; idx = intToASN1(rv, idx, seqlen); rv[idx++] = (byte) 0x30; idx = intToASN1(rv, idx, extslen); // Subject Key Identifier rv[idx++] = (byte) 0x30; idx = intToASN1(rv, idx, ext1len); System.arraycopy(oid1, 0, rv, idx, oid1.length); idx += oid1.length; // octet string wraps an octet string rv[idx++] = (byte) 0x04; idx = intToASN1(rv, idx, wrap1len); rv[idx++] = (byte) 0x04; idx = intToASN1(rv, idx, sha.length); System.arraycopy(sha, 0, rv, idx, sha.length); idx += sha.length; // Authority Key Identifier rv[idx++] = (byte) 0x30; idx = intToASN1(rv, idx, ext5len); System.arraycopy(oid5, 0, rv, idx, oid5.length); idx += oid5.length; // octet string wraps a sequence containing a choice 0 (key identifier) byte string rv[idx++] = (byte) 0x04; idx = intToASN1(rv, idx, wrap5len); rv[idx++] = (byte) 0x30; idx = intToASN1(rv, idx, wrap51len); rv[idx++] = (byte) 0x80; // choice idx = intToASN1(rv, idx, sha.length); System.arraycopy(sha, 0, rv, idx, sha.length); idx += sha.length; if (isCA) { // Basic Constraints (critical) rv[idx++] = (byte) 0x30; idx = intToASN1(rv, idx, ext3len); System.arraycopy(oid3, 0, rv, idx, oid3.length); idx += oid3.length; System.arraycopy(TRUE, 0, rv, idx, TRUE.length); idx += TRUE.length; // octet string wraps an sequence containing TRUE rv[idx++] = (byte) 0x04; idx = intToASN1(rv, idx, wrap3len); rv[idx++] = (byte) 0x30; idx = intToASN1(rv, idx, TRUE.length); System.arraycopy(TRUE, 0, rv, idx, TRUE.length); idx += TRUE.length; } // Key Usage (critical) rv[idx++] = (byte) 0x30; idx = intToASN1(rv, idx, ext2len); System.arraycopy(oid2, 0, rv, idx, oid2.length); idx += oid2.length; System.arraycopy(TRUE, 0, rv, idx, TRUE.length); idx += TRUE.length; // octet string wraps a bit string rv[idx++] = (byte) 0x04; idx = intToASN1(rv, idx, wrap2len); rv[idx++] = (byte) 0x03; rv[idx++] = (byte) 0x02; rv[idx++] = (byte) 0x01; rv[idx++] = (byte) 0xa6; // sig, key encipherment, cert, CRL // Subject Alternative Name rv[idx++] = (byte) 0x30; idx = intToASN1(rv, idx, ext4len); System.arraycopy(oid4, 0, rv, idx, oid4.length); idx += oid4.length; // octet string wraps a sequence containing a choice 2 (DNSName) IA5String rv[idx++] = (byte) 0x04; idx = intToASN1(rv, idx, wrap4len); rv[idx++] = (byte) 0x30; idx = intToASN1(rv, idx, wrap41len); rv[idx++] = (byte) (isCA ? 0x82 : 0x81); // choice, dNSName or rfc822Name, IA5String implied idx = intToASN1(rv, idx, cnameBytes.length); System.arraycopy(cnameBytes, 0, rv, idx, cnameBytes.length); idx += cnameBytes.length; return rv; } /** * * @param crlNum 0-255 because lazy * @return 16 bytes ASN.1 encoded object */ private static byte[] getCRLExtensions(int crlNum) { if (crlNum < 0 || crlNum > 255) throw new IllegalArgumentException(); byte[] oid = getEncodedOID(OID_CRLNUM); int extlen = oid.length + 5; int extslen = spaceFor(extlen); int seqlen = spaceFor(extslen); int totlen = spaceFor(seqlen); byte[] rv = new byte[totlen]; int idx = 0; rv[idx++] = (byte) 0xa0; idx = intToASN1(rv, idx, seqlen); rv[idx++] = (byte) 0x30; idx = intToASN1(rv, idx, extslen); rv[idx++] = (byte) 0x30; idx = intToASN1(rv, idx, extlen); System.arraycopy(oid, 0, rv, idx, oid.length); idx += oid.length; // don't know why we wrap the int in an octet string rv[idx++] = (byte) 0x04; rv[idx++] = (byte) 3; rv[idx++] = (byte) 0x02; rv[idx++] = (byte) 1; rv[idx++] = (byte) crlNum; return rv; } /** * 0x30 len 0x06 len encodedbytes... 0x05 0 * @return ASN.1 encoded object * @throws IllegalArgumentException */ private static byte[] getEncodedOIDSeq(String oid) { ByteArrayOutputStream baos = new ByteArrayOutputStream(16); baos.write(0x30); // len to be filled in later baos.write(0); byte[] b = getEncodedOID(oid); baos.write(b, 0, b.length); // NULL baos.write(0x05); baos.write(0); byte[] rv = baos.toByteArray(); rv[1] = (byte) (rv.length - 2); return rv; } /** * 0x06 len encodedbytes... * @return ASN.1 encoded object * @throws IllegalArgumentException */ private static byte[] getEncodedOID(String oid) { ByteArrayOutputStream baos = new ByteArrayOutputStream(16); baos.write(0x06); // len to be filled in later baos.write(0); String[] f = DataHelper.split(oid, "[.]"); if (f.length < 2) throw new IllegalArgumentException("length: " + f.length); baos.write((40 * Integer.parseInt(f[0])) + Integer.parseInt(f[1])); for (int i = 2; i < f.length; i++) { int v = Integer.parseInt(f[i]); if (v >= 128 * 128 * 128 || v < 0) throw new IllegalArgumentException(); if (v >= 128 * 128) baos.write((v >> 14) | 0x80); if (v >= 128) baos.write((v >> 7) | 0x80); baos.write(v & 0x7f); } byte[] rv = baos.toByteArray(); if (rv.length > 129) throw new IllegalArgumentException(); rv[1] = (byte) (rv.length - 2); return rv; } /**** public static void main(String[] args) { try { int i = 0; for (SigType t : java.util.EnumSet.allOf(SigType.class)) { if (t.isAvailable()) test("test" + i, t); i++; } } catch (Exception e) { e.printStackTrace(); } } private static final void test(String name, SigType type) throws Exception { Object[] rv = generate("cname@example.com", "ou", "o", null, "st", "c", 3652, type); PublicKey jpub = (PublicKey) rv[0]; PrivateKey jpriv = (PrivateKey) rv[1]; X509Certificate cert = (X509Certificate) rv[2]; X509CRL crl = (X509CRL) rv[3]; File ks = new File(name + ".ks"); List<X509Certificate> certs = new ArrayList<X509Certificate>(1); certs.add(cert); KeyStoreUtil.storePrivateKey(ks, "changeit", "foo", "foobar", jpriv, certs); System.out.println("Private key saved to " + ks + " with alias foo, password foobar, keystore password changeit"); File cf = new File(name + ".crt"); CertUtil.saveCert(cert, cf); System.out.println("Certificate saved to " + cf); File pf = new File(name + ".priv"); FileOutputStream pfs = new SecureFileOutputStream(pf); KeyStoreUtil.exportPrivateKey(ks, "changeit", "foo", "foobar", pfs); pfs.close(); System.out.println("Private key saved to " + pf); File cr = new File(name + ".crl"); CertUtil.saveCRL(crl, cr); System.out.println("CRL saved to " + cr); } ****/ }