PrivateKeyFile: New constructor with padding

Router:
 - Use eepPriv.dat format for router keys file (thx orignal)
 - Consolidate router keys readin code
 - Update killKeys file list
RouterPrivateKeyFile: New extension to add getRouterIdentity()
This commit is contained in:
zzz
2014-08-24 19:15:26 +00:00
parent 04ad7de2e1
commit 308923448b
7 changed files with 174 additions and 156 deletions

View File

@@ -1,11 +1,13 @@
package net.i2p.data;
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.security.GeneralSecurityException;
import java.util.Locale;
import java.util.Map;
@@ -24,6 +26,7 @@ import net.i2p.crypto.DSAEngine;
import net.i2p.crypto.KeyGenerator;
import net.i2p.crypto.SigType;
import net.i2p.util.RandomSource;
import net.i2p.util.SecureFileOutputStream;
/**
* This helper class reads and writes files in the
@@ -48,11 +51,11 @@ public class PrivateKeyFile {
private static final int HASH_EFFORT = VerifiedDestination.MIN_HASHCASH_EFFORT;
private final File file;
protected final File file;
private final I2PClient client;
private Destination dest;
private PrivateKey privKey;
private SigningPrivateKey signingPrivKey;
protected PrivateKey privKey;
protected SigningPrivateKey signingPrivKey;
/**
* Create a new PrivateKeyFile, or modify an existing one, with various
@@ -224,6 +227,16 @@ public class PrivateKeyFile {
*/
public PrivateKeyFile(File file, PublicKey pubkey, SigningPublicKey spubkey, Certificate cert,
PrivateKey pk, SigningPrivateKey spk) {
this(file, pubkey, spubkey, cert, pk, spk, null);
}
/**
* @param padding null OK, must be non-null if spubkey length < 128
* @throws IllegalArgumentException on mismatch of spubkey and spk types
* @since 0.9.16
*/
public PrivateKeyFile(File file, PublicKey pubkey, SigningPublicKey spubkey, Certificate cert,
PrivateKey pk, SigningPrivateKey spk, byte[] padding) {
if (spubkey.getType() != spk.getType())
throw new IllegalArgumentException("Signing key type mismatch");
this.file = file;
@@ -232,6 +245,8 @@ public class PrivateKeyFile {
this.dest.setPublicKey(pubkey);
this.dest.setSigningPublicKey(spubkey);
this.dest.setCertificate(cert);
if (padding != null)
this.dest.setPadding(padding);
this.privKey = pk;
this.signingPrivKey = spk;
}
@@ -241,9 +256,9 @@ public class PrivateKeyFile {
*/
public Destination createIfAbsent() throws I2PException, IOException, DataFormatException {
if(!this.file.exists()) {
FileOutputStream out = null;
OutputStream out = null;
try {
out = new FileOutputStream(this.file);
out = new SecureFileOutputStream(this.file);
if (this.client != null)
this.client.createDestination(out);
else
@@ -257,7 +272,10 @@ public class PrivateKeyFile {
return getDestination();
}
/** Also sets the local privKey and signingPrivKey */
/**
* If the destination is not set, read it in from the file.
* Also sets the local privKey and signingPrivKey.
*/
public Destination getDestination() throws I2PSessionException, IOException, DataFormatException {
if (dest == null) {
I2PSession s = open();
@@ -408,9 +426,9 @@ public class PrivateKeyFile {
}
public I2PSession open(Properties opts) throws I2PSessionException, IOException {
FileInputStream in = null;
InputStream in = null;
try {
in = new FileInputStream(this.file);
in = new BufferedInputStream(new FileInputStream(this.file));
I2PSession s = this.client.createSession(in, opts);
return s;
} finally {
@@ -424,13 +442,12 @@ public class PrivateKeyFile {
* Copied from I2PClientImpl.createDestination()
*/
public void write() throws IOException, DataFormatException {
FileOutputStream out = null;
OutputStream out = null;
try {
out = new FileOutputStream(this.file);
out = new SecureFileOutputStream(this.file);
this.dest.writeBytes(out);
this.privKey.writeBytes(out);
this.signingPrivKey.writeBytes(out);
out.flush();
} finally {
if (out != null) {
try { out.close(); } catch (IOException ioe) {}

View File

@@ -0,0 +1,52 @@
package net.i2p.data.router;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
import net.i2p.crypto.SigType;
import net.i2p.data.DataFormatException;
import net.i2p.data.PrivateKey;
import net.i2p.data.PrivateKeyFile;
import net.i2p.data.SigningPrivateKey;
/**
* Same format as super, simply adds a method to
* treat it as a RouterIdentity instead of a Destination.
*
* @since 0.9.16
*/
public class RouterPrivateKeyFile extends PrivateKeyFile {
public RouterPrivateKeyFile(File file) {
super(file);
}
/**
* Read it in from the file.
* Also sets the local privKey and signingPrivKey.
*/
public RouterIdentity getRouterIdentity() throws IOException, DataFormatException {
InputStream in = null;
try {
in = new BufferedInputStream(new FileInputStream(this.file));
RouterIdentity ri = new RouterIdentity();
ri.readBytes(in);
privKey = new PrivateKey();
privKey.readBytes(in);
SigType type = ri.getSigningPublicKey().getType();
if (type == null)
throw new DataFormatException("Unknown sig type");
signingPrivKey = new SigningPrivateKey(type);
signingPrivKey.readBytes(in);
return ri;
} finally {
if (in != null) {
try { in.close(); } catch (IOException ioe) {}
}
}
}
}

View File

@@ -49,10 +49,10 @@ public class KeyManager {
public final static String PROP_KEYDIR = "router.keyBackupDir";
public final static String DEFAULT_KEYDIR = "keyBackup";
private final static String KEYFILE_PRIVATE_ENC = "privateEncryption.key";
private final static String KEYFILE_PUBLIC_ENC = "publicEncryption.key";
private final static String KEYFILE_PRIVATE_SIGNING = "privateSigning.key";
private final static String KEYFILE_PUBLIC_SIGNING = "publicSigning.key";
public final static String KEYFILE_PRIVATE_ENC = "privateEncryption.key";
public final static String KEYFILE_PUBLIC_ENC = "publicEncryption.key";
public final static String KEYFILE_PRIVATE_SIGNING = "privateSigning.key";
public final static String KEYFILE_PUBLIC_SIGNING = "publicSigning.key";
public KeyManager(RouterContext context) {
_context = context;

View File

@@ -34,6 +34,7 @@ import net.i2p.data.SigningPrivateKey;
import net.i2p.data.i2np.GarlicMessage;
import net.i2p.router.message.GarlicMessageHandler;
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
import net.i2p.router.startup.CreateRouterInfoJob;
import net.i2p.router.startup.StartupJob;
import net.i2p.router.startup.WorkingDir;
import net.i2p.router.tasks.*;
@@ -679,16 +680,18 @@ public class Router implements RouterClock.ClockShiftListener {
* Ugly list of files that we need to kill if we are building a new identity
*
*/
private static final String _rebuildFiles[] = new String[] { "router.info",
"router.keys",
"netDb/my.info", // no longer used
"connectionTag.keys", // never used?
"keyBackup/privateEncryption.key",
"keyBackup/privateSigning.key",
"keyBackup/publicEncryption.key",
"keyBackup/publicSigning.key",
"sessionKeys.dat" // no longer used
};
private static final String _rebuildFiles[] = new String[] {
CreateRouterInfoJob.INFO_FILENAME,
CreateRouterInfoJob.KEYS_FILENAME,
CreateRouterInfoJob.KEYS2_FILENAME,
"netDb/my.info", // no longer used
"connectionTag.keys", // never used?
KeyManager.DEFAULT_KEYDIR + '/' + KeyManager.KEYFILE_PRIVATE_ENC,
KeyManager.DEFAULT_KEYDIR + '/' + KeyManager.KEYFILE_PUBLIC_ENC,
KeyManager.DEFAULT_KEYDIR + '/' + KeyManager.KEYFILE_PRIVATE_SIGNING,
KeyManager.DEFAULT_KEYDIR + '/' + KeyManager.KEYFILE_PUBLIC_SIGNING,
"sessionKeys.dat" // no longer used
};
public void killKeys() {
//new Exception("Clearing identity files").printStackTrace();

View File

@@ -21,6 +21,7 @@ import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.KeyCertificate;
import net.i2p.data.PrivateKey;
import net.i2p.data.PrivateKeyFile;
import net.i2p.data.PublicKey;
import net.i2p.data.router.RouterIdentity;
import net.i2p.data.router.RouterInfo;
@@ -46,13 +47,11 @@ public class CreateRouterInfoJob extends JobImpl {
private final Job _next;
public static final String INFO_FILENAME = "router.info";
static final String KEYS_FILENAME = "router.keys";
static final String KEYS2_FILENAME = "router.keys2";
public static final String KEYS_FILENAME = "router.keys";
public static final String KEYS2_FILENAME = "router.keys.dat";
private static final String PROP_ROUTER_SIGTYPE = "router.sigType";
/** TODO when changing, check isAvailable() and fallback to DSA_SHA1 */
private static final SigType DEFAULT_SIGTYPE = SigType.DSA_SHA1;
static final byte[] KEYS2_MAGIC = DataHelper.getASCII("I2Pkeys2");
static final int KEYS2_UNUSED_BYTES = 28;
CreateRouterInfoJob(RouterContext ctx, Job next) {
super(ctx);
@@ -75,20 +74,9 @@ public class CreateRouterInfoJob extends JobImpl {
* Writes 6 files: router.info (standard RI format),
* router.keys2, and 4 individual key files under keyBackup/
*
* router.keys2 file format: Note that this is NOT the
* same "eepPriv.dat" format used by the client code.
*<pre>
* - Magic "I2Pkeys2"
* - 2 byte crypto type, always 0000 for now
* - 2 byte sig type, see SigType
* - 28 bytes unused
* - Private key (256 bytes)
* - Signing Private key (20 bytes or see SigType)
* - Public key (256 bytes)
* - Random padding for Signing Public Key if less than 128 bytes
* - Signing Public key (128 bytes or see SigTpe)
* Total 660 bytes
*</pre>
* router.keys2 file format: This is the
* same "eepPriv.dat" format used by the client code,
* as documented in PrivateKeyFile.
*
* Old router.keys file format: Note that this is NOT the
* same "eepPriv.dat" format used by the client code.
@@ -106,7 +94,6 @@ public class CreateRouterInfoJob extends JobImpl {
SigType type = getSigTypeConfig(getContext());
RouterInfo info = new RouterInfo();
OutputStream fos1 = null;
OutputStream fos2 = null;
try {
info.setAddresses(getContext().commSystem().createAddresses());
Properties stats = getContext().statPublisher().publishStatistics();
@@ -151,19 +138,11 @@ public class CreateRouterInfoJob extends JobImpl {
fos1 = new BufferedOutputStream(new SecureFileOutputStream(ifile));
info.writeBytes(fos1);
// write router.keys2
// write router.keys.dat
File kfile = new File(getContext().getRouterDir(), KEYS2_FILENAME);
fos2 = new BufferedOutputStream(new SecureFileOutputStream(kfile));
fos2.write(KEYS2_MAGIC);
DataHelper.writeLong(fos2, 2, 0);
DataHelper.writeLong(fos2, 2, type.getCode());
fos2.write(new byte[KEYS2_UNUSED_BYTES]);
privkey.writeBytes(fos2);
signingPrivKey.writeBytes(fos2);
pubkey.writeBytes(fos2);
if (padding != null)
fos2.write(padding);
signingPubKey.writeBytes(fos2);
PrivateKeyFile pkf = new PrivateKeyFile(kfile, pubkey, signingPubKey, cert,
privkey, signingPrivKey, padding);
pkf.write();
getContext().keyManager().setKeys(pubkey, privkey, signingPubKey, signingPrivKey);
@@ -178,7 +157,6 @@ public class CreateRouterInfoJob extends JobImpl {
_log.log(Log.CRIT, "Error writing out the new router information", ioe);
} finally {
if (fos1 != null) try { fos1.close(); } catch (IOException ioe) {}
if (fos2 != null) try { fos2.close(); } catch (IOException ioe) {}
}
return info;
}

View File

@@ -16,13 +16,16 @@ import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
import net.i2p.crypto.SigType;
import net.i2p.data.Certificate;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey;
import net.i2p.data.router.RouterInfo;
import net.i2p.data.SigningPrivateKey;
import net.i2p.data.SigningPublicKey;
import net.i2p.data.router.RouterIdentity;
import net.i2p.data.router.RouterInfo;
import net.i2p.data.router.RouterPrivateKeyFile;
import net.i2p.router.JobImpl;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
@@ -76,7 +79,6 @@ class LoadRouterInfoJob extends JobImpl {
boolean keys2Exist = rkf2.exists();
InputStream fis1 = null;
InputStream fis2 = null;
try {
// if we have a routerinfo but no keys, things go bad in a hurry:
// CRIT ...rkdb.PublishLocalRouterInfoJob: Internal error - signing private key not known? rescheduling publish for 30s
@@ -97,33 +99,17 @@ class LoadRouterInfoJob extends JobImpl {
}
if (keys2Exist || keysExist) {
SigType stype;
if (keys2Exist) {
fis2 = new BufferedInputStream(new FileInputStream(rkf2));
// read keys2 headers
byte[] magic = new byte[CreateRouterInfoJob.KEYS2_MAGIC.length];
DataHelper.read(fis2, magic);
if (!DataHelper.eq(magic, CreateRouterInfoJob.KEYS2_MAGIC))
throw new IOException("Bad magic");
int ctype = (int) DataHelper.readLong(fis2, 2);
if (ctype != 0)
throw new IOException("Unsupported RI crypto type " + ctype);
int sstype = (int) DataHelper.readLong(fis2, 2);
stype = SigType.getByCode(sstype);
if (stype == null || !stype.isAvailable())
throw new IOException("Unsupported RI sig type " + stype);
DataHelper.skip(fis2, CreateRouterInfoJob.KEYS2_UNUSED_BYTES);
} else {
fis2 = new BufferedInputStream(new FileInputStream(rkf));
stype = SigType.DSA_SHA1;
}
KeyData kd = readKeyData(rkf, rkf2);
PublicKey pubkey = kd.routerIdentity.getPublicKey();
SigningPublicKey signingPubKey = kd.routerIdentity.getSigningPublicKey();
PrivateKey privkey = kd.privateKey;
SigningPrivateKey signingPrivKey = kd.signingPrivateKey;
SigType stype = signingPubKey.getType();
// check if the sigtype config changed
SigType cstype = CreateRouterInfoJob.getSigTypeConfig(getContext());
boolean sigTypeChanged = stype != cstype;
PrivateKey privkey = new PrivateKey();
privkey.readBytes(fis2);
if (sigTypeChanged || shouldRebuild(privkey)) {
if (sigTypeChanged)
_log.logAlways(Log.WARN, "Rebuilding RouterInfo with new signature type " + cstype);
@@ -133,24 +119,11 @@ class LoadRouterInfoJob extends JobImpl {
try { fis1.close(); } catch (IOException ioe) {}
fis1 = null;
}
try { fis2.close(); } catch (IOException ioe) {}
fis2 = null;
rif.delete();
rkf.delete();
rkf2.delete();
return;
}
SigningPrivateKey signingPrivKey = new SigningPrivateKey(stype);
signingPrivKey.readBytes(fis2);
PublicKey pubkey = new PublicKey();
pubkey.readBytes(fis2);
SigningPublicKey signingPubKey = new SigningPublicKey(stype);
int padLen = SigningPublicKey.KEYSIZE_BYTES - signingPubKey.length();
if (padLen > 0) {
// we lose the padding as keymanager doesn't store it, what to do?
DataHelper.skip(fis2, padLen);
}
signingPubKey.readBytes(fis2);
getContext().keyManager().setKeys(pubkey, privkey, signingPubKey, signingPrivKey);
}
@@ -162,10 +135,6 @@ class LoadRouterInfoJob extends JobImpl {
try { fis1.close(); } catch (IOException ioe2) {}
fis1 = null;
}
if (fis2 != null) {
try { fis2.close(); } catch (IOException ioe2) {}
fis2 = null;
}
rif.delete();
rkf.delete();
rkf2.delete();
@@ -177,16 +146,11 @@ class LoadRouterInfoJob extends JobImpl {
try { fis1.close(); } catch (IOException ioe) {}
fis1 = null;
}
if (fis2 != null) {
try { fis2.close(); } catch (IOException ioe) {}
fis2 = null;
}
rif.delete();
rkf.delete();
rkf2.delete();
} finally {
if (fis1 != null) try { fis1.close(); } catch (IOException ioe) {}
if (fis2 != null) try { fis2.close(); } catch (IOException ioe) {}
}
}
@@ -219,4 +183,55 @@ class LoadRouterInfoJob extends JobImpl {
_log.logAlways(Log.WARN, "Rebuilding RouterInfo with faster key");
return uselong != haslong;
}
/** @since 0.9.16 */
public static class KeyData {
public final RouterIdentity routerIdentity;
public final PrivateKey privateKey;
public final SigningPrivateKey signingPrivateKey;
public KeyData(RouterIdentity ri, PrivateKey pk, SigningPrivateKey spk) {
routerIdentity = ri;
privateKey = pk;
signingPrivateKey = spk;
}
}
/**
* @param rkf1 in router.keys format, tried second
* @param rkf2 in eepPriv.dat format, tried first
* @return non-null, throws IOE if neither exisits
* @since 0.9.16
*/
public static KeyData readKeyData(File rkf1, File rkf2) throws DataFormatException, IOException {
RouterIdentity ri;
PrivateKey privkey;
SigningPrivateKey signingPrivKey;
if (rkf2.exists()) {
RouterPrivateKeyFile pkf = new RouterPrivateKeyFile(rkf2);
ri = pkf.getRouterIdentity();
privkey = pkf.getPrivKey();
signingPrivKey = pkf.getSigningPrivKey();
} else {
InputStream fis = null;
try {
fis = new BufferedInputStream(new FileInputStream(rkf1));
privkey = new PrivateKey();
privkey.readBytes(fis);
signingPrivKey = new SigningPrivateKey();
signingPrivKey.readBytes(fis);
PublicKey pubkey = new PublicKey();
pubkey.readBytes(fis);
SigningPublicKey signingPubKey = new SigningPublicKey();
signingPubKey.readBytes(fis);
ri = new RouterIdentity();
ri.setPublicKey(pubkey);
ri.setSigningPublicKey(signingPubKey);
ri.setCertificate(Certificate.NULL_CERT);
} finally {
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
}
}
return new KeyData(ri, privkey, signingPrivKey);
}
}

View File

@@ -8,11 +8,8 @@ package net.i2p.router.startup;
*
*/
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.util.Properties;
@@ -29,6 +26,7 @@ import net.i2p.data.SigningPublicKey;
import net.i2p.router.JobImpl;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.startup.LoadRouterInfoJob.KeyData;
import net.i2p.util.Log;
import net.i2p.util.SecureFileOutputStream;
@@ -96,61 +94,16 @@ class RebuildRouterInfoJob extends JobImpl {
// ok, no need to rebuild a brand new identity, just update what we can
RouterInfo oldinfo = getContext().router().getRouterInfo();
if (oldinfo == null) {
info = new RouterInfo();
InputStream fis = null;
try {
SigType stype;
if (keyFile2.exists()) {
fis = new BufferedInputStream(new FileInputStream(keyFile2));
byte[] magic = new byte[CreateRouterInfoJob.KEYS2_MAGIC.length];
DataHelper.read(fis, magic);
if (!DataHelper.eq(magic, CreateRouterInfoJob.KEYS2_MAGIC))
throw new IOException("Bad magic");
int ctype = (int) DataHelper.readLong(fis, 2);
if (ctype != 0)
throw new IOException("Unsupported RI crypto type " + ctype);
int sstype = (int) DataHelper.readLong(fis, 2);
stype = SigType.getByCode(sstype);
if (stype == null || !stype.isAvailable())
throw new IOException("Unsupported RI sig type " + stype);
DataHelper.skip(fis, CreateRouterInfoJob.KEYS2_UNUSED_BYTES);
} else {
fis = new BufferedInputStream(new FileInputStream(keyFile));
stype = SigType.DSA_SHA1;
}
PrivateKey privkey = new PrivateKey();
privkey.readBytes(fis);
SigningPrivateKey signingPrivKey = new SigningPrivateKey(stype);
signingPrivKey.readBytes(fis);
PublicKey pubkey = new PublicKey();
pubkey.readBytes(fis);
SigningPublicKey signingPubKey = new SigningPublicKey(stype);
byte[] padding;
int padLen = SigningPublicKey.KEYSIZE_BYTES - signingPubKey.length();
if (padLen > 0) {
padding = new byte[padLen];
DataHelper.read(fis, padding);
} else {
padding = null;
}
signingPubKey.readBytes(fis);
RouterIdentity ident = new RouterIdentity();
Certificate cert = CreateRouterInfoJob.createCertificate(getContext(), signingPubKey);
ident.setCertificate(cert);
ident.setPublicKey(pubkey);
ident.setSigningPublicKey(signingPubKey);
if (padding != null)
ident.setPadding(padding);
info.setIdentity(ident);
KeyData kd = LoadRouterInfoJob.readKeyData(keyFile, keyFile2);
info = new RouterInfo();
info.setIdentity(kd.routerIdentity);
} catch (Exception e) {
_log.log(Log.CRIT, "Error reading in the key data from " + keyFile.getAbsolutePath(), e);
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
fis = null;
keyFile.delete();
keyFile2.delete();
rebuildRouterInfo(alreadyRunning);
return;
} finally {
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
}
} else {
// Make a new RI from the old identity, or else info.setAddresses() will throw an ISE