propagate from branch 'i2p.i2p' (head 79d0ad4538a0adc4ced6ac26cb725abe3d5ccee3)

to branch 'i2p.i2p.zzz.test2' (head 73032545b42f6f9caffffca08d0a8b97f5cf7e3a)
This commit is contained in:
zzz
2014-07-22 14:38:28 +00:00
28 changed files with 496 additions and 228 deletions

View File

@ -0,0 +1,45 @@
package net.i2p.client;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import net.i2p.I2PAppContext;
/**
* Bridge to Unix domain socket (or similar).
* <p/>
* This is a stub that does nothing.
* This class is replaced in the Android build.
*
* @author str4d
* @since 0.9.14
*/
public class DomainSocketFactory {
public static String I2CP_SOCKET_ADDRESS = "net.i2p.client.i2cp";
/**
* @throws UnsupportedOperationException always
*/
public DomainSocketFactory(I2PAppContext context) {
throw new UnsupportedOperationException();
}
/**
* Override in Android.
* @throws IOException
* @throws UnsupportedOperationException always
*/
public Socket createSocket(String name) throws IOException {
throw new UnsupportedOperationException();
}
/**
* Override in Android.
* @throws IOException
* @throws UnsupportedOperationException always
*/
public ServerSocket createServerSocket(String name) throws IOException {
throw new UnsupportedOperationException();
}
}

View File

