* SSLEepGet, Reseeder:

- Implement additional CA loading
      - Provide facility to reuse SSL state for speed
      - Provide facility to store previously untrusted certificates
      - Add SSL reseed hosts, prefer them by default
      - Reseed message cleanup
    * build.xml:
      - Add www.cacert.org cert to the installer and updater so
        SSL on a.netdb.i2p2.de and c.netdb.i2p2.de will work
      - Cleanup, fix distclean error in older ants.
This commit is contained in:
zzz
2010-11-21 20:37:49 +00:00
parent c8cad6ab79
commit 106af9967a
7 changed files with 654 additions and 194 deletions

View File

@ -68,6 +68,10 @@ Public domain except as listed below:
Copyright (C) 2001, 2007 Free Software Foundation, Inc.
See licenses/LICENSE-LGPLv2.1.txt
SSLEepGet:
Contains some code Copyright 2006 Sun Microsystems, Inc.
See licenses/LICENSE-InstallCert.txt
Router:
Public domain except as listed below:

View File

@ -245,7 +245,7 @@
<delete file="debian/substvars"/>
</target>
<target name="distclean" depends="clean">
<delete includeemptydirs="true" removeNotFollowedSymlinks="true" failonerror="false" >
<delete includeemptydirs="true" failonerror="false" >
<fileset dir="debian/packages" followSymlinks="false" />
</delete>
<delete dir="debian/repo" />
@ -368,7 +368,7 @@
</copy>
</target>
<target name="preppkg-base" depends="build, preplicenses, prepconsoleDocs">
<target name="preppkg-base" depends="build, preplicenses, prepConsoleDocs, prepthemeupdates, prepCertificates">
<!-- if updater200 was run previously, it left *.pack files in pkg-temp -->
<delete>
<fileset dir="pkg-temp" includes="**/*.jar.pack **/*.war.pack" />
@ -419,10 +419,6 @@
<copy file="installer/resources/start.ico" todir="pkg-temp/docs/" />
<copy file="installer/resources/console.ico" todir="pkg-temp/docs/" />
<copy file="installer/resources/uninstall.ico" todir="pkg-temp/docs/" />
<mkdir dir="pkg-temp/docs/themes/" />
<copy todir="pkg-temp/docs/themes/" >
<fileset dir="installer/resources/themes/" />
</copy>
<!-- Eepsite stuff here -->
<mkdir dir="pkg-temp/eepsite" />
<mkdir dir="pkg-temp/eepsite/webapps" />
@ -455,42 +451,17 @@
<copy file="installer/lib/launch4j/lib/JGoodies.Looks.LICENSE.txt" tofile="pkg-temp/licenses/LICENSE-JGoodies-Looks.txt" />
<copy file="installer/lib/launch4j/lib/XStream.LICENSE.txt" tofile="pkg-temp/licenses/LICENSE-XStream.txt" />
</target>
<target name="prepthemeupdates">
<!-- Migrated all Snark content to its own dir. Need to ensure snark dir excluded from console theme choices!! -->
<!-- Snark's visible Assets -->
<copy todir="pkg-temp/docs/themes/snark/ubergine/" >
<fileset dir="installer/resources/themes/snark/ubergine/" />
<copy todir="pkg-temp/docs/themes/" >
<fileset dir="installer/resources/themes/" />
</copy>
<!-- No need to copy these individually, we're copying the whole dir below..
<copy file="installer/resources/themes/console/images/favicon.ico" todir="pkg-temp/docs/themes/console/images/" />
<copy file="installer/resources/themes/console/images/i2plogo.png" todir="pkg-temp/docs/themes/console/images/" />
-->
<!-- Since the logo moved, we have to update the error pages -->
<copy todir="pkg-temp/docs/" >
<fileset dir="installer/resources/proxy" />
</copy>
<!-- make a "classic" theme -->
<copy todir="pkg-temp/docs/themes/console/classic/" >
<fileset dir="installer/resources/themes/console/classic/" />
</copy>
<!-- Add dark theme -->
<copy todir="pkg-temp/docs/themes/console/dark/" >
<fileset dir="installer/resources/themes/console/dark/" />
</copy>
<!-- Add light theme -->
<copy todir="pkg-temp/docs/themes/console/light/" >
<fileset dir="installer/resources/themes/console/light/" />
</copy>
<!-- Add midnight theme -->
<copy todir="pkg-temp/docs/themes/console/midnight/" >
<fileset dir="installer/resources/themes/console/midnight/" />
</copy>
<!-- Add shared images.. these are subject to flux and change! -->
<copy todir="pkg-temp/docs/themes/console/images/" >
<fileset dir="installer/resources/themes/console/images/" />
</copy>
<copy todir="pkg-temp/docs/" >
<fileset dir="installer/resources/readme/" includes="readme*.html" />
</target>
<!-- SSL Certs -->
<target name="prepCertificates">
<copy todir="pkg-temp/certificates/" >
<fileset dir="installer/resources/certificates/" />
</copy>
</target>
@ -500,16 +471,23 @@
<tarfileset dir="pkg-temp" includes="**/*" prefix="i2p" />
</tar>
</target>
<target name="deletepkg-temp">
<delete dir="pkg-temp" />
</target>
<target name="prepconsoleDocs" depends="prepgeoupdate">
<!-- readme and proxy error page files, GeoIP files, and flag icons -->
<target name="prepConsoleDocs" depends="prepConsoleDocUpdates, prepgeoupdate" />
<!-- readme and proxy error page files -->
<target name="prepConsoleDocUpdates">
<copy todir="pkg-temp/docs/" >
<fileset dir="installer/resources/readme/" includes="readme*.html" />
<fileset dir="installer/resources/proxy" />
<fileset dir="installer/resources/proxy/" includes="*.ht" />
</copy>
</target>
<target name="consoleDocs" depends="deletepkg-temp, prepconsoleDocs">
<target name="consoleDocs" depends="deletepkg-temp, prepConsoleDocs">
<zip destfile="docs.zip" basedir="pkg-temp" whenempty="fail" />
</target>
@ -560,7 +538,8 @@
<copy file="core/java/build/i2ptest.jar" todir="pkg-temp/lib" />
<zip destfile="i2pupdate.zip" basedir="pkg-temp" />
</target>
<target name="prepupdate" depends="build2, prepupdateSmall">
<target name="prepupdate" depends="build2, prepupdateSmall, prepConsoleDocUpdates, prepCertificates">
<copy file="build/BOB.jar" todir="pkg-temp/lib/" />
<copy file="build/sam.jar" todir="pkg-temp/lib/" />
<copy file="build/i2psnark.jar" todir="pkg-temp/lib" />
@ -587,6 +566,7 @@
<copy file="installer/resources/news.xml" todir="pkg-temp/docs/" />
-->
</target>
<target name="prepupdateSmall" depends="buildSmall, prepupdateRouter, prepthemeupdates">
<copy file="build/i2ptunnel.jar" todir="pkg-temp/lib/" />
<copy file="build/mstreaming.jar" todir="pkg-temp/lib/" />
@ -601,10 +581,13 @@
<!-- decapitalized the file in 0.7.8 -->
<copy file="installer/resources/countries.txt" todir="pkg-temp/geoip/" />
</target>
<target name="prepupdateRouter" depends="buildrouter, deletepkg-temp">
<copy file="build/i2p.jar" todir="pkg-temp/lib/" />
<copy file="build/router.jar" todir="pkg-temp/lib/" />
</target>
<!-- GeoIP files and flag icons -->
<target name="prepgeoupdate">
<copy file="installer/resources/geoip.txt" todir="pkg-temp/geoip/" />
<copy file="installer/resources/countries.txt" todir="pkg-temp/geoip/" />
@ -612,6 +595,7 @@
<fileset dir="installer/resources/icons/flags" />
</copy>
</target>
<target name="prepjupdate" depends="prepupdate, buildWEB">
<copy file="build/jasper-compiler.jar" todir="pkg-temp/lib/" />
<copy file="build/jasper-runtime.jar" todir="pkg-temp/lib/" />

