forked from I2P_Developers/i2p.i2p
Crypto: New utils to support private key import/export
Console: New /configfamily, /exportfamily
This commit is contained in:
@ -9,12 +9,19 @@ import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.naming.InvalidNameException;
|
||||
@ -24,6 +31,7 @@ import javax.security.auth.x500.X500Principal;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SecureFileOutputStream;
|
||||
import net.i2p.util.SystemVersion;
|
||||
@ -235,4 +243,88 @@ public class CertUtil {
|
||||
try { if (fis != null) fis.close(); } catch (IOException foo) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single Private Key from an input stream.
|
||||
* Does NOT close the stream.
|
||||
*
|
||||
* @return non-null, non-empty, throws on all errors including certificate invalid
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public static PrivateKey loadPrivateKey(InputStream in) throws IOException, GeneralSecurityException {
|
||||
try {
|
||||
String line;
|
||||
while ((line = DataHelper.readLine(in)) != null) {
|
||||
if (line.startsWith("---") && line.contains("BEGIN") && line.contains("PRIVATE"))
|
||||
break;
|
||||
}
|
||||
if (line == null)
|
||||
throw new IOException("no private key found");
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
while ((line = DataHelper.readLine(in)) != null) {
|
||||
if (line.startsWith("---"))
|
||||
break;
|
||||
buf.append(line.trim());
|
||||
}
|
||||
if (buf.length() <= 0)
|
||||
throw new IOException("no private key found");
|
||||
byte[] data = Base64.decode(buf.toString(), true);
|
||||
if (data == null)
|
||||
throw new CertificateEncodingException("bad base64 cert");
|
||||
PrivateKey rv = null;
|
||||
// try all the types
|
||||
for (SigAlgo algo : EnumSet.allOf(SigAlgo.class)) {
|
||||
try {
|
||||
KeySpec ks = new PKCS8EncodedKeySpec(data);
|
||||
String alg = algo.getName();
|
||||
KeyFactory kf = KeyFactory.getInstance(alg);
|
||||
rv = kf.generatePrivate(ks);
|
||||
break;
|
||||
} catch (GeneralSecurityException gse) {
|
||||
//gse.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (rv == null)
|
||||
throw new InvalidKeyException("unsupported key type");
|
||||
return rv;
|
||||
} catch (IllegalArgumentException iae) {
|
||||
// java 1.8.0_40-b10, openSUSE
|
||||
// Exception in thread "main" java.lang.IllegalArgumentException: Input byte array has wrong 4-byte ending unit
|
||||
// at java.util.Base64$Decoder.decode0(Base64.java:704)
|
||||
throw new GeneralSecurityException("key error", iae);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get one or more certificates from an input stream.
|
||||
* Throws if any certificate is invalid (e.g. expired).
|
||||
* Does NOT close the stream.
|
||||
*
|
||||
* @return non-null, non-empty, throws on all errors including certificate invalid
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public static List<X509Certificate> loadCerts(InputStream in) throws IOException, GeneralSecurityException {
|
||||
try {
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
Collection<? extends Certificate> certs = cf.generateCertificates(in);
|
||||
List<X509Certificate> rv = new ArrayList<X509Certificate>(certs.size());
|
||||
for (Certificate cert : certs) {
|
||||
if (!(cert instanceof X509Certificate))
|
||||
throw new GeneralSecurityException("not a X.509 cert");
|
||||
X509Certificate xcert = (X509Certificate) cert;
|
||||
xcert.checkValidity();
|
||||
rv.add(xcert);
|
||||
}
|
||||
if (rv.isEmpty())
|
||||
throw new IOException("no certs found");
|
||||
return rv;
|
||||
} catch (IllegalArgumentException iae) {
|
||||
// java 1.8.0_40-b10, openSUSE
|
||||
// Exception in thread "main" java.lang.IllegalArgumentException: Input byte array has wrong 4-byte ending unit
|
||||
// at java.util.Base64$Decoder.decode0(Base64.java:704)
|
||||
throw new GeneralSecurityException("cert error", iae);
|
||||
} finally {
|
||||
try { in.close(); } catch (IOException foo) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import java.security.cert.CertificateExpiredException;
|
||||
import java.security.cert.CertificateNotYetValidException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
@ -559,6 +560,103 @@ public class KeyStoreUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the private key and certificate chain (if any) out of a keystore.
|
||||
* Does NOT close the stream. Throws on all errors.
|
||||
*
|
||||
* @param ks path to the keystore
|
||||
* @param ksPW the keystore password, may be null
|
||||
* @param alias the name of the key
|
||||
* @param keyPW the key password, must be at least 6 characters
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public static void exportPrivateKey(File ks, String ksPW, String alias, String keyPW,
|
||||
OutputStream out)
|
||||
throws GeneralSecurityException, IOException {
|
||||
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);
|
||||
char[] keypwchars = keyPW.toCharArray();
|
||||
PrivateKey pk = (PrivateKey) keyStore.getKey(alias, keypwchars);
|
||||
if (pk == null)
|
||||
throw new GeneralSecurityException("private key not found: " + alias);
|
||||
Certificate[] certs = keyStore.getCertificateChain(alias);
|
||||
CertUtil.exportPrivateKey(pk, certs, out);
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import the private key and certificate chain to a keystore.
|
||||
* Keystore will be created if it does not exist.
|
||||
* Private key MUST be first in the stream.
|
||||
* Closes the stream. Throws on all errors.
|
||||
*
|
||||
* @param ks path to the keystore
|
||||
* @param ksPW the keystore password, may be null
|
||||
* @param alias the name of the key. If null, will be taken from the Subject CN
|
||||
* of the first certificate in the chain.
|
||||
* @param keyPW the key password, must be at least 6 characters
|
||||
* @return the alias as specified or extracted
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public static String importPrivateKey(File ks, String ksPW, String alias, String keyPW,
|
||||
InputStream in)
|
||||
throws GeneralSecurityException, IOException {
|
||||
OutputStream fos = null;
|
||||
try {
|
||||
KeyStore keyStore = createKeyStore(ks, ksPW);
|
||||
PrivateKey pk = CertUtil.loadPrivateKey(in);
|
||||
List<X509Certificate> certs = CertUtil.loadCerts(in);
|
||||
if (alias == null) {
|
||||
alias = CertUtil.getSubjectValue(certs.get(0), "CN");
|
||||
if (alias == null)
|
||||
throw new GeneralSecurityException("no alias specified and no Subject CN in cert");
|
||||
if (alias.endsWith(".family.i2p.net") && alias.length() > ".family.i2p.net".length())
|
||||
alias = alias.substring(0, ".family.i2p.net".length());
|
||||
}
|
||||
keyStore.setKeyEntry(alias, pk, keyPW.toCharArray(), certs.toArray(new Certificate[certs.size()]));
|
||||
char[] pwchars = ksPW != null ? ksPW.toCharArray() : null;
|
||||
fos = new SecureFileOutputStream(ks);
|
||||
keyStore.store(fos, pwchars);
|
||||
return alias;
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
|
||||
try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import the private key and certificate chain to a keystore.
|
||||
* Keystore will be created if it does not exist.
|
||||
* Private key MUST be first in the stream.
|
||||
* Closes the stream. Throws on all errors.
|
||||
*
|
||||
* @param ks path to the keystore
|
||||
* @param ksPW the keystore password, may be null
|
||||
* @param alias the name of the key, non-null.
|
||||
* @param keyPW the key password, must be at least 6 characters
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public static void storePrivateKey(File ks, String ksPW, String alias, String keyPW,
|
||||
PrivateKey pk, List<X509Certificate> certs)
|
||||
throws GeneralSecurityException, IOException {
|
||||
OutputStream fos = null;
|
||||
try {
|
||||
KeyStore keyStore = createKeyStore(ks, ksPW);
|
||||
keyStore.setKeyEntry(alias, pk, keyPW.toCharArray(), certs.toArray(new Certificate[certs.size()]));
|
||||
char[] pwchars = ksPW != null ? ksPW.toCharArray() : null;
|
||||
fos = new SecureFileOutputStream(ks);
|
||||
keyStore.store(fos, pwchars);
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a cert out of a keystore
|
||||
*
|
||||
@ -644,8 +742,16 @@ public class KeyStoreUtil {
|
||||
*/
|
||||
/****
|
||||
public static void main(String[] args) {
|
||||
File ksf = (args.length > 0) ? new File(args[0]) : null;
|
||||
try {
|
||||
if (args.length > 0 && "import".equals(args[0])) {
|
||||
testImport(args);
|
||||
return;
|
||||
}
|
||||
if (args.length > 0 && "export".equals(args[0])) {
|
||||
testExport(args);
|
||||
return;
|
||||
}
|
||||
File ksf = (args.length > 0) ? new File(args[0]) : null;
|
||||
if (ksf != null && !ksf.exists()) {
|
||||
createKeyStore(ksf, DEFAULT_KEYSTORE_PASSWORD);
|
||||
System.out.println("Created empty keystore " + ksf);
|
||||
@ -674,5 +780,22 @@ public class KeyStoreUtil {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static void testImport(String[] args) throws Exception {
|
||||
File ksf = new File(args[1]);
|
||||
InputStream in = new FileInputStream(args[2]);
|
||||
String alias = args[2];
|
||||
String pw = args[3];
|
||||
importPrivateKey(ksf, DEFAULT_KEYSTORE_PASSWORD, alias, pw, in);
|
||||
}
|
||||
|
||||
|
||||
private static void testExport(String[] args) throws Exception {
|
||||
File ksf = new File(args[1]);
|
||||
String alias = args[2];
|
||||
String pw = args[3];
|
||||
exportPrivateKey(ksf, DEFAULT_KEYSTORE_PASSWORD, alias, pw, System.out);
|
||||
}
|
||||
|
||||
****/
|
||||
}
|
||||
|
@ -104,6 +104,17 @@ public class Base64 {
|
||||
return safeDecode(s, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes data from Base64 notation using the I2P alphabet.
|
||||
*
|
||||
* @param useStandardAlphabet Warning, must be false for I2P compatibility
|
||||
* @return the decoded data, null on error
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public static byte[] decode(String s, boolean useStandardAlphabet) {
|
||||
return safeDecode(s, useStandardAlphabet);
|
||||
}
|
||||
|
||||
/** Maximum line length (76) of Base64 output. */
|
||||
private final static int MAX_LINE_LENGTH = 76;
|
||||
|
||||
|
Reference in New Issue
Block a user