forked from I2P_Developers/i2p.i2p
Crypto - prep for using certificates in SU3File:
Consolidate KeyStore code from SSLEepGet, I2CPSSLSocketFactory, SSLClientListenerRunner, and RouterConsoleRunner into new KeyStoreUtil and CertUtil classes in net.i2p.crypto (ticket #744)
This commit is contained in:
@ -27,6 +27,7 @@ import net.i2p.app.ClientAppManager;
|
|||||||
import net.i2p.app.ClientAppState;
|
import net.i2p.app.ClientAppState;
|
||||||
import static net.i2p.app.ClientAppState.*;
|
import static net.i2p.app.ClientAppState.*;
|
||||||
import net.i2p.apps.systray.SysTray;
|
import net.i2p.apps.systray.SysTray;
|
||||||
|
import net.i2p.crypto.KeyStoreUtil;
|
||||||
import net.i2p.data.Base32;
|
import net.i2p.data.Base32;
|
||||||
import net.i2p.data.DataHelper;
|
import net.i2p.data.DataHelper;
|
||||||
import net.i2p.jetty.I2PLogger;
|
import net.i2p.jetty.I2PLogger;
|
||||||
@ -38,8 +39,6 @@ import net.i2p.util.FileUtil;
|
|||||||
import net.i2p.util.I2PAppThread;
|
import net.i2p.util.I2PAppThread;
|
||||||
import net.i2p.util.PortMapper;
|
import net.i2p.util.PortMapper;
|
||||||
import net.i2p.util.SecureDirectory;
|
import net.i2p.util.SecureDirectory;
|
||||||
import net.i2p.util.SecureFileOutputStream;
|
|
||||||
import net.i2p.util.ShellCommand;
|
|
||||||
import net.i2p.util.SystemVersion;
|
import net.i2p.util.SystemVersion;
|
||||||
import net.i2p.util.VersionComparator;
|
import net.i2p.util.VersionComparator;
|
||||||
|
|
||||||
@ -675,12 +674,6 @@ public class RouterConsoleRunner implements RouterApp {
|
|||||||
System.err.println("Console SSL error, must set " + PROP_KEY_PASSWORD + " in " + (new File(_context.getConfigDir(), "router.config")).getAbsolutePath());
|
System.err.println("Console SSL error, must set " + PROP_KEY_PASSWORD + " in " + (new File(_context.getConfigDir(), "router.config")).getAbsolutePath());
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
File dir = ks.getParentFile();
|
|
||||||
if (!dir.exists()) {
|
|
||||||
File sdir = new SecureDirectory(dir.getAbsolutePath());
|
|
||||||
if (!sdir.mkdir())
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return createKeyStore(ks);
|
return createKeyStore(ks);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -695,31 +688,13 @@ public class RouterConsoleRunner implements RouterApp {
|
|||||||
*/
|
*/
|
||||||
private boolean createKeyStore(File ks) {
|
private boolean createKeyStore(File ks) {
|
||||||
// make a random 48 character password (30 * 8 / 5)
|
// make a random 48 character password (30 * 8 / 5)
|
||||||
byte[] rand = new byte[30];
|
String keyPassword = KeyStoreUtil.randomString();
|
||||||
_context.random().nextBytes(rand);
|
|
||||||
String keyPassword = Base32.encode(rand);
|
|
||||||
// and one for the cname
|
// and one for the cname
|
||||||
_context.random().nextBytes(rand);
|
String cname = KeyStoreUtil.randomString() + ".console.i2p.net";
|
||||||
String cname = Base32.encode(rand) + ".console.i2p.net";
|
boolean success = KeyStoreUtil.createKeys(ks, "console", cname, "Console", keyPassword);
|
||||||
|
|
||||||
String keytool = (new File(System.getProperty("java.home"), "bin/keytool")).getAbsolutePath();
|
|
||||||
String[] args = new String[] {
|
|
||||||
keytool,
|
|
||||||
"-genkey", // -genkeypair preferred in newer keytools, but this works with more
|
|
||||||
"-storetype", KeyStore.getDefaultType(),
|
|
||||||
"-keystore", ks.getAbsolutePath(),
|
|
||||||
"-storepass", DEFAULT_KEYSTORE_PASSWORD,
|
|
||||||
"-alias", "console",
|
|
||||||
"-dname", "CN=" + cname + ",OU=Console,O=I2P Anonymous Network,L=XX,ST=XX,C=XX",
|
|
||||||
"-validity", "3652", // 10 years
|
|
||||||
"-keyalg", "DSA",
|
|
||||||
"-keysize", "1024",
|
|
||||||
"-keypass", keyPassword};
|
|
||||||
boolean success = (new ShellCommand()).executeSilentAndWaitTimed(args, 30); // 30 secs
|
|
||||||
if (success) {
|
if (success) {
|
||||||
success = ks.exists();
|
success = ks.exists();
|
||||||
if (success) {
|
if (success) {
|
||||||
SecureFileOutputStream.setPerms(ks);
|
|
||||||
try {
|
try {
|
||||||
Map<String, String> changes = new HashMap();
|
Map<String, String> changes = new HashMap();
|
||||||
changes.put(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD);
|
changes.put(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD);
|
||||||
@ -733,13 +708,8 @@ public class RouterConsoleRunner implements RouterApp {
|
|||||||
"The certificate name was generated randomly, and is not associated with your " +
|
"The certificate name was generated randomly, and is not associated with your " +
|
||||||
"IP address, host name, router identity, or destination keys.");
|
"IP address, host name, router identity, or destination keys.");
|
||||||
} else {
|
} else {
|
||||||
System.err.println("Failed to create console SSL keystore using command line:");
|
System.err.println("Failed to create console SSL keystore.\n" +
|
||||||
StringBuilder buf = new StringBuilder(256);
|
"This is for the Sun/Oracle keytool, others may be incompatible.\n" +
|
||||||
for (int i = 0; i < args.length; i++) {
|
|
||||||
buf.append('"').append(args[i]).append("\" ");
|
|
||||||
}
|
|
||||||
System.err.println(buf.toString());
|
|
||||||
System.err.println("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 +
|
"If you create the keystore manually, you must add " + PROP_KEYSTORE_PASSWORD + " and " + PROP_KEY_PASSWORD +
|
||||||
" to " + (new File(_context.getConfigDir(), "router.config")).getAbsolutePath());
|
" to " + (new File(_context.getConfigDir(), "router.config")).getAbsolutePath());
|
||||||
}
|
}
|
||||||
|
@ -7,17 +7,13 @@ import java.io.InputStream;
|
|||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.cert.CertificateExpiredException;
|
|
||||||
import java.security.cert.CertificateNotYetValidException;
|
|
||||||
import java.security.cert.CertificateFactory;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
|
|
||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.crypto.KeyStoreUtil;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -71,14 +67,14 @@ class I2CPSSLSocketFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
File dir = new File(context.getConfigDir(), CERT_DIR);
|
File dir = new File(context.getConfigDir(), CERT_DIR);
|
||||||
int adds = addCerts(dir, ks);
|
int adds = KeyStoreUtil.addCerts(dir, ks);
|
||||||
int totalAdds = adds;
|
int totalAdds = adds;
|
||||||
if (adds > 0)
|
if (adds > 0)
|
||||||
info(context, "Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath());
|
info(context, "Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath());
|
||||||
|
|
||||||
File dir2 = new File(System.getProperty("user.dir"), CERT_DIR);
|
File dir2 = new File(System.getProperty("user.dir"), CERT_DIR);
|
||||||
if (!dir.getAbsolutePath().equals(dir2.getAbsolutePath())) {
|
if (!dir.getAbsolutePath().equals(dir2.getAbsolutePath())) {
|
||||||
adds = addCerts(dir2, ks);
|
adds = KeyStoreUtil.addCerts(dir2, ks);
|
||||||
totalAdds += adds;
|
totalAdds += adds;
|
||||||
if (adds > 0)
|
if (adds > 0)
|
||||||
info(context, "Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath());
|
info(context, "Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath());
|
||||||
@ -105,82 +101,6 @@ class I2CPSSLSocketFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Load all X509 Certs from a directory and add them to the
|
|
||||||
* trusted set of certificates in the key store
|
|
||||||
*
|
|
||||||
* @return number successfully added
|
|
||||||
*/
|
|
||||||
private static int addCerts(File dir, KeyStore ks) {
|
|
||||||
info("Looking for X509 Certificates in " + dir.getAbsolutePath());
|
|
||||||
int added = 0;
|
|
||||||
if (dir.exists() && dir.isDirectory()) {
|
|
||||||
File[] files = dir.listFiles();
|
|
||||||
if (files != null) {
|
|
||||||
for (int i = 0; i < files.length; i++) {
|
|
||||||
File f = files[i];
|
|
||||||
if (!f.isFile())
|
|
||||||
continue;
|
|
||||||
// use file name as alias
|
|
||||||
String alias = f.getName().toLowerCase(Locale.US);
|
|
||||||
boolean success = addCert(f, alias, ks);
|
|
||||||
if (success)
|
|
||||||
added++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return added;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load an X509 Cert from a file and add it to the
|
|
||||||
* trusted set of certificates in the key store
|
|
||||||
*
|
|
||||||
* @return success
|
|
||||||
*/
|
|
||||||
private static boolean addCert(File file, String alias, KeyStore ks) {
|
|
||||||
InputStream fis = null;
|
|
||||||
try {
|
|
||||||
fis = new FileInputStream(file);
|
|
||||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
|
||||||
X509Certificate cert = (X509Certificate)cf.generateCertificate(fis);
|
|
||||||
info("Read X509 Certificate from " + file.getAbsolutePath() +
|
|
||||||
" Issuer: " + cert.getIssuerX500Principal() +
|
|
||||||
"; Valid From: " + cert.getNotBefore() +
|
|
||||||
" To: " + cert.getNotAfter());
|
|
||||||
try {
|
|
||||||
cert.checkValidity();
|
|
||||||
} catch (CertificateExpiredException cee) {
|
|
||||||
error("Rejecting expired X509 Certificate: " + file.getAbsolutePath(), cee);
|
|
||||||
return false;
|
|
||||||
} catch (CertificateNotYetValidException cnyve) {
|
|
||||||
error("Rejecting X509 Certificate not yet valid: " + file.getAbsolutePath(), cnyve);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
ks.setCertificateEntry(alias, cert);
|
|
||||||
info("Now trusting X509 Certificate, Issuer: " + cert.getIssuerX500Principal());
|
|
||||||
} catch (GeneralSecurityException gse) {
|
|
||||||
error("Error reading X509 Certificate: " + file.getAbsolutePath(), gse);
|
|
||||||
return false;
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
error("Error reading X509 Certificate: " + file.getAbsolutePath(), ioe);
|
|
||||||
return false;
|
|
||||||
} finally {
|
|
||||||
try { if (fis != null) fis.close(); } catch (IOException foo) {}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @since 0.9.8 */
|
|
||||||
private static void info(String msg) {
|
|
||||||
log(I2PAppContext.getGlobalContext(), Log.INFO, msg, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @since 0.9.8 */
|
|
||||||
private static void error(String msg, Throwable t) {
|
|
||||||
log(I2PAppContext.getGlobalContext(), Log.ERROR, msg, t);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @since 0.9.8 */
|
/** @since 0.9.8 */
|
||||||
private static void info(I2PAppContext ctx, String msg) {
|
private static void info(I2PAppContext ctx, String msg) {
|
||||||
log(ctx, Log.INFO, msg, null);
|
log(ctx, Log.INFO, msg, null);
|
||||||
|
80
core/java/src/net/i2p/crypto/CertUtil.java
Normal file
80
core/java/src/net/i2p/crypto/CertUtil.java
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package net.i2p.crypto;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.CertificateEncodingException;
|
||||||
|
import java.security.cert.CertificateExpiredException;
|
||||||
|
import java.security.cert.CertificateNotYetValidException;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.data.Base64;
|
||||||
|
import net.i2p.util.Log;
|
||||||
|
import net.i2p.util.SecureFileOutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Java X.509 certificate utilities, consolidated from various places.
|
||||||
|
*
|
||||||
|
* @since 0.9.9
|
||||||
|
*/
|
||||||
|
public class CertUtil {
|
||||||
|
|
||||||
|
private static final int LINE_LENGTH = 64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modified from:
|
||||||
|
* http://www.exampledepot.com/egs/java.security.cert/ExportCert.html
|
||||||
|
*
|
||||||
|
* This method writes a certificate to a file in base64 format.
|
||||||
|
*
|
||||||
|
* @return success
|
||||||
|
* @since 0.8.2, moved from SSLEepGet in 0.9.9
|
||||||
|
*/
|
||||||
|
public static boolean saveCert(Certificate cert, File file) {
|
||||||
|
OutputStream os = null;
|
||||||
|
try {
|
||||||
|
// Get the encoded form which is suitable for exporting
|
||||||
|
byte[] buf = cert.getEncoded();
|
||||||
|
os = new SecureFileOutputStream(file);
|
||||||
|
PrintWriter wr = new PrintWriter(os);
|
||||||
|
wr.println("-----BEGIN CERTIFICATE-----");
|
||||||
|
String b64 = Base64.encode(buf, true); // true = use standard alphabet
|
||||||
|
for (int i = 0; i < b64.length(); i += LINE_LENGTH) {
|
||||||
|
wr.println(b64.substring(i, Math.min(i + LINE_LENGTH, b64.length())));
|
||||||
|
}
|
||||||
|
wr.println("-----END CERTIFICATE-----");
|
||||||
|
wr.flush();
|
||||||
|
return true;
|
||||||
|
} catch (CertificateEncodingException cee) {
|
||||||
|
error("Error writing X509 Certificate " + file.getAbsolutePath(), cee);
|
||||||
|
return false;
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
error("Error writing X509 Certificate " + file.getAbsolutePath(), ioe);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
try { if (os != null) os.close(); } catch (IOException foo) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void error(String msg, Throwable t) {
|
||||||
|
log(I2PAppContext.getGlobalContext(), Log.ERROR, msg, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void error(I2PAppContext ctx, String msg, Throwable t) {
|
||||||
|
log(ctx, Log.ERROR, msg, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void log(I2PAppContext ctx, int level, String msg, Throwable t) {
|
||||||
|
Log l = ctx.logManager().getLog(CertUtil.class);
|
||||||
|
l.log(level, msg, t);
|
||||||
|
}
|
||||||
|
}
|
399
core/java/src/net/i2p/crypto/KeyStoreUtil.java
Normal file
399
core/java/src/net/i2p/crypto/KeyStoreUtil.java
Normal file
@ -0,0 +1,399 @@
|
|||||||
|
package net.i2p.crypto;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.CertificateEncodingException;
|
||||||
|
import java.security.cert.CertificateExpiredException;
|
||||||
|
import java.security.cert.CertificateNotYetValidException;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.data.Base32;
|
||||||
|
import net.i2p.data.DataHelper;
|
||||||
|
import net.i2p.util.Log;
|
||||||
|
import net.i2p.util.SecureDirectory;
|
||||||
|
import net.i2p.util.SecureFileOutputStream;
|
||||||
|
import net.i2p.util.ShellCommand;
|
||||||
|
import net.i2p.util.SystemVersion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keystore utilities, consolidated from various places.
|
||||||
|
*
|
||||||
|
* @since 0.9.9
|
||||||
|
*/
|
||||||
|
public class KeyStoreUtil {
|
||||||
|
|
||||||
|
public static final String DEFAULT_KEYSTORE_PASSWORD = "changeit";
|
||||||
|
private static final String DEFAULT_KEY_ALGORITHM = "DSA";
|
||||||
|
private static final int DEFAULT_KEY_SIZE = 1024;
|
||||||
|
private static final int DEFAULT_KEY_VALID_DAYS = 3562;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new KeyStore object, and load it from ksFile if it is
|
||||||
|
* non-null and it exists.
|
||||||
|
* If ksFile is non-null and it does not exist, create a new empty
|
||||||
|
* keystore file.
|
||||||
|
*
|
||||||
|
* @param ksFile may be null
|
||||||
|
* @param password may be null
|
||||||
|
* @return success
|
||||||
|
*/
|
||||||
|
public static KeyStore createKeyStore(File ksFile, String password)
|
||||||
|
throws GeneralSecurityException, IOException {
|
||||||
|
boolean exists = ksFile != null && ksFile.exists();
|
||||||
|
char[] pwchars = password != null ? password.toCharArray() : null;
|
||||||
|
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||||
|
if (exists) {
|
||||||
|
InputStream fis = new FileInputStream(ksFile);
|
||||||
|
ks.load(fis, pwchars);
|
||||||
|
}
|
||||||
|
if (ksFile != null && !exists)
|
||||||
|
ks.store(new SecureFileOutputStream(ksFile), pwchars);
|
||||||
|
return ks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads certs from location of javax.net.ssl.keyStore property,
|
||||||
|
* else from $JAVA_HOME/lib/security/jssacacerts,
|
||||||
|
* else from $JAVA_HOME/lib/security/cacerts.
|
||||||
|
*
|
||||||
|
* @return null on catastrophic failure, returns empty KeyStore if can't load system file
|
||||||
|
* @since 0.8.2, moved from SSLEepGet.initSSLContext() in 0.9.9
|
||||||
|
*/
|
||||||
|
public static KeyStore loadSystemKeyStore() {
|
||||||
|
KeyStore ks;
|
||||||
|
try {
|
||||||
|
ks = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||||
|
} catch (GeneralSecurityException gse) {
|
||||||
|
error("Key Store init error", gse);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
boolean success = false;
|
||||||
|
String override = System.getProperty("javax.net.ssl.keyStore");
|
||||||
|
if (override != null)
|
||||||
|
success = loadCerts(new File(override), ks);
|
||||||
|
if (!success) {
|
||||||
|
if (SystemVersion.isAndroid()) {
|
||||||
|
// thru API 13. As of API 14 (ICS), the file is gone, but
|
||||||
|
// ks.load(null, pw) will bring in the default certs?
|
||||||
|
success = loadCerts(new File(System.getProperty("java.home"), "etc/security/cacerts.bks"), ks);
|
||||||
|
} else {
|
||||||
|
success = loadCerts(new File(System.getProperty("java.home"), "lib/security/jssecacerts"), ks);
|
||||||
|
if (!success)
|
||||||
|
success = loadCerts(new File(System.getProperty("java.home"), "lib/security/cacerts"), ks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
try {
|
||||||
|
// must be initted
|
||||||
|
ks.load(null, DEFAULT_KEYSTORE_PASSWORD.toCharArray());
|
||||||
|
} catch (Exception e) {}
|
||||||
|
error("All key store loads failed, will only load local certificates", null);
|
||||||
|
}
|
||||||
|
return ks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load all X509 Certs from a key store File into a KeyStore
|
||||||
|
* Note that each call reinitializes the KeyStore
|
||||||
|
*
|
||||||
|
* @return success
|
||||||
|
* @since 0.8.2, moved from SSLEepGet in 0.9.9
|
||||||
|
*/
|
||||||
|
private static boolean loadCerts(File file, KeyStore ks) {
|
||||||
|
if (!file.exists())
|
||||||
|
return false;
|
||||||
|
InputStream fis = null;
|
||||||
|
try {
|
||||||
|
fis = new FileInputStream(file);
|
||||||
|
// "changeit" is the default password
|
||||||
|
ks.load(fis, DEFAULT_KEYSTORE_PASSWORD.toCharArray());
|
||||||
|
info("Certs loaded from " + file);
|
||||||
|
} catch (GeneralSecurityException gse) {
|
||||||
|
error("KeyStore load error, no default keys: " + file.getAbsolutePath(), gse);
|
||||||
|
try {
|
||||||
|
// not clear if null is allowed for password
|
||||||
|
ks.load(null, DEFAULT_KEYSTORE_PASSWORD.toCharArray());
|
||||||
|
} catch (Exception foo) {}
|
||||||
|
return false;
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
error("KeyStore load error, no default keys: " + file.getAbsolutePath(), ioe);
|
||||||
|
try {
|
||||||
|
ks.load(null, DEFAULT_KEYSTORE_PASSWORD.toCharArray());
|
||||||
|
} catch (Exception foo) {}
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
try { if (fis != null) fis.close(); } catch (IOException foo) {}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count all X509 Certs in a key store
|
||||||
|
*
|
||||||
|
* @return number successfully added
|
||||||
|
* @since 0.8.2, moved from SSLEepGet in 0.9.9
|
||||||
|
*/
|
||||||
|
public static int countCerts(KeyStore ks) {
|
||||||
|
int count = 0;
|
||||||
|
try {
|
||||||
|
for(Enumeration<String> e = ks.aliases(); e.hasMoreElements();) {
|
||||||
|
String alias = e.nextElement();
|
||||||
|
if (ks.isCertificateEntry(alias)) {
|
||||||
|
info("Found cert " + alias);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception foo) {}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load all X509 Certs from a directory and add them to the
|
||||||
|
* trusted set of certificates in the key store
|
||||||
|
*
|
||||||
|
* @return number successfully added
|
||||||
|
* @since 0.8.2, moved from SSLEepGet in 0.9.9
|
||||||
|
*/
|
||||||
|
public static int addCerts(File dir, KeyStore ks) {
|
||||||
|
info("Looking for X509 Certificates in " + dir.getAbsolutePath());
|
||||||
|
int added = 0;
|
||||||
|
if (dir.exists() && dir.isDirectory()) {
|
||||||
|
File[] files = dir.listFiles();
|
||||||
|
if (files != null) {
|
||||||
|
for (int i = 0; i < files.length; i++) {
|
||||||
|
File f = files[i];
|
||||||
|
if (!f.isFile())
|
||||||
|
continue;
|
||||||
|
// use file name as alias
|
||||||
|
// https://www.sslshopper.com/ssl-converter.html
|
||||||
|
// No idea if all these formats can actually be read by CertificateFactory
|
||||||
|
String alias = f.getName().toLowerCase(Locale.US);
|
||||||
|
if (alias.endsWith(".crt") || alias.endsWith(".pem") || alias.endsWith(".key") ||
|
||||||
|
alias.endsWith(".der") || alias.endsWith(".key") || alias.endsWith(".p7b") ||
|
||||||
|
alias.endsWith(".p7c") || alias.endsWith(".pfx") || alias.endsWith(".p12"))
|
||||||
|
alias = alias.substring(0, alias.length() - 4);
|
||||||
|
boolean success = addCert(f, alias, ks);
|
||||||
|
if (success)
|
||||||
|
added++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return added;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load an X509 Cert from a file and add it to the
|
||||||
|
* trusted set of certificates in the key store
|
||||||
|
*
|
||||||
|
* @return success
|
||||||
|
* @since 0.8.2, moved from SSLEepGet in 0.9.9
|
||||||
|
*/
|
||||||
|
public static boolean addCert(File file, String alias, KeyStore ks) {
|
||||||
|
InputStream fis = null;
|
||||||
|
try {
|
||||||
|
fis = new FileInputStream(file);
|
||||||
|
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||||
|
X509Certificate cert = (X509Certificate)cf.generateCertificate(fis);
|
||||||
|
info("Read X509 Certificate from " + file.getAbsolutePath() +
|
||||||
|
" Issuer: " + cert.getIssuerX500Principal() +
|
||||||
|
"; Valid From: " + cert.getNotBefore() +
|
||||||
|
" To: " + cert.getNotAfter());
|
||||||
|
try {
|
||||||
|
cert.checkValidity();
|
||||||
|
} catch (CertificateExpiredException cee) {
|
||||||
|
error("Rejecting expired X509 Certificate: " + file.getAbsolutePath(), cee);
|
||||||
|
return false;
|
||||||
|
} catch (CertificateNotYetValidException cnyve) {
|
||||||
|
error("Rejecting X509 Certificate not yet valid: " + file.getAbsolutePath(), cnyve);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ks.setCertificateEntry(alias, cert);
|
||||||
|
info("Now trusting X509 Certificate, Issuer: " + cert.getIssuerX500Principal());
|
||||||
|
} catch (GeneralSecurityException gse) {
|
||||||
|
error("Error reading X509 Certificate: " + file.getAbsolutePath(), gse);
|
||||||
|
return false;
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
error("Error reading X509 Certificate: " + file.getAbsolutePath(), ioe);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
try { if (fis != null) fis.close(); } catch (IOException foo) {}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 48 char b32 string (30 bytes of entropy) */
|
||||||
|
public static String randomString() {
|
||||||
|
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||||
|
// make a random 48 character password (30 * 8 / 5)
|
||||||
|
byte[] rand = new byte[30];
|
||||||
|
ctx.random().nextBytes(rand);
|
||||||
|
return Base32.encode(rand);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a keypair and store it in the keystore at ks, creating it if necessary.
|
||||||
|
* Use default keystore password, valid days, algorithm, and key size.
|
||||||
|
*
|
||||||
|
* Warning, may take a long time.
|
||||||
|
*
|
||||||
|
* @param ks path to the keystore
|
||||||
|
* @param alias the name of the key
|
||||||
|
* @param cname e.g. randomstuff.console.i2p.net
|
||||||
|
* @param ou e.g. console
|
||||||
|
* @param keqPW the key password
|
||||||
|
*
|
||||||
|
* @return success
|
||||||
|
* @since 0.8.3, consolidated from RouterConsoleRUnner and SSLClientListenerRunner in 0.9.9
|
||||||
|
*/
|
||||||
|
public static boolean createKeys(File ks, String alias, String cname, String ou,
|
||||||
|
String keyPW) {
|
||||||
|
return createKeys(ks, DEFAULT_KEYSTORE_PASSWORD, alias, cname, ou,
|
||||||
|
DEFAULT_KEY_VALID_DAYS, DEFAULT_KEY_ALGORITHM, DEFAULT_KEY_SIZE, keyPW);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 keqPW the key password
|
||||||
|
*
|
||||||
|
* @return success
|
||||||
|
* @since 0.8.3, consolidated from RouterConsoleRUnner and SSLClientListenerRunner in 0.9.9
|
||||||
|
*/
|
||||||
|
public static boolean createKeys(File ks, String ksPW, String alias, String cname, String ou,
|
||||||
|
int validDays, String keyAlg, int keySize, String keyPW) {
|
||||||
|
if (!ks.exists()) {
|
||||||
|
File dir = ks.getParentFile();
|
||||||
|
if (!dir.exists()) {
|
||||||
|
File sdir = new SecureDirectory(dir.getAbsolutePath());
|
||||||
|
if (!sdir.mkdir()) {
|
||||||
|
error("Can't create directory " + dir, null);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String keytool = (new File(System.getProperty("java.home"), "bin/keytool")).getAbsolutePath();
|
||||||
|
String[] args = new String[] {
|
||||||
|
keytool,
|
||||||
|
"-genkey", // -genkeypair preferred in newer keytools, but this works with more
|
||||||
|
"-storetype", KeyStore.getDefaultType(),
|
||||||
|
"-keystore", ks.getAbsolutePath(),
|
||||||
|
"-storepass", ksPW,
|
||||||
|
"-alias", alias,
|
||||||
|
"-dname", "CN=" + cname + ",OU=" + ou + ",O=I2P Anonymous Network,L=XX,ST=XX,C=XX",
|
||||||
|
"-validity", Integer.toString(validDays), // 10 years
|
||||||
|
"-keyalg", keyAlg,
|
||||||
|
"-keysize", "1024",
|
||||||
|
"-keypass", keyPW
|
||||||
|
};
|
||||||
|
boolean success = (new ShellCommand()).executeSilentAndWaitTimed(args, 30); // 30 secs
|
||||||
|
if (success) {
|
||||||
|
success = ks.exists();
|
||||||
|
}
|
||||||
|
if (success) {
|
||||||
|
SecureFileOutputStream.setPerms(ks);
|
||||||
|
info("Created self-signed certificate for " + cname + " in keystore: " + ks.getAbsolutePath());
|
||||||
|
} else {
|
||||||
|
StringBuilder buf = new StringBuilder(256);
|
||||||
|
for (int i = 0; i < args.length; i++) {
|
||||||
|
buf.append('"').append(args[i]).append("\" ");
|
||||||
|
}
|
||||||
|
error("Failed to create SSL keystore using command line:" + buf, null);
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pull the cert back OUT of the keystore and save it as ascii
|
||||||
|
* so the clients can get to it.
|
||||||
|
*
|
||||||
|
* @param ks path to the keystore
|
||||||
|
* @param ksPW the keystore password, may be null
|
||||||
|
* @param alias the name of the key
|
||||||
|
* @param certFile output
|
||||||
|
* @return success
|
||||||
|
* @since 0.8.3 moved from SSLClientListenerRunner in 0.9.9
|
||||||
|
*/
|
||||||
|
public static boolean exportCert(File ks, String ksPW, String alias, File certFile) {
|
||||||
|
InputStream fis = null;
|
||||||
|
try {
|
||||||
|
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||||
|
fis = new FileInputStream(ks);
|
||||||
|
char[] pwchars = ksPW != null ? ksPW.toCharArray() : null;
|
||||||
|
keyStore.load(fis, pwchars);
|
||||||
|
Certificate cert = keyStore.getCertificate(alias);
|
||||||
|
if (cert != null)
|
||||||
|
return CertUtil.saveCert(cert, certFile);
|
||||||
|
} catch (GeneralSecurityException gse) {
|
||||||
|
error("Error saving ASCII SSL keys", gse);
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
error("Error saving ASCII SSL keys", ioe);
|
||||||
|
} finally {
|
||||||
|
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void info(String msg) {
|
||||||
|
log(I2PAppContext.getGlobalContext(), Log.INFO, msg, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void error(String msg, Throwable t) {
|
||||||
|
log(I2PAppContext.getGlobalContext(), Log.ERROR, msg, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void info(I2PAppContext ctx, String msg) {
|
||||||
|
log(ctx, Log.INFO, msg, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void error(I2PAppContext ctx, String msg, Throwable t) {
|
||||||
|
log(ctx, Log.ERROR, msg, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void log(I2PAppContext ctx, int level, String msg, Throwable t) {
|
||||||
|
Log l = ctx.logManager().getLog(KeyStoreUtil.class);
|
||||||
|
l.log(level, msg, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
try {
|
||||||
|
if (args.length > 0) {
|
||||||
|
File ksf = new File(args[0]);
|
||||||
|
createKeyStore(ksf, DEFAULT_KEYSTORE_PASSWORD);
|
||||||
|
System.out.println("Created empty keystore " + ksf);
|
||||||
|
} else {
|
||||||
|
KeyStore ks = loadSystemKeyStore();
|
||||||
|
if (ks != null) {
|
||||||
|
System.out.println("Loaded system keystore");
|
||||||
|
int count = countCerts(ks);
|
||||||
|
System.out.println("Found " + count + " certs");
|
||||||
|
} else {
|
||||||
|
System.out.println("FAIL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -46,18 +46,13 @@ import java.io.InputStream;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.PipedInputStream;
|
import java.io.PipedInputStream;
|
||||||
import java.io.PipedOutputStream;
|
import java.io.PipedOutputStream;
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.cert.Certificate;
|
import java.security.cert.Certificate;
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
import java.security.cert.CertificateEncodingException;
|
import java.security.cert.CertificateEncodingException;
|
||||||
import java.security.cert.CertificateExpiredException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.CertificateNotYetValidException;
|
|
||||||
import java.security.cert.CertificateFactory;
|
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
import javax.net.ssl.SSLHandshakeException;
|
import javax.net.ssl.SSLHandshakeException;
|
||||||
@ -67,7 +62,8 @@ import javax.net.ssl.TrustManagerFactory;
|
|||||||
import javax.net.ssl.X509TrustManager;
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
|
||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
import net.i2p.data.Base64;
|
import net.i2p.crypto.CertUtil;
|
||||||
|
import net.i2p.crypto.KeyStoreUtil;
|
||||||
import net.i2p.data.DataHelper;
|
import net.i2p.data.DataHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -91,8 +87,6 @@ public class SSLEepGet extends EepGet {
|
|||||||
/** may be null if init failed */
|
/** may be null if init failed */
|
||||||
private SavingTrustManager _stm;
|
private SavingTrustManager _stm;
|
||||||
|
|
||||||
private static final boolean _isAndroid = SystemVersion.isAndroid();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A new SSLEepGet with a new SSLState
|
* A new SSLEepGet with a new SSLState
|
||||||
*/
|
*/
|
||||||
@ -184,55 +178,24 @@ public class SSLEepGet extends EepGet {
|
|||||||
* @since 0.8.2
|
* @since 0.8.2
|
||||||
*/
|
*/
|
||||||
private SSLContext initSSLContext() {
|
private SSLContext initSSLContext() {
|
||||||
KeyStore ks;
|
KeyStore ks = KeyStoreUtil.loadSystemKeyStore();
|
||||||
try {
|
if (ks == null) {
|
||||||
ks = KeyStore.getInstance(KeyStore.getDefaultType());
|
_log.error("Key Store init error");
|
||||||
} catch (GeneralSecurityException gse) {
|
|
||||||
_log.error("Key Store init error", gse);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
boolean success = false;
|
if (_log.shouldLog(Log.INFO)) {
|
||||||
String override = System.getProperty("javax.net.ssl.keyStore");
|
int count = KeyStoreUtil.countCerts(ks);
|
||||||
if (override != null)
|
|
||||||
success = loadCerts(new File(override), ks);
|
|
||||||
if (!success) {
|
|
||||||
if (_isAndroid) {
|
|
||||||
// thru API 13. As of API 14 (ICS), the file is gone, but
|
|
||||||
// ks.load(null, pw) will bring in the default certs?
|
|
||||||
success = loadCerts(new File(System.getProperty("java.home"), "etc/security/cacerts.bks"), ks);
|
|
||||||
} else {
|
|
||||||
success = loadCerts(new File(System.getProperty("java.home"), "lib/security/jssecacerts"), ks);
|
|
||||||
if (!success)
|
|
||||||
success = loadCerts(new File(System.getProperty("java.home"), "lib/security/cacerts"), ks);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
try {
|
|
||||||
// must be initted
|
|
||||||
ks.load(null, "changeit".toCharArray());
|
|
||||||
} catch (Exception e) {}
|
|
||||||
_log.error("All key store loads failed, will only load local certificates");
|
|
||||||
} else if (_log.shouldLog(Log.INFO)) {
|
|
||||||
int count = 0;
|
|
||||||
try {
|
|
||||||
for(Enumeration<String> e = ks.aliases(); e.hasMoreElements();) {
|
|
||||||
String alias = e.nextElement();
|
|
||||||
if (ks.isCertificateEntry(alias))
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
} catch (Exception foo) {}
|
|
||||||
_log.info("Loaded " + count + " default trusted certificates");
|
_log.info("Loaded " + count + " default trusted certificates");
|
||||||
}
|
}
|
||||||
|
|
||||||
File dir = new File(_context.getBaseDir(), "certificates");
|
File dir = new File(_context.getBaseDir(), "certificates");
|
||||||
int adds = addCerts(dir, ks);
|
int adds = KeyStoreUtil.addCerts(dir, ks);
|
||||||
int totalAdds = adds;
|
int totalAdds = adds;
|
||||||
if (adds > 0 && _log.shouldLog(Log.INFO))
|
if (adds > 0 && _log.shouldLog(Log.INFO))
|
||||||
_log.info("Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath());
|
_log.info("Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath());
|
||||||
if (!_context.getBaseDir().getAbsolutePath().equals(_context.getConfigDir().getAbsolutePath())) {
|
if (!_context.getBaseDir().getAbsolutePath().equals(_context.getConfigDir().getAbsolutePath())) {
|
||||||
dir = new File(_context.getConfigDir(), "certificates");
|
dir = new File(_context.getConfigDir(), "certificates");
|
||||||
adds = addCerts(dir, ks);
|
adds = KeyStoreUtil.addCerts(dir, ks);
|
||||||
totalAdds += adds;
|
totalAdds += adds;
|
||||||
if (adds > 0 && _log.shouldLog(Log.INFO))
|
if (adds > 0 && _log.shouldLog(Log.INFO))
|
||||||
_log.info("Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath());
|
_log.info("Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath());
|
||||||
@ -240,7 +203,7 @@ public class SSLEepGet extends EepGet {
|
|||||||
dir = new File(System.getProperty("user.dir"));
|
dir = new File(System.getProperty("user.dir"));
|
||||||
if (!_context.getBaseDir().getAbsolutePath().equals(dir.getAbsolutePath())) {
|
if (!_context.getBaseDir().getAbsolutePath().equals(dir.getAbsolutePath())) {
|
||||||
dir = new File(_context.getConfigDir(), "certificates");
|
dir = new File(_context.getConfigDir(), "certificates");
|
||||||
adds = addCerts(dir, ks);
|
adds = KeyStoreUtil.addCerts(dir, ks);
|
||||||
totalAdds += adds;
|
totalAdds += adds;
|
||||||
if (adds > 0 && _log.shouldLog(Log.INFO))
|
if (adds > 0 && _log.shouldLog(Log.INFO))
|
||||||
_log.info("Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath());
|
_log.info("Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath());
|
||||||
@ -261,118 +224,6 @@ public class SSLEepGet extends EepGet {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Load all X509 Certs from a key store File into a KeyStore
|
|
||||||
* Note that each call reinitializes the KeyStore
|
|
||||||
*
|
|
||||||
* @return success
|
|
||||||
* @since 0.8.2
|
|
||||||
*/
|
|
||||||
private boolean loadCerts(File file, KeyStore ks) {
|
|
||||||
if (!file.exists())
|
|
||||||
return false;
|
|
||||||
InputStream fis = null;
|
|
||||||
try {
|
|
||||||
fis = new FileInputStream(file);
|
|
||||||
// "changeit" is the default password
|
|
||||||
ks.load(fis, "changeit".toCharArray());
|
|
||||||
} catch (GeneralSecurityException gse) {
|
|
||||||
_log.error("KeyStore load error, no default keys: " + file.getAbsolutePath(), gse);
|
|
||||||
try {
|
|
||||||
// not clear if null is allowed for password
|
|
||||||
ks.load(null, "changeit".toCharArray());
|
|
||||||
} catch (Exception foo) {}
|
|
||||||
return false;
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
_log.error("KeyStore load error, no default keys: " + file.getAbsolutePath(), ioe);
|
|
||||||
try {
|
|
||||||
ks.load(null, "changeit".toCharArray());
|
|
||||||
} catch (Exception foo) {}
|
|
||||||
return false;
|
|
||||||
} finally {
|
|
||||||
try { if (fis != null) fis.close(); } catch (IOException foo) {}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load all X509 Certs from a directory and add them to the
|
|
||||||
* trusted set of certificates in the key store
|
|
||||||
*
|
|
||||||
* @return number successfully added
|
|
||||||
* @since 0.8.2
|
|
||||||
*/
|
|
||||||
private int addCerts(File dir, KeyStore ks) {
|
|
||||||
if (_log.shouldLog(Log.INFO))
|
|
||||||
_log.info("Looking for X509 Certificates in " + dir.getAbsolutePath());
|
|
||||||
int added = 0;
|
|
||||||
if (dir.exists() && dir.isDirectory()) {
|
|
||||||
File[] files = dir.listFiles();
|
|
||||||
if (files != null) {
|
|
||||||
for (int i = 0; i < files.length; i++) {
|
|
||||||
File f = files[i];
|
|
||||||
if (!f.isFile())
|
|
||||||
continue;
|
|
||||||
// use file name as alias
|
|
||||||
// https://www.sslshopper.com/ssl-converter.html
|
|
||||||
// No idea if all these formats can actually be read by CertificateFactory
|
|
||||||
String alias = f.getName().toLowerCase(Locale.US);
|
|
||||||
if (alias.endsWith(".crt") || alias.endsWith(".pem") || alias.endsWith(".key") ||
|
|
||||||
alias.endsWith(".der") || alias.endsWith(".key") || alias.endsWith(".p7b") ||
|
|
||||||
alias.endsWith(".p7c") || alias.endsWith(".pfx") || alias.endsWith(".p12"))
|
|
||||||
alias = alias.substring(0, alias.length() - 4);
|
|
||||||
boolean success = addCert(f, alias, ks);
|
|
||||||
if (success)
|
|
||||||
added++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return added;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load an X509 Cert from a file and add it to the
|
|
||||||
* trusted set of certificates in the key store
|
|
||||||
*
|
|
||||||
* @return success
|
|
||||||
* @since 0.8.2
|
|
||||||
*/
|
|
||||||
private boolean addCert(File file, String alias, KeyStore ks) {
|
|
||||||
InputStream fis = null;
|
|
||||||
try {
|
|
||||||
fis = new FileInputStream(file);
|
|
||||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
|
||||||
X509Certificate cert = (X509Certificate)cf.generateCertificate(fis);
|
|
||||||
if (_log.shouldLog(Log.INFO)) {
|
|
||||||
_log.info("Read X509 Certificate from " + file.getAbsolutePath() +
|
|
||||||
" Issuer: " + cert.getIssuerX500Principal() +
|
|
||||||
"; Valid From: " + cert.getNotBefore() +
|
|
||||||
" To: " + cert.getNotAfter());
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
cert.checkValidity();
|
|
||||||
} catch (CertificateExpiredException cee) {
|
|
||||||
_log.error("Rejecting expired X509 Certificate: " + file.getAbsolutePath(), cee);
|
|
||||||
return false;
|
|
||||||
} catch (CertificateNotYetValidException cnyve) {
|
|
||||||
_log.error("Rejecting X509 Certificate not yet valid: " + file.getAbsolutePath(), cnyve);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
ks.setCertificateEntry(alias, cert);
|
|
||||||
if (_log.shouldLog(Log.INFO))
|
|
||||||
_log.info("Now trusting X509 Certificate, Issuer: " + cert.getIssuerX500Principal());
|
|
||||||
} catch (GeneralSecurityException gse) {
|
|
||||||
_log.error("Error reading X509 Certificate: " + file.getAbsolutePath(), gse);
|
|
||||||
return false;
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
_log.error("Error reading X509 Certificate: " + file.getAbsolutePath(), ioe);
|
|
||||||
return false;
|
|
||||||
} finally {
|
|
||||||
try { if (fis != null) fis.close(); } catch (IOException foo) {}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* From http://blogs.sun.com/andreas/resource/InstallCert.java
|
* From http://blogs.sun.com/andreas/resource/InstallCert.java
|
||||||
@ -425,44 +276,12 @@ public class SSLEepGet extends EepGet {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println(" WARNING: Certificate is not currently valid, it cannot be used");
|
System.out.println(" WARNING: Certificate is not currently valid, it cannot be used");
|
||||||
}
|
}
|
||||||
saveCert(cert, new File(name));
|
CertUtil.saveCert(cert, new File(name));
|
||||||
}
|
}
|
||||||
System.out.println("NOTE: To trust them, copy the certificate file(s) to the certificates directory and rerun without the -s option");
|
System.out.println("NOTE: To trust them, copy the certificate file(s) to the certificates directory and rerun without the -s option");
|
||||||
System.out.println("NOTE: EepGet failed, certificate error follows:");
|
System.out.println("NOTE: EepGet failed, certificate error follows:");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final int LINE_LENGTH = 64;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modified from:
|
|
||||||
* http://www.exampledepot.com/egs/java.security.cert/ExportCert.html
|
|
||||||
*
|
|
||||||
* This method writes a certificate to a file in base64 format.
|
|
||||||
* @since 0.8.2
|
|
||||||
*/
|
|
||||||
private static void saveCert(Certificate cert, File file) {
|
|
||||||
OutputStream os = null;
|
|
||||||
try {
|
|
||||||
// Get the encoded form which is suitable for exporting
|
|
||||||
byte[] buf = cert.getEncoded();
|
|
||||||
os = new FileOutputStream(file);
|
|
||||||
PrintWriter wr = new PrintWriter(os);
|
|
||||||
wr.println("-----BEGIN CERTIFICATE-----");
|
|
||||||
String b64 = Base64.encode(buf, true); // true = use standard alphabet
|
|
||||||
for (int i = 0; i < b64.length(); i += LINE_LENGTH) {
|
|
||||||
wr.println(b64.substring(i, Math.min(i + LINE_LENGTH, b64.length())));
|
|
||||||
}
|
|
||||||
wr.println("-----END CERTIFICATE-----");
|
|
||||||
wr.flush();
|
|
||||||
} catch (CertificateEncodingException cee) {
|
|
||||||
System.out.println("Error writing X509 Certificate " + file.getAbsolutePath() + ' ' + cee);
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
System.out.println("Error writing X509 Certificate " + file.getAbsolutePath() + ' ' + ioe);
|
|
||||||
} finally {
|
|
||||||
try { if (os != null) os.close(); } catch (IOException foo) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An opaque class for the caller to pass to repeated instantiations of SSLEepGet.
|
* An opaque class for the caller to pass to repeated instantiations of SSLEepGet.
|
||||||
* @since 0.8.2
|
* @since 0.8.2
|
||||||
|
@ -21,13 +21,12 @@ import javax.net.ssl.SSLServerSocketFactory;
|
|||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
|
|
||||||
import net.i2p.client.I2PClient;
|
import net.i2p.client.I2PClient;
|
||||||
|
import net.i2p.crypto.CertUtil;
|
||||||
|
import net.i2p.crypto.KeyStoreUtil;
|
||||||
import net.i2p.data.Base32;
|
import net.i2p.data.Base32;
|
||||||
import net.i2p.data.Base64;
|
|
||||||
import net.i2p.router.RouterContext;
|
import net.i2p.router.RouterContext;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
import net.i2p.util.SecureDirectory;
|
import net.i2p.util.SecureDirectory;
|
||||||
import net.i2p.util.SecureFileOutputStream;
|
|
||||||
import net.i2p.util.ShellCommand;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SSL version of ClientListenerRunner
|
* SSL version of ClientListenerRunner
|
||||||
@ -87,31 +86,14 @@ class SSLClientListenerRunner extends ClientListenerRunner {
|
|||||||
*/
|
*/
|
||||||
private boolean createKeyStore(File ks) {
|
private boolean createKeyStore(File ks) {
|
||||||
// make a random 48 character password (30 * 8 / 5)
|
// make a random 48 character password (30 * 8 / 5)
|
||||||
byte[] rand = new byte[30];
|
String keyPassword = KeyStoreUtil.randomString();
|
||||||
_context.random().nextBytes(rand);
|
|
||||||
String keyPassword = Base32.encode(rand);
|
|
||||||
// and one for the cname
|
// and one for the cname
|
||||||
_context.random().nextBytes(rand);
|
String cname = KeyStoreUtil.randomString() + ".i2cp.i2p.net";
|
||||||
String cname = Base32.encode(rand) + ".i2cp.i2p.net";
|
|
||||||
|
|
||||||
String keytool = (new File(System.getProperty("java.home"), "bin/keytool")).getAbsolutePath();
|
boolean success = KeyStoreUtil.createKeys(ks, KEY_ALIAS, cname, "I2CP", keyPassword);
|
||||||
String[] args = new String[] {
|
|
||||||
keytool,
|
|
||||||
"-genkey", // -genkeypair preferred in newer keytools, but this works with more
|
|
||||||
"-storetype", KeyStore.getDefaultType(),
|
|
||||||
"-keystore", ks.getAbsolutePath(),
|
|
||||||
"-storepass", DEFAULT_KEYSTORE_PASSWORD,
|
|
||||||
"-alias", KEY_ALIAS,
|
|
||||||
"-dname", "CN=" + cname + ",OU=I2CP,O=I2P Anonymous Network,L=XX,ST=XX,C=XX",
|
|
||||||
"-validity", "3652", // 10 years
|
|
||||||
"-keyalg", "DSA",
|
|
||||||
"-keysize", "1024",
|
|
||||||
"-keypass", keyPassword};
|
|
||||||
boolean success = (new ShellCommand()).executeSilentAndWaitTimed(args, 30); // 30 secs
|
|
||||||
if (success) {
|
if (success) {
|
||||||
success = ks.exists();
|
success = ks.exists();
|
||||||
if (success) {
|
if (success) {
|
||||||
SecureFileOutputStream.setPerms(ks);
|
|
||||||
Map<String, String> changes = new HashMap();
|
Map<String, String> changes = new HashMap();
|
||||||
changes.put(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD);
|
changes.put(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD);
|
||||||
changes.put(PROP_KEY_PASSWORD, keyPassword);
|
changes.put(PROP_KEY_PASSWORD, keyPassword);
|
||||||
@ -123,13 +105,8 @@ class SSLClientListenerRunner extends ClientListenerRunner {
|
|||||||
"The certificate name was generated randomly, and is not associated with your " +
|
"The certificate name was generated randomly, and is not associated with your " +
|
||||||
"IP address, host name, router identity, or destination keys.");
|
"IP address, host name, router identity, or destination keys.");
|
||||||
} else {
|
} else {
|
||||||
_log.error("Failed to create I2CP SSL keystore using command line:");
|
_log.error("Failed to create I2CP SSL keystore.\n" +
|
||||||
StringBuilder buf = new StringBuilder(256);
|
"This is for the Sun/Oracle keytool, others may be incompatible.\n" +
|
||||||
for (int i = 0; i < args.length; i++) {
|
|
||||||
buf.append('"').append(args[i]).append("\" ");
|
|
||||||
}
|
|
||||||
_log.error(buf.toString());
|
|
||||||
_log.error("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 +
|
"If you create the keystore manually, you must add " + PROP_KEYSTORE_PASSWORD + " and " + PROP_KEY_PASSWORD +
|
||||||
" to " + (new File(_context.getConfigDir(), "router.config")).getAbsolutePath());
|
" to " + (new File(_context.getConfigDir(), "router.config")).getAbsolutePath());
|
||||||
}
|
}
|
||||||
@ -143,62 +120,16 @@ class SSLClientListenerRunner extends ClientListenerRunner {
|
|||||||
private void exportCert(File ks) {
|
private void exportCert(File ks) {
|
||||||
File sdir = new SecureDirectory(_context.getConfigDir(), "certificates");
|
File sdir = new SecureDirectory(_context.getConfigDir(), "certificates");
|
||||||
if (sdir.exists() || sdir.mkdir()) {
|
if (sdir.exists() || sdir.mkdir()) {
|
||||||
InputStream fis = null;
|
String ksPass = _context.getProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD);
|
||||||
try {
|
File out = new File(sdir, ASCII_KEYFILE);
|
||||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
boolean success = KeyStoreUtil.exportCert(ks, ksPass, KEY_ALIAS, out);
|
||||||
fis = new FileInputStream(ks);
|
if (!success)
|
||||||
String ksPass = _context.getProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD);
|
_log.error("Error getting SSL cert to save as ASCII");
|
||||||
keyStore.load(fis, ksPass.toCharArray());
|
|
||||||
Certificate cert = keyStore.getCertificate(KEY_ALIAS);
|
|
||||||
if (cert != null) {
|
|
||||||
File certFile = new File(sdir, ASCII_KEYFILE);
|
|
||||||
saveCert(cert, certFile);
|
|
||||||
} else {
|
|
||||||
_log.error("Error getting SSL cert to save as ASCII");
|
|
||||||
}
|
|
||||||
} catch (GeneralSecurityException gse) {
|
|
||||||
_log.error("Error saving ASCII SSL keys", gse);
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
_log.error("Error saving ASCII SSL keys", ioe);
|
|
||||||
} finally {
|
|
||||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
_log.error("Error saving ASCII SSL keys");
|
_log.error("Error saving ASCII SSL keys");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final int LINE_LENGTH = 64;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modified from:
|
|
||||||
* http://www.exampledepot.com/egs/java.security.cert/ExportCert.html
|
|
||||||
*
|
|
||||||
* Write a certificate to a file in base64 format.
|
|
||||||
*/
|
|
||||||
private void saveCert(Certificate cert, File file) {
|
|
||||||
OutputStream os = null;
|
|
||||||
try {
|
|
||||||
// Get the encoded form which is suitable for exporting
|
|
||||||
byte[] buf = cert.getEncoded();
|
|
||||||
os = new SecureFileOutputStream(file);
|
|
||||||
PrintWriter wr = new PrintWriter(os);
|
|
||||||
wr.println("-----BEGIN CERTIFICATE-----");
|
|
||||||
String b64 = Base64.encode(buf, true); // true = use standard alphabet
|
|
||||||
for (int i = 0; i < b64.length(); i += LINE_LENGTH) {
|
|
||||||
wr.println(b64.substring(i, Math.min(i + LINE_LENGTH, b64.length())));
|
|
||||||
}
|
|
||||||
wr.println("-----END CERTIFICATE-----");
|
|
||||||
wr.flush();
|
|
||||||
} catch (CertificateEncodingException cee) {
|
|
||||||
_log.error("Error writing X509 Certificate " + file.getAbsolutePath(), cee);
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
_log.error("Error writing X509 Certificate " + file.getAbsolutePath(), ioe);
|
|
||||||
} finally {
|
|
||||||
try { if (os != null) os.close(); } catch (IOException foo) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up the SSLContext and sets the socket factory.
|
* Sets up the SSLContext and sets the socket factory.
|
||||||
* @return success
|
* @return success
|
||||||
|
Reference in New Issue
Block a user