package net.i2p.router;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.i2p.crypto.SigType;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataStructure;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey;
import net.i2p.data.SigningPrivateKey;
import net.i2p.data.SigningPublicKey;
import net.i2p.router.startup.CreateRouterInfoJob;
import net.i2p.util.Log;
import net.i2p.util.SecureDirectory;
import net.i2p.util.SecureFileOutputStream;
/**
* Maintain all of the key pairs for the router.
* Router keys are written to files in a backup directory.
* LeaseSet keys are not written to files.
*
*/
public class KeyManager {
private final Log _log;
private final RouterContext _context;
private PrivateKey _privateKey;
private PublicKey _publicKey;
private SigningPrivateKey _signingPrivateKey;
private SigningPublicKey _signingPublicKey;
private final Map<Hash, LeaseSetKeys> _leaseSetKeys; // Destination --> LeaseSetKeys
public final static String PROP_KEYDIR = "router.keyBackupDir";
public final static String DEFAULT_KEYDIR = "keyBackup";
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;
_log = _context.logManager().getLog(KeyManager.class);
_leaseSetKeys = new ConcurrentHashMap<Hash, LeaseSetKeys>();
}
/**
* Read keys in from disk, blocking
*
* @deprecated we never read keys in anymore
*/
@Deprecated
public void startup() {
// run inline so keys are loaded immediately
(new SynchronizeKeysJob()).runJob();
}
/**
* Configure the router's keys.
* @since 0.9.4 replace individual setters
*/
public void setKeys(PublicKey key1, PrivateKey key2, SigningPublicKey key3, SigningPrivateKey key4) {
synchronized(this) {
_publicKey = key1;
_privateKey = key2;
_signingPublicKey = key3;
_signingPrivateKey = key4;
}
queueWrite();
}
/**
* Router key
* @return will be null on error or before startup() or setKeys() is called
*/
public synchronized PrivateKey getPrivateKey() { return _privateKey; }
/**
* Router key
* @return will be null on error or before startup() or setKeys() is called
*/
public synchronized PublicKey getPublicKey() { return _publicKey; }
/**
* Router key
* @return will be null on error or before startup() or setKeys() is called
*/
public synchronized SigningPrivateKey getSigningPrivateKey() { return _signingPrivateKey; }
/**
* Router key
* @return will be null on error or before startup() or setKeys() is called
*/
public synchronized SigningPublicKey getSigningPublicKey() { return _signingPublicKey; }
/** client */
public void registerKeys(Destination dest, SigningPrivateKey leaseRevocationPrivateKey, PrivateKey endpointDecryptionKey) {
if (_log.shouldLog(Log.INFO))
_log.info("Registering keys for destination " + dest.calculateHash().toBase64());
LeaseSetKeys keys = new LeaseSetKeys(dest, leaseRevocationPrivateKey, endpointDecryptionKey);
_leaseSetKeys.put(dest.calculateHash(), keys);
}
/**
* Read/Write the router keys from/to disk
*/
private void queueWrite() {
_context.jobQueue().addJob(new SynchronizeKeysJob());
}
/** client */
public LeaseSetKeys unregisterKeys(Destination dest) {
if (_log.shouldLog(Log.INFO))
_log.info("Unregistering keys for destination " + dest.calculateHash().toBase64());
return _leaseSetKeys.remove(dest.calculateHash());
}
/** client */
public LeaseSetKeys getKeys(Destination dest) {
return getKeys(dest.calculateHash());
}
/** client */
public LeaseSetKeys getKeys(Hash dest) {
return _leaseSetKeys.get(dest);
}
/**
* Read/Write the 4 files in keyBackup/
* As of 0.9.4 this is run on-demand only, there's no need to
* periodically sync.
* Actually, there's little need for this at all.
* If router.keys is corrupt, we should just make a new router identity,
* there's no real reason to try so hard to recover our old keys.
*/
private class SynchronizeKeysJob extends JobImpl {
public SynchronizeKeysJob() {
super(KeyManager.this._context);
}
public void runJob() {
String keyDir = getContext().getProperty(PROP_KEYDIR, DEFAULT_KEYDIR);
File dir = new SecureDirectory(getContext().getRouterDir(), keyDir);
if (!dir.exists())
dir.mkdirs();
if (dir.exists() && dir.isDirectory() && dir.canRead() && dir.canWrite()) {
synchronized(KeyManager.this) {
syncKeys(dir);
}
} else {
_log.log(Log.CRIT, "Unable to synchronize keys in " + keyDir + " - permissions problem?");
}
}
private void syncKeys(File keyDir) {
syncPrivateKey(keyDir);
syncPublicKey(keyDir);
SigType type = CreateRouterInfoJob.getSigTypeConfig(getContext());
syncSigningKey(keyDir, type);
syncVerificationKey(keyDir, type);
}
private void syncPrivateKey(File keyDir) {
DataStructure ds;
File keyFile = new File(keyDir, KEYFILE_PRIVATE_ENC);
boolean exists = (_privateKey != null);
if (exists)
ds = _privateKey;
else
ds = new PrivateKey();
DataStructure readin = syncKey(keyFile, ds, exists);
if (readin != null && !exists)
_privateKey = (PrivateKey) readin;
}
private void syncPublicKey(File keyDir) {
DataStructure ds;
File keyFile = new File(keyDir, KEYFILE_PUBLIC_ENC);
boolean exists = (_publicKey != null);
if (exists)
ds = _publicKey;
else
ds = new PublicKey();
DataStructure readin = syncKey(keyFile, ds, exists);
if (readin != null && !exists)
_publicKey = (PublicKey) readin;
}
/**
* @param type the SigType to expect on read-in, ignored on write
*/
private void syncSigningKey(File keyDir, SigType type) {
DataStructure ds;
File keyFile = new File(keyDir, KEYFILE_PRIVATE_SIGNING);
boolean exists = (_signingPrivateKey != null);
if (exists)
ds = _signingPrivateKey;
else
ds = new SigningPrivateKey(type);
DataStructure readin = syncKey(keyFile, ds, exists);
if (readin != null && !exists)
_signingPrivateKey = (SigningPrivateKey) readin;
}
/**
* @param type the SigType to expect on read-in, ignored on write
*/
private void syncVerificationKey(File keyDir, SigType type) {
DataStructure ds;
File keyFile = new File(keyDir, KEYFILE_PUBLIC_SIGNING);
boolean exists = (_signingPublicKey != null);
if (exists)
ds = _signingPublicKey;
else
ds = new SigningPublicKey(type);
DataStructure readin = syncKey(keyFile, ds, exists);
if (readin != null && !exists)
_signingPublicKey = (SigningPublicKey) readin;
}
/**
* @param param non-null, filled-in if exists is true, or without data if exists is false
* @param exists write to file if true, read from file if false
* @return structure or null on read error
*/
private DataStructure syncKey(File keyFile, DataStructure structure, boolean exists) {
OutputStream out = null;
InputStream in = null;
try {
if (exists) {
out = new BufferedOutputStream(new SecureFileOutputStream(keyFile));
structure.writeBytes(out);
return structure;
} else {
if (keyFile.exists()) {
in = new BufferedInputStream(new FileInputStream(keyFile));
structure.readBytes(in);
return structure;
} else {
// we don't have it, and its not on disk. oh well.
return null;
}
}
} catch (IOException ioe) {
_log.error("Error syncing the structure to " + keyFile.getAbsolutePath(), ioe);
} catch (DataFormatException dfe) {
_log.error("Error syncing the structure with " + keyFile.getAbsolutePath(), dfe);
} finally {
if (out != null) try { out.close(); } catch (IOException ioe) {}
if (in != null) try { in.close(); } catch (IOException ioe) {}
}
if (exists)
return structure;
else
return null;
}
public String getName() { return "Synchronize Keys to Disk"; }
}
}