From 66ecdb2f7a9136043214dc483ca7e9c01ccbf660 Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 23 Jul 2019 15:56:03 +0000 Subject: [PATCH] Data: Initial support for enc types in PrivateKeyFile and I2PSessionImpl CreateRouterInfoJob updates and cleanups; KeyCert updates --- .../net/i2p/client/impl/I2PSessionImpl.java | 11 +- .../java/src/net/i2p/data/KeyCertificate.java | 50 ++++++++- .../java/src/net/i2p/data/PrivateKeyFile.java | 102 ++++++++++++++++-- .../router/startup/CreateRouterInfoJob.java | 51 +++++---- 4 files changed, 181 insertions(+), 33 deletions(-) diff --git a/core/java/src/net/i2p/client/impl/I2PSessionImpl.java b/core/java/src/net/i2p/client/impl/I2PSessionImpl.java index 1ca8716a8a..f4a1063ad9 100644 --- a/core/java/src/net/i2p/client/impl/I2PSessionImpl.java +++ b/core/java/src/net/i2p/client/impl/I2PSessionImpl.java @@ -38,6 +38,7 @@ import net.i2p.client.I2PClient; import net.i2p.client.I2PSession; import net.i2p.client.I2PSessionException; import net.i2p.client.I2PSessionListener; +import net.i2p.crypto.EncType; import net.i2p.crypto.SigType; import net.i2p.data.Base32; import net.i2p.data.DataFormatException; @@ -88,9 +89,9 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2 /** who we are */ private final Destination _myDestination; /** private key for decryption */ - private final PrivateKey _privateKey; + private PrivateKey _privateKey; /** private key for signing */ - private /* final */ SigningPrivateKey _signingPrivateKey; + private SigningPrivateKey _signingPrivateKey; /** configuration options */ private final Properties _options; /** this session's Id */ @@ -587,6 +588,12 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2 */ private void readDestination(InputStream destKeyStream) throws DataFormatException, IOException { _myDestination.readBytes(destKeyStream); + // we don't support EncTypes in Destinations, + // but i2pd does, and also, this code is used from PrivateKeyFile to + // read in router.keys.dat files and we will support EncTypes for RouterIdentities + EncType etype = _myDestination.getPublicKey().getType(); + if (etype != EncType.ELGAMAL_2048) + _privateKey = new PrivateKey(etype); _privateKey.readBytes(destKeyStream); SigType dtype = _myDestination.getSigningPublicKey().getType(); _signingPrivateKey = new SigningPrivateKey(dtype); diff --git a/core/java/src/net/i2p/data/KeyCertificate.java b/core/java/src/net/i2p/data/KeyCertificate.java index 113f57fbf2..87374186c8 100644 --- a/core/java/src/net/i2p/data/KeyCertificate.java +++ b/core/java/src/net/i2p/data/KeyCertificate.java @@ -90,6 +90,35 @@ public class KeyCertificate extends Certificate { System.arraycopy(spk.getData(), 128, _payload, HEADER_LENGTH, extra); } + /** + * A KeyCertificate with enc type from the given public key, + * and the signature type and extra data from the given public key. + * EncType lengths greater than 256 not supported. + * + * @param spk non-null data non-null + * @param pk non-null + * @throws IllegalArgumentException + * @since 0.9.42 + */ + public KeyCertificate(SigningPublicKey spk, PublicKey pk) { + super(CERTIFICATE_TYPE_KEY, null); + if (spk == null || spk.getData() == null || + pk == null || pk.getData() == null) + throw new IllegalArgumentException(); + SigType type = spk.getType(); + int len = type.getPubkeyLen(); + int extra = Math.max(0, len - 128); + _payload = new byte[HEADER_LENGTH + extra]; + int code = type.getCode(); + _payload[0] = (byte) (code >> 8); + _payload[1] = (byte) (code & 0xff); + code = pk.getType().getCode(); + _payload[2] = (byte) (code >> 8); + _payload[3] = (byte) (code & 0xff); + if (extra > 0) + System.arraycopy(spk.getData(), 128, _payload, HEADER_LENGTH, extra); + } + /** * A KeyCertificate with crypto type 0 (ElGamal) * and the signature type as specified. @@ -101,6 +130,23 @@ public class KeyCertificate extends Certificate { * @throws IllegalArgumentException */ public KeyCertificate(SigType type) { + this(type, EncType.ELGAMAL_2048); + } + + /** + * A KeyCertificate with crypto type + * and the signature type as specified. + * Payload is created. + * If type.getPubkeyLen() is greater than 128, caller MUST + * fill in the extra key data in the payload. + * EncType lengths greater than 256 not supported. + * + * @param type non-null + * @param etype non-null + * @throws IllegalArgumentException + * @since 0.9.42 + */ + public KeyCertificate(SigType type, EncType etype) { super(CERTIFICATE_TYPE_KEY, null); int len = type.getPubkeyLen(); int extra = Math.max(0, len - 128); @@ -108,7 +154,9 @@ public class KeyCertificate extends Certificate { int code = type.getCode(); _payload[0] = (byte) (code >> 8); _payload[1] = (byte) (code & 0xff); - // 2 and 3 always 0, it is the only crypto code for now + code = etype.getCode(); + _payload[2] = (byte) (code >> 8); + _payload[3] = (byte) (code & 0xff); } /** diff --git a/core/java/src/net/i2p/data/PrivateKeyFile.java b/core/java/src/net/i2p/data/PrivateKeyFile.java index 866b7125a0..3cb20d2998 100644 --- a/core/java/src/net/i2p/data/PrivateKeyFile.java +++ b/core/java/src/net/i2p/data/PrivateKeyFile.java @@ -29,7 +29,9 @@ import net.i2p.client.I2PSessionException; import net.i2p.client.naming.HostTxtEntry; import net.i2p.crypto.Blinding; import net.i2p.crypto.DSAEngine; +import net.i2p.crypto.EncType; import net.i2p.crypto.KeyGenerator; +import net.i2p.crypto.KeyPair; import net.i2p.crypto.SigType; import net.i2p.util.OrderedProperties; import net.i2p.util.RandomSource; @@ -90,13 +92,14 @@ public class PrivateKeyFile { public static void main(String args[]) { int hashEffort = HASH_EFFORT; String stype = null; + String etype = null; String ttype = null; String hostname = null; String offline = null; int days = 365; int mode = 0; boolean error = false; - Getopt g = new Getopt("pkf", args, "t:nuxhse:c:a:o:d:r:"); + Getopt g = new Getopt("pkf", args, "t:nuxhse:c:a:o:d:r:p:"); int c; while ((c = g.getopt()) != -1) { switch (c) { @@ -104,6 +107,10 @@ public class PrivateKeyFile { stype = g.getOptarg(); break; + case 'p': + etype = g.getOptarg(); + break; + case 't': stype = g.getOptarg(); // fall thru... @@ -171,7 +178,21 @@ public class PrivateKeyFile { boolean exists = f.exists(); PrivateKeyFile pkf = new PrivateKeyFile(f, client); Destination d; - if (stype != null) { + if (etype != null && !exists) { + // create router.keys.dat format, no support in I2PClient + SigType type; + if (stype == null) { + type = SigType.EdDSA_SHA512_Ed25519; + } else { + type = SigType.parseSigType(stype); + if (type == null) + throw new IllegalArgumentException("Signature type " + stype + " is not supported"); + } + EncType ptype = EncType.parseEncType(etype); + if (ptype == null) + throw new IllegalArgumentException("Encryption type " + etype + " is not supported"); + d = pkf.createIfAbsent(type, ptype); + } else if (stype != null) { SigType type = SigType.parseSigType(stype); if (type == null) throw new IllegalArgumentException("Signature type " + stype + " is not supported"); @@ -319,10 +340,11 @@ public class PrivateKeyFile { " -x (changes to hidden cert)\n" + " \nother options:\n" + " -a example.i2p (generate addressbook authentication string)\n" + - " -d days (specify expiration in days of offline sig, default 365)\n" + " -c sigtype (specify sig type of destination)\n" + + " -d days (specify expiration in days of offline sig, default 365)\n" + " -e effort (specify HashCash effort instead of default " + HASH_EFFORT + ")\n" + " -o offlinedestfile (generate the online key file using the offline key file specified)\n" + + " -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" + ""); @@ -411,7 +433,7 @@ public class PrivateKeyFile { public Destination createIfAbsent() throws I2PException, IOException, DataFormatException { return createIfAbsent(I2PClient.DEFAULT_SIGTYPE); } - + /** * Create with the specified signature type if nonexistent. * @@ -424,11 +446,12 @@ public class PrivateKeyFile { if(!this.file.exists()) { OutputStream out = null; try { - out = new SecureFileOutputStream(this.file); - if (this.client != null) + if (this.client != null) { + out = new SecureFileOutputStream(this.file); this.client.createDestination(out, type); - else + } else { write(); + } } finally { if (out != null) { try { out.close(); } catch (IOException ioe) {} @@ -437,7 +460,70 @@ public class PrivateKeyFile { } return getDestination(); } - + + /** + * Create with the specified signature and encryption types if nonexistent. + * Private for now, only for router.keys.dat testing. + * + * Also reads in the file to get the privKey and signingPrivKey, + * which aren't available from I2PClient. + * + * @since 0.9.42 + */ + private Destination createIfAbsent(SigType type, EncType ptype) throws I2PException, IOException, DataFormatException { + if(!this.file.exists()) { + OutputStream out = null; + try { + if (this.client != null) { + out = new SecureFileOutputStream(this.file); + // no support for this in I2PClient, + // so we modify code from CreateRouterInfoJob.createRouterInfo() + I2PAppContext ctx = I2PAppContext.getGlobalContext(); + KeyPair keypair = ctx.keyGenerator().generatePKIKeys(ptype); + PublicKey pub = keypair.getPublic(); + PrivateKey priv = keypair.getPrivate(); + SimpleDataStructure signingKeypair[] = ctx.keyGenerator().generateSigningKeys(type); + SigningPublicKey spub = (SigningPublicKey)signingKeypair[0]; + SigningPrivateKey spriv = (SigningPrivateKey)signingKeypair[1]; + Certificate cert; + if (type != SigType.DSA_SHA1 || ptype != EncType.ELGAMAL_2048) { + // TODO long sig types + if (type.getPubkeyLen() > 128) + throw new UnsupportedOperationException("TODO"); + cert = new KeyCertificate(type, ptype); + } else { + cert = Certificate.NULL_CERT; + } + byte[] padding; + int padLen = (SigningPublicKey.KEYSIZE_BYTES - spub.length()) + + (PublicKey.KEYSIZE_BYTES - pub.length()); + if (padLen > 0) { + padding = new byte[padLen]; + ctx.random().nextBytes(padding); + } else { + padding = null; + } + pub.writeBytes(out); + if (padding != null) + out.write(padding); + spub.writeBytes(out); + cert.writeBytes(out); + priv.writeBytes(out); + spriv.writeBytes(out); + } else { + write(); + } + } catch (GeneralSecurityException gse) { + throw new RuntimeException("keygen fail", gse); + } finally { + if (out != null) { + try { out.close(); } catch (IOException ioe) {} + } + } + } + return getDestination(); + } + /** * If the destination is not set, read it in from the file. * Also sets the local privKey and signingPrivKey. diff --git a/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java b/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java index b834a1fbd4..b14997f8b5 100644 --- a/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java +++ b/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java @@ -17,6 +17,8 @@ import java.util.HashMap; import java.util.Map; import java.util.Properties; +import net.i2p.crypto.EncType; +import net.i2p.crypto.KeyPair; import net.i2p.crypto.SigType; import net.i2p.data.Base64; import net.i2p.data.Certificate; @@ -57,6 +59,7 @@ public class CreateRouterInfoJob extends JobImpl { /** TODO make everybody Ed */ private static final SigType DEFAULT_SIGTYPE = SystemVersion.isAndroid() ? SigType.DSA_SHA1 : SigType.EdDSA_SHA512_Ed25519; + private static final EncType DEFAULT_ENCTYPE = EncType.ELGAMAL_2048; CreateRouterInfoJob(RouterContext ctx, Job next) { super(ctx); @@ -96,36 +99,40 @@ public class CreateRouterInfoJob extends JobImpl { * Caller must hold Router.routerInfoFileLock. */ RouterInfo createRouterInfo() { - SigType type = getSigTypeConfig(getContext()); + RouterContext ctx = getContext(); + SigType type = getSigTypeConfig(ctx); RouterInfo info = new RouterInfo(); OutputStream fos1 = null; try { - info.setAddresses(getContext().commSystem().createAddresses()); + info.setAddresses(ctx.commSystem().createAddresses()); // not necessary, in constructor //info.setPeers(new HashSet()); - info.setPublished(getCurrentPublishDate(getContext())); - Object keypair[] = getContext().keyGenerator().generatePKIKeypair(); - PublicKey pubkey = (PublicKey)keypair[0]; - PrivateKey privkey = (PrivateKey)keypair[1]; - SimpleDataStructure signingKeypair[] = getContext().keyGenerator().generateSigningKeys(type); + info.setPublished(getCurrentPublishDate(ctx)); + // TODO + EncType etype = DEFAULT_ENCTYPE; + KeyPair keypair = ctx.keyGenerator().generatePKIKeys(etype); + PublicKey pubkey = keypair.getPublic(); + PrivateKey privkey = keypair.getPrivate(); + SimpleDataStructure signingKeypair[] = ctx.keyGenerator().generateSigningKeys(type); SigningPublicKey signingPubKey = (SigningPublicKey)signingKeypair[0]; SigningPrivateKey signingPrivKey = (SigningPrivateKey)signingKeypair[1]; RouterIdentity ident = new RouterIdentity(); - Certificate cert = createCertificate(getContext(), signingPubKey); + Certificate cert = createCertificate(ctx, signingPubKey, pubkey); ident.setCertificate(cert); ident.setPublicKey(pubkey); ident.setSigningPublicKey(signingPubKey); byte[] padding; - int padLen = SigningPublicKey.KEYSIZE_BYTES - signingPubKey.length(); + int padLen = (SigningPublicKey.KEYSIZE_BYTES - signingPubKey.length()) + + (PublicKey.KEYSIZE_BYTES - pubkey.length()); if (padLen > 0) { padding = new byte[padLen]; - getContext().random().nextBytes(padding); + ctx.random().nextBytes(padding); ident.setPadding(padding); } else { padding = null; } info.setIdentity(ident); - Properties stats = getContext().statPublisher().publishStatistics(ident.getHash()); + Properties stats = ctx.statPublisher().publishStatistics(ident.getHash()); info.setOptions(stats); info.sign(signingPrivKey); @@ -134,15 +141,15 @@ public class CreateRouterInfoJob extends JobImpl { throw new DataFormatException("RouterInfo we just built is invalid: " + info); // remove router.keys - (new File(getContext().getRouterDir(), KEYS_FILENAME)).delete(); + (new File(ctx.getRouterDir(), KEYS_FILENAME)).delete(); // write router.info - File ifile = new File(getContext().getRouterDir(), INFO_FILENAME); + File ifile = new File(ctx.getRouterDir(), INFO_FILENAME); fos1 = new BufferedOutputStream(new SecureFileOutputStream(ifile)); info.writeBytes(fos1); // write router.keys.dat - File kfile = new File(getContext().getRouterDir(), KEYS2_FILENAME); + File kfile = new File(ctx.getRouterDir(), KEYS2_FILENAME); PrivateKeyFile pkf = new PrivateKeyFile(kfile, pubkey, signingPubKey, cert, privkey, signingPrivKey, padding); pkf.write(); @@ -150,17 +157,17 @@ public class CreateRouterInfoJob extends JobImpl { // set or overwrite old random keys Map map = new HashMap(2); byte rk[] = new byte[32]; - getContext().random().nextBytes(rk); + ctx.random().nextBytes(rk); map.put(Router.PROP_IB_RANDOM_KEY, Base64.encode(rk)); - getContext().random().nextBytes(rk); + ctx.random().nextBytes(rk); map.put(Router.PROP_OB_RANDOM_KEY, Base64.encode(rk)); - getContext().router().saveConfig(map, null); + ctx.router().saveConfig(map, null); - getContext().keyManager().setKeys(pubkey, privkey, signingPubKey, signingPrivKey); + ctx.keyManager().setKeys(pubkey, privkey, signingPubKey, signingPrivKey); if (_log.shouldLog(Log.INFO)) _log.info("Router info created and stored at " + ifile.getAbsolutePath() + " with private keys stored at " + kfile.getAbsolutePath() + " [" + info + "]"); - getContext().router().eventLog().addEvent(EventLog.REKEYED, ident.calculateHash().toBase64()); + ctx.router().eventLog().addEvent(EventLog.REKEYED, ident.calculateHash().toBase64()); } catch (GeneralSecurityException gse) { _log.log(Log.CRIT, "Error building the new router information", gse); } catch (DataFormatException dfe) { @@ -211,9 +218,9 @@ public class CreateRouterInfoJob extends JobImpl { * @return the certificate for a new RouterInfo - probably a null cert. * @since 0.9.16 moved from Router */ - static Certificate createCertificate(RouterContext ctx, SigningPublicKey spk) { - if (spk.getType() != SigType.DSA_SHA1) - return new KeyCertificate(spk); + private static Certificate createCertificate(RouterContext ctx, SigningPublicKey spk, PublicKey pk) { + if (spk.getType() != SigType.DSA_SHA1 || pk.getType() != EncType.ELGAMAL_2048) + return new KeyCertificate(spk, pk); if (ctx.getBooleanProperty(Router.PROP_HIDDEN)) return new Certificate(Certificate.CERTIFICATE_TYPE_HIDDEN, null); return Certificate.NULL_CERT;