View File

@ -456,7 +456,7 @@ public class EepGet {
for (int i = 0; i < _listeners.size(); i++)
_listeners.get(i).attemptFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt, _numRetries, ioe);
if (_log.shouldLog(Log.WARN))
_log.warn("ERR: doFetch failed " + ioe);
_log.warn("ERR: doFetch failed ", ioe);
if (ioe instanceof MalformedURLException)
_keepFetching = false;
} finally {

View File

@ -1,55 +1,131 @@
package net.i2p.util;
/*
* Contains code from:
* http://blogs.sun.com/andreas/resource/InstallCert.java
* http://blogs.sun.com/andreas/entry/no_more_unable_to_find
*
* ===============
*
* Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Sun Microsystems nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyStore;
import java.security.GeneralSecurityException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSocketFactory;
// all part of the CA experiment below
//import java.io.FileInputStream;
//import java.io.InputStream;
//import java.util.Enumeration;
//import java.security.KeyStore;
//import java.security.GeneralSecurityException;
//import java.security.cert.CertificateExpiredException;
//import java.security.cert.CertificateNotYetValidException;
//import java.security.cert.CertificateFactory;
//import java.security.cert.X509Certificate;
//import javax.net.ssl.KeyManagerFactory;
//import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
/**
* HTTPS only, non-proxied only, no retries, no min and max size options, no timeout option
* Fails on 301 or 302 (doesn't follow redirect)
* Fails on self-signed certs (must have a valid cert chain)
* Fails on bad certs (must have a valid cert chain)
* Self-signed certs or CAs not in the JVM key store must be loaded to be trusted.
*
* Since 0.8.2, loads additional trusted CA certs from $I2P/certificates/ and ~/.i2p/certificates/
*
* @author zzz
* @since 0.7.10
*/
public class SSLEepGet extends EepGet {
//private static SSLContext _sslContext;
/** if true, save cert chain on cert error */
private boolean _saveCerts;
/** true if called from main(), used for logging */
private boolean _commandLine;
/** may be null if init failed */
private final SSLContext _sslContext;
/** may be null if init failed */
private SavingTrustManager _stm;
/**
* A new SSLEepGet with a new SSLState
*/
public SSLEepGet(I2PAppContext ctx, OutputStream outputStream, String url) {
// we're using this constructor:
// public EepGet(I2PAppContext ctx, boolean shouldProxy, String proxyHost, int proxyPort, int numRetries, long minSize, long maxSize, String outputFile, OutputStream outputStream, String url, boolean allowCaching, String etag, String postData) {
super(ctx, false, null, -1, 0, -1, -1, null, outputStream, url, true, null, null);
this(ctx, outputStream, url, null);
}
/**
* SSLEepGet url
* no command line options supported
* @param state an SSLState retrieved from a previous SSLEepGet with getSSLState(), or null.
* This makes repeated fetches from the same host MUCH faster,
* and prevents repeated key store loads even for different hosts.
* @since 0.8.2
*/
public SSLEepGet(I2PAppContext ctx, OutputStream outputStream, String url, SSLState state) {
// we're using this constructor:
// public EepGet(I2PAppContext ctx, boolean shouldProxy, String proxyHost, int proxyPort, int numRetries, long minSize, long maxSize, String outputFile, OutputStream outputStream, String url, boolean allowCaching, String etag, String postData) {
super(ctx, false, null, -1, 0, -1, -1, null, outputStream, url, true, null, null);
if (state != null && state.context != null)
_sslContext = state.context;
else
_sslContext = initSSLContext();
if (_sslContext == null)
_log.error("Failed to initialize custom SSL context, using default context");
}
/**
* SSLEepGet https://foo/bar
* or to save cert chain:
* SSLEepGet -s https://foo/bar
*/
public static void main(String args[]) {
String url = null;
boolean saveCerts = false;
try {
for (int i = 0; i < args.length; i++) {
if (args[i].startsWith("-")) {
if (args[i].equals("-s")) {
saveCerts = true;
} else if (args[i].startsWith("-")) {
usage();
return;
} else {
@ -77,93 +153,322 @@ public class SSLEepGet extends EepGet {
return;
}
/******
* This is all an experiment to add a CA cert loaded from a file so we can use
* selfsigned certs on our servers.
* But it's failing.
* Run as java -Djava.security.debug=certpath -Djavax.net.debug=trustmanager -cp $I2P/lib/i2p.jar net.i2p.util.SSLEepGet "$@"
* to see the problems. It isn't including the added cert in the Trust Anchor list.
******/
/******
String foo = System.getProperty("javax.net.ssl.keyStore");
if (foo == null) {
File cacerts = new File(System.getProperty("java.home"), "lib/security/cacerts");
foo = cacerts.getAbsolutePath();
}
System.err.println("Location is: " + foo);
try {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
try {
InputStream fis = new FileInputStream(foo);
ks.load(fis, "changeit".toCharArray());
fis.close();
} catch (GeneralSecurityException gse) {
System.err.println("KS error, no default keys: " + gse);
ks.load(null, "changeit".toCharArray());
} catch (IOException ioe) {
System.err.println("IO error, no default keys: " + ioe);
ks.load(null, "changeit".toCharArray());
}
addCert(ks, "cacert");
for(Enumeration<String> e = ks.aliases(); e.hasMoreElements();) {
String alias = e.nextElement();
System.err.println("Aliases: " + alias + " isCert? " + ks.isCertificateEntry(alias));
}
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, "".toCharArray());
SSLContext sslc = SSLContext.getInstance("SSL");
sslc.init(kmf.getKeyManagers(), null, null);
_sslContext = sslc;
} catch (GeneralSecurityException gse) {
System.err.println("KS error: " + gse);
return;
} catch (IOException ioe) {
System.err.println("IO error: " + ioe);
return;
}
*******/
EepGet get = new SSLEepGet(I2PAppContext.getGlobalContext(), out, url);
SSLEepGet get = new SSLEepGet(I2PAppContext.getGlobalContext(), out, url);
if (saveCerts)
get._saveCerts = true;
get._commandLine = true;
get.addStatusListener(get.new CLIStatusListener(1024, 40));
get.fetch(45*1000, -1, 60*1000);
}
private static void usage() {
System.err.println("SSLEepGet url");
System.err.println("Usage: SSLEepGet https://url");
System.err.println("To save unknown certs, use: SSLEepGet -s https://url");
}
/******
private static boolean addCert(KeyStore ks, String file) {
/**
* Loads certs from location of javax.net.ssl.keyStore property,
* else from $JAVA_HOME/lib/security/jssacacerts,
* else from $JAVA_HOME/lib/security/cacerts.
*
* Then adds certs found in the $I2P/certificates/ directory
* and in the ~/.i2p/certificates/ directory.
*
* @return null on failure
* @since 0.8.2
*/
private SSLContext initSSLContext() {
KeyStore ks;
try {
ks = KeyStore.getInstance(KeyStore.getDefaultType());
} catch (GeneralSecurityException gse) {
_log.error("Key Store init error", gse);
return null;
}
boolean success = false;
String override = System.getProperty("javax.net.ssl.keyStore");
if (override != null)
success = loadCerts(new File(override), ks);
if (!success)
success = loadCerts(new File(System.getProperty("java.home"), "lib/security/jssecacerts"), ks);
if (!success)
success = loadCerts(new File(System.getProperty("java.home"), "lib/security/cacerts"), ks);
if (!success) {
_log.error("All key store loads failed, will only load local certificates");
} else if (_log.shouldLog(Log.INFO)) {
int count = 0;
try {
InputStream fis = new FileInputStream(file);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(fis);
fis.close();
System.err.println("Adding cert, Issuer: " + cert.getIssuerX500Principal());
try {
cert.checkValidity();
} catch (CertificateExpiredException cee) {
System.err.println("Warning - expired cert: " + cee);
} catch (CertificateNotYetValidException cnyve) {
System.err.println("Warning - not yet valid cert: " + cnyve);
for(Enumeration<String> e = ks.aliases(); e.hasMoreElements();) {
String alias = e.nextElement();
if (ks.isCertificateEntry(alias))
count++;
}
// use file name as alias
ks.setCertificateEntry(file, cert);
} catch (GeneralSecurityException gse) {
System.err.println("Read cert error: " + gse);
return false;
} catch (IOException ioe) {
System.err.println("Read cert error: " + ioe);
return false;
}
} catch (Exception foo) {}
_log.info("Loaded " + count + " default trusted certificates");
}
File dir = new File(_context.getBaseDir(), "certificates");
int adds = addCerts(dir, ks);
int totalAdds = adds;
if (adds > 0 && _log.shouldLog(Log.INFO))
_log.info("Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath());
if (!_context.getBaseDir().getAbsolutePath().equals(_context.getConfigDir().getAbsolutePath())) {
dir = new File(_context.getConfigDir(), "certificates");
adds = addCerts(dir, ks);
totalAdds += adds;
if (adds > 0 && _log.shouldLog(Log.INFO))
_log.info("Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath());
}
dir = new File(System.getProperty("user.dir"));
if (!_context.getBaseDir().getAbsolutePath().equals(dir.getAbsolutePath())) {
dir = new File(_context.getConfigDir(), "certificates");
adds = addCerts(dir, ks);
totalAdds += adds;
if (adds > 0 && _log.shouldLog(Log.INFO))
_log.info("Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath());
}
if (_log.shouldLog(Log.INFO))
_log.info("Loaded total of " + totalAdds + " new trusted certificates");
try {
SSLContext sslc = SSLContext.getInstance("TLS");
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
X509TrustManager defaultTrustManager = (X509TrustManager)tmf.getTrustManagers()[0];
_stm = new SavingTrustManager(defaultTrustManager);
sslc.init(null, new TrustManager[] {_stm}, null);
return sslc;
} catch (GeneralSecurityException gse) {
_log.error("Key Store update error", gse);
}
return null;
}
/**
* Load all X509 Certs from a key store File into a KeyStore
* Note that each call reinitializes the KeyStore
*
* @return success
* @since 0.8.2
*/
private boolean loadCerts(File file, KeyStore ks) {
if (!file.exists())
return false;
InputStream fis = null;
try {
fis = new FileInputStream(file);
// "changeit" is the default password
ks.load(fis, "changeit".toCharArray());
} catch (GeneralSecurityException gse) {
_log.error("KeyStore load error, no default keys: " + file.getAbsolutePath(), gse);
try {
// not clear if null is allowed for password
ks.load(null, "changeit".toCharArray());
} catch (Exception foo) {}
return false;
} catch (IOException ioe) {
_log.error("KeyStore load error, no default keys: " + file.getAbsolutePath(), ioe);
try {
ks.load(null, "changeit".toCharArray());
} catch (Exception foo) {}
return false;
} finally {
try { if (fis != null) fis.close(); } catch (IOException foo) {}
}
return true;
}
*******/
/**
* Load all X509 Certs from a directory and add them to the
* trusted set of certificates in the key store
*
* @return number successfully added
* @since 0.8.2
*/
private int addCerts(File dir, KeyStore ks) {
if (_log.shouldLog(Log.INFO))
_log.info("Looking for X509 Certificates in " + dir.getAbsolutePath());
int added = 0;
if (dir.exists() && dir.isDirectory()) {
File[] files = dir.listFiles();
if (files != null) {
for (int i = 0; i < files.length; i++) {
File f = files[i];
if (!f.isFile())
continue;
// use file name as alias
// https://www.sslshopper.com/ssl-converter.html
// No idea if all these formats can actually be read by CertificateFactory
String alias = f.getName().toLowerCase();
if (alias.endsWith(".crt") || alias.endsWith(".pem") || alias.endsWith(".key") ||
alias.endsWith(".der") || alias.endsWith(".key") || alias.endsWith(".p7b") ||
alias.endsWith(".p7c") || alias.endsWith(".pfx") || alias.endsWith(".p12"))
alias = alias.substring(0, alias.length() - 4);
boolean success = addCert(f, alias, ks);
if (success)
added++;
}
}
}
return added;
}
/**
* Load an X509 Cert from a file and add it to the
* trusted set of certificates in the key store
*
* @return success
* @since 0.8.2
*/
private boolean addCert(File file, String alias, KeyStore ks) {
InputStream fis = null;
try {
fis = new FileInputStream(file);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(fis);
if (_log.shouldLog(Log.INFO)) {
_log.info("Read X509 Certificate from " + file.getAbsolutePath() +
" Issuer: " + cert.getIssuerX500Principal() +
"; Valid From: " + cert.getNotBefore() +
" To: " + cert.getNotAfter());
}
try {
cert.checkValidity();
} catch (CertificateExpiredException cee) {
_log.error("Rejecting expired X509 Certificate: " + file.getAbsolutePath(), cee);
return false;
} catch (CertificateNotYetValidException cnyve) {
_log.error("Rejecting X509 Certificate not yet valid: " + file.getAbsolutePath(), cnyve);
return false;
}
ks.setCertificateEntry(alias, cert);
if (_log.shouldLog(Log.INFO))
_log.info("Now trusting X509 Certificate, Issuer: " + cert.getIssuerX500Principal());
} catch (GeneralSecurityException gse) {
_log.error("Error reading X509 Certificate: " + file.getAbsolutePath(), gse);
return false;
} catch (IOException ioe) {
_log.error("Error reading X509 Certificate: " + file.getAbsolutePath(), ioe);
return false;
} finally {
try { if (fis != null) fis.close(); } catch (IOException foo) {}
}
return true;
}
/**
* From http://blogs.sun.com/andreas/resource/InstallCert.java
* This just saves the certificate chain for later inspection.
* @since 0.8.2
*/
private static class SavingTrustManager implements X509TrustManager {
private final X509TrustManager tm;
private X509Certificate[] chain;
SavingTrustManager(X509TrustManager tm) {
this.tm = tm;
}
public X509Certificate[] getAcceptedIssuers() {
throw new UnsupportedOperationException();
}
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
throw new UnsupportedOperationException();
}
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
this.chain = chain;
tm.checkServerTrusted(chain, authType);
}
}
/**
* Modified from http://blogs.sun.com/andreas/resource/InstallCert.java
* @since 0.8.2
*/
private static void saveCerts(String host, SavingTrustManager stm) {
X509Certificate[] chain = stm.chain;
if (chain == null) {
System.out.println("Could not obtain server certificate chain");
return;
}
for (int k = 0; k < chain.length; k++) {
X509Certificate cert = chain[k];
String name = host + '-' + (k + 1) + ".crt";
System.out.println("NOTE: Saving untrusted X509 certificate as " + name);
System.out.println(" Issuer: " + cert.getIssuerX500Principal());
System.out.println(" Valid From: " + cert.getNotBefore());
System.out.println(" Valid To: " + cert.getNotAfter());
try {
cert.checkValidity();
} catch (Exception e) {
System.out.println(" WARNING: Certificate is not currently valid, it cannot be used");
}
saveCert(cert, new File(name));
}
System.out.println("NOTE: To trust them, copy the certificate file(s) to the certificates directory and rerun without the -s option");
System.out.println("NOTE: EepGet failed, certificate error follows:");
}
private static final int LINE_LENGTH = 64;
/**
* Modified from:
* http://www.exampledepot.com/egs/java.security.cert/ExportCert.html
*
* This method writes a certificate to a file in base64 format.
* @since 0.8.2
*/
private static void saveCert(Certificate cert, File file) {
OutputStream os = null;
try {
// Get the encoded form which is suitable for exporting
byte[] buf = cert.getEncoded();
os = new FileOutputStream(file);
PrintWriter wr = new PrintWriter(os);
wr.println("-----BEGIN CERTIFICATE-----");
String b64 = Base64.encode(buf, true); // true = use standard alphabet
for (int i = 0; i < b64.length(); i += LINE_LENGTH) {
wr.println(b64.substring(i, Math.min(i + LINE_LENGTH, b64.length())));
}
wr.println("-----END CERTIFICATE-----");
wr.flush();
} catch (CertificateEncodingException cee) {
System.out.println("Error writing X509 Certificate " + file.getAbsolutePath() + ' ' + cee);
} catch (IOException ioe) {
System.out.println("Error writing X509 Certificate " + file.getAbsolutePath() + ' ' + ioe);
} finally {
try { if (os != null) os.close(); } catch (IOException foo) {}
}
}
/**
* An opaque class for the caller to pass to repeated instantiations of SSLEepGet.
* @since 0.8.2
*/
public static class SSLState {
private SSLContext context;
private SSLState(SSLContext ctx) {
context = ctx;
}
}
/**
* Pass this back to the next SSLEepGet constructor for faster fetches.
* This may be called either after the constructor or after the fetch.
* @since 0.8.2
*/
public SSLState getSSLState() {
return new SSLState(_sslContext);
}
///// end of all the SSL stuff
///// start of overrides
@Override
protected void doFetch(SocketTimeout timeout) throws IOException {
@ -288,15 +593,16 @@ public class SSLEepGet extends EepGet {
//try {
URL url = new URL(_actualURL);
String host = null;
int port = 0;
if ("https".equals(url.getProtocol())) {
String host = url.getHost();
int port = url.getPort();
host = url.getHost();
port = url.getPort();
if (port == -1)
port = 443;
// part of the experiment above
//if (_sslContext != null)
// _proxy = _sslContext.getSocketFactory().createSocket(host, port);
//else
if (_sslContext != null)
_proxy = _sslContext.getSocketFactory().createSocket(host, port);
else
_proxy = SSLSocketFactory.getDefault().createSocket(host, port);
} else {
throw new IOException("Only https supported: " + _actualURL);
@ -309,8 +615,23 @@ public class SSLEepGet extends EepGet {
_proxyIn = _proxy.getInputStream();
_proxyOut = _proxy.getOutputStream();
_proxyOut.write(DataHelper.getUTF8(req));
_proxyOut.flush();
// This is where the cert errors happen
try {
_proxyOut.write(DataHelper.getUTF8(req));
_proxyOut.flush();
} catch (SSLHandshakeException sslhe) {
// this maybe would be better done in the catch in super.fetch(), but
// then we'd have to copy it all over here.
_log.error("SSL negotiation error with " + host + ':' + port +
" - self-signed certificate or untrusted certificate authority?", sslhe);
if (_saveCerts && _stm != null)
saveCerts(host, _stm);
else if (_commandLine) {
System.out.println("FAILED (probably due to untrusted certificates) - Run with -s option to save certificates");
}
// this is an IOE
throw sslhe;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Request flushed");

View File

@ -0,0 +1,41 @@
-----BEGIN CERTIFICATE-----
MIIHPTCCBSWgAwIBAgIBADANBgkqhkiG9w0BAQQFADB5MRAwDgYDVQQKEwdSb290
IENBMR4wHAYDVQQLExVodHRwOi8vd3d3LmNhY2VydC5vcmcxIjAgBgNVBAMTGUNB
IENlcnQgU2lnbmluZyBBdXRob3JpdHkxITAfBgkqhkiG9w0BCQEWEnN1cHBvcnRA
Y2FjZXJ0Lm9yZzAeFw0wMzAzMzAxMjI5NDlaFw0zMzAzMjkxMjI5NDlaMHkxEDAO
BgNVBAoTB1Jvb3QgQ0ExHjAcBgNVBAsTFWh0dHA6Ly93d3cuY2FjZXJ0Lm9yZzEi
MCAGA1UEAxMZQ0EgQ2VydCBTaWduaW5nIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJ
ARYSc3VwcG9ydEBjYWNlcnQub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
CgKCAgEAziLA4kZ97DYoB1CW8qAzQIxL8TtmPzHlawI229Z89vGIj053NgVBlfkJ
8BLPRoZzYLdufujAWGSuzbCtRRcMY/pnCujW0r8+55jE8Ez64AO7NV1sId6eINm6
zWYyN3L69wj1x81YyY7nDl7qPv4coRQKFWyGhFtkZip6qUtTefWIonvuLwphK42y
fk1WpRPs6tqSnqxEQR5YYGUFZvjARL3LlPdCfgv3ZWiYUQXw8wWRBB0bF4LsyFe7
w2t6iPGwcswlWyCR7BYCEo8y6RcYSNDHBS4CMEK4JZwFaz+qOqfrU0j36NK2B5jc
G8Y0f3/JHIJ6BVgrCFvzOKKrF11myZjXnhCLotLddJr3cQxyYN/Nb5gznZY0dj4k
epKwDpUeb+agRThHqtdB7Uq3EvbXG4OKDy7YCbZZ16oE/9KTfWgu3YtLq1i6L43q
laegw1SJpfvbi1EinbLDvhG+LJGGi5Z4rSDTii8aP8bQUWWHIbEZAWV/RRyH9XzQ
QUxPKZgh/TMfdQwEUfoZd9vUFBzugcMd9Zi3aQaRIt0AUMyBMawSB3s42mhb5ivU
fslfrejrckzzAeVLIL+aplfKkQABi6F1ITe1Yw1nPkZPcCBnzsXWWdsC4PDSy826
YreQQejdIOQpvGQpQsgi3Hia/0PsmBsJUUtaWsJx8cTLc6nloQsCAwEAAaOCAc4w
ggHKMB0GA1UdDgQWBBQWtTIb1Mfz4OaO873SsDrusjkY0TCBowYDVR0jBIGbMIGY
gBQWtTIb1Mfz4OaO873SsDrusjkY0aF9pHsweTEQMA4GA1UEChMHUm9vdCBDQTEe
MBwGA1UECxMVaHR0cDovL3d3dy5jYWNlcnQub3JnMSIwIAYDVQQDExlDQSBDZXJ0
IFNpZ25pbmcgQXV0aG9yaXR5MSEwHwYJKoZIhvcNAQkBFhJzdXBwb3J0QGNhY2Vy
dC5vcmeCAQAwDwYDVR0TAQH/BAUwAwEB/zAyBgNVHR8EKzApMCegJaAjhiFodHRw
czovL3d3dy5jYWNlcnQub3JnL3Jldm9rZS5jcmwwMAYJYIZIAYb4QgEEBCMWIWh0
dHBzOi8vd3d3LmNhY2VydC5vcmcvcmV2b2tlLmNybDA0BglghkgBhvhCAQgEJxYl
aHR0cDovL3d3dy5jYWNlcnQub3JnL2luZGV4LnBocD9pZD0xMDBWBglghkgBhvhC
AQ0ESRZHVG8gZ2V0IHlvdXIgb3duIGNlcnRpZmljYXRlIGZvciBGUkVFIGhlYWQg
b3ZlciB0byBodHRwOi8vd3d3LmNhY2VydC5vcmcwDQYJKoZIhvcNAQEEBQADggIB
ACjH7pyCArpcgBLKNQodgW+JapnM8mgPf6fhjViVPr3yBsOQWqy1YPaZQwGjiHCc
nWKdpIevZ1gNMDY75q1I08t0AoZxPuIrA2jxNGJARjtT6ij0rPtmlVOKTV39O9lg
18p5aTuxZZKmxoGCXJzN600BiqXfEVWqFcofN8CCmHBh22p8lqOOLlQ+TyGpkO/c
gr/c6EWtTZBzCDyUZbAEmXZ/4rzCahWqlwQ3JNgelE5tDlG+1sSPypZt90Pf6DBl
Jzt7u0NDY8RD97LsaMzhGY4i+5jhe1o+ATc7iwiwovOVThrLm82asduycPAtStvY
sONvRUgzEv/+PDIqVPfE94rwiCPCR/5kenHA0R6mY7AHfqQv0wGP3J8rtsYIqQ+T
SCX8Ev2fQtzzxD72V7DX3WnRBnc0CkvSyqD/HMaMyRa+xMwyN2hzXwj7UfdJUzYF
CpUCTPJ5GhD22Dp1nPMd8aINcGeGG7MW9S/lpOt5hvk9C8JzC6WZrG/8Z7jlLwum
GCSNe9FINSkYQKyTYOGWhlC0elnYjyELn8+CkcY7v2vcB5G5l1YjqrZslMZIBjzk
zk6q5PYvCdxTby78dOs6Y5nCpqyJvKeyRKANihDjbPIky/qbn3BHLt4Ui9SyIAmW
omTxJBzcoTWcFbLUvFUufQb1nA5V9FrWk9p2rSVzTMVD
-----END CERTIFICATE-----

View File

@ -0,0 +1,29 @@
Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of Sun Microsystems nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -29,6 +29,8 @@ import net.i2p.util.Translate;
* specified below unless the I2P configuration property "i2p.reseedURL" is
* set. It always writes to ./netDb/, so don't mess with that.
*
* This is somewhat complicated by trying to log to three places - the console,
* the router log, and the wrapper log.
*/
public class Reseeder {
private static ReseedRunner _reseedRunner;
@ -40,12 +42,25 @@ public class Reseeder {
private static final String DEFAULT_SEED_URL =
"http://a.netdb.i2p2.de/,http://b.netdb.i2p2.de/,http://c.netdb.i2p2.de/," +
"http://reseed.i2p-projekt.de/,http://i2pbote.net/netDb/,http://r31453.ovh.net/static_media/netDb/";
"http://reseed.i2p-projekt.de/,http://www.i2pbote.net/netDb/,http://r31453.ovh.net/static_media/netDb/";
/** @since 0.8.2 */
private static final String DEFAULT_SSL_SEED_URL =
"https://a.netdb.i2p2.de/,https://c.netdb.i2p2.de/," +
"https://www.i2pbote.net/netDb/";
private static final String PROP_INPROGRESS = "net.i2p.router.web.ReseedHandler.reseedInProgress";
/** the console shows this message while reseedInProgress == false */
private static final String PROP_ERROR = "net.i2p.router.web.ReseedHandler.errorMessage";
/** the console shows this message while reseedInProgress == true */
private static final String PROP_STATUS = "net.i2p.router.web.ReseedHandler.statusMessage";
public static final String PROP_PROXY_HOST = "router.reseedProxyHost";
public static final String PROP_PROXY_PORT = "router.reseedProxyPort";
/** @since 0.8.2 */
public static final String PROP_PROXY_ENABLE = "router.reseedProxyEnable";
/** @since 0.8.2 */
public static final String PROP_SSL_DISABLE = "router.reseedSSLDisable";
private static final String RESEED_TIPS =
_x("Ensure that nothing blocks outbound HTTP, check <a target=\"_top\" href=\"logs.jsp\">logs</a> " +
"and if nothing helps, read the <a target=\"_top\" href=\"http://www.i2p2.de/faq.html\">FAQ</a> about reseeding manually.");
@ -63,7 +78,6 @@ public class Reseeder {
if (_reseedRunner.isRunning()) {
return;
} else {
System.setProperty(PROP_INPROGRESS, "true");
// set to daemon so it doesn't hang a shutdown
Thread reseed = new I2PAppThread(_reseedRunner, "Reseed", true);
reseed.start();
@ -76,20 +90,46 @@ public class Reseeder {
private boolean _isRunning;
private String _proxyHost;
private int _proxyPort;
private SSLEepGet.SSLState _sslState;
public ReseedRunner() {
_isRunning = false;
System.clearProperty(PROP_ERROR);
System.setProperty(PROP_STATUS, _("Reseeding"));
System.setProperty(PROP_INPROGRESS, "true");
}
public boolean isRunning() { return _isRunning; }
/*
* Do it.
* We update PROP_ERROR here.
*/
public void run() {
_isRunning = true;
_proxyHost = _context.getProperty(PROP_PROXY_HOST);
_proxyPort = _context.getProperty(PROP_PROXY_PORT, -1);
_sslState = null; // start fresh
if (_context.getBooleanProperty(PROP_PROXY_ENABLE)) {
_proxyHost = _context.getProperty(PROP_PROXY_HOST);
_proxyPort = _context.getProperty(PROP_PROXY_PORT, -1);
}
System.out.println("Reseed start");
reseed(false);
System.out.println("Reseed complete");
int total = reseed(false);
if (total >= 50) {
System.out.println("Reseed complete, " + total + " received");
System.clearProperty(PROP_ERROR);
} else if (total > 0) {
System.out.println("Reseed complete, only " + total + " received");
System.setProperty(PROP_ERROR, ngettext("Reseed fetched only 1 router.",
"Reseed fetched only {0} routers.", total));
} else {
System.out.println("Reseed failed, check network connection");
System.out.println(
"Ensure that nothing blocks outbound HTTP, check the logs, " +
"and if nothing helps, read the FAQ about reseeding manually.");
System.setProperty(PROP_ERROR, _("Reseed failed.") + ' ' + _(RESEED_TIPS));
}
System.setProperty(PROP_INPROGRESS, "false");
System.clearProperty(PROP_STATUS);
_sslState = null; // don't hold ref
_isRunning = false;
}
@ -112,16 +152,56 @@ public class Reseeder {
* the routerInfo-*.dat files from the specified URL (or the default) and
* save them into this router's netDb dir.
*
* - If list specified in the properties, use it randomly, without regard to http/https
* - If SSL not disabled, use the https randomly then
* the http randomly
* - Otherwise just the http randomly.
*
* @param echoStatus apparently always false
* @return count of routerinfos successfully fetched
*/
private void reseed(boolean echoStatus) {
List URLList = new ArrayList();
String URLs = _context.getProperty("i2p.reseedURL", DEFAULT_SEED_URL);
private int reseed(boolean echoStatus) {
List<String> URLList = new ArrayList();
String URLs = _context.getProperty("i2p.reseedURL");
boolean defaulted = URLs == null;
boolean SSLDisable = _context.getBooleanProperty(PROP_SSL_DISABLE);
if (defaulted) {
if (SSLDisable)
URLs = DEFAULT_SEED_URL;
else
URLs = DEFAULT_SSL_SEED_URL;
}
StringTokenizer tok = new StringTokenizer(URLs, " ,");
while (tok.hasMoreTokens())
URLList.add(tok.nextToken().trim());
Collections.shuffle(URLList);
for (int i = 0; i < URLList.size() && _isRunning; i++)
reseedOne((String) URLList.get(i), echoStatus);
if (defaulted && !SSLDisable) {
// put the non-SSL at the end of the SSL
List<String> URLList2 = new ArrayList();
tok = new StringTokenizer(DEFAULT_SSL_SEED_URL, " ,");
while (tok.hasMoreTokens())
URLList2.add(tok.nextToken().trim());
Collections.shuffle(URLList2);
URLList.addAll(URLList2);
}
int total = 0;
for (int i = 0; i < URLList.size() && _isRunning; i++) {
String url = URLList.get(i);
int dl = reseedOne(url, echoStatus);
if (dl > 0) {
total += dl;
// remove alternate version if we haven't tried it yet
String alt;
if (url.startsWith("http://"))
alt = url.replace("http://", "https://");
else
alt = url.replace("https://", "http://");
int idx = URLList.indexOf(alt);
if (idx > i)
URLList.remove(i);
}
}
return total;
}
/**
@ -138,22 +218,23 @@ public class Reseeder {
*
* Jetty directory listings are not compatible, as they look like
* HREF="/full/path/to/routerInfo-...
*
* We update PROP_STATUS here.
*
* @param echoStatus apparently always false
* @return count of routerinfos successfully fetched
**/
private void reseedOne(String seedURL, boolean echoStatus) {
private int reseedOne(String seedURL, boolean echoStatus) {
try {
System.setProperty(PROP_ERROR, "");
System.setProperty(PROP_STATUS, _("Reseeding: fetching seed URL."));
System.err.println("Reseed from " + seedURL);
System.err.println("Reseeding from " + seedURL);
URL dir = new URL(seedURL);
byte contentRaw[] = readURL(dir);
if (contentRaw == null) {
System.setProperty(PROP_ERROR,
_("Last reseed failed fully (failed reading seed URL).") + ' ' +
_(RESEED_TIPS));
// Logging deprecated here since attemptFailed() provides better info
_log.debug("Failed reading seed URL: " + seedURL);
return;
_log.warn("Failed reading seed URL: " + seedURL);
System.err.println("Reseed got no router infos from " + seedURL);
return 0;
}
String content = new String(contentRaw);
Set<String> urls = new HashSet(1024);
@ -173,11 +254,9 @@ public class Reseeder {
cur = end + 1;
}
if (total <= 0) {
_log.error("Read " + contentRaw.length + " bytes from seed " + seedURL + ", but found no routerInfo URLs.");
System.setProperty(PROP_ERROR,
_("Last reseed failed fully (no routerInfo URLs at seed URL).") + ' ' +
_(RESEED_TIPS));
return;
_log.warn("Read " + contentRaw.length + " bytes from seed " + seedURL + ", but found no routerInfo URLs.");
System.err.println("Reseed got no router infos from " + seedURL);
return 0;
}
List<String> urlList = new ArrayList(urls);
@ -201,32 +280,18 @@ public class Reseeder {
errors++;
}
}
System.err.println("Reseed got " + fetched + " router infos from " + seedURL);
System.err.println("Reseed got " + fetched + " router infos from " + seedURL + " with " + errors + " errors");
int failPercent = 100 * errors / total;
// Less than 10% of failures is considered success,
// because some routerInfos will always fail.
if ((failPercent >= 10) && (failPercent < 90)) {
System.setProperty(PROP_ERROR,
_("Last reseed failed partly ({0}% of {1}).", failPercent, total) + ' ' +
_(RESEED_TIPS));
}
if (failPercent >= 90) {
System.setProperty(PROP_ERROR,
_("Last reseed failed ({0}% of {1}).", failPercent, total) + ' ' +
_(RESEED_TIPS));
}
if (fetched > 0)
_context.netDb().rescan();
// Don't go on to the next URL if we have enough
if (fetched >= 100)
_isRunning = false;
return fetched;
} catch (Throwable t) {
System.setProperty(PROP_ERROR,
_("Last reseed failed fully (exception caught).") + ' ' +
_(RESEED_TIPS));
_log.error("Error reseeding", t);
_log.warn("Error reseeding", t);
System.err.println("Reseed got no router infos from " + seedURL);
return 0;
}
}
@ -248,8 +313,17 @@ public class Reseeder {
ByteArrayOutputStream baos = new ByteArrayOutputStream(4*1024);
EepGet get;
if (url.toString().startsWith("https")) {
get = new SSLEepGet(I2PAppContext.getGlobalContext(), baos, url.toString());
boolean ssl = url.toString().startsWith("https");
if (ssl) {
SSLEepGet sslget;
if (_sslState == null) {
sslget = new SSLEepGet(I2PAppContext.getGlobalContext(), baos, url.toString());
// save state for next time
_sslState = sslget.getSSLState();
} else {
sslget = new SSLEepGet(I2PAppContext.getGlobalContext(), baos, url.toString(), _sslState);
}
get = sslget;
} else {
// Do a (probably) non-proxied eepget into our ByteArrayOutputStream with 0 retries
boolean shouldProxy = _proxyHost != null && _proxyHost.length() > 0 && _proxyPort > 0;
@ -257,7 +331,9 @@ public class Reseeder {
null, baos, url.toString(), false, null, null);
}
get.addStatusListener(ReseedRunner.this);
if (get.fetch()) return baos.toByteArray(); else return null;
if (get.fetch())
return baos.toByteArray();
return null;
}
private void writeSeed(String name, byte data[]) throws Exception {
@ -295,6 +371,11 @@ public class Reseeder {
return Translate.getString(s, o, o2, _context, BUNDLE_NAME);
}
/** translate */
private String ngettext(String s, String p, int n) {
return Translate.getString(n, s, p, _context, BUNDLE_NAME);
}
/******
public static void main(String args[]) {
if ( (args != null) && (args.length == 1) && (!Boolean.valueOf(args[0]).booleanValue()) ) {