From 981b708230649b4515739b45b8af89e7f0a2fcb6 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Tue, 9 Feb 2016 20:48:23 +0000 Subject: [PATCH] Crypto: Use new internal key generation instead of calling out to keytool; save CRL for new su3 amd family keys Allow su3 bulksign for xml files (news) --- core/java/src/net/i2p/crypto/CertUtil.java | 6 +- .../java/src/net/i2p/crypto/KeyStoreUtil.java | 138 ++++++++++++++++++ core/java/src/net/i2p/crypto/SU3File.java | 64 +++++--- .../i2p/router/crypto/FamilyKeyCrypto.java | 72 +++++---- 4 files changed, 229 insertions(+), 51 deletions(-) diff --git a/core/java/src/net/i2p/crypto/CertUtil.java b/core/java/src/net/i2p/crypto/CertUtil.java index ecb6a5f559..97032065ac 100644 --- a/core/java/src/net/i2p/crypto/CertUtil.java +++ b/core/java/src/net/i2p/crypto/CertUtil.java @@ -97,9 +97,9 @@ public final class CertUtil { * Writes a certificate in base64 format. * Does NOT close the stream. Throws on all errors. * - * @since 0.9.24, pulled out of saveCert() + * @since 0.9.24, pulled out of saveCert(), public since 0.9.25 */ - private static void exportCert(Certificate cert, OutputStream out) + public static void exportCert(Certificate cert, OutputStream out) throws IOException, CertificateEncodingException { // Get the encoded form which is suitable for exporting byte[] buf = cert.getEncoded(); @@ -365,7 +365,7 @@ public final class CertUtil { * @throws CRLException if the crl does not support encoding * @since 0.9.25 */ - private static void exportCRL(X509CRL crl, OutputStream out) + public static void exportCRL(X509CRL crl, OutputStream out) throws IOException, CRLException { byte[] buf = crl.getEncoded(); writePEM(buf, "X509 CRL", out); diff --git a/core/java/src/net/i2p/crypto/KeyStoreUtil.java b/core/java/src/net/i2p/crypto/KeyStoreUtil.java index 1de4687de2..24844c2a55 100644 --- a/core/java/src/net/i2p/crypto/KeyStoreUtil.java +++ b/core/java/src/net/i2p/crypto/KeyStoreUtil.java @@ -9,12 +9,16 @@ import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.PrivateKey; +import java.security.PublicKey; import java.security.cert.Certificate; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; +import java.security.cert.X509CRL; import java.util.ArrayList; +import java.util.Collections; import java.util.Enumeration; +import java.util.EnumSet; import java.util.List; import java.util.Locale; @@ -430,6 +434,9 @@ public final class KeyStoreUtil { /** * Create a keypair and store it in the keystore at ks, creating it if necessary. * + * For new code, the createKeys() with the SigType argument is recommended over this one, + * as it throws exceptions, and returns the certificate and CRL. + * * Warning, may take a long time. * * @param ks path to the keystore @@ -447,6 +454,137 @@ public final class KeyStoreUtil { */ public static boolean createKeys(File ks, String ksPW, String alias, String cname, String ou, int validDays, String keyAlg, int keySize, String keyPW) { + boolean useKeytool = I2PAppContext.getGlobalContext().getBooleanProperty("crypto.useExternalKeytool"); + if (useKeytool) { + return createKeysCLI(ks, ksPW, alias, cname, ou, validDays, keyAlg, keySize, keyPW); + } else { + try { + createKeysAndCRL(ks, ksPW, alias, cname, ou, validDays, keyAlg, keySize, keyPW); + return true; + } catch (GeneralSecurityException gse) { + error("Create keys error", gse); + return false; + } catch (IOException ioe) { + error("Create keys error", ioe); + return false; + } + } + } + + /** + * New way - Native Java, does not call out to keytool. + * Create a keypair and store it in the keystore at ks, creating it if necessary. + * + * This returns the public key, private key, certificate, and CRL in an array. + * All of these are Java classes. Keys may be converted to I2P classes with SigUtil. + * The private key and selfsigned cert are stored in the keystore. + * The public key may be derived from the private key with KeyGenerator.getSigningPublicKey(). + * The public key certificate may be stored separately with + * CertUtil.saveCert() if desired. + * The CRL is not stored by this method, store it with + * CertUtil.saveCRL() or CertUtil.exportCRL() if desired. + * + * Throws on all errors. + * Warning, may take a long time. + * + * @param ks path to the keystore + * @param ksPW the keystore password + * @param alias the name of the key + * @param cname e.g. randomstuff.console.i2p.net + * @param ou e.g. console + * @param validDays e.g. 3652 (10 years) + * @param keyAlg e.g. DSA , RSA, EC + * @param keySize e.g. 1024 + * @param keyPW the key password, must be at least 6 characters + * @return all you need: + * rv[0] is a Java PublicKey + * rv[1] is a Java PrivateKey + * rv[2] is a Java X509Certificate + * rv[3] is a Java X509CRL + * @since 0.9.25 + */ + public static Object[] createKeysAndCRL(File ks, String ksPW, String alias, String cname, String ou, + int validDays, String keyAlg, int keySize, String keyPW) + throws GeneralSecurityException, IOException { + String algoName = getSigAlg(keySize, keyAlg); + SigType type = null; + for (SigType t : EnumSet.allOf(SigType.class)) { + if (t.getAlgorithmName().equals(algoName)) { + type = t; + break; + } + } + if (type == null) + throw new GeneralSecurityException("Unsupported algorithm/size: " + keyAlg + '/' + keySize); + return createKeysAndCRL(ks, ksPW, alias, cname, ou, validDays, type, keyPW); + } + + /** + * New way - Native Java, does not call out to keytool. + * Create a keypair and store it in the keystore at ks, creating it if necessary. + * + * This returns the public key, private key, certificate, and CRL in an array. + * All of these are Java classes. Keys may be converted to I2P classes with SigUtil. + * The private key and selfsigned cert are stored in the keystore. + * The public key may be derived from the private key with KeyGenerator.getSigningPublicKey(). + * The public key certificate may be stored separately with + * CertUtil.saveCert() if desired. + * The CRL is not stored by this method, store it with + * CertUtil.saveCRL() or CertUtil.exportCRL() if desired. + * + * Throws on all errors. + * Warning, may take a long time. + * + * @param ks path to the keystore + * @param ksPW the keystore password + * @param alias the name of the key + * @param cname e.g. randomstuff.console.i2p.net + * @param ou e.g. console + * @param validDays e.g. 3652 (10 years) + * @param keyAlg e.g. DSA , RSA, EC + * @param keySize e.g. 1024 + * @param keyPW the key password, must be at least 6 characters + * @return all you need: + * rv[0] is a Java PublicKey + * rv[1] is a Java PrivateKey + * rv[2] is a Java X509Certificate + * rv[3] is a Java X509CRL + * @since 0.9.25 + */ + public static Object[] createKeysAndCRL(File ks, String ksPW, String alias, String cname, String ou, + int validDays, SigType type, String keyPW) + throws GeneralSecurityException, IOException { + Object[] rv = SelfSignedGenerator.generate(cname, ou, "XX", "I2P Anonymous Network", "XX", "XX", validDays, type); + PublicKey jpub = (PublicKey) rv[0]; + PrivateKey jpriv = (PrivateKey) rv[1]; + X509Certificate cert = (X509Certificate) rv[2]; + X509CRL crl = (X509CRL) rv[3]; + List<X509Certificate> certs = Collections.singletonList(cert); + storePrivateKey(ks, ksPW, alias, keyPW, jpriv, certs); + return rv; + } + + /** + * OLD way - keytool + * Create a keypair and store it in the keystore at ks, creating it if necessary. + * + * Warning, may take a long time. + * + * @param ks path to the keystore + * @param ksPW the keystore password + * @param alias the name of the key + * @param cname e.g. randomstuff.console.i2p.net + * @param ou e.g. console + * @param validDays e.g. 3652 (10 years) + * @param keyAlg e.g. DSA , RSA, EC + * @param keySize e.g. 1024 + * @param keyPW the key password, must be at least 6 characters + * + * @return success + * @since 0.8.3, consolidated from RouterConsoleRunner and SSLClientListenerRunner in 0.9.9 + */ + private static boolean createKeysCLI(File ks, String ksPW, String alias, String cname, String ou, + int validDays, String keyAlg, int keySize, String keyPW) { if (ks.exists()) { try { if (getCert(ks, ksPW, alias) != null) { diff --git a/core/java/src/net/i2p/crypto/SU3File.java b/core/java/src/net/i2p/crypto/SU3File.java index 3c8154c6ee..38302e52ec 100644 --- a/core/java/src/net/i2p/crypto/SU3File.java +++ b/core/java/src/net/i2p/crypto/SU3File.java @@ -15,6 +15,8 @@ import java.security.GeneralSecurityException; import java.security.MessageDigest; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.cert.X509Certificate; +import java.security.cert.X509CRL; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; @@ -33,6 +35,7 @@ import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.Signature; import net.i2p.data.SimpleDataStructure; +import net.i2p.util.SecureFileOutputStream; /** * Succesor to the ".sud" format used in TrustedUpdate. @@ -538,10 +541,11 @@ public class SU3File { String ctype = null; String ftype = null; String kfile = null; + String crlfile = null; String kspass = KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD; boolean error = false; boolean shouldVerify = true; - Getopt g = new Getopt("SU3File", args, "t:c:f:k:xp:"); + Getopt g = new Getopt("SU3File", args, "t:c:f:k:xp:r:"); int c; while ((c = g.getopt()) != -1) { switch (c) { @@ -561,6 +565,10 @@ public class SU3File { kfile = g.getOptarg(); break; + case 'r': + crlfile = g.getOptarg(); + break; + case 'x': shouldVerify = false; break; @@ -598,7 +606,10 @@ public class SU3File { } else if ("verifysig".equals(cmd)) { ok = verifySigCLI(a.get(0), kfile); } else if ("keygen".equals(cmd)) { - ok = genKeysCLI(stype, a.get(0), a.get(1), a.get(2), kspass); + Properties props = new Properties(); + props.setProperty("prng.bufferSize", "16384"); + new I2PAppContext(props); + ok = genKeysCLI(stype, a.get(0), a.get(1), crlfile, a.get(2), kspass); } else if ("extract".equals(cmd)) { ok = extractCLI(a.get(0), a.get(1), shouldVerify, kfile); } else { @@ -614,9 +625,10 @@ public class SU3File { } private static final void showUsageCLI() { - System.err.println("Usage: SU3File keygen [-t type|code] [-p keystorepw] publicKeyFile keystore.ks you@mail.i2p\n" + + System.err.println("Usage: SU3File keygen [-t type|code] [-p keystorepw] [-r crlFile.crl] publicKeyFile.crt keystore.ks you@mail.i2p\n" + " SU3File sign [-t type|code] [-c type|code] [-f type|code] [-p keystorepw] inputFile.zip signedFile.su3 keystore.ks version you@mail.i2p\n" + " SU3File bulksign [-t type|code] [-c type|code] [-p keystorepw] directory keystore.ks version you@mail.i2p\n" + + " (signs all .zip and .xml files in the directory)\n" + " SU3File showversion signedFile.su3\n" + " SU3File verifysig [-k file.crt] signedFile.su3 ## -k use this pubkey cert for verification\n" + " SU3File extract [-x] [-k file.crt] signedFile.su3 outFile ## -x don't check sig"); @@ -750,7 +762,7 @@ public class SU3File { int success = 0; for (File in : files) { String inputFile = in.getPath(); - if (!inputFile.endsWith(".zip")) + if (!inputFile.endsWith(".zip") && !inputFile.endsWith(".xml")) continue; String signedFile = inputFile.substring(0, inputFile.length() - 4) + ".su3"; boolean rv = signCLI(stype, ctype, null, inputFile, signedFile, @@ -897,26 +909,29 @@ public class SU3File { } /** + * @param crlFile may be null; non-null to save * @return success * @since 0.9.9 */ private static final boolean genKeysCLI(String stype, String publicKeyFile, String privateKeyFile, - String alias, String kspass) { + String crlFile, String alias, String kspass) { SigType type = stype == null ? SigType.getByCode(Integer.valueOf(DEFAULT_SIG_CODE)) : SigType.parseSigType(stype); if (type == null) { System.out.println("Signature type " + stype + " is not supported"); return false; } - return genKeysCLI(type, publicKeyFile, privateKeyFile, alias, kspass); + return genKeysCLI(type, publicKeyFile, privateKeyFile, crlFile, alias, kspass); } /** * Writes Java-encoded keys (X.509 for public and PKCS#8 for private) + * + * @param crlFile may be null; non-null to save * @return success * @since 0.9.9 */ private static final boolean genKeysCLI(SigType type, String publicKeyFile, String privateKeyFile, - String alias, String kspass) { + String crlFile, String alias, String kspass) { File pubFile = new File(publicKeyFile); if (pubFile.exists()) { System.out.println("Error: Not overwriting file " + publicKeyFile); @@ -948,24 +963,29 @@ public class SU3File { } catch (IOException ioe) { return false; } - int keylen = type.getPubkeyLen() * 8; - if (type.getBaseAlgorithm() == SigAlgo.EC) { - keylen /= 2; - if (keylen == 528) - keylen = 521; - } - boolean success = KeyStoreUtil.createKeys(ksFile, kspass, alias, - alias, "I2P", 3652, type.getBaseAlgorithm().getName(), - keylen, keypw); - if (!success) { + OutputStream out = null; + try { + Object[] rv = KeyStoreUtil.createKeysAndCRL(ksFile, kspass, alias, + alias, "I2P", 3652, type, keypw); + X509Certificate cert = (X509Certificate) rv[2]; + out = new SecureFileOutputStream(publicKeyFile); + CertUtil.exportCert(cert, out); + if (crlFile != null) { + out.close(); + X509CRL crl = (X509CRL) rv[3]; + out = new SecureFileOutputStream(crlFile); + CertUtil.exportCRL(crl, out); + } + } catch (GeneralSecurityException gse) { System.err.println("Error creating keys for " + alias); + gse.printStackTrace(); return false; - } - File outfile = new File(publicKeyFile); - success = KeyStoreUtil.exportCert(ksFile, KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD, alias, outfile); - if (!success) { - System.err.println("Error writing public key for " + alias + " to " + outfile); + } catch (IOException ioe) { + System.err.println("Error creating keys for " + alias); + ioe.printStackTrace(); return false; + } finally { + if (out != null) try { out.close(); } catch (IOException ioe) {} } return true; } diff --git a/router/java/src/net/i2p/router/crypto/FamilyKeyCrypto.java b/router/java/src/net/i2p/router/crypto/FamilyKeyCrypto.java index 39b81458a2..cdb0811dc7 100644 --- a/router/java/src/net/i2p/router/crypto/FamilyKeyCrypto.java +++ b/router/java/src/net/i2p/router/crypto/FamilyKeyCrypto.java @@ -8,6 +8,8 @@ import java.security.KeyStore; import java.security.GeneralSecurityException; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.cert.X509Certificate; +import java.security.cert.X509CRL; import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -51,6 +53,7 @@ public class FamilyKeyCrypto { public static final String PROP_FAMILY_NAME = "netdb.family.name"; public static final String PROP_KEY_PASSWORD = "netdb.family.keyPassword"; public static final String CERT_SUFFIX = ".crt"; + public static final String CRL_SUFFIX = ".crl"; public static final String KEYSTORE_PREFIX = "family-"; public static final String KEYSTORE_SUFFIX = ".ks"; public static final String CN_SUFFIX = ".family.i2p.net"; @@ -61,6 +64,7 @@ public class FamilyKeyCrypto { private static final int DEFAULT_KEY_SIZE = SigType.ECDSA_SHA256_P256.isAvailable() ? 256 : 1024; private static final String KS_DIR = "keystore"; private static final String CERT_DIR = "certificates/family"; + private static final String CRL_DIR = "crls"; public static final String OPT_NAME = "family"; public static final String OPT_SIG = "family.sig"; public static final String OPT_KEY = "family.key"; @@ -270,11 +274,12 @@ public class FamilyKeyCrypto { throw new GeneralSecurityException(s); } } - createKeyStore(ks); - // Now read it back out of the new keystore and save it in ascii form - // where the clients can get to it. - exportCert(ks); + try { + createKeyStore(ks); + } catch (IOException ioe) { + throw new GeneralSecurityException("Failed to create NetDb family keystore", ioe); + } } @@ -286,26 +291,22 @@ public class FamilyKeyCrypto { * * @throws GeneralSecurityException on all errors */ - private void createKeyStore(File ks) throws GeneralSecurityException { + private void createKeyStore(File ks) throws GeneralSecurityException, IOException { // make a random 48 character password (30 * 8 / 5) String keyPassword = KeyStoreUtil.randomString(); // and one for the cname String cname = _fname + CN_SUFFIX; - boolean success = KeyStoreUtil.createKeys(ks, KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD, _fname, cname, "family", + Object[] rv = KeyStoreUtil.createKeysAndCRL(ks, KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD, _fname, cname, "family", DEFAULT_KEY_VALID_DAYS, DEFAULT_KEY_ALGORITHM, DEFAULT_KEY_SIZE, keyPassword); - if (success) { - success = ks.exists(); - if (success) { + Map<String, String> changes = new HashMap<String, String>(); changes.put(PROP_KEYSTORE_PASSWORD, KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD); changes.put(PROP_KEY_PASSWORD, keyPassword); changes.put(PROP_FAMILY_NAME, _fname); _context.router().saveConfig(changes, null); - } - } - if (success) { + _log.logAlways(Log.INFO, "Created new private key for netdb family \"" + _fname + "\" in keystore: " + ks.getAbsolutePath() + "\n" + "Copy the keystore to the other routers in the family,\n" + @@ -314,27 +315,22 @@ public class FamilyKeyCrypto { PROP_KEYSTORE_PASSWORD + '=' + KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD + '\n' + PROP_KEY_PASSWORD + '=' + keyPassword); - } else { - String s = "Failed to create NetDb family keystore.\n" + - "This is for the Sun/Oracle keytool, others may be incompatible.\n" + - "If you create the keystore manually, you must add " + PROP_KEYSTORE_PASSWORD + " and " + PROP_KEY_PASSWORD + - " to " + (new File(_context.getConfigDir(), "router.config")).getAbsolutePath(); - _log.error(s); - throw new GeneralSecurityException(s); - } + X509Certificate cert = (X509Certificate) rv[2]; + exportCert(cert); + X509CRL crl = (X509CRL) rv[3]; + exportCRL(ks.getParentFile(), crl); } /** - * Pull the cert back OUT of the keystore and save it as ascii + * Save the public key certificate * so the clients can get to it. */ - private void exportCert(File ks) { + private void exportCert(X509Certificate cert) { File sdir = new SecureDirectory(_context.getConfigDir(), CERT_DIR); if (sdir.exists() || sdir.mkdirs()) { - String ksPass = _context.getProperty(PROP_KEYSTORE_PASSWORD, KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD); String name = _fname.replace("@", "_at_") + CERT_SUFFIX; File out = new File(sdir, name); - boolean success = KeyStoreUtil.exportCert(ks, ksPass, _fname, out); + boolean success = CertUtil.saveCert(cert, out); if (success) { _log.logAlways(Log.INFO, "Created new public key certificate for netdb family \"" + _fname + "\" in file: " + out.getAbsolutePath() + "\n" + @@ -342,10 +338,34 @@ public class FamilyKeyCrypto { "Copy the certificate to the directory $I2P/" + CERT_DIR + " for each of the other routers in the family.\n" + "Give this certificate to an I2P developer for inclusion in the next I2P release."); } else { - _log.error("Error getting SSL cert to save as ASCII"); + _log.error("Error saving family key certificate"); + } + } else { + _log.error("Error saving family key certificate"); + } + } + + /** + * Save the CRL just in case. + * @param ksdir parent of directory to save in + * @since 0.9.25 + */ + private void exportCRL(File ksdir, X509CRL crl) { + File sdir = new SecureDirectory(ksdir, CRL_DIR); + if (sdir.exists() || sdir.mkdirs()) { + String name = KEYSTORE_PREFIX + _fname.replace("@", "_at_") + '-' + System.currentTimeMillis() + CRL_SUFFIX; + File out = new File(sdir, name); + boolean success = CertUtil.saveCRL(crl, out); + if (success) { + _log.logAlways(Log.INFO, "Created certificate revocation list (CRL) for netdb family \"" + _fname + + "\" in file: " + out.getAbsolutePath() + "\n" + + "Back up the keystore and CRL files and keep them secure.\n" + + "If your private key is ever compromised, give the CRL to an I2P developer for publication."); + } else { + _log.error("Error saving family key CRL"); } } else { - _log.error("Error saving ASCII SSL keys"); + _log.error("Error saving family key CRL"); } } -- GitLab