forked from I2P_Developers/i2p.i2p
Set alt names for console cert Use utils to validate console IP addresses
This commit is contained in:
@ -42,6 +42,8 @@ import net.i2p.util.SecureDirectory;
|
|||||||
import net.i2p.util.I2PSSLSocketFactory;
|
import net.i2p.util.I2PSSLSocketFactory;
|
||||||
import net.i2p.util.SystemVersion;
|
import net.i2p.util.SystemVersion;
|
||||||
|
|
||||||
|
import org.apache.http.conn.util.InetAddressUtils;
|
||||||
|
|
||||||
import org.eclipse.jetty.security.HashLoginService;
|
import org.eclipse.jetty.security.HashLoginService;
|
||||||
import org.eclipse.jetty.security.ConstraintMapping;
|
import org.eclipse.jetty.security.ConstraintMapping;
|
||||||
import org.eclipse.jetty.security.ConstraintSecurityHandler;
|
import org.eclipse.jetty.security.ConstraintSecurityHandler;
|
||||||
@ -516,17 +518,15 @@ public class RouterConsoleRunner implements RouterApp {
|
|||||||
try {
|
try {
|
||||||
// Test before we add the connector, because Jetty 6 won't start if any of the
|
// Test before we add the connector, because Jetty 6 won't start if any of the
|
||||||
// connectors are bad
|
// connectors are bad
|
||||||
InetAddress test = InetAddress.getByName(host);
|
if ((!hasIPV6) && InetAddressUtils.isIPv6Address(host))
|
||||||
if ((!hasIPV6) && (!(test instanceof Inet4Address)))
|
|
||||||
throw new IOException("IPv6 addresses unsupported");
|
throw new IOException("IPv6 addresses unsupported");
|
||||||
if ((!hasIPV4) && (test instanceof Inet4Address))
|
if ((!hasIPV4) && InetAddressUtils.isIPv4Address(host))
|
||||||
throw new IOException("IPv4 addresses unsupported");
|
throw new IOException("IPv4 addresses unsupported");
|
||||||
ServerSocket testSock = null;
|
ServerSocket testSock = null;
|
||||||
try {
|
try {
|
||||||
// On Windows, this was passing and Jetty was still failing,
|
// On Windows, this was passing and Jetty was still failing,
|
||||||
// possibly due to %scope_id ???
|
// possibly due to %scope_id ???
|
||||||
// https://issues.apache.org/jira/browse/ZOOKEEPER-667
|
// https://issues.apache.org/jira/browse/ZOOKEEPER-667
|
||||||
//testSock = new ServerSocket(0, 0, test);
|
|
||||||
// so do exactly what Jetty does in SelectChannelConnector.open()
|
// so do exactly what Jetty does in SelectChannelConnector.open()
|
||||||
testSock = new ServerSocket();
|
testSock = new ServerSocket();
|
||||||
InetSocketAddress isa = new InetSocketAddress(host, 0);
|
InetSocketAddress isa = new InetSocketAddress(host, 0);
|
||||||
@ -574,7 +574,23 @@ public class RouterConsoleRunner implements RouterApp {
|
|||||||
}
|
}
|
||||||
if (sslPort > 0) {
|
if (sslPort > 0) {
|
||||||
File keyStore = new File(_context.getConfigDir(), "keystore/console.ks");
|
File keyStore = new File(_context.getConfigDir(), "keystore/console.ks");
|
||||||
if (verifyKeyStore(keyStore)) {
|
// Put the list of hosts together early, so we can put it in the selfsigned cert.
|
||||||
|
StringTokenizer tok = new StringTokenizer(_sslListenHost, " ,");
|
||||||
|
Set<String> altNames = new HashSet<String>(4);
|
||||||
|
while (tok.hasMoreTokens()) {
|
||||||
|
String s = tok.nextToken().trim();
|
||||||
|
if (!s.equals("0.0.0.0") && !s.equals("::") &&
|
||||||
|
!s.equals("0:0:0:0:0:0:0:0"))
|
||||||
|
altNames.add(s);
|
||||||
|
}
|
||||||
|
String allowed = _context.getProperty(PROP_ALLOWED_HOSTS);
|
||||||
|
if (allowed != null) {
|
||||||
|
tok = new StringTokenizer(allowed, " ,");
|
||||||
|
while (tok.hasMoreTokens()) {
|
||||||
|
altNames.add(tok.nextToken().trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (verifyKeyStore(keyStore, altNames)) {
|
||||||
// the keystore path and password
|
// the keystore path and password
|
||||||
SslContextFactory sslFactory = new SslContextFactory(keyStore.getAbsolutePath());
|
SslContextFactory sslFactory = new SslContextFactory(keyStore.getAbsolutePath());
|
||||||
sslFactory.setKeyStorePassword(_context.getProperty(PROP_KEYSTORE_PASSWORD, KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD));
|
sslFactory.setKeyStorePassword(_context.getProperty(PROP_KEYSTORE_PASSWORD, KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD));
|
||||||
@ -585,22 +601,20 @@ public class RouterConsoleRunner implements RouterApp {
|
|||||||
sslFactory.addExcludeCipherSuites(I2PSSLSocketFactory.EXCLUDE_CIPHERS.toArray(
|
sslFactory.addExcludeCipherSuites(I2PSSLSocketFactory.EXCLUDE_CIPHERS.toArray(
|
||||||
new String[I2PSSLSocketFactory.EXCLUDE_CIPHERS.size()]));
|
new String[I2PSSLSocketFactory.EXCLUDE_CIPHERS.size()]));
|
||||||
List<String> hosts = new ArrayList<String>(2);
|
List<String> hosts = new ArrayList<String>(2);
|
||||||
StringTokenizer tok = new StringTokenizer(_sslListenHost, " ,");
|
tok = new StringTokenizer(_sslListenHost, " ,");
|
||||||
while (tok.hasMoreTokens()) {
|
while (tok.hasMoreTokens()) {
|
||||||
String host = tok.nextToken().trim();
|
String host = tok.nextToken().trim();
|
||||||
// doing it this way means we don't have to escape an IPv6 host with []
|
// doing it this way means we don't have to escape an IPv6 host with []
|
||||||
try {
|
try {
|
||||||
// Test before we add the connector, because Jetty 6 won't start if any of the
|
// Test before we add the connector, because Jetty 6 won't start if any of the
|
||||||
// connectors are bad
|
// connectors are bad
|
||||||
InetAddress test = InetAddress.getByName(host);
|
if ((!hasIPV6) && InetAddressUtils.isIPv6Address(host))
|
||||||
if ((!hasIPV6) && (!(test instanceof Inet4Address)))
|
|
||||||
throw new IOException("IPv6 addresses unsupported");
|
throw new IOException("IPv6 addresses unsupported");
|
||||||
if ((!hasIPV4) && (test instanceof Inet4Address))
|
if ((!hasIPV4) && InetAddressUtils.isIPv4Address(host))
|
||||||
throw new IOException("IPv4 addresses unsupported");
|
throw new IOException("IPv4 addresses unsupported");
|
||||||
ServerSocket testSock = null;
|
ServerSocket testSock = null;
|
||||||
try {
|
try {
|
||||||
// see comments above
|
// see comments above
|
||||||
//testSock = new ServerSocket(0, 0, test);
|
|
||||||
testSock = new ServerSocket();
|
testSock = new ServerSocket();
|
||||||
InetSocketAddress isa = new InetSocketAddress(host, 0);
|
InetSocketAddress isa = new InetSocketAddress(host, 0);
|
||||||
testSock.bind(isa);
|
testSock.bind(isa);
|
||||||
@ -839,30 +853,28 @@ public class RouterConsoleRunner implements RouterApp {
|
|||||||
* @return success if it exists and we have a password, or it was created successfully.
|
* @return success if it exists and we have a password, or it was created successfully.
|
||||||
* @since 0.8.3
|
* @since 0.8.3
|
||||||
*/
|
*/
|
||||||
private boolean verifyKeyStore(File ks) {
|
private boolean verifyKeyStore(File ks, Set<String> altNames) {
|
||||||
if (ks.exists()) {
|
if (ks.exists()) {
|
||||||
boolean rv = _context.getProperty(PROP_KEY_PASSWORD) != null;
|
boolean rv = _context.getProperty(PROP_KEY_PASSWORD) != null;
|
||||||
if (!rv)
|
if (!rv)
|
||||||
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;
|
||||||
}
|
}
|
||||||
return createKeyStore(ks);
|
return createKeyStore(ks, altNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call out to keytool to create a new keystore with a keypair in it.
|
* Create a new keystore with a keypair in it.
|
||||||
* Trying to do this programatically is a nightmare, requiring either BouncyCastle
|
|
||||||
* libs or using proprietary Sun libs, and it's a huge mess.
|
|
||||||
*
|
*
|
||||||
* @return success
|
* @return success
|
||||||
* @since 0.8.3
|
* @since 0.8.3
|
||||||
*/
|
*/
|
||||||
private boolean createKeyStore(File ks) {
|
private boolean createKeyStore(File ks, Set<String> altNames) {
|
||||||
// make a random 48 character password (30 * 8 / 5)
|
// make a random 48 character password (30 * 8 / 5)
|
||||||
String keyPassword = KeyStoreUtil.randomString();
|
String keyPassword = KeyStoreUtil.randomString();
|
||||||
String cname = "localhost";
|
String cname = "localhost";
|
||||||
boolean success = KeyStoreUtil.createKeys(ks, "console", cname, "Console", keyPassword);
|
boolean success = KeyStoreUtil.createKeys(ks, "console", cname, altNames, "Console", keyPassword);
|
||||||
if (success) {
|
if (success) {
|
||||||
success = ks.exists();
|
success = ks.exists();
|
||||||
if (success) {
|
if (success) {
|
||||||
@ -883,7 +895,8 @@ public class RouterConsoleRunner implements RouterApp {
|
|||||||
}
|
}
|
||||||
if (success) {
|
if (success) {
|
||||||
System.err.println("Created self-signed certificate for " + cname + " in keystore: " + ks.getAbsolutePath() + "\n" +
|
System.err.println("Created self-signed certificate for " + cname + " in keystore: " + ks.getAbsolutePath() + "\n" +
|
||||||
"The certificate was generated randomly, and is not associated with your " +
|
"The certificate was generated randomly.\n" +
|
||||||
|
"Unless you have changed the default settings, the certificate 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.\n" +
|
System.err.println("Failed to create console SSL keystore.\n" +
|
||||||
|
@ -467,7 +467,30 @@ public final class KeyStoreUtil {
|
|||||||
*/
|
*/
|
||||||
public static boolean createKeys(File ks, String alias, String cname, String ou,
|
public static boolean createKeys(File ks, String alias, String cname, String ou,
|
||||||
String keyPW) {
|
String keyPW) {
|
||||||
return createKeys(ks, DEFAULT_KEYSTORE_PASSWORD, alias, cname, ou,
|
return createKeys(ks, DEFAULT_KEYSTORE_PASSWORD, alias, cname, null, 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.
|
||||||
|
* 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. localhost. Must be a hostname or email address. IP addresses will not be correctly encoded.
|
||||||
|
* @param altNames the Subject Alternative Names. May be null. May contain hostnames and/or IP addresses.
|
||||||
|
* cname, localhost, 127.0.0.1, and ::1 will be automatically added.
|
||||||
|
* @param ou e.g. console
|
||||||
|
* @param keyPW the key password, must be at least 6 characters
|
||||||
|
*
|
||||||
|
* @return success
|
||||||
|
* @since 0.9.34 added altNames param
|
||||||
|
*/
|
||||||
|
public static boolean createKeys(File ks, String alias, String cname, Set<String> altNames, String ou,
|
||||||
|
String keyPW) {
|
||||||
|
return createKeys(ks, DEFAULT_KEYSTORE_PASSWORD, alias, cname, altNames, ou,
|
||||||
DEFAULT_KEY_VALID_DAYS, DEFAULT_KEY_ALGORITHM, DEFAULT_KEY_SIZE, keyPW);
|
DEFAULT_KEY_VALID_DAYS, DEFAULT_KEY_ALGORITHM, DEFAULT_KEY_SIZE, keyPW);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -494,12 +517,42 @@ public final class KeyStoreUtil {
|
|||||||
*/
|
*/
|
||||||
public static boolean createKeys(File ks, String ksPW, String alias, String cname, String ou,
|
public static boolean createKeys(File ks, String ksPW, String alias, String cname, String ou,
|
||||||
int validDays, String keyAlg, int keySize, String keyPW) {
|
int validDays, String keyAlg, int keySize, String keyPW) {
|
||||||
|
return createKeys(ks, ksPW, alias, cname, null, ou, validDays, keyAlg, keySize, keyPW);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a keypair and store it in the keystore at ks, creating it if necessary.
|
||||||
|
*
|
||||||
|
* For new code, the createKeysAndCRL() 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
|
||||||
|
* @param ksPW the keystore password
|
||||||
|
* @param alias the name of the key
|
||||||
|
* @param cname e.g. localhost. Must be a hostname or email address. IP addresses will not be correctly encoded.
|
||||||
|
* @param altNames the Subject Alternative Names. May be null. May contain hostnames and/or IP addresses.
|
||||||
|
* cname, localhost, 127.0.0.1, and ::1 will be automatically added.
|
||||||
|
* @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.9.34 added altNames param
|
||||||
|
*/
|
||||||
|
public static boolean createKeys(File ks, String ksPW, String alias, String cname, Set<String> altNames, String ou,
|
||||||
|
int validDays, String keyAlg, int keySize, String keyPW) {
|
||||||
boolean useKeytool = I2PAppContext.getGlobalContext().getBooleanProperty("crypto.useExternalKeytool");
|
boolean useKeytool = I2PAppContext.getGlobalContext().getBooleanProperty("crypto.useExternalKeytool");
|
||||||
if (useKeytool) {
|
if (useKeytool) {
|
||||||
|
if (altNames != null)
|
||||||
|
throw new IllegalArgumentException("can't do SAN in keytool");
|
||||||
return createKeysCLI(ks, ksPW, alias, cname, ou, validDays, keyAlg, keySize, keyPW);
|
return createKeysCLI(ks, ksPW, alias, cname, ou, validDays, keyAlg, keySize, keyPW);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
createKeysAndCRL(ks, ksPW, alias, cname, ou, validDays, keyAlg, keySize, keyPW);
|
createKeysAndCRL(ks, ksPW, alias, cname, altNames, ou, validDays, keyAlg, keySize, keyPW);
|
||||||
return true;
|
return true;
|
||||||
} catch (GeneralSecurityException gse) {
|
} catch (GeneralSecurityException gse) {
|
||||||
error("Create keys error", gse);
|
error("Create keys error", gse);
|
||||||
@ -546,6 +599,46 @@ public final class KeyStoreUtil {
|
|||||||
public static Object[] createKeysAndCRL(File ks, String ksPW, String alias, String cname, String ou,
|
public static Object[] createKeysAndCRL(File ks, String ksPW, String alias, String cname, String ou,
|
||||||
int validDays, String keyAlg, int keySize, String keyPW)
|
int validDays, String keyAlg, int keySize, String keyPW)
|
||||||
throws GeneralSecurityException, IOException {
|
throws GeneralSecurityException, IOException {
|
||||||
|
return createKeysAndCRL(ks, ksPW, alias, cname, null, ou, validDays, keyAlg, keySize, 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. localhost. Must be a hostname or email address. IP addresses will not be correctly encoded.
|
||||||
|
* @param altNames the Subject Alternative Names. May be null. May contain hostnames and/or IP addresses.
|
||||||
|
* cname, localhost, 127.0.0.1, and ::1 will be automatically added.
|
||||||
|
* @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.34 added altNames param
|
||||||
|
*/
|
||||||
|
public static Object[] createKeysAndCRL(File ks, String ksPW, String alias, String cname, Set<String> altNames, String ou,
|
||||||
|
int validDays, String keyAlg, int keySize, String keyPW)
|
||||||
|
throws GeneralSecurityException, IOException {
|
||||||
String algoName = getSigAlg(keySize, keyAlg);
|
String algoName = getSigAlg(keySize, keyAlg);
|
||||||
SigType type = null;
|
SigType type = null;
|
||||||
for (SigType t : EnumSet.allOf(SigType.class)) {
|
for (SigType t : EnumSet.allOf(SigType.class)) {
|
||||||
@ -556,7 +649,7 @@ public final class KeyStoreUtil {
|
|||||||
}
|
}
|
||||||
if (type == null)
|
if (type == null)
|
||||||
throw new GeneralSecurityException("Unsupported algorithm/size: " + keyAlg + '/' + keySize);
|
throw new GeneralSecurityException("Unsupported algorithm/size: " + keyAlg + '/' + keySize);
|
||||||
return createKeysAndCRL(ks, ksPW, alias, cname, ou, validDays, type, keyPW);
|
return createKeysAndCRL(ks, ksPW, alias, cname, altNames, ou, validDays, type, keyPW);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -592,13 +685,51 @@ public final class KeyStoreUtil {
|
|||||||
public static Object[] createKeysAndCRL(File ks, String ksPW, String alias, String cname, String ou,
|
public static Object[] createKeysAndCRL(File ks, String ksPW, String alias, String cname, String ou,
|
||||||
int validDays, SigType type, String keyPW)
|
int validDays, SigType type, String keyPW)
|
||||||
throws GeneralSecurityException, IOException {
|
throws GeneralSecurityException, IOException {
|
||||||
|
return createKeysAndCRL(ks, ksPW, alias, cname, null, 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. localhost. Must be a hostname or email address. IP addresses will not be correctly encoded.
|
||||||
|
* @param altNames the Subject Alternative Names. May be null. May contain hostnames and/or IP addresses.
|
||||||
|
* cname, localhost, 127.0.0.1, and ::1 will be automatically added.
|
||||||
|
* @param ou e.g. console
|
||||||
|
* @param validDays e.g. 3652 (10 years)
|
||||||
|
* @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.34 added altNames param
|
||||||
|
*/
|
||||||
|
public static Object[] createKeysAndCRL(File ks, String ksPW, String alias, String cname, Set<String> altNames, String ou,
|
||||||
|
int validDays, SigType type, String keyPW)
|
||||||
|
throws GeneralSecurityException, IOException {
|
||||||
File dir = ks.getParentFile();
|
File dir = ks.getParentFile();
|
||||||
if (dir != null && !dir.exists()) {
|
if (dir != null && !dir.exists()) {
|
||||||
File sdir = new SecureDirectory(dir.getAbsolutePath());
|
File sdir = new SecureDirectory(dir.getAbsolutePath());
|
||||||
if (!sdir.mkdirs())
|
if (!sdir.mkdirs())
|
||||||
throw new IOException("Can't create directory " + dir);
|
throw new IOException("Can't create directory " + dir);
|
||||||
}
|
}
|
||||||
Object[] rv = SelfSignedGenerator.generate(cname, ou, "I2P", "I2P Anonymous Network", null, null, validDays, type);
|
Object[] rv = SelfSignedGenerator.generate(cname, altNames, ou, "I2P", "I2P Anonymous Network", null, null, validDays, type);
|
||||||
//PublicKey jpub = (PublicKey) rv[0];
|
//PublicKey jpub = (PublicKey) rv[0];
|
||||||
PrivateKey jpriv = (PrivateKey) rv[1];
|
PrivateKey jpriv = (PrivateKey) rv[1];
|
||||||
X509Certificate cert = (X509Certificate) rv[2];
|
X509Certificate cert = (X509Certificate) rv[2];
|
||||||
|
@ -18,8 +18,10 @@ import java.text.SimpleDateFormat;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
import javax.crypto.interfaces.DHPublicKey;
|
import javax.crypto.interfaces.DHPublicKey;
|
||||||
@ -27,12 +29,15 @@ import javax.crypto.spec.DHParameterSpec;
|
|||||||
import javax.crypto.spec.DHPublicKeySpec;
|
import javax.crypto.spec.DHPublicKeySpec;
|
||||||
import javax.security.auth.x500.X500Principal;
|
import javax.security.auth.x500.X500Principal;
|
||||||
|
|
||||||
|
import org.apache.http.conn.util.InetAddressUtils;
|
||||||
|
|
||||||
import static net.i2p.crypto.SigUtil.intToASN1;
|
import static net.i2p.crypto.SigUtil.intToASN1;
|
||||||
import net.i2p.data.DataHelper;
|
import net.i2p.data.DataHelper;
|
||||||
import net.i2p.data.Signature;
|
import net.i2p.data.Signature;
|
||||||
import net.i2p.data.SigningPrivateKey;
|
import net.i2p.data.SigningPrivateKey;
|
||||||
import net.i2p.data.SigningPublicKey;
|
import net.i2p.data.SigningPublicKey;
|
||||||
import net.i2p.data.SimpleDataStructure;
|
import net.i2p.data.SimpleDataStructure;
|
||||||
|
import net.i2p.util.Addresses;
|
||||||
import net.i2p.util.HexDump;
|
import net.i2p.util.HexDump;
|
||||||
import net.i2p.util.RandomSource;
|
import net.i2p.util.RandomSource;
|
||||||
import net.i2p.util.SecureFileOutputStream;
|
import net.i2p.util.SecureFileOutputStream;
|
||||||
@ -42,10 +47,12 @@ import net.i2p.util.SystemVersion;
|
|||||||
* Generate keys and a selfsigned certificate, suitable for
|
* Generate keys and a selfsigned certificate, suitable for
|
||||||
* storing in a Keystore with KeyStoreUtil.storePrivateKey().
|
* storing in a Keystore with KeyStoreUtil.storePrivateKey().
|
||||||
* All done programatically, no keytool, no BC libs, no sun classes.
|
* All done programatically, no keytool, no BC libs, no sun classes.
|
||||||
* Ref: RFC 2459
|
* Ref: RFC 2459, RFC 5280
|
||||||
*
|
*
|
||||||
* This is coded to create a cert that is similar to what comes out of keytool,
|
* This is coded to create a cert that is similar to what comes out of keytool.
|
||||||
* even if I don't understand all of it.
|
*
|
||||||
|
* NOTE: Recommended use is via KeyStoreUtil.createKeys() and related methods.
|
||||||
|
* This API may not be stable.
|
||||||
*
|
*
|
||||||
* @since 0.9.25
|
* @since 0.9.25
|
||||||
*/
|
*/
|
||||||
@ -108,6 +115,29 @@ public final class SelfSignedGenerator {
|
|||||||
*/
|
*/
|
||||||
public static Object[] generate(String cname, String ou, String o, String l, String st, String c,
|
public static Object[] generate(String cname, String ou, String o, String l, String st, String c,
|
||||||
int validDays, SigType type) throws GeneralSecurityException {
|
int validDays, SigType type) throws GeneralSecurityException {
|
||||||
|
return generate(cname, null, ou, o, l, st, c, validDays, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param cname the common name, non-null. Must be a hostname or email address. IP addresses will not be correctly encoded.
|
||||||
|
* @param altNames the Subject Alternative Names. May be null. May contain hostnames and/or IP addresses.
|
||||||
|
* cname, localhost, 127.0.0.1, and ::1 will be automatically added.
|
||||||
|
* @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
|
||||||
|
*
|
||||||
|
* @since 0.9.34 added altNames param
|
||||||
|
*/
|
||||||
|
public static Object[] generate(String cname, Set<String> altNames, String ou, String o, String l, String st, String c,
|
||||||
|
int validDays, SigType type) throws GeneralSecurityException {
|
||||||
SimpleDataStructure[] keys = KeyGenerator.getInstance().generateSigningKeys(type);
|
SimpleDataStructure[] keys = KeyGenerator.getInstance().generateSigningKeys(type);
|
||||||
SigningPublicKey pub = (SigningPublicKey) keys[0];
|
SigningPublicKey pub = (SigningPublicKey) keys[0];
|
||||||
SigningPrivateKey priv = (SigningPrivateKey) keys[1];
|
SigningPrivateKey priv = (SigningPrivateKey) keys[1];
|
||||||
@ -132,7 +162,7 @@ public final class SelfSignedGenerator {
|
|||||||
}
|
}
|
||||||
byte[] sigoid = getEncodedOIDSeq(oid);
|
byte[] sigoid = getEncodedOIDSeq(oid);
|
||||||
|
|
||||||
byte[] tbs = genTBS(cname, ou, o, l, st, c, validDays, sigoid, jpub);
|
byte[] tbs = genTBS(cname, altNames, ou, o, l, st, c, validDays, sigoid, jpub);
|
||||||
int tbslen = tbs.length;
|
int tbslen = tbs.length;
|
||||||
|
|
||||||
Signature sig = DSAEngine.getInstance().sign(tbs, priv);
|
Signature sig = DSAEngine.getInstance().sign(tbs, priv);
|
||||||
@ -261,13 +291,15 @@ public final class SelfSignedGenerator {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param cname the common name, non-null
|
* @param cname the common name, non-null
|
||||||
|
* @param altNames the Subject Alternative Names. May be null. May contain hostnames and/or IP addresses.
|
||||||
|
* cname, localhost, 127.0.0.1, and ::1 will be automatically added.
|
||||||
* @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 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 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 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 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
|
* @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,
|
private static byte[] genTBS(String cname, Set<String> altNames, String ou, String o, String l, String st, String c,
|
||||||
int validDays, byte[] sigoid, PublicKey jpub) throws GeneralSecurityException {
|
int validDays, byte[] sigoid, PublicKey jpub) throws GeneralSecurityException {
|
||||||
// a0 ???, int = 2
|
// a0 ???, int = 2
|
||||||
byte[] version = { (byte) 0xa0, 3, 2, 1, 2 };
|
byte[] version = { (byte) 0xa0, 3, 2, 1, 2 };
|
||||||
@ -298,7 +330,7 @@ public final class SelfSignedGenerator {
|
|||||||
byte[] subject = issuer;
|
byte[] subject = issuer;
|
||||||
|
|
||||||
byte[] pubbytes = jpub.getEncoded();
|
byte[] pubbytes = jpub.getEncoded();
|
||||||
byte[] extbytes = getExtensions(pubbytes, cname);
|
byte[] extbytes = getExtensions(pubbytes, cname, altNames);
|
||||||
|
|
||||||
int len = version.length + serial.length + sigoid.length + issuer.length +
|
int len = version.length + serial.length + sigoid.length + issuer.length +
|
||||||
validity.length + subject.length + pubbytes.length + extbytes.length;
|
validity.length + subject.length + pubbytes.length + extbytes.length;
|
||||||
@ -494,9 +526,11 @@ public final class SelfSignedGenerator {
|
|||||||
* Ref: RFC 5280
|
* Ref: RFC 5280
|
||||||
*
|
*
|
||||||
* @param pubbytes bit string
|
* @param pubbytes bit string
|
||||||
|
* @param altNames the Subject Alternative Names. May be null. May contain hostnames and/or IP addresses.
|
||||||
|
* cname, localhost, 127.0.0.1, and ::1 will be automatically added.
|
||||||
* @return ASN.1 encoded object
|
* @return ASN.1 encoded object
|
||||||
*/
|
*/
|
||||||
private static byte[] getExtensions(byte[] pubbytes, String cname) {
|
private static byte[] getExtensions(byte[] pubbytes, String cname, Set<String> altNames) {
|
||||||
// RFC 2549 sec. 4.2.1.2
|
// RFC 2549 sec. 4.2.1.2
|
||||||
// subject public key identifier is the sha1 hash of the bit string of the public key
|
// subject public key identifier is the sha1 hash of the bit string of the public key
|
||||||
// without the tag, length, and igore fields
|
// without the tag, length, and igore fields
|
||||||
@ -530,21 +564,27 @@ public final class SelfSignedGenerator {
|
|||||||
int wrap3len = spaceFor(TRUE.length);
|
int wrap3len = spaceFor(TRUE.length);
|
||||||
int ext3len = oid3.length + TRUE.length + spaceFor(wrap3len);
|
int ext3len = oid3.length + TRUE.length + spaceFor(wrap3len);
|
||||||
|
|
||||||
// TODO if IP address, encode as 4 or 16 bytes
|
int wrap41len = 0;
|
||||||
byte[] cnameBytes = DataHelper.getASCII(cname);
|
if (altNames == null)
|
||||||
int wrap41len = spaceFor(cnameBytes.length);
|
altNames = new HashSet<String>(4);
|
||||||
// only used for CA
|
else
|
||||||
byte[] ipv4;
|
altNames.remove("0:0:0:0:0:0:0:1"); // We don't want dup of "::1"
|
||||||
byte[] ipv6;
|
altNames.add(cname);
|
||||||
final boolean isCA = !cname.contains("@");
|
final boolean isCA = !cname.contains("@");
|
||||||
if (isCA) {
|
if (isCA) {
|
||||||
ipv4 = new byte[] { 127, 0, 0, 1 };
|
altNames.add("localhost");
|
||||||
ipv6 = new byte[16];
|
altNames.add("127.0.0.1");
|
||||||
ipv6[15] = 1;
|
altNames.add("::1");
|
||||||
wrap41len += spaceFor(ipv4.length) + spaceFor(ipv6.length);
|
}
|
||||||
} else {
|
for (String n : altNames) {
|
||||||
ipv4 = null;
|
int len;
|
||||||
ipv6 = null;
|
if (InetAddressUtils.isIPv4Address(n))
|
||||||
|
len = 4;
|
||||||
|
else if (InetAddressUtils.isIPv6Address(n))
|
||||||
|
len = 16;
|
||||||
|
else
|
||||||
|
len = n.length();
|
||||||
|
wrap41len += spaceFor(len);
|
||||||
}
|
}
|
||||||
int wrap4len = spaceFor(wrap41len);
|
int wrap4len = spaceFor(wrap41len);
|
||||||
int ext4len = oid4.length + spaceFor(wrap4len);
|
int ext4len = oid4.length + spaceFor(wrap4len);
|
||||||
@ -643,26 +683,26 @@ public final class SelfSignedGenerator {
|
|||||||
idx = intToASN1(rv, idx, ext4len);
|
idx = intToASN1(rv, idx, ext4len);
|
||||||
System.arraycopy(oid4, 0, rv, idx, oid4.length);
|
System.arraycopy(oid4, 0, rv, idx, oid4.length);
|
||||||
idx += oid4.length;
|
idx += oid4.length;
|
||||||
// octet string wraps a sequence containing a choice 2 (DNSName) IA5String
|
// octet string wraps a sequence containing the names and IP addresses
|
||||||
// followed by two byteArrays (IP addresses)
|
|
||||||
rv[idx++] = (byte) 0x04;
|
rv[idx++] = (byte) 0x04;
|
||||||
idx = intToASN1(rv, idx, wrap4len);
|
idx = intToASN1(rv, idx, wrap4len);
|
||||||
rv[idx++] = (byte) 0x30;
|
rv[idx++] = (byte) 0x30;
|
||||||
idx = intToASN1(rv, idx, wrap41len);
|
idx = intToASN1(rv, idx, wrap41len);
|
||||||
// TODO if IP address, encode as 0x87
|
for (String n : altNames) {
|
||||||
rv[idx++] = (byte) (isCA ? 0x82 : 0x81); // choice, dNSName or rfc822Name, IA5String implied
|
byte[] b;
|
||||||
idx = intToASN1(rv, idx, cnameBytes.length);
|
if (InetAddressUtils.isIPv4Address(n) ||
|
||||||
System.arraycopy(cnameBytes, 0, rv, idx, cnameBytes.length);
|
InetAddressUtils.isIPv6Address(n)) {
|
||||||
idx += cnameBytes.length;
|
b = Addresses.getIP(n);
|
||||||
if (isCA) {
|
if (b == null) // shouldn't happen
|
||||||
rv[idx++] = (byte) 0x87; // choice, octet string for IP address
|
throw new IllegalArgumentException("fail " + n);
|
||||||
idx = intToASN1(rv, idx, ipv4.length);
|
rv[idx++] = (byte) 0x87; // choice, octet string for IP address
|
||||||
System.arraycopy(ipv4, 0, rv, idx, ipv4.length);
|
} else {
|
||||||
idx += ipv4.length;
|
b = DataHelper.getASCII(n);
|
||||||
rv[idx++] = (byte) 0x87; // choice, octet string for IP address
|
rv[idx++] = (byte) (isCA ? 0x82 : 0x81); // choice, dNSName or rfc822Name, IA5String implied
|
||||||
idx = intToASN1(rv, idx, ipv6.length);
|
}
|
||||||
System.arraycopy(ipv6, 0, rv, idx, ipv6.length);
|
idx = intToASN1(rv, idx, b.length);
|
||||||
idx += ipv6.length;
|
System.arraycopy(b, 0, rv, idx, b.length);
|
||||||
|
idx += b.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Policy
|
// Policy
|
||||||
|
Reference in New Issue
Block a user