From 106af9967ad0a1ff12b7ff53f0280ad2e8ab8d24 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sun, 21 Nov 2010 20:37:49 +0000 Subject: [PATCH] * 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. --- LICENSE.txt | 4 + build.xml | 72 +-- core/java/src/net/i2p/util/EepGet.java | 2 +- core/java/src/net/i2p/util/SSLEepGet.java | 513 ++++++++++++++---- .../resources/certificates/www.cacert.org.crt | 41 ++ licenses/LICENSE-InstallCert.txt | 29 + .../i2p/router/networkdb/reseed/Reseeder.java | 177 ++++-- 7 files changed, 649 insertions(+), 189 deletions(-) create mode 100644 installer/resources/certificates/www.cacert.org.crt create mode 100644 licenses/LICENSE-InstallCert.txt diff --git a/LICENSE.txt b/LICENSE.txt index a4bd67dcf..e3b631a3b 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -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: diff --git a/build.xml b/build.xml index 0e749ce49..44011705d 100644 --- a/build.xml +++ b/build.xml @@ -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> - <!-- 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 todir="pkg-temp/docs/themes/" > + <fileset dir="installer/resources/themes/" /> </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/" /> diff --git a/core/java/src/net/i2p/util/EepGet.java b/core/java/src/net/i2p/util/EepGet.java index 5b3f25c19..f055ce98c 100644 --- a/core/java/src/net/i2p/util/EepGet.java +++ b/core/java/src/net/i2p/util/EepGet.java @@ -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 { diff --git a/core/java/src/net/i2p/util/SSLEepGet.java b/core/java/src/net/i2p/util/SSLEepGet.java index 8a5bf364a..e3ba43ae7 100644 --- a/core/java/src/net/i2p/util/SSLEepGet.java +++ b/core/java/src/net/i2p/util/SSLEepGet.java @@ -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) { + this(ctx, outputStream, url, null); + } + + /** + * @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 url - * no command line options supported + * 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,94 +153,323 @@ 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); + 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("Usage: SSLEepGet https://url"); + System.err.println("To save unknown certs, use: SSLEepGet -s https://url"); + } + + /** + * 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 { - KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); + 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(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()); - } + for(Enumeration<String> e = ks.aliases(); e.hasMoreElements();) { + String alias = e.nextElement(); + if (ks.isCertificateEntry(alias)) + count++; + } + } catch (Exception foo) {} + _log.info("Loaded " + count + " default trusted certificates"); + } - addCert(ks, "cacert"); + 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"); - for(Enumeration<String> e = ks.aliases(); e.hasMoreElements();) { - String alias = e.nextElement(); - System.err.println("Aliases: " + alias + " isCert? " + ks.isCertificateEntry(alias)); - } + 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; + } - KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - kmf.init(ks, "".toCharArray()); - SSLContext sslc = SSLContext.getInstance("SSL"); - sslc.init(kmf.getKeyManagers(), null, null); - _sslContext = sslc; + /** + * 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) { - System.err.println("KS error: " + gse); - return; + _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) { - System.err.println("IO error: " + ioe); - return; + _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) {} } - *******/ - - EepGet get = new SSLEepGet(I2PAppContext.getGlobalContext(), out, url); - get.addStatusListener(get.new CLIStatusListener(1024, 40)); - get.fetch(45*1000, -1, 60*1000); - } - - private static void usage() { - System.err.println("SSLEepGet url"); + return true; } -/****** - private static boolean addCert(KeyStore ks, String file) { + /** + * 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 { - 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); - } - // use file name as alias - ks.setCertificateEntry(file, cert); - } catch (GeneralSecurityException gse) { - System.err.println("Read cert error: " + gse); + cert.checkValidity(); + } catch (CertificateExpiredException cee) { + _log.error("Rejecting expired X509 Certificate: " + file.getAbsolutePath(), cee); return false; - } catch (IOException ioe) { - System.err.println("Read cert error: " + ioe); + } 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 { _headersRead = false; @@ -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"); diff --git a/installer/resources/certificates/www.cacert.org.crt b/installer/resources/certificates/www.cacert.org.crt new file mode 100644 index 000000000..e7dfc8294 --- /dev/null +++ b/installer/resources/certificates/www.cacert.org.crt @@ -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----- diff --git a/licenses/LICENSE-InstallCert.txt b/licenses/LICENSE-InstallCert.txt new file mode 100644 index 000000000..17620f66d --- /dev/null +++ b/licenses/LICENSE-InstallCert.txt @@ -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. + diff --git a/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java b/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java index 776d7eee4..9577b2b20 100644 --- a/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java +++ b/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java @@ -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); - - 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)); - } + System.err.println("Reseed got " + fetched + " router infos from " + seedURL + " with " + errors + " errors"); + 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()) ) { -- GitLab