diff --git a/core/java/src/net/i2p/crypto/CertUtil.java b/core/java/src/net/i2p/crypto/CertUtil.java index a723dcf574..dc8b357293 100644 --- a/core/java/src/net/i2p/crypto/CertUtil.java +++ b/core/java/src/net/i2p/crypto/CertUtil.java @@ -40,6 +40,7 @@ import javax.security.auth.x500.X500Principal; import net.i2p.I2PAppContext; import net.i2p.data.Base64; import net.i2p.data.DataHelper; +import net.i2p.data.SigningPrivateKey; import net.i2p.util.Log; import net.i2p.util.FileSuffixFilter; import net.i2p.util.SecureFileOutputStream; @@ -583,6 +584,10 @@ public final class CertUtil { Certificate cert = loadCert(f); boolean rv = isRevoked(I2PAppContext.getGlobalContext(), cert); System.out.println("Revoked? " + rv); + } else if (args[0].equals("loadprivatekey")) { + PrivateKey priv = loadPrivateKey(new FileInputStream(f)); + SigningPrivateKey spk = SigUtil.fromJavaKey(priv); + System.out.println("Found private key: " + spk); } else if (args[0].equals("checkall")) { int rv = checkAll(f); //System.exit(rv); diff --git a/core/java/src/net/i2p/crypto/SelfSignedGenerator.java b/core/java/src/net/i2p/crypto/SelfSignedGenerator.java index 3575f629f9..bd1a50713e 100644 --- a/core/java/src/net/i2p/crypto/SelfSignedGenerator.java +++ b/core/java/src/net/i2p/crypto/SelfSignedGenerator.java @@ -157,6 +157,23 @@ public final class SelfSignedGenerator { } } + /** + * Create a self-signed certificate for the existing private key. + * + * @param cname the common name, non-null. Must be a hostname or email address. IP addresses will not be correctly encoded. + * @return self-signed certificate + * @since 0.9.46 + */ + public static X509Certificate generate(SigningPrivateKey priv, String cname, + int validDays) throws GeneralSecurityException { + SigningPublicKey pub = priv.toPublic(); + PublicKey jpub = SigUtil.toJavaKey(pub); + PrivateKey jpriv = SigUtil.toJavaKey(priv); + SigType type = priv.getType(); + Object[] o = generate(jpub, jpriv, priv, type, cname, null, null, null, null, null, null, validDays); + return (X509Certificate) o[2]; + } + /** * @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. diff --git a/core/java/src/net/i2p/data/PrivateKeyFile.java b/core/java/src/net/i2p/data/PrivateKeyFile.java index 625c8bf533..4fdcd32c21 100644 --- a/core/java/src/net/i2p/data/PrivateKeyFile.java +++ b/core/java/src/net/i2p/data/PrivateKeyFile.java @@ -5,12 +5,14 @@ import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.InputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.security.GeneralSecurityException; import java.security.NoSuchAlgorithmException; +import java.security.cert.X509Certificate; import java.util.Date; import java.util.EnumSet; import java.util.Locale; @@ -29,12 +31,15 @@ import net.i2p.client.I2PSession; import net.i2p.client.I2PSessionException; import net.i2p.client.naming.HostTxtEntry; import net.i2p.crypto.Blinding; +import net.i2p.crypto.CertUtil; import net.i2p.crypto.DSAEngine; import net.i2p.crypto.EncType; import net.i2p.crypto.KeyGenerator; import net.i2p.crypto.KeyPair; +import net.i2p.crypto.SelfSignedGenerator; import net.i2p.crypto.SigAlgo; import net.i2p.crypto.SigType; +import net.i2p.crypto.SigUtil; import net.i2p.util.OrderedProperties; import net.i2p.util.RandomSource; import net.i2p.util.SecureFileOutputStream; @@ -101,10 +106,11 @@ public class PrivateKeyFile { String signer = null; String signername = null; String signaction = null; + String certfile = null; int days = 365; int mode = 0; boolean error = false; - Getopt g = new Getopt("pkf", args, "t:nuxhse:c:a:o:d:r:p:b:y:z:"); + Getopt g = new Getopt("pkf", args, "t:nuxhse:c:a:o:d:r:p:b:y:z:w:"); int c; while ((c = g.getopt()) != -1) { switch (c) { @@ -143,6 +149,14 @@ public class PrivateKeyFile { signername = g.getOptarg(); break; + case 'w': + certfile = g.getOptarg(); + if (mode == 0) + mode = c; + else + error = true; + break; + case 'y': signer = g.getOptarg(); break; @@ -348,6 +362,36 @@ public class PrivateKeyFile { return; } + case 'w': + { + if (pkf.isOffline()) { + System.out.println("Private key is offline, not present in file, export failed"); + return; + } + OutputStream out = null; + try { + SigningPrivateKey priv = pkf.getSigningPrivKey(); + java.security.PrivateKey jpriv = SigUtil.toJavaKey(priv); + if (signername == null) + signername = "example.i2p"; + X509Certificate cert = SelfSignedGenerator.generate(priv, signername, days); + java.security.cert.Certificate[] certs = { cert }; + out = new FileOutputStream(certfile); + CertUtil.exportPrivateKey(jpriv, certs, out); + System.out.println("Private key and self-signed certificate exported to " + certfile); + } catch (IOException ioe) { + System.out.println("Private key export failed"); + ioe.printStackTrace(); + } catch (GeneralSecurityException gse) { + System.out.println("Private key export failed"); + gse.printStackTrace(); + } finally { + if (out != null) try { out.close(); } catch (IOException ioe) {} + } + return; + } + + default: // shouldn't happen usage(); @@ -359,9 +403,13 @@ public class PrivateKeyFile { verifySignature(pkf.getDestination()); } } catch (I2PException e) { + String orig = offline != null ? offline : filearg; + System.out.println("Error processing file: " + orig); e.printStackTrace(); System.exit(1); } catch (IOException e) { + String orig = offline != null ? offline : filearg; + System.out.println("Error processing file: " + orig); e.printStackTrace(); System.exit(1); } @@ -385,6 +433,7 @@ public class PrivateKeyFile { " -p enctype (specify enc type of destination)\n" + " -r sigtype (specify sig type of transient key, default Ed25519)\n" + " -t sigtype (changes to KeyCertificate of the given sig type)\n" + + " -w file.key (export the private signing key to the file specified, also uses -d and -b options)\n" + " -y 2lddestfile (sign the authentication string with the 2LD key file specified)\n" + " -z signaction (authentication string command, must be \"addsubdomain\"\n" + ""); @@ -941,8 +990,8 @@ public class PrivateKeyFile { s.append(_transientSigningPrivKey); } else { s.append(this.signingPrivKey); - s.append("\n"); } + s.append("\n"); return s.toString(); }