diff --git a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java index 9f46055b5d..899dd6048b 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java @@ -42,6 +42,8 @@ import net.i2p.util.SecureDirectory; import net.i2p.util.I2PSSLSocketFactory; import net.i2p.util.SystemVersion; +import org.apache.http.conn.util.InetAddressUtils; + import org.eclipse.jetty.security.HashLoginService; import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.ConstraintSecurityHandler; @@ -516,17 +518,15 @@ public class RouterConsoleRunner implements RouterApp { try { // Test before we add the connector, because Jetty 6 won't start if any of the // connectors are bad - InetAddress test = InetAddress.getByName(host); - if ((!hasIPV6) && (!(test instanceof Inet4Address))) + if ((!hasIPV6) && InetAddressUtils.isIPv6Address(host)) throw new IOException("IPv6 addresses unsupported"); - if ((!hasIPV4) && (test instanceof Inet4Address)) + if ((!hasIPV4) && InetAddressUtils.isIPv4Address(host)) throw new IOException("IPv4 addresses unsupported"); ServerSocket testSock = null; try { // On Windows, this was passing and Jetty was still failing, // possibly due to %scope_id ??? // https://issues.apache.org/jira/browse/ZOOKEEPER-667 - //testSock = new ServerSocket(0, 0, test); // so do exactly what Jetty does in SelectChannelConnector.open() testSock = new ServerSocket(); InetSocketAddress isa = new InetSocketAddress(host, 0); @@ -574,7 +574,23 @@ public class RouterConsoleRunner implements RouterApp { } if (sslPort > 0) { 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 altNames = new HashSet(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 SslContextFactory sslFactory = new SslContextFactory(keyStore.getAbsolutePath()); 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( new String[I2PSSLSocketFactory.EXCLUDE_CIPHERS.size()])); List hosts = new ArrayList(2); - StringTokenizer tok = new StringTokenizer(_sslListenHost, " ,"); + tok = new StringTokenizer(_sslListenHost, " ,"); while (tok.hasMoreTokens()) { String host = tok.nextToken().trim(); // doing it this way means we don't have to escape an IPv6 host with [] try { // Test before we add the connector, because Jetty 6 won't start if any of the // connectors are bad - InetAddress test = InetAddress.getByName(host); - if ((!hasIPV6) && (!(test instanceof Inet4Address))) + if ((!hasIPV6) && InetAddressUtils.isIPv6Address(host)) throw new IOException("IPv6 addresses unsupported"); - if ((!hasIPV4) && (test instanceof Inet4Address)) + if ((!hasIPV4) && InetAddressUtils.isIPv4Address(host)) throw new IOException("IPv4 addresses unsupported"); ServerSocket testSock = null; try { // see comments above - //testSock = new ServerSocket(0, 0, test); testSock = new ServerSocket(); InetSocketAddress isa = new InetSocketAddress(host, 0); 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. * @since 0.8.3 */ - private boolean verifyKeyStore(File ks) { + private boolean verifyKeyStore(File ks, Set altNames) { if (ks.exists()) { boolean rv = _context.getProperty(PROP_KEY_PASSWORD) != null; if (!rv) System.err.println("Console SSL error, must set " + PROP_KEY_PASSWORD + " in " + (new File(_context.getConfigDir(), "router.config")).getAbsolutePath()); return rv; } - return createKeyStore(ks); + return createKeyStore(ks, altNames); } /** - * Call out to keytool to 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. + * Create a new keystore with a keypair in it. * * @return success * @since 0.8.3 */ - private boolean createKeyStore(File ks) { + private boolean createKeyStore(File ks, Set altNames) { // make a random 48 character password (30 * 8 / 5) String keyPassword = KeyStoreUtil.randomString(); String cname = "localhost"; - boolean success = KeyStoreUtil.createKeys(ks, "console", cname, "Console", keyPassword); + boolean success = KeyStoreUtil.createKeys(ks, "console", cname, altNames, "Console", keyPassword); if (success) { success = ks.exists(); if (success) { @@ -883,7 +895,8 @@ public class RouterConsoleRunner implements RouterApp { } if (success) { 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."); } else { System.err.println("Failed to create console SSL keystore.\n" + diff --git a/core/java/src/net/i2p/crypto/KeyStoreUtil.java b/core/java/src/net/i2p/crypto/KeyStoreUtil.java index a922088b8c..dc7111e4cb 100644 --- a/core/java/src/net/i2p/crypto/KeyStoreUtil.java +++ b/core/java/src/net/i2p/crypto/KeyStoreUtil.java @@ -467,7 +467,30 @@ public final class KeyStoreUtil { */ public static boolean createKeys(File ks, String alias, String cname, String ou, 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 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); } @@ -494,12 +517,42 @@ public final class KeyStoreUtil { */ public static boolean createKeys(File ks, String ksPW, String alias, String cname, String ou, int validDays, String keyAlg, int keySize, String keyPW) { + 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 altNames, String ou, + int validDays, String keyAlg, int keySize, String keyPW) { boolean useKeytool = I2PAppContext.getGlobalContext().getBooleanProperty("crypto.useExternalKeytool"); 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); } else { try { - createKeysAndCRL(ks, ksPW, alias, cname, ou, validDays, keyAlg, keySize, keyPW); + createKeysAndCRL(ks, ksPW, alias, cname, altNames, ou, validDays, keyAlg, keySize, keyPW); return true; } catch (GeneralSecurityException 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, int validDays, String keyAlg, int keySize, String keyPW) 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 altNames, String ou, + int validDays, String keyAlg, int keySize, String keyPW) + throws GeneralSecurityException, IOException { String algoName = getSigAlg(keySize, keyAlg); SigType type = null; for (SigType t : EnumSet.allOf(SigType.class)) { @@ -556,7 +649,7 @@ public final class KeyStoreUtil { } if (type == null) 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, int validDays, SigType type, String keyPW) 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 altNames, String ou, + int validDays, SigType type, String keyPW) + throws GeneralSecurityException, IOException { File dir = ks.getParentFile(); if (dir != null && !dir.exists()) { File sdir = new SecureDirectory(dir.getAbsolutePath()); if (!sdir.mkdirs()) 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]; PrivateKey jpriv = (PrivateKey) rv[1]; X509Certificate cert = (X509Certificate) rv[2]; diff --git a/core/java/src/net/i2p/crypto/SelfSignedGenerator.java b/core/java/src/net/i2p/crypto/SelfSignedGenerator.java index 8dcf48489a..721ed216a3 100644 --- a/core/java/src/net/i2p/crypto/SelfSignedGenerator.java +++ b/core/java/src/net/i2p/crypto/SelfSignedGenerator.java @@ -18,8 +18,10 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.TimeZone; import javax.crypto.interfaces.DHPublicKey; @@ -27,12 +29,15 @@ import javax.crypto.spec.DHParameterSpec; import javax.crypto.spec.DHPublicKeySpec; import javax.security.auth.x500.X500Principal; +import org.apache.http.conn.util.InetAddressUtils; + import static net.i2p.crypto.SigUtil.intToASN1; import net.i2p.data.DataHelper; import net.i2p.data.Signature; import net.i2p.data.SigningPrivateKey; import net.i2p.data.SigningPublicKey; import net.i2p.data.SimpleDataStructure; +import net.i2p.util.Addresses; import net.i2p.util.HexDump; import net.i2p.util.RandomSource; import net.i2p.util.SecureFileOutputStream; @@ -42,10 +47,12 @@ import net.i2p.util.SystemVersion; * Generate keys and a selfsigned certificate, suitable for * storing in a Keystore with KeyStoreUtil.storePrivateKey(). * All done programatically, no keytool, no BC libs, no sun classes. - * Ref: RFC 2459 + * Ref: RFC 2459, RFC 5280 * - * 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. + * This is coded to create a cert that is similar to what comes out of keytool. + * + * NOTE: Recommended use is via KeyStoreUtil.createKeys() and related methods. + * This API may not be stable. * * @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, 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 altNames, String ou, String o, String l, String st, String c, + int validDays, SigType type) throws GeneralSecurityException { SimpleDataStructure[] keys = KeyGenerator.getInstance().generateSigningKeys(type); SigningPublicKey pub = (SigningPublicKey) keys[0]; SigningPrivateKey priv = (SigningPrivateKey) keys[1]; @@ -132,7 +162,7 @@ public final class SelfSignedGenerator { } 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; Signature sig = DSAEngine.getInstance().sign(tbs, priv); @@ -261,13 +291,15 @@ public final class SelfSignedGenerator { /** * @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 o The O (organization)in the distinguished name, non-null before 0.9.28, may be null as of 0.9.28 * @param l The L (city or locality) in the distinguished name, non-null before 0.9.28, may be null as of 0.9.28 * @param st The ST (state or province) in the distinguished name, non-null before 0.9.28, may be null as of 0.9.28 * @param c The C (country) in the distinguished name, non-null before 0.9.28, may be null as of 0.9.28 */ - private static byte[] genTBS(String cname, String ou, String o, String l, String st, String c, + private static byte[] genTBS(String cname, Set altNames, String ou, String o, String l, String st, String c, int validDays, byte[] sigoid, PublicKey jpub) throws GeneralSecurityException { // a0 ???, int = 2 byte[] version = { (byte) 0xa0, 3, 2, 1, 2 }; @@ -298,7 +330,7 @@ public final class SelfSignedGenerator { byte[] subject = issuer; 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 + validity.length + subject.length + pubbytes.length + extbytes.length; @@ -494,9 +526,11 @@ public final class SelfSignedGenerator { * Ref: RFC 5280 * * @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 */ - private static byte[] getExtensions(byte[] pubbytes, String cname) { + private static byte[] getExtensions(byte[] pubbytes, String cname, Set altNames) { // RFC 2549 sec. 4.2.1.2 // subject public key identifier is the sha1 hash of the bit string of the public key // without the tag, length, and igore fields @@ -530,21 +564,27 @@ public final class SelfSignedGenerator { int wrap3len = spaceFor(TRUE.length); int ext3len = oid3.length + TRUE.length + spaceFor(wrap3len); - // TODO if IP address, encode as 4 or 16 bytes - byte[] cnameBytes = DataHelper.getASCII(cname); - int wrap41len = spaceFor(cnameBytes.length); - // only used for CA - byte[] ipv4; - byte[] ipv6; + int wrap41len = 0; + if (altNames == null) + altNames = new HashSet(4); + else + altNames.remove("0:0:0:0:0:0:0:1"); // We don't want dup of "::1" + altNames.add(cname); final boolean isCA = !cname.contains("@"); if (isCA) { - ipv4 = new byte[] { 127, 0, 0, 1 }; - ipv6 = new byte[16]; - ipv6[15] = 1; - wrap41len += spaceFor(ipv4.length) + spaceFor(ipv6.length); - } else { - ipv4 = null; - ipv6 = null; + altNames.add("localhost"); + altNames.add("127.0.0.1"); + altNames.add("::1"); + } + for (String n : altNames) { + int len; + 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 ext4len = oid4.length + spaceFor(wrap4len); @@ -643,26 +683,26 @@ public final class SelfSignedGenerator { idx = intToASN1(rv, idx, ext4len); System.arraycopy(oid4, 0, rv, idx, oid4.length); idx += oid4.length; - // octet string wraps a sequence containing a choice 2 (DNSName) IA5String - // followed by two byteArrays (IP addresses) + // octet string wraps a sequence containing the names and IP addresses rv[idx++] = (byte) 0x04; idx = intToASN1(rv, idx, wrap4len); rv[idx++] = (byte) 0x30; idx = intToASN1(rv, idx, wrap41len); - // TODO if IP address, encode as 0x87 - rv[idx++] = (byte) (isCA ? 0x82 : 0x81); // choice, dNSName or rfc822Name, IA5String implied - idx = intToASN1(rv, idx, cnameBytes.length); - System.arraycopy(cnameBytes, 0, rv, idx, cnameBytes.length); - idx += cnameBytes.length; - if (isCA) { - 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; + 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 + } + idx = intToASN1(rv, idx, b.length); + System.arraycopy(b, 0, rv, idx, b.length); + idx += b.length; } // Policy