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

View File

@@ -20,6 +20,7 @@ import net.i2p.I2PException;
import net.i2p.client.I2PClient; import net.i2p.client.I2PClient;
import net.i2p.client.I2PClientFactory; import net.i2p.client.I2PClientFactory;
import net.i2p.client.naming.NamingService; import net.i2p.client.naming.NamingService;
import net.i2p.crypto.SigType;
import net.i2p.data.Base64; import net.i2p.data.Base64;
import net.i2p.data.DataFormatException; import net.i2p.data.DataFormatException;
import net.i2p.data.Destination; import net.i2p.data.Destination;
@@ -37,16 +38,30 @@ class SAMUtils {
//private final static Log _log = new Log(SAMUtils.class); //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 priv Stream used to write the destination and private keys
* @param pub Stream used to write the public key (may be null) * @param pub Stream used to write the destination (may be null)
*/ */
public static void genRandomKey(OutputStream priv, OutputStream pub) { 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..."); //_log.debug("Generating random keys...");
try { try {
I2PClient c = I2PClientFactory.createClient(); I2PClient c = I2PClientFactory.createClient();
Destination d = c.createDestination(priv); Destination d = c.createDestination(priv, sigType);
priv.flush(); priv.flush();
if (pub != null) { if (pub != null) {
@@ -85,7 +100,10 @@ class SAMUtils {
* @return true if valid * @return true if valid
*/ */
public static boolean checkPrivateDestination(String dest) { 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 { try {
Destination d = new Destination(); Destination d = new Destination();
d.readBytes(destKeyStream); d.readBytes(destKeyStream);

View File

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

View File

@@ -28,6 +28,7 @@ import java.util.StringTokenizer;
import net.i2p.I2PException; import net.i2p.I2PException;
import net.i2p.client.I2PClient; import net.i2p.client.I2PClient;
import net.i2p.client.I2PSessionException; import net.i2p.client.I2PSessionException;
import net.i2p.crypto.SigType;
import net.i2p.data.Base64; import net.i2p.data.Base64;
import net.i2p.data.DataFormatException; import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
@@ -487,21 +488,32 @@ class SAMv3Handler extends SAMv1Handler
} }
props.remove("DESTINATION"); props.remove("DESTINATION");
if (dest.equals("TRANSIENT")) { if (dest.equals("TRANSIENT")) {
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))
_log.debug("TRANSIENT destination requested"); _log.debug("TRANSIENT destination requested");
ByteArrayOutputStream priv = new ByteArrayOutputStream(640); String sigTypeStr = props.getProperty("SIGNATURE_TYPE");
SAMUtils.genRandomKey(priv, null); 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()); dest = Base64.encode(priv.toByteArray());
} else { } else {
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))
_log.debug("Custom destination specified [" + dest + "]"); _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"); nick = props.getProperty("ID");
if (nick == null) { 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 2014-06-23 zzz
* Streaming: * Streaming:
- Bundle new socket messages for translation - Bundle new socket messages for translation

View File

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