Crypto: Add support for more alt names in certs (tickets #2159, #2160)

Set alt names for console cert
Use utils to validate console IP addresses
This commit is contained in:
zzz
2018-02-25 14:17:01 +00:00
parent 622c6801ae
commit 79baf70f9a
3 changed files with 242 additions and 58 deletions

View File

@ -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" +

View File

@ -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];

View File

@ -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) {
byte[] b;
if (InetAddressUtils.isIPv4Address(n) ||
InetAddressUtils.isIPv6Address(n)) {
b = Addresses.getIP(n);
if (b == null) // shouldn't happen
throw new IllegalArgumentException("fail " + n);
rv[idx++] = (byte) 0x87; // choice, octet string for IP address
} else {
b = DataHelper.getASCII(n);
rv[idx++] = (byte) (isCA ? 0x82 : 0x81); // choice, dNSName or rfc822Name, IA5String implied rv[idx++] = (byte) (isCA ? 0x82 : 0x81); // choice, dNSName or rfc822Name, IA5String implied
idx = intToASN1(rv, idx, cnameBytes.length); }
System.arraycopy(cnameBytes, 0, rv, idx, cnameBytes.length); idx = intToASN1(rv, idx, b.length);
idx += cnameBytes.length; System.arraycopy(b, 0, rv, idx, b.length);
if (isCA) { idx += b.length;
rv[idx++] = (byte) 0x87; // choice, octet string for IP address
idx = intToASN1(rv, idx, ipv4.length);
System.arraycopy(ipv4, 0, rv, idx, ipv4.length);
idx += ipv4.length;
rv[idx++] = (byte) 0x87; // choice, octet string for IP address
idx = intToASN1(rv, idx, ipv6.length);
System.arraycopy(ipv6, 0, rv, idx, ipv6.length);
idx += ipv6.length;
} }
// Policy // Policy