use of net.i2p.data.router.RouterInfo in project i2p.i2p by i2p.
the class CreateRouterInfoJob method createRouterInfo.
/**
* Writes 6 files: router.info (standard RI format),
* router.keys.dat, and 4 individual key files under keyBackup/
*
* router.keys.dat 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.
*<pre>
* - Private key (256 bytes)
* - Signing Private key (20 bytes)
* - Public key (256 bytes)
* - Signing Public key (128 bytes)
* Total 660 bytes
*</pre>
*
* Caller must hold Router.routerInfoFileLock.
*/
RouterInfo createRouterInfo() {
SigType type = getSigTypeConfig(getContext());
RouterInfo info = new RouterInfo();
OutputStream fos1 = null;
try {
info.setAddresses(getContext().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);
SigningPublicKey signingPubKey = (SigningPublicKey) signingKeypair[0];
SigningPrivateKey signingPrivKey = (SigningPrivateKey) signingKeypair[1];
RouterIdentity ident = new RouterIdentity();
Certificate cert = createCertificate(getContext(), signingPubKey);
ident.setCertificate(cert);
ident.setPublicKey(pubkey);
ident.setSigningPublicKey(signingPubKey);
byte[] padding;
int padLen = SigningPublicKey.KEYSIZE_BYTES - signingPubKey.length();
if (padLen > 0) {
padding = new byte[padLen];
getContext().random().nextBytes(padding);
ident.setPadding(padding);
} else {
padding = null;
}
info.setIdentity(ident);
Properties stats = getContext().statPublisher().publishStatistics(ident.getHash());
info.setOptions(stats);
info.sign(signingPrivKey);
if (!info.isValid())
throw new DataFormatException("RouterInfo we just built is invalid: " + info);
// remove router.keys
(new File(getContext().getRouterDir(), KEYS_FILENAME)).delete();
// write router.info
File ifile = new File(getContext().getRouterDir(), INFO_FILENAME);
fos1 = new BufferedOutputStream(new SecureFileOutputStream(ifile));
info.writeBytes(fos1);
// write router.keys.dat
File kfile = new File(getContext().getRouterDir(), KEYS2_FILENAME);
PrivateKeyFile pkf = new PrivateKeyFile(kfile, pubkey, signingPubKey, cert, privkey, signingPrivKey, padding);
pkf.write();
// set or overwrite old random keys
Map<String, String> map = new HashMap<String, String>(2);
byte[] rk = new byte[32];
getContext().random().nextBytes(rk);
map.put(Router.PROP_IB_RANDOM_KEY, Base64.encode(rk));
getContext().random().nextBytes(rk);
map.put(Router.PROP_OB_RANDOM_KEY, Base64.encode(rk));
getContext().router().saveConfig(map, null);
getContext().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());
} catch (GeneralSecurityException gse) {
_log.log(Log.CRIT, "Error building the new router information", gse);
} catch (DataFormatException dfe) {
_log.log(Log.CRIT, "Error building the new router information", dfe);
} catch (IOException ioe) {
_log.log(Log.CRIT, "Error writing out the new router information", ioe);
} finally {
if (fos1 != null)
try {
fos1.close();
} catch (IOException ioe) {
}
}
return info;
}
use of net.i2p.data.router.RouterInfo in project i2p.i2p by i2p.
the class LoadRouterInfoJob method loadRouterInfo.
/**
* Loads router.info and either router.keys.dat or router.keys.
*
* See CreateRouterInfoJob for file formats
*/
private void loadRouterInfo() {
RouterInfo info = null;
File rif = new File(getContext().getRouterDir(), CreateRouterInfoJob.INFO_FILENAME);
boolean infoExists = rif.exists();
File rkf = new File(getContext().getRouterDir(), CreateRouterInfoJob.KEYS_FILENAME);
boolean keysExist = rkf.exists();
File rkf2 = new File(getContext().getRouterDir(), CreateRouterInfoJob.KEYS2_FILENAME);
boolean keys2Exist = rkf2.exists();
InputStream fis1 = null;
try {
// so pretend the RI isn't there if there is no keyfile
if (infoExists && (keys2Exist || keysExist)) {
fis1 = new BufferedInputStream(new FileInputStream(rif));
info = new RouterInfo();
info.readBytes(fis1);
// Catch this here before it all gets worse
if (!info.isValid())
throw new DataFormatException("Our RouterInfo has a bad signature");
if (_log.shouldLog(Log.DEBUG))
_log.debug("Reading in routerInfo from " + rif.getAbsolutePath() + " and it has " + info.getAddresses().size() + " addresses");
// don't reuse if family name changed
if (DataHelper.eq(info.getOption(FamilyKeyCrypto.OPT_NAME), getContext().getProperty(FamilyKeyCrypto.PROP_FAMILY_NAME))) {
_us = info;
} else {
_log.logAlways(Log.WARN, "NetDb family name changed");
}
}
if (keys2Exist || keysExist) {
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;
if (sigTypeChanged && getContext().getProperty(CreateRouterInfoJob.PROP_ROUTER_SIGTYPE) == null) {
// TODO reduce to ~3 (i.e. increase probability) in future release
if (getContext().random().nextInt(4) > 0) {
sigTypeChanged = false;
if (_log.shouldWarn())
_log.warn("Deferring RI rekey from " + stype + " to " + cstype);
}
}
if (sigTypeChanged || shouldRebuild(privkey)) {
if (_us != null) {
Hash h = _us.getIdentity().getHash();
_log.logAlways(Log.WARN, "Deleting old router identity " + h.toBase64());
// the netdb hasn't started yet, but we want to delete the RI
File f = PersistentDataStore.getRouterInfoFile(getContext(), h);
f.delete();
// the banlist can be called at any time
getContext().banlist().banlistRouterForever(h, "Our previous identity");
_us = null;
}
if (sigTypeChanged)
_log.logAlways(Log.WARN, "Rebuilding RouterInfo with new signature type " + cstype);
// windows... close before deleting
if (fis1 != null) {
try {
fis1.close();
} catch (IOException ioe) {
}
fis1 = null;
}
rif.delete();
rkf.delete();
rkf2.delete();
return;
}
getContext().keyManager().setKeys(pubkey, privkey, signingPubKey, signingPrivKey);
}
} catch (IOException ioe) {
_log.log(Log.CRIT, "Error reading the router info from " + rif.getAbsolutePath() + " and the keys from " + rkf.getAbsolutePath(), ioe);
_us = null;
// windows... close before deleting
if (fis1 != null) {
try {
fis1.close();
} catch (IOException ioe2) {
}
fis1 = null;
}
rif.delete();
rkf.delete();
rkf2.delete();
} catch (DataFormatException dfe) {
_log.log(Log.CRIT, "Corrupt router info or keys at " + rif.getAbsolutePath() + " / " + rkf.getAbsolutePath(), dfe);
_us = null;
// windows... close before deleting
if (fis1 != null) {
try {
fis1.close();
} catch (IOException ioe) {
}
fis1 = null;
}
rif.delete();
rkf.delete();
rkf2.delete();
} finally {
if (fis1 != null)
try {
fis1.close();
} catch (IOException ioe) {
}
}
}
use of net.i2p.data.router.RouterInfo in project i2p.i2p by i2p.
the class InboundMessageDistributor method distribute.
public void distribute(I2NPMessage msg, Hash target, TunnelId tunnel) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("IBMD for " + _client + " to " + target + " / " + tunnel + " : " + msg);
// allow messages on client tunnels even after client disconnection, as it may
// include e.g. test messages, etc. DataMessages will be dropped anyway
/*
if ( (_client != null) && (!_context.clientManager().isLocal(_client)) ) {
if (_log.shouldLog(Log.INFO))
_log.info("Not distributing a message, as it came down a client's tunnel ("
+ _client.toBase64() + ") after the client disconnected: " + msg);
return;
}
*/
int type = msg.getType();
// if the message came down a client tunnel:
if (_client != null) {
switch(type) {
case DatabaseSearchReplyMessage.MESSAGE_TYPE:
/**
**
* DatabaseSearchReplyMessage orig = (DatabaseSearchReplyMessage) msg;
* if (orig.getNumReplies() > 0) {
* if (_log.shouldLog(Log.INFO))
* _log.info("Removing replies from a DSRM down a tunnel for " + _client + ": " + msg);
* DatabaseSearchReplyMessage newMsg = new DatabaseSearchReplyMessage(_context);
* newMsg.setFromHash(orig.getFromHash());
* newMsg.setSearchKey(orig.getSearchKey());
* msg = newMsg;
* }
***
*/
break;
case DatabaseStoreMessage.MESSAGE_TYPE:
DatabaseStoreMessage dsm = (DatabaseStoreMessage) msg;
if (dsm.getEntry().getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO) {
// Todo: if peer was ff and RI is not ff, queue for exploration in netdb (but that isn't part of the facade now)
if (_log.shouldLog(Log.WARN))
_log.warn("Dropping DSM down a tunnel for " + _client + ": " + msg);
// Handle safely by just updating the caps table, after doing basic validation
Hash key = dsm.getKey();
if (_context.routerHash().equals(key))
return;
RouterInfo ri = (RouterInfo) dsm.getEntry();
if (!key.equals(ri.getIdentity().getHash()))
return;
if (!ri.isValid())
return;
RouterInfo oldri = _context.netDb().lookupRouterInfoLocally(key);
// only update if RI is newer and non-ff
if (oldri != null && oldri.getPublished() < ri.getPublished() && !FloodfillNetworkDatabaseFacade.isFloodfill(ri)) {
if (_log.shouldLog(Log.WARN))
_log.warn("Updating caps for RI " + key + " from \"" + oldri.getCapabilities() + "\" to \"" + ri.getCapabilities() + '"');
_context.peerManager().setCapabilities(key, ri.getCapabilities());
}
return;
} else if (dsm.getReplyToken() != 0) {
_context.statManager().addRateData("tunnel.dropDangerousClientTunnelMessage", 1, type);
_log.error("Dropping LS DSM w/ reply token down a tunnel for " + _client + ": " + msg);
return;
} else {
// allow DSM of our own key (used by FloodfillVerifyStoreJob)
// or other keys (used by IterativeSearchJob)
// as long as there's no reply token (we will never set a reply token but an attacker might)
((LeaseSet) dsm.getEntry()).setReceivedAsReply();
}
break;
case DeliveryStatusMessage.MESSAGE_TYPE:
case GarlicMessage.MESSAGE_TYPE:
case TunnelBuildReplyMessage.MESSAGE_TYPE:
case VariableTunnelBuildReplyMessage.MESSAGE_TYPE:
// these are safe, handled below
break;
default:
// drop it, since we should only get the above message types down
// client tunnels
_context.statManager().addRateData("tunnel.dropDangerousClientTunnelMessage", 1, type);
_log.error("Dropped dangerous message down a tunnel for " + _client + ": " + msg, new Exception("cause"));
return;
}
// switch
} else {
// expl. tunnel
switch(type) {
case DatabaseStoreMessage.MESSAGE_TYPE:
DatabaseStoreMessage dsm = (DatabaseStoreMessage) msg;
if (dsm.getReplyToken() != 0) {
_context.statManager().addRateData("tunnel.dropDangerousExplTunnelMessage", 1, type);
_log.error("Dropping DSM w/ reply token down a expl. tunnel: " + msg);
return;
}
if (dsm.getEntry().getType() == DatabaseEntry.KEY_TYPE_LEASESET)
((LeaseSet) dsm.getEntry()).setReceivedAsReply();
break;
case DatabaseSearchReplyMessage.MESSAGE_TYPE:
case DeliveryStatusMessage.MESSAGE_TYPE:
case GarlicMessage.MESSAGE_TYPE:
case TunnelBuildReplyMessage.MESSAGE_TYPE:
case VariableTunnelBuildReplyMessage.MESSAGE_TYPE:
// these are safe, handled below
break;
default:
_context.statManager().addRateData("tunnel.dropDangerousExplTunnelMessage", 1, type);
_log.error("Dropped dangerous message down expl tunnel: " + msg, new Exception("cause"));
return;
}
// switch
}
if ((target == null) || ((tunnel == null) && (_context.routerHash().equals(target)))) {
// make sure we don't honor any remote requests directly (garlic instructions, etc)
if (type == GarlicMessage.MESSAGE_TYPE) {
// in case we're looking for replies to a garlic message (cough load tests cough)
_context.inNetMessagePool().handleReplies(msg);
// if (_log.shouldLog(Log.DEBUG))
// _log.debug("received garlic message in the tunnel, parse it out");
_receiver.receive((GarlicMessage) msg);
} else {
if (_log.shouldLog(Log.INFO))
_log.info("distributing inbound tunnel message into our inNetMessagePool: " + msg);
_context.inNetMessagePool().add(msg, null, null);
}
/**
**** latency measuring attack?
* } else if (_context.routerHash().equals(target)) {
* // the want to send it to a tunnel, except we are also that tunnel's gateway
* // dispatch it directly
* if (_log.shouldLog(Log.INFO))
* _log.info("distributing inbound tunnel message back out, except we are the gateway");
* TunnelGatewayMessage gw = new TunnelGatewayMessage(_context);
* gw.setMessage(msg);
* gw.setTunnelId(tunnel);
* gw.setMessageExpiration(_context.clock().now()+10*1000);
* gw.setUniqueId(_context.random().nextLong(I2NPMessage.MAX_ID_VALUE));
* _context.tunnelDispatcher().dispatch(gw);
*****
*/
} else {
// ok, they want us to send it remotely, but that'd bust our anonymity,
// so we send it out a tunnel first
// TODO use the OCMOSJ cache to pick OB tunnel we are already using?
TunnelInfo out = _context.tunnelManager().selectOutboundTunnel(_client, target);
if (out == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("no outbound tunnel to send the client message for " + _client + ": " + msg);
return;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("distributing IB tunnel msg type " + type + " back out " + out + " targetting " + target);
TunnelId outId = out.getSendTunnelId(0);
if (outId == null) {
if (_log.shouldLog(Log.ERROR))
_log.error("strange? outbound tunnel has no outboundId? " + out + " failing to distribute " + msg);
return;
}
long exp = _context.clock().now() + 20 * 1000;
if (msg.getMessageExpiration() < exp)
msg.setMessageExpiration(exp);
_context.tunnelDispatcher().dispatchOutbound(msg, outId, tunnel, target);
}
}
use of net.i2p.data.router.RouterInfo in project i2p.i2p by i2p.
the class PeerTestManager method receiveFromAliceAsBob.
/**
* The PeerTest message came from the peer referenced in the message (or there wasn't
* any info in the message), plus we are not acting as Charlie (so we've got to be Bob).
*
* testInfo IP/port ignored
* @param state null if new
*/
private void receiveFromAliceAsBob(RemoteHostId from, UDPPacketReader.PeerTestReader testInfo, long nonce, PeerTestState state) {
// we are Bob, so pick a (potentially) Charlie and send Charlie Alice's info
PeerState charlie;
RouterInfo charlieInfo = null;
int sz = from.getIP().length;
boolean isIPv6 = sz == 16;
if (state == null) {
// pick a new charlie
// if (from.getIP().length != 4) {
// if (_log.shouldLog(Log.WARN))
// _log.warn("PeerTest over IPv6 from Alice as Bob? " + from);
// return;
// }
charlie = _transport.pickTestPeer(CHARLIE, isIPv6, from);
} else {
charlie = _transport.getPeerState(new RemoteHostId(state.getCharlieIP().getAddress(), state.getCharliePort()));
}
if (charlie == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to pick a charlie (no peer), IPv6? " + isIPv6);
return;
}
charlieInfo = _context.netDb().lookupRouterInfoLocally(charlie.getRemotePeer());
if (charlieInfo == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to pick a charlie (no RI), IPv6? " + isIPv6);
return;
}
// TODO should only do most of this if isNew
InetAddress aliceIP = null;
SessionKey aliceIntroKey = null;
try {
aliceIP = InetAddress.getByAddress(from.getIP());
aliceIntroKey = new SessionKey(new byte[SessionKey.KEYSIZE_BYTES]);
testInfo.readIntroKey(aliceIntroKey.getData(), 0);
RouterAddress raddr = _transport.getTargetAddress(charlieInfo);
if (raddr == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to pick a charlie (no addr), IPv6? " + isIPv6);
return;
}
UDPAddress addr = new UDPAddress(raddr);
byte[] ikey = addr.getIntroKey();
if (ikey == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to pick a charlie (no ikey), IPv6? " + isIPv6);
return;
}
SessionKey charlieIntroKey = new SessionKey(ikey);
// UDPPacket packet = _packetBuilder.buildPeerTestToAlice(aliceIP, from.getPort(), aliceIntroKey, charlieIntroKey, nonce);
// _transport.send(packet);
long now = _context.clock().now();
boolean isNew = false;
if (state == null) {
isNew = true;
state = new PeerTestState(BOB, isIPv6, nonce, now);
} else {
if (state.getReceiveAliceTime() > now - (RESEND_TIMEOUT / 2)) {
if (_log.shouldLog(Log.WARN))
_log.warn("Too soon, not retransmitting: " + state);
return;
}
}
state.setAliceIP(aliceIP);
state.setAlicePort(from.getPort());
state.setAliceIntroKey(aliceIntroKey);
state.setCharlieIP(charlie.getRemoteIPAddress());
state.setCharliePort(charlie.getRemotePort());
state.setCharlieIntroKey(charlieIntroKey);
state.setLastSendTime(now);
state.setReceiveAliceTime(now);
if (state.incrementPacketsRelayed() > MAX_RELAYED_PER_TEST_BOB) {
if (_log.shouldLog(Log.WARN))
_log.warn("Too many, not retransmitting: " + state);
return;
}
if (isNew) {
_activeTests.put(Long.valueOf(nonce), state);
_context.simpleTimer2().addEvent(new RemoveTest(nonce), MAX_CHARLIE_LIFETIME);
}
UDPPacket packet = _packetBuilder.buildPeerTestToCharlie(aliceIP, from.getPort(), aliceIntroKey, nonce, charlie.getRemoteIPAddress(), charlie.getRemotePort(), charlie.getCurrentCipherKey(), charlie.getCurrentMACKey());
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive from Alice: " + state);
_transport.send(packet);
} catch (UnknownHostException uhe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to build the aliceIP from " + from, uhe);
_context.statManager().addRateData("udp.testBadIP", 1);
}
}
use of net.i2p.data.router.RouterInfo in project i2p.i2p by i2p.
the class TunnelParticipant method dispatch.
public void dispatch(TunnelDataMessage msg, Hash recvFrom) {
boolean ok = false;
if (_processor != null)
ok = _processor.process(msg.getData(), 0, msg.getData().length, recvFrom);
else if (_inboundEndpointProcessor != null)
ok = _inboundEndpointProcessor.retrievePreprocessedData(msg.getData(), 0, msg.getData().length, recvFrom);
if (!ok) {
if (_log.shouldLog(Log.WARN))
_log.warn("Failed to dispatch " + msg + ": processor=" + _processor + " inboundEndpoint=" + _inboundEndpointProcessor);
if (_config != null)
_config.incrementProcessedMessages();
_context.statManager().addRateData("tunnel.corruptMessage", 1, 1);
return;
}
if ((_config != null) && (_config.getSendTo() != null)) {
_config.incrementProcessedMessages();
RouterInfo ri = _nextHopCache;
if (ri == null)
ri = _context.netDb().lookupRouterInfoLocally(_config.getSendTo());
if (ri != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Send off to nextHop directly (" + _config.getSendTo() + " for " + msg);
send(_config, msg, ri);
// see comments below
// if (_config != null)
// incrementThroughput(_config.getReceiveFrom());
} else {
// It should be rare to forget the router info for the next peer
if (_log.shouldLog(Log.WARN))
_log.warn("Lookup the nextHop (" + _config.getSendTo() + " for " + msg);
_context.netDb().lookupRouterInfo(_config.getSendTo(), new SendJob(_context, msg), new TimeoutJob(_context, msg), MAX_LOOKUP_TIME);
}
} else {
_inboundEndpointProcessor.getConfig().incrementProcessedMessages();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive fragment: on " + _config + ": " + msg);
_handler.receiveTunnelMessage(msg.getData(), 0, msg.getData().length);
}
}
Aggregations