SAM: Cherrypick from patch in ticket #1318:

- Add SIGNATURE_TYPE support to GENERATE and CREATE
   - Don't NPE checking dest+privkeys
   - Simplify HELLO checking
   - Don't require two params in HELLO message
   - Make MIN parameter optional too
   - Version checking fixes
   - Bump version to 3.1, only visible if requested
   - Cleanups, javadocs
This commit is contained in:
zzz
2014-06-28 14:14:39 +00:00
parent f191e50b14
commit fef591412e
6 changed files with 94 additions and 53 deletions

View File

@@ -24,7 +24,7 @@ import net.i2p.util.VersionComparator;
*/
class SAMHandlerFactory {
private static final String VERSION = "3.0";
private static final String VERSION = "3.1";
/**
* Return the right SAM handler depending on the protocol version
@@ -53,29 +53,22 @@ class SAMHandlerFactory {
throw new SAMException("Unexpected error", e);
}
// Message format: HELLO VERSION MIN=v1 MAX=v2
if (tok.countTokens() != 4) {
throw new SAMException("Bad format in HELLO message");
// Message format: HELLO VERSION [MIN=v1] [MAX=v2]
if (tok.countTokens() < 2) {
throw new SAMException("Must start with HELLO VERSION");
}
if (!tok.nextToken().equals("HELLO")) {
throw new SAMException("Bad domain in HELLO message");
}
{
String opcode;
if (!(opcode = tok.nextToken()).equals("VERSION")) {
throw new SAMException("Unrecognized HELLO message opcode: '"
+ opcode + "'");
}
if (!tok.nextToken().equals("HELLO") ||
!tok.nextToken().equals("VERSION")) {
throw new SAMException("Must start with HELLO VERSION");
}
Properties props = SAMUtils.parseParams(tok);
if (props.isEmpty()) {
throw new SAMException("No parameters in HELLO VERSION message");
}
String minVer = props.getProperty("MIN");
if (minVer == null) {
throw new SAMException("Missing MIN parameter in HELLO VERSION message");
//throw new SAMException("Missing MIN parameter in HELLO VERSION message");
// MIN optional as of 0.9.14
minVer = "1";
}
String maxVer = props.getProperty("MAX");
@@ -126,18 +119,14 @@ class SAMHandlerFactory {
* @return "x.y" the best version we can use, or null on failure
*/
private static String chooseBestVersion(String minVer, String maxVer) {
if (VersionComparator.comp(VERSION, minVer) >= 0 &&
VersionComparator.comp(VERSION, maxVer) <= 0)
return VERSION;
// in VersionComparator, "3" < "3.0" so
// use comparisons carefully
if (VersionComparator.comp("3.0", minVer) >= 0) {
// Documentation said:
// In order to force protocol version 3.0, the values of $min and $max
// must be "3.0".
int maxcomp = VersionComparator.comp("3", maxVer);
if (maxcomp == 0 || maxVer.equals("3.0"))
return "3.0"; // spoof version
if (maxcomp < 0)
return VERSION;
}
if (VersionComparator.comp("3.0", minVer) >= 0 &&
VersionComparator.comp("3", maxVer) <= 0)
return "3.0";
if (VersionComparator.comp("2.0", minVer) >= 0 &&
VersionComparator.comp("2", maxVer) <= 0)
return "2.0";
@@ -147,21 +136,23 @@ class SAMHandlerFactory {
return null;
}
/* Get the major protocol version from a string */
/* Get the major protocol version from a string, or -1 */
private static int getMajor(String ver) {
if ( (ver == null) || (ver.indexOf('.') < 0) )
if (ver == null)
return -1;
int dot = ver.indexOf(".");
if (dot == 0)
return -1;
if (dot > 0)
ver = ver.substring(0, dot);
try {
String major = ver.substring(0, ver.indexOf("."));
return Integer.parseInt(major);
return Integer.parseInt(ver);
} catch (NumberFormatException e) {
return -1;
} catch (ArrayIndexOutOfBoundsException e) {
return -1;
}
}
/* Get the minor protocol version from a string */
/* Get the minor protocol version from a string, or -1 */
private static int getMinor(String ver) {
if ( (ver == null) || (ver.indexOf('.') < 0) )
return -1;

View File

@@ -20,6 +20,7 @@ import net.i2p.I2PException;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PClientFactory;
import net.i2p.client.naming.NamingService;
import net.i2p.crypto.SigType;
import net.i2p.data.Base64;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
@@ -37,16 +38,30 @@ class SAMUtils {
//private final static Log _log = new Log(SAMUtils.class);
/**
* Generate a random destination key
* Generate a random destination key using DSA_SHA1 signature type.
* Caller must close streams. Fails silently.
*
* @param priv Stream used to write the private key
* @param pub Stream used to write the public key (may be null)
* @param priv Stream used to write the destination and private keys
* @param pub Stream used to write the destination (may be null)
*/
public static void genRandomKey(OutputStream priv, OutputStream pub) {
genRandomKey(priv, pub, SigType.DSA_SHA1);
}
/**
* Generate a random destination key.
* Caller must close streams. Fails silently.
*
* @param priv Stream used to write the destination and private keys
* @param pub Stream used to write the destination (may be null)
* @param sigType what signature type
* @since 0.9.14
*/
public static void genRandomKey(OutputStream priv, OutputStream pub, SigType sigType) {
//_log.debug("Generating random keys...");
try {
I2PClient c = I2PClientFactory.createClient();
Destination d = c.createDestination(priv);
Destination d = c.createDestination(priv, sigType);
priv.flush();
if (pub != null) {
@@ -85,7 +100,10 @@ class SAMUtils {
* @return true if valid
*/
public static boolean checkPrivateDestination(String dest) {
ByteArrayInputStream destKeyStream = new ByteArrayInputStream(Base64.decode(dest));
byte[] b = Base64.decode(dest);
if (b == null || b.length < 663)
return false;
ByteArrayInputStream destKeyStream = new ByteArrayInputStream(b);
try {
Destination d = new Destination();
d.readBytes(destKeyStream);

View File

@@ -24,6 +24,7 @@ import java.util.concurrent.atomic.AtomicLong;
import net.i2p.I2PException;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PSessionException;
import net.i2p.crypto.SigType;
import net.i2p.data.Base64;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
@@ -320,7 +321,7 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece
}
SAMStreamSession newSAMStreamSession(String destKeystream, String direction, Properties props )
private SAMStreamSession newSAMStreamSession(String destKeystream, String direction, Properties props )
throws IOException, DataFormatException, SAMException
{
return new SAMStreamSession(destKeystream, direction, props, this) ;
@@ -330,16 +331,23 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece
protected boolean execDestMessage(String opcode, Properties props) {
if (opcode.equals("GENERATE")) {
if (!props.isEmpty()) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Properties specified in DEST GENERATE message");
return false;
String sigTypeStr = props.getProperty("SIGNATURE_TYPE");
SigType sigType;
if (sigTypeStr != null) {
sigType = SigType.parseSigType(sigTypeStr);
if (sigType == null) {
writeString("DEST REPLY RESULT=I2P_ERROR MESSAGE=\"SIGNATURE_TYPE " +
sigTypeStr + " unsupported\"\n");
return false;
}
} else {
sigType = SigType.DSA_SHA1;
}
ByteArrayOutputStream priv = new ByteArrayOutputStream();
ByteArrayOutputStream pub = new ByteArrayOutputStream();
ByteArrayOutputStream priv = new ByteArrayOutputStream(663);
ByteArrayOutputStream pub = new ByteArrayOutputStream(387);
SAMUtils.genRandomKey(priv, pub);
SAMUtils.genRandomKey(priv, pub, sigType);
return writeString("DEST REPLY"
+ " PUB="
+ Base64.encode(pub.toByteArray())
@@ -347,6 +355,7 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece
+ Base64.encode(priv.toByteArray())
+ "\n");
} else {
writeString("DEST REPLY RESULT=I2P_ERROR MESSAGE=\"DEST GENERATE required\"");
if (_log.shouldLog(Log.DEBUG))
_log.debug("Unrecognized DEST message opcode: \"" + opcode + "\"");
return false;

View File

@@ -28,6 +28,7 @@ import java.util.StringTokenizer;
import net.i2p.I2PException;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PSessionException;
import net.i2p.crypto.SigType;
import net.i2p.data.Base64;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
@@ -487,21 +488,32 @@ class SAMv3Handler extends SAMv1Handler
}
props.remove("DESTINATION");
if (dest.equals("TRANSIENT")) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("TRANSIENT destination requested");
ByteArrayOutputStream priv = new ByteArrayOutputStream(640);
SAMUtils.genRandomKey(priv, null);
String sigTypeStr = props.getProperty("SIGNATURE_TYPE");
SigType sigType;
if (sigTypeStr != null) {
sigType = SigType.parseSigType(sigTypeStr);
if (sigType == null) {
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"SIGNATURE_TYPE "
+ sigTypeStr + " unsupported\"\n");
}
props.remove("SIGNATURE_TYPE");
} else {
sigType = SigType.DSA_SHA1;
}
ByteArrayOutputStream priv = new ByteArrayOutputStream(663);
SAMUtils.genRandomKey(priv, null, sigType);
dest = Base64.encode(priv.toByteArray());
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Custom destination specified [" + dest + "]");
if (!SAMUtils.checkPrivateDestination(dest))
return writeString("SESSION STATUS RESULT=INVALID_KEY\n");
}
if (!SAMUtils.checkPrivateDestination(dest))
return writeString("SESSION STATUS RESULT=INVALID_KEY\n");
nick = props.getProperty("ID");
if (nick == null) {

View File

@@ -1,3 +1,14 @@
2014-06-28 zzz
* SAM:
- Support SIGNATURE_TYPE, bump to 3.1 (ticket #1318)
- Private key checking fixes (ticket #1318)
- Parameter parsing fixes (ticket #1325)
- Cleanups
2014-06-24 zzz
* Streaming; Drop the preliminary channel implementations,
as they don't work and can't ever work as designed.
2014-06-23 zzz
* Streaming:
- Bundle new socket messages for translation

View File

@@ -18,7 +18,7 @@ public class RouterVersion {
/** deprecated */
public final static String ID = "Monotone";
public final static String VERSION = CoreVersion.VERSION;
public final static long BUILD = 9;
public final static long BUILD = 10;
/** for example "-test" */
public final static String EXTRA = "";