@ -52,6 +52,7 @@ import net.i2p.util.LHMCache;
import net.i2p.util.Log;
import net.i2p.util.OrderedProperties;
import net.i2p.util.SimpleTimer2;
import net.i2p.util.SystemVersion;
import net.i2p.util.VersionComparator;
/**
@ -157,6 +158,12 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
protected static final String PROP_USER = "i2cp.username";
protected static final String PROP_PW = "i2cp.password";
/**
* Use Unix domain socket (or similar) to connect to a router
* @since 0.9.14
*/
protected static final String PROP_DOMAIN_SOCKET = "i2cp.domainSocket";
private static final long VERIFY_USAGE_TIME = 60*1000;
private static final long MAX_SEND_WAIT = 10*1000;
@ -279,6 +286,10 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
if (_context.isRouterContext())
// just for logging
return "[internal connection]";
else if (SystemVersion.isAndroid() &&
Boolean.parseBoolean(_options.getProperty(PROP_DOMAIN_SOCKET)))
// just for logging
return "[Domain socket connection]";
return _options.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1");
}
@ -287,7 +298,9 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
* @since 0.9.7 was in loadConfig()
*/
private int getPort() {
if (_context.isRouterContext())
if (_context.isRouterContext() ||
(SystemVersion.isAndroid() &&
Boolean.parseBoolean(_options.getProperty(PROP_DOMAIN_SOCKET))))
// just for logging
return 0;
String portNum = _options.getProperty(I2PClient.PROP_TCP_PORT, LISTEN_PORT + "");
@ -447,7 +460,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
try {
// protect w/ closeSocket()
synchronized(_stateLock) {
// If we are in the router JVM, connect using the interal queue
// If we are in the router JVM, connect using the internal queue
if (_context.isRouterContext()) {
// _socket and _writer remain null
InternalClientManager mgr = _context.internalClientManager();
@ -457,7 +470,11 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
_queue = mgr.connect();
_reader = new QueuedI2CPMessageReader(_queue, this);
} else {
if (Boolean.parseBoolean(_options.getProperty(PROP_ENABLE_SSL))) {
if (SystemVersion.isAndroid() &&
Boolean.parseBoolean(_options.getProperty(PROP_DOMAIN_SOCKET))) {
final DomainSocketFactory fact = new DomainSocketFactory(_context);
_socket = fact.createSocket(DomainSocketFactory.I2CP_SOCKET_ADDRESS);
} else if (Boolean.parseBoolean(_options.getProperty(PROP_ENABLE_SSL))) {
try {
I2PSSLSocketFactory fact = new I2PSSLSocketFactory(_context, false, "certificates/i2cp");
_socket = fact.createSocket(_hostname, _portNum);

View File

@ -19,6 +19,7 @@ import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.data.Signature;
import net.i2p.data.SigningPublicKey;
import net.i2p.util.Log;
/**
@ -34,16 +35,11 @@ public final class I2PDatagramDissector {
private final DSAEngine dsaEng = DSAEngine.getInstance();
private final SHA256Generator hashGen = SHA256Generator.getInstance();
private Hash rxHash;
private byte[] rxHash;
private Signature rxSign;
private Destination rxDest;
private final byte[] rxPayload = new byte[DGRAM_BUFSIZE];
private int rxPayloadLen;
private boolean valid;
/**
@ -56,6 +52,17 @@ public final class I2PDatagramDissector {
* Load an I2P repliable datagram into the dissector.
* Does NOT verify the signature.
*
* Format is:
* <ol>
* <li>Destination (387+ bytes)
* <li>Signature (40+ bytes, type and length as implied by signing key type in the Destination)
* <li>Payload
* </ol>
*
* For DSA_SHA1 Destinations, the signature is of the SHA-256 Hash of the payload.
*
* As of 0.9.14, for non-DSA_SHA1 Destinations, the signature is of the payload itself.
*
* @param dgram non-null I2P repliable datagram to be loaded
*
* @throws DataFormatException If there's an error in the datagram format
@ -79,14 +86,21 @@ public final class I2PDatagramDissector {
rxPayloadLen = dgStream.read(rxPayload);
// calculate the hash of the payload
this.rxHash = hashGen.calculateHash(rxPayload, 0, rxPayloadLen);
assert this.hashGen.calculateHash(this.extractPayload()).equals(this.rxHash);
if (type == SigType.DSA_SHA1) {
if (rxHash == null)
rxHash = new byte[Hash.HASH_LENGTH];
// non-caching
hashGen.calculateHash(rxPayload, 0, rxPayloadLen, rxHash, 0);
//assert this.hashGen.calculateHash(this.extractPayload()).equals(this.rxHash);
} else {
rxHash = null;
}
} catch (IOException e) {
Log log = I2PAppContext.getGlobalContext().logManager().getLog(I2PDatagramDissector.class);
log.error("Caught IOException - INCONSISTENT STATE!", e);
} catch(AssertionError e) {
Log log = I2PAppContext.getGlobalContext().logManager().getLog(I2PDatagramDissector.class);
log.error("Assertion failed!", e);
//} catch(AssertionError e) {
// Log log = I2PAppContext.getGlobalContext().logManager().getLog(I2PDatagramDissector.class);
// log.error("Assertion failed!", e);
}
//_log.debug("Datagram payload size: " + rxPayloadLen + "; content:\n"
@ -125,14 +139,16 @@ public final class I2PDatagramDissector {
* Extract the hash of the payload of an I2P repliable datagram (previously
* loaded with the loadI2PDatagram() method), verifying the datagram
* signature.
*
* As of 0.9.14, for signature types other than DSA_SHA1, this returns null.
*
* @return The hash of the payload of the I2P repliable datagram
* @throws I2PInvalidDatagramException if the signature verification fails
*/
public Hash getHash() throws I2PInvalidDatagramException {
// make sure it has a valid signature
this.verifySignature();
return this.extractHash();
return extractHash();
}
/**
@ -178,10 +194,18 @@ public final class I2PDatagramDissector {
* Extract the hash of the payload of an I2P repliable datagram (previously
* loaded with the loadI2PDatagram() method), without verifying the datagram
* signature.
*
* As of 0.9.14, for signature types other than DSA_SHA1, this returns null.
*
* @return The hash of the payload of the I2P repliable datagram
*/
public Hash extractHash() {
return this.rxHash;
if (rxHash == null)
return null;
// make a copy as we will reuse rxHash
byte[] hash = new byte[Hash.HASH_LENGTH];
System.arraycopy(rxHash, 0, hash, 0, Hash.HASH_LENGTH);
return new Hash(hash);
}
/**
@ -194,12 +218,21 @@ public final class I2PDatagramDissector {
if(this.valid)
return;
if (rxSign == null || rxSign.getData() == null || rxDest == null || rxDest.getSigningPublicKey() == null)
if (rxSign == null || rxSign.getData() == null || rxDest == null)
throw new I2PInvalidDatagramException("Datagram not yet read");
// now validate
if (!this.dsaEng.verifySignature(rxSign, rxHash.getData(), rxDest.getSigningPublicKey()))
throw new I2PInvalidDatagramException("Incorrect I2P repliable datagram signature");
SigningPublicKey spk = rxDest.getSigningPublicKey();
SigType type = spk.getType();
if (type == null)
throw new I2PInvalidDatagramException("unsupported sig type");
if (type == SigType.DSA_SHA1) {
if (!this.dsaEng.verifySignature(rxSign, rxHash, spk))
throw new I2PInvalidDatagramException("Incorrect I2P repliable datagram signature");
} else {
if (!this.dsaEng.verifySignature(rxSign, rxPayload, 0, rxPayloadLen, spk))
throw new I2PInvalidDatagramException("Incorrect I2P repliable datagram signature");
}
// set validated
this.valid = true;

View File

@ -16,8 +16,12 @@ import net.i2p.client.I2PSession;
import net.i2p.crypto.DSAEngine;
import net.i2p.crypto.SHA256Generator;
import net.i2p.data.DataFormatException;
import net.i2p.data.Hash;
import net.i2p.data.Signature;
import net.i2p.data.SigningPrivateKey;
import net.i2p.crypto.SigType;
import net.i2p.util.Log;
import net.i2p.util.SimpleByteCache;
/**
* Class for creating I2P repliable datagrams. Note that objects of this class
@ -44,9 +48,9 @@ public final class I2PDatagramMaker {
* @param session I2PSession used to send I2PDatagrams through
*/
public I2PDatagramMaker(I2PSession session) {
this();
this.setI2PDatagramMaker(session);
}
/**
* Construct a new I2PDatagramMaker that is null.
* Use setI2PDatagramMaker to set the parameters.
@ -59,22 +63,53 @@ public final class I2PDatagramMaker {
sxPrivKey = session.getPrivateKey();
sxDestBytes = session.getMyDestination().toByteArray();
}
/**
* Make a repliable I2P datagram containing the specified payload.
*
* Format is:
* <ol>
* <li>Destination (387+ bytes)
* <li>Signature (40+ bytes, type and length as implied by signing key type in the Destination)
* <li>Payload
* </ol>
*
* Maximum datagram size is 32768, so maximum payload size is 32341, or less for
* non-DSA_SHA1 destinations. Practical maximum is a few KB less due to
* ElGamal/AES overhead. 10 KB or less is recommended for best results.
*
* For DSA_SHA1 Destinations, the signature is of the SHA-256 Hash of the payload.
*
* As of 0.9.14, for non-DSA_SHA1 Destinations, the signature is of the payload itself.
*
* @param payload non-null Bytes to be contained in the I2P datagram.
* @return null on error
* @throws IllegalArgumentException if payload is too big
* @throws IllegalStateException if Destination signature type unsupported
*/
public byte[] makeI2PDatagram(byte[] payload) {
sxDGram.reset();
try {
sxDGram.write(sxDestBytes);
SigType type = sxPrivKey.getType();
if (type == null)
throw new IllegalStateException("Unsupported sig type");
dsaEng.sign(hashGen.calculateHash(payload).toByteArray(),
sxPrivKey).writeBytes(sxDGram);
Signature sig;
if (type == SigType.DSA_SHA1) {
byte[] hash = SimpleByteCache.acquire(Hash.HASH_LENGTH);
// non-caching
hashGen.calculateHash(payload, 0, payload.length, hash, 0);
sig = dsaEng.sign(hash, sxPrivKey);
SimpleByteCache.release(hash);
} else {
sig = dsaEng.sign(payload, sxPrivKey);
}
sig.writeBytes(sxDGram);
sxDGram.write(payload);
if (sxDGram.size() > DGRAM_BUFSIZE)
throw new IllegalArgumentException("Too big");
return sxDGram.toByteArray();
} catch (IOException e) {
Log log = I2PAppContext.getGlobalContext().logManager().getLog(I2PDatagramMaker.class);

View File

@ -10,6 +10,9 @@ in turn, use the {@link net.i2p.client.datagram.I2PDatagramMaker} to build a
message that can be parsed. </p>
<p>The datagram format implemented here includes
the sender's {@link net.i2p.data.Destination}, the payload, and a hash of the
payload (signed by the sender's {@link net.i2p.data.SigningPrivateKey}).</p>
the sender's {@link net.i2p.data.Destination}, the payload, and a signature
(signed by the sender's {@link net.i2p.data.SigningPrivateKey}).
For DSA_SHA1 destinations, the signature is of the SHA-256 Hash of the payload.
For other destination types, the signature is of the payload itself.
</p>
</body></html>

View File

@ -18,6 +18,7 @@ import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.util.Log;
import net.i2p.util.SecureFileOutputStream;
import net.i2p.util.SystemVersion;
/**
* Java X.509 certificate utilities, consolidated from various places.
@ -65,11 +66,18 @@ public class CertUtil {
}
/**
* Get a value out of the subject distinguished name
* Get a value out of the subject distinguished name.
*
* Warning - unsupported in Android (no javax.naming), returns null.
*
* @param type e.g. "CN"
* @return value or null if not found
*/
public static String getSubjectValue(X509Certificate cert, String type) {
if (SystemVersion.isAndroid()) {
error("Don't call this in Android", new UnsupportedOperationException("I did it"));
return null;
}
type = type.toUpperCase(Locale.US);
X500Principal p = cert.getSubjectX500Principal();
String subj = p.getName();

View File

@ -14,9 +14,11 @@ import java.security.PublicKey;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import net.i2p.util.SystemVersion;
/**
* Dumb storage in a directory for testing.
* No sanitization of filenames, unsafe.
* Simple storage of each cert in a separate file in a directory.
* Limited sanitization of filenames.
*
* @since 0.9.9
*/
@ -30,7 +32,9 @@ class DirKeyRing implements KeyRing {
/**
* Cert must be in the file (escaped keyName).crt,
* and have a CN == keyName
* and have a CN == keyName.
*
* CN check unsupported on Android.
*/
public PublicKey getKey(String keyName, String scope, SigType type)
throws GeneralSecurityException, IOException {
@ -49,14 +53,21 @@ class DirKeyRing implements KeyRing {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(fis);
cert.checkValidity();
String cn = CertUtil.getSubjectValue(cert, "CN");
if (!keyName.equals(cn))
throw new GeneralSecurityException("CN mismatch: " + cn);
if (!SystemVersion.isAndroid()) {
// getSubjectValue() unsupported on Android.
// Any cert problems will be caught in non-Android testing.
String cn = CertUtil.getSubjectValue(cert, "CN");
if (!keyName.equals(cn))
throw new GeneralSecurityException("CN mismatch: " + cn);
}
return cert.getPublicKey();
} finally {
try { if (fis != null) fis.close(); } catch (IOException foo) {}
}
}
/**
* Unimplemented, unused.
*/
public void setKey(String keyName, String scope, PublicKey key) {}
}

View File

@ -29,7 +29,7 @@ public class SessionKeyManager {
/**
* A dummy SessionKeyManager for testing or for passing to
* ElGamalAESEngine.encrypt()
* ElGamalAESEngine.decrypt()
*
* @since 0.9.14
*/
@ -37,7 +37,7 @@ public class SessionKeyManager {
/**
* A dummy SessionKeyManager for testing or for passing to
* ElGamalAESEngine.encrypt()
* ElGamalAESEngine.decrypt()
*
* @param context unused
* @since public since 0.9.14; protected before that