forked from I2P_Developers/i2p.i2p
propagate from branch 'i2p.i2p.zzz.test2' (head c182b371bc28158dd47262b89e5bd0cdda29e07b)
to branch 'i2p.i2p' (head 4fc776f7b76b028bb890affccfdcfbefbb932c58)
This commit is contained in:
@ -119,15 +119,16 @@ public class BOB implements Runnable, ClientApp {
|
||||
public final static String PROP_BOB_HOST = "BOB.host";
|
||||
public final static String PROP_CFG_VER = "BOB.CFG.VER";
|
||||
|
||||
/** unused when started via the ClientApp interface */
|
||||
private static BOB _bob;
|
||||
|
||||
private NamedDB database;
|
||||
private Properties props = new Properties();
|
||||
private AtomicBoolean spin = new AtomicBoolean(true);
|
||||
private final NamedDB database;
|
||||
private final Properties props = new Properties();
|
||||
private final AtomicBoolean spin = new AtomicBoolean(true);
|
||||
private static final String P_RUNNING = "RUNNING";
|
||||
private static final String P_STARTING = "STARTING";
|
||||
private static final String P_STOPPING = "STOPPING";
|
||||
private AtomicBoolean lock = new AtomicBoolean(false);
|
||||
private final AtomicBoolean lock = new AtomicBoolean(false);
|
||||
// no longer used.
|
||||
// private static int maxConnections = 0;
|
||||
|
||||
@ -143,8 +144,9 @@ public class BOB implements Runnable, ClientApp {
|
||||
* Stop BOB gracefully
|
||||
* @deprecated unused
|
||||
*/
|
||||
public static void stop() {
|
||||
_bob.shutdown(null);
|
||||
public synchronized static void stop() {
|
||||
if (_bob != null)
|
||||
_bob.shutdown(null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -189,7 +191,7 @@ public class BOB implements Runnable, ClientApp {
|
||||
*
|
||||
* @param args
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
public synchronized static void main(String[] args) {
|
||||
try {
|
||||
_bob = new BOB(I2PAppContext.getGlobalContext(), null, args);
|
||||
_bob.startup();
|
||||
|
@ -43,7 +43,9 @@ class PluginUpdateHandler implements Checker, Updater {
|
||||
|
||||
Properties props = PluginStarter.pluginProperties(_context, appName);
|
||||
String oldVersion = props.getProperty("version");
|
||||
String xpi2pURL = props.getProperty("updateURL");
|
||||
String xpi2pURL = props.getProperty("updateURL.su3");
|
||||
if (xpi2pURL == null)
|
||||
xpi2pURL = props.getProperty("updateURL");
|
||||
List<URI> updateSources = null;
|
||||
if (xpi2pURL != null) {
|
||||
try {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.i2p.router.update;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.IllegalArgumentException;
|
||||
import java.net.URI;
|
||||
@ -9,6 +10,7 @@ import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.CoreVersion;
|
||||
import net.i2p.crypto.SU3File;
|
||||
import net.i2p.crypto.TrustedUpdate;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
@ -121,7 +123,6 @@ class PluginUpdateRunner extends UpdateRunner {
|
||||
|
||||
@Override
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
|
||||
boolean update = false;
|
||||
updateStatus("<b>" + _("Plugin downloaded") + "</b>");
|
||||
File f = new File(_updateFile);
|
||||
File appDir = new SecureDirectory(_context.getConfigDir(), PLUGIN_DIR);
|
||||
@ -130,7 +131,43 @@ class PluginUpdateRunner extends UpdateRunner {
|
||||
statusDone("<b>" + _("Cannot create plugin directory {0}", appDir.getAbsolutePath()) + "</b>");
|
||||
return;
|
||||
}
|
||||
boolean isSU3;
|
||||
try {
|
||||
isSU3 = isSU3File(f);
|
||||
} catch (IOException ioe) {
|
||||
f.delete();
|
||||
statusDone("<b>" + ioe + "</b>");
|
||||
return;
|
||||
}
|
||||
if (isSU3)
|
||||
processSU3(f, appDir, url);
|
||||
else
|
||||
processSUD(f, appDir, url);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.15
|
||||
* @return if SU3
|
||||
*/
|
||||
private static boolean isSU3File(File f) throws IOException {
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(f);
|
||||
for (int i = 0; i < SU3File.MAGIC.length(); i++) {
|
||||
if (fis.read() != SU3File.MAGIC.charAt(i))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.15
|
||||
* @return success
|
||||
*/
|
||||
private void processSUD(File f, File appDir, String url) {
|
||||
TrustedUpdate up = new TrustedUpdate(_context);
|
||||
File to = new File(_context.getTempDir(), "tmp" + _context.random().nextInt() + ZIP);
|
||||
// extract to a zip file whether the sig is good or not, so we can get the properties file
|
||||
@ -141,27 +178,9 @@ class PluginUpdateRunner extends UpdateRunner {
|
||||
to.delete();
|
||||
return;
|
||||
}
|
||||
File tempDir = new File(_context.getTempDir(), "tmp" + _context.random().nextInt() + "-unzip");
|
||||
if (!FileUtil.extractZip(to, tempDir, Log.ERROR)) {
|
||||
f.delete();
|
||||
to.delete();
|
||||
FileUtil.rmdir(tempDir, false);
|
||||
statusDone("<b>" + _("Plugin from {0} is corrupt", url) + "</b>");
|
||||
Properties props = getPluginConfig(f, to, url);
|
||||
if (props == null)
|
||||
return;
|
||||
}
|
||||
File installProps = new File(tempDir, "plugin.config");
|
||||
Properties props = new OrderedProperties();
|
||||
try {
|
||||
DataHelper.loadProps(props, installProps);
|
||||
} catch (IOException ioe) {
|
||||
f.delete();
|
||||
to.delete();
|
||||
FileUtil.rmdir(tempDir, false);
|
||||
statusDone("<b>" + _("Plugin from {0} does not contain the required configuration file", url) + "</b>");
|
||||
return;
|
||||
}
|
||||
// we don't need this anymore, we will unzip again
|
||||
FileUtil.rmdir(tempDir, false);
|
||||
|
||||
// ok, now we check sigs and deal with a bad sig
|
||||
String pubkey = props.getProperty("key");
|
||||
@ -251,7 +270,90 @@ class PluginUpdateRunner extends UpdateRunner {
|
||||
|
||||
String sudVersion = TrustedUpdate.getVersionString(f);
|
||||
f.delete();
|
||||
processFinal(to, appDir, url, props, sudVersion, pubkey, signer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.15
|
||||
*/
|
||||
private void processSU3(File f, File appDir, String url) {
|
||||
SU3File su3 = new SU3File(_context, f);
|
||||
File to = new File(_context.getTempDir(), "tmp" + _context.random().nextInt() + ZIP);
|
||||
String sudVersion;
|
||||
String signingKeyName;
|
||||
try {
|
||||
su3.verifyAndMigrate(to);
|
||||
if (su3.getFileType() != SU3File.TYPE_ZIP)
|
||||
throw new IOException("bad file type");
|
||||
if (su3.getContentType() != SU3File.CONTENT_PLUGIN)
|
||||
throw new IOException("bad content type");
|
||||
sudVersion = su3.getVersionString();
|
||||
signingKeyName = su3.getSignerString();
|
||||
} catch (IOException ioe) {
|
||||
statusDone("<b>" + ioe + ' ' + _("from {0}", url) + " </b>");
|
||||
f.delete();
|
||||
to.delete();
|
||||
return;
|
||||
}
|
||||
Properties props = getPluginConfig(f, to, url);
|
||||
if (props == null)
|
||||
return;
|
||||
String signer = props.getProperty("signer");
|
||||
if (signer == null || signer.length() <= 0) {
|
||||
f.delete();
|
||||
to.delete();
|
||||
statusDone("<b>" + _("Plugin from {0} contains an invalid key", url) + "</b>");
|
||||
return;
|
||||
}
|
||||
if (!signer.equals(signingKeyName)) {
|
||||
f.delete();
|
||||
to.delete();
|
||||
if (signingKeyName == null)
|
||||
_log.error("Failed to verify plugin signature, corrupt plugin or bad signature, signed by: " + signer);
|
||||
else
|
||||
// shouldn't happen
|
||||
_log.error("Plugin signer \"" + signer + "\" does not match new signer in plugin.config file \"" + signingKeyName + "\"");
|
||||
statusDone("<b>" + _("Plugin signature verification of {0} failed", url) + "</b>");
|
||||
return;
|
||||
}
|
||||
processFinal(to, appDir, url, props, sudVersion, null, signer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.15
|
||||
* @return null on error
|
||||
*/
|
||||
private Properties getPluginConfig(File f, File to, String url) {
|
||||
File tempDir = new File(_context.getTempDir(), "tmp" + _context.random().nextInt() + "-unzip");
|
||||
if (!FileUtil.extractZip(to, tempDir, Log.ERROR)) {
|
||||
f.delete();
|
||||
to.delete();
|
||||
FileUtil.rmdir(tempDir, false);
|
||||
statusDone("<b>" + _("Plugin from {0} is corrupt", url) + "</b>");
|
||||
return null;
|
||||
}
|
||||
File installProps = new File(tempDir, "plugin.config");
|
||||
Properties props = new OrderedProperties();
|
||||
try {
|
||||
DataHelper.loadProps(props, installProps);
|
||||
} catch (IOException ioe) {
|
||||
f.delete();
|
||||
to.delete();
|
||||
statusDone("<b>" + _("Plugin from {0} does not contain the required configuration file", url) + "</b>");
|
||||
return null;
|
||||
} finally {
|
||||
// we don't need this anymore, we will unzip again
|
||||
FileUtil.rmdir(tempDir, false);
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pubkey null OK for su3
|
||||
* @since 0.9.15
|
||||
*/
|
||||
private void processFinal(File to, File appDir, String url, Properties props, String sudVersion, String pubkey, String signer) {
|
||||
boolean update = false;
|
||||
String appName = props.getProperty("name");
|
||||
String version = props.getProperty("version");
|
||||
if (appName == null || version == null || appName.length() <= 0 || version.length() <= 0 ||
|
||||
@ -302,14 +404,13 @@ class PluginUpdateRunner extends UpdateRunner {
|
||||
DataHelper.loadProps(oldProps, oldPropFile);
|
||||
} catch (IOException ioe) {
|
||||
to.delete();
|
||||
FileUtil.rmdir(tempDir, false);
|
||||
statusDone("<b>" + _("Installed plugin does not contain the required configuration file", url) + "</b>");
|
||||
return;
|
||||
}
|
||||
String oldPubkey = oldProps.getProperty("key");
|
||||
String oldKeyName = oldProps.getProperty("signer");
|
||||
String oldAppName = oldProps.getProperty("name");
|
||||
if ((!pubkey.equals(oldPubkey)) || (!signer.equals(oldKeyName)) || (!appName.equals(oldAppName))) {
|
||||
if ((pubkey != null && !pubkey.equals(oldPubkey)) || (!signer.equals(oldKeyName)) || (!appName.equals(oldAppName))) {
|
||||
to.delete();
|
||||
statusDone("<b>" + _("Signature of downloaded plugin does not match installed plugin") + "</b>");
|
||||
return;
|
||||
|
@ -390,7 +390,9 @@ public class ConfigClientsHandler extends FormHandler {
|
||||
|
||||
private void updatePlugin(String app) {
|
||||
Properties props = PluginStarter.pluginProperties(_context, app);
|
||||
String url = props.getProperty("updateURL");
|
||||
String url = props.getProperty("updateURL.su3");
|
||||
if (url == null)
|
||||
url = props.getProperty("updateURL");
|
||||
if (url == null) {
|
||||
addFormError(_("No update URL specified for {0}",app));
|
||||
return;
|
||||
|
@ -248,7 +248,9 @@ public class ConfigClientsHelper extends HelperBase {
|
||||
desc.append("<tr><td>")
|
||||
.append("<a href=\"").append(s).append("\">").append(_("Website")).append("</a><td> ");
|
||||
}
|
||||
String updateURL = stripHTML(appProps, "updateURL");
|
||||
String updateURL = stripHTML(appProps, "updateURL.su3");
|
||||
if (updateURL == null)
|
||||
updateURL = stripHTML(appProps, "updateURL");
|
||||
if (updateURL != null) {
|
||||
desc.append("<tr><td>")
|
||||
.append("<a href=\"").append(updateURL).append("\">").append(_("Update link")).append("</a><td> ");
|
||||
|
@ -298,9 +298,9 @@ public class ConfigNetHelper extends HelperBase {
|
||||
StringBuilder buf = new StringBuilder(256);
|
||||
buf.append("<select style=\"text-align: right !important;\" name=\"sharePercentage\">\n");
|
||||
boolean found = false;
|
||||
for (int i = 30; i <= 110; i += 10) {
|
||||
for (int i = 100; i >= -10; i -= 10) {
|
||||
int val = i;
|
||||
if (i == 110) {
|
||||
if (i == -10) {
|
||||
if (found)
|
||||
break;
|
||||
else
|
||||
|
@ -161,6 +161,7 @@ public class SAMBridge implements Runnable, ClientApp {
|
||||
*
|
||||
* @param name name of the destination
|
||||
* @return null if the name does not exist, or if it is improperly formatted
|
||||
* @deprecated unused
|
||||
*/
|
||||
public Destination getDestination(String name) {
|
||||
synchronized (nameToPrivKeys) {
|
||||
|
@ -12,6 +12,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.client.datagram.I2PDatagramDissector;
|
||||
import net.i2p.client.datagram.I2PDatagramMaker;
|
||||
@ -88,7 +89,9 @@ class SAMDatagramSession extends SAMMessageSession {
|
||||
synchronized (dgramMaker) {
|
||||
dgram = dgramMaker.makeI2PDatagram(data);
|
||||
}
|
||||
return sendBytesThroughMessageSession(dest, dgram);
|
||||
// TODO pass ports through
|
||||
return sendBytesThroughMessageSession(dest, dgram, I2PSession.PROTO_DATAGRAM,
|
||||
I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED);
|
||||
}
|
||||
|
||||
protected void messageReceived(byte[] msg) {
|
||||
|
@ -13,6 +13,7 @@ import java.nio.channels.SocketChannel;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
@ -54,7 +55,7 @@ abstract class SAMHandler implements Runnable {
|
||||
*/
|
||||
protected SAMHandler(SocketChannel s,
|
||||
int verMajor, int verMinor, Properties i2cpProps) throws IOException {
|
||||
_log = new Log(getClass());
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(getClass());
|
||||
socket = s;
|
||||
|
||||
this.verMajor = verMajor;
|
||||
|
@ -9,12 +9,13 @@ package net.i2p.sam;
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.VersionComparator;
|
||||
@ -26,6 +27,8 @@ class SAMHandlerFactory {
|
||||
|
||||
private static final String VERSION = "3.1";
|
||||
|
||||
private static final int HELLO_TIMEOUT = 60*1000;
|
||||
|
||||
/**
|
||||
* Return the right SAM handler depending on the protocol version
|
||||
* required by the client.
|
||||
@ -36,17 +39,21 @@ class SAMHandlerFactory {
|
||||
* @return A SAM protocol handler, or null if the client closed before the handshake
|
||||
*/
|
||||
public static SAMHandler createSAMHandler(SocketChannel s, Properties i2cpProps) throws SAMException {
|
||||
String line;
|
||||
StringTokenizer tok;
|
||||
Log log = new Log(SAMHandlerFactory.class);
|
||||
Log log = I2PAppContext.getGlobalContext().logManager().getLog(SAMHandlerFactory.class);
|
||||
|
||||
try {
|
||||
line = DataHelper.readLine(s.socket().getInputStream());
|
||||
Socket sock = s.socket();
|
||||
sock.setSoTimeout(HELLO_TIMEOUT);
|
||||
String line = DataHelper.readLine(sock.getInputStream());
|
||||
sock.setSoTimeout(0);
|
||||
if (line == null) {
|
||||
log.debug("Connection closed by client");
|
||||
return null;
|
||||
}
|
||||
tok = new StringTokenizer(line.trim(), " ");
|
||||
} catch (SocketTimeoutException e) {
|
||||
throw new SAMException("Timeout waiting for HELLO VERSION", e);
|
||||
} catch (IOException e) {
|
||||
throw new SAMException("Error reading from socket", e);
|
||||
} catch (Exception e) {
|
||||
|
@ -13,6 +13,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.client.I2PSession;
|
||||
@ -33,10 +34,8 @@ import net.i2p.util.Log;
|
||||
abstract class SAMMessageSession {
|
||||
|
||||
protected final Log _log;
|
||||
|
||||
private I2PSession session = null;
|
||||
|
||||
private SAMMessageSessionHandler handler = null;
|
||||
private I2PSession session;
|
||||
private SAMMessageSessionHandler handler;
|
||||
|
||||
/**
|
||||
* Initialize a new SAM message-based session.
|
||||
@ -48,7 +47,7 @@ abstract class SAMMessageSession {
|
||||
* @throws I2PSessionException
|
||||
*/
|
||||
protected SAMMessageSession(String dest, Properties props) throws IOException, DataFormatException, I2PSessionException {
|
||||
_log = new Log(getClass());
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(getClass());
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(Base64.decode(dest));
|
||||
initSAMMessageSession(bais, props);
|
||||
}
|
||||
@ -103,11 +102,15 @@ abstract class SAMMessageSession {
|
||||
*
|
||||
* @param dest Destination
|
||||
* @param data Bytes to be sent
|
||||
* @param proto I2CP protocol
|
||||
* @param fromPort I2CP from port
|
||||
* @param toPort I2CP to port
|
||||
*
|
||||
* @return True if the data was sent, false otherwise
|
||||
* @throws DataFormatException
|
||||
* @throws DataFormatException on unknown / bad dest
|
||||
*/
|
||||
protected boolean sendBytesThroughMessageSession(String dest, byte[] data) throws DataFormatException {
|
||||
protected boolean sendBytesThroughMessageSession(String dest, byte[] data,
|
||||
int proto, int fromPort, int toPort) throws DataFormatException {
|
||||
Destination d = SAMUtils.getDest(dest);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
@ -115,7 +118,7 @@ abstract class SAMMessageSession {
|
||||
}
|
||||
|
||||
try {
|
||||
return session.sendMessage(d, data);
|
||||
return session.sendMessage(d, data, proto, fromPort, toPort);
|
||||
} catch (I2PSessionException e) {
|
||||
_log.error("I2PSessionException while sending data", e);
|
||||
return false;
|
||||
|
@ -12,6 +12,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.util.Log;
|
||||
@ -73,7 +74,9 @@ class SAMRawSession extends SAMMessageSession {
|
||||
public boolean sendBytes(String dest, byte[] data) throws DataFormatException {
|
||||
if (data.length > RAW_SIZE_MAX)
|
||||
throw new DataFormatException("Data size limit exceeded (" + data.length + ")");
|
||||
return sendBytesThroughMessageSession(dest, data);
|
||||
// TODO pass ports through
|
||||
return sendBytesThroughMessageSession(dest, data, I2PSession.PROTO_DATAGRAM_RAW,
|
||||
I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED);
|
||||
}
|
||||
|
||||
protected void messageReceived(byte[] msg) {
|
||||
|
@ -25,6 +25,7 @@ import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
@ -107,9 +108,9 @@ class SAMStreamSession {
|
||||
public SAMStreamSession(InputStream destStream, String dir,
|
||||
Properties props, SAMStreamReceiver recv) throws IOException, DataFormatException, SAMException {
|
||||
this.recv = recv;
|
||||
_log = new Log(getClass());
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(getClass());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("SAM STREAM session instantiated");
|
||||
_log.debug("SAM STREAM session instantiated");
|
||||
|
||||
Properties allprops = (Properties) System.getProperties().clone();
|
||||
allprops.putAll(props);
|
||||
|
@ -123,26 +123,12 @@ class SAMUtils {
|
||||
* Resolved the specified hostname.
|
||||
*
|
||||
* @param name Hostname to be resolved
|
||||
* @param pubKey A stream to write the Destination public key (may be null)
|
||||
*
|
||||
* @return the Destination for the specified hostname, or null if not found
|
||||
*/
|
||||
public static Destination lookupHost(String name, OutputStream pubKey) {
|
||||
private static Destination lookupHost(String name) {
|
||||
NamingService ns = I2PAppContext.getGlobalContext().namingService();
|
||||
Destination dest = ns.lookup(name);
|
||||
|
||||
if ((pubKey != null) && (dest != null)) {
|
||||
try {
|
||||
dest.writeBytes(pubKey);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
} catch (DataFormatException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
@ -151,20 +137,26 @@ class SAMUtils {
|
||||
*
|
||||
* @param s Hostname or key to be resolved
|
||||
*
|
||||
* @return the Destination for the specified hostname, or null if not found
|
||||
* @return the Destination for the specified hostname, non-null
|
||||
* @throws DataFormatException on bad Base 64 or name not found
|
||||
*/
|
||||
public static Destination getDest(String s) throws DataFormatException
|
||||
{
|
||||
Destination d = new Destination() ;
|
||||
try {
|
||||
d.fromBase64(s);
|
||||
} catch (DataFormatException e) {
|
||||
d = lookupHost(s, null);
|
||||
if ( d==null ) {
|
||||
throw e ;
|
||||
}
|
||||
}
|
||||
return d ;
|
||||
// NamingService caches b64 so just use it for everything
|
||||
// TODO: Add a static local cache here so SAM doesn't flush the
|
||||
// NamingService cache
|
||||
Destination d = lookupHost(s);
|
||||
if (d == null) {
|
||||
String msg;
|
||||
if (s.length() >= 516)
|
||||
msg = "Bad Base64 dest: ";
|
||||
else if (s.length() == 60 && s.endsWith(".b32.i2p"))
|
||||
msg = "Lease set not found: ";
|
||||
else
|
||||
msg = "Host name not found: ";
|
||||
throw new DataFormatException(msg + s);
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -246,7 +246,7 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece
|
||||
destKeystream = bridge.getKeystream(dest);
|
||||
if (destKeystream == null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Custom destination specified [" + dest + "] but it isnt know, creating a new one");
|
||||
_log.debug("Custom destination specified [" + dest + "] but it isn't known, creating a new one");
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(640);
|
||||
SAMUtils.genRandomKey(baos, null);
|
||||
destKeystream = Base64.encode(baos.toByteArray());
|
||||
@ -284,7 +284,7 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece
|
||||
if (!dir.equals("CREATE") && !dir.equals("RECEIVE")
|
||||
&& !dir.equals("BOTH")) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Unknow DIRECTION parameter value: [" + dir + "]");
|
||||
_log.debug("Unknown DIRECTION parameter value: [" + dir + "]");
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unknown DIRECTION parameter\"\n");
|
||||
}
|
||||
props.remove("DIRECTION");
|
||||
|
@ -25,6 +25,7 @@ import java.util.Properties;
|
||||
import java.util.HashMap;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
@ -159,7 +160,13 @@ class SAMv3Handler extends SAMv1Handler
|
||||
ByteBuffer outBuf = ByteBuffer.wrap(new byte[inBuf.remaining()]);
|
||||
outBuf.put(inBuf);
|
||||
outBuf.flip();
|
||||
new I2PAppThread(new MessageDispatcher(outBuf.array()), "MessageDispatcher").start();
|
||||
// A new thread for every message is wildly inefficient...
|
||||
//new I2PAppThread(new MessageDispatcher(outBuf.array()), "MessageDispatcher").start();
|
||||
// inline
|
||||
// Even though we could be sending messages through multiple sessions,
|
||||
// that isn't a common use case, and blocking should be rare.
|
||||
// Inside router context, I2CP drops on overflow.
|
||||
(new MessageDispatcher(outBuf.array())).run();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -194,9 +201,15 @@ class SAMv3Handler extends SAMv1Handler
|
||||
SessionRecord rec = sSessionsHash.get(nick);
|
||||
if (rec!=null) {
|
||||
rec.getHandler().session.sendBytes(dest,data);
|
||||
} else {
|
||||
Log log = I2PAppContext.getGlobalContext().logManager().getLog(SAMv3Handler.class);
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn("Dropping datagram, no session for " + nick);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// FIXME log? throw?
|
||||
Log log = I2PAppContext.getGlobalContext().logManager().getLog(SAMv3Handler.class);
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn("Error handling datagram", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,6 @@ import net.i2p.crypto.HMACGenerator;
|
||||
import net.i2p.crypto.KeyGenerator;
|
||||
import net.i2p.crypto.SHA256Generator;
|
||||
import net.i2p.crypto.SessionKeyManager;
|
||||
import net.i2p.crypto.TransientSessionKeyManager;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.RoutingKeyGenerator;
|
||||
import net.i2p.internal.InternalClientManager;
|
||||
@ -76,7 +75,7 @@ public class I2PAppContext {
|
||||
protected final I2PProperties _overrideProps;
|
||||
|
||||
private StatManager _statManager;
|
||||
private SessionKeyManager _sessionKeyManager;
|
||||
protected SessionKeyManager _sessionKeyManager;
|
||||
private NamingService _namingService;
|
||||
private ElGamalEngine _elGamalEngine;
|
||||
private ElGamalAESEngine _elGamalAESEngine;
|
||||
@ -96,7 +95,7 @@ public class I2PAppContext {
|
||||
private SimpleTimer2 _simpleTimer2;
|
||||
private final PortMapper _portMapper;
|
||||
private volatile boolean _statManagerInitialized;
|
||||
private volatile boolean _sessionKeyManagerInitialized;
|
||||
protected volatile boolean _sessionKeyManagerInitialized;
|
||||
private volatile boolean _namingServiceInitialized;
|
||||
private volatile boolean _elGamalEngineInitialized;
|
||||
private volatile boolean _elGamalAESEngineInitialized;
|
||||
@ -599,6 +598,9 @@ public class I2PAppContext {
|
||||
* For client crypto within the router,
|
||||
* use RouterContext.clientManager.getClientSessionKeyManager(dest)
|
||||
*
|
||||
* As of 0.9.15, this returns a dummy SessionKeyManager in I2PAppContext.
|
||||
* The dummy SKM does NOT handle session tags.
|
||||
* Overridden in RouterContext to return the full TransientSessionKeyManager.
|
||||
*/
|
||||
public SessionKeyManager sessionKeyManager() {
|
||||
if (!_sessionKeyManagerInitialized)
|
||||
@ -606,11 +608,11 @@ public class I2PAppContext {
|
||||
return _sessionKeyManager;
|
||||
}
|
||||
|
||||
private void initializeSessionKeyManager() {
|
||||
protected void initializeSessionKeyManager() {
|
||||
synchronized (_lock3) {
|
||||
if (_sessionKeyManager == null)
|
||||
//_sessionKeyManager = new PersistentSessionKeyManager(this);
|
||||
_sessionKeyManager = new TransientSessionKeyManager(this);
|
||||
_sessionKeyManager = new SessionKeyManager(this);
|
||||
_sessionKeyManagerInitialized = true;
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ import java.security.GeneralSecurityException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
@ -49,29 +51,36 @@ public class SU3File {
|
||||
private int _versionLength;
|
||||
private String _signer;
|
||||
private int _signerLength;
|
||||
private int _fileType = -1;
|
||||
private ContentType _contentType;
|
||||
private long _contentLength;
|
||||
private PublicKey _signerPubkey;
|
||||
private boolean _headerVerified;
|
||||
private SigType _sigType;
|
||||
private boolean _verifySignature = true;
|
||||
private File _certFile;
|
||||
|
||||
private static final byte[] MAGIC = DataHelper.getUTF8("I2Psu3");
|
||||
public static final String MAGIC = "I2Psu3";
|
||||
private static final byte[] MAGIC_BYTES = DataHelper.getASCII(MAGIC);
|
||||
private static final int FILE_VERSION = 0;
|
||||
private static final int MIN_VERSION_BYTES = 16;
|
||||
private static final int VERSION_OFFSET = 40; // Signature.SIGNATURE_BYTES; avoid early ctx init
|
||||
|
||||
private static final int TYPE_ZIP = 0;
|
||||
public static final int TYPE_ZIP = 0;
|
||||
public static final int TYPE_XML = 1;
|
||||
|
||||
public static final int CONTENT_UNKNOWN = 0;
|
||||
public static final int CONTENT_ROUTER = 1;
|
||||
public static final int CONTENT_PLUGIN = 2;
|
||||
public static final int CONTENT_RESEED = 3;
|
||||
public static final int CONTENT_NEWS = 4;
|
||||
|
||||
private enum ContentType {
|
||||
UNKNOWN(CONTENT_UNKNOWN, "unknown"),
|
||||
ROUTER(CONTENT_ROUTER, "router"),
|
||||
PLUGIN(CONTENT_PLUGIN, "plugin"),
|
||||
RESEED(CONTENT_RESEED, "reseed")
|
||||
RESEED(CONTENT_RESEED, "reseed"),
|
||||
NEWS(CONTENT_NEWS, "news")
|
||||
;
|
||||
|
||||
private final int code;
|
||||
@ -125,17 +134,44 @@ public class SU3File {
|
||||
_file = file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the signature be verified? Default true
|
||||
* @since 0.9.15
|
||||
*/
|
||||
public void setVerifySignature(boolean shouldVerify) {
|
||||
_verifySignature = shouldVerify;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this X.509 cert file for verification instead of $I2P/certificates/content_type/foo_at_mail.i2p
|
||||
* @since 0.9.15
|
||||
*/
|
||||
private void setPublicKeyCertificate(File certFile) {
|
||||
_certFile = certFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* This does not check the signature, but it will fail if the signer is unknown,
|
||||
* unless setVerifySignature(false) has been called.
|
||||
*/
|
||||
public String getVersionString() throws IOException {
|
||||
verifyHeader();
|
||||
return _version;
|
||||
}
|
||||
|
||||
/**
|
||||
* This does not check the signature, but it will fail if the signer is unknown,
|
||||
* unless setVerifySignature(false) has been called.
|
||||
*/
|
||||
public String getSignerString() throws IOException {
|
||||
verifyHeader();
|
||||
return _signer;
|
||||
}
|
||||
|
||||
/**
|
||||
* This does not check the signature, but it will fail if the signer is unknown,
|
||||
* unless setVerifySignature(false) has been called.
|
||||
*
|
||||
* @return null if unknown
|
||||
* @since 0.9.9
|
||||
*/
|
||||
@ -145,6 +181,9 @@ public class SU3File {
|
||||
}
|
||||
|
||||
/**
|
||||
* This does not check the signature, but it will fail if the signer is unknown,
|
||||
* unless setVerifySignature(false) has been called.
|
||||
*
|
||||
* @return -1 if unknown
|
||||
* @since 0.9.9
|
||||
*/
|
||||
@ -154,6 +193,21 @@ public class SU3File {
|
||||
}
|
||||
|
||||
/**
|
||||
* This does not check the signature, but it will fail if the signer is unknown,
|
||||
* unless setVerifySignature(false) has been called.
|
||||
*
|
||||
* @return -1 if unknown
|
||||
* @since 0.9.15
|
||||
*/
|
||||
public int getFileType() throws IOException {
|
||||
verifyHeader();
|
||||
return _fileType;
|
||||
}
|
||||
|
||||
/**
|
||||
* This does not check the signature, but it will fail if the signer is unknown,
|
||||
* unless setVerifySignature(false) has been called.
|
||||
*
|
||||
* Throws IOE if verify vails.
|
||||
*/
|
||||
public void verifyHeader() throws IOException {
|
||||
@ -176,9 +230,9 @@ public class SU3File {
|
||||
* Throws if verify vails.
|
||||
*/
|
||||
private void verifyHeader(InputStream in) throws IOException, DataFormatException {
|
||||
byte[] magic = new byte[MAGIC.length];
|
||||
byte[] magic = new byte[MAGIC_BYTES.length];
|
||||
DataHelper.read(in, magic);
|
||||
if (!DataHelper.eq(magic, MAGIC))
|
||||
if (!DataHelper.eq(magic, MAGIC_BYTES))
|
||||
throw new IOException("Not an su3 file");
|
||||
skip(in, 1);
|
||||
int foo = in.read();
|
||||
@ -204,9 +258,9 @@ public class SU3File {
|
||||
if (_contentLength <= 0)
|
||||
throw new IOException("bad content length");
|
||||
skip(in, 1);
|
||||
foo = in.read();
|
||||
if (foo != TYPE_ZIP)
|
||||
throw new IOException("bad type");
|
||||
_fileType = in.read();
|
||||
if (_fileType != TYPE_ZIP && _fileType != TYPE_XML)
|
||||
throw new IOException("bad file type");
|
||||
skip(in, 1);
|
||||
int cType = in.read();
|
||||
_contentType = BY_CODE.get(Integer.valueOf(cType));
|
||||
@ -231,17 +285,22 @@ public class SU3File {
|
||||
throw new EOFException();
|
||||
_signer = DataHelper.getUTF8(data);
|
||||
|
||||
KeyRing ring = new DirKeyRing(new File(_context.getBaseDir(), "certificates"));
|
||||
try {
|
||||
_signerPubkey = ring.getKey(_signer, _contentType.getName(), _sigType);
|
||||
} catch (GeneralSecurityException gse) {
|
||||
IOException ioe = new IOException("keystore error");
|
||||
ioe.initCause(gse);
|
||||
throw ioe;
|
||||
if (_verifySignature) {
|
||||
if (_certFile != null) {
|
||||
_signerPubkey = loadKey(_certFile);
|
||||
} else {
|
||||
KeyRing ring = new DirKeyRing(new File(_context.getBaseDir(), "certificates"));
|
||||
try {
|
||||
_signerPubkey = ring.getKey(_signer, _contentType.getName(), _sigType);
|
||||
} catch (GeneralSecurityException gse) {
|
||||
IOException ioe = new IOException("keystore error");
|
||||
ioe.initCause(gse);
|
||||
throw ioe;
|
||||
}
|
||||
if (_signerPubkey == null)
|
||||
throw new IOException("unknown signer: " + _signer + " for content type: " + _contentType.getName());
|
||||
}
|
||||
}
|
||||
|
||||
if (_signerPubkey == null)
|
||||
throw new IOException("unknown signer: " + _signer);
|
||||
_headerVerified = true;
|
||||
}
|
||||
|
||||
@ -287,9 +346,9 @@ public class SU3File {
|
||||
// read 10 bytes to get the sig type
|
||||
in.mark(10);
|
||||
// following is a dup of that in verifyHeader()
|
||||
byte[] magic = new byte[MAGIC.length];
|
||||
byte[] magic = new byte[MAGIC_BYTES.length];
|
||||
DataHelper.read(in, magic);
|
||||
if (!DataHelper.eq(magic, MAGIC))
|
||||
if (!DataHelper.eq(magic, MAGIC_BYTES))
|
||||
throw new IOException("Not an su3 file");
|
||||
skip(in, 1);
|
||||
int foo = in.read();
|
||||
@ -310,8 +369,10 @@ public class SU3File {
|
||||
verifyHeader(in);
|
||||
else
|
||||
skip(in, getContentOffset());
|
||||
if (_signerPubkey == null)
|
||||
throw new IOException("unknown signer: " + _signer);
|
||||
if (_verifySignature) {
|
||||
if (_signerPubkey == null)
|
||||
throw new IOException("unknown signer: " + _signer + " for content type: " + _contentType.getName());
|
||||
}
|
||||
if (migrateTo != null) // else verify only
|
||||
out = new FileOutputStream(migrateTo);
|
||||
byte[] buf = new byte[16*1024];
|
||||
@ -324,15 +385,19 @@ public class SU3File {
|
||||
out.write(buf, 0, read);
|
||||
tot += read;
|
||||
}
|
||||
byte[] sha = md.digest();
|
||||
din.on(false);
|
||||
Signature signature = new Signature(_sigType);
|
||||
signature.readBytes(in);
|
||||
SimpleDataStructure hash = _sigType.getHashInstance();
|
||||
hash.setData(sha);
|
||||
//System.out.println("hash\n" + HexDump.dump(sha));
|
||||
//System.out.println("sig\n" + HexDump.dump(signature.getData()));
|
||||
rv = _context.dsa().verifySignature(signature, hash, _signerPubkey);
|
||||
if (_verifySignature) {
|
||||
byte[] sha = md.digest();
|
||||
din.on(false);
|
||||
Signature signature = new Signature(_sigType);
|
||||
signature.readBytes(in);
|
||||
SimpleDataStructure hash = _sigType.getHashInstance();
|
||||
hash.setData(sha);
|
||||
//System.out.println("hash\n" + HexDump.dump(sha));
|
||||
//System.out.println("sig\n" + HexDump.dump(signature.getData()));
|
||||
rv = _context.dsa().verifySignature(signature, hash, _signerPubkey);
|
||||
} else {
|
||||
rv = true;
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
IOException ioe = new IOException("foo");
|
||||
ioe.initCause(dfe);
|
||||
@ -352,11 +417,12 @@ public class SU3File {
|
||||
* Throws on all errors.
|
||||
*
|
||||
* @param content the input file, probably in zip format
|
||||
* @param contentType 0-255, 0 for zip
|
||||
* @param fileType 0-255, 0 for zip
|
||||
* @param contentType 0-255
|
||||
* @param version 1-255 bytes when converted to UTF-8
|
||||
* @param signer ID of the public key, 1-255 bytes when converted to UTF-8
|
||||
*/
|
||||
public void write(File content, int contentType, String version,
|
||||
public void write(File content, int fileType, int contentType, String version,
|
||||
String signer, PrivateKey privkey, SigType sigType) throws IOException {
|
||||
InputStream in = null;
|
||||
DigestOutputStream out = null;
|
||||
@ -365,7 +431,7 @@ public class SU3File {
|
||||
in = new BufferedInputStream(new FileInputStream(content));
|
||||
MessageDigest md = sigType.getDigestInstance();
|
||||
out = new DigestOutputStream(new BufferedOutputStream(new FileOutputStream(_file)), md);
|
||||
out.write(MAGIC);
|
||||
out.write(MAGIC_BYTES);
|
||||
out.write((byte) 0);
|
||||
out.write((byte) FILE_VERSION);
|
||||
DataHelper.writeLong(out, 2, sigType.getCode());
|
||||
@ -386,7 +452,9 @@ public class SU3File {
|
||||
throw new IllegalArgumentException("No content");
|
||||
DataHelper.writeLong(out, 8, contentLength);
|
||||
out.write((byte) 0);
|
||||
out.write((byte) TYPE_ZIP);
|
||||
if (fileType < 0 || fileType > 255)
|
||||
throw new IllegalArgumentException("bad content type");
|
||||
out.write((byte) fileType);
|
||||
out.write((byte) 0);
|
||||
if (contentType < 0 || contentType > 255)
|
||||
throw new IllegalArgumentException("bad content type");
|
||||
@ -443,8 +511,11 @@ public class SU3File {
|
||||
// defaults
|
||||
String stype = null;
|
||||
String ctype = null;
|
||||
String ftype = null;
|
||||
String kfile = null;
|
||||
boolean error = false;
|
||||
Getopt g = new Getopt("SU3File", args, "t:c:");
|
||||
boolean shouldVerify = true;
|
||||
Getopt g = new Getopt("SU3File", args, "t:c:f:k:x");
|
||||
int c;
|
||||
while ((c = g.getopt()) != -1) {
|
||||
switch (c) {
|
||||
@ -456,6 +527,18 @@ public class SU3File {
|
||||
ctype = g.getOptarg();
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
ftype = g.getOptarg();
|
||||
break;
|
||||
|
||||
case 'k':
|
||||
kfile = g.getOptarg();
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
shouldVerify = false;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
case ':':
|
||||
default:
|
||||
@ -476,18 +559,18 @@ public class SU3File {
|
||||
Properties props = new Properties();
|
||||
props.setProperty("prng.bufferSize", "16384");
|
||||
new I2PAppContext(props);
|
||||
ok = signCLI(stype, ctype, a.get(0), a.get(1), a.get(2), a.get(3), a.get(4), "");
|
||||
ok = signCLI(stype, ctype, ftype, a.get(0), a.get(1), a.get(2), a.get(3), a.get(4), "");
|
||||
} else if ("bulksign".equals(cmd)) {
|
||||
Properties props = new Properties();
|
||||
props.setProperty("prng.bufferSize", "16384");
|
||||
new I2PAppContext(props);
|
||||
ok = bulkSignCLI(stype, ctype, a.get(0), a.get(1), a.get(2), a.get(3));
|
||||
} else if ("verifysig".equals(cmd)) {
|
||||
ok = verifySigCLI(a.get(0));
|
||||
ok = verifySigCLI(a.get(0), kfile);
|
||||
} else if ("keygen".equals(cmd)) {
|
||||
ok = genKeysCLI(stype, a.get(0), a.get(1), a.get(2));
|
||||
} else if ("extract".equals(cmd)) {
|
||||
ok = extractCLI(a.get(0), a.get(1));
|
||||
ok = extractCLI(a.get(0), a.get(1), shouldVerify);
|
||||
} else {
|
||||
showUsageCLI();
|
||||
}
|
||||
@ -502,11 +585,11 @@ public class SU3File {
|
||||
|
||||
private static final void showUsageCLI() {
|
||||
System.err.println("Usage: SU3File keygen [-t type|code] publicKeyFile keystore.ks you@mail.i2p");
|
||||
System.err.println(" SU3File sign [-c type|code] [-t type|code] inputFile.zip signedFile.su3 keystore.ks version you@mail.i2p");
|
||||
System.err.println(" SU3File bulksign [-c type|code] [-t type|code] directory keystore.ks version you@mail.i2p");
|
||||
System.err.println(" SU3File sign [-t type|code] [-c type|code] [-f type|code] inputFile.zip signedFile.su3 keystore.ks version you@mail.i2p");
|
||||
System.err.println(" SU3File bulksign [-t type|code] [-c type|code] directory keystore.ks version you@mail.i2p");
|
||||
System.err.println(" SU3File showversion signedFile.su3");
|
||||
System.err.println(" SU3File verifysig signedFile.su3");
|
||||
System.err.println(" SU3File extract signedFile.su3 outFile.zip");
|
||||
System.err.println(" SU3File verifysig [-k file.crt] signedFile.su3 ## -k use this pubkey cert for verification");
|
||||
System.err.println(" SU3File extract [-x] [-k file.crt] signedFile.su3 outFile ## -x don't check sig");
|
||||
System.err.println(dumpTypes());
|
||||
}
|
||||
|
||||
@ -529,6 +612,9 @@ public class SU3File {
|
||||
buf.append(" DEFAULT");
|
||||
buf.append('\n');
|
||||
}
|
||||
buf.append("Available file types (-f):\n");
|
||||
buf.append(" ZIP\t(code: 0) DEFAULT\n");
|
||||
buf.append(" XML\t(code: 1)\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
@ -554,6 +640,7 @@ public class SU3File {
|
||||
private static final boolean showVersionCLI(String signedFile) {
|
||||
try {
|
||||
SU3File file = new SU3File(signedFile);
|
||||
file.setVerifySignature(false);
|
||||
String versionString = file.getVersionString();
|
||||
if (versionString.equals(""))
|
||||
System.out.println("No version string found in file '" + signedFile + "'");
|
||||
@ -574,6 +661,7 @@ public class SU3File {
|
||||
}
|
||||
|
||||
/**
|
||||
* Zip only
|
||||
* @return success
|
||||
* @since 0.9.9
|
||||
*/
|
||||
@ -593,7 +681,12 @@ public class SU3File {
|
||||
try {
|
||||
while (keypw.length() < 6) {
|
||||
System.out.print("Enter password for key \"" + signerName + "\": ");
|
||||
keypw = DataHelper.readLine(System.in).trim();
|
||||
keypw = DataHelper.readLine(System.in);
|
||||
if (keypw == null) {
|
||||
System.out.println("\nEOF reading password");
|
||||
return false;
|
||||
}
|
||||
keypw = keypw.trim();
|
||||
if (keypw.length() > 0 && keypw.length() < 6)
|
||||
System.out.println("Key password must be at least 6 characters");
|
||||
}
|
||||
@ -608,7 +701,7 @@ public class SU3File {
|
||||
if (!inputFile.endsWith(".zip"))
|
||||
continue;
|
||||
String signedFile = inputFile.substring(0, inputFile.length() - 4) + ".su3";
|
||||
boolean rv = signCLI(stype, ctype, inputFile, signedFile, privateKeyFile, version, signerName, keypw);
|
||||
boolean rv = signCLI(stype, ctype, null, inputFile, signedFile, privateKeyFile, version, signerName, keypw);
|
||||
if (!rv)
|
||||
return false;
|
||||
success++;
|
||||
@ -622,7 +715,7 @@ public class SU3File {
|
||||
* @return success
|
||||
* @since 0.9.9
|
||||
*/
|
||||
private static final boolean signCLI(String stype, String ctype, String inputFile, String signedFile,
|
||||
private static final boolean signCLI(String stype, String ctype, String ftype, String inputFile, String signedFile,
|
||||
String privateKeyFile, String version, String signerName, String keypw) {
|
||||
SigType type = stype == null ? SigType.getByCode(Integer.valueOf(DEFAULT_SIG_CODE)) : SigType.parseSigType(stype);
|
||||
if (type == null) {
|
||||
@ -634,19 +727,42 @@ public class SU3File {
|
||||
System.out.println("Content type " + ctype + " is not supported");
|
||||
return false;
|
||||
}
|
||||
return signCLI(type, ct, inputFile, signedFile, privateKeyFile, version, signerName, keypw);
|
||||
int ft = TYPE_ZIP;
|
||||
if (ftype != null) {
|
||||
if (ftype.equalsIgnoreCase("ZIP")) {
|
||||
ft = TYPE_ZIP;
|
||||
} else if (ftype.equalsIgnoreCase("XML")) {
|
||||
ft = TYPE_XML;
|
||||
} else {
|
||||
try {
|
||||
ft = Integer.parseInt(ftype);
|
||||
} catch (NumberFormatException nfe) {
|
||||
ft = -1;
|
||||
}
|
||||
if (ft != TYPE_ZIP && ft != TYPE_XML) {
|
||||
System.out.println("File type " + ftype + " is not supported");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return signCLI(type, ct, ft, inputFile, signedFile, privateKeyFile, version, signerName, keypw);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return success
|
||||
* @since 0.9.9
|
||||
*/
|
||||
private static final boolean signCLI(SigType type, ContentType ctype, String inputFile, String signedFile,
|
||||
private static final boolean signCLI(SigType type, ContentType ctype, int ftype, String inputFile, String signedFile,
|
||||
String privateKeyFile, String version, String signerName, String keypw) {
|
||||
try {
|
||||
while (keypw.length() < 6) {
|
||||
System.out.print("Enter password for key \"" + signerName + "\": ");
|
||||
keypw = DataHelper.readLine(System.in).trim();
|
||||
keypw = DataHelper.readLine(System.in);
|
||||
if (keypw == null) {
|
||||
System.out.println("\nEOF reading password");
|
||||
return false;
|
||||
}
|
||||
keypw = keypw.trim();
|
||||
if (keypw.length() > 0 && keypw.length() < 6)
|
||||
System.out.println("Key password must be at least 6 characters");
|
||||
}
|
||||
@ -657,7 +773,7 @@ public class SU3File {
|
||||
return false;
|
||||
}
|
||||
SU3File file = new SU3File(signedFile);
|
||||
file.write(new File(inputFile), ctype.getCode(), version, signerName, pk, type);
|
||||
file.write(new File(inputFile), ftype, ctype.getCode(), version, signerName, pk, type);
|
||||
System.out.println("Input file '" + inputFile + "' signed and written to '" + signedFile + "'");
|
||||
return true;
|
||||
} catch (GeneralSecurityException gse) {
|
||||
@ -672,10 +788,12 @@ public class SU3File {
|
||||
}
|
||||
|
||||
/** @return valid */
|
||||
private static final boolean verifySigCLI(String signedFile) {
|
||||
private static final boolean verifySigCLI(String signedFile, String pkFile) {
|
||||
InputStream in = null;
|
||||
try {
|
||||
SU3File file = new SU3File(signedFile);
|
||||
if (pkFile != null)
|
||||
file.setPublicKeyCertificate(new File(pkFile));
|
||||
boolean isValidSignature = file.verify();
|
||||
if (isValidSignature)
|
||||
System.out.println("Signature VALID (signed by " + file.getSignerString() + ' ' + file._sigType + ')');
|
||||
@ -693,10 +811,11 @@ public class SU3File {
|
||||
* @return success
|
||||
* @since 0.9.9
|
||||
*/
|
||||
private static final boolean extractCLI(String signedFile, String outFile) {
|
||||
private static final boolean extractCLI(String signedFile, String outFile, boolean verifySig) {
|
||||
InputStream in = null;
|
||||
try {
|
||||
SU3File file = new SU3File(signedFile);
|
||||
file.setVerifySignature(verifySig);
|
||||
File out = new File(outFile);
|
||||
boolean ok = file.verifyAndMigrate(out);
|
||||
if (ok)
|
||||
@ -740,11 +859,21 @@ public class SU3File {
|
||||
try {
|
||||
while (alias.length() == 0) {
|
||||
System.out.print("Enter key name (example@mail.i2p): ");
|
||||
alias = DataHelper.readLine(System.in).trim();
|
||||
alias = DataHelper.readLine(System.in);
|
||||
if (alias == null) {
|
||||
System.out.println("\nEOF reading key name");
|
||||
return false;
|
||||
}
|
||||
alias = alias.trim();
|
||||
}
|
||||
while (keypw.length() < 6) {
|
||||
System.out.print("Enter new key password: ");
|
||||
keypw = DataHelper.readLine(System.in).trim();
|
||||
keypw = DataHelper.readLine(System.in);
|
||||
if (keypw == null) {
|
||||
System.out.println("\nEOF reading password");
|
||||
return false;
|
||||
}
|
||||
keypw = keypw.trim();
|
||||
if (keypw.length() > 0 && keypw.length() < 6)
|
||||
System.out.println("Key password must be at least 6 characters");
|
||||
}
|
||||
@ -772,4 +901,26 @@ public class SU3File {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* For the -k CLI option
|
||||
* @return non-null, throws IOE on all errors
|
||||
* @since 0.9.15
|
||||
*/
|
||||
private static PublicKey loadKey(File kd) throws IOException {
|
||||
InputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(kd);
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
X509Certificate cert = (X509Certificate)cf.generateCertificate(fis);
|
||||
cert.checkValidity();
|
||||
return cert.getPublicKey();
|
||||
} catch (GeneralSecurityException gse) {
|
||||
IOException ioe = new IOException("cert error");
|
||||
ioe.initCause(gse);
|
||||
throw ioe;
|
||||
} finally {
|
||||
try { if (fis != null) fis.close(); } catch (IOException foo) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -178,6 +178,29 @@ public enum SigType {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if supported in this JVM
|
||||
* @since 0.9.15
|
||||
*/
|
||||
public static boolean isAvailable(int code) {
|
||||
SigType type = getByCode(code);
|
||||
if (type == null)
|
||||
return false;
|
||||
return type.isAvailable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param stype number or name
|
||||
* @return true if supported in this JVM
|
||||
* @since 0.9.15
|
||||
*/
|
||||
public static boolean isAvailable(String stype) {
|
||||
SigType type = parseSigType(stype);
|
||||
if (type == null)
|
||||
return false;
|
||||
return type.isAvailable();
|
||||
}
|
||||
|
||||
private static final Map<Integer, SigType> BY_CODE = new HashMap<Integer, SigType>();
|
||||
|
||||
static {
|
||||
|
@ -56,6 +56,7 @@ public class Base32 {
|
||||
};
|
||||
|
||||
private final static byte BAD_ENCODING = -9; // Indicates error in encoding
|
||||
|
||||
/** Defeats instantiation. */
|
||||
private Base32() { // nop
|
||||
}
|
||||
@ -136,6 +137,9 @@ public class Base32 {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns lower case.
|
||||
* Does not add trailing '='.
|
||||
*
|
||||
* @param source if null will return ""
|
||||
*/
|
||||
public static String encode(String source) {
|
||||
@ -143,6 +147,9 @@ public class Base32 {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns lower case.
|
||||
* Does not add trailing '='.
|
||||
*
|
||||
* @param source The data to convert non-null
|
||||
*/
|
||||
public static String encode(byte[] source) {
|
||||
@ -182,6 +189,8 @@ public class Base32 {
|
||||
/**
|
||||
* Decodes data from Base32 notation and
|
||||
* returns it as a string.
|
||||
* Case-insensitive.
|
||||
* Does not allow trailing '='.
|
||||
*
|
||||
* @param s the string to decode, if null returns null
|
||||
* @return The data as a string or null on failure
|
||||
@ -194,6 +203,9 @@ public class Base32 {
|
||||
}
|
||||
|
||||
/**
|
||||
* Case-insensitive.
|
||||
* Does not allow trailing '='.
|
||||
*
|
||||
* @param s non-null
|
||||
* @return decoded data, null on error
|
||||
*/
|
||||
|
@ -130,17 +130,19 @@ public class KeysAndCert extends DataStructureImpl {
|
||||
&& DataHelper.eq(_certificate, ident._certificate);
|
||||
}
|
||||
|
||||
/** the public key has enough randomness in it to use it by itself for speed */
|
||||
/** the signing key has enough randomness in it to use it by itself for speed */
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (_publicKey == null)
|
||||
// don't use public key, some app devs thinking of using
|
||||
// an all-zeros or leading-zeros public key for destinations
|
||||
if (_signingKey == null)
|
||||
return 0;
|
||||
return _publicKey.hashCode();
|
||||
return _signingKey.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder(64);
|
||||
StringBuilder buf = new StringBuilder(256);
|
||||
buf.append('[').append(getClass().getSimpleName()).append(": ");
|
||||
buf.append("\n\tHash: ").append(getHash().toBase64());
|
||||
buf.append("\n\tCertificate: ").append(_certificate);
|
||||
|
@ -13,6 +13,7 @@ import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.internal.InternalClientManager;
|
||||
import net.i2p.router.client.ClientManagerFacadeImpl;
|
||||
import net.i2p.router.crypto.TransientSessionKeyManager;
|
||||
import net.i2p.router.dummy.*;
|
||||
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
|
||||
import net.i2p.router.peermanager.PeerManagerFacadeImpl;
|
||||
@ -67,7 +68,7 @@ public class RouterContext extends I2PAppContext {
|
||||
private final Set<Runnable> _finalShutdownTasks;
|
||||
// split up big lock on this to avoid deadlocks
|
||||
private volatile boolean _initialized;
|
||||
private final Object _lock1 = new Object(), _lock2 = new Object();
|
||||
private final Object _lock1 = new Object(), _lock2 = new Object(), _lock3 = new Object();
|
||||
|
||||
private static final List<RouterContext> _contexts = new CopyOnWriteArrayList<RouterContext>();
|
||||
|
||||
@ -565,4 +566,20 @@ public class RouterContext extends I2PAppContext {
|
||||
public RouterAppManager routerAppManager() {
|
||||
return _appManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* As of 0.9.15, this returns a dummy SessionKeyManager in I2PAppContext.
|
||||
* Overridden in RouterContext to return the full TransientSessionKeyManager.
|
||||
*
|
||||
* @since 0.9.15
|
||||
*/
|
||||
@Override
|
||||
protected void initializeSessionKeyManager() {
|
||||
synchronized (_lock3) {
|
||||
if (_sessionKeyManager == null)
|
||||
//_sessionKeyManager = new PersistentSessionKeyManager(this);
|
||||
_sessionKeyManager = new TransientSessionKeyManager(this);
|
||||
_sessionKeyManagerInitialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.crypto.SessionKeyManager;
|
||||
import net.i2p.crypto.TransientSessionKeyManager;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
@ -43,6 +42,7 @@ import net.i2p.data.i2cp.SessionId;
|
||||
import net.i2p.router.Job;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.crypto.TransientSessionKeyManager;
|
||||
import net.i2p.util.ConcurrentHashSet;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
@ -11,6 +11,7 @@ package net.i2p.router.client;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.CoreVersion;
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.Payload;
|
||||
import net.i2p.data.i2cp.BandwidthLimitsMessage;
|
||||
@ -195,10 +196,16 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Signature verified correctly on create session message");
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Signature verification *FAILED* on a create session message. Hijack attempt?");
|
||||
// For now, we do NOT send a SessionStatusMessage - see javadoc above
|
||||
_runner.disconnectClient("Invalid signature on CreateSessionMessage");
|
||||
int itype = in.getDestination().getCertificate().getCertificateType();
|
||||
SigType stype = SigType.getByCode(itype);
|
||||
if (stype == null || !stype.isAvailable()) {
|
||||
_log.error("Client requested unsupported signature type " + itype);
|
||||
_runner.disconnectClient("Unsupported signature type " + itype);
|
||||
} else {
|
||||
_log.error("Signature verification failed on a create session message");
|
||||
_runner.disconnectClient("Invalid signature on CreateSessionMessage");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package net.i2p.crypto;
|
||||
package net.i2p.router.crypto;
|
||||
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
@ -25,6 +25,8 @@ import java.util.TreeSet;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.SessionKeyManager;
|
||||
import net.i2p.crypto.TagSetHandle;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SessionKey;
|
7
router/java/src/net/i2p/router/crypto/package.html
Normal file
7
router/java/src/net/i2p/router/crypto/package.html
Normal file
@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Classes formerly in net.i2p.crypto but moved here as they are only used by the router.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
@ -967,6 +967,32 @@ class EstablishmentManager {
|
||||
notifyActivity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from UDPReceiver.
|
||||
* Accelerate response to RelayResponse if we haven't sent it yet.
|
||||
*
|
||||
* @since 0.9.15
|
||||
*/
|
||||
void receiveHolePunch(InetAddress from, int fromPort) {
|
||||
RemoteHostId id = new RemoteHostId(from.getAddress(), fromPort);
|
||||
OutboundEstablishState state = _outboundStates.get(id);
|
||||
if (state != null) {
|
||||
boolean sendNow = state.receiveHolePunch();
|
||||
if (sendNow) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Hole punch from " + state + ", sending SessionRequest now");
|
||||
notifyActivity();
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Hole punch from " + state + ", already sent SessionRequest");
|
||||
}
|
||||
} else {
|
||||
// HolePunch received before RelayResponse, and we didn't know the IP/port, or it changed
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("No state found for hole punch from " + from + " port " + fromPort);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Are IP and port valid? This is only for checking the relay response.
|
||||
* Reject all IPv6, for now, even if we are configured for it.
|
||||
|
@ -99,6 +99,8 @@ class OutboundEstablishState {
|
||||
/** max delay including backoff */
|
||||
private static final long MAX_DELAY = 15*1000;
|
||||
|
||||
private static final long WAIT_FOR_HOLE_PUNCH_DELAY = 500;
|
||||
|
||||
/**
|
||||
* @param claimedAddress an IP/port based RemoteHostId, or null if unknown
|
||||
* @param remoteHostId non-null, == claimedAddress if direct, or a hash-based one if indirect
|
||||
@ -556,7 +558,7 @@ class OutboundEstablishState {
|
||||
public synchronized void introduced(byte bobIP[], int bobPort) {
|
||||
if (_currentState != OutboundState.OB_STATE_PENDING_INTRO)
|
||||
return; // we've already successfully been introduced, so don't overwrite old settings
|
||||
_nextSend = _context.clock().now() + 500; // wait briefly for the hole punching
|
||||
_nextSend = _context.clock().now() + WAIT_FOR_HOLE_PUNCH_DELAY; // wait briefly for the hole punching
|
||||
_currentState = OutboundState.OB_STATE_INTRODUCED;
|
||||
if (_claimedAddress != null && bobPort == _bobPort && DataHelper.eq(bobIP, _bobIP)) {
|
||||
// he's who he said he was
|
||||
@ -571,6 +573,24 @@ class OutboundEstablishState {
|
||||
_log.info("Introduced to " + _remoteHostId + ", now lets get on with establishing");
|
||||
}
|
||||
|
||||
/**
|
||||
* Accelerate response to RelayResponse if we haven't sent it yet.
|
||||
*
|
||||
* @return true if we should send the SessionRequest now
|
||||
* @since 0.9.15
|
||||
*/
|
||||
synchronized boolean receiveHolePunch() {
|
||||
if (_currentState != OutboundState.OB_STATE_INTRODUCED)
|
||||
return false;
|
||||
if (_requestSentCount > 0)
|
||||
return false;
|
||||
long now = _context.clock().now();
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(toString() + " accelerating SessionRequest by " + (_nextSend - now) + " ms");
|
||||
_nextSend = now;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** how long have we been trying to establish this session? */
|
||||
public long getLifetime() { return _context.clock().now() - _establishBegin; }
|
||||
public long getEstablishBeginTime() { return _establishBegin; }
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.i2p.router.transport.udp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.util.Arrays;
|
||||
|
||||
@ -220,11 +221,12 @@ class UDPReceiver {
|
||||
// _socketChanged = false;
|
||||
//}
|
||||
UDPPacket packet = UDPPacket.acquire(_context, true);
|
||||
DatagramPacket dpacket = packet.getPacket();
|
||||
|
||||
// Android ICS bug
|
||||
// http://code.google.com/p/android/issues/detail?id=24748
|
||||
if (_isAndroid)
|
||||
packet.getPacket().setLength(UDPPacket.MAX_PACKET_SIZE);
|
||||
dpacket.setLength(UDPPacket.MAX_PACKET_SIZE);
|
||||
|
||||
// block before we read...
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
@ -236,9 +238,9 @@ class UDPReceiver {
|
||||
//if (_log.shouldLog(Log.INFO))
|
||||
// _log.info("Before blocking socket.receive on " + System.identityHashCode(packet));
|
||||
//synchronized (Runner.this) {
|
||||
_socket.receive(packet.getPacket());
|
||||
_socket.receive(dpacket);
|
||||
//}
|
||||
int size = packet.getPacket().getLength();
|
||||
int size = dpacket.getLength();
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("After blocking socket.receive: packet is " + size + " bytes on " + System.identityHashCode(packet));
|
||||
packet.resetBegin();
|
||||
@ -266,7 +268,8 @@ class UDPReceiver {
|
||||
_context.statManager().addRateData("udp.receiveHolePunch", 1);
|
||||
// nat hole punch packets are 0 bytes
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Received a 0 byte udp packet from " + packet.getPacket().getAddress() + ":" + packet.getPacket().getPort());
|
||||
_log.info("Received a 0 byte udp packet from " + dpacket.getAddress() + ":" + dpacket.getPort());
|
||||
_transport.getEstablisher().receiveHolePunch(dpacket.getAddress(), dpacket.getPort());
|
||||
packet.release();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
|
@ -21,18 +21,21 @@
|
||||
|
||||
package org.cybergarage.xml.parser;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.cybergarage.xml.Node;
|
||||
import org.cybergarage.xml.Parser;
|
||||
import org.cybergarage.xml.ParserException;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
import org.xml.sax.EntityResolver;
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
|
||||
@ -116,8 +119,25 @@ public class JaxpParser extends Parser
|
||||
org.cybergarage.xml.Node root = null;
|
||||
|
||||
try {
|
||||
// https://www.owasp.org/index.php/XML_External_Entity_%28XXE%29_Processing
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setValidating(false);
|
||||
factory.setNamespaceAware(true);
|
||||
factory.setExpandEntityReferences(false);
|
||||
try {
|
||||
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||
} catch (ParserConfigurationException pce) {}
|
||||
try {
|
||||
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||
} catch (ParserConfigurationException pce) {}
|
||||
try {
|
||||
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
||||
} catch (ParserConfigurationException pce) {}
|
||||
try {
|
||||
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
|
||||
} catch (ParserConfigurationException pce) {}
|
||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||
builder.setEntityResolver(new BlankingResolver());
|
||||
InputSource inSrc = new InputSource(new NullFilterInputStream(inStream));
|
||||
Document doc = builder.parse(inSrc);
|
||||
|
||||
@ -163,4 +183,16 @@ public class JaxpParser extends Parser
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* I2P -
|
||||
* http://stackoverflow.com/questions/5883542/disable-xml-validation-based-on-external-dtd-xsd
|
||||
*/
|
||||
private static class BlankingResolver implements EntityResolver {
|
||||
private static final byte[] DUMMY = new byte[0];
|
||||
|
||||
public InputSource resolveEntity(String arg0, String arg1) {
|
||||
return new InputSource(new ByteArrayInputStream(DUMMY));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user