propagate from branch 'i2p.i2p.zzz.gmp6-prop' (head 416ef26df4b91fb9de3e27623551c7f87ec2bfe0)

to branch 'i2p.i2p' (head 9466fdeae338d6b0bf049d86975db9b6ddbd3064)
This commit is contained in:
zzz
2016-04-28 01:34:48 +00:00
1529 changed files with 211145 additions and 119391 deletions

View File

@@ -9,6 +9,8 @@ import java.security.SecureRandom;
import java.text.SimpleDateFormat;
import java.security.NoSuchAlgorithmException;
import net.i2p.data.DataHelper;
/**
* Class for generation and parsing of <a href="http://www.hashcash.org/">HashCash</a><br>
* Copyright 2006 Gregory Rubin <a href="mailto:grrubin@gmail.com">grrubin@gmail.com</a><br>
@@ -61,7 +63,7 @@ public class HashCash implements Comparable<HashCash> {
myExtensions = deserializeExtensions(parts[index++]);
MessageDigest md = MessageDigest.getInstance("SHA1");
md.update(cash.getBytes());
md.update(DataHelper.getUTF8(cash));
byte[] tempBytes = md.digest();
int tempValue = numberOfLeadingZeros(tempBytes);
@@ -190,7 +192,7 @@ public class HashCash implements Comparable<HashCash> {
serializeExtensions(extensions) + ":";
result.myToken = generateCash(prefix, value, md);
md.reset();
md.update(result.myToken.getBytes());
md.update(DataHelper.getUTF8(result.myToken));
result.myValue = numberOfLeadingZeros(md.digest());
break;
@@ -294,7 +296,7 @@ public class HashCash implements Comparable<HashCash> {
counter++;
temp = prefix + Long.toHexString(counter);
md.reset();
md.update(temp.getBytes());
md.update(DataHelper.getUTF8(temp));
bArray = md.digest();
tempValue = numberOfLeadingZeros(bArray);
} while ( tempValue < value);

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
HashCash implementation. Unused.
</p>
</body>
</html>

View File

@@ -53,7 +53,7 @@ class IntelInfoImpl extends CPUIDCPUInfo implements IntelCPUInfo
{
// http://en.wikipedia.org/wiki/Cpuid
// http://web.archive.org/web/20110307080258/http://www.intel.com/Assets/PDF/appnote/241618.pdf
// http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-vol-2a-manual.pdf
// http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-manual-325462.pdf
// #include "llvm/Support/Host.h", http://llvm.org/docs/doxygen/html/Host_8cpp_source.html
String modelString = null;
int family = CPUID.getCPUFamily();
@@ -471,12 +471,10 @@ class IntelInfoImpl extends CPUIDCPUInfo implements IntelCPUInfo
break;
case 7: {
// Flags TODO
modelString = "Intel Itanium model " + model;
}
break;
// 15 + 0
case 15: {
isPentiumCompatible = true;
isPentiumMMXCompatible = true;
@@ -507,9 +505,7 @@ class IntelInfoImpl extends CPUIDCPUInfo implements IntelCPUInfo
}
break;
// 15 + 1
case 16: {
// Flags TODO
modelString = "Intel Itanium II model " + model;
}
}

View File

@@ -0,0 +1,9 @@
<html>
<body>
<p>
CPUID implementaion, borrowed from freenet, updated and heavily modified.
Not recommended for external use, not maintained as a public API.
</p>
</body>
</html>

View File

@@ -51,7 +51,9 @@ package gnu.crypto.hash;
* See SHA256Generator for more information.
*
* @version $Revision: 1.1 $
* @deprecated to be removed in 0.9.27
*/
@Deprecated
public abstract class BaseHashStandalone implements IMessageDigestStandalone {
// Constants and variables

View File

@@ -54,7 +54,9 @@ package gnu.crypto.hash;
* See SHA256Generator for more information.
*
* @version $Revision: 1.1 $
* @deprecated to be removed in 0.9.27
*/
@Deprecated
public interface IMessageDigestStandalone extends Cloneable {
// Constants

View File

@@ -64,7 +64,9 @@ package gnu.crypto.hash;
* See SHA256Generator for more information.
*
* @version $Revision: 1.2 $
* @deprecated to be removed in 0.9.27
*/
@Deprecated
public class Sha256Standalone extends BaseHashStandalone {
// Constants and variables
// -------------------------------------------------------------------------

View File

@@ -56,7 +56,7 @@ public class AsyncFortunaStandalone extends FortunaStandalone implements Runnabl
_isRunning = true;
_refillThread = new I2PThread(this, "PRNG");
_refillThread.setDaemon(true);
_refillThread.setPriority(Thread.MIN_PRIORITY+1);
_refillThread.setPriority(Thread.NORM_PRIORITY - 2);
_refillThread.start();
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
The Fortuna PRNG from GNU Crypto, updated and modifed for I2P.
</p>
</body>
</html>

View File

@@ -0,0 +1,31 @@
#**************************************************************************
#* MessagesBundle.properties -- English language error messages
#*
#* Copyright (c) 1998 by William King (wrking@eng.sun.com) and
#* Aaron M. Renn (arenn@urbanophile.com)
#*
#* This program is free software; you can redistribute it and/or modify
#* it under the terms of the GNU Library General Public License as published
#* by the Free Software Foundation; either version 2 of the License or
#* (at your option) any later version.
#*
#* This program is distributed in the hope that it will be useful, but
#* WITHOUT ANY WARRANTY; without even the implied warranty of
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#* GNU Library General Public License for more details.
#*
#* You should have received a copy of the GNU Library General Public License
#* along with this program; see the file COPYING.LIB. If not, write to
#* the Free Software Foundation Inc., 59 Temple Place - Suite 330,
#* Boston, MA 02111-1307 USA
#**************************************************************************/
getopt.ambigious={0}\: valitsin \u201d{1}\u201d ei ole yksiselitteinen
getopt.arguments1={0}\: valitsin ''--{1}'' ei salli argumenttia
getopt.arguments2={0}\: valitsin ''{1}{2}'' ei salli argumenttia
getopt.requires={0}\: valitsin ''{1}'' vaatii argumentin
getopt.unrecognized={0}\: tunnistamaton valitsin ''--{1}''
getopt.unrecognized2={0}\: tunnistamaton valitsin ''{1}{2}''
getopt.illegal={0}\: virheellinen valitsin -- {1}
getopt.invalid={0}\: virheellinen valitsin -- {1}
getopt.requires2={0}\: valitsin vaatii argumentin -- {1}
getopt.invalidValue=Virheellinen arvo {0} parametrille 'has_arg'

View File

@@ -0,0 +1,31 @@
#**************************************************************************
#* MessagesBundle.properties -- English language error messages
#*
#* Copyright (c) 1998 by William King (wrking@eng.sun.com) and
#* Aaron M. Renn (arenn@urbanophile.com)
#*
#* This program is free software; you can redistribute it and/or modify
#* it under the terms of the GNU Library General Public License as published
#* by the Free Software Foundation; either version 2 of the License or
#* (at your option) any later version.
#*
#* This program is distributed in the hope that it will be useful, but
#* WITHOUT ANY WARRANTY; without even the implied warranty of
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#* GNU Library General Public License for more details.
#*
#* You should have received a copy of the GNU Library General Public License
#* along with this program; see the file COPYING.LIB. If not, write to
#* the Free Software Foundation Inc., 59 Temple Place - Suite 330,
#* Boston, MA 02111-1307 USA
#**************************************************************************/
getopt.ambigious={0}\: opsi ''{1}'' meragukan
getopt.arguments1={0}\: opsi ''--{1}'' tidak mengizinkan argumen
getopt.arguments2={0}\: opsi ''{1}{2}'' tidak mengizinkan argumen
getopt.requires={0}\: opsi ''{1}'' memerlukan argumen
getopt.unrecognized={0}\: opsi tidak dikenal ''--{1}''
getopt.unrecognized2={0}\: opsi tidak dikenal ''{1}{2}''
getopt.illegal={0}\: opsi ilegal -- {1}
getopt.invalid={0}\: opsi tidak valid -- {1}
getopt.requires2={0}\: opsi memerlukan argumen -- {1}
getopt.invalidValue=Vaue tidak valid {0} untuk parameter 'has_arg'

View File

@@ -0,0 +1,31 @@
#**************************************************************************
#* MessagesBundle.properties -- English language error messages
#*
#* Copyright (c) 1998 by William King (wrking@eng.sun.com) and
#* Aaron M. Renn (arenn@urbanophile.com)
#*
#* This program is free software; you can redistribute it and/or modify
#* it under the terms of the GNU Library General Public License as published
#* by the Free Software Foundation; either version 2 of the License or
#* (at your option) any later version.
#*
#* This program is distributed in the hope that it will be useful, but
#* WITHOUT ANY WARRANTY; without even the implied warranty of
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#* GNU Library General Public License for more details.
#*
#* You should have received a copy of the GNU Library General Public License
#* along with this program; see the file COPYING.LIB. If not, write to
#* the Free Software Foundation Inc., 59 Temple Place - Suite 330,
#* Boston, MA 02111-1307 USA
#**************************************************************************/
getopt.ambigious={0}\: \uc635\uc158 "{1}"\uc774 \uc560\ub9e4\ubaa8\ud638\ud568
getopt.arguments1={0}\: \uc635\uc158 "--{1}"\uc740 \uc778\uc218\ub97c \ud5c8\uc6a9\ud558\uc9c0 \uc54a\uc74c
getopt.arguments2={0}\: \uc635\uc158 "{1}{2}" \uc740 \uc778\uc218\ub97c \ud5c8\uc6a9\ud558\uc9c0 \uc54a\uc74c
getopt.requires={0}\: \uc635\uc158 "{1}" \uc740 \uc778\uc218\uac00 \ud544\uc694\ud568
getopt.unrecognized={0}\: \uc54c\uc218\uc5c6\ub294 \uc635\uc158 "--{1}"
getopt.unrecognized2={0}\: \uc54c\uc218\uc5c6\ub294 \uc635\uc158 "{1}{2}"
getopt.illegal={0}\: \uc798\ubabb\ub41c \uc635\uc158 -- {1}
getopt.invalid={0}\: \uc798\ubabb\ub41c \uc635\uc158 -- {1}
getopt.requires2={0}\: \uc635\uc158\uc740 \uc778\uc790\ub97c \ud544\uc694\ub85c \ud568 -- {1}
getopt.invalidValue=\uc778\uc218 'has_arg'\uc5d0 \ubd80\uc801\ud569\ud55c \uac12 {0}

View File

@@ -1,7 +1,8 @@
#**************************************************************************
#* MessagesBundle_nl.properties -- Dutch language error messages
#* MessagesBundle.properties -- English language error messages
#*
#* Copyright (c) 1999 by Ernst de Haan (ernst@jollem.com)
#* Copyright (c) 1998 by William King (wrking@eng.sun.com) and
#* Aaron M. Renn (arenn@urbanophile.com)
#*
#* This program is free software; you can redistribute it and/or modify
#* it under the terms of the GNU Library General Public License as published
@@ -18,13 +19,13 @@
#* the Free Software Foundation Inc., 59 Temple Place - Suite 330,
#* Boston, MA 02111-1307 USA
#**************************************************************************/
getopt.ambigious={0}: optie ''{1}'' is ambigue
getopt.arguments1={0}: optie ''--{1}'' staat geen argumenten toe
getopt.arguments2={0}: optie ''{1}{2}'' staat geen argumenten toe
getopt.requires={0}: optie ''{1}'' heeft een argument nodig
getopt.unrecognized={0}: onbekende optie ''--{1}''
getopt.unrecognized2={0}: onbekende optie ''{1}{2}''
getopt.illegal={0}: niet-toegestane optie -- {1}
getopt.invalid={0}: onjuiste optie -- {1}
getopt.requires2={0}: optie heeft een argument nodig -- {1}
getopt.invalidValue=Ongeldige waarde {0} voor parameter 'has_arg'
getopt.ambigious={0}\: optie ''{1}'' is ambigu
getopt.arguments1={0}\: optie ''--{1}'' staat geen argumenten toe
getopt.arguments2={0}\: optie ''{1}{2}'' staat geen argumenten toe
getopt.requires={0}\: optie ''{1}'' heeft een argument nodig
getopt.unrecognized={0}\: onbekende optie ''--{1}''
getopt.unrecognized2={0}\: onbekende optie ''{1}{2}''
getopt.illegal={0}\: niet toegestane optie -- {1}
getopt.invalid={0}\: onjuiste optie -- {1}
getopt.requires2={0}\: optie heeft een argument nodig -- {1}
getopt.invalidValue=Ongeldige waarde {0} voor parameter 'has_arg'

View File

@@ -1,8 +1,8 @@
#**************************************************************************
#* MessagesBundle_pl.properties -- Polish language error messages
#* MessagesBundle.properties -- English language error messages
#*
#* Polish Messages Copyright (c) 2006 by Krzysztof Szyma?ski (sirch.s@gmail.com)
#* These messages are encoded in ISO-8859-2
#* Copyright (c) 1998 by William King (wrking@eng.sun.com) and
#* Aaron M. Renn (arenn@urbanophile.com)
#*
#* This program is free software; you can redistribute it and/or modify
#* it under the terms of the GNU Library General Public License as published
@@ -19,13 +19,13 @@
#* the Free Software Foundation Inc., 59 Temple Place - Suite 330,
#* Boston, MA 02111-1307 USA
#**************************************************************************/
getopt.ambigious={0}: opcja ''{1}''jest wieloznaczna
getopt.arguments1={0}: opcja ''--{1}'' nie akceptuje argumentu
getopt.arguments2={0}: opcja ''{1}{2}'' nie akceptuje argumentu
getopt.requires={0}: opcja ''{1}'' wymaga argumentu
getopt.unrecognized={0}: nierozpoznana opcja ''--{1}''
getopt.unrecognized2={0}: nierozpoznana opcja ''{1}{2}''
getopt.illegal={0}: nie dopuszczalna opcja --{1}
getopt.invalid={0}: b??dna opcja --{1}
getopt.requires2={0}: opcja --{1} oczekuje argumentu
getopt.invalidValue=Nie poprawna warto?? {0} argument 'has_arg'
getopt.ambigious={0}\: opcja ''{1}'' jest wieloznaczna
getopt.arguments1={0}\: opcja ''--{1}'' nie akceptuje argumentu
getopt.arguments2={0}\: opcja ''{1}{2}'' nie akceptuje argumentu
getopt.requires={0}\: opcja ''{1}'' wymaga argumentu
getopt.unrecognized={0}\: nierozpoznana opcja ''--{1}''
getopt.unrecognized2={0}\: nierozpoznana opcja ''{1}{2}''
getopt.illegal={0}\: niedopuszczalna opcja -- {1}
getopt.invalid={0}\: b\u0142\u0119dna opcja -- {1}
getopt.requires2={0}\: opcja wymaga argumentu -- {1}
getopt.invalidValue=Niepoprawna warto\u015b\u0107 {0} dla parametru 'has_arg'

View File

@@ -0,0 +1,31 @@
#**************************************************************************
#* MessagesBundle.properties -- English language error messages
#*
#* Copyright (c) 1998 by William King (wrking@eng.sun.com) and
#* Aaron M. Renn (arenn@urbanophile.com)
#*
#* This program is free software; you can redistribute it and/or modify
#* it under the terms of the GNU Library General Public License as published
#* by the Free Software Foundation; either version 2 of the License or
#* (at your option) any later version.
#*
#* This program is distributed in the hope that it will be useful, but
#* WITHOUT ANY WARRANTY; without even the implied warranty of
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#* GNU Library General Public License for more details.
#*
#* You should have received a copy of the GNU Library General Public License
#* along with this program; see the file COPYING.LIB. If not, write to
#* the Free Software Foundation Inc., 59 Temple Place - Suite 330,
#* Boston, MA 02111-1307 USA
#**************************************************************************/
getopt.ambigious={0}\: \u043e\u043f\u0446\u0456\u044f ''{1}'' \u043d\u0435\u043e\u0434\u043d\u043e\u0437\u043d\u0430\u0447\u043d\u0430
getopt.arguments1={0}\: \u043e\u043f\u0446\u0456\u044f ''--{1}'' \u043d\u0435 \u0432\u0438\u043c\u0430\u0433\u0430\u0454 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440
getopt.arguments2={0}\: \u043e\u043f\u0446\u0456\u044f ''{1}{2}'' \u043d\u0435 \u0432\u0438\u043c\u0430\u0433\u0430\u0454 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440
getopt.requires={0}\: \u043e\u043f\u0446\u0456\u044f ''{1}'' \u0432\u0438\u043c\u0430\u0433\u0430\u0454 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440
getopt.unrecognized={0}\: \u043d\u0435\u0432\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u0430 \u043e\u043f\u0446\u0456\u044f ''--{1}''
getopt.unrecognized2={0}\: \u043d\u0435\u0432\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u0430 \u043e\u043f\u0446\u0456\u044f ''{1}{2}''
getopt.illegal={0}\: \u043d\u0435\u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u0430 \u043e\u043f\u0446\u0456\u044f -- {1}
getopt.invalid={0}\: \u043d\u0435\u0434\u0456\u0439\u0441\u043d\u0430 \u043e\u043f\u0446\u0456\u044f -- {1}
getopt.requires2={0}\: \u043e\u043f\u0446\u0456\u044f \u0432\u0438\u043c\u0430\u0433\u0430\u0454 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 -- {1}
getopt.invalidValue=\u041d\u0435\u0434\u0456\u0439\u0441\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f {0} \u0434\u043b\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0443 'has_arg'

View File

@@ -0,0 +1,8 @@
<html>
<body>
<p>
GettextResource only, for ngettext().
Called only from net.i2p.util.Translate, which is where everything except ngettext is implemented.
</p>
</body>
</html>

View File

@@ -14,9 +14,20 @@ package net.i2p;
*
*/
public class CoreVersion {
/** deprecated */
public final static String ID = "Monotone";
public final static String VERSION = "0.9.17";
public final static String VERSION = "0.9.25";
/**
* For Vuze.
* @return VERSION
* @since 0.9.19
*/
public static String getVersion() {
return VERSION;
}
public static void main(String args[]) {
System.out.println("I2P Core version: " + VERSION);

View File

@@ -23,7 +23,6 @@ import net.i2p.data.Base64;
import net.i2p.data.RoutingKeyGenerator;
import net.i2p.internal.InternalClientManager;
import net.i2p.stat.StatManager;
import net.i2p.update.UpdateManager;
import net.i2p.util.Clock;
import net.i2p.util.ConcurrentHashSet;
import net.i2p.util.FileUtil;
@@ -85,6 +84,7 @@ public class I2PAppContext {
private RandomSource _random;
private KeyGenerator _keyGenerator;
protected KeyRing _keyRing; // overridden in RouterContext
@SuppressWarnings("deprecation")
private SimpleScheduler _simpleScheduler;
private SimpleTimer _simpleTimer;
private SimpleTimer2 _simpleTimer2;
@@ -406,9 +406,11 @@ public class I2PAppContext {
} else if (_tmpDir.mkdir()) {
_tmpDir.deleteOnExit();
} else {
System.err.println("Could not create temp dir " + _tmpDir.getAbsolutePath());
System.err.println("WARNING: Could not create temp dir " + _tmpDir.getAbsolutePath());
_tmpDir = new SecureDirectory(_routerDir, "tmp");
_tmpDir.mkdir();
_tmpDir.mkdirs();
if (!_tmpDir.exists())
System.err.println("ERROR: Could not create temp dir " + _tmpDir.getAbsolutePath());
}
}
}
@@ -531,7 +533,7 @@ public class I2PAppContext {
* @return set of Strings containing the names of defined system properties
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public Set<String> getPropertyNames() {
public Set<String> getPropertyNames() {
// clone to avoid ConcurrentModificationException
Set<String> names = new HashSet<String>((Set<String>) (Set) ((Properties) System.getProperties().clone()).keySet()); // TODO-Java6: s/keySet()/stringPropertyNames()/
if (_overrideProps != null)
@@ -742,6 +744,7 @@ public class I2PAppContext {
}
/** @deprecated used only by syndie */
@Deprecated
public HMAC256Generator hmac256() {
if (!_hmac256Initialized)
initializeHMAC256();
@@ -749,6 +752,7 @@ public class I2PAppContext {
}
/** @deprecated used only by syndie */
@Deprecated
private void initializeHMAC256() {
synchronized (_lock10) {
if (_hmac256 == null) {
@@ -937,13 +941,20 @@ public class I2PAppContext {
/**
* Use instead of SimpleScheduler.getInstance()
* @since 0.9 to replace static instance in the class
* @deprecated in 0.9.20, use simpleTimer2()
*/
@Deprecated
@SuppressWarnings("deprecation")
public SimpleScheduler simpleScheduler() {
if (!_simpleSchedulerInitialized)
initializeSimpleScheduler();
return _simpleScheduler;
}
/**
* @deprecated in 0.9.20
*/
@Deprecated
private void initializeSimpleScheduler() {
synchronized (_lock18) {
if (_simpleScheduler == null)
@@ -957,6 +968,7 @@ public class I2PAppContext {
* @since 0.9 to replace static instance in the class
* @deprecated use SimpleTimer2
*/
@Deprecated
public SimpleTimer simpleTimer() {
if (!_simpleTimerInitialized)
initializeSimpleTimer();
@@ -966,6 +978,7 @@ public class I2PAppContext {
/**
* @deprecated use SimpleTimer2
*/
@Deprecated
private void initializeSimpleTimer() {
synchronized (_lock19) {
if (_simpleTimer == null)

View File

@@ -53,7 +53,7 @@ public interface ClientApp {
public String getName();
/**
* The dislplay name of the ClientApp, used in user interfaces.
* The display name of the ClientApp, used in user interfaces.
* The app must translate.
* @return non-null
*/

View File

@@ -72,6 +72,8 @@ public interface I2PClient {
* the router how to handle the new session, and to configure the end to end
* encryption.
*
* As of 0.9.19, defaults in options are honored.
*
* @param destKeyStream location from which to read the Destination, PrivateKey, and SigningPrivateKey from,
* format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile}
* @param options set of options to configure the router with, if null will use System properties

View File

@@ -9,6 +9,8 @@ package net.i2p.client;
*
*/
import net.i2p.client.impl.I2PClientImpl;
/**
* Provide a means of hooking into an appropriate I2PClient implementation
*
@@ -21,4 +23,4 @@ public class I2PClientFactory {
public static I2PClient createClient() {
return new I2PClientImpl();
}
}
}

View File

@@ -9,6 +9,8 @@ package net.i2p.client;
*
*/
import java.io.InputStream;
import java.util.List;
import java.util.Properties;
import java.util.Set;
@@ -16,12 +18,13 @@ import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.data.PrivateKey;
import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag;
import net.i2p.data.SigningPrivateKey;
/**
* <p>Define the standard means of sending and receiving messages on the
* I2P network by using the I2CP (the client protocol). This is done over a
* bidirectional TCP socket and never sends any private keys.
* bidirectional TCP socket.
*
* End to end encryption in I2PSession was disabled in release 0.6.
*
@@ -96,7 +99,7 @@ public interface I2PSession {
* objects that were sent along side the given keyUsed.
* @return success
*/
public boolean sendMessage(Destination dest, byte[] payload, SessionKey keyUsed, Set tagsSent) throws I2PSessionException;
public boolean sendMessage(Destination dest, byte[] payload, SessionKey keyUsed, Set<SessionTag> tagsSent) throws I2PSessionException;
/**
* End-to-End Crypto is disabled, tags and keys are ignored.
@@ -104,7 +107,7 @@ public interface I2PSession {
* @param tagsSent UNUSED, IGNORED.
* @return success
*/
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent) throws I2PSessionException;
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set<SessionTag> tagsSent) throws I2PSessionException;
/**
* End-to-End Crypto is disabled, tags and keys are ignored.
@@ -114,7 +117,7 @@ public interface I2PSession {
* @return success
* @since 0.7.1
*/
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expire) throws I2PSessionException;
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set<SessionTag> tagsSent, long expire) throws I2PSessionException;
/**
* See I2PSessionMuxedImpl for proto/port details.
@@ -131,7 +134,7 @@ public interface I2PSession {
* @return success
* @since 0.7.1
*/
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent,
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set<SessionTag> tagsSent,
int proto, int fromPort, int toPort) throws I2PSessionException;
/**
@@ -150,7 +153,7 @@ public interface I2PSession {
* @return success
* @since 0.7.1
*/
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expire,
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set<SessionTag> tagsSent, long expire,
int proto, int fromPort, int toPort) throws I2PSessionException;
/**
@@ -169,7 +172,7 @@ public interface I2PSession {
* @return success
* @since 0.8.4
*/
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expire,
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set<SessionTag> tagsSent, long expire,
int proto, int fromPort, int toPort, int flags) throws I2PSessionException;
/**
@@ -247,6 +250,26 @@ public interface I2PSession {
*
*/
public void destroySession() throws I2PSessionException;
/**
* @return a new subsession, non-null
* @param privateKeyStream null for transient, if non-null must have same encryption keys as primary session
* and different signing keys
* @param opts subsession options if any, may be null
* @since 0.9.21
*/
public I2PSession addSubsession(InputStream privateKeyStream, Properties opts) throws I2PSessionException;
/**
* @since 0.9.21
*/
public void removeSubsession(I2PSession session);
/**
* @return a list of subsessions, non-null, does not include the primary session
* @since 0.9.21
*/
public List<I2PSession> getSubsessions();
/**
* Actually connect the session and start receiving/sending messages
@@ -257,7 +280,7 @@ public interface I2PSession {
/**
* Have we closed the session?
*
* @return true if the session is closed
* @return true if the session is closed, OR connect() has not been called yet
*/
public boolean isClosed();

View File

@@ -12,6 +12,7 @@ import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.impl.I2PSimpleSession;
import net.i2p.crypto.SigType;
import net.i2p.data.Certificate;
import net.i2p.data.Destination;
@@ -26,6 +27,7 @@ public class I2PSimpleClient implements I2PClient {
* @deprecated Don't do this
* @throws UnsupportedOperationException always
*/
@Deprecated
public Destination createDestination(OutputStream destKeyStream) throws I2PException, IOException {
throw new UnsupportedOperationException();
}
@@ -35,6 +37,7 @@ public class I2PSimpleClient implements I2PClient {
* @throws UnsupportedOperationException always
* @since 0.9.12
*/
@Deprecated
public Destination createDestination(OutputStream destKeyStream, SigType type) throws I2PException, IOException {
throw new UnsupportedOperationException();
}
@@ -43,6 +46,7 @@ public class I2PSimpleClient implements I2PClient {
* @deprecated Don't do this
* @throws UnsupportedOperationException always
*/
@Deprecated
public Destination createDestination(OutputStream destKeyStream, Certificate cert) throws I2PException, IOException {
throw new UnsupportedOperationException();
}

View File

@@ -97,7 +97,8 @@ public final class I2PDatagramDissector {
}
} catch (IOException e) {
Log log = I2PAppContext.getGlobalContext().logManager().getLog(I2PDatagramDissector.class);
log.error("Caught IOException - INCONSISTENT STATE!", e);
log.error("Error loading datagram", e);
throw new DataFormatException("Error loading datagram", e);
//} catch(AssertionError e) {
// Log log = I2PAppContext.getGlobalContext().logManager().getLog(I2PDatagramDissector.class);
// log.error("Assertion failed!", e);

View File

@@ -1,4 +1,4 @@
package net.i2p.client;
package net.i2p.client.impl;
/*
* Released into the public domain

View File

@@ -1,4 +1,4 @@
package net.i2p.client;
package net.i2p.client.impl;
import java.io.BufferedOutputStream;
import java.io.IOException;
@@ -9,6 +9,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import net.i2p.I2PAppContext;
import net.i2p.client.I2PSessionException;
import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.data.i2cp.I2CPMessageException;
import net.i2p.internal.PoisonI2CPMessage;

View File

@@ -1,4 +1,4 @@
package net.i2p.client;
package net.i2p.client.impl;
/*
* Released into the public domain

View File

@@ -1,4 +1,4 @@
package net.i2p.client;
package net.i2p.client.impl;
/*
* free (adj.): unencumbered; not under the control of others
@@ -10,6 +10,7 @@ package net.i2p.client;
*/
import net.i2p.I2PAppContext;
import net.i2p.client.I2PSessionException;
import net.i2p.data.i2cp.DisconnectMessage;
import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.util.I2PAppThread;

View File

@@ -1,4 +1,4 @@
package net.i2p.client;
package net.i2p.client.impl;
/*
* free (adj.): unencumbered; not under the control of others

View File

@@ -1,4 +1,4 @@
package net.i2p.client;
package net.i2p.client.impl;
/*
* Released into the public domain

View File

@@ -1,4 +1,4 @@
package net.i2p.client;
package net.i2p.client.impl;
/*
* free (adj.): unencumbered; not under the control of others

View File

@@ -1,4 +1,4 @@
package net.i2p.client;
package net.i2p.client.impl;
/*
* free (adj.): unencumbered; not under the control of others
@@ -15,6 +15,8 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import net.i2p.I2PAppContext;
import net.i2p.client.I2PSessionException;
import net.i2p.client.SendMessageOptions;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.data.LeaseSet;
@@ -34,6 +36,7 @@ import net.i2p.data.i2cp.ReportAbuseMessage;
import net.i2p.data.i2cp.SendMessageMessage;
import net.i2p.data.i2cp.SendMessageExpiresMessage;
import net.i2p.data.i2cp.SessionConfig;
import net.i2p.data.i2cp.SessionId;
import net.i2p.util.Log;
/**
@@ -99,7 +102,7 @@ class I2CPMessageProducer {
if (_log.shouldLog(Log.DEBUG)) _log.debug("config signed");
msg.setSessionConfig(cfg);
if (_log.shouldLog(Log.DEBUG)) _log.debug("config loaded into message");
session.sendMessage(msg);
session.sendMessage_unchecked(msg);
if (_log.shouldLog(Log.DEBUG)) _log.debug("config message sent");
}
@@ -111,7 +114,7 @@ class I2CPMessageProducer {
if (session.isClosed()) return;
DestroySessionMessage dmsg = new DestroySessionMessage();
dmsg.setSessionId(session.getSessionId());
session.sendMessage(dmsg);
session.sendMessage_unchecked(dmsg);
// use DisconnectMessage only if we fail and drop connection...
// todo: update the code to fire off DisconnectMessage on socket error
//DisconnectMessage msg = new DisconnectMessage();
@@ -129,7 +132,7 @@ class I2CPMessageProducer {
* @param newKey unused - no end-to-end crypto
*/
public void sendMessage(I2PSessionImpl session, Destination dest, long nonce, byte[] payload, SessionTag tag,
SessionKey key, Set tags, SessionKey newKey, long expires) throws I2PSessionException {
SessionKey key, Set<SessionTag> tags, SessionKey newKey, long expires) throws I2PSessionException {
sendMessage(session, dest, nonce, payload, expires, 0);
}
@@ -154,7 +157,12 @@ class I2CPMessageProducer {
} else
msg = new SendMessageMessage();
msg.setDestination(dest);
msg.setSessionId(session.getSessionId());
SessionId sid = session.getSessionId();
if (sid == null) {
_log.error(session.toString() + " send message w/o session", new Exception());
return;
}
msg.setSessionId(sid);
msg.setNonce(nonce);
Payload data = createPayload(dest, payload, null, null, null, null);
msg.setPayload(data);
@@ -176,7 +184,12 @@ class I2CPMessageProducer {
return;
SendMessageMessage msg = new SendMessageExpiresMessage(options);
msg.setDestination(dest);
msg.setSessionId(session.getSessionId());
SessionId sid = session.getSessionId();
if (sid == null) {
_log.error(session.toString() + " send message w/o session", new Exception());
return;
}
msg.setSessionId(sid);
msg.setNonce(nonce);
Payload data = createPayload(dest, payload, null, null, null, null);
msg.setPayload(data);
@@ -352,8 +365,13 @@ class I2CPMessageProducer {
msg.setLeaseSet(leaseSet);
msg.setPrivateKey(priv);
msg.setSigningPrivateKey(signingPriv);
msg.setSessionId(session.getSessionId());
session.sendMessage(msg);
SessionId sid = session.getSessionId();
if (sid == null) {
_log.error(session.toString() + " create LS w/o session", new Exception());
return;
}
msg.setSessionId(sid);
session.sendMessage_unchecked(msg);
}
/**
@@ -381,7 +399,12 @@ class I2CPMessageProducer {
throw new I2PSessionException("Unable to sign the session config", dfe);
}
msg.setSessionConfig(cfg);
msg.setSessionId(session.getSessionId());
SessionId sid = session.getSessionId();
if (sid == null) {
_log.error(session.toString() + " update config w/o session", new Exception());
return;
}
msg.setSessionId(sid);
session.sendMessage(msg);
}
}

View File

@@ -1,4 +1,4 @@
package net.i2p.client;
package net.i2p.client.impl;
/*
* free (adj.): unencumbered; not under the control of others
@@ -17,6 +17,9 @@ import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.crypto.KeyGenerator;
import net.i2p.crypto.SigType;
import net.i2p.data.Certificate;
@@ -36,7 +39,7 @@ import net.i2p.util.RandomSource;
*
* @author jrandom
*/
class I2PClientImpl implements I2PClient {
public class I2PClientImpl implements I2PClient {
/**
* Create a destination with a DSA 1024/160 signature type and a null certificate.

View File

@@ -1,4 +1,4 @@
package net.i2p.client;
package net.i2p.client.impl;
/*
* free (adj.): unencumbered; not under the control of others

View File

@@ -1,9 +1,13 @@
package net.i2p.client;
package net.i2p.client.impl;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
import net.i2p.I2PAppContext;
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.client.I2PSessionListener;
import net.i2p.client.I2PSessionMuxedListener;
import net.i2p.util.Log;
/*
@@ -74,7 +78,9 @@ public class I2PSessionDemultiplexer implements I2PSessionMuxedListener {
* (Streaming lib)
*/
public void addListener(I2PSessionListener l, int proto, int port) {
_listeners.put(key(proto, port), new NoPortsListener(l));
I2PSessionListener old = _listeners.put(key(proto, port), new NoPortsListener(l));
if (old != null && _log.shouldLog(Log.WARN))
_log.warn("Listener " + l + " replaces " + old + " for proto: " + proto + " port: " + port);
}
/**
@@ -82,7 +88,9 @@ public class I2PSessionDemultiplexer implements I2PSessionMuxedListener {
* UDP perhaps
*/
public void addMuxedListener(I2PSessionMuxedListener l, int proto, int port) {
_listeners.put(key(proto, port), l);
I2PSessionListener old = _listeners.put(key(proto, port), l);
if (old != null && _log.shouldLog(Log.WARN))
_log.warn("Listener " + l + " replaces " + old + " for proto: " + proto + " port: " + port);
}
public void removeListener(int proto, int port) {

View File

@@ -1,4 +1,4 @@
package net.i2p.client;
package net.i2p.client.impl;
/*
* free (adj.): unencumbered; not under the control of others
@@ -23,11 +23,17 @@ import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import net.i2p.CoreVersion;
import net.i2p.I2PAppContext;
import net.i2p.client.DomainSocketFactory;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.client.I2PSessionListener;
import net.i2p.data.Base32;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
@@ -36,13 +42,16 @@ import net.i2p.data.LeaseSet;
import net.i2p.data.PrivateKey;
import net.i2p.data.SigningPrivateKey;
import net.i2p.data.i2cp.DestLookupMessage;
import net.i2p.data.i2cp.DestReplyMessage;
import net.i2p.data.i2cp.GetBandwidthLimitsMessage;
import net.i2p.data.i2cp.GetDateMessage;
import net.i2p.data.i2cp.HostLookupMessage;
import net.i2p.data.i2cp.HostReplyMessage;
import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.data.i2cp.I2CPMessageReader;
import net.i2p.data.i2cp.MessagePayloadMessage;
import net.i2p.data.i2cp.SessionId;
import net.i2p.data.i2cp.SessionStatusMessage;
import net.i2p.internal.I2CPMessageQueue;
import net.i2p.internal.InternalClientManager;
import net.i2p.internal.QueuedI2CPMessageReader;
@@ -59,9 +68,14 @@ import net.i2p.util.VersionComparator;
* Implementation of an I2P session running over TCP. This class is NOT thread safe -
* only one thread should send messages at any given time
*
* Public only for clearCache().
* Except for methods defined in I2PSession and I2CPMessageEventListener,
* not maintained as a public API, not for external use.
* Use I2PClientFactory to get an I2PClient and then createSession().
*
* @author jrandom
*/
abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessageEventListener {
public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessageEventListener {
protected final Log _log;
/** who we are */
private final Destination _myDestination;
@@ -74,7 +88,16 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
/** this session's Id */
private SessionId _sessionId;
/** currently granted lease set, or null */
private volatile LeaseSet _leaseSet;
protected volatile LeaseSet _leaseSet;
// subsession stuff
// registered subsessions
private final List<SubSession> _subsessions;
// established subsessions
private final ConcurrentHashMap<SessionId, SubSession> _subsessionMap;
private final Object _subsessionLock = new Object();
private static final String MIN_SUBSESSION_VERSION = "0.9.21";
private volatile boolean _routerSupportsSubsessions;
/** hostname of router - will be null if in RouterContext */
protected final String _hostname;
@@ -114,12 +137,14 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
protected final I2PAppContext _context;
/** monitor for waiting until a lease set has been granted */
private final Object _leaseSetWait = new Object();
protected final Object _leaseSetWait = new Object();
/**
* @since 0.9.8
*/
protected enum State {
/** @since 0.9.20 */
INIT,
OPENING,
/** @since 0.9.11 */
GOTDATE,
@@ -128,7 +153,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
CLOSED
}
private State _state = State.CLOSED;
protected State _state = State.INIT;
protected final Object _stateLock = new Object();
/**
@@ -145,11 +170,12 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
private volatile boolean _routerSupportsFastReceive;
private volatile boolean _routerSupportsHostLookup;
protected static final int CACHE_MAX_SIZE = SystemVersion.isAndroid() ? 32 : 128;
/**
* Since 0.9.11, key is either a Hash or a String
* @since 0.8.9
*/
private static final Map<Object, Destination> _lookupCache = new LHMCache<Object, Destination>(64);
private static final Map<Object, Destination> _lookupCache = new LHMCache<Object, Destination>(CACHE_MAX_SIZE);
private static final String MIN_HOST_LOOKUP_VERSION = "0.9.11";
private static final boolean TEST_LOOKUP = false;
@@ -179,10 +205,12 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
TEST_LOOKUP ||
(routerVersion != null && routerVersion.length() > 0 &&
VersionComparator.comp(routerVersion, MIN_HOST_LOOKUP_VERSION) >= 0);
_routerSupportsSubsessions = _context.isRouterContext() ||
(routerVersion != null && routerVersion.length() > 0 &&
VersionComparator.comp(routerVersion, MIN_SUBSESSION_VERSION) >= 0);
synchronized (_stateLock) {
if (_state == State.OPENING) {
_state = State.GOTDATE;
_stateLock.notifyAll();
changeState(State.GOTDATE);
}
}
}
@@ -196,18 +224,42 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
*/
protected I2PSessionImpl(I2PAppContext context, Properties options,
I2PClientMessageHandlerMap handlerMap) {
this(context, options, handlerMap, false);
this(context, options, handlerMap, null, false);
}
/*
* For extension by SubSession via I2PSessionMuxedImpl and I2PSessionImpl2
*
* @param destKeyStream stream containing the private key data,
* format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile}
* @param options set of options to configure the router with, if null will use System properties
* @since 0.9.21
*/
protected I2PSessionImpl(I2PSessionImpl primary, InputStream destKeyStream, Properties options) throws I2PSessionException {
this(primary.getContext(), options, primary.getHandlerMap(), primary.getProducer(), true);
_availabilityNotifier = new AvailabilityNotifier();
try {
readDestination(destKeyStream);
} catch (DataFormatException dfe) {
throw new I2PSessionException("Error reading the destination key stream", dfe);
} catch (IOException ioe) {
throw new I2PSessionException("Error reading the destination key stream", ioe);
}
}
/**
* Basic setup of finals
* @since 0.9.7
*/
private I2PSessionImpl(I2PAppContext context, Properties options,
I2PClientMessageHandlerMap handlerMap, boolean hasDest) {
I2PClientMessageHandlerMap handlerMap,
I2CPMessageProducer producer,
boolean hasDest) {
_context = context;
_handlerMap = handlerMap;
_log = context.logManager().getLog(getClass());
_subsessions = new CopyOnWriteArrayList<SubSession>();
_subsessionMap = new ConcurrentHashMap<SessionId, SubSession>(4);
if (options == null)
options = (Properties) System.getProperties().clone();
_options = loadConfig(options);
@@ -215,7 +267,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
_portNum = getPort();
_fastReceive = Boolean.parseBoolean(_options.getProperty(I2PClient.PROP_FAST_RECEIVE));
if (hasDest) {
_producer = new I2CPMessageProducer(context);
_producer = producer;
_availableMessages = new ConcurrentHashMap<Long, MessagePayloadMessage>();
_myDestination = new Destination();
_privateKey = new PrivateKey();
@@ -229,19 +281,22 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
}
_routerSupportsFastReceive = _context.isRouterContext();
_routerSupportsHostLookup = _context.isRouterContext();
_routerSupportsSubsessions = _context.isRouterContext();
}
/**
* Create a new session, reading the Destination, PrivateKey, and SigningPrivateKey
* from the destKeyStream, and using the specified options to connect to the router
*
* As of 0.9.19, defaults in options are honored.
*
* @param destKeyStream stream containing the private key data,
* format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile}
* @param options set of options to configure the router with, if null will use System properties
* @throws I2PSessionException if there is a problem loading the private keys or
* @throws I2PSessionException if there is a problem loading the private keys
*/
public I2PSessionImpl(I2PAppContext context, InputStream destKeyStream, Properties options) throws I2PSessionException {
this(context, options, new I2PClientMessageHandlerMap(context), true);
this(context, options, new I2PClientMessageHandlerMap(context), new I2CPMessageProducer(context), true);
_availabilityNotifier = new AvailabilityNotifier();
try {
readDestination(destKeyStream);
@@ -251,6 +306,69 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
throw new I2PSessionException("Error reading the destination key stream", ioe);
}
}
/**
* Router must be connected or was connected... for now.
*
* @return a new subsession, non-null
* @param privateKeyStream null for transient, if non-null must have same encryption keys as primary session
* and different signing keys
* @param opts subsession options if any, may be null
* @since 0.9.21
*/
public I2PSession addSubsession(InputStream privateKeyStream, Properties opts) throws I2PSessionException {
if (!_routerSupportsSubsessions)
throw new I2PSessionException("Router does not support subsessions");
SubSession sub;
synchronized(_subsessionLock) {
if (_subsessions.size() > _subsessionMap.size())
throw new I2PSessionException("Subsession request already pending");
sub = new SubSession(this, privateKeyStream, opts);
for (SubSession ss : _subsessions) {
if (ss.getDecryptionKey().equals(sub.getDecryptionKey()) &&
ss.getPrivateKey().equals(sub.getPrivateKey())) {
throw new I2PSessionException("Dup subsession");
}
}
_subsessions.add(sub);
}
synchronized (_stateLock) {
if (_state == State.OPEN) {
_producer.connect(sub);
} // else will be called in connect()
}
return sub;
}
/**
* @since 0.9.21
*/
public void removeSubsession(I2PSession session) {
if (!(session instanceof SubSession))
return;
synchronized(_subsessionLock) {
_subsessions.remove(session);
SessionId id = ((SubSession) session).getSessionId();
if (id != null)
_subsessionMap.remove(id);
/// tell the subsession
try {
// doesn't really throw
session.destroySession();
} catch (I2PSessionException ise) {}
}
}
/**
* @return a list of subsessions, non-null, does not include the primary session
* @since 0.9.21
*/
public List<I2PSession> getSubsessions() {
synchronized(_subsessionLock) {
return new ArrayList<I2PSession>(_subsessions);
}
}
/**
* Parse the config for anything we know about.
@@ -314,11 +432,14 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
}
}
/** save some memory, don't pass along the pointless properties */
/**
* Save some memory, don't pass along the pointless properties.
* As of 0.9.19, defaults from options will be promoted to real values in rv.
* @return a new Properties without defaults
*/
private Properties filter(Properties options) {
Properties rv = new Properties();
for (Object oKey : options.keySet()) { // TODO-Java6: s/keySet()/stringPropertyNames()/
String key = (String) oKey;
for (String key : options.stringPropertyNames()) {
if (key.startsWith("java.") ||
key.startsWith("user.") ||
key.startsWith("os.") ||
@@ -388,6 +509,8 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
}
protected void changeState(State state) {
if (_log.shouldInfo())
_log.info(getPrefix() + "Change state to " + state);
synchronized (_stateLock) {
_state = state;
_stateLock.notifyAll();
@@ -424,6 +547,9 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
boolean loop = true;
while (loop) {
switch (_state) {
case INIT:
loop = false;
break;
case CLOSED:
if (wasOpening)
throw new I2PSessionException("connect by other thread failed");
@@ -478,6 +604,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
try {
I2PSSLSocketFactory fact = new I2PSSLSocketFactory(_context, false, "certificates/i2cp");
_socket = fact.createSocket(_hostname, _portNum);
_socket.setKeepAlive(true);
} catch (GeneralSecurityException gse) {
IOException ioe = new IOException("SSL Fail");
ioe.initCause(gse);
@@ -485,6 +612,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
}
} else {
_socket = new Socket(_hostname, _portNum);
_socket.setKeepAlive(true);
}
// _socket.setSoTimeout(1000000); // Uhmmm we could really-really use a real timeout, and handle it.
OutputStream out = _socket.getOutputStream();
@@ -507,7 +635,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
auth.setProperty(PROP_USER, _options.getProperty(PROP_USER));
auth.setProperty(PROP_PW, _options.getProperty(PROP_PW));
}
sendMessage(new GetDateMessage(CoreVersion.VERSION, auth));
sendMessage_unchecked(new GetDateMessage(CoreVersion.VERSION, auth));
waitForDate();
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Before producer.connect()");
@@ -536,12 +664,31 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
startIdleMonitor();
startVerifyUsage();
success = true;
// now send CreateSessionMessages for all subsessions, one at a time, must wait for each response
synchronized(_subsessionLock) {
for (SubSession ss : _subsessions) {
if (_log.shouldLog(Log.INFO))
_log.info(getPrefix() + "Connecting subsession " + ss);
_producer.connect(ss);
}
}
} catch (InterruptedException ie) {
throw new I2PSessionException("Interrupted", ie);
} catch (UnknownHostException uhe) {
throw new I2PSessionException(getPrefix() + "Cannot connect to the router on " + _hostname + ':' + _portNum, uhe);
} catch (IOException ioe) {
throw new I2PSessionException(getPrefix() + "Cannot connect to the router on " + _hostname + ':' + _portNum, ioe);
// Generate the best error message as this will be logged
String msg;
if (_context.isRouterContext())
msg = "Failed to build tunnels";
else if (SystemVersion.isAndroid() &&
Boolean.parseBoolean(_options.getProperty(PROP_DOMAIN_SOCKET)))
msg = "Failed to bind to the router and build tunnels";
else
msg = "Cannot connect to the router on " + _hostname + ':' + _portNum + " and build tunnels";
throw new I2PSessionException(getPrefix() + msg, ioe);
} finally {
if (success) {
changeState(State.OPEN);
@@ -571,8 +718,8 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
synchronized(_stateLock) {
if (_state == State.GOTDATE)
break;
if (_state != State.OPENING)
throw new IOException("Socket closed");
if (_state != State.OPENING && _state != State.INIT)
throw new IOException("Socket closed, state=" + _state);
// InterruptedException caught by caller
_stateLock.wait(1000);
}
@@ -599,7 +746,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
* Report abuse with regards to the given messageId
*/
public void reportAbuse(int msgId, int severity) throws I2PSessionException {
if (isClosed()) throw new I2PSessionException(getPrefix() + "Already closed");
verifyOpen();
_producer.reportAbuse(this, msgId, severity);
}
@@ -725,7 +872,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
if ((duration > 100) && _log.shouldLog(Log.INFO))
_log.info("Message availability notification for " + msgId.intValue() + " took "
+ duration + " to " + _sessionListener);
} catch (Exception e) {
} catch (RuntimeException e) {
_log.log(Log.CRIT, "Error notifying app of message availability", e);
}
} else {
@@ -739,19 +886,83 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
/**
* The I2CPMessageEventListener callback.
* Recieve notification of some I2CP message and handle it if possible.
*
* We route the message based on message type AND session ID.
*
* The following types never contain a session ID and are not routable to
* a subsession:
* BandwidthLimitsMessage, DestReplyMessage
*
* The following types may not contain a valid session ID
* even when intended for a subsession, so we must take special care:
* SessionStatusMessage
*
* @param reader unused
*/
public void messageReceived(I2CPMessageReader reader, I2CPMessage message) {
I2CPMessageHandler handler = _handlerMap.getHandler(message.getType());
if (handler == null) {
if (_log.shouldLog(Log.WARN))
_log.warn(getPrefix() + "Unknown message or unhandleable message received: type = "
+ message.getType());
int type = message.getType();
SessionId id = message.sessionId();
SessionId currId = _sessionId;
if (id == null || id.equals(currId) ||
(currId == null && id != null && type == SessionStatusMessage.MESSAGE_TYPE) ||
((id == null || id.getSessionId() == 65535) &&
(type == HostReplyMessage.MESSAGE_TYPE || type == DestReplyMessage.MESSAGE_TYPE))) {
// it's for us
I2CPMessageHandler handler = _handlerMap.getHandler(type);
if (handler != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix() + "Message received of type " + type
+ " to be handled by " + handler.getClass().getSimpleName());
handler.handleMessage(message, this);
} else {
if (_log.shouldLog(Log.WARN))
_log.warn(getPrefix() + "Unknown message or unhandleable message received: type = "
+ type);
}
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix() + "Message received of type " + message.getType()
+ " to be handled by " + handler.getClass().getSimpleName());
handler.handleMessage(message, this);
SubSession sub = _subsessionMap.get(id);
if (sub != null) {
// it's for a subsession
if (_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix() + "Message received of type " + type
+ " to be handled by " + sub);
sub.messageReceived(reader, message);
} else if (id != null && type == SessionStatusMessage.MESSAGE_TYPE) {
// look for a subsession without a session
synchronized (_subsessionLock) {
for (SubSession sess : _subsessions) {
if (sess.getSessionId() == null) {
sess.messageReceived(reader, message);
id = sess.getSessionId();
if (id != null) {
if (id.equals(_sessionId)) {
// shouldnt happen
sess.setSessionId(null);
if (_log.shouldLog(Log.WARN))
_log.warn("Dup or our session id " + id);
} else {
SubSession old = _subsessionMap.putIfAbsent(id, sess);
if (old != null) {
// shouldnt happen
sess.setSessionId(null);
if (_log.shouldLog(Log.WARN))
_log.warn("Dup session id " + id);
}
}
}
return;
}
if (_log.shouldLog(Log.WARN))
_log.warn(getPrefix() + "No session " + id + " to handle message: type = "
+ type);
}
}
} else {
// it's for nobody
if (_log.shouldLog(Log.WARN))
_log.warn(getPrefix() + "No session " + id + " to handle message: type = "
+ type);
}
}
}
@@ -787,7 +998,21 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
I2CPMessageProducer getProducer() { return _producer; }
/**
* Retrieve the configuration options
* For Subsessions
* @since 0.9.21
*/
I2PClientMessageHandlerMap getHandlerMap() { return _handlerMap; }
/**
* For Subsessions
* @since 0.9.21
*/
I2PAppContext getContext() { return _context; }
/**
* Retrieve the configuration options, filtered.
* All defaults passed in via constructor have been promoted to the primary map.
*
* @return non-null, if insantiated with null options, this will be the System properties.
*/
Properties getOptions() { return _options; }
@@ -803,11 +1028,44 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
/**
* Has the session been closed (or not yet connected)?
* False when open and during transitions. Unsynchronized.
* False when open and during transitions. Synchronized.
*/
public boolean isClosed() {
synchronized (_stateLock) {
return _state == State.CLOSED;
return _state == State.CLOSED || _state == State.INIT;
}
}
/**
* Throws I2PSessionException if uninitialized, closed or closing.
* Blocks if opening.
*
* @since 0.9.23
*/
protected void verifyOpen() throws I2PSessionException {
synchronized (_stateLock) {
while (true) {
switch (_state) {
case INIT:
throw new I2PSessionException("Not open, must call connect() first");
case OPENING: // fall thru
case GOTDATE:
try {
_stateLock.wait(5*1000);
continue;
} catch (InterruptedException ie) {
throw new I2PSessionException("Interrupted", ie);
}
case OPEN:
return;
case CLOSING: // fall thru
case CLOSED:
throw new I2PSessionException("Already closed");
}
}
}
}
@@ -818,9 +1076,20 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
* @throws I2PSessionException if the message is malformed or there is an error writing it out
*/
void sendMessage(I2CPMessage message) throws I2PSessionException {
if (isClosed()) {
throw new I2PSessionException("Already closed");
} else if (_queue != null) {
verifyOpen();
sendMessage_unchecked(message);
}
/**
* Deliver an I2CP message to the router.
* Does NOT check state. Call only from connect() or other methods that need to
* send messages when not in OPEN state.
*
* @throws I2PSessionException if the message is malformed or there is an error writing it out
* @since 0.9.23
*/
void sendMessage_unchecked(I2CPMessage message) throws I2PSessionException {
if (_queue != null) {
// internal
try {
if (!_queue.offer(message, MAX_SEND_WAIT))
@@ -828,10 +1097,13 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
} catch (InterruptedException ie) {
throw new I2PSessionException("Interrupted", ie);
}
} else if (_writer == null) {
throw new I2PSessionException("Already closed");
} else {
_writer.addMessage(message);
ClientWriterRunner writer = _writer;
if (writer == null) {
throw new I2PSessionException("Already closed or not open");
} else {
writer.addMessage(message);
}
}
}
@@ -873,7 +1145,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
*/
public void destroySession(boolean sendDisconnect) {
synchronized(_stateLock) {
if (_state == State.CLOSING || _state == State.CLOSED)
if (_state == State.CLOSING || _state == State.CLOSED || _state == State.INIT)
return;
changeState(State.CLOSING);
}
@@ -892,6 +1164,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
if (_availabilityNotifier != null)
_availabilityNotifier.stopNotifying();
closeSocket();
_subsessionMap.clear();
if (_sessionListener != null) _sessionListener.disconnected(this);
}
@@ -906,6 +1179,13 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
locked_closeSocket();
changeState(State.CLOSED);
}
synchronized (_subsessionLock) {
for (SubSession sess : _subsessions) {
sess.changeState(State.CLOSED);
sess.setSessionId(null);
sess.setLeaseSet(null);
}
}
}
/**
@@ -935,6 +1215,8 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
_socket = null; // so when propogateError calls closeSocket, it doesnt loop
}
}
setSessionId(null);
setLeaseSet(null);
}
/**
@@ -953,7 +1235,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
protected void disconnect() {
State oldState;
synchronized(_stateLock) {
if (_state == State.CLOSING || _state == State.CLOSED)
if (_state == State.CLOSING || _state == State.CLOSED || _state == State.INIT)
return;
oldState = _state;
changeState(State.CLOSING);
@@ -1016,13 +1298,15 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
protected String getPrefix() {
StringBuilder buf = new StringBuilder();
buf.append('[');
buf.append(_state.toString()).append(' ');
String s = _options.getProperty("inbound.nickname");
if (s != null)
buf.append(s);
else
buf.append(getClass().getSimpleName());
if (_sessionId != null)
buf.append(" #").append(_sessionId.getSessionId());
SessionId id = _sessionId;
if (id != null)
buf.append(" #").append(id.getSessionId());
buf.append("]: ");
return buf.toString();
}
@@ -1142,6 +1426,13 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
}
}
/** @since 0.9.20 */
public static void clearCache() {
synchronized (_lookupCache) {
_lookupCache.clear();
}
}
/**
* Blocking. Waits a max of 10 seconds by default.
* See lookupDest with maxWait parameter to change.
@@ -1166,10 +1457,15 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
if (rv != null)
return rv;
}
if (isClosed()) {
if (_log.shouldLog(Log.INFO))
_log.info("Session closed, cannot lookup " + h);
return null;
synchronized (_stateLock) {
// not before GOTDATE
if (_state == State.CLOSED ||
_state == State.INIT ||
_state == State.OPENING) {
if (_log.shouldLog(Log.INFO))
_log.info("Session closed, cannot lookup " + h);
return null;
}
}
LookupWaiter waiter;
long nonce;
@@ -1189,11 +1485,11 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
SessionId id = _sessionId;
if (id == null)
id = new SessionId(65535);
sendMessage(new HostLookupMessage(id, h, nonce, maxWait));
sendMessage_unchecked(new HostLookupMessage(id, h, nonce, maxWait));
} else {
if (_log.shouldLog(Log.INFO))
_log.info("Sending DestLookup for " + h);
sendMessage(new DestLookupMessage(h));
sendMessage_unchecked(new DestLookupMessage(h));
}
try {
synchronized (waiter) {
@@ -1281,7 +1577,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
SessionId id = _sessionId;
if (id == null)
id = new SessionId(65535);
sendMessage(new HostLookupMessage(id, name, nonce, maxWait));
sendMessage_unchecked(new HostLookupMessage(id, name, nonce, maxWait));
try {
synchronized (waiter) {
waiter.wait(maxWait);
@@ -1305,9 +1601,17 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
* @return null on failure
*/
public int[] bandwidthLimits() throws I2PSessionException {
if (isClosed())
return null;
sendMessage(new GetBandwidthLimitsMessage());
synchronized (_stateLock) {
// not before GOTDATE
if (_state == State.CLOSED ||
_state == State.INIT ||
_state == State.OPENING) {
if (_log.shouldLog(Log.INFO))
_log.info("Session closed, cannot get bw limits");
return null;
}
}
sendMessage_unchecked(new GetBandwidthLimitsMessage());
try {
synchronized (_bwReceivedLock) {
_bwReceivedLock.wait(5*1000);
@@ -1346,7 +1650,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
boolean close = Boolean.parseBoolean(_options.getProperty("i2cp.closeOnIdle"));
if (reduce || close) {
updateActivity();
_context.simpleScheduler().addEvent(new SessionIdleTimer(_context, this, reduce, close), SessionIdleTimer.MINIMUM_TIME);
_context.simpleTimer2().addEvent(new SessionIdleTimer(_context, this, reduce, close), SessionIdleTimer.MINIMUM_TIME);
}
}

View File

@@ -1,4 +1,4 @@
package net.i2p.client;
package net.i2p.client.impl;
/*
* free (adj.): unencumbered; not under the control of others
@@ -20,9 +20,16 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import net.i2p.I2PAppContext;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PSessionException;
import net.i2p.client.I2PSessionListener;
import net.i2p.client.I2PSessionMuxedListener;
import net.i2p.client.SendMessageOptions;
import net.i2p.client.SendMessageStatusListener;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag;
import net.i2p.data.i2cp.MessageId;
import net.i2p.data.i2cp.MessageStatusMessage;
import net.i2p.util.Log;
@@ -50,9 +57,9 @@ class I2PSessionImpl2 extends I2PSessionImpl {
private static final long REMOVE_EXPIRED_TIME = 63*1000;
/**
* for extension by SimpleSession (no dest)
*/
/**
* for extension by SimpleSession (no dest)
*/
protected I2PSessionImpl2(I2PAppContext context, Properties options,
I2PClientMessageHandlerMap handlerMap) {
super(context, options, handlerMap);
@@ -61,15 +68,17 @@ class I2PSessionImpl2 extends I2PSessionImpl {
}
/**
* for extension by I2PSessionMuxedImpl
*
* Create a new session, reading the Destination, PrivateKey, and SigningPrivateKey
* from the destKeyStream, and using the specified options to connect to the router
*
* @param destKeyStream stream containing the private key data,
* format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile}
* @param options set of options to configure the router with, if null will use System properties
* @throws I2PSessionException if there is a problem loading the private keys or
* @throws I2PSessionException if there is a problem loading the private keys
*/
public I2PSessionImpl2(I2PAppContext ctx, InputStream destKeyStream, Properties options) throws I2PSessionException {
protected I2PSessionImpl2(I2PAppContext ctx, InputStream destKeyStream, Properties options) throws I2PSessionException {
super(ctx, destKeyStream, options);
_sendingStates = new ConcurrentHashMap<Long, MessageState>(32);
_sendMessageNonce = new AtomicLong();
@@ -94,6 +103,26 @@ class I2PSessionImpl2 extends I2PSessionImpl {
_context.statManager().createRateStat("i2cp.tx.msgExpanded", "size before compression", "i2cp", new long[] { 30*60*1000 });
}
/*
* For extension by SubSession via I2PSessionMuxedImpl
*
* @param destKeyStream stream containing the private key data,
* format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile}
* @param options set of options to configure the router with, if null will use System properties
* @since 0.9.21
*/
protected I2PSessionImpl2(I2PSessionImpl primary, InputStream destKeyStream, Properties options) throws I2PSessionException {
super(primary, destKeyStream, options);
_sendingStates = new ConcurrentHashMap<Long, MessageState>(32);
_sendMessageNonce = new AtomicLong();
_noEffort = "none".equals(getOptions().getProperty(I2PClient.PROP_RELIABILITY, "").toLowerCase(Locale.US));
_context.statManager().createRateStat("i2cp.receiveStatusTime.1", "How long it took to get status=1 back", "i2cp", new long[] { 10*60*1000 });
_context.statManager().createRateStat("i2cp.receiveStatusTime.4", "How long it took to get status=4 back", "i2cp", new long[] { 10*60*1000 });
_context.statManager().createRateStat("i2cp.receiveStatusTime.5", "How long it took to get status=5 back", "i2cp", new long[] { 10*60*1000 });
_context.statManager().createRateStat("i2cp.tx.msgCompressed", "compressed size transferred", "i2cp", new long[] { 30*60*1000 });
_context.statManager().createRateStat("i2cp.tx.msgExpanded", "size before compression", "i2cp", new long[] { 30*60*1000 });
}
/**
* Fire up a periodic task to check for unclaimed messages
* @since 0.9.14
@@ -182,17 +211,17 @@ class I2PSessionImpl2 extends I2PSessionImpl {
throw new UnsupportedOperationException("Use MuxedImpl");
}
/** @throws UnsupportedOperationException always, use MuxedImpl */
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent,
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set<SessionTag> tagsSent,
int proto, int fromport, int toport) throws I2PSessionException {
throw new UnsupportedOperationException("Use MuxedImpl");
}
/** @throws UnsupportedOperationException always, use MuxedImpl */
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expire,
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set<SessionTag> tagsSent, long expire,
int proto, int fromport, int toport) throws I2PSessionException {
throw new UnsupportedOperationException("Use MuxedImpl");
}
/** @throws UnsupportedOperationException always, use MuxedImpl */
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expire,
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set<SessionTag> tagsSent, long expire,
int proto, int fromport, int toport, int flags) throws I2PSessionException {
throw new UnsupportedOperationException("Use MuxedImpl");
}
@@ -225,7 +254,7 @@ class I2PSessionImpl2 extends I2PSessionImpl {
* @param tagsSent unused - no end-to-end crypto
*/
@Override
public boolean sendMessage(Destination dest, byte[] payload, SessionKey keyUsed, Set tagsSent) throws I2PSessionException {
public boolean sendMessage(Destination dest, byte[] payload, SessionKey keyUsed, Set<SessionTag> tagsSent) throws I2PSessionException {
return sendMessage(dest, payload, 0, payload.length, keyUsed, tagsSent, 0);
}
@@ -233,7 +262,7 @@ class I2PSessionImpl2 extends I2PSessionImpl {
* @param keyUsed unused - no end-to-end crypto
* @param tagsSent unused - no end-to-end crypto
*/
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent)
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set<SessionTag> tagsSent)
throws I2PSessionException {
return sendMessage(dest, payload, offset, size, keyUsed, tagsSent, 0);
}
@@ -244,10 +273,10 @@ class I2PSessionImpl2 extends I2PSessionImpl {
* @param keyUsed unused - no end-to-end crypto
* @param tagsSent unused - no end-to-end crypto
*/
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expires)
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set<SessionTag> tagsSent, long expires)
throws I2PSessionException {
if (_log.shouldLog(Log.DEBUG)) _log.debug("sending message");
if (isClosed()) throw new I2PSessionException("Already closed");
verifyOpen();
updateActivity();
// Sadly there is no way to send something completely uncompressed in a backward-compatible way,
@@ -304,7 +333,7 @@ class I2PSessionImpl2 extends I2PSessionImpl {
* @param keyUsed unused - no end-to-end crypto
* @param tagsSent unused - no end-to-end crypto
*/
protected boolean sendBestEffort(Destination dest, byte payload[], SessionKey keyUsed, Set tagsSent, long expires)
protected boolean sendBestEffort(Destination dest, byte payload[], SessionKey keyUsed, Set<SessionTag> tagsSent, long expires)
throws I2PSessionException {
return sendBestEffort(dest, payload, expires, 0);
}

View File

@@ -1,4 +1,4 @@
package net.i2p.client;
package net.i2p.client.impl;
/*
* public domain
@@ -8,12 +8,18 @@ import java.io.InputStream;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import net.i2p.I2PAppContext;
import net.i2p.client.I2PSessionException;
import net.i2p.client.I2PSessionListener;
import net.i2p.client.I2PSessionMuxedListener;
import net.i2p.client.SendMessageOptions;
import net.i2p.client.SendMessageStatusListener;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag;
import net.i2p.data.i2cp.MessagePayloadMessage;
import net.i2p.util.Log;
@@ -82,6 +88,24 @@ class I2PSessionMuxedImpl extends I2PSessionImpl2 {
// discards the one in super(), sorry about that... (no it wasn't started yet)
_availabilityNotifier = new MuxedAvailabilityNotifier();
}
/*
* For extension by SubSession
*
* @param destKeyStream stream containing the private key data,
* format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile}
* @param options set of options to configure the router with, if null will use System properties
* @since 0.9.21
*/
protected I2PSessionMuxedImpl(I2PSessionImpl primary, InputStream destKeyStream, Properties options) throws I2PSessionException {
super(primary, destKeyStream, options);
// also stored in _sessionListener but we keep it in _demultipexer
// as well so we don't have to keep casting
_demultiplexer = new I2PSessionDemultiplexer(primary.getContext());
super.setSessionListener(_demultiplexer);
// discards the one in super(), sorry about that... (no it wasn't started yet)
_availabilityNotifier = new MuxedAvailabilityNotifier();
}
/** listen on all protocols and ports */
@Override
@@ -140,7 +164,7 @@ class I2PSessionMuxedImpl extends I2PSessionImpl2 {
*/
@Override
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size,
SessionKey keyUsed, Set tagsSent, long expires)
SessionKey keyUsed, Set<SessionTag> tagsSent, long expires)
throws I2PSessionException {
return sendMessage(dest, payload, offset, size, keyUsed, tagsSent, 0, PROTO_UNSPECIFIED, PORT_UNSPECIFIED, PORT_UNSPECIFIED);
}
@@ -150,7 +174,7 @@ class I2PSessionMuxedImpl extends I2PSessionImpl2 {
* @param tagsSent unused - no end-to-end crypto
*/
@Override
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent,
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set<SessionTag> tagsSent,
int proto, int fromport, int toport) throws I2PSessionException {
return sendMessage(dest, payload, offset, size, keyUsed, tagsSent, 0, proto, fromport, toport);
}
@@ -169,7 +193,7 @@ class I2PSessionMuxedImpl extends I2PSessionImpl2 {
*/
@Override
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size,
SessionKey keyUsed, Set tagsSent, long expires,
SessionKey keyUsed, Set<SessionTag> tagsSent, long expires,
int proto, int fromPort, int toPort)
throws I2PSessionException {
return sendMessage(dest, payload, offset, size, keyUsed, tagsSent, 0, proto, fromPort, toPort, 0);
@@ -190,7 +214,7 @@ class I2PSessionMuxedImpl extends I2PSessionImpl2 {
*/
@Override
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size,
SessionKey keyUsed, Set tagsSent, long expires,
SessionKey keyUsed, Set<SessionTag> tagsSent, long expires,
int proto, int fromPort, int toPort, int flags)
throws I2PSessionException {
payload = prepPayload(payload, offset, size, proto, fromPort, toPort);
@@ -256,7 +280,7 @@ class I2PSessionMuxedImpl extends I2PSessionImpl2 {
* @since 0.9.14
*/
private byte[] prepPayload(byte[] payload, int offset, int size, int proto, int fromPort, int toPort) throws I2PSessionException {
if (isClosed()) throw new I2PSessionException("Already closed");
verifyOpen();
updateActivity();
if (shouldCompress(size))
@@ -308,9 +332,9 @@ class I2PSessionMuxedImpl extends I2PSessionImpl2 {
protected class MuxedAvailabilityNotifier extends AvailabilityNotifier {
private final LinkedBlockingQueue<MsgData> _msgs;
private volatile boolean _alive = false;
private volatile boolean _alive;
private static final int POISON_SIZE = -99999;
private final AtomicBoolean stopping = new AtomicBoolean(false);
private final AtomicBoolean stopping = new AtomicBoolean();
public MuxedAvailabilityNotifier() {
_msgs = new LinkedBlockingQueue<MsgData>();
@@ -318,12 +342,12 @@ class I2PSessionMuxedImpl extends I2PSessionImpl2 {
@Override
public void stopNotifying() {
boolean again = true;
synchronized (stopping) {
if( !stopping.getAndSet(true)) {
if (_alive == true) {
_msgs.clear();
if (_alive) {
// System.out.println("I2PSessionMuxedImpl.stopNotifying()");
_msgs.clear();
boolean again = true;
while(again) {
try {
_msgs.put(new MsgData(0, POISON_SIZE, 0, 0, 0));
@@ -333,8 +357,8 @@ class I2PSessionMuxedImpl extends I2PSessionImpl2 {
continue;
}
}
_alive = false;
}
_alive = false;
stopping.set(false);
}
// stopping.notifyAll();
@@ -348,17 +372,24 @@ class I2PSessionMuxedImpl extends I2PSessionImpl2 {
try {
_msgs.put(new MsgData((int)(msgId & 0xffffffff), size, proto, fromPort, toPort));
} catch (InterruptedException ie) {}
if (!_alive && _log.shouldLog(Log.WARN))
_log.warn(getPrefix() + "message available but notifier not running");
}
@Override
public void run() {
MsgData msg;
if (_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix() + "starting muxed availability notifier");
_msgs.clear();
_alive=true;
while (_alive) {
MsgData msg;
try {
msg = _msgs.take();
} catch (InterruptedException ie) {
_log.debug("I2PSessionMuxedImpl.run() InterruptedException " + String.valueOf(_msgs.size()) + " Messages, Alive " + _alive);
if (_log.shouldLog(Log.DEBUG))
_log.debug("I2PSessionMuxedImpl.run() InterruptedException " +
String.valueOf(_msgs.size()) + " Messages, Alive " + _alive);
continue;
}
if (msg.size == POISON_SIZE) {
@@ -368,7 +399,7 @@ class I2PSessionMuxedImpl extends I2PSessionImpl2 {
try {
_demultiplexer.messageAvailable(I2PSessionMuxedImpl.this,
msg.id, msg.size, msg.proto, msg.fromPort, msg.toPort);
} catch (Exception e) {
} catch (RuntimeException e) {
_log.error("Error notifying app of message availability", e);
}
}

View File

@@ -1,4 +1,4 @@
package net.i2p.client;
package net.i2p.client.impl;
/*
* Released into the public domain
@@ -16,6 +16,8 @@ import java.util.Properties;
import net.i2p.CoreVersion;
import net.i2p.I2PAppContext;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PSessionException;
import net.i2p.data.i2cp.BandwidthLimitsMessage;
import net.i2p.data.i2cp.DestReplyMessage;
import net.i2p.data.i2cp.DisconnectMessage;
@@ -28,6 +30,7 @@ import net.i2p.internal.QueuedI2CPMessageReader;
import net.i2p.util.I2PSSLSocketFactory;
import net.i2p.util.Log;
import net.i2p.util.OrderedProperties;
import net.i2p.util.SystemVersion;
/**
* Create a new session for doing naming and bandwidth queries only. Do not create a Destination.
@@ -37,7 +40,7 @@ import net.i2p.util.OrderedProperties;
*
* @author zzz
*/
class I2PSimpleSession extends I2PSessionImpl2 {
public class I2PSimpleSession extends I2PSessionImpl2 {
private static final int BUF_SIZE = 1024;
@@ -89,6 +92,7 @@ class I2PSimpleSession extends I2PSessionImpl2 {
} else {
_socket = new Socket(_hostname, _portNum);
}
_socket.setKeepAlive(true);
OutputStream out = _socket.getOutputStream();
out.write(I2PClient.PROTOCOL_BYTE);
out.flush();
@@ -119,11 +123,11 @@ class I2PSimpleSession extends I2PSessionImpl2 {
Properties auth = new OrderedProperties();
auth.setProperty(PROP_USER, opts.getProperty(PROP_USER));
auth.setProperty(PROP_PW, opts.getProperty(PROP_PW));
sendMessage(new GetDateMessage(CoreVersion.VERSION, auth));
sendMessage_unchecked(new GetDateMessage(CoreVersion.VERSION, auth));
} else {
// we must now send a GetDate even in SimpleSession, or we won't know
// what version we are talking with and cannot use HostLookup
sendMessage(new GetDateMessage(CoreVersion.VERSION));
sendMessage_unchecked(new GetDateMessage(CoreVersion.VERSION));
}
waitForDate();
}
@@ -137,7 +141,16 @@ class I2PSimpleSession extends I2PSessionImpl2 {
} catch (UnknownHostException uhe) {
throw new I2PSessionException(getPrefix() + "Cannot connect to the router on " + _hostname + ':' + _portNum, uhe);
} catch (IOException ioe) {
throw new I2PSessionException(getPrefix() + "Cannot connect to the router on " + _hostname + ':' + _portNum, ioe);
// Generate the best error message as this will be logged
String msg;
if (_context.isRouterContext())
msg = "Failed internal router binding";
else if (SystemVersion.isAndroid() &&
Boolean.parseBoolean(getOptions().getProperty(PROP_DOMAIN_SOCKET)))
msg = "Failed to bind to the router";
else
msg = "Cannot connect to the router on " + _hostname + ':' + _portNum;
throw new I2PSessionException(getPrefix() + msg, ioe);
} finally {
changeState(success ? State.OPEN : State.CLOSED);
}

View File

@@ -1,4 +1,4 @@
package net.i2p.client;
package net.i2p.client.impl;
/*
* free (adj.): unencumbered; not under the control of others
@@ -10,6 +10,7 @@ package net.i2p.client;
*/
import net.i2p.I2PAppContext;
import net.i2p.client.I2PSessionException;
import net.i2p.data.DataFormatException;
import net.i2p.data.Payload;
import net.i2p.data.i2cp.I2CPMessage;
@@ -33,7 +34,7 @@ class MessagePayloadMessageHandler extends HandlerImpl {
public void handleMessage(I2CPMessage message, I2PSessionImpl session) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Handle message " + message);
_log.debug("Handle message " + message + " for session " + session);
try {
MessagePayloadMessage msg = (MessagePayloadMessage) message;
long id = msg.getMessageId();

View File

@@ -1,8 +1,10 @@
package net.i2p.client;
package net.i2p.client.impl;
import java.util.concurrent.atomic.AtomicLong;
import net.i2p.I2PAppContext;
import net.i2p.client.I2PSession;
import net.i2p.client.SendMessageStatusListener;
import net.i2p.data.i2cp.MessageId;
import net.i2p.data.i2cp.MessageStatusMessage;
import net.i2p.util.Log;

View File

@@ -1,4 +1,4 @@
package net.i2p.client;
package net.i2p.client.impl;
/*
* free (adj.): unencumbered; not under the control of others
@@ -10,6 +10,7 @@ package net.i2p.client;
*/
import net.i2p.I2PAppContext;
import net.i2p.client.I2PSessionException;
import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.data.i2cp.MessageStatusMessage;
import net.i2p.data.i2cp.ReceiveMessageBeginMessage;

View File

@@ -1,4 +1,4 @@
package net.i2p.client;
package net.i2p.client.impl;
/*
* free (adj.): unencumbered; not under the control of others
@@ -14,6 +14,7 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.i2p.I2PAppContext;
import net.i2p.client.I2PSessionException;
import net.i2p.crypto.KeyGenerator;
import net.i2p.crypto.SigType;
import net.i2p.data.DataFormatException;
@@ -74,22 +75,84 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
* Finish creating and signing the new LeaseSet
* @since 0.9.7
*/
protected void signLeaseSet(LeaseSet leaseSet, I2PSessionImpl session) {
protected synchronized void signLeaseSet(LeaseSet leaseSet, I2PSessionImpl session) {
Destination dest = session.getMyDestination();
// also, if this session is connected to multiple routers, include other leases here
leaseSet.setDestination(session.getMyDestination());
leaseSet.setDestination(dest);
// reuse the old keys for the client
LeaseInfo li = _existingLeaseSets.get(session.getMyDestination());
LeaseInfo li = _existingLeaseSets.get(dest);
if (li == null) {
li = new LeaseInfo(session.getMyDestination());
_existingLeaseSets.put(session.getMyDestination(), li);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Creating new leaseInfo keys for "
+ session.getMyDestination().calculateHash().toBase64());
// [enctype:]b64 of private key
String spk = session.getOptions().getProperty("i2cp.leaseSetPrivateKey");
// [sigtype:]b64 of private key
String sspk = session.getOptions().getProperty("i2cp.leaseSetSigningPrivateKey");
PrivateKey privKey = null;
SigningPrivateKey signingPrivKey = null;
if (spk != null && sspk != null) {
boolean useOldKeys = true;
int colon = sspk.indexOf(':');
SigType type = dest.getSigType();
if (colon > 0) {
String stype = sspk.substring(0, colon);
SigType t = SigType.parseSigType(stype);
if (t == type)
sspk = sspk.substring(colon + 1);
else
useOldKeys = false;
}
colon = spk.indexOf(':');
// just ignore for now, no other types supported
if (colon >= 0)
spk = spk.substring(colon + 1);
if (useOldKeys) {
try {
signingPrivKey = new SigningPrivateKey(type);
signingPrivKey.fromBase64(sspk);
} catch (DataFormatException iae) {
useOldKeys = false;
signingPrivKey = null;
}
}
if (useOldKeys) {
try {
privKey = new PrivateKey();
privKey.fromBase64(spk);
} catch (DataFormatException iae) {
privKey = null;
}
}
}
if (privKey == null && !_existingLeaseSets.isEmpty()) {
// look for keypair from another dest using same pubkey
PublicKey pk = dest.getPublicKey();
for (Map.Entry<Destination, LeaseInfo> e : _existingLeaseSets.entrySet()) {
if (pk.equals(e.getKey().getPublicKey())) {
privKey = e.getValue().getPrivateKey();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Creating new leaseInfo keys for " + dest + " with private key from " + e.getKey());
break;
}
}
}
if (privKey != null) {
if (signingPrivKey != null) {
li = new LeaseInfo(privKey, signingPrivKey);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Creating new leaseInfo keys for " + dest + " WITH configured private keys");
} else {
li = new LeaseInfo(privKey, dest);
}
} else {
li = new LeaseInfo(dest);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Creating new leaseInfo keys for " + dest + " without configured private keys");
}
_existingLeaseSets.put(dest, li);
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Caching the old leaseInfo keys for "
+ session.getMyDestination().calculateHash().toBase64());
+ dest);
}
leaseSet.setEncryptionKey(li.getPublicKey());
@@ -132,8 +195,11 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
private final SigningPublicKey _signingPubKey;
private final SigningPrivateKey _signingPrivKey;
/**
* New keys
*/
public LeaseInfo(Destination dest) {
Object encKeys[] = KeyGenerator.getInstance().generatePKIKeypair();
SimpleDataStructure encKeys[] = KeyGenerator.getInstance().generatePKIKeys();
// must be same type as the Destination's signing key
SimpleDataStructure signKeys[];
try {
@@ -147,6 +213,34 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
_signingPrivKey = (SigningPrivateKey) signKeys[1];
}
/**
* Existing keys
* @since 0.9.18
*/
public LeaseInfo(PrivateKey privKey, SigningPrivateKey signingPrivKey) {
_pubKey = KeyGenerator.getPublicKey(privKey);
_privKey = privKey;
_signingPubKey = KeyGenerator.getSigningPublicKey(signingPrivKey);
_signingPrivKey = signingPrivKey;
}
/**
* Existing crypto key, new signing key
* @since 0.9.21
*/
public LeaseInfo(PrivateKey privKey, Destination dest) {
SimpleDataStructure signKeys[];
try {
signKeys = KeyGenerator.getInstance().generateSigningKeys(dest.getSigningPublicKey().getType());
} catch (GeneralSecurityException gse) {
throw new IllegalStateException(gse);
}
_pubKey = KeyGenerator.getPublicKey(privKey);
_privKey = privKey;
_signingPubKey = (SigningPublicKey) signKeys[0];
_signingPrivKey = (SigningPrivateKey) signKeys[1];
}
public PublicKey getPublicKey() {
return _pubKey;
}

View File

@@ -1,4 +1,4 @@
package net.i2p.client;
package net.i2p.client.impl;
/*
* free (adj.): unencumbered; not under the control of others

View File

@@ -1,4 +1,4 @@
package net.i2p.client;
package net.i2p.client.impl;
/*
* free (adj.): unencumbered; not under the control of others
@@ -8,6 +8,7 @@ package net.i2p.client;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.client.I2PSessionException;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer;
@@ -118,6 +119,6 @@ class SessionIdleTimer implements SimpleTimer.TimedEvent {
} else {
nextDelay = _minimumTime - (now - lastActivity);
}
_context.simpleScheduler().addEvent(this, nextDelay);
_context.simpleTimer2().addEvent(this, nextDelay);
}
}

View File

@@ -1,4 +1,4 @@
package net.i2p.client;
package net.i2p.client.impl;
/*
* free (adj.): unencumbered; not under the control of others
@@ -10,6 +10,7 @@ package net.i2p.client;
*/
import net.i2p.I2PAppContext;
import net.i2p.client.I2PSessionException;
import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.data.i2cp.SessionStatusMessage;
import net.i2p.util.Log;

View File

@@ -1,4 +1,4 @@
package net.i2p.client;
package net.i2p.client.impl;
/*
* free (adj.): unencumbered; not under the control of others

View File

@@ -0,0 +1,347 @@
package net.i2p.client.impl;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.data.PrivateKey;
import net.i2p.data.SigningPrivateKey;
import net.i2p.data.i2cp.CreateLeaseSetMessage;
import net.i2p.data.i2cp.CreateSessionMessage;
import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.data.i2cp.SessionId;
import net.i2p.util.I2PAppThread;
/**
* An additional session using another session's connection.
*
* A subsession uses the same connection to the router as the primary session,
* but has a different Destination. It uses the same tunnels as the primary
* but has its own leaseset. It must use the same encryption keys as the primary
* so that garlic encryption/decryption works.
*
* The message handler map and message producer are reused from primary.
*
* Does NOT reuse the session listener ????
*
* While the I2CP protocol, in theory, allows for fully independent sessions
* over the same I2CP connection, this is not currently supported by the router.
*
* @since 0.9.21
*/
class SubSession extends I2PSessionMuxedImpl {
private final I2PSessionMuxedImpl _primary;
/**
* @param primary must be a I2PSessionMuxedImpl
*/
public SubSession(I2PSession primary, InputStream destKeyStream, Properties options) throws I2PSessionException {
super((I2PSessionMuxedImpl)primary, destKeyStream, options);
_primary = (I2PSessionMuxedImpl) primary;
if (!getDecryptionKey().equals(_primary.getDecryptionKey()))
throw new I2PSessionException("encryption key mismatch");
if (getPrivateKey().equals(_primary.getPrivateKey()))
throw new I2PSessionException("signing key must differ");
// state management
}
/**
* Unsupported in a subsession.
* @throws UnsupportedOperationException always
*/
@Override
public I2PSession addSubsession(InputStream destKeyStream, Properties opts) throws I2PSessionException {
throw new UnsupportedOperationException();
}
/**
* Unsupported in a subsession.
* Does nothing.
*/
@Override
public void removeSubsession(I2PSession session) {}
/**
* Unsupported in a subsession.
* @return empty list always
*/
@Override
public List<I2PSession> getSubsessions() {
return Collections.emptyList();
}
/**
* Does nothing for now
*/
@Override
public void updateOptions(Properties options) {}
/**
* Connect to the router and establish a session. This call blocks until
* a session is granted.
*
* Should be threadsafe, other threads will block until complete.
* Disconnect / destroy from another thread may be called simultaneously and
* will (should?) interrupt the connect.
*
* @throws I2PSessionException if there is a configuration error or the router is
* not reachable
*/
@Override
public void connect() throws I2PSessionException {
synchronized(_stateLock) {
if (_state != State.OPEN) {
changeState(State.OPENING);
}
}
boolean success = false;
try {
_primary.connect();
// wait until we have created a lease set
int waitcount = 0;
while (_leaseSet == null) {
if (waitcount++ > 5*60) {
throw new IOException("No tunnels built after waiting 5 minutes. Your network connection may be down, or there is severe network congestion.");
}
synchronized (_leaseSetWait) {
// InterruptedException caught below
_leaseSetWait.wait(1000);
}
}
synchronized(_stateLock) {
if (_state != State.OPEN) {
Thread notifier = new I2PAppThread(_availabilityNotifier, "ClientNotifier " + getPrefix(), true);
notifier.start();
changeState(State.OPEN);
}
}
success = true;
} catch (InterruptedException ie) {
throw new I2PSessionException("Interrupted", ie);
} catch (IOException ioe) {
throw new I2PSessionException(getPrefix() + "Cannot connect to the router on " + _hostname + ':' + _portNum, ioe);
} finally {
if (!success) {
_availabilityNotifier.stopNotifying();
changeState(State.CLOSED);
}
}
}
/**
* Has the session been closed (or not yet connected)?
* False when open and during transitions.
*/
@Override
public boolean isClosed() {
return super.isClosed() || _primary.isClosed();
}
/**
* Deliver an I2CP message to the router
* May block for several seconds if the write queue to the router is full
*
* @throws I2PSessionException if the message is malformed or there is an error writing it out
*/
@Override
void sendMessage(I2CPMessage message) throws I2PSessionException {
// workaround for now, as primary will send out our CreateSession
// from his connect, while we are still closed.
// If we did it in connect() we wouldn't need this
if (isClosed() &&
message.getType() != CreateSessionMessage.MESSAGE_TYPE &&
message.getType() != CreateLeaseSetMessage.MESSAGE_TYPE)
throw new I2PSessionException("Already closed");
_primary.sendMessage_unchecked(message);
}
/**
* Deliver an I2CP message to the router.
* Does NOT check state. Call only from connect() or other methods that need to
* send messages when not in OPEN state.
*
* @throws I2PSessionException if the message is malformed or there is an error writing it out
* @since 0.9.23
*/
@Override
void sendMessage_unchecked(I2CPMessage message) throws I2PSessionException {
_primary.sendMessage_unchecked(message);
}
/**
* Pass off the error to the listener
* Misspelled, oh well.
* @param error non-null
*/
@Override
void propogateError(String msg, Throwable error) {
_primary.propogateError(msg, error);
if (_sessionListener != null) _sessionListener.errorOccurred(this, msg, error);
}
/**
* Tear down the session, and do NOT reconnect.
*
* Blocks if session has not been fully started.
*/
@Override
public void destroySession() {
_primary.destroySession();
if (_availabilityNotifier != null)
_availabilityNotifier.stopNotifying();
if (_sessionListener != null) _sessionListener.disconnected(this);
changeState(State.CLOSED);
}
/**
* Will interrupt a connect in progress.
*/
@Override
protected void disconnect() {
_primary.disconnect();
}
@Override
protected boolean reconnect() {
return _primary.reconnect();
}
/**
* Called by the message handler
* on reception of DestReplyMessage
*
* This will never happen, as the dest reply message does not contain a session ID.
*/
@Override
void destReceived(Destination d) {
_primary.destReceived(d);
}
/**
* Called by the message handler
* on reception of DestReplyMessage
*
* This will never happen, as the dest reply message does not contain a session ID.
*
* @param h non-null
*/
@Override
void destLookupFailed(Hash h) {
_primary.destLookupFailed(h);
}
/**
* Called by the message handler
* on reception of HostReplyMessage
* @param d non-null
*/
void destReceived(long nonce, Destination d) {
_primary.destReceived(nonce, d);
}
/**
* Called by the message handler
* on reception of HostReplyMessage
*/
@Override
void destLookupFailed(long nonce) {
_primary.destLookupFailed(nonce);
}
/**
* Called by the message handler.
* This will never happen, as the bw limits message does not contain a session ID.
*/
@Override
void bwReceived(int[] i) {
_primary.bwReceived(i);
}
/**
* Blocking. Waits a max of 10 seconds by default.
* See lookupDest with maxWait parameter to change.
* Implemented in 0.8.3 in I2PSessionImpl;
* previously was available only in I2PSimpleSession.
* Multiple outstanding lookups are now allowed.
* @return null on failure
*/
@Override
public Destination lookupDest(Hash h) throws I2PSessionException {
return _primary.lookupDest(h);
}
/**
* Blocking.
* @param maxWait ms
* @return null on failure
*/
@Override
public Destination lookupDest(Hash h, long maxWait) throws I2PSessionException {
return _primary.lookupDest(h, maxWait);
}
/**
* Ask the router to lookup a Destination by host name.
* Blocking. Waits a max of 10 seconds by default.
*
* This only makes sense for a b32 hostname, OR outside router context.
* Inside router context, just query the naming service.
* Outside router context, this does NOT query the context naming service.
* Do that first if you expect a local addressbook.
*
* This will log a warning for non-b32 in router context.
*
* See interface for suggested implementation.
*
* Requires router side to be 0.9.11 or higher. If the router is older,
* this will return null immediately.
*/
@Override
public Destination lookupDest(String name) throws I2PSessionException {
return _primary.lookupDest(name);
}
/**
* Ask the router to lookup a Destination by host name.
* Blocking. See above for details.
* @param maxWait ms
* @return null on failure
*/
@Override
public Destination lookupDest(String name, long maxWait) throws I2PSessionException {
return _primary.lookupDest(name, maxWait);
}
/**
* This won't be called, as the reply does not contain a session ID, so
* it won't be routed back to us
*/
@Override
public int[] bandwidthLimits() throws I2PSessionException {
return _primary.bandwidthLimits();
}
@Override
protected void updateActivity() {
_primary.updateActivity();
}
@Override
public long lastActivity() {
return _primary.lastActivity();
}
@Override
public void setReduced() {
_primary.setReduced();
}
}

View File

@@ -0,0 +1,11 @@
<html><body>
<p>Implements the base I2P SDK for developing applications that communicate
through I2P.</p>
<p>
These classes are for implementing the client side of I2CP
and are not to be used externally.
Subject to change and not part of the public API.
Moved from net.i2p.client in 0.9.21.
</p>
</body></html>

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,7 @@ import net.i2p.I2PAppContext;
import net.i2p.client.I2PSessionException;
import net.i2p.data.Destination;
import net.i2p.util.LHMCache;
import net.i2p.util.SystemVersion;
/**
* A Dummy naming service that can only handle base64 and b32 destinations.
@@ -23,7 +24,7 @@ class DummyNamingService extends NamingService {
protected static final int BASE32_HASH_LENGTH = 52; // 1 + Hash.HASH_LENGTH * 8 / 5
public final static String PROP_B32 = "i2p.naming.hostsTxt.useB32";
protected static final int CACHE_MAX_SIZE = 32;
protected static final int CACHE_MAX_SIZE = SystemVersion.isAndroid() ? 32 : 128;
public static final int DEST_SIZE = 516; // Std. Base64 length (no certificate)
/**

View File

@@ -10,6 +10,7 @@ import java.io.IOException;
import java.util.Locale;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
/**
@@ -34,6 +35,7 @@ import net.i2p.data.Destination;
* @deprecated use HostsTxtNamingService.put()
* @since 0.7.9
*/
@Deprecated
public class EepGetAndAddNamingService extends EepGetNamingService {
/** default hosts.txt filename */
@@ -57,7 +59,7 @@ public class EepGetAndAddNamingService extends EepGetNamingService {
try {
fos = new FileOutputStream(f, true);
String line = hostname + '=' + rv.toBase64() + System.getProperty("line.separator");
fos.write(line.getBytes());
fos.write(DataHelper.getASCII(line));
} catch (IOException ioe) {
System.err.println("Error appending: " + ioe);
} finally {

View File

@@ -0,0 +1,534 @@
package net.i2p.client.naming;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Map;
import net.i2p.crypto.DSAEngine;
import net.i2p.crypto.SigType;
import net.i2p.data.Base64;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.Signature;
import net.i2p.data.SigningPrivateKey;
import net.i2p.data.SigningPublicKey;
import net.i2p.util.OrderedProperties;
// for testing only
import java.io.File;
import java.io.OutputStreamWriter;
import java.util.Arrays;
import net.i2p.data.Base32;
import net.i2p.data.PrivateKeyFile;
import net.i2p.util.RandomSource;
/**
* A hostname, b64 destination, and optional properties.
* Includes methods to sign and verify the entry.
* Used by addressbook to parse subscription data,
* and by i2ptunnel to generate signed metadata.
*
* @since 0.9.26
*/
public class HostTxtEntry {
private final String name;
private final String dest;
private final OrderedProperties props;
private boolean isValidated;
private boolean isValid;
public static final char KV_SEPARATOR = '=';
public static final String PROPS_SEPARATOR = "#!";
public static final char PROP_SEPARATOR = '#';
public static final String PROP_ACTION = "action";
public static final String PROP_DATE = "date";
public static final String PROP_DEST = "dest";
public static final String PROP_EXPIRES = "expires";
public static final String PROP_NAME = "name";
public static final String PROP_OLDDEST = "olddest";
public static final String PROP_OLDNAME = "oldname";
public static final String PROP_OLDSIG = "oldsig";
public static final String PROP_SIG = "sig";
public static final String ACTION_ADDDEST = "adddest";
public static final String ACTION_ADDNAME = "addname";
public static final String ACTION_ADDSUBDOMAIN = "addsubdomain";
public static final String ACTION_CHANGEDEST = "changedest";
public static final String ACTION_CHANGENAME = "changename";
public static final String ACTION_REMOVE = "remove";
public static final String ACTION_REMOVEALL = "removeall";
public static final String ACTION_UPDATE = "update";
/**
* Properties will be null
*/
public HostTxtEntry(String name, String dest) {
this(name, dest, (OrderedProperties) null);
}
/**
* @param sprops line part after the #!, non-null
* @throws IllegalArgumentException on dup key in sprops and other errors
*/
public HostTxtEntry(String name, String dest, String sprops) throws IllegalArgumentException {
this(name, dest, parseProps(sprops));
}
/**
* A 'remove' entry. Name and Dest will be null.
* @param sprops line part after the #!, non-null
* @throws IllegalArgumentException on dup key in sprops and other errors
*/
public HostTxtEntry(String sprops) throws IllegalArgumentException {
this(null, null, parseProps(sprops));
}
/**
* @param props may be null
*/
public HostTxtEntry(String name, String dest, OrderedProperties props) {
this.name = name;
this.dest = dest;
this.props = props;
}
public String getName() {
return name;
}
public String getDest() {
return dest;
}
public OrderedProperties getProps() {
return props;
}
/**
* @param line part after the #!
* @throws IllegalArgumentException on dup key and other errors
*/
private static OrderedProperties parseProps(String line) throws IllegalArgumentException {
line = line.trim();
OrderedProperties rv = new OrderedProperties();
String[] entries = DataHelper.split(line, "#");
for (int i = 0; i < entries.length; i++) {
String kv = entries[i];
int eq = kv.indexOf("=");
if (eq <= 0 || eq == kv.length() - 1)
throw new IllegalArgumentException("No value: \"" + kv + '"');
String k = kv.substring(0, eq);
String v = kv.substring(eq + 1);
Object old = rv.setProperty(k, v);
if (old != null)
throw new IllegalArgumentException("Dup key: " + k);
}
return rv;
}
/**
* Write as a standard line name=dest[#!k1=v1#k2=v2...]
* Includes newline.
*/
public void write(BufferedWriter out) throws IOException {
write((Writer) out);
out.newLine();
}
/**
* Write as a standard line name=dest[#!k1=v1#k2=v2...]
* Does not include newline.
*/
public void write(Writer out) throws IOException {
if (name != null && dest != null) {
out.write(name);
out.write(KV_SEPARATOR);
out.write(dest);
}
writeProps(out);
}
/**
* Write as a "remove" line #!dest=dest#name=name#k1=v1#sig=sig...]
* This works whether constructed with name and dest, or just properties.
* Includes newline.
* Must have been constructed with non-null properties.
*/
public void writeRemoveLine(BufferedWriter out) throws IOException {
writeRemove(out);
out.newLine();
}
/**
* Write as a "remove" line #!dest=dest#name=name#k1=v1#sig=sig...]
* This works whether constructed with name and dest, or just properties.
* Does not include newline.
* Must have been constructed with non-null properties.
*/
public void writeRemove(Writer out) throws IOException {
if (props == null)
throw new IllegalStateException();
if (name != null && dest != null) {
props.setProperty(PROP_NAME, name);
props.setProperty(PROP_DEST, dest);
}
writeProps(out);
if (name != null && dest != null) {
props.remove(PROP_NAME);
props.remove(PROP_DEST);
}
}
/**
* Write the props part (if any) only, without newline
*/
public void writeProps(Writer out) throws IOException {
writeProps(out, false, false);
}
/**
* Write the props part (if any) only, without newline
*/
private void writeProps(Writer out, boolean omitSig, boolean omitOldSig) throws IOException {
if (props == null)
return;
boolean started = false;
for (Map.Entry<Object, Object> e : props.entrySet()) {
String k = (String) e.getKey();
if (omitSig && k.equals(PROP_SIG))
continue;
if (omitOldSig && k.equals(PROP_OLDSIG))
continue;
if (started) {
out.write(PROP_SEPARATOR);
} else {
started = true;
out.write(PROPS_SEPARATOR);
}
String v = (String) e.getValue();
out.write(k);
out.write(KV_SEPARATOR);
out.write(v);
}
}
/**
* Verify with the dest public key using the "sig" property
*/
public boolean hasValidSig() {
if (props == null || name == null || dest == null)
return false;
if (!isValidated) {
isValidated = true;
StringWriter buf = new StringWriter(1024);
String sig = props.getProperty(PROP_SIG);
if (sig == null)
return false;
buf.append(name);
buf.append(KV_SEPARATOR);
buf.append(dest);
try {
writeProps(buf, true, false);
} catch (IOException ioe) {
// won't happen
return false;
}
byte[] sdata = Base64.decode(sig);
if (sdata == null)
return false;
Destination d;
try {
d = new Destination(dest);
} catch (DataFormatException dfe) {
return false;
}
SigningPublicKey spk = d.getSigningPublicKey();
SigType type = spk.getType();
if (type == null)
return false;
Signature s;
try {
s = new Signature(type, sdata);
} catch (IllegalArgumentException iae) {
return false;
}
isValid = DSAEngine.getInstance().verifySignature(s, DataHelper.getUTF8(buf.toString()), spk);
}
return isValid;
}
/**
* Verify with the "olddest" property's public key using the "oldsig" property
*/
public boolean hasValidInnerSig() {
if (props == null || name == null || dest == null)
return false;
boolean rv = false;
// don't cache result
if (true) {
StringWriter buf = new StringWriter(1024);
String sig = props.getProperty(PROP_OLDSIG);
String olddest = props.getProperty(PROP_OLDDEST);
if (sig == null || olddest == null)
return false;
buf.append(name);
buf.append(KV_SEPARATOR);
buf.append(dest);
try {
writeProps(buf, true, true);
} catch (IOException ioe) {
// won't happen
return false;
}
byte[] sdata = Base64.decode(sig);
if (sdata == null)
return false;
Destination d;
try {
d = new Destination(olddest);
} catch (DataFormatException dfe) {
return false;
}
SigningPublicKey spk = d.getSigningPublicKey();
SigType type = spk.getType();
if (type == null)
return false;
Signature s;
try {
s = new Signature(type, sdata);
} catch (IllegalArgumentException iae) {
return false;
}
rv = DSAEngine.getInstance().verifySignature(s, DataHelper.getUTF8(buf.toString()), spk);
}
return rv;
}
/**
* Verify with the "dest" property's public key using the "sig" property
*/
public boolean hasValidRemoveSig() {
if (props == null)
return false;
boolean rv = false;
// don't cache result
if (true) {
StringWriter buf = new StringWriter(1024);
String sig = props.getProperty(PROP_SIG);
String olddest = props.getProperty(PROP_DEST);
if (sig == null || olddest == null)
return false;
try {
writeProps(buf, true, true);
} catch (IOException ioe) {
// won't happen
return false;
}
byte[] sdata = Base64.decode(sig);
if (sdata == null)
return false;
Destination d;
try {
d = new Destination(olddest);
} catch (DataFormatException dfe) {
return false;
}
SigningPublicKey spk = d.getSigningPublicKey();
SigType type = spk.getType();
if (type == null)
return false;
Signature s;
try {
s = new Signature(type, sdata);
} catch (IllegalArgumentException iae) {
return false;
}
rv = DSAEngine.getInstance().verifySignature(s, DataHelper.getUTF8(buf.toString()), spk);
}
return rv;
}
@Override
public int hashCode() {
return dest.hashCode();
}
/**
* Compares Destination only, not properties
*/
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof HostTxtEntry))
return false;
HostTxtEntry he = (HostTxtEntry) o;
return dest.equals(he.getDest());
}
/**
* Sign and set the "sig" property
* Must have been constructed with non-null properties.
*/
public void sign(SigningPrivateKey spk) {
signIt(spk, PROP_SIG);
}
/**
* Sign and set the "oldsig" property
* Must have been constructed with non-null properties.
*/
public void signInner(SigningPrivateKey spk) {
signIt(spk, PROP_OLDSIG);
}
/**
* Sign as a "remove" line #!dest=dest#name=name#k1=v1#sig=sig...]
* Must have been constructed with non-null properties.
*/
public void signRemove(SigningPrivateKey spk) {
if (props == null)
throw new IllegalStateException();
if (props.containsKey(PROP_SIG))
throw new IllegalStateException();
props.setProperty(PROP_NAME, name);
props.setProperty(PROP_DEST, dest);
StringWriter buf = new StringWriter(1024);
try {
writeProps(buf);
} catch (IOException ioe) {
throw new IllegalStateException(ioe);
}
props.remove(PROP_NAME);
props.remove(PROP_DEST);
Signature s = DSAEngine.getInstance().sign(DataHelper.getUTF8(buf.toString()), spk);
if (s == null)
throw new IllegalArgumentException("sig failed");
props.setProperty(PROP_SIG, s.toBase64());
}
/**
* @param sigprop The signature property to set
*/
private void signIt(SigningPrivateKey spk, String sigprop) {
if (props == null)
throw new IllegalStateException();
if (props.containsKey(sigprop))
throw new IllegalStateException();
StringWriter buf = new StringWriter(1024);
buf.append(name);
buf.append(KV_SEPARATOR);
buf.append(dest);
try {
writeProps(buf);
} catch (IOException ioe) {
throw new IllegalStateException(ioe);
}
Signature s = DSAEngine.getInstance().sign(DataHelper.getUTF8(buf.toString()), spk);
if (s == null)
throw new IllegalArgumentException("sig failed");
props.setProperty(sigprop, s.toBase64());
}
/**
* Usage: HostTxtEntry [-i] [-x] [hostname.i2p] [key=val]...
*/
public static void main(String[] args) throws Exception {
boolean inner = false;
boolean remove = false;
if (args.length > 0 && args[0].equals("-i")) {
inner = true;
args = Arrays.copyOfRange(args, 1, args.length);
}
if (args.length > 0 && args[0].equals("-x")) {
remove = true;
args = Arrays.copyOfRange(args, 1, args.length);
}
String host;
if (args.length > 0 && args[0].endsWith(".i2p")) {
host = args[0];
args = Arrays.copyOfRange(args, 1, args.length);
} else {
byte[] rand = new byte[5];
RandomSource.getInstance().nextBytes(rand);
host = Base32.encode(rand) + ".i2p";
}
OrderedProperties props = new OrderedProperties();
for (int i = 0; i < args.length; i++) {
int eq = args[i].indexOf("=");
props.setProperty(args[i].substring(0, eq), args[i].substring(eq + 1));
}
props.setProperty("zzzz", "zzzzzzzzzzzzzzz");
// outer
File f = new File("tmp-eepPriv.dat");
PrivateKeyFile pkf = new PrivateKeyFile(f);
pkf.createIfAbsent(SigType.EdDSA_SHA512_Ed25519);
//f.delete();
PrivateKeyFile pkf2;
if (inner) {
// inner
File f2 = new File("tmp-eepPriv2.dat");
pkf2 = new PrivateKeyFile(f2);
pkf2.createIfAbsent(SigType.DSA_SHA1);
//f2.delete();
props.setProperty(PROP_OLDDEST, pkf2.getDestination().toBase64());
} else {
pkf2 = null;
}
HostTxtEntry he = new HostTxtEntry(host, pkf.getDestination().toBase64(), props);
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
//out.write("Before signing:\n");
//he.write(out);
//out.flush();
SigningPrivateKey priv = pkf.getSigningPrivKey();
StringWriter sw = new StringWriter(1024);
BufferedWriter buf = new BufferedWriter(sw);
if (!remove) {
if (inner) {
SigningPrivateKey priv2 = pkf2.getSigningPrivKey();
he.signInner(priv2);
//out.write("After signing inner:\n");
//he.write(out);
}
he.sign(priv);
//out.write("After signing:\n");
he.write(out);
out.flush();
if (inner && !he.hasValidInnerSig())
throw new IllegalStateException("Inner fail 1");
if (!he.hasValidSig())
throw new IllegalStateException("Outer fail 1");
// now create 2nd, read in
he.write(buf);
buf.flush();
String line = sw.toString();
line = line.substring(line.indexOf(PROPS_SEPARATOR) + 2);
HostTxtEntry he2 = new HostTxtEntry(host, pkf.getDestination().toBase64(), line);
if (inner && !he2.hasValidInnerSig())
throw new IllegalStateException("Inner fail 2");
if (!he2.hasValidSig())
throw new IllegalStateException("Outer fail 2");
} else {
// 'remove' tests (corrupts earlier sigs)
he.getProps().remove(PROP_SIG);
he.signRemove(priv);
//out.write("Remove entry:\n");
sw = new StringWriter(1024);
buf = new BufferedWriter(sw);
he.writeRemoveLine(buf);
buf.flush();
out.write(sw.toString());
out.flush();
String line = sw.toString().substring(2).trim();
HostTxtEntry he3 = new HostTxtEntry(line);
if (!he3.hasValidRemoveSig())
throw new IllegalStateException("Remove verify fail");
}
//out.write("Test passed\n");
//out.flush();
}
}

View File

@@ -1,5 +1,7 @@
package net.i2p.client.naming;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Constructor;
import java.util.Collections;
import java.util.HashMap;
@@ -39,8 +41,8 @@ public class MetaNamingService extends DummyNamingService {
while (tok.hasMoreTokens()) {
try {
Class<?> cls = Class.forName(tok.nextToken());
Constructor<?> con = cls.getConstructor(new Class[] { I2PAppContext.class });
addNamingService((NamingService)con.newInstance(new Object[] { context }), false);
Constructor<?> con = cls.getConstructor(I2PAppContext.class);
addNamingService((NamingService)con.newInstance(), false);
} catch (Exception ex) {
}
}
@@ -179,6 +181,19 @@ public class MetaNamingService extends DummyNamingService {
return rv;
}
/**
* All services aggregated
* @since 0.9.20
*/
@Override
public Map<String, String> getBase64Entries(Properties options) {
Map<String, String> rv = new HashMap<String, String>();
for (NamingService ns : _services) {
rv.putAll(ns.getBase64Entries(options));
}
return rv;
}
/**
* All services aggregated
*/
@@ -191,6 +206,17 @@ public class MetaNamingService extends DummyNamingService {
return rv;
}
/**
* All services aggregated.
* Duplicates not removed (for efficiency)
* @since 0.9.20
*/
public void export(Writer out, Properties options) throws IOException {
for (NamingService ns : _services) {
export(out, options);
}
}
/**
* All services aggregated
*/

View File

@@ -7,12 +7,16 @@
*/
package net.i2p.client.naming;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Constructor;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.CopyOnWriteArraySet;
import net.i2p.I2PAppContext;
@@ -60,6 +64,8 @@ public abstract class NamingService {
/**
* Reverse lookup a destination
* This implementation returns reverseLookup(dest, null).
*
* @param dest non-null
* @return a host name for this Destination, or <code>null</code>
* if none is known. It is safe for subclasses to always return
@@ -70,7 +76,10 @@ public abstract class NamingService {
}
/**
* Reverse lookup a hash
* Reverse lookup a hash.
* This implementation returns null.
* Subclasses implementing reverse lookups should override.
*
* @param h non-null
* @return a host name for this hash, or <code>null</code>
* if none is known. It is safe for subclasses to always return
@@ -79,8 +88,8 @@ public abstract class NamingService {
public String reverseLookup(Hash h) { return null; }
/**
* Check if host name is valid Base64 encoded dest and return this
* dest in that case. Useful as a "fallback" in custom naming
* If the host name is a valid Base64 encoded destination, return the
* decoded Destination. Useful as a "fallback" in custom naming
* implementations.
* This is misnamed as it isn't a "lookup" at all, but
* a simple conversion from a Base64 string to a Destination.
@@ -116,6 +125,7 @@ public abstract class NamingService {
/**
* Warning - unimplemented in any subclass.
* Returns null always.
*
* @return NamingService-specific options or null
* @since 0.8.7
@@ -126,6 +136,7 @@ public abstract class NamingService {
/**
* Warning - unimplemented in any subclass.
* Returns true always.
*
* @return success
* @since 0.8.7
@@ -137,6 +148,9 @@ public abstract class NamingService {
// These are for daisy chaining (MetaNamingService)
/**
* This implementation returns null.
* Subclasses implementing chaining should override.
*
* @return chained naming services or null
* @since 0.8.7
*/
@@ -145,6 +159,9 @@ public abstract class NamingService {
}
/**
* This implementation returns null.
* Subclasses implementing chaining should override.
*
* @return parent naming service or null if this is the root
* @since 0.8.7
*/
@@ -163,7 +180,10 @@ public abstract class NamingService {
/**
* Only for chaining-capable NamingServices
* Only for chaining-capable NamingServices.
* This implementation returns false.
* Subclasses implementing chaining should override.
*
* @param head or tail
* @return success
* @since 0.8.7
@@ -173,7 +193,10 @@ public abstract class NamingService {
}
/**
* Only for chaining-capable NamingServices
* Only for chaining-capable NamingServices.
* This implementation returns false.
* Subclasses implementing chaining should override.
*
* @return success
* @since 0.8.7
*/
@@ -194,6 +217,9 @@ public abstract class NamingService {
}
/**
* This implementation returns -1.
* Most subclasses should override.
*
* @param options NamingService-specific, can be null
* @return number of entries (matching the options if non-null) or -1 if unknown
* @since 0.8.7
@@ -235,14 +261,83 @@ public abstract class NamingService {
* Warning - This will bring the whole database into memory
* if options is null, empty, or unsupported, use with caution.
*
* This implementation calls getEntries(options) and returns a SortedMap.
* Subclasses should override if they store base64 natively.
*
* @param options NamingService-specific, can be null
* @return all mappings (matching the options if non-null)
* or empty Map if none;
* Returned Map is not necessarily sorted, implementation dependent
* @since 0.8.7
* @since 0.8.7, implemented in 0.9.20
*/
public Map<String, String> getBase64Entries(Properties options) {
return Collections.emptyMap();
Map<String, Destination> entries = getEntries(options);
if (entries.size() <= 0)
return Collections.emptyMap();
Map<String, String> rv = new TreeMap<String, String>();
for (Map.Entry<String, Destination> e : entries.entrySet()) {
rv.put(e.getKey(), e.getValue().toBase64());
}
return rv;
}
/**
* Export in a hosts.txt format.
* Output is not necessarily sorted, implementation dependent.
* Output may or may not contain comment lines, implementation dependent.
* Caller must close writer.
*
* This implementation calls getBase64Entries().
* Subclasses should override if they store in a hosts.txt format natively.
*
* @since 0.9.20
*/
public void export(Writer out) throws IOException {
export(out, null);
}
/**
* Export in a hosts.txt format.
* Output is not necessarily sorted, implementation dependent.
* Output may or may not contain comment lines, implementation dependent.
* Caller must close writer.
*
* This implementation calls getBase64Entries(options).
* Subclasses should override if they store in a hosts.txt format natively.
*
* @param options NamingService-specific, can be null
* @since 0.9.20
*/
public void export(Writer out, Properties options) throws IOException {
Map<String, String> entries = getBase64Entries(options);
out.write("# Address book: ");
out.write(getName());
if (options != null) {
String list = options.getProperty("list");
if (list != null)
out.write(" (" + list + ')');
}
final String nl = System.getProperty("line.separator", "\n");
out.write(nl);
int sz = entries.size();
if (sz <= 0) {
out.write("# No entries");
out.write(nl);
return;
}
out.write("# Exported: ");
out.write((new Date()).toString());
out.write(nl);
if (sz > 1) {
out.write("# " + sz + " entries");
out.write(nl);
}
for (Map.Entry<String, String> e : entries.entrySet()) {
out.write(e.getKey());
out.write('=');
out.write(e.getValue());
out.write(nl);
}
}
/**
@@ -267,6 +362,10 @@ public abstract class NamingService {
}
/**
* Add a hostname and Destination to the addressbook.
* Overwrites old entry if it exists.
* See also putIfAbsent() and update().
*
* @return success
* @since 0.8.7
*/
@@ -275,6 +374,10 @@ public abstract class NamingService {
}
/**
* Add a hostname and Destination to the addressbook.
* Overwrites old entry if it exists.
* See also putIfAbsent() and update().
*
* @param options NamingService-specific, can be null
* @return success
* @since 0.8.7
@@ -284,7 +387,10 @@ public abstract class NamingService {
}
/**
* Fails if entry previously exists
* Add a hostname and Destination to the addressbook.
* Fails if entry previously exists.
* See also put() and update().
*
* @return success
* @since 0.8.7
*/
@@ -293,7 +399,10 @@ public abstract class NamingService {
}
/**
* Fails if entry previously exists
* Add a hostname and Destination to the addressbook.
* Fails if entry previously exists.
* See also put() and update().
*
* @param options NamingService-specific, can be null
* @return success
* @since 0.8.7
@@ -303,8 +412,12 @@ public abstract class NamingService {
}
/**
* Put all the entries, each with the given options.
* This implementation calls put() for each entry.
* Subclasses may override if a more efficient implementation is available.
*
* @param options NamingService-specific, can be null
* @return success
* @return total success, or false if any put failed
* @since 0.8.7
*/
public boolean putAll(Map<String, Destination> entries, Properties options) {
@@ -319,6 +432,7 @@ public abstract class NamingService {
/**
* Fails if entry did not previously exist.
* Warning - unimplemented in any subclass.
* This implementation returns false.
*
* @param d may be null if only options are changing
* @param options NamingService-specific, can be null
@@ -330,16 +444,18 @@ public abstract class NamingService {
}
/**
* @return success
* Delete the entry.
* @return true if removed successfully, false on error or if it did not exist
* @since 0.8.7
*/
public boolean remove(String hostname) {
return remove(hostname, null);
return remove(hostname, (Properties) null);
}
/**
* Delete the entry.
* @param options NamingService-specific, can be null
* @return success
* @return true if removed successfully, false on error or if it did not exist
* @since 0.8.7
*/
public boolean remove(String hostname, Properties options) {
@@ -387,8 +503,9 @@ public abstract class NamingService {
/**
* Same as lookup(hostname) but with in and out options
* Note that whether this (and lookup(hostname)) resolve B32 addresses is
* NamingService-specific.
* Note that whether this (and lookup(hostname)) resolve Base 32 addresses
* in the form {52 chars}.b32.i2p is NamingService-specific.
*
* @param lookupOptions input parameter, NamingService-specific, can be null
* @param storedOptions output parameter, NamingService-specific, any stored properties will be added if non-null
* @return dest or null
@@ -398,6 +515,9 @@ public abstract class NamingService {
/**
* Same as reverseLookup(dest) but with options
* This implementation returns null.
* Subclasses implementing reverse lookups should override.
*
* @param d non-null
* @param options NamingService-specific, can be null
* @return host name or null
@@ -410,6 +530,9 @@ public abstract class NamingService {
/**
* Lookup a Base 32 address. This may require the router to fetch the LeaseSet,
* which may take quite a while.
* This implementation returns null.
* See also lookup(Hash, int).
*
* @param hostname must be {52 chars}.b32.i2p
* @param timeout in seconds; <= 0 means use router default
* @return dest or null
@@ -420,7 +543,9 @@ public abstract class NamingService {
}
/**
* Same as lookupB32 but with the SHA256 Hash precalculated
* Same as lookupBase32() but with the SHA256 Hash precalculated
* This implementation returns null.
*
* @param timeout in seconds; <= 0 means use router default
* @return dest or null
* @since 0.8.7
@@ -447,6 +572,172 @@ public abstract class NamingService {
//// End New API
//// Begin new API for multiple Destinations
/**
* For NamingServices that support multiple Destinations for a single host name,
* return all of them.
*
* It is recommended that the returned list is in order of priority, highest-first,
* but this is NamingService-specific.
*
* Not recommended for resolving Base 32 addresses;
* whether this does resolve Base 32 addresses
* in the form {52 chars}.b32.i2p is NamingService-specific.
*
* @return non-empty List of Destinations, or null if nothing found
* @since 0.9.26
*/
public List<Destination> lookupAll(String hostname) {
return lookupAll(hostname, null, null);
}
/**
* For NamingServices that support multiple Destinations and Properties for a single host name,
* return all of them.
*
* It is recommended that the returned list is in order of priority, highest-first,
* but this is NamingService-specific.
*
* If storedOptions is non-null, it must be a List that supports null entries.
* If the returned value (the List of Destinations) is non-null,
* the same number of Properties objects will be added to storedOptions.
* If no properties were found for a given Destination, the corresponding
* entry in the storedOptions list will be null.
*
* Not recommended for resolving Base 32 addresses;
* whether this does resolve Base 32 addresses
* in the form {52 chars}.b32.i2p is NamingService-specific.
*
* This implementation simply calls lookup().
* Subclasses implementing multiple destinations per hostname should override.
*
* @param lookupOptions input parameter, NamingService-specific, may be null
* @param storedOptions output parameter, NamingService-specific, any stored properties will be added if non-null
* @return non-empty List of Destinations, or null if nothing found
* @since 0.9.26
*/
public List<Destination> lookupAll(String hostname, Properties lookupOptions, List<Properties> storedOptions) {
Properties props = storedOptions != null ? new Properties() : null;
Destination d = lookup(hostname, lookupOptions, props);
List<Destination> rv;
if (d != null) {
rv = Collections.singletonList(d);
if (storedOptions != null)
storedOptions.add(props.isEmpty() ? null : props);
} else {
rv = null;
}
return rv;
}
/**
* Add a Destination to an existing hostname's entry in the addressbook.
*
* @return success
* @since 0.9.26
*/
public boolean addDestination(String hostname, Destination d) {
return addDestination(hostname, d, null);
}
/**
* Add a Destination to an existing hostname's entry in the addressbook.
* This implementation simply calls putIfAbsent().
* Subclasses implementing multiple destinations per hostname should override.
*
* @param options NamingService-specific, may be null
* @return success
* @since 0.9.26
*/
public boolean addDestination(String hostname, Destination d, Properties options) {
return putIfAbsent(hostname, d, options);
}
/**
* Remove a hostname's entry only if it contains the Destination d.
* If the NamingService supports multiple Destinations per hostname,
* and this is the only Destination, removes the entire entry.
* If aditional Destinations remain, it only removes the
* specified Destination from the entry.
*
* @return true if entry containing d was successfully removed.
* @since 0.9.26
*/
public boolean remove(String hostname, Destination d) {
return remove(hostname, d, null);
}
/**
* Remove a hostname's entry only if it contains the Destination d.
* If the NamingService supports multiple Destinations per hostname,
* and this is the only Destination, removes the entire entry.
* If aditional Destinations remain, it only removes the
* specified Destination from the entry.
*
* This implementation simply calls lookup() and remove().
* Subclasses implementing multiple destinations per hostname,
* or with more efficient implementations, should override.
* Fails if entry previously exists.
*
* @param options NamingService-specific, may be null
* @return true if entry containing d was successfully removed.
* @since 0.9.26
*/
public boolean remove(String hostname, Destination d, Properties options) {
Destination old = lookup(hostname, options, null);
if (!d.equals(old))
return false;
return remove(hostname, options);
}
/**
* Reverse lookup a hash.
* This implementation returns the result from reverseLookup, or null.
* Subclasses implementing reverse lookups should override.
*
* @param h non-null
* @return a non-empty list of host names for this hash, or <code>null</code>
* if none is known. It is safe for subclasses to always return
* <code>null</code> if no reverse lookup is possible.
* @since 0.9.26
*/
public List<String> reverseLookupAll(Hash h) {
String s = reverseLookup(h);
return (s != null) ? Collections.singletonList(s) : null;
}
/**
* Reverse lookup a destination
* This implementation returns reverseLookupAll(dest, null).
*
* @param dest non-null
* @return a non-empty list of host names for this Destination, or <code>null</code>
* if none is known. It is safe for subclasses to always return
* <code>null</code> if no reverse lookup is possible.
* @since 0.9.26
*/
public List<String> reverseLookupAll(Destination dest) {
return reverseLookupAll(dest, null);
}
/**
* Same as reverseLookupAll(dest) but with options
* This implementation returns the result from reverseLookup, or null.
* Subclasses implementing reverse lookups should override.
*
* @param d non-null
* @param options NamingService-specific, can be null
* @return a non-empty list of host names for this Destination, or <code>null</code>
* @since 0.9.26
*/
public List<String> reverseLookupAll(Destination d, Properties options) {
String s = reverseLookup(d, options);
return (s != null) ? Collections.singletonList(s) : null;
}
//// End new API for multiple Destinations
/**
* WARNING - for use by I2PAppContext only - others must use
* I2PAppContext.namingService()
@@ -463,8 +754,8 @@ public abstract class NamingService {
String impl = context.getProperty(PROP_IMPL, DEFAULT_IMPL);
try {
Class<?> cls = Class.forName(impl);
Constructor<?> con = cls.getConstructor(new Class[] { I2PAppContext.class });
instance = (NamingService)con.newInstance(new Object[] { context });
Constructor<?> con = cls.getConstructor(I2PAppContext.class);
instance = (NamingService)con.newInstance(context);
} catch (Exception ex) {
Log log = context.logManager().getLog(NamingService.class);
// Blockfile may throw RuntimeException but HostsTxt won't

View File

@@ -15,7 +15,9 @@ import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -26,6 +28,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
import net.i2p.I2PAppContext;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.util.FileUtil;
import net.i2p.util.Log;
@@ -74,6 +77,8 @@ public class SingleFileNamingService extends NamingService {
}
/**
* Will strip a "www." prefix and retry if lookup fails
*
* @param hostname case-sensitive; caller should convert to lower case
* @param lookupOptions ignored
* @param storedOptions ignored
@@ -82,9 +87,11 @@ public class SingleFileNamingService extends NamingService {
public Destination lookup(String hostname, Properties lookupOptions, Properties storedOptions) {
try {
String key = getKey(hostname);
if (key == null && hostname.startsWith("www.") && hostname.length() > 7)
key = getKey(hostname.substring(4));
if (key != null)
return lookupBase64(key);
} catch (Exception ioe) {
} catch (IOException ioe) {
if (_file.exists())
_log.error("Error loading hosts file " + _file, ioe);
else if (_log.shouldLog(Log.WARN))
@@ -116,7 +123,7 @@ public class SingleFileNamingService extends NamingService {
return line.substring(0, split);
}
return null;
} catch (Exception ioe) {
} catch (IOException ioe) {
if (_file.exists())
_log.error("Error loading hosts file " + _file, ioe);
else if (_log.shouldLog(Log.WARN))
@@ -158,7 +165,8 @@ public class SingleFileNamingService extends NamingService {
/**
* @param hostname case-sensitive; caller should convert to lower case
* @param options ignored
* @param options if non-null, any prefixed with '=' will be appended
* in subscription format
*/
@Override
public boolean put(String hostname, Destination d, Properties options) {
@@ -189,6 +197,9 @@ public class SingleFileNamingService extends NamingService {
out.write(hostname);
out.write('=');
out.write(d.toBase64());
// subscription options
if (options != null)
writeOptions(options, out);
out.newLine();
out.close();
boolean success = FileUtil.rename(tmp, _file);
@@ -208,11 +219,12 @@ public class SingleFileNamingService extends NamingService {
/**
* @param hostname case-sensitive; caller should convert to lower case
* @param options ignored
* @param options if non-null, any prefixed with '=' will be appended
* in subscription format
*/
@Override
public boolean putIfAbsent(String hostname, Destination d, Properties options) {
OutputStream out = null;
BufferedWriter out = null;
if (!getWriteLock())
return false;
try {
@@ -229,11 +241,14 @@ public class SingleFileNamingService extends NamingService {
}
// else new file
}
out = new SecureFileOutputStream(_file, true);
out = new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(_file, true), "UTF-8"));
// FIXME fails if previous last line didn't have a trailing \n
out.write(hostname.getBytes("UTF-8"));
out.write(hostname);
out.write('=');
out.write(d.toBase64().getBytes());
out.write(d.toBase64());
// subscription options
if (options != null)
writeOptions(options, out);
out.write('\n');
out.close();
for (NamingServiceListener nsl : _listeners) {
@@ -247,6 +262,34 @@ public class SingleFileNamingService extends NamingService {
} finally { releaseWriteLock(); }
}
/**
* Write the subscription options part of the line (including the #!).
* Only options starting with '=' (if any) are written (with the '=' stripped).
* Does not write a newline.
*
* @param options non-null
* @since 0.9.26
*/
private static void writeOptions(Properties options, Writer out) throws IOException {
boolean started = false;
for (Map.Entry<Object, Object> e : options.entrySet()) {
String k = (String) e.getKey();
if (!k.startsWith("="))
continue;
k = k.substring(1);
String v = (String) e.getValue();
if (started) {
out.write(HostTxtEntry.PROP_SEPARATOR);
} else {
started = true;
out.write(HostTxtEntry.PROPS_SEPARATOR);
}
out.write(k);
out.write('=');
out.write(v);
}
}
/**
* @param hostname case-sensitive; caller should convert to lower case
* @param options ignored
@@ -364,6 +407,103 @@ public class SingleFileNamingService extends NamingService {
}
}
/**
* Overridden since we store base64 natively.
*
* @param options As follows:
* Key "search": return only those matching substring
* Key "startsWith": return only those starting with
* ("[0-9]" allowed)
* @return all mappings (matching the options if non-null)
* or empty Map if none.
* Returned Map is not sorted.
* @since 0.9.20
*/
public Map<String, String> getBase64Entries(Properties options) {
if (!_file.exists())
return Collections.emptyMap();
String searchOpt = null;
String startsWith = null;
if (options != null) {
searchOpt = options.getProperty("search");
startsWith = options.getProperty("startsWith");
}
BufferedReader in = null;
getReadLock();
try {
in = new BufferedReader(new InputStreamReader(new FileInputStream(_file), "UTF-8"), 16*1024);
String line = null;
Map<String, String> rv = new HashMap<String, String>();
while ( (line = in.readLine()) != null) {
if (line.length() <= 0)
continue;
if (startsWith != null) {
if (startsWith.equals("[0-9]")) {
if (line.charAt(0) < '0' || line.charAt(0) > '9')
continue;
} else if (!line.startsWith(startsWith)) {
continue;
}
}
if (line.startsWith("#"))
continue;
if (line.indexOf('#') > 0) // trim off any end of line comment
line = line.substring(0, line.indexOf('#')).trim();
int split = line.indexOf('=');
if (split <= 0)
continue;
String key = line.substring(0, split);
if (searchOpt != null && key.indexOf(searchOpt) < 0)
continue;
String b64 = line.substring(split+1); //.trim() ??????????????
if (b64.length() < 387)
continue;
rv.put(key, b64);
}
if (searchOpt == null && startsWith == null) {
_lastWrite = _file.lastModified();
_size = rv.size();
}
return rv;
} catch (IOException ioe) {
_log.error("getEntries error", ioe);
return Collections.emptyMap();
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
releaseReadLock();
}
}
/**
* Overridden for efficiency.
* Output is not sorted.
*
* @param options ignored
* @since 0.9.20
*/
public void export(Writer out, Properties options) throws IOException {
out.write("# Address book: ");
out.write(getName());
final String nl = System.getProperty("line.separator", "\n");
out.write(nl);
out.write("# Exported: ");
out.write((new Date()).toString());
out.write(nl);
BufferedReader in = null;
getReadLock();
try {
in = new BufferedReader(new InputStreamReader(new FileInputStream(_file), "UTF-8"), 16*1024);
String line = null;
while ( (line = in.readLine()) != null) {
out.write(line);
out.write(nl);
}
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
releaseReadLock();
}
}
/**
* @param options ignored
* @return all known host names, unsorted

View File

@@ -1,6 +1,13 @@
<html><body>
<p>Implements the base I2P SDK for developing applications that communicate
through I2P.</p>
<p>
Interfaces and factories for
the base I2P SDK used to develop applications that communicate
through I2P.
</p>
<p>
Implementation moved to net.i2p.client.impl in 0.9.21.
</p>
<p>When a client application wants to communicate over I2P, the first thing it
needs to do is get a {@link net.i2p.client.I2PClient} from the

View File

@@ -72,6 +72,7 @@ public class AESEngine {
* @return null on error
* @deprecated unused
*/
@Deprecated
public byte[] safeEncrypt(byte payload[], SessionKey sessionKey, byte iv[], int paddedSize) {
if ((iv == null) || (payload == null) || (sessionKey == null) || (iv.length != 16)) return null;
@@ -103,6 +104,7 @@ public class AESEngine {
* @return null on error
* @deprecated unused
*/
@Deprecated
public byte[] safeDecrypt(byte payload[], SessionKey sessionKey, byte iv[]) {
if ((iv == null) || (payload == null) || (sessionKey == null) || (iv.length != 16)) return null;

View File

@@ -1,13 +1,35 @@
package net.i2p.crypto;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertStore;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.CRL;
import java.security.cert.CRLException;
import java.security.cert.X509Certificate;
import java.security.cert.X509CRL;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.KeySpec;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import javax.naming.InvalidNameException;
import javax.naming.ldap.LdapName;
@@ -16,6 +38,7 @@ import javax.security.auth.x500.X500Principal;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
import net.i2p.util.SecureFileOutputStream;
import net.i2p.util.SystemVersion;
@@ -25,34 +48,23 @@ import net.i2p.util.SystemVersion;
*
* @since 0.9.9
*/
public class CertUtil {
public final class CertUtil {
private static final String CERT_DIR = "certificates";
private static final String REVOCATION_DIR = "revocations";
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.
* Write a certificate to a file in base64 format.
*
* @return success
* @since 0.8.2, moved from SSLEepGet in 0.9.9
*/
public static boolean saveCert(Certificate cert, File file) {
OutputStream os = null;
PrintWriter wr = null;
try {
// Get the encoded form which is suitable for exporting
byte[] buf = cert.getEncoded();
os = new SecureFileOutputStream(file);
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();
exportCert(cert, os);
return true;
} catch (CertificateEncodingException cee) {
error("Error writing X509 Certificate " + file.getAbsolutePath(), cee);
@@ -65,6 +77,84 @@ public class CertUtil {
}
}
/**
* Writes the private key and all certs in base64 format.
* Does NOT close the stream. Throws on all errors.
*
* @param pk non-null
* @param certs certificate chain, null or empty to export pk only
* @throws InvalidKeyException if the key does not support encoding
* @throws CertificateEncodingException if a cert does not support encoding
* @since 0.9.24
*/
public static void exportPrivateKey(PrivateKey pk, Certificate[] certs, OutputStream out)
throws IOException, GeneralSecurityException {
exportPrivateKey(pk, out);
if (certs == null)
return;
for (int i = 0; i < certs.length; i++) {
exportCert(certs[i], out);
}
}
/**
* Modified from:
* http://www.exampledepot.com/egs/java.security.cert/ExportCert.html
*
* Writes a certificate in base64 format.
* Does NOT close the stream. Throws on all errors.
*
* @since 0.9.24, pulled out of saveCert(), public since 0.9.25
*/
public static void exportCert(Certificate cert, OutputStream out)
throws IOException, CertificateEncodingException {
// Get the encoded form which is suitable for exporting
byte[] buf = cert.getEncoded();
writePEM(buf, "CERTIFICATE", out);
}
/**
* Modified from:
* http://www.exampledepot.com/egs/java.security.cert/ExportCert.html
*
* Writes a private key in base64 format.
* Does NOT close the stream. Throws on all errors.
*
* @throws InvalidKeyException if the key does not support encoding
* @since 0.9.24
*/
private static void exportPrivateKey(PrivateKey pk, OutputStream out)
throws IOException, InvalidKeyException {
// Get the encoded form which is suitable for exporting
byte[] buf = pk.getEncoded();
if (buf == null)
throw new InvalidKeyException("encoding unsupported for this key");
writePEM(buf, "PRIVATE KEY", out);
}
/**
* Modified from:
* http://www.exampledepot.com/egs/java.security.cert/ExportCert.html
*
* Writes data in base64 format.
* Does NOT close the stream. Throws on all errors.
*
* @since 0.9.25 consolidated from other methods
*/
private static void writePEM(byte[] buf, String what, OutputStream out)
throws IOException {
PrintWriter wr = new PrintWriter(new OutputStreamWriter(out, "UTF-8"));
wr.println("-----BEGIN " + what + "-----");
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 " + what + "-----");
wr.flush();
if (wr.checkError())
throw new IOException("Failed write to " + out);
}
/**
* Get a value out of the subject distinguished name.
*
@@ -74,12 +164,40 @@ public class CertUtil {
* @return value or null if not found
*/
public static String getSubjectValue(X509Certificate cert, String type) {
X500Principal p = cert.getSubjectX500Principal();
return getValue(p, type);
}
/**
* Get a value out of the issuer distinguished name.
*
* Warning - unsupported in Android (no javax.naming), returns null.
*
* @param type e.g. "CN"
* @return value or null if not found
* @since 0.9.24
*/
public static String getIssuerValue(X509Certificate cert, String type) {
X500Principal p = cert.getIssuerX500Principal();
return getValue(p, type);
}
/**
* Get a value out of a X500Principal.
*
* Warning - unsupported in Android (no javax.naming), returns null.
*
* @param type e.g. "CN"
* @return value or null if not found
*/
private static String getValue(X500Principal p, String type) {
if (SystemVersion.isAndroid()) {
error("Don't call this in Android", new UnsupportedOperationException("I did it"));
return null;
}
if (p == null)
return null;
type = type.toUpperCase(Locale.US);
X500Principal p = cert.getSubjectX500Principal();
String subj = p.getName();
try {
LdapName name = new LdapName(subj);
@@ -103,4 +221,341 @@ public class CertUtil {
Log l = ctx.logManager().getLog(CertUtil.class);
l.log(level, msg, t);
}
/**
* Get the Java public key from a X.509 certificate file.
* Throws if the certificate is invalid (e.g. expired).
*
* This DOES check for revocation.
*
* @return non-null, throws on all errors including certificate invalid
* @since 0.9.24 moved from SU3File private method
*/
public static PublicKey loadKey(File kd) throws IOException, GeneralSecurityException {
X509Certificate cert = loadCert(kd);
if (isRevoked(cert))
throw new CRLException("Certificate is revoked");
return cert.getPublicKey();
}
/**
* Get the certificate from a X.509 certificate file.
* Throws if the certificate is invalid (e.g. expired).
*
* This does NOT check for revocation.
*
* @return non-null, throws on all errors including certificate invalid
* @since 0.9.24 adapted from SU3File private method
*/
public static X509Certificate loadCert(File kd) throws IOException, GeneralSecurityException {
InputStream fis = null;
try {
fis = new FileInputStream(kd);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(fis);
cert.checkValidity();
return cert;
} catch (IllegalArgumentException iae) {
// java 1.8.0_40-b10, openSUSE
// Exception in thread "main" java.lang.IllegalArgumentException: Input byte array has wrong 4-byte ending unit
// at java.util.Base64$Decoder.decode0(Base64.java:704)
throw new GeneralSecurityException("cert error", iae);
} finally {
try { if (fis != null) fis.close(); } catch (IOException foo) {}
}
}
/**
* Get a single Private Key from an input stream.
* Does NOT close the stream.
*
* @return non-null, non-empty, throws on all errors including certificate invalid
* @since 0.9.25
*/
public static PrivateKey loadPrivateKey(InputStream in) throws IOException, GeneralSecurityException {
try {
String line;
while ((line = DataHelper.readLine(in)) != null) {
if (line.startsWith("---") && line.contains("BEGIN") && line.contains("PRIVATE"))
break;
}
if (line == null)
throw new IOException("no private key found");
StringBuilder buf = new StringBuilder(128);
while ((line = DataHelper.readLine(in)) != null) {
if (line.startsWith("---"))
break;
buf.append(line.trim());
}
if (buf.length() <= 0)
throw new IOException("no private key found");
byte[] data = Base64.decode(buf.toString(), true);
if (data == null)
throw new CertificateEncodingException("bad base64 cert");
PrivateKey rv = null;
// try all the types
for (SigAlgo algo : EnumSet.allOf(SigAlgo.class)) {
try {
KeySpec ks = new PKCS8EncodedKeySpec(data);
String alg = algo.getName();
KeyFactory kf = KeyFactory.getInstance(alg);
rv = kf.generatePrivate(ks);
break;
} catch (GeneralSecurityException gse) {
//gse.printStackTrace();
}
}
if (rv == null)
throw new InvalidKeyException("unsupported key type");
return rv;
} catch (IllegalArgumentException iae) {
// java 1.8.0_40-b10, openSUSE
// Exception in thread "main" java.lang.IllegalArgumentException: Input byte array has wrong 4-byte ending unit
// at java.util.Base64$Decoder.decode0(Base64.java:704)
throw new GeneralSecurityException("key error", iae);
}
}
/**
* Get one or more certificates from an input stream.
* Throws if any certificate is invalid (e.g. expired).
* Does NOT close the stream.
*
* This does NOT check for revocation.
*
* @return non-null, non-empty, throws on all errors including certificate invalid
* @since 0.9.25
*/
public static List<X509Certificate> loadCerts(InputStream in) throws IOException, GeneralSecurityException {
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Collection<? extends Certificate> certs = cf.generateCertificates(in);
List<X509Certificate> rv = new ArrayList<X509Certificate>(certs.size());
for (Certificate cert : certs) {
if (!(cert instanceof X509Certificate))
throw new GeneralSecurityException("not a X.509 cert");
X509Certificate xcert = (X509Certificate) cert;
xcert.checkValidity();
rv.add(xcert);
}
if (rv.isEmpty())
throw new IOException("no certs found");
return rv;
} catch (IllegalArgumentException iae) {
// java 1.8.0_40-b10, openSUSE
// Exception in thread "main" java.lang.IllegalArgumentException: Input byte array has wrong 4-byte ending unit
// at java.util.Base64$Decoder.decode0(Base64.java:704)
throw new GeneralSecurityException("cert error", iae);
} finally {
try { in.close(); } catch (IOException foo) {}
}
}
/**
* Write a CRL to a file in base64 format.
*
* @return success
* @since 0.9.25
*/
public static boolean saveCRL(X509CRL crl, File file) {
OutputStream os = null;
try {
os = new SecureFileOutputStream(file);
exportCRL(crl, os);
return true;
} catch (CRLException ce) {
error("Error writing X509 CRL " + file.getAbsolutePath(), ce);
return false;
} catch (IOException ioe) {
error("Error writing X509 CRL " + file.getAbsolutePath(), ioe);
return false;
} finally {
try { if (os != null) os.close(); } catch (IOException foo) {}
}
}
/**
* Writes a CRL in base64 format.
* Does NOT close the stream. Throws on all errors.
*
* @throws CRLException if the crl does not support encoding
* @since 0.9.25
*/
public static void exportCRL(X509CRL crl, OutputStream out)
throws IOException, CRLException {
byte[] buf = crl.getEncoded();
writePEM(buf, "X509 CRL", out);
}
/**
* Is the certificate revoked?
* This loads the CRLs from disk.
* For efficiency, call loadCRLs() and then pass to isRevoked().
*
* @since 0.9.25
*/
public static boolean isRevoked(Certificate cert) {
return isRevoked(I2PAppContext.getGlobalContext(), cert);
}
/**
* Is the certificate revoked?
* This loads the CRLs from disk.
* For efficiency, call loadCRLs() and then pass to isRevoked().
*
* @since 0.9.25
*/
public static boolean isRevoked(I2PAppContext ctx, Certificate cert) {
CertStore store = loadCRLs(ctx);
return isRevoked(store, cert);
}
/**
* Is the certificate revoked?
*
* @since 0.9.25
*/
public static boolean isRevoked(CertStore store, Certificate cert) {
try {
for (CRL crl : store.getCRLs(null)) {
if (crl.isRevoked(cert))
return true;
}
} catch (GeneralSecurityException gse) {}
return false;
}
/**
* Load CRLs from standard locations.
*
* @return non-null, possibly empty
* @since 0.9.25
*/
public static CertStore loadCRLs() {
return loadCRLs(I2PAppContext.getGlobalContext());
}
/**
* Load CRLs from standard locations.
*
* @return non-null, possibly empty
* @since 0.9.25
*/
public static CertStore loadCRLs(I2PAppContext ctx) {
Set<X509CRL> crls = new HashSet<X509CRL>(8);
File dir = new File(ctx.getBaseDir(), CERT_DIR);
dir = new File(dir, REVOCATION_DIR);
loadCRLs(crls, dir);
boolean diff = true;
try {
diff = !ctx.getBaseDir().getCanonicalPath().equals(ctx.getConfigDir().getCanonicalPath());
} catch (IOException ioe) {}
if (diff) {
File dir2 = new File(ctx.getConfigDir(), CERT_DIR);
dir2 = new File(dir2, REVOCATION_DIR);
loadCRLs(crls, dir2);
}
//System.out.println("Loaded " + crls.size() + " CRLs");
CollectionCertStoreParameters ccsp = new CollectionCertStoreParameters(crls);
try {
CertStore store = CertStore.getInstance("Collection", ccsp);
return store;
} catch (GeneralSecurityException gse) {
// shouldn't happen
error("CertStore", gse);
throw new UnsupportedOperationException(gse);
}
}
/**
* Load CRLs from the directory into the set.
*
* @since 0.9.25
*/
private static void loadCRLs(Set<X509CRL> crls, File dir) {
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;
if (f.getName().endsWith(".crl")) {
try {
X509CRL crl = loadCRL(f);
crls.add(crl);
} catch (IOException ioe) {
error("Cannot load CRL from " + f, ioe);
} catch (GeneralSecurityException crle) {
error("Cannot load CRL from " + f, crle);
}
}
}
}
}
}
/**
* Load a CRL.
*
* @return non-null, possibly empty
* @since 0.9.25
*/
private static X509CRL loadCRL(File file) throws IOException, GeneralSecurityException {
InputStream in = null;
try {
in = new FileInputStream(file);
return loadCRL(in);
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
}
}
/**
* Load a CRL. Does NOT Close the stream.
*
* @return non-null
* @since 0.9.25
*/
private static X509CRL loadCRL(InputStream in) throws GeneralSecurityException {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
return (X509CRL) cf.generateCRL(in);
}
/****
public static final void main(String[] args) {
if (args.length < 2) {
System.out.println("Usage: [loadcert | loadcrl | loadcrldir | loadcrldirs | isrevoked | loadprivatekey] file");
System.exit(1);
}
try {
File f = new File(args[1]);
if (args[0].equals("loadcert")) {
loadCert(f);
} else if (args[0].equals("loadcrl")) {
loadCRL(f);
} else if (args[0].equals("loadcrldir")) {
Set<X509CRL> crls = new HashSet<X509CRL>(8);
loadCRLs(crls, f);
System.out.println("Found " + crls.size() + " CRLs");
} else if (args[0].equals("loadcrldirs")) {
CertStore store = loadCRLs(I2PAppContext.getGlobalContext());
Collection<? extends CRL> crls = store.getCRLs(null);
System.out.println("Found " + crls.size() + " CRLs");
} else if (args[0].equals("isrevoked")) {
Certificate cert = loadCert(f);
boolean rv = isRevoked(I2PAppContext.getGlobalContext(), cert);
System.out.println("Revoked? " + rv);
} else {
System.out.println("Usage: [loadcert | loadcrl | loadprivatekey] file");
}
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
****/
}

View File

@@ -36,36 +36,14 @@ import net.i2p.util.SystemVersion;
*
* @author jrandom, thecrypto
*/
public class CryptixAESEngine extends AESEngine {
public final class CryptixAESEngine extends AESEngine {
private final static CryptixRijndael_Algorithm _algo = new CryptixRijndael_Algorithm();
private final static boolean USE_FAKE_CRYPTO = false;
// keys are now cached in the SessionKey objects
//private CryptixAESKeyCache _cache;
/** see test results below */
private static final int MIN_SYSTEM_AES_LENGTH = 704;
private static final boolean USE_SYSTEM_AES;
static {
boolean systemOK = false;
if (hasAESNI()) {
try {
systemOK = Cipher.getMaxAllowedKeyLength("AES") >= 256;
} catch (GeneralSecurityException gse) {
// a NoSuchAlgorithmException
} catch (NoSuchMethodError nsme) {
// JamVM, gij
try {
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec key = new SecretKeySpec(new byte[32], "AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
systemOK = true;
} catch (GeneralSecurityException gse) {
}
}
}
USE_SYSTEM_AES = systemOK;
//System.out.println("Using system AES? " + systemOK);
}
private static final boolean USE_SYSTEM_AES = hasAESNI() && CryptoCheck.isUnlimited();
/**
* Do we have AES-NI support in the processor and JVM?
@@ -124,12 +102,6 @@ public class CryptixAESEngine extends AESEngine {
if (length % 16 != 0)
throw new IllegalArgumentException("Only lengths mod 16 are supported here");
if (USE_FAKE_CRYPTO) {
_log.warn("AES Crypto disabled! Using trivial XOR");
System.arraycopy(payload, payloadIndex, out, outIndex, length);
return;
}
if (USE_SYSTEM_AES && length >= MIN_SYSTEM_AES_LENGTH) {
try {
SecretKeySpec key = new SecretKeySpec(sessionKey.getData(), "AES");
@@ -177,12 +149,6 @@ public class CryptixAESEngine extends AESEngine {
throw new IllegalArgumentException("out is too small (out.length=" + out.length
+ " outIndex=" + outIndex + " length=" + length);
if (USE_FAKE_CRYPTO) {
_log.warn("AES Crypto disabled! Using trivial XOR");
System.arraycopy(payload, payloadIndex, out, outIndex, length);
return ;
}
if (USE_SYSTEM_AES && length >= MIN_SYSTEM_AES_LENGTH) {
try {
SecretKeySpec key = new SecretKeySpec(sessionKey.getData(), "AES");

View File

@@ -26,6 +26,7 @@ public final class CryptixAESKeyCache {
/*
* @deprecated unused, keys are now cached in the SessionKey objects
*/
@Deprecated
public CryptixAESKeyCache() {
_availableKeys = new LinkedBlockingQueue<KeyCacheEntry>(MAX_KEYS);
}
@@ -35,6 +36,7 @@ public final class CryptixAESKeyCache {
*
* @deprecated unused, keys are now cached in the SessionKey objects
*/
@Deprecated
public final KeyCacheEntry acquireKey() {
KeyCacheEntry rv = _availableKeys.poll();
if (rv != null)
@@ -47,6 +49,7 @@ public final class CryptixAESKeyCache {
*
* @deprecated unused, keys are now cached in the SessionKey objects
*/
@Deprecated
public final void releaseKey(KeyCacheEntry key) {
_availableKeys.offer(key);
}

View File

@@ -32,6 +32,7 @@ public final class CryptixRijndael_Algorithm // implicit no-argument constructor
// Debugging methods and variables
//...........................................................................
/****
private static final String _NAME = "Rijndael_Algorithm";
private static final boolean _IN = true, _OUT = false;
@@ -53,6 +54,7 @@ public final class CryptixRijndael_Algorithm // implicit no-argument constructor
private static void trace(String s) {
if (_TRACE) _err.println("<=> " + _NAME + "." + s);
}
****/
// Constants and variables
//...........................................................................
@@ -89,11 +91,13 @@ public final class CryptixRijndael_Algorithm // implicit no-argument constructor
//...........................................................................
static {
/****
if (_RDEBUG && _debuglevel > 6) {
System.out.println("Algorithm Name: Rijndael ver 0.1");
System.out.println("Electronic Codebook (ECB) Mode");
System.out.println();
}
****/
int ROOT = 0x11B;
int i, j = 0;
@@ -383,7 +387,7 @@ public final class CryptixRijndael_Algorithm // implicit no-argument constructor
* @param sessionKey The session key to use for encryption.
*/
public static final void blockEncrypt(byte[] in, byte[] result, int inOffset, int outOffset, Object sessionKey) {
if (_RDEBUG) trace(_IN, "blockEncrypt(" + in + ", " + inOffset + ", " + sessionKey + ")");
//if (_RDEBUG) trace(_IN, "blockEncrypt(" + in + ", " + inOffset + ", " + sessionKey + ")");
int[][] Ke = (int[][]) ((Object[]) sessionKey)[0]; // extract encryption round keys
int ROUNDS = Ke.length - 1;
int[] Ker = Ke[0];
@@ -409,9 +413,11 @@ public final class CryptixRijndael_Algorithm // implicit no-argument constructor
t1 = a1;
t2 = a2;
t3 = a3;
/****
if (_RDEBUG && _debuglevel > 6)
System.out.println("CT" + r + "=" + intToString(t0) + intToString(t1) + intToString(t2)
+ intToString(t3));
****/
}
// last round is special
@@ -436,11 +442,13 @@ public final class CryptixRijndael_Algorithm // implicit no-argument constructor
result[outOffset++] = (byte) (_S[(t0 >>> 16) & 0xFF] ^ (tt >>> 16));
result[outOffset++] = (byte) (_S[(t1 >>> 8) & 0xFF] ^ (tt >>> 8));
result[outOffset++] = (byte) (_S[t2 & 0xFF] ^ tt);
/****
if (_RDEBUG && _debuglevel > 6) {
System.out.println("CT=" + toString(result));
System.out.println();
}
if (_RDEBUG) trace(_OUT, "blockEncrypt()");
****/
}
/**
@@ -458,7 +466,7 @@ public final class CryptixRijndael_Algorithm // implicit no-argument constructor
+ " result.len=" + result.length + " result.offset=" + outOffset);
if (in.length - inOffset <= 15)
throw new IllegalArgumentException("data too small: " + in.length + " inOffset: " + inOffset);
if (_RDEBUG) trace(_IN, "blockDecrypt(" + in + ", " + inOffset + ", " + sessionKey + ")");
//if (_RDEBUG) trace(_IN, "blockDecrypt(" + in + ", " + inOffset + ", " + sessionKey + ")");
int[][] Kd = (int[][]) ((Object[]) sessionKey)[1]; // extract decryption round keys
int ROUNDS = Kd.length - 1;
int[] Kdr = Kd[0];
@@ -484,9 +492,11 @@ public final class CryptixRijndael_Algorithm // implicit no-argument constructor
t1 = a1;
t2 = a2;
t3 = a3;
/****
if (_RDEBUG && _debuglevel > 6)
System.out.println("PT" + r + "=" + intToString(t0) + intToString(t1) + intToString(t2)
+ intToString(t3));
****/
}
// last round is special
@@ -511,11 +521,13 @@ public final class CryptixRijndael_Algorithm // implicit no-argument constructor
result[outOffset++] = (byte) (_Si[(t2 >>> 16) & 0xFF] ^ (tt >>> 16));
result[outOffset++] = (byte) (_Si[(t1 >>> 8) & 0xFF] ^ (tt >>> 8));
result[outOffset++] = (byte) (_Si[t0 & 0xFF] ^ tt);
/****
if (_RDEBUG && _debuglevel > 6) {
System.out.println("PT=" + toString(result));
System.out.println();
}
if (_RDEBUG) trace(_OUT, "blockDecrypt()");
****/
}
/** A basic symmetric encryption/decryption test. */
@@ -544,7 +556,7 @@ public final class CryptixRijndael_Algorithm // implicit no-argument constructor
return makeKey(k, blockSize, null);
}
public static final/* synchronized */Object makeKey(byte[] k, int blockSize, CryptixAESKeyCache.KeyCacheEntry keyData) throws InvalidKeyException {
if (_RDEBUG) trace(_IN, "makeKey(" + k + ", " + blockSize + ")");
//if (_RDEBUG) trace(_IN, "makeKey(" + k + ", " + blockSize + ")");
if (k == null) throw new InvalidKeyException("Empty key");
if (!(k.length == 16 || k.length == 24 || k.length == 32))
throw new InvalidKeyException("Incorrect key length");
@@ -629,7 +641,7 @@ public final class CryptixRijndael_Algorithm // implicit no-argument constructor
sessionKey = new Object[] { Ke, Kd};
else
sessionKey = keyData.key;
if (_RDEBUG) trace(_OUT, "makeKey()");
//if (_RDEBUG) trace(_OUT, "makeKey()");
return sessionKey;
}
@@ -647,7 +659,7 @@ public final class CryptixRijndael_Algorithm // implicit no-argument constructor
blockEncrypt(in, result, inOffset, outOffset, sessionKey);
return;
}
if (_RDEBUG) trace(_IN, "blockEncrypt(" + in + ", " + inOffset + ", " + sessionKey + ", " + blockSize + ")");
//if (_RDEBUG) trace(_IN, "blockEncrypt(" + in + ", " + inOffset + ", " + sessionKey + ", " + blockSize + ")");
Object[] sKey = (Object[]) sessionKey; // extract encryption round keys
int[][] Ke = (int[][]) sKey[0];
@@ -673,7 +685,7 @@ public final class CryptixRijndael_Algorithm // implicit no-argument constructor
^ _T3[(t[(i + s2) % BC] >>> 8) & 0xFF] ^ _T4[t[(i + s3) % BC] & 0xFF])
^ Ke[r][i];
System.arraycopy(a, 0, t, 0, BC);
if (_RDEBUG && _debuglevel > 6) System.out.println("CT" + r + "=" + toString(t));
//if (_RDEBUG && _debuglevel > 6) System.out.println("CT" + r + "=" + toString(t));
}
for (i = 0; i < BC; i++) { // last round is special
tt = Ke[ROUNDS][i];
@@ -682,11 +694,13 @@ public final class CryptixRijndael_Algorithm // implicit no-argument constructor
result[j++] = (byte) (_S[(t[(i + s2) % BC] >>> 8) & 0xFF] ^ (tt >>> 8));
result[j++] = (byte) (_S[t[(i + s3) % BC] & 0xFF] ^ tt);
}
/****
if (_RDEBUG && _debuglevel > 6) {
System.out.println("CT=" + toString(result));
System.out.println();
}
if (_RDEBUG) trace(_OUT, "blockEncrypt()");
****/
}
/**
@@ -704,7 +718,7 @@ public final class CryptixRijndael_Algorithm // implicit no-argument constructor
return;
}
if (_RDEBUG) trace(_IN, "blockDecrypt(" + in + ", " + inOffset + ", " + sessionKey + ", " + blockSize + ")");
//if (_RDEBUG) trace(_IN, "blockDecrypt(" + in + ", " + inOffset + ", " + sessionKey + ", " + blockSize + ")");
Object[] sKey = (Object[]) sessionKey; // extract decryption round keys
int[][] Kd = (int[][]) sKey[1];
@@ -730,7 +744,7 @@ public final class CryptixRijndael_Algorithm // implicit no-argument constructor
^ _T7[(t[(i + s2) % BC] >>> 8) & 0xFF] ^ _T8[t[(i + s3) % BC] & 0xFF])
^ Kd[r][i];
System.arraycopy(a, 0, t, 0, BC);
if (_RDEBUG && _debuglevel > 6) System.out.println("PT" + r + "=" + toString(t));
//if (_RDEBUG && _debuglevel > 6) System.out.println("PT" + r + "=" + toString(t));
}
for (i = 0; i < BC; i++) { // last round is special
tt = Kd[ROUNDS][i];
@@ -739,11 +753,13 @@ public final class CryptixRijndael_Algorithm // implicit no-argument constructor
result[j++] = (byte) (_Si[(t[(i + s2) % BC] >>> 8) & 0xFF] ^ (tt >>> 8));
result[j++] = (byte) (_Si[t[(i + s3) % BC] & 0xFF] ^ tt);
}
/****
if (_RDEBUG && _debuglevel > 6) {
System.out.println("PT=" + toString(result));
System.out.println();
}
if (_RDEBUG) trace(_OUT, "blockDecrypt()");
****/
}
/** A basic symmetric encryption/decryption test for a given key size. */

View File

@@ -0,0 +1,47 @@
package net.i2p.crypto;
import java.security.GeneralSecurityException;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
/**
* Moved from CryptixAESEngine and net.i2p.router.tasks.CryptoChecker
*
* @since 0.9.23
*/
public class CryptoCheck {
private static final boolean _isUnlimited;
static {
boolean unlimited = false;
try {
unlimited = Cipher.getMaxAllowedKeyLength("AES") >= 256;
} catch (GeneralSecurityException gse) {
// a NoSuchAlgorithmException
} catch (NoSuchMethodError nsme) {
// JamVM, gij
try {
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec key = new SecretKeySpec(new byte[32], "AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
unlimited = true;
} catch (GeneralSecurityException gse) {
}
}
_isUnlimited = unlimited;
}
private CryptoCheck() {}
/**
* Do we have unlimited crypto?
*/
public static boolean isUnlimited() {
return _isUnlimited;
}
public static void main(String args[]) {
System.out.println("Unlimited? " + isUnlimited());
}
}

View File

@@ -29,16 +29,22 @@ package net.i2p.crypto;
* POSSIBILITY OF SUCH DAMAGE.
*/
import java.lang.reflect.Constructor;
import java.math.BigInteger;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.DSAParameterSpec;
import net.i2p.crypto.elgamal.spec.ElGamalParameterSpec;
import net.i2p.util.NativeBigInteger;
/**
* Prime for ElGamal from http://tools.ietf.org/html/rfc3526
* Primes for DSA: Generated by TheCrypto http://article.gmane.org/gmane.comp.security.invisiblenet.iip.devel/343
*
* See also: ECConstants, RSAConstants
*
*/
public class CryptoConstants {
public final class CryptoConstants {
public static final NativeBigInteger dsap = new NativeBigInteger(
"9c05b2aa960d9b97b8931963c9cc9e8c3026e9b8ed92fad0a69cc886d5bf8015fcadae31"
+ "a0ad18fab3f01b00a358de237655c4964afaa2b337e96ad316b9fb1cc564b5aec5b69a9f"
@@ -52,6 +58,8 @@ public class CryptoConstants {
+ "985e43d136cdcfc6bd5409cd2f450821142a5e6f8eb1c3ab5d0484b8129fcf17bce4f7f3"
+ "3321c3cb3dbb14a905e7b2b3e93be4708cbcc82",
16);
/** 2048-bit MODP Group from RFC 3526 */
public static final NativeBigInteger elgp = new NativeBigInteger("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
@@ -63,10 +71,45 @@ public class CryptoConstants {
+ "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
+ "DE2BCBF6955817183995497CEA956AE515D2261898FA0510"
+ "15728E5A8AACAA68FFFFFFFFFFFFFFFF", 16);
public static final NativeBigInteger elgg = new NativeBigInteger("2");
/**
* @since 0.9.9
*/
public static final DSAParameterSpec DSA_SHA1_SPEC = new DSAParameterSpec(dsap, dsaq, dsag);
/**
* @since 0.9.25
*/
public static final ElGamalParameterSpec I2P_ELGAMAL_2048_SPEC = new ElGamalParameterSpec(elgp, elgg);
/**
* This will be org.bouncycastle.jce.spec.ElgamalParameterSpec
* if BC is available, otherwise it
* will be net.i2p.crypto.ElgamalParameterSpec
*
* @since 0.9.18
*/
public static final AlgorithmParameterSpec ELGAMAL_2048_SPEC;
static {
AlgorithmParameterSpec spec;
if (ECConstants.isBCAvailable()) {
try {
Class<?> cls = Class.forName("org.bouncycastle.jce.spec.ElGamalParameterSpec");
Constructor<?> con = cls.getConstructor(BigInteger.class, BigInteger.class);
spec = (AlgorithmParameterSpec)con.newInstance(elgp, elgg);
//System.out.println("BC ElG spec loaded");
} catch (Exception e) {
//System.out.println("BC ElG spec failed");
//e.printStackTrace();
spec = I2P_ELGAMAL_2048_SPEC;
}
} else {
//System.out.println("BC not available");
spec = I2P_ELGAMAL_2048_SPEC;
}
ELGAMAL_2048_SPEC = spec;
}
}

View File

@@ -72,7 +72,7 @@ import net.i2p.util.NativeBigInteger;
*
* EdDSA support added in 0.9.15
*/
public class DSAEngine {
public final class DSAEngine {
private final Log _log;
private final I2PAppContext _context;
@@ -257,7 +257,7 @@ public class DSAEngine {
_log.warn("Took too long to verify the signature (" + diff + "ms)");
}
return ok;
} catch (Exception e) {
} catch (RuntimeException e) {
_log.log(Log.CRIT, "Error verifying the signature", e);
return false;
}
@@ -285,8 +285,8 @@ public class DSAEngine {
try {
return altSign(data, offset, length, signingKey);
} catch (GeneralSecurityException gse) {
if (_log.shouldLog(Log.WARN))
_log.warn(type + " Sign Fail", gse);
if (_log.shouldLog(Log.ERROR))
_log.error(type + " Sign Fail", gse);
return null;
}
}
@@ -402,8 +402,7 @@ public class DSAEngine {
long start = _context.clock().now();
BigInteger k;
boolean ok = false;
boolean ok;
do {
k = new NativeBigInteger(160, _context.random());
ok = k.compareTo(CryptoConstants.dsaq) != 1;
@@ -516,15 +515,20 @@ public class DSAEngine {
if (type == SigType.DSA_SHA1)
return altVerifySigSHA1(signature, data, offset, len, verifyingKey);
java.security.Signature jsig;
if (type.getBaseAlgorithm() == SigAlgo.EdDSA)
jsig = new EdDSAEngine(type.getDigestInstance());
else
jsig = java.security.Signature.getInstance(type.getAlgorithmName());
PublicKey pubKey = SigUtil.toJavaKey(verifyingKey);
jsig.initVerify(pubKey);
jsig.update(data, offset, len);
boolean rv = jsig.verify(SigUtil.toJavaSig(signature));
byte[] sigbytes = SigUtil.toJavaSig(signature);
boolean rv;
if (type.getBaseAlgorithm() == SigAlgo.EdDSA) {
// take advantage of one-shot mode
EdDSAEngine jsig = new EdDSAEngine(type.getDigestInstance());
jsig.initVerify(pubKey);
rv = jsig.verifyOneShot(data, offset, len, sigbytes);
} else {
java.security.Signature jsig = java.security.Signature.getInstance(type.getAlgorithmName());
jsig.initVerify(pubKey);
jsig.update(data, offset, len);
rv = jsig.verify(sigbytes);
}
return rv;
}
@@ -564,15 +568,21 @@ public class DSAEngine {
if (type.getHashLen() != hashlen)
throw new IllegalArgumentException("type mismatch hash=" + hash.getClass() + " key=" + type);
String algo = getRawAlgo(type);
java.security.Signature jsig;
if (type.getBaseAlgorithm() == SigAlgo.EdDSA)
jsig = new EdDSAEngine(); // Ignore algo, EdDSAKey includes a hash specification.
else
jsig = java.security.Signature.getInstance(algo);
jsig.initVerify(pubKey);
jsig.update(hash.getData());
boolean rv = jsig.verify(SigUtil.toJavaSig(signature));
byte[] sigbytes = SigUtil.toJavaSig(signature);
boolean rv;
if (type.getBaseAlgorithm() == SigAlgo.EdDSA) {
// take advantage of one-shot mode
// Ignore algo, EdDSAKey includes a hash specification.
EdDSAEngine jsig = new EdDSAEngine();
jsig.initVerify(pubKey);
rv = jsig.verifyOneShot(hash.getData(), sigbytes);
} else {
String algo = getRawAlgo(type);
java.security.Signature jsig = java.security.Signature.getInstance(algo);
jsig.initVerify(pubKey);
jsig.update(hash.getData());
rv = jsig.verify(sigbytes);
}
return rv;
}
@@ -607,15 +617,20 @@ public class DSAEngine {
if (type == SigType.DSA_SHA1)
return altSignSHA1(data, offset, len, privateKey);
java.security.Signature jsig;
if (type.getBaseAlgorithm() == SigAlgo.EdDSA)
jsig = new EdDSAEngine(type.getDigestInstance());
else
jsig = java.security.Signature.getInstance(type.getAlgorithmName());
PrivateKey privKey = SigUtil.toJavaKey(privateKey);
jsig.initSign(privKey, _context.random());
jsig.update(data, offset, len);
return SigUtil.fromJavaSig(jsig.sign(), type);
byte[] sigbytes;
if (type.getBaseAlgorithm() == SigAlgo.EdDSA) {
// take advantage of one-shot mode
EdDSAEngine jsig = new EdDSAEngine(type.getDigestInstance());
jsig.initSign(privKey);
sigbytes = jsig.signOneShot(data, offset, len);
} else {
java.security.Signature jsig = java.security.Signature.getInstance(type.getAlgorithmName());
jsig.initSign(privKey, _context.random());
jsig.update(data, offset, len);
sigbytes = jsig.sign();
}
return SigUtil.fromJavaSig(sigbytes, type);
}
/**
@@ -650,14 +665,20 @@ public class DSAEngine {
if (type.getHashLen() != hashlen)
throw new IllegalArgumentException("type mismatch hash=" + hash.getClass() + " key=" + type);
java.security.Signature jsig;
if (type.getBaseAlgorithm() == SigAlgo.EdDSA)
jsig = new EdDSAEngine(); // Ignore algo, EdDSAKey includes a hash specification.
else
jsig = java.security.Signature.getInstance(algo);
jsig.initSign(privKey, _context.random());
jsig.update(hash.getData());
return SigUtil.fromJavaSig(jsig.sign(), type);
byte[] sigbytes;
if (type.getBaseAlgorithm() == SigAlgo.EdDSA) {
// take advantage of one-shot mode
// Ignore algo, EdDSAKey includes a hash specification.
EdDSAEngine jsig = new EdDSAEngine();
jsig.initSign(privKey);
sigbytes = jsig.signOneShot(hash.getData());
} else {
java.security.Signature jsig = java.security.Signature.getInstance(algo);
jsig.initSign(privKey, _context.random());
jsig.update(hash.getData());
sigbytes = jsig.sign();
}
return SigUtil.fromJavaSig(sigbytes, type);
}
/**

View File

@@ -12,6 +12,7 @@ import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
import java.security.cert.CertificateFactory;
import java.security.cert.CRLException;
import java.security.cert.X509Certificate;
import net.i2p.util.SystemVersion;
@@ -34,7 +35,11 @@ class DirKeyRing implements KeyRing {
* Cert must be in the file (escaped keyName).crt,
* and have a CN == keyName.
*
* This DOES do a revocation check.
*
* CN check unsupported on Android.
*
* @return null if file doesn't exist, throws on all other errors
*/
public PublicKey getKey(String keyName, String scope, SigType type)
throws GeneralSecurityException, IOException {
@@ -47,23 +52,17 @@ class DirKeyRing implements KeyRing {
File kd = new File(sd, fileName + ".crt");
if (!kd.exists())
return null;
InputStream fis = null;
try {
fis = new FileInputStream(kd);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(fis);
cert.checkValidity();
if (!SystemVersion.isAndroid()) {
// getSubjectValue() unsupported on Android.
// Any cert problems will be caught in non-Android testing.
String cn = CertUtil.getSubjectValue(cert, "CN");
if (!keyName.equals(cn))
throw new GeneralSecurityException("CN mismatch: " + cn);
}
return cert.getPublicKey();
} finally {
try { if (fis != null) fis.close(); } catch (IOException foo) {}
X509Certificate cert = CertUtil.loadCert(kd);
if (CertUtil.isRevoked(cert))
throw new CRLException("Certificate is revoked");
if (!SystemVersion.isAndroid()) {
// getSubjectValue() unsupported on Android.
// Any cert problems will be caught in non-Android testing.
String cn = CertUtil.getSubjectValue(cert, "CN");
if (!keyName.equals(cn))
throw new GeneralSecurityException("CN mismatch: " + cn);
}
return cert.getPublicKey();
}
/**

View File

@@ -3,6 +3,7 @@ package net.i2p.crypto;
import java.lang.reflect.Constructor;
import java.math.BigInteger;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.Provider;
import java.security.Security;
import java.security.spec.ECField;
@@ -19,7 +20,7 @@ import net.i2p.util.NativeBigInteger;
*
* @since 0.9.9
*/
class ECConstants {
final class ECConstants {
private static final boolean DEBUG = false;
@@ -42,8 +43,8 @@ class ECConstants {
if (Security.getProvider("BC") == null) {
try {
Class<?> cls = Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
Constructor<?> con = cls.getConstructor(new Class[0]);
Provider bc = (Provider)con.newInstance(new Object[0]);
Constructor<?> con = cls.getConstructor();
Provider bc = (Provider)con.newInstance();
Security.addProvider(bc);
log("Added BC provider");
loaded = true;
@@ -278,7 +279,7 @@ class ECConstants {
AlgorithmParameters ap;
try {
ap = AlgorithmParameters.getInstance("EC");
} catch (Exception e) {
} catch (GeneralSecurityException e) {
if (BC_AVAILABLE) {
log("Named curve " + name + " is not available, trying BC", e);
ap = AlgorithmParameters.getInstance("EC", "BC");
@@ -292,7 +293,7 @@ class ECConstants {
ECParameterSpec rv = ap.getParameterSpec(ECParameterSpec.class);
log("Named curve " + name + " loaded");
return rv;
} catch (Exception e) {
} catch (GeneralSecurityException e) {
log("Named curve " + name + " is not available", e);
return null;
}

View File

@@ -19,7 +19,7 @@ import net.i2p.util.NativeBigInteger;
*
* @since 0.9.16
*/
class ECUtil {
final class ECUtil {
private static final BigInteger TWO = new BigInteger("2");
private static final BigInteger THREE = new BigInteger("3");

View File

@@ -32,7 +32,7 @@ import net.i2p.util.SimpleByteCache;
*
* No, this does not extend AESEngine or CryptixAESEngine.
*/
public class ElGamalAESEngine {
public final class ElGamalAESEngine {
private final Log _log;
private final static int MIN_ENCRYPTED_SIZE = 80; // smallest possible resulting size
private final I2PAppContext _context;
@@ -327,12 +327,12 @@ public class ElGamalAESEngine {
//ByteArrayInputStream bais = new ByteArrayInputStream(decrypted);
int cur = 0;
long numTags = DataHelper.fromLong(decrypted, cur, 2);
if ((numTags < 0) || (numTags > MAX_TAGS_RECEIVED)) throw new Exception("Invalid number of session tags");
if ((numTags < 0) || (numTags > MAX_TAGS_RECEIVED)) throw new IllegalArgumentException("Invalid number of session tags");
if (numTags > 0) tags = new ArrayList<SessionTag>((int)numTags);
cur += 2;
//_log.debug("# tags: " + numTags);
if (numTags * SessionTag.BYTE_LENGTH > decrypted.length - 2) {
throw new Exception("# tags: " + numTags + " is too many for " + (decrypted.length - 2));
throw new IllegalArgumentException("# tags: " + numTags + " is too many for " + (decrypted.length - 2));
}
for (int i = 0; i < numTags; i++) {
byte tag[] = new byte[SessionTag.BYTE_LENGTH];
@@ -344,7 +344,7 @@ public class ElGamalAESEngine {
cur += 4;
//_log.debug("len: " + len);
if ((len < 0) || (len > decrypted.length - cur - Hash.HASH_LENGTH - 1))
throw new Exception("Invalid size of payload (" + len + ", remaining " + (decrypted.length-cur) +")");
throw new IllegalArgumentException("Invalid size of payload (" + len + ", remaining " + (decrypted.length-cur) +")");
//byte hashval[] = new byte[Hash.HASH_LENGTH];
//System.arraycopy(decrypted, cur, hashval, 0, Hash.HASH_LENGTH);
//readHash = new Hash();
@@ -379,8 +379,8 @@ public class ElGamalAESEngine {
return unencrData;
}
throw new Exception("Hash does not match");
} catch (Exception e) {
throw new RuntimeException("Hash does not match");
} catch (RuntimeException e) {
if (_log.shouldLog(Log.WARN)) _log.warn("Unable to decrypt AES block", e);
return null;
}

View File

@@ -52,7 +52,7 @@ import net.i2p.util.SimpleByteCache;
* @author thecrypto, jrandom
*/
public class ElGamalEngine {
public final class ElGamalEngine {
private final Log _log;
private final I2PAppContext _context;
private final YKGenerator _ykgen;

View File

@@ -0,0 +1,22 @@
package net.i2p.crypto;
/**
* PRELIMINARY - unused - subject to change
*
* Base encryption algorithm type
*
* @since 0.9.18
*/
public enum EncAlgo {
ELGAMAL("ElGamal"),
EC("EC");
private final String name;
EncAlgo(String name) {
this.name = name;
}
public String getName() { return name; }
}

View File

@@ -0,0 +1,171 @@
package net.i2p.crypto;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidParameterSpecException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import net.i2p.data.Hash;
import net.i2p.data.SimpleDataStructure;
/**
* PRELIMINARY - unused - subject to change
*
* Defines the properties for various encryption types
* that I2P supports or may someday support.
*
* All PublicKeys and PrivateKeys have a type.
* Note that a EncType specifies both an algorithm and parameters, so that
* we may change primes or curves for a given algorithm.
*
* @since 0.9.18
*/
public enum EncType {
/**
* 2048-bit MODP Group from RFC 3526.
* This is the default.
* Pubkey 256 bytes, privkey 256 bytes.
*/
ELGAMAL_2048(0, 256, 256, EncAlgo.ELGAMAL, "ElGamal/None/NoPadding", CryptoConstants.I2P_ELGAMAL_2048_SPEC, "0"),
/** Pubkey 64 bytes; privkey 32 bytes; */
EC_P256(1, 64, 32, EncAlgo.EC, "EC/None/NoPadding", ECConstants.P256_SPEC, "0.9.20"),
/** Pubkey 96 bytes; privkey 48 bytes; */
EC_P384(2, 96, 48, EncAlgo.EC, "EC/None/NoPadding", ECConstants.P384_SPEC, "0.9.20"),
/** Pubkey 132 bytes; privkey 66 bytes; */
EC_P521(3, 132, 66, EncAlgo.EC, "EC/None/NoPadding", ECConstants.P521_SPEC, "0.9.20");
private final int code, pubkeyLen, privkeyLen;
private final EncAlgo base;
private final String algoName, since;
private final AlgorithmParameterSpec params;
private final boolean isAvail;
/**
*
* @param transformation algorithm/mode/padding
*
*/
EncType(int cod, int pubLen, int privLen, EncAlgo baseAlgo,
String transformation, AlgorithmParameterSpec pSpec, String supportedSince) {
code = cod;
pubkeyLen = pubLen;
privkeyLen = privLen;
base = baseAlgo;
algoName = transformation;
params = pSpec;
since = supportedSince;
isAvail = x_isAvailable();
}
/** the unique identifier for this type */
public int getCode() { return code; }
/** the length of the public key, in bytes */
public int getPubkeyLen() { return pubkeyLen; }
/** the length of the private key, in bytes */
public int getPrivkeyLen() { return privkeyLen; }
/** the standard base algorithm name used for the Java crypto factories */
public EncAlgo getBaseAlgorithm() { return base; }
/** the standard name used for the Java crypto factories */
public String getAlgorithmName() { return algoName; }
/**
* The elliptic curve ECParameterSpec for ECDSA; DSAParameterSpec for DSA
* @throws InvalidParameterSpecException if the algorithm is not available on this JVM.
*/
public AlgorithmParameterSpec getParams() throws InvalidParameterSpecException {
if (params == null)
throw new InvalidParameterSpecException(toString() + " is not available in this JVM");
return params;
}
/**
* The router version in which this type was first supported.
*/
public String getSupportedSince() {
return since;
}
/**
* @return true if supported in this JVM
*/
public boolean isAvailable() {
return isAvail;
}
private boolean x_isAvailable() {
if (ELGAMAL_2048 == this)
return true;
try {
getParams();
} catch (InvalidParameterSpecException e) {
return false;
}
return true;
}
/**
* @return true if supported in this JVM
*/
public static boolean isAvailable(int code) {
EncType type = getByCode(code);
if (type == null)
return false;
return type.isAvailable();
}
/**
* @param stype number or name
* @return true if supported in this JVM
*/
public static boolean isAvailable(String stype) {
EncType type = parseEncType(stype);
if (type == null)
return false;
return type.isAvailable();
}
private static final Map<Integer, EncType> BY_CODE = new HashMap<Integer, EncType>();
static {
for (EncType type : EncType.values()) {
if (BY_CODE.put(Integer.valueOf(type.getCode()), type) != null)
throw new IllegalStateException("Duplicate EncType code");
}
}
/** @return null if not supported */
public static EncType getByCode(int code) {
return BY_CODE.get(Integer.valueOf(code));
}
/**
* Convenience for user apps
*
* @param stype number or name
* @return null if not found
*/
public static EncType parseEncType(String stype) {
try {
String uc = stype.toUpperCase(Locale.US);
return valueOf(uc);
} catch (IllegalArgumentException iae) {
try {
int code = Integer.parseInt(stype);
return getByCode(code);
} catch (NumberFormatException nfe) {
return null;
}
}
}
}

View File

@@ -21,7 +21,7 @@ import org.bouncycastle.oldcrypto.macs.I2PHMac;
*
* Deprecated, used only by Syndie.
*/
public class HMAC256Generator extends HMACGenerator {
public final class HMAC256Generator extends HMACGenerator {
/**
* @param context unused

View File

@@ -58,6 +58,7 @@ public class HMACGenerator {
* @return the first 16 bytes contain the HMAC, the last 16 bytes are zero
* @deprecated unused (not even by Syndie)
*/
@Deprecated
public Hash calculate(SessionKey key, byte data[]) {
if ((key == null) || (key.getData() == null) || (data == null))
throw new NullPointerException("Null arguments for HMAC");

View File

@@ -26,11 +26,15 @@ import java.security.spec.ECPublicKeySpec;
import java.security.spec.EllipticCurve;
import java.security.spec.RSAKeyGenParameterSpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collection;
import net.i2p.I2PAppContext;
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
import net.i2p.crypto.eddsa.EdDSAPublicKey;
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
import net.i2p.crypto.provider.I2PProvider;
import net.i2p.data.Hash;
import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey;
@@ -52,12 +56,14 @@ import net.i2p.util.RandomSource;
/** Define a way of generating asymmetrical key pairs as well as symmetrical keys
* @author jrandom
*/
public class KeyGenerator {
//private final Log _log;
public final class KeyGenerator {
private final I2PAppContext _context;
static {
I2PProvider.addProvider();
}
public KeyGenerator(I2PAppContext context) {
//_log = context.logManager().getLog(KeyGenerator.class);
_context = context;
}
@@ -82,7 +88,6 @@ public class KeyGenerator {
/**
* PBE the passphrase with the salt.
* Warning - SLOW
* Deprecated - Used by Syndie only.
*/
public SessionKey generateSessionKey(byte salt[], byte passphrase[]) {
byte salted[] = new byte[16+passphrase.length];
@@ -119,6 +124,7 @@ public class KeyGenerator {
/**
* @deprecated use getElGamalExponentSize() which allows override in the properties
*/
@Deprecated
public static final int PUBKEY_EXPONENT_SIZE = DEFAULT_USE_LONG_EXPONENT ?
PUBKEY_EXPONENT_SIZE_FULL :
PUBKEY_EXPONENT_SIZE_SHORT;
@@ -207,10 +213,10 @@ public class KeyGenerator {
SimpleDataStructure[] keys = new SimpleDataStructure[2];
BigInteger x = null;
// make sure the random key is less than the DSA q
// make sure the random key is less than the DSA q and greater than zero
do {
x = new NativeBigInteger(160, _context.random());
} while (x.compareTo(CryptoConstants.dsaq) >= 0);
} while (x.compareTo(CryptoConstants.dsaq) >= 0 || x.equals(BigInteger.ZERO));
BigInteger y = CryptoConstants.dsag.modPow(x, CryptoConstants.dsap);
keys[0] = new SigningPublicKey();
@@ -225,7 +231,7 @@ public class KeyGenerator {
}
/**
* Generic signature type, supports DSA and ECDSA
* Generic signature type, supports DSA, ECDSA, EdDSA
* @since 0.9.9
*/
public SimpleDataStructure[] generateSigningKeys(SigType type) throws GeneralSecurityException {
@@ -334,27 +340,51 @@ public class KeyGenerator {
}
}
/**
* Usage: KeyGenerator [sigtype...]
*/
public static void main(String args[]) {
try {
main2(args);
} catch (Exception e) {
} catch (RuntimeException e) {
e.printStackTrace();
}
}
public static void main2(String args[]) {
/**
* Usage: KeyGenerator [sigtype...]
*/
private static void main2(String args[]) {
RandomSource.getInstance().nextBoolean();
try { Thread.sleep(1000); } catch (InterruptedException ie) {}
int runs = 200; // warmup
Collection<SigType> toTest;
if (args.length > 0) {
toTest = new ArrayList<SigType>();
for (int i = 0; i < args.length; i++) {
SigType type = SigType.parseSigType(args[i]);
if (type != null)
toTest.add(type);
else
System.out.println("Unknown type: " + args[i]);
}
if (toTest.isEmpty()) {
System.out.println("No types to test");
return;
}
} else {
toTest = Arrays.asList(SigType.values());
}
for (int j = 0; j < 2; j++) {
for (SigType type : SigType.values()) {
for (SigType type : toTest) {
if (!type.isAvailable()) {
System.out.println("Skipping unavailable: " + type);
continue;
}
try {
System.out.println("Testing " + type);
testSig(type, runs);
} catch (Exception e) {
} catch (GeneralSecurityException e) {
System.out.println("error testing " + type);
e.printStackTrace();
}
@@ -390,6 +420,8 @@ public class KeyGenerator {
RandomSource.getInstance().nextBytes(src);
long start = System.nanoTime();
Signature sig = DSAEngine.getInstance().sign(src, privkey);
if (sig == null)
throw new GeneralSecurityException("signature generation failed");
long mid = System.nanoTime();
boolean ok = DSAEngine.getInstance().verifySignature(sig, src, pubkey);
long end = System.nanoTime();

View File

@@ -5,18 +5,29 @@ import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertStore;
import java.security.cert.X509Certificate;
import java.security.cert.X509CRL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import net.i2p.I2PAppContext;
import net.i2p.crypto.provider.I2PProvider;
import net.i2p.data.Base32;
import net.i2p.util.Log;
import net.i2p.util.SecureDirectory;
@@ -29,13 +40,100 @@ import net.i2p.util.SystemVersion;
*
* @since 0.9.9
*/
public class KeyStoreUtil {
public final class KeyStoreUtil {
public static boolean _blacklistLogged;
public static final String DEFAULT_KEYSTORE_PASSWORD = "changeit";
private static final String DEFAULT_KEY_ALGORITHM = "RSA";
private static final int DEFAULT_KEY_SIZE = 2048;
private static final int DEFAULT_KEY_VALID_DAYS = 3652; // 10 years
static {
I2PProvider.addProvider();
}
/**
* SHA1 hashes
*
* No reports of some of these in a Java keystore but just to be safe...
* CNNIC ones are in Ubuntu keystore.
*
* In comments below are the serial numer, CN, and OU
*/
private static final String[] BLACKLIST_SHA1 = new String[] {
// CNNIC https://googleonlinesecurity.blogspot.com/2015/03/maintaining-digital-certificate-security.html
//new BigInteger("49:33:00:01".replace(":", ""), 16),
//"CNNIC ROOT",
//null,
"8b:af:4c:9b:1d:f0:2a:92:f7:da:12:8e:b9:1b:ac:f4:98:60:4b:6f",
// CNNIC EV root https://bugzilla.mozilla.org/show_bug.cgi?id=607208
//new BigInteger("48:9f:00:01".replace(":", ""), 16),
//"China Internet Network Information Center EV Certificates Root",
//null,
"4f:99:aa:93:fb:2b:d1:37:26:a1:99:4a:ce:7f:f0:05:f2:93:5d:1e",
// Superfish http://blog.erratasec.com/2015/02/extracting-superfish-certificate.html
//new BigInteger("d2:fc:13:87:a9:44:dc:e7".replace(":", ""), 16),
//"Superfish, Inc.",
//null,
"c8:64:48:48:69:d4:1d:2b:0d:32:31:9c:5a:62:f9:31:5a:af:2c:bd",
// eDellRoot https://www.reddit.com/r/technology/comments/3twmfv/dell_ships_laptops_with_rogue_root_ca_exactly/
//new BigInteger("6b:c5:7b:95:18:93:aa:97:4b:62:4a:c0:88:fc:3b:b6".replace(":", ""), 16),
//"eDellRoot",
//null,
"98:a0:4e:41:63:35:77:90:c4:a7:9e:6d:71:3f:f0:af:51:fe:69:27",
// DSDTestProvider https://blog.hboeck.de/archives/876-Superfish-2.0-Dangerous-Certificate-on-Dell-Laptops-breaks-encrypted-HTTPS-Connections.html
// serial number is actually negative; hex string as reported by certtool below
//new BigInteger("a4:4c:38:47:f8:ee:71:80:43:4d:b1:80:b9:a7:e9:62".replace(":", ""), 16)
//new BigInteger("-5b:b3:c7:b8:07:11:8e:7f:bc:b2:4e:7f:46:58:16:9e".replace(":", ""), 16),
//"DSDTestProvider",
//null,
"02:c2:d9:31:06:2d:7b:1d:c2:a5:c7:f5:f0:68:50:64:08:1f:b2:21",
// Verisign G1 Roots
// https://googleonlinesecurity.blogspot.com/2015/12/proactive-measures-in-digital.html
// https://knowledge.symantec.com/support/ssl-certificates-support/index?page=content&id=ALERT1941
// SHA-1
//new BigInteger("3c:91:31:cb:1f:f6:d0:1b:0e:9a:b8:d0:44:bf:12:be".replace(":", ""), 16),
//null,
//"Class 3 Public Primary Certification Authority",
"a1:db:63:93:91:6f:17:e4:18:55:09:40:04:15:c7:02:40:b0:ae:6b",
// MD2
//new BigInteger("70:ba:e4:1d:10:d9:29:34:b6:38:ca:7b:03:cc:ba:bf".replace(":", ""), 16),
//null,
//"Class 3 Public Primary Certification Authority",
"74:2c:31:92:e6:07:e4:24:eb:45:49:54:2b:e1:bb:c5:3e:61:74:e2",
// Comodo SHA1 https://cabforum.org/pipermail/public/2015-December/006500.html
// https://bugzilla.mozilla.org/show_bug.cgi?id=1208461
//new BigInteger("44:be:0c:8b:50:00:21:b4:11:d3:2a:68:06:a9:ad:69".replace(":", ""), 16)
//"UTN - DATACorp SGC"
//null
"58:11:9f:0e:12:82:87:ea:50:fd:d9:87:45:6f:4f:78:dc:fa:d6:d4"
};
private static final Set<SHA1Hash> _blacklist = new HashSet<SHA1Hash>(16);
static {
for (int i = 0; i < BLACKLIST_SHA1.length; i++) {
String s = BLACKLIST_SHA1[i].replace(":", "");
BigInteger bi = new BigInteger(s, 16);
byte[] b = bi.toByteArray();
if (b.length == 21) {
byte[] b2 = new byte[20];
System.arraycopy(b, 1, b2, 0, 20);
b = b2;
}
SHA1Hash h = new SHA1Hash(b);
_blacklist.add(h);
}
}
/**
* Create a new KeyStore object, and load it from ksFile if it is
* non-null and it exists.
@@ -63,6 +161,8 @@ public class KeyStoreUtil {
if (ksFile != null && !exists) {
OutputStream fos = null;
try {
// must be initted
ks.load(null, DEFAULT_KEYSTORE_PASSWORD.toCharArray());
fos = new SecureFileOutputStream(ksFile);
ks.store(fos, pwchars);
} finally {
@@ -98,7 +198,8 @@ public class KeyStoreUtil {
try {
ks.load(null, DEFAULT_KEYSTORE_PASSWORD.toCharArray());
success = addCerts(new File(System.getProperty("java.home"), "etc/security/cacerts"), ks) > 0;
} catch (Exception e) {}
} catch (IOException e) {
} catch (GeneralSecurityException e) {}
} else {
success = loadCerts(new File(System.getProperty("java.home"), "etc/security/cacerts.bks"), ks);
}
@@ -109,11 +210,14 @@ public class KeyStoreUtil {
}
}
if (!success) {
if (success) {
removeBlacklistedCerts(ks);
} else {
try {
// must be initted
ks.load(null, DEFAULT_KEYSTORE_PASSWORD.toCharArray());
} catch (Exception e) {}
} catch (IOException e) {
} catch (GeneralSecurityException e) {}
error("All key store loads failed, will only load local certificates", null);
}
return ks;
@@ -140,13 +244,15 @@ public class KeyStoreUtil {
try {
// not clear if null is allowed for password
ks.load(null, DEFAULT_KEYSTORE_PASSWORD.toCharArray());
} catch (Exception foo) {}
} catch (IOException foo) {
} catch (GeneralSecurityException e) {}
return false;
} catch (IOException ioe) {
error("KeyStore load error, no default keys: " + file.getAbsolutePath(), ioe);
try {
ks.load(null, DEFAULT_KEYSTORE_PASSWORD.toCharArray());
} catch (Exception foo) {}
} catch (IOException foo) {
} catch (GeneralSecurityException e) {}
return false;
} finally {
try { if (fis != null) fis.close(); } catch (IOException foo) {}
@@ -167,11 +273,74 @@ public class KeyStoreUtil {
for(Enumeration<String> e = ks.aliases(); e.hasMoreElements();) {
String alias = e.nextElement();
if (ks.isCertificateEntry(alias)) {
info("Found cert " + alias);
//info("Found cert " + alias);
count++;
}
}
} catch (Exception foo) {}
} catch (GeneralSecurityException e) {}
return count;
}
/**
* Remove all blacklisted X509 Certs in a key store.
*
* @return number successfully removed
* @since 0.9.24
*/
private static int removeBlacklistedCerts(KeyStore ks) {
if (SystemVersion.isAndroid())
return 0;
int count = 0;
try {
MessageDigest md = SHA1.getInstance();
for(Enumeration<String> e = ks.aliases(); e.hasMoreElements();) {
String alias = e.nextElement();
if (ks.isCertificateEntry(alias)) {
Certificate c = ks.getCertificate(alias);
if (c != null && (c instanceof X509Certificate)) {
//X509Certificate xc = (X509Certificate) c;
//BigInteger serial = xc.getSerialNumber();
// debug:
//String xname = CertUtil.getIssuerValue(xc, "CN");
//info("Found \"" + xname + "\" s/n: " + serial.toString(16));
//if (xname == null)
// info("name is null, full issuer: " + xc.getIssuerX500Principal().getName());
byte[] enc = c.getEncoded();
if (enc != null) {
byte[] h = md.digest(enc);
//StringBuilder buf = new StringBuilder(60);
//String hex = DataHelper.toString(h);
//for (int i = 0; i < hex.length(); i += 2) {
// buf.append(hex.charAt(i));
// buf.append(hex.charAt(i+1));
// if (i < hex.length() - 2)
// buf.append(':');
//}
//info("hex is: " + buf);
if (_blacklist.contains(new SHA1Hash(h))) {
ks.deleteEntry(alias);
count++;
if (!_blacklistLogged) {
// should this be a logAlways?
X509Certificate xc = (X509Certificate) c;
BigInteger serial = xc.getSerialNumber();
String cn = CertUtil.getIssuerValue(xc, "CN");
String ou = CertUtil.getIssuerValue(xc, "OU");
warn("Ignoring blacklisted certificate \"" + alias +
"\" CN: \"" + cn +
"\" OU: \"" + ou +
"\" s/n: " + serial.toString(16), null);
}
}
} else {
info("null encoding!!!");
}
}
}
}
} catch (GeneralSecurityException e) {}
if (count > 0)
_blacklistLogged = true;
return count;
}
@@ -179,6 +348,8 @@ public class KeyStoreUtil {
* Load all X509 Certs from a directory and add them to the
* trusted set of certificates in the key store
*
* This DOES check for revocation.
*
* @return number successfully added
* @since 0.8.2, moved from SSLEepGet in 0.9.9
*/
@@ -188,6 +359,7 @@ public class KeyStoreUtil {
if (dir.exists() && dir.isDirectory()) {
File[] files = dir.listFiles();
if (files != null) {
CertStore cs = CertUtil.loadCRLs();
for (int i = 0; i < files.length; i++) {
File f = files[i];
if (!f.isFile())
@@ -198,9 +370,10 @@ public class KeyStoreUtil {
String alias = f.getName().toLowerCase(Locale.US);
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.endsWith(".p7c") || alias.endsWith(".pfx") || alias.endsWith(".p12") ||
alias.endsWith(".cer"))
alias = alias.substring(0, alias.length() - 4);
boolean success = addCert(f, alias, ks);
boolean success = addCert(f, alias, ks, cs);
if (success)
added++;
}
@@ -213,43 +386,56 @@ public class KeyStoreUtil {
* Load an X509 Cert from a file and add it to the
* trusted set of certificates in the key store
*
* This does NOT check for revocation.
*
* @return success
* @since 0.8.2, moved from SSLEepGet in 0.9.9
*/
public static boolean addCert(File file, String alias, KeyStore ks) {
InputStream fis = null;
return addCert(file, alias, ks, null);
}
/**
* Load an X509 Cert from a file and add it to the
* trusted set of certificates in the key store
*
* This DOES check for revocation, IF cs is non-null.
*
* @param cs may be null; if non-null, check for revocation
* @return success
* @since 0.9.25
*/
public static boolean addCert(File file, String alias, KeyStore ks, CertStore cs) {
try {
fis = new FileInputStream(file);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(fis);
X509Certificate cert = CertUtil.loadCert(file);
info("Read X509 Certificate from " + file.getAbsolutePath() +
" Issuer: " + cert.getIssuerX500Principal() +
" Serial: " + cert.getSerialNumber().toString(16) +
"; Valid From: " + cert.getNotBefore() +
" To: " + cert.getNotAfter());
try {
cert.checkValidity();
} catch (CertificateExpiredException cee) {
String s = "Rejecting expired X509 Certificate: " + file.getAbsolutePath();
// Android often has old system certs
if (SystemVersion.isAndroid())
warn(s, cee);
else
error(s, cee);
return false;
} catch (CertificateNotYetValidException cnyve) {
error("Rejecting X509 Certificate not yet valid: " + file.getAbsolutePath(), cnyve);
if (cs != null && CertUtil.isRevoked(cs, cert)) {
error("Certificate is revoked: " + file, new Exception());
return false;
}
ks.setCertificateEntry(alias, cert);
info("Now trusting X509 Certificate, Issuer: " + cert.getIssuerX500Principal());
} catch (CertificateExpiredException cee) {
String s = "Rejecting expired X509 Certificate: " + file.getAbsolutePath();
// Android often has old system certs
if (SystemVersion.isAndroid())
warn(s, cee);
else
error(s, cee);
return false;
} catch (CertificateNotYetValidException cnyve) {
error("Rejecting X509 Certificate not yet valid: " + file.getAbsolutePath(), cnyve);
return false;
} catch (GeneralSecurityException gse) {
error("Error reading X509 Certificate: " + file.getAbsolutePath(), gse);
return false;
} catch (IOException ioe) {
error("Error reading X509 Certificate: " + file.getAbsolutePath(), ioe);
return false;
} finally {
try { if (fis != null) fis.close(); } catch (IOException foo) {}
}
return true;
}
@@ -287,6 +473,9 @@ public class KeyStoreUtil {
/**
* Create a keypair and store it in the keystore at ks, creating it if necessary.
*
* For new code, the createKeys() with the SigType argument is recommended over this one,
* as it throws exceptions, and returns the certificate and CRL.
*
* Warning, may take a long time.
*
* @param ks path to the keystore
@@ -304,13 +493,145 @@ public class KeyStoreUtil {
*/
public static boolean createKeys(File ks, String ksPW, String alias, String cname, String ou,
int validDays, String keyAlg, int keySize, String keyPW) {
boolean useKeytool = I2PAppContext.getGlobalContext().getBooleanProperty("crypto.useExternalKeytool");
if (useKeytool) {
return createKeysCLI(ks, ksPW, alias, cname, ou, validDays, keyAlg, keySize, keyPW);
} else {
try {
createKeysAndCRL(ks, ksPW, alias, cname, ou, validDays, keyAlg, keySize, keyPW);
return true;
} catch (GeneralSecurityException gse) {
error("Create keys error", gse);
return false;
} catch (IOException ioe) {
error("Create keys error", ioe);
return false;
}
}
}
/**
* New way - Native Java, does not call out to keytool.
* Create a keypair and store it in the keystore at ks, creating it if necessary.
*
* This returns the public key, private key, certificate, and CRL in an array.
* All of these are Java classes. Keys may be converted to I2P classes with SigUtil.
* The private key and selfsigned cert are stored in the keystore.
* The public key may be derived from the private key with KeyGenerator.getSigningPublicKey().
* The public key certificate may be stored separately with
* CertUtil.saveCert() if desired.
* The CRL is not stored by this method, store it with
* CertUtil.saveCRL() or CertUtil.exportCRL() if desired.
*
* Throws on all errors.
* Warning, may take a long time.
*
* @param ks path to the keystore
* @param ksPW the keystore password
* @param alias the name of the key
* @param cname e.g. randomstuff.console.i2p.net
* @param ou e.g. console
* @param validDays e.g. 3652 (10 years)
* @param keyAlg e.g. DSA , RSA, EC
* @param keySize e.g. 1024
* @param keyPW the key password, must be at least 6 characters
* @return all you need:
* rv[0] is a Java PublicKey
* rv[1] is a Java PrivateKey
* rv[2] is a Java X509Certificate
* rv[3] is a Java X509CRL
* @since 0.9.25
*/
public static Object[] createKeysAndCRL(File ks, String ksPW, String alias, String cname, String ou,
int validDays, String keyAlg, int keySize, String keyPW)
throws GeneralSecurityException, IOException {
String algoName = getSigAlg(keySize, keyAlg);
SigType type = null;
for (SigType t : EnumSet.allOf(SigType.class)) {
if (t.getAlgorithmName().equals(algoName)) {
type = t;
break;
}
}
if (type == null)
throw new GeneralSecurityException("Unsupported algorithm/size: " + keyAlg + '/' + keySize);
return createKeysAndCRL(ks, ksPW, alias, cname, ou, validDays, type, keyPW);
}
/**
* New way - Native Java, does not call out to keytool.
* Create a keypair and store it in the keystore at ks, creating it if necessary.
*
* This returns the public key, private key, certificate, and CRL in an array.
* All of these are Java classes. Keys may be converted to I2P classes with SigUtil.
* The private key and selfsigned cert are stored in the keystore.
* The public key may be derived from the private key with KeyGenerator.getSigningPublicKey().
* The public key certificate may be stored separately with
* CertUtil.saveCert() if desired.
* The CRL is not stored by this method, store it with
* CertUtil.saveCRL() or CertUtil.exportCRL() if desired.
*
* Throws on all errors.
* Warning, may take a long time.
*
* @param ks path to the keystore
* @param ksPW the keystore password
* @param alias the name of the key
* @param cname e.g. randomstuff.console.i2p.net
* @param ou e.g. console
* @param validDays e.g. 3652 (10 years)
* @param keyPW the key password, must be at least 6 characters
* @return all you need:
* rv[0] is a Java PublicKey
* rv[1] is a Java PrivateKey
* rv[2] is a Java X509Certificate
* rv[3] is a Java X509CRL
* @since 0.9.25
*/
public static Object[] createKeysAndCRL(File ks, String ksPW, String alias, String cname, String ou,
int validDays, SigType type, String keyPW)
throws GeneralSecurityException, IOException {
Object[] rv = SelfSignedGenerator.generate(cname, ou, "XX", "I2P Anonymous Network", "XX", "XX", validDays, type);
PublicKey jpub = (PublicKey) rv[0];
PrivateKey jpriv = (PrivateKey) rv[1];
X509Certificate cert = (X509Certificate) rv[2];
X509CRL crl = (X509CRL) rv[3];
List<X509Certificate> certs = Collections.singletonList(cert);
storePrivateKey(ks, ksPW, alias, keyPW, jpriv, certs);
return rv;
}
/**
* OLD way - keytool
* Create a keypair and store it in the keystore at ks, creating it if necessary.
*
* Warning, may take a long time.
*
* @param ks path to the keystore
* @param ksPW the keystore password
* @param alias the name of the key
* @param cname e.g. randomstuff.console.i2p.net
* @param ou e.g. console
* @param validDays e.g. 3652 (10 years)
* @param keyAlg e.g. DSA , RSA, EC
* @param keySize e.g. 1024
* @param keyPW the key password, must be at least 6 characters
*
* @return success
* @since 0.8.3, consolidated from RouterConsoleRunner and SSLClientListenerRunner in 0.9.9
*/
private static boolean createKeysCLI(File ks, String ksPW, String alias, String cname, String ou,
int validDays, String keyAlg, int keySize, String keyPW) {
if (ks.exists()) {
try {
if (getCert(ks, ksPW, alias) != null) {
error("Not overwriting key " + alias + ", already exists in " + ks, null);
return false;
}
} catch (Exception e) {
} catch (IOException e) {
error("Not overwriting key \"" + alias + "\", already exists in " + ks, e);
return false;
} catch (GeneralSecurityException e) {
error("Not overwriting key \"" + alias + "\", already exists in " + ks, e);
return false;
}
@@ -325,20 +646,29 @@ public class KeyStoreUtil {
}
}
String keytool = (new File(System.getProperty("java.home"), "bin/keytool")).getAbsolutePath();
String[] args = new String[] {
keytool,
"-genkey", // -genkeypair preferred in newer keytools, but this works with more
"-storetype", KeyStore.getDefaultType(),
"-keystore", ks.getAbsolutePath(),
"-storepass", ksPW,
"-alias", alias,
"-dname", "CN=" + cname + ",OU=" + ou + ",O=I2P Anonymous Network,L=XX,ST=XX,C=XX",
"-validity", Integer.toString(validDays), // 10 years
"-keyalg", keyAlg,
"-sigalg", getSigAlg(keySize, keyAlg),
"-keysize", Integer.toString(keySize),
"-keypass", keyPW
};
List<String> a = new ArrayList<String>(32);
a.add(keytool);
a.add("-genkey"); // -genkeypair preferred in newer keytools, but this works with more
//a.add("-v"); // verbose, gives you a stack trace on exception
a.add("-storetype"); a.add(KeyStore.getDefaultType());
a.add("-keystore"); a.add(ks.getAbsolutePath());
a.add("-storepass"); a.add(ksPW);
a.add("-alias"); a.add(alias);
a.add("-dname"); a.add("CN=" + cname + ",OU=" + ou + ",O=I2P Anonymous Network,L=XX,ST=XX,C=XX");
a.add("-validity"); a.add(Integer.toString(validDays)); // 10 years
a.add("-keyalg"); a.add(keyAlg);
a.add("-sigalg"); a.add(getSigAlg(keySize, keyAlg));
a.add("-keysize"); a.add(Integer.toString(keySize));
a.add("-keypass"); a.add(keyPW);
if (keyAlg.equals("Ed") || keyAlg.equals("EdDSA") || keyAlg.equals("ElGamal")) {
File f = I2PAppContext.getGlobalContext().getBaseDir();
f = new File(f, "lib");
f = new File(f, "i2p.jar");
// providerpath is not in the man page; see keytool -genkey -help
a.add("-providerpath"); a.add(f.getAbsolutePath());
a.add("-providerclass"); a.add("net.i2p.crypto.provider.I2PProvider");
}
String[] args = a.toArray(new String[a.size()]);
// TODO pipe key password to process; requires ShellCommand enhancements
boolean success = (new ShellCommand()).executeSilentAndWaitTimed(args, 240);
if (success) {
@@ -348,7 +678,10 @@ public class KeyStoreUtil {
success = getPrivateKey(ks, ksPW, alias, keyPW) != null;
if (!success)
error("Key gen failed to get private key", null);
} catch (Exception e) {
} catch (IOException e) {
error("Key gen failed to get private key", e);
success = false;
} catch (GeneralSecurityException e) {
error("Key gen failed to get private key", e);
success = false;
}
@@ -372,6 +705,8 @@ public class KeyStoreUtil {
private static String getSigAlg(int size, String keyalg) {
if (keyalg.equals("EC"))
keyalg = "ECDSA";
else if (keyalg.equals("Ed"))
keyalg = "EdDSA";
String hash;
if (keyalg.equals("ECDSA")) {
if (size <= 256)
@@ -380,6 +715,8 @@ public class KeyStoreUtil {
hash = "SHA384";
else
hash = "SHA512";
} else if (keyalg.equals("EdDSA")) {
hash = "SHA512";
} else {
if (size <= 1024)
hash = "SHA1";
@@ -417,6 +754,103 @@ public class KeyStoreUtil {
}
}
/**
* Export the private key and certificate chain (if any) out of a keystore.
* Does NOT close the stream. Throws on all errors.
*
* @param ks path to the keystore
* @param ksPW the keystore password, may be null
* @param alias the name of the key
* @param keyPW the key password, must be at least 6 characters
* @since 0.9.25
*/
public static void exportPrivateKey(File ks, String ksPW, String alias, String keyPW,
OutputStream out)
throws GeneralSecurityException, IOException {
InputStream fis = null;
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
fis = new FileInputStream(ks);
char[] pwchars = ksPW != null ? ksPW.toCharArray() : null;
keyStore.load(fis, pwchars);
char[] keypwchars = keyPW.toCharArray();
PrivateKey pk = (PrivateKey) keyStore.getKey(alias, keypwchars);
if (pk == null)
throw new GeneralSecurityException("private key not found: " + alias);
Certificate[] certs = keyStore.getCertificateChain(alias);
CertUtil.exportPrivateKey(pk, certs, out);
} finally {
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
}
}
/**
* Import the private key and certificate chain to a keystore.
* Keystore will be created if it does not exist.
* Private key MUST be first in the stream.
* Closes the stream. Throws on all errors.
*
* @param ks path to the keystore
* @param ksPW the keystore password, may be null
* @param alias the name of the key. If null, will be taken from the Subject CN
* of the first certificate in the chain.
* @param keyPW the key password, must be at least 6 characters
* @return the alias as specified or extracted
* @since 0.9.25
*/
public static String importPrivateKey(File ks, String ksPW, String alias, String keyPW,
InputStream in)
throws GeneralSecurityException, IOException {
OutputStream fos = null;
try {
KeyStore keyStore = createKeyStore(ks, ksPW);
PrivateKey pk = CertUtil.loadPrivateKey(in);
List<X509Certificate> certs = CertUtil.loadCerts(in);
if (alias == null) {
alias = CertUtil.getSubjectValue(certs.get(0), "CN");
if (alias == null)
throw new GeneralSecurityException("no alias specified and no Subject CN in cert");
if (alias.endsWith(".family.i2p.net") && alias.length() > ".family.i2p.net".length())
alias = alias.substring(0, ".family.i2p.net".length());
}
keyStore.setKeyEntry(alias, pk, keyPW.toCharArray(), certs.toArray(new Certificate[certs.size()]));
char[] pwchars = ksPW != null ? ksPW.toCharArray() : null;
fos = new SecureFileOutputStream(ks);
keyStore.store(fos, pwchars);
return alias;
} finally {
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
try { in.close(); } catch (IOException ioe) {}
}
}
/**
* Import the private key and certificate chain to a keystore.
* Keystore will be created if it does not exist.
* Private key MUST be first in the stream.
* Closes the stream. Throws on all errors.
*
* @param ks path to the keystore
* @param ksPW the keystore password, may be null
* @param alias the name of the key, non-null.
* @param keyPW the key password, must be at least 6 characters
* @since 0.9.25
*/
public static void storePrivateKey(File ks, String ksPW, String alias, String keyPW,
PrivateKey pk, List<X509Certificate> certs)
throws GeneralSecurityException, IOException {
OutputStream fos = null;
try {
KeyStore keyStore = createKeyStore(ks, ksPW);
keyStore.setKeyEntry(alias, pk, keyPW.toCharArray(), certs.toArray(new Certificate[certs.size()]));
char[] pwchars = ksPW != null ? ksPW.toCharArray() : null;
fos = new SecureFileOutputStream(ks);
keyStore.store(fos, pwchars);
} finally {
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
}
}
/**
* Get a cert out of a keystore
*
@@ -495,10 +929,36 @@ public class KeyStoreUtil {
l.log(level, msg, t);
}
/**
* Usage: KeyStoreUtil (loads from system keystore)
* KeyStoreUtil foo.ks (loads from system keystore, and from foo.ks keystore if exists, else creates empty)
* KeyStoreUtil certDir (loads from system keystore and all certs in certDir if exists)
* KeyStoreUtil import file.ks file.key alias keypw (imxports private key from file to keystore)
* KeyStoreUtil export file.ks alias keypw (exports private key from keystore)
* KeyStoreUtil keygen file.ks alias keypw (create keypair in keystore)
* KeyStoreUtil keygen2 file.ks alias keypw (create keypair using I2PProvider)
*/
/****
public static void main(String[] args) {
try {
if (args.length > 0) {
File ksf = new File(args[0]);
if (args.length > 0 && "import".equals(args[0])) {
testImport(args);
return;
}
if (args.length > 0 && "export".equals(args[0])) {
testExport(args);
return;
}
if (args.length > 0 && "keygen".equals(args[0])) {
testKeygen(args);
return;
}
if (args.length > 0 && "keygen2".equals(args[0])) {
testKeygen2(args);
return;
}
File ksf = (args.length > 0) ? new File(args[0]) : null;
if (ksf != null && !ksf.exists()) {
createKeyStore(ksf, DEFAULT_KEYSTORE_PASSWORD);
System.out.println("Created empty keystore " + ksf);
} else {
@@ -507,6 +967,17 @@ public class KeyStoreUtil {
System.out.println("Loaded system keystore");
int count = countCerts(ks);
System.out.println("Found " + count + " certs");
if (ksf != null && ksf.isDirectory()) {
count = addCerts(ksf, ks);
System.out.println("Found " + count + " certs in " + ksf);
if (count > 0) {
// rerun blacklist as a test
_blacklistLogged = false;
count = removeBlacklistedCerts(ks);
if (count > 0)
System.out.println("Found " + count + " blacklisted certs in " + ksf);
}
}
} else {
System.out.println("FAIL");
}
@@ -515,4 +986,63 @@ public class KeyStoreUtil {
e.printStackTrace();
}
}
private static void testImport(String[] args) throws Exception {
File ksf = new File(args[1]);
InputStream in = new FileInputStream(args[2]);
String alias = args[3];
String pw = args[4];
importPrivateKey(ksf, DEFAULT_KEYSTORE_PASSWORD, alias, pw, in);
}
private static void testExport(String[] args) throws Exception {
File ksf = new File(args[1]);
String alias = args[2];
String pw = args[3];
exportPrivateKey(ksf, DEFAULT_KEYSTORE_PASSWORD, alias, pw, System.out);
}
private static void testKeygen(String[] args) throws Exception {
File ksf = new File(args[1]);
String alias = args[2];
String pw = args[3];
boolean ok = createKeys(ksf, DEFAULT_KEYSTORE_PASSWORD, alias, "test cname", "test ou",
//DEFAULT_KEY_VALID_DAYS, "EdDSA", 256, pw);
DEFAULT_KEY_VALID_DAYS, "ElGamal", 2048, pw);
System.out.println("genkey ok? " + ok);
}
private static void testKeygen2(String[] args) throws Exception {
// keygen test using the I2PProvider
SigType type = SigType.EdDSA_SHA512_Ed25519;
//SigType type = SigType.ElGamal_SHA256_MODP2048;
java.security.KeyPairGenerator kpg = java.security.KeyPairGenerator.getInstance(type.getBaseAlgorithm().getName());
kpg.initialize(type.getParams());
java.security.KeyPair kp = kpg.generateKeyPair();
java.security.PublicKey jpub = kp.getPublic();
java.security.PrivateKey jpriv = kp.getPrivate();
System.out.println("Encoded private key:");
System.out.println(net.i2p.util.HexDump.dump(jpriv.getEncoded()));
System.out.println("Encoded public key:");
System.out.println(net.i2p.util.HexDump.dump(jpub.getEncoded()));
java.security.Signature jsig = java.security.Signature.getInstance(type.getAlgorithmName());
jsig.initSign(jpriv);
byte[] data = new byte[111];
net.i2p.util.RandomSource.getInstance().nextBytes(data);
jsig.update(data);
byte[] bsig = jsig.sign();
System.out.println("Encoded signature:");
System.out.println(net.i2p.util.HexDump.dump(bsig));
jsig.initVerify(jpub);
jsig.update(data);
boolean ok = jsig.verify(bsig);
System.out.println("verify passed? " + ok);
net.i2p.data.Signature sig = SigUtil.fromJavaSig(bsig, type);
System.out.println("Signature test: " + sig);
}
****/
}

View File

@@ -10,7 +10,7 @@ import net.i2p.util.NativeBigInteger;
*
* @since 0.9.9
*/
class RSAConstants {
final class RSAConstants {
/**
* Generate a spec

View File

@@ -1,7 +1,6 @@
package net.i2p.crypto;
import gnu.crypto.hash.Sha256Standalone;
import java.security.DigestException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.LinkedBlockingQueue;
@@ -13,24 +12,13 @@ import net.i2p.data.Hash;
* Defines a wrapper for SHA-256 operation.
*
* As of release 0.8.7, uses java.security.MessageDigest by default.
* If that is unavailable, it uses
* As of release 0.9.25, uses only MessageDigest.
* GNU-Crypto {@link gnu.crypto.hash.Sha256Standalone}
* is deprecated.
*/
public final class SHA256Generator {
private final LinkedBlockingQueue<MessageDigest> _digests;
private static final boolean _useGnu;
static {
boolean useGnu = false;
try {
MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
useGnu = true;
System.out.println("INFO: Using GNU SHA-256");
}
_useGnu = useGnu;
}
/**
* @param context unused
*/
@@ -53,13 +41,13 @@ public final class SHA256Generator {
/**
* Calculate the hash and cache the result.
* @param source what to hash
*/
public final Hash calculateHash(byte[] source, int start, int len) {
MessageDigest digest = acquire();
digest.update(source, start, len);
byte rv[] = digest.digest();
release(digest);
//return new Hash(rv);
return Hash.create(rv);
}
@@ -71,9 +59,13 @@ public final class SHA256Generator {
public final void calculateHash(byte[] source, int start, int len, byte out[], int outOffset) {
MessageDigest digest = acquire();
digest.update(source, start, len);
byte rv[] = digest.digest();
release(digest);
System.arraycopy(rv, 0, out, outOffset, rv.length);
try {
digest.digest(out, outOffset, Hash.HASH_LENGTH);
} catch (DigestException e) {
throw new RuntimeException(e);
} finally {
release(digest);
}
}
private MessageDigest acquire() {
@@ -90,45 +82,14 @@ public final class SHA256Generator {
}
/**
* Return a new MessageDigest from the system libs unless unavailable
* in this JVM, in that case return a wrapped GNU Sha256Standalone
* Return a new MessageDigest from the system libs.
* @since 0.8.7, public since 0.8.8 for FortunaStandalone
*/
public static MessageDigest getDigestInstance() {
if (!_useGnu) {
try {
return MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {}
}
return new GnuMessageDigest();
}
/**
* Wrapper to make Sha256Standalone a MessageDigest
* @since 0.8.7
*/
private static class GnuMessageDigest extends MessageDigest {
private final Sha256Standalone _gnu;
protected GnuMessageDigest() {
super("SHA-256");
_gnu = new Sha256Standalone();
}
protected byte[] engineDigest() {
return _gnu.digest();
}
protected void engineReset() {
_gnu.reset();
}
protected void engineUpdate(byte input) {
_gnu.update(input);
}
protected void engineUpdate(byte[] input, int offset, int len) {
_gnu.update(input, offset, len);
try {
return MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}

View File

@@ -15,8 +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.security.cert.X509CRL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
@@ -35,6 +35,7 @@ import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Signature;
import net.i2p.data.SimpleDataStructure;
import net.i2p.util.SecureFileOutputStream;
/**
* Succesor to the ".sud" format used in TrustedUpdate.
@@ -50,6 +51,7 @@ public class SU3File {
private String _version;
private int _versionLength;
private String _signer;
private int _signatureLength;
private int _signerLength;
private int _fileType = -1;
private ContentType _contentType;
@@ -125,7 +127,7 @@ public class SU3File {
private static final ContentType DEFAULT_CONTENT_TYPE = ContentType.UNKNOWN;
// avoid early ctx init
//private static final SigType DEFAULT_SIG_TYPE = SigType.DSA_SHA1;
private static final int DEFAULT_SIG_CODE = 0;
private static final int DEFAULT_SIG_CODE = 6;
/**
*
@@ -265,16 +267,16 @@ public class SU3File {
// In verifyAndMigrate it reads this far then rewinds, but we don't need to here
if (_sigType == null)
throw new IOException("unknown sig type: " + sigTypeCode);
_signerLength = (int) DataHelper.readLong(in, 2);
if (_signerLength != _sigType.getSigLen())
_signatureLength = (int) DataHelper.readLong(in, 2);
if (_signatureLength != _sigType.getSigLen())
throw new IOException("bad sig length");
skip(in, 1);
int _versionLength = in.read();
if (_versionLength < MIN_VERSION_BYTES)
throw new IOException("bad version length");
skip(in, 1);
int signerLen = in.read();
if (signerLen <= 0)
_signerLength = in.read();
if (_signerLength <= 0)
throw new IOException("bad signer length");
_contentLength = DataHelper.readLong(in, 8);
if (_contentLength <= 0)
@@ -302,9 +304,9 @@ public class SU3File {
}
_version = new String(data, 0, zbyte, "UTF-8");
data = new byte[signerLen];
data = new byte[_signerLength];
bytesRead = DataHelper.read(in, data);
if (bytesRead != signerLen)
if (bytesRead != _signerLength)
throw new EOFException();
_signer = DataHelper.getUTF8(data);
@@ -413,6 +415,9 @@ public class SU3File {
din.on(false);
Signature signature = new Signature(_sigType);
signature.readBytes(in);
int avail = in.available();
if (avail > 0)
throw new IOException(avail + " bytes data after sig");
SimpleDataStructure hash = _sigType.getHashInstance();
hash.setData(sha);
//System.out.println("hash\n" + HexDump.dump(sha));
@@ -536,9 +541,11 @@ public class SU3File {
String ctype = null;
String ftype = null;
String kfile = null;
String crlfile = null;
String kspass = KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD;
boolean error = false;
boolean shouldVerify = true;
Getopt g = new Getopt("SU3File", args, "t:c:f:k:x");
Getopt g = new Getopt("SU3File", args, "t:c:f:k:xp:r:");
int c;
while ((c = g.getopt()) != -1) {
switch (c) {
@@ -558,10 +565,18 @@ public class SU3File {
kfile = g.getOptarg();
break;
case 'r':
crlfile = g.getOptarg();
break;
case 'x':
shouldVerify = false;
break;
case 'p':
kspass = g.getOptarg();
break;
case '?':
case ':':
default:
@@ -582,18 +597,21 @@ public class SU3File {
Properties props = new Properties();
props.setProperty("prng.bufferSize", "16384");
new I2PAppContext(props);
ok = signCLI(stype, ctype, ftype, 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), "", kspass);
} 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));
ok = bulkSignCLI(stype, ctype, a.get(0), a.get(1), a.get(2), a.get(3), kspass);
} else if ("verifysig".equals(cmd)) {
ok = verifySigCLI(a.get(0), kfile);
} else if ("keygen".equals(cmd)) {
ok = genKeysCLI(stype, a.get(0), a.get(1), a.get(2));
Properties props = new Properties();
props.setProperty("prng.bufferSize", "16384");
new I2PAppContext(props);
ok = genKeysCLI(stype, a.get(0), a.get(1), crlfile, a.get(2), kspass);
} else if ("extract".equals(cmd)) {
ok = extractCLI(a.get(0), a.get(1), shouldVerify);
ok = extractCLI(a.get(0), a.get(1), shouldVerify, kfile);
} else {
showUsageCLI();
}
@@ -607,12 +625,14 @@ 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 [-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 [-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("Usage: SU3File keygen [-t type|code] [-p keystorepw] [-r crlFile.crl] publicKeyFile.crt keystore.ks you@mail.i2p\n" +
" SU3File sign [-t type|code] [-c type|code] [-f type|code] [-p keystorepw] inputFile.zip signedFile.su3 keystore.ks version you@mail.i2p\n" +
" SU3File bulksign [-t type|code] [-c type|code] [-p keystorepw] directory keystore.ks version you@mail.i2p\n" +
" (signs all .zip, .xml, and .xml.gz files in the directory)\n" +
" SU3File showversion signedFile.su3\n" +
" SU3File verifysig [-k file.crt] signedFile.su3 ## -k use this pubkey cert for verification\n" +
" SU3File extract [-x] [-k file.crt] signedFile.su3 outFile ## -x don't check sig");
System.err.println("Default keystore password: \"" + KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD + '"');
System.err.println(dumpTypes());
}
@@ -705,12 +725,12 @@ public class SU3File {
}
/**
* Zip only
* Zip, xml, and xml.gz only
* @return success
* @since 0.9.9
*/
private static final boolean bulkSignCLI(String stype, String ctype, String dir,
String privateKeyFile, String version, String signerName) {
String privateKeyFile, String version, String signerName, String kspass) {
File d = new File(dir);
if (!d.isDirectory()) {
System.out.println("Directory does not exist: " + d);
@@ -742,10 +762,23 @@ public class SU3File {
int success = 0;
for (File in : files) {
String inputFile = in.getPath();
if (!inputFile.endsWith(".zip"))
int len;
String ftype;
if (inputFile.endsWith(".zip")) {
len = 4;
ftype = "ZIP";
} else if (inputFile.endsWith(".xml")) {
len = 4;
ftype = "XML";
} else if (inputFile.endsWith(".xml.gz")) {
len = 7;
ftype = "XML_GZ";
} else {
continue;
String signedFile = inputFile.substring(0, inputFile.length() - 4) + ".su3";
boolean rv = signCLI(stype, ctype, null, inputFile, signedFile, privateKeyFile, version, signerName, keypw);
}
String signedFile = inputFile.substring(0, inputFile.length() - len) + ".su3";
boolean rv = signCLI(stype, ctype, ftype, inputFile, signedFile,
privateKeyFile, version, signerName, keypw, kspass);
if (!rv)
return false;
success++;
@@ -760,7 +793,7 @@ public class SU3File {
* @since 0.9.9
*/
private static final boolean signCLI(String stype, String ctype, String ftype, String inputFile, String signedFile,
String privateKeyFile, String version, String signerName, String keypw) {
String privateKeyFile, String version, String signerName, String keypw, String kspass) {
SigType type = stype == null ? SigType.getByCode(Integer.valueOf(DEFAULT_SIG_CODE)) : SigType.parseSigType(stype);
if (type == null) {
System.out.println("Signature type " + stype + " is not supported");
@@ -795,7 +828,7 @@ public class SU3File {
System.out.println("Warning: File type " + ftype + " is undefined");
}
}
return signCLI(type, ct, ft, inputFile, signedFile, privateKeyFile, version, signerName, keypw);
return signCLI(type, ct, ft, inputFile, signedFile, privateKeyFile, version, signerName, keypw, kspass);
}
/**
@@ -803,7 +836,7 @@ public class SU3File {
* @since 0.9.9
*/
private static final boolean signCLI(SigType type, ContentType ctype, int ftype, String inputFile, String signedFile,
String privateKeyFile, String version, String signerName, String keypw) {
String privateKeyFile, String version, String signerName, String keypw, String kspass) {
try {
while (keypw.length() < 6) {
System.out.print("Enter password for key \"" + signerName + "\": ");
@@ -817,11 +850,16 @@ public class SU3File {
System.out.println("Key password must be at least 6 characters");
}
File pkfile = new File(privateKeyFile);
PrivateKey pk = KeyStoreUtil.getPrivateKey(pkfile,KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD, signerName, keypw);
PrivateKey pk = KeyStoreUtil.getPrivateKey(pkfile, kspass, signerName, keypw);
if (pk == null) {
System.out.println("Private key for " + signerName + " not found in keystore " + privateKeyFile);
return false;
}
// now fix the sig type based on the private key
SigType oldType = type;
type = SigUtil.fromJavaKey(pk).getType();
if (oldType != type)
System.out.println("Warning: Using private key type " + type + ", ignoring specified type " + oldType);
SU3File file = new SU3File(signedFile);
file.write(new File(inputFile), ftype, ctype.getCode(), version, signerName, pk, type);
System.out.println("Input file '" + inputFile + "' signed and written to '" + signedFile + "'");
@@ -861,10 +899,12 @@ public class SU3File {
* @return success
* @since 0.9.9
*/
private static final boolean extractCLI(String signedFile, String outFile, boolean verifySig) {
private static final boolean extractCLI(String signedFile, String outFile, boolean verifySig, String pkFile) {
InputStream in = null;
try {
SU3File file = new SU3File(signedFile);
if (pkFile != null)
file.setPublicKeyCertificate(new File(pkFile));
file.setVerifySignature(verifySig);
File out = new File(outFile);
boolean ok = file.verifyAndMigrate(out);
@@ -881,24 +921,29 @@ public class SU3File {
}
/**
* @param crlFile may be null; non-null to save
* @return success
* @since 0.9.9
*/
private static final boolean genKeysCLI(String stype, String publicKeyFile, String privateKeyFile, String alias) {
private static final boolean genKeysCLI(String stype, String publicKeyFile, String privateKeyFile,
String crlFile, String alias, String kspass) {
SigType type = stype == null ? SigType.getByCode(Integer.valueOf(DEFAULT_SIG_CODE)) : SigType.parseSigType(stype);
if (type == null) {
System.out.println("Signature type " + stype + " is not supported");
return false;
}
return genKeysCLI(type, publicKeyFile, privateKeyFile, alias);
return genKeysCLI(type, publicKeyFile, privateKeyFile, crlFile, alias, kspass);
}
/**
* Writes Java-encoded keys (X.509 for public and PKCS#8 for private)
*
* @param crlFile may be null; non-null to save
* @return success
* @since 0.9.9
*/
private static final boolean genKeysCLI(SigType type, String publicKeyFile, String privateKeyFile, String alias) {
private static final boolean genKeysCLI(SigType type, String publicKeyFile, String privateKeyFile,
String crlFile, String alias, String kspass) {
File pubFile = new File(publicKeyFile);
if (pubFile.exists()) {
System.out.println("Error: Not overwriting file " + publicKeyFile);
@@ -930,24 +975,29 @@ public class SU3File {
} catch (IOException ioe) {
return false;
}
int keylen = type.getPubkeyLen() * 8;
if (type.getBaseAlgorithm() == SigAlgo.EC) {
keylen /= 2;
if (keylen == 528)
keylen = 521;
}
boolean success = KeyStoreUtil.createKeys(ksFile, KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD, alias,
alias, "I2P", 3652, type.getBaseAlgorithm().getName(),
keylen, keypw);
if (!success) {
OutputStream out = null;
try {
Object[] rv = KeyStoreUtil.createKeysAndCRL(ksFile, kspass, alias,
alias, "I2P", 3652, type, keypw);
X509Certificate cert = (X509Certificate) rv[2];
out = new SecureFileOutputStream(publicKeyFile);
CertUtil.exportCert(cert, out);
if (crlFile != null) {
out.close();
X509CRL crl = (X509CRL) rv[3];
out = new SecureFileOutputStream(crlFile);
CertUtil.exportCRL(crl, out);
}
} catch (GeneralSecurityException gse) {
System.err.println("Error creating keys for " + alias);
gse.printStackTrace();
return false;
}
File outfile = new File(publicKeyFile);
success = KeyStoreUtil.exportCert(ksFile, KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD, alias, outfile);
if (!success) {
System.err.println("Error writing public key for " + alias + " to " + outfile);
} catch (IOException ioe) {
System.err.println("Error creating keys for " + alias);
ioe.printStackTrace();
return false;
} finally {
if (out != null) try { out.close(); } catch (IOException ioe) {}
}
return true;
}
@@ -958,19 +1008,12 @@ public class SU3File {
* @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();
return CertUtil.loadKey(kd);
} catch (GeneralSecurityException gse) {
IOException ioe = new IOException("cert error");
ioe.initCause(gse);
throw ioe;
} finally {
try { if (fis != null) fis.close(); } catch (IOException foo) {}
}
}
}

View File

@@ -0,0 +1,605 @@
package net.i2p.crypto;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
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.security.cert.X509CRL;
import java.security.spec.X509EncodedKeySpec;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DHParameterSpec;
import javax.crypto.spec.DHPublicKeySpec;
import javax.security.auth.x500.X500Principal;
import static net.i2p.crypto.SigUtil.intToASN1;
import net.i2p.data.DataHelper;
import net.i2p.data.Signature;
import net.i2p.data.SigningPrivateKey;
import net.i2p.data.SigningPublicKey;
import net.i2p.data.SimpleDataStructure;
import net.i2p.util.HexDump;
import net.i2p.util.RandomSource;
import net.i2p.util.SecureFileOutputStream;
import net.i2p.util.SystemVersion;
/**
* Generate keys and a selfsigned certificate, suitable for
* storing in a Keystore with KeyStoreUtil.storePrivateKey().
* All done programatically, no keytool, no BC libs, no sun classes.
* Ref: RFC 2459
*
* This is coded to create a cert that matches what comes out of keytool
* exactly, even if I don't understand all of it.
*
* @since 0.9.25
*/
public final class SelfSignedGenerator {
private static final boolean DEBUG = false;
private static final String OID_CN = "2.5.4.3";
private static final String OID_C = "2.5.4.6";
private static final String OID_L = "2.5.4.7";
private static final String OID_ST = "2.5.4.8";
private static final String OID_O = "2.5.4.10";
private static final String OID_OU = "2.5.4.11";
// Subject Key Identifier
private static final String OID_SKI = "2.5.29.14";
// CRL number
private static final String OID_CRLNUM = "2.5.29.20";
private static final Map<String, String> OIDS;
static {
OIDS = new HashMap<String, String>(16);
OIDS.put(OID_CN, "CN");
OIDS.put(OID_C, "C");
OIDS.put(OID_L, "L");
OIDS.put(OID_ST, "ST");
OIDS.put(OID_O, "O");
OIDS.put(OID_OU, "OU");
OIDS.put(OID_SKI, "SKI");
}
/**
* rv[0] is a Java PublicKey
* rv[1] is a Java PrivateKey
* rv[2] is a Java X509Certificate
* rv[3] is a Java X509CRL
*/
public static Object[] generate(String cname, String ou, String o, String l, String st, String c,
int validDays, SigType type) throws GeneralSecurityException {
SimpleDataStructure[] keys = KeyGenerator.getInstance().generateSigningKeys(type);
SigningPublicKey pub = (SigningPublicKey) keys[0];
SigningPrivateKey priv = (SigningPrivateKey) keys[1];
PublicKey jpub = SigUtil.toJavaKey(pub);
PrivateKey jpriv = SigUtil.toJavaKey(priv);
String oid;
switch (type) {
case DSA_SHA1:
case ECDSA_SHA256_P256:
case ECDSA_SHA384_P384:
case ECDSA_SHA512_P521:
case RSA_SHA256_2048:
case RSA_SHA384_3072:
case RSA_SHA512_4096:
case EdDSA_SHA512_Ed25519:
case EdDSA_SHA512_Ed25519ph:
oid = type.getOID();
break;
default:
throw new GeneralSecurityException("Unsupported: " + type);
}
byte[] sigoid = getEncodedOIDSeq(oid);
byte[] tbs = genTBS(cname, ou, o, l, st, c, validDays, sigoid, jpub);
int tbslen = tbs.length;
Signature sig = DSAEngine.getInstance().sign(tbs, priv);
if (sig == null)
throw new GeneralSecurityException("sig failed");
byte[] sigbytes= SigUtil.toJavaSig(sig);
int seqlen = tbslen + sigoid.length + spaceFor(sigbytes.length + 1);
int totlen = spaceFor(seqlen);
byte[] cb = new byte[totlen];
int idx = 0;
// construct the whole encoded cert
cb[idx++] = 0x30;
idx = intToASN1(cb, idx, seqlen);
// TBS cert
System.arraycopy(tbs, 0, cb, idx, tbs.length);
idx += tbs.length;
// sig algo
System.arraycopy(sigoid, 0, cb, idx, sigoid.length);
idx += sigoid.length;
// sig (bit string)
cb[idx++] = 0x03;
idx = intToASN1(cb, idx, sigbytes.length + 1);
cb[idx++] = 0;
System.arraycopy(sigbytes, 0, cb, idx, sigbytes.length);
if (DEBUG) {
System.out.println("Sig OID");
System.out.println(HexDump.dump(sigoid));
System.out.println("Signature");
System.out.println(HexDump.dump(sigbytes));
System.out.println("Whole cert");
System.out.println(HexDump.dump(cb));
}
ByteArrayInputStream bais = new ByteArrayInputStream(cb);
X509Certificate cert;
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
cert = (X509Certificate)cf.generateCertificate(bais);
cert.checkValidity();
} catch (IllegalArgumentException iae) {
throw new GeneralSecurityException("cert error", iae);
}
X509CRL crl = generateCRL(cert, validDays, 1, sigoid, jpriv);
// some simple tests
PublicKey cpub = cert.getPublicKey();
cert.verify(cpub);
if (!cpub.equals(jpub))
throw new GeneralSecurityException("pubkey mismatch");
// todo crl tests
Object[] rv = { jpub, jpriv, cert, crl };
return rv;
}
/**
* Generate a CRL for the given cert, signed with the given private key
*/
private static X509CRL generateCRL(X509Certificate cert, int validDays, int crlNum,
byte[] sigoid, PrivateKey jpriv) throws GeneralSecurityException {
SigningPrivateKey priv = SigUtil.fromJavaKey(jpriv);
byte[] tbs = genTBSCRL(cert, validDays, crlNum, sigoid);
int tbslen = tbs.length;
Signature sig = DSAEngine.getInstance().sign(tbs, priv);
if (sig == null)
throw new GeneralSecurityException("sig failed");
byte[] sigbytes= SigUtil.toJavaSig(sig);
int seqlen = tbslen + sigoid.length + spaceFor(sigbytes.length + 1);
int totlen = spaceFor(seqlen);
byte[] cb = new byte[totlen];
int idx = 0;
// construct the whole encoded cert
cb[idx++] = 0x30;
idx = intToASN1(cb, idx, seqlen);
// TBS cert
System.arraycopy(tbs, 0, cb, idx, tbs.length);
idx += tbs.length;
// sig algo
System.arraycopy(sigoid, 0, cb, idx, sigoid.length);
idx += sigoid.length;
// sig (bit string)
cb[idx++] = 0x03;
idx = intToASN1(cb, idx, sigbytes.length + 1);
cb[idx++] = 0;
System.arraycopy(sigbytes, 0, cb, idx, sigbytes.length);
/****
if (DEBUG) {
System.out.println("CRL Sig OID");
System.out.println(HexDump.dump(sigoid));
System.out.println("CRL Signature");
System.out.println(HexDump.dump(sigbytes));
System.out.println("Whole CRL");
System.out.println(HexDump.dump(cb));
}
****/
ByteArrayInputStream bais = new ByteArrayInputStream(cb);
X509CRL rv;
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// wow, unlike for x509Certificates, there's no validation here at all
// ASN.1 errors don't cause any exceptions
rv = (X509CRL)cf.generateCRL(bais);
} catch (IllegalArgumentException iae) {
throw new GeneralSecurityException("cert error", iae);
}
return rv;
}
private static byte[] genTBS(String cname, String ou, String o, String l, String st, String c,
int validDays, byte[] sigoid, PublicKey jpub) throws GeneralSecurityException {
// a0 ???, int = 2
byte[] version = { (byte) 0xa0, 3, 2, 1, 2 };
// postive serial number (int)
byte[] serial = new byte[6];
serial[0] = 2;
serial[1] = 4;
RandomSource.getInstance().nextBytes(serial, 2, 4);
serial[2] &= 0x7f;
// going to use this for both issuer and subject
String dname = "CN=" + cname + ",OU=" + ou + ",O=" + o + ",L=" + l + ",ST=" + st + ",C=" + c;
byte[] issuer = (new X500Principal(dname, OIDS)).getEncoded();
byte[] validity = getValidity(validDays);
byte[] subject = issuer;
byte[] pubbytes = jpub.getEncoded();
byte[] extbytes = getExtensions(pubbytes);
int len = version.length + serial.length + sigoid.length + issuer.length +
validity.length + subject.length + pubbytes.length + extbytes.length;
int totlen = spaceFor(len);
byte[] rv = new byte[totlen];
int idx = 0;
rv[idx++] = 0x30;
idx = intToASN1(rv, idx, len);
System.arraycopy(version, 0, rv, idx, version.length);
idx += version.length;
System.arraycopy(serial, 0, rv, idx, serial.length);
idx += serial.length;
System.arraycopy(sigoid, 0, rv, idx, sigoid.length);
idx += sigoid.length;
System.arraycopy(issuer, 0, rv, idx, issuer.length);
idx += issuer.length;
System.arraycopy(validity, 0, rv, idx, validity.length);
idx += validity.length;
System.arraycopy(subject, 0, rv, idx, subject.length);
idx += subject.length;
System.arraycopy(pubbytes, 0, rv, idx, pubbytes.length);
idx += pubbytes.length;
System.arraycopy(extbytes, 0, rv, idx, extbytes.length);
if (DEBUG) {
System.out.println(HexDump.dump(version));
System.out.println("serial");
System.out.println(HexDump.dump(serial));
System.out.println("oid");
System.out.println(HexDump.dump(sigoid));
System.out.println("issuer");
System.out.println(HexDump.dump(issuer));
System.out.println("valid");
System.out.println(HexDump.dump(validity));
System.out.println("subject");
System.out.println(HexDump.dump(subject));
System.out.println("pub");
System.out.println(HexDump.dump(pubbytes));
System.out.println("extensions");
System.out.println(HexDump.dump(extbytes));
System.out.println("TBS cert");
System.out.println(HexDump.dump(rv));
}
return rv;
}
/**
*
* @param crlNum 0-255 because lazy
* @return ASN.1 encoded object
*/
private static byte[] genTBSCRL(X509Certificate cert, int validDays,
int crlNum, byte[] sigalg) throws GeneralSecurityException {
// a0 ???, int = 2
byte[] version = { 2, 1, 1 };
byte[] issuer = cert.getIssuerX500Principal().getEncoded();
byte[] serial = cert.getSerialNumber().toByteArray();
if (serial.length > 255)
throw new IllegalArgumentException();
long now = System.currentTimeMillis();
long then = now + (validDays * 24L * 60 * 60 * 1000);
// used for CRL time and revocation time
byte[] nowbytes = getDate(now);
// used for next CRL time
byte[] thenbytes = getDate(then);
byte[] extbytes = getCRLExtensions(crlNum);
int revlen = 2 + serial.length + nowbytes.length;
int revseqlen = spaceFor(revlen);
int revsseqlen = spaceFor(revseqlen);
int len = version.length + sigalg.length + issuer.length + nowbytes.length +
thenbytes.length + revsseqlen + extbytes.length;
int totlen = spaceFor(len);
byte[] rv = new byte[totlen];
int idx = 0;
rv[idx++] = 0x30;
idx = intToASN1(rv, idx, len);
System.arraycopy(version, 0, rv, idx, version.length);
idx += version.length;
System.arraycopy(sigalg, 0, rv, idx, sigalg.length);
idx += sigalg.length;
System.arraycopy(issuer, 0, rv, idx, issuer.length);
idx += issuer.length;
System.arraycopy(nowbytes, 0, rv, idx, nowbytes.length);
idx += nowbytes.length;
System.arraycopy(thenbytes, 0, rv, idx, thenbytes.length);
idx += thenbytes.length;
// the certs
rv[idx++] = 0x30;
idx = intToASN1(rv, idx, revseqlen);
// the cert
rv[idx++] = 0x30;
idx = intToASN1(rv, idx, revlen);
rv[idx++] = 0x02;
rv[idx++] = (byte) serial.length;
System.arraycopy(serial, 0, rv, idx, serial.length);
idx += serial.length;
System.arraycopy(nowbytes, 0, rv, idx, nowbytes.length);
idx += nowbytes.length;
// extensions
System.arraycopy(extbytes, 0, rv, idx, extbytes.length);
if (DEBUG) {
System.out.println("version");
System.out.println(HexDump.dump(version));
System.out.println("sigalg");
System.out.println(HexDump.dump(sigalg));
System.out.println("issuer");
System.out.println(HexDump.dump(issuer));
System.out.println("now");
System.out.println(HexDump.dump(nowbytes));
System.out.println("then");
System.out.println(HexDump.dump(thenbytes));
System.out.println("serial");
System.out.println(HexDump.dump(serial));
System.out.println("extensions");
System.out.println(HexDump.dump(extbytes));
System.out.println("TBS CRL");
System.out.println(HexDump.dump(rv));
}
return rv;
}
/**
* @param val the length of the value, 65535 max
* @return the length of the TLV
*/
private static int spaceFor(int val) {
int rv;
if (val > 255)
rv = 3;
else if (val > 127)
rv = 2;
else
rv = 1;
return 1 + rv + val;
}
/**
* Sequence of two UTCDates
* @return 32 bytes ASN.1 encoded object
*/
private static byte[] getValidity(int validDays) {
byte[] rv = new byte[32];
rv[0] = 0x30;
rv[1] = 30;
long now = System.currentTimeMillis();
long then = now + (validDays * 24L * 60 * 60 * 1000);
byte[] nowbytes = getDate(now);
byte[] thenbytes = getDate(then);
System.arraycopy(nowbytes, 0, rv, 2, 15);
System.arraycopy(thenbytes, 0, rv, 17, 15);
return rv;
}
/**
* A single UTCDate
* @return 15 bytes ASN.1 encoded object
*/
private static byte[] getDate(long now) {
// UTCDate format (HH 0-23)
SimpleDateFormat fmt = new SimpleDateFormat("yyMMddHHmmss");
fmt.setTimeZone(TimeZone.getTimeZone("GMT"));
byte[] nowbytes = DataHelper.getASCII(fmt.format(new Date(now)));
if (nowbytes.length != 12)
throw new IllegalArgumentException();
byte[] rv = new byte[15];
rv[0] = 0x17;
rv[1] = 13;
System.arraycopy(nowbytes, 0, rv, 2, 12);
rv[14] = (byte) 'Z';
return rv;
}
/**
*
* @param pubbytes bit string
* @return 35 bytes ASN.1 encoded object
*/
private static byte[] getExtensions(byte[] pubbytes) {
// RFC 2549 sec. 4.2.1.2
// subject public key identifier is the sha1 hash of the bit string of the public key
// without the tag, length, and igore fields
int pidx = 1;
int skip = pubbytes[pidx++];
if ((skip & 0x80)!= 0)
pidx += skip & 0x80;
pidx++; // ignore
MessageDigest md = SHA1.getInstance();
md.update(pubbytes, pidx, pubbytes.length - pidx);
byte[] sha = md.digest();
byte[] oid = getEncodedOID(OID_SKI);
int wraplen = spaceFor(sha.length);
int extlen = oid.length + spaceFor(wraplen);
int extslen = spaceFor(extlen);
int seqlen = spaceFor(extslen);
int totlen = spaceFor(seqlen);
byte[] rv = new byte[totlen];
int idx = 0;
rv[idx++] = (byte) 0xa3;
idx = intToASN1(rv, idx, seqlen);
rv[idx++] = (byte) 0x30;
idx = intToASN1(rv, idx, extslen);
rv[idx++] = (byte) 0x30;
idx = intToASN1(rv, idx, extlen);
System.arraycopy(oid, 0, rv, idx, oid.length);
idx += oid.length;
// don't know why we wrap the octet string in an octet string
rv[idx++] = (byte) 0x04;
idx = intToASN1(rv, idx, wraplen);
rv[idx++] = (byte) 0x04;
idx = intToASN1(rv, idx, sha.length);
System.arraycopy(sha, 0, rv, idx, sha.length);
return rv;
}
/**
*
* @param crlNum 0-255 because lazy
* @return 16 bytes ASN.1 encoded object
*/
private static byte[] getCRLExtensions(int crlNum) {
if (crlNum < 0 || crlNum > 255)
throw new IllegalArgumentException();
byte[] oid = getEncodedOID(OID_CRLNUM);
int extlen = oid.length + 5;
int extslen = spaceFor(extlen);
int seqlen = spaceFor(extslen);
int totlen = spaceFor(seqlen);
byte[] rv = new byte[totlen];
int idx = 0;
rv[idx++] = (byte) 0xa0;
idx = intToASN1(rv, idx, seqlen);
rv[idx++] = (byte) 0x30;
idx = intToASN1(rv, idx, extslen);
rv[idx++] = (byte) 0x30;
idx = intToASN1(rv, idx, extlen);
System.arraycopy(oid, 0, rv, idx, oid.length);
idx += oid.length;
// don't know why we wrap the int in an octet string
rv[idx++] = (byte) 0x04;
rv[idx++] = (byte) 3;
rv[idx++] = (byte) 0x02;
rv[idx++] = (byte) 1;
rv[idx++] = (byte) crlNum;
return rv;
}
/**
* 0x30 len 0x06 len encodedbytes... 0x05 0
* @return ASN.1 encoded object
* @throws IllegalArgumentException
*/
private static byte[] getEncodedOIDSeq(String oid) {
ByteArrayOutputStream baos = new ByteArrayOutputStream(16);
baos.write(0x30);
// len to be filled in later
baos.write(0);
byte[] b = getEncodedOID(oid);
baos.write(b, 0, b.length);
// NULL
baos.write(0x05);
baos.write(0);
byte[] rv = baos.toByteArray();
rv[1] = (byte) (rv.length - 2);
return rv;
}
/**
* 0x06 len encodedbytes...
* @return ASN.1 encoded object
* @throws IllegalArgumentException
*/
private static byte[] getEncodedOID(String oid) {
ByteArrayOutputStream baos = new ByteArrayOutputStream(16);
baos.write(0x06);
// len to be filled in later
baos.write(0);
String[] f = DataHelper.split(oid, "[.]");
if (f.length < 2)
throw new IllegalArgumentException("length: " + f.length);
baos.write((40 * Integer.parseInt(f[0])) + Integer.parseInt(f[1]));
for (int i = 2; i < f.length; i++) {
int v = Integer.parseInt(f[i]);
if (v >= 128 * 128 * 128 || v < 0)
throw new IllegalArgumentException();
if (v >= 128 * 128)
baos.write((v >> 14) | 0x80);
if (v >= 128)
baos.write((v >> 7) | 0x80);
baos.write(v & 0x7f);
}
byte[] rv = baos.toByteArray();
if (rv.length > 129)
throw new IllegalArgumentException();
rv[1] = (byte) (rv.length - 2);
return rv;
}
/****
public static void main(String[] args) {
try {
test("test0", SigType.DSA_SHA1);
test("test1", SigType.ECDSA_SHA256_P256);
test("test2", SigType.ECDSA_SHA384_P384);
test("test3", SigType.ECDSA_SHA512_P521);
test("test4", SigType.RSA_SHA256_2048);
test("test5", SigType.RSA_SHA384_3072);
test("test6", SigType.RSA_SHA512_4096);
test("test7", SigType.EdDSA_SHA512_Ed25519);
test("test8", SigType.EdDSA_SHA512_Ed25519ph);
} catch (Exception e) {
e.printStackTrace();
}
}
private static final void test(String name, SigType type) throws Exception {
Object[] rv = generate("cname", "ou", "l", "o", "st", "c", 3652, type);
PublicKey jpub = (PublicKey) rv[0];
PrivateKey jpriv = (PrivateKey) rv[1];
X509Certificate cert = (X509Certificate) rv[2];
X509CRL crl = (X509CRL) rv[3];
File ks = new File(name + ".ks");
List<X509Certificate> certs = new ArrayList<X509Certificate>(1);
certs.add(cert);
KeyStoreUtil.storePrivateKey(ks, "changeit", "foo", "foobar", jpriv, certs);
System.out.println("Private key saved to " + ks + " with alias foo, password foobar, keystore password changeit");
File cf = new File(name + ".crt");
CertUtil.saveCert(cert, cf);
System.out.println("Certificate saved to " + cf);
File pf = new File(name + ".priv");
FileOutputStream pfs = new SecureFileOutputStream(pf);
KeyStoreUtil.exportPrivateKey(ks, "changeit", "foo", "foobar", pfs);
pfs.close();
System.out.println("Private key saved to " + pf);
File cr = new File(name + ".crl");
CertUtil.saveCRL(crl, cr);
System.out.println("CRL saved to " + cr);
}
****/
}

View File

@@ -10,7 +10,15 @@ public enum SigAlgo {
DSA("DSA"),
EC("EC"),
EdDSA("EdDSA"),
RSA("RSA")
/**
* For local use only, not for use in the network.
*/
RSA("RSA"),
/**
* For local use only, not for use in the network.
* @since 0.9.25
*/
ElGamal("ElGamal")
;
private final String name;

View File

@@ -1,5 +1,6 @@
package net.i2p.crypto;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
@@ -11,7 +12,9 @@ import java.util.Map;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
import net.i2p.data.Hash;
import net.i2p.data.SigningPrivateKey;
import net.i2p.data.SimpleDataStructure;
import net.i2p.util.SystemVersion;
/**
* Defines the properties for various signature types
@@ -29,20 +32,20 @@ public enum SigType {
* Pubkey 128 bytes; privkey 20 bytes; hash 20 bytes; sig 40 bytes
* @since 0.9.8
*/
DSA_SHA1(0, 128, 20, 20, 40, SigAlgo.DSA, "SHA-1", "SHA1withDSA", CryptoConstants.DSA_SHA1_SPEC, "0"),
DSA_SHA1(0, 128, 20, 20, 40, SigAlgo.DSA, "SHA-1", "SHA1withDSA", CryptoConstants.DSA_SHA1_SPEC, "1.2.840.10040.4.3", "0"),
/** Pubkey 64 bytes; privkey 32 bytes; hash 32 bytes; sig 64 bytes */
ECDSA_SHA256_P256(1, 64, 32, 32, 64, SigAlgo.EC, "SHA-256", "SHA256withECDSA", ECConstants.P256_SPEC, "0.9.12"),
ECDSA_SHA256_P256(1, 64, 32, 32, 64, SigAlgo.EC, "SHA-256", "SHA256withECDSA", ECConstants.P256_SPEC, "1.2.840.10045.4.3.2", "0.9.12"),
/** Pubkey 96 bytes; privkey 48 bytes; hash 48 bytes; sig 96 bytes */
ECDSA_SHA384_P384(2, 96, 48, 48, 96, SigAlgo.EC, "SHA-384", "SHA384withECDSA", ECConstants.P384_SPEC, "0.9.12"),
ECDSA_SHA384_P384(2, 96, 48, 48, 96, SigAlgo.EC, "SHA-384", "SHA384withECDSA", ECConstants.P384_SPEC, "1.2.840.10045.4.3.3", "0.9.12"),
/** Pubkey 132 bytes; privkey 66 bytes; hash 64 bytes; sig 132 bytes */
ECDSA_SHA512_P521(3, 132, 66, 64, 132, SigAlgo.EC, "SHA-512", "SHA512withECDSA", ECConstants.P521_SPEC, "0.9.12"),
ECDSA_SHA512_P521(3, 132, 66, 64, 132, SigAlgo.EC, "SHA-512", "SHA512withECDSA", ECConstants.P521_SPEC, "1.2.840.10045.4.3.4", "0.9.12"),
/** Pubkey 256 bytes; privkey 512 bytes; hash 32 bytes; sig 256 bytes */
RSA_SHA256_2048(4, 256, 512, 32, 256, SigAlgo.RSA, "SHA-256", "SHA256withRSA", RSAConstants.F4_2048_SPEC, "0.9.12"),
RSA_SHA256_2048(4, 256, 512, 32, 256, SigAlgo.RSA, "SHA-256", "SHA256withRSA", RSAConstants.F4_2048_SPEC, "1.2.840.113549.1.1.11", "0.9.12"),
/** Pubkey 384 bytes; privkey 768 bytes; hash 48 bytes; sig 384 bytes */
RSA_SHA384_3072(5, 384, 768, 48, 384, SigAlgo.RSA, "SHA-384", "SHA384withRSA", RSAConstants.F4_3072_SPEC, "0.9.12"),
RSA_SHA384_3072(5, 384, 768, 48, 384, SigAlgo.RSA, "SHA-384", "SHA384withRSA", RSAConstants.F4_3072_SPEC, "1.2.840.113549.1.1.12", "0.9.12"),
/** Pubkey 512 bytes; privkey 1024 bytes; hash 64 bytes; sig 512 bytes */
RSA_SHA512_4096(6, 512, 1024, 64, 512, SigAlgo.RSA, "SHA-512", "SHA512withRSA", RSAConstants.F4_4096_SPEC, "0.9.12"),
RSA_SHA512_4096(6, 512, 1024, 64, 512, SigAlgo.RSA, "SHA-512", "SHA512withRSA", RSAConstants.F4_4096_SPEC, "1.2.840.113549.1.1.13", "0.9.12"),
/**
* Pubkey 32 bytes; privkey 32 bytes; hash 64 bytes; sig 64 bytes
@@ -52,8 +55,17 @@ public enum SigType {
* @since 0.9.15
*/
EdDSA_SHA512_Ed25519(7, 32, 32, 64, 64, SigAlgo.EdDSA, "SHA-512", "SHA512withEdDSA",
EdDSANamedCurveTable.getByName("ed25519-sha-512"), "0.9.17");
EdDSANamedCurveTable.getByName("ed25519-sha-512"), "1.3.101.101", "0.9.17"),
/**
* Prehash version (double hashing, for offline use such as su3, not for use on the network)
* Pubkey 32 bytes; privkey 32 bytes; hash 64 bytes; sig 64 bytes
* @since 0.9.25
*/
EdDSA_SHA512_Ed25519ph(8, 32, 32, 64, 64, SigAlgo.EdDSA, "SHA-512", "NonewithEdDSA",
EdDSANamedCurveTable.getByName("ed25519-sha-512"), "1.3.101.101", "0.9.25"),
;
// TESTING....................
@@ -96,12 +108,12 @@ public enum SigType {
private final int code, pubkeyLen, privkeyLen, hashLen, sigLen;
private final SigAlgo base;
private final String digestName, algoName, since;
private final String digestName, algoName, oid, since;
private final AlgorithmParameterSpec params;
private final boolean isAvail;
SigType(int cod, int pubLen, int privLen, int hLen, int sLen, SigAlgo baseAlgo,
String mdName, String aName, AlgorithmParameterSpec pSpec, String supportedSince) {
String mdName, String aName, AlgorithmParameterSpec pSpec, String oid, String supportedSince) {
code = cod;
pubkeyLen = pubLen;
privkeyLen = privLen;
@@ -111,6 +123,7 @@ public enum SigType {
digestName = mdName;
algoName = aName;
params = pSpec;
this.oid = oid;
since = supportedSince;
isAvail = x_isAvailable();
}
@@ -180,6 +193,15 @@ public enum SigType {
return since;
}
/**
* The OID for the signature.
*
* @since 0.9.25
*/
public String getOID() {
return oid;
}
/**
* @since 0.9.12
* @return true if supported in this JVM
@@ -193,11 +215,29 @@ public enum SigType {
return true;
try {
getParams();
if (getBaseAlgorithm() != SigAlgo.EdDSA)
Signature.getInstance(getAlgorithmName());
if (getBaseAlgorithm() != SigAlgo.EdDSA) {
Signature jsig = Signature.getInstance(getAlgorithmName());
if (getBaseAlgorithm() == SigAlgo.EC && SystemVersion.isGentoo() ) {
// Do a full keygen/sign test on Gentoo, because it lies. Keygen works but sigs fail.
// https://bugs.gentoo.org/show_bug.cgi?id=528338
// http://icedtea.classpath.org/bugzilla/show_bug.cgi?id=2497
// http://zzz.i2p/topics/1931
// Be sure nothing in the code paths below calls isAvailable()
// get an I2P keypair
SimpleDataStructure[] keys = KeyGenerator.getInstance().generateSigningKeys(this);
SigningPrivateKey privKey = (SigningPrivateKey) keys[1];
// convert privkey back to Java key and sign
jsig.initSign(SigUtil.toJavaECKey(privKey));
// use the pubkey as random data
jsig.update(keys[0].getData());
jsig.sign();
}
}
getDigestInstance();
getHashInstance();
} catch (Exception e) {
} catch (GeneralSecurityException e) {
return false;
} catch (RuntimeException e) {
return false;
}
return true;
@@ -253,6 +293,8 @@ public enum SigType {
// handle mixed-case enum
if (uc.equals("EDDSA_SHA512_ED25519"))
return EdDSA_SHA512_Ed25519;
if (uc.equals("EDDSA_SHA512_ED25519PH"))
return EdDSA_SHA512_Ed25519ph;
return valueOf(uc);
} catch (IllegalArgumentException iae) {
try {

View File

@@ -18,6 +18,7 @@ import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.DSAPrivateKeySpec;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.ECParameterSpec;
@@ -49,7 +50,7 @@ import net.i2p.util.NativeBigInteger;
*
* @since 0.9.9, public since 0.9.12
*/
public class SigUtil {
public final class SigUtil {
private static final Map<SigningPublicKey, ECPublicKey> _ECPubkeyCache = new LHMCache<SigningPublicKey, ECPublicKey>(64);
private static final Map<SigningPrivateKey, ECPrivateKey> _ECPrivkeyCache = new LHMCache<SigningPrivateKey, ECPrivateKey>(16);
@@ -97,6 +98,55 @@ public class SigUtil {
}
/**
* Use if SigType is unknown.
* For efficiency, use fromJavakey(pk, type) if type is known.
*
* @param pk JAVA key!
* @throws IllegalArgumentException on unknown type
* @since 0.9.18
*/
public static SigningPublicKey fromJavaKey(PublicKey pk)
throws GeneralSecurityException {
if (pk instanceof DSAPublicKey) {
return fromJavaKey((DSAPublicKey) pk);
}
if (pk instanceof ECPublicKey) {
ECPublicKey k = (ECPublicKey) pk;
AlgorithmParameterSpec spec = k.getParams();
SigType type;
if (spec.equals(SigType.ECDSA_SHA256_P256.getParams()))
type = SigType.ECDSA_SHA256_P256;
else if (spec.equals(SigType.ECDSA_SHA384_P384.getParams()))
type = SigType.ECDSA_SHA384_P384;
else if (spec.equals(SigType.ECDSA_SHA512_P521.getParams()))
type = SigType.ECDSA_SHA512_P521;
else
throw new IllegalArgumentException("Unknown EC type");
return fromJavaKey(k, type);
}
if (pk instanceof EdDSAPublicKey) {
return fromJavaKey((EdDSAPublicKey) pk, SigType.EdDSA_SHA512_Ed25519);
}
if (pk instanceof RSAPublicKey) {
RSAPublicKey k = (RSAPublicKey) pk;
int sz = k.getModulus().bitLength();
SigType type;
if (sz <= ((RSAKeyGenParameterSpec) SigType.RSA_SHA256_2048.getParams()).getKeysize())
type = SigType.RSA_SHA256_2048;
else if (sz <= ((RSAKeyGenParameterSpec) SigType.RSA_SHA384_3072.getParams()).getKeysize())
type = SigType.RSA_SHA384_3072;
else if (sz <= ((RSAKeyGenParameterSpec) SigType.RSA_SHA512_4096.getParams()).getKeysize())
type = SigType.RSA_SHA512_4096;
else
throw new IllegalArgumentException("Unknown RSA type");
return fromJavaKey(k, type);
}
throw new IllegalArgumentException("Unknown type: " + pk.getClass());
}
/**
* Use if SigType is known.
*
* @param pk JAVA key!
*/
public static SigningPublicKey fromJavaKey(PublicKey pk, SigType type)
@@ -111,11 +161,60 @@ public class SigUtil {
case RSA:
return fromJavaKey((RSAPublicKey) pk, type);
default:
throw new IllegalArgumentException();
throw new IllegalArgumentException("Unknown type: " + type);
}
}
/**
* Use if SigType is unknown.
* For efficiency, use fromJavakey(pk, type) if type is known.
*
* @param pk JAVA key!
* @throws IllegalArgumentException on unknown type
* @since 0.9.18
*/
public static SigningPrivateKey fromJavaKey(PrivateKey pk)
throws GeneralSecurityException {
if (pk instanceof DSAPrivateKey) {
return fromJavaKey((DSAPrivateKey) pk);
}
if (pk instanceof ECPrivateKey) {
ECPrivateKey k = (ECPrivateKey) pk;
AlgorithmParameterSpec spec = k.getParams();
SigType type;
if (spec.equals(SigType.ECDSA_SHA256_P256.getParams()))
type = SigType.ECDSA_SHA256_P256;
else if (spec.equals(SigType.ECDSA_SHA384_P384.getParams()))
type = SigType.ECDSA_SHA384_P384;
else if (spec.equals(SigType.ECDSA_SHA512_P521.getParams()))
type = SigType.ECDSA_SHA512_P521;
else
throw new IllegalArgumentException("Unknown EC type");
return fromJavaKey(k, type);
}
if (pk instanceof EdDSAPrivateKey) {
return fromJavaKey((EdDSAPrivateKey) pk, SigType.EdDSA_SHA512_Ed25519);
}
if (pk instanceof RSAPrivateKey) {
RSAPrivateKey k = (RSAPrivateKey) pk;
int sz = k.getModulus().bitLength();
SigType type;
if (sz <= ((RSAKeyGenParameterSpec) SigType.RSA_SHA256_2048.getParams()).getKeysize())
type = SigType.RSA_SHA256_2048;
else if (sz <= ((RSAKeyGenParameterSpec) SigType.RSA_SHA384_3072.getParams()).getKeysize())
type = SigType.RSA_SHA384_3072;
else if (sz <= ((RSAKeyGenParameterSpec) SigType.RSA_SHA512_4096.getParams()).getKeysize())
type = SigType.RSA_SHA512_4096;
else
throw new IllegalArgumentException("Unknown RSA type");
return fromJavaKey(k, type);
}
throw new IllegalArgumentException("Unknown type: " + pk.getClass());
}
/**
* Use if SigType is known.
*
* @param pk JAVA key!
*/
public static SigningPrivateKey fromJavaKey(PrivateKey pk, SigType type)
@@ -130,7 +229,7 @@ public class SigUtil {
case RSA:
return fromJavaKey((RSAPrivateKey) pk, type);
default:
throw new IllegalArgumentException();
throw new IllegalArgumentException("Unknown type: " + type);
}
}
@@ -450,9 +549,13 @@ public class SigUtil {
/**
* Split a byte array into two BigIntegers
* @param b length must be even
* @return array of two BigIntegers
* @since 0.9.9
*/
private static BigInteger[] split(byte[] b) {
private static NativeBigInteger[] split(byte[] b) {
if ((b.length & 0x01) != 0)
throw new IllegalArgumentException("length must be even");
int sublen = b.length / 2;
byte[] bx = new byte[sublen];
byte[] by = new byte[sublen];
@@ -466,9 +569,12 @@ public class SigUtil {
/**
* Combine two BigIntegers of nominal length = len / 2
* @return array of exactly len bytes
* @since 0.9.9
*/
private static byte[] combine(BigInteger x, BigInteger y, int len)
throws InvalidKeyException {
if ((len & 0x01) != 0)
throw new InvalidKeyException("length must be even");
int sublen = len / 2;
byte[] b = new byte[len];
byte[] bx = rectify(x, sublen);
@@ -510,7 +616,8 @@ public class SigUtil {
/**
* http://download.oracle.com/javase/1.5.0/docs/guide/security/CryptoSpec.html
* Signature Format ASN.1 sequence of two INTEGER values: r and s, in that order:
*<pre>
* Signature Format: ASN.1 sequence of two INTEGER values: r and s, in that order:
* SEQUENCE ::= { r INTEGER, s INTEGER }
*
* http://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One
@@ -520,6 +627,7 @@ public class SigUtil {
* 02 -- tag indicating INTEGER
* xx - length in octets
* xxxxxx - value
*</pre>
*
* Convert to BigInteger and back so we have the minimum length representation, as required.
* r and s are always non-negative.
@@ -527,46 +635,72 @@ public class SigUtil {
* Only supports sigs up to about 252 bytes. See code to fix BER encoding for this before you
* add a SigType with bigger signatures.
*
* @param sig length must be even
* @throws IllegalArgumentException if too big
* @since 0.8.7, moved to SigUtil in 0.9.9
*/
private static byte[] sigBytesToASN1(byte[] sig) {
//System.out.println("pre TO asn1\n" + net.i2p.util.HexDump.dump(sig));
int len = sig.length;
int sublen = len / 2;
byte[] tmp = new byte[sublen];
BigInteger[] rs = split(sig);
return sigBytesToASN1(rs[0], rs[1]);
}
System.arraycopy(sig, 0, tmp, 0, sublen);
BigInteger r = new BigInteger(1, tmp);
/**
* http://download.oracle.com/javase/1.5.0/docs/guide/security/CryptoSpec.html
*<pre>
* Signature Format: ASN.1 sequence of two INTEGER values: r and s, in that order:
* SEQUENCE ::= { r INTEGER, s INTEGER }
*
* http://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One
* 30 -- tag indicating SEQUENCE
* xx - length in octets
*
* 02 -- tag indicating INTEGER
* xx - length in octets
* xxxxxx - value
*</pre>
*
* r and s are always non-negative.
*
* Only supports sigs up to about 65530 bytes. See code to fix BER encoding for this before you
* add a SigType with bigger signatures.
*
* @throws IllegalArgumentException if too big
* @since 0.9.25, split out from sigBytesToASN1(byte[])
*/
public static byte[] sigBytesToASN1(BigInteger r, BigInteger s) {
int extra = 4;
byte[] rb = r.toByteArray();
if (rb.length > 127)
throw new IllegalArgumentException("FIXME R length > 127");
System.arraycopy(sig, sublen, tmp, 0, sublen);
BigInteger s = new BigInteger(1, tmp);
if (rb.length > 127) {
extra++;
if (rb.length > 255)
extra++;
}
byte[] sb = s.toByteArray();
if (sb.length > 127)
throw new IllegalArgumentException("FIXME S length > 127");
int seqlen = rb.length + sb.length + 4;
if (seqlen > 255)
throw new IllegalArgumentException("FIXME seq length > 255");
if (sb.length > 127) {
extra++;
if (sb.length > 255)
extra++;
}
int seqlen = rb.length + sb.length + extra;
int totlen = seqlen + 2;
if (seqlen > 127)
if (seqlen > 127) {
totlen++;
if (seqlen > 255)
totlen++;
}
byte[] rv = new byte[totlen];
int idx = 0;
rv[idx++] = 0x30;
if (seqlen > 127)
rv[idx++] =(byte) 0x81;
rv[idx++] = (byte) seqlen;
idx = intToASN1(rv, idx, seqlen);
rv[idx++] = 0x02;
rv[idx++] = (byte) rb.length;
idx = intToASN1(rv, idx, rb.length);
System.arraycopy(rb, 0, rv, idx, rb.length);
idx += rb.length;
rv[idx++] = 0x02;
rv[idx++] = (byte) sb.length;
idx = intToASN1(rv, idx, sb.length);
System.arraycopy(sb, 0, rv, idx, sb.length);
//System.out.println("post TO asn1\n" + net.i2p.util.HexDump.dump(rv));
@@ -574,10 +708,34 @@ public class SigUtil {
}
/**
* See above.
* Only supports sigs up to about 252 bytes. See code to fix BER encoding for bigger than that.
* Output an length or integer value in ASN.1
* Does NOT output the tag e.g. 0x02 / 0x30
*
* @return len bytes
* @param val 0-65535
* @return the new index
* @since 0.9.25
*/
public static int intToASN1(byte[] d, int idx, int val) {
if (val < 0 || val > 65535)
throw new IllegalArgumentException("fixme length " + val);
if (val > 127) {
if (val > 255) {
d[idx++] = (byte) 0x82;
d[idx++] = (byte) (val >> 8);
} else {
d[idx++] = (byte) 0x81;
}
}
d[idx++] = (byte) val;
return idx;
}
/**
* See above.
* Only supports sigs up to about 65530 bytes. See code to fix BER encoding for bigger than that.
*
* @param len must be even, twice the nominal length of each BigInteger
* @return len bytes, call split() on the result to get two BigIntegers
* @since 0.8.7, moved to SigUtil in 0.9.9
*/
private static byte[] aSN1ToSigBytes(byte[] asn, int len)
@@ -594,8 +752,17 @@ public class SigUtil {
byte[] rv = new byte[len];
int sublen = len / 2;
int rlen = asn[++idx];
if ((rlen & 0x80) != 0)
throw new SignatureException("FIXME R length > 127");
if ((rlen & 0x80) != 0) {
if ((rlen & 0xff) == 0x81) {
rlen = asn[++idx] & 0xff;
} else if ((rlen & 0xff) == 0x82) {
rlen = asn[++idx] & 0xff;
rlen <<= 8;
rlen |= asn[++idx] & 0xff;
} else {
throw new SignatureException("FIXME R length > 65535");
}
}
if ((asn[++idx] & 0x80) != 0)
throw new SignatureException("R is negative");
if (rlen > sublen + 1)
@@ -605,24 +772,47 @@ public class SigUtil {
else
System.arraycopy(asn, idx, rv, sublen - rlen, rlen);
idx += rlen;
int slenloc = idx + 1;
if (asn[idx] != 0x02)
throw new SignatureException("asn[s] = " + (asn[idx] & 0xff));
int slen = asn[slenloc];
if ((slen & 0x80) != 0)
throw new SignatureException("FIXME S length > 127");
if ((asn[slenloc + 1] & 0x80) != 0)
int slen = asn[++idx];
if ((slen & 0x80) != 0) {
if ((slen & 0xff) == 0x81) {
slen = asn[++idx] & 0xff;
} else if ((slen & 0xff) == 0x82) {
slen = asn[++idx] & 0xff;
slen <<= 8;
slen |= asn[++idx] & 0xff;
} else {
throw new SignatureException("FIXME S length > 65535");
}
}
if ((asn[++idx] & 0x80) != 0)
throw new SignatureException("S is negative");
if (slen > sublen + 1)
throw new SignatureException("S too big " + slen);
if (slen == sublen + 1)
System.arraycopy(asn, slenloc + 2, rv, sublen, sublen);
System.arraycopy(asn, idx + 1, rv, sublen, sublen);
else
System.arraycopy(asn, slenloc + 1, rv, len - slen, slen);
System.arraycopy(asn, idx, rv, len - slen, slen);
//System.out.println("post from asn1\n" + net.i2p.util.HexDump.dump(rv));
return rv;
}
/**
* See above.
* Only supports sigs up to about 65530 bytes. See code to fix BER encoding for bigger than that.
*
* @param len nominal length of each BigInteger
* @return two BigIntegers
* @since 0.9.25
*/
public static NativeBigInteger[] aSN1ToBigInteger(byte[] asn, int len)
throws SignatureException {
byte[] sig = aSN1ToSigBytes(asn, len * 2);
return split(sig);
}
public static void clearCaches() {
synchronized(_ECPubkeyCache) {
_ECPubkeyCache.clear();

View File

@@ -344,7 +344,11 @@ riCe6OlAEiNpcc6mMyIYYWFICbrDFTrDR3wXqwc/Jkcx6L5VVWoagpSzbo3yGhc=
System.out.println("\r\nPrivate key written to: " + privateKeyFile);
System.out.println("Public key written to: " + publicKeyFile);
System.out.println("\r\nPublic key: " + signingPublicKey.toBase64() + "\r\n");
} catch (Exception e) {
} catch (IOException e) {
System.err.println("Error writing keys:");
e.printStackTrace();
return false;
} catch (DataFormatException e) {
System.err.println("Error writing keys:");
e.printStackTrace();
return false;
@@ -758,7 +762,7 @@ riCe6OlAEiNpcc6mMyIYYWFICbrDFTrDR3wXqwc/Jkcx6L5VVWoagpSzbo3yGhc=
bytesToSignInputStream = new SequenceInputStream(versionHeaderInputStream, fileInputStream);
signature = _context.dsa().sign(bytesToSignInputStream, signingPrivateKey);
} catch (Exception e) {
} catch (IOException e) {
if (_log.shouldLog(Log.ERROR))
_log.error("Error signing", e);
@@ -767,10 +771,10 @@ riCe6OlAEiNpcc6mMyIYYWFICbrDFTrDR3wXqwc/Jkcx6L5VVWoagpSzbo3yGhc=
if (bytesToSignInputStream != null)
try {
bytesToSignInputStream.close();
fileInputStream.close();
} catch (IOException ioe) {
}
fileInputStream = null;
}
FileOutputStream fileOutputStream = null;

View File

@@ -35,7 +35,7 @@ import net.i2p.util.SystemVersion;
*
* @author jrandom
*/
class YKGenerator {
final class YKGenerator {
//private final static Log _log = new Log(YKGenerator.class);
private final int MIN_NUM_BUILDERS;
private final int MAX_NUM_BUILDERS;
@@ -91,7 +91,7 @@ class YKGenerator {
return;
_precalcThread = new I2PThread(new YKPrecalcRunner(MIN_NUM_BUILDERS, MAX_NUM_BUILDERS),
"YK Precalc", true);
_precalcThread.setPriority(Thread.MIN_PRIORITY);
_precalcThread.setPriority(Thread.NORM_PRIORITY - 2);
_isRunning = true;
_precalcThread.start();
}

View File

@@ -2,6 +2,7 @@ package net.i2p.crypto.eddsa;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -9,6 +10,7 @@ import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Arrays;
import net.i2p.crypto.eddsa.math.Curve;
@@ -16,21 +18,68 @@ import net.i2p.crypto.eddsa.math.GroupElement;
import net.i2p.crypto.eddsa.math.ScalarOps;
/**
* Signing and verification for EdDSA.
*<p>
* The EdDSA sign and verify algorithms do not interact well with
* the Java Signature API, as one or more update() methods must be
* called before sign() or verify(). Using the standard API,
* this implementation must copy and buffer all data passed in
* via update().
*</p><p>
* This implementation offers two ways to avoid this copying,
* but only if all data to be signed or verified is available
* in a single byte array.
*</p><p>
*Option 1:
*</p><ol>
*<li>Call initSign() or initVerify() as usual.
*</li><li>Call setParameter(ONE_SHOT_MODE)
*</li><li>Call update(byte[]) or update(byte[], int, int) exactly once
*</li><li>Call sign() or verify() as usual.
*</li><li>If doing additional one-shot signs or verifies with this object, you must
* call setParameter(ONE_SHOT_MODE) each time
*</li></ol>
*
*<p>
*Option 2:
*</p><ol>
*<li>Call initSign() or initVerify() as usual.
*</li><li>Call one of the signOneShot() or verifyOneShot() methods.
*</li><li>If doing additional one-shot signs or verifies with this object,
* just call signOneShot() or verifyOneShot() again.
*</li></ol>
*
* @since 0.9.15
* @author str4d
*
*/
public class EdDSAEngine extends Signature {
public final class EdDSAEngine extends Signature {
private MessageDigest digest;
private final ByteArrayOutputStream baos;
private ByteArrayOutputStream baos;
private EdDSAKey key;
private boolean oneShotMode;
private byte[] oneShotBytes;
private int oneShotOffset;
private int oneShotLength;
/**
* To efficiently sign or verify data in one shot, pass this to setParameters()
* after initSign() or initVerify() but BEFORE THE FIRST AND ONLY
* update(data) or update(data, off, len). The data reference will be saved
* and then used in sign() or verify() without copying the data.
* Violate these rules and you will get a SignatureException.
*
* @since 0.9.25
*/
public static final AlgorithmParameterSpec ONE_SHOT_MODE = new OneShotSpec();
private static class OneShotSpec implements AlgorithmParameterSpec {}
/**
* No specific hash requested, allows any EdDSA key.
*/
public EdDSAEngine() {
super("EdDSA");
baos = new ByteArrayOutputStream(256);
}
/**
@@ -42,12 +91,21 @@ public class EdDSAEngine extends Signature {
this.digest = digest;
}
@Override
protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException {
/**
* @since 0.9.25
*/
private void reset() {
if (digest != null)
digest.reset();
baos.reset();
if (baos != null)
baos.reset();
oneShotMode = false;
oneShotBytes = null;
}
@Override
protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException {
reset();
if (privateKey instanceof EdDSAPrivateKey) {
EdDSAPrivateKey privKey = (EdDSAPrivateKey) privateKey;
key = privKey;
@@ -61,21 +119,22 @@ public class EdDSAEngine extends Signature {
}
} else if (!key.getParams().getHashAlgorithm().equals(digest.getAlgorithm()))
throw new InvalidKeyException("Key hash algorithm does not match chosen digest");
digestInitSign(privKey);
} else {
throw new InvalidKeyException("cannot identify EdDSA private key: " + privateKey.getClass());
}
}
// Preparing for hash
// r = H(h_b,...,h_2b-1,M)
int b = privKey.getParams().getCurve().getField().getb();
digest.update(privKey.getH(), b/8, b/4 - b/8);
} else
throw new InvalidKeyException("cannot identify EdDSA private key.");
private void digestInitSign(EdDSAPrivateKey privKey) {
// Preparing for hash
// r = H(h_b,...,h_2b-1,M)
int b = privKey.getParams().getCurve().getField().getb();
digest.update(privKey.getH(), b/8, b/4 - b/8);
}
@Override
protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {
if (digest != null)
digest.reset();
baos.reset();
reset();
if (publicKey instanceof EdDSAPublicKey) {
key = (EdDSAPublicKey) publicKey;
@@ -88,34 +147,79 @@ public class EdDSAEngine extends Signature {
}
} else if (!key.getParams().getHashAlgorithm().equals(digest.getAlgorithm()))
throw new InvalidKeyException("Key hash algorithm does not match chosen digest");
} else
throw new InvalidKeyException("cannot identify EdDSA public key.");
} else {
throw new InvalidKeyException("cannot identify EdDSA public key: " + publicKey.getClass());
}
}
/**
* @throws SignatureException if in one-shot mode
*/
@Override
protected void engineUpdate(byte b) throws SignatureException {
// We need to store the message because it is used in several hashes
// XXX Can this be done more efficiently?
if (oneShotMode)
throw new SignatureException("unsupported in one-shot mode");
if (baos == null)
baos = new ByteArrayOutputStream(256);
baos.write(b);
}
/**
* @throws SignatureException if one-shot rules are violated
*/
@Override
protected void engineUpdate(byte[] b, int off, int len)
throws SignatureException {
// We need to store the message because it is used in several hashes
// XXX Can this be done more efficiently?
baos.write(b, off, len);
if (oneShotMode) {
if (oneShotBytes != null)
throw new SignatureException("update() already called");
oneShotBytes = b;
oneShotOffset = off;
oneShotLength = len;
} else {
if (baos == null)
baos = new ByteArrayOutputStream(256);
baos.write(b, off, len);
}
}
@Override
protected byte[] engineSign() throws SignatureException {
try {
return x_engineSign();
} finally {
reset();
// must leave the object ready to sign again with
// the same key, as required by the API
EdDSAPrivateKey privKey = (EdDSAPrivateKey) key;
digestInitSign(privKey);
}
}
private byte[] x_engineSign() throws SignatureException {
Curve curve = key.getParams().getCurve();
ScalarOps sc = key.getParams().getScalarOps();
byte[] a = ((EdDSAPrivateKey) key).geta();
byte[] message = baos.toByteArray();
byte[] message;
int offset, length;
if (oneShotMode) {
if (oneShotBytes == null)
throw new SignatureException("update() not called first");
message = oneShotBytes;
offset = oneShotOffset;
length = oneShotLength;
} else {
if (baos == null)
message = new byte[0];
else
message = baos.toByteArray();
offset = 0;
length = message.length;
}
// r = H(h_b,...,h_2b-1,M)
byte[] r = digest.digest(message);
digest.update(message, offset, length);
byte[] r = digest.digest();
// r mod l
// Reduces r from 64 bytes to 32 bytes
@@ -128,7 +232,8 @@ public class EdDSAEngine extends Signature {
// S = (r + H(Rbar,Abar,M)*a) mod l
digest.update(Rbyte);
digest.update(((EdDSAPrivateKey) key).getAbyte());
byte[] h = digest.digest(message);
digest.update(message, offset, length);
byte[] h = digest.digest();
h = sc.reduce(h);
byte[] S = sc.multiplyAndAdd(h, a, r);
@@ -141,6 +246,14 @@ public class EdDSAEngine extends Signature {
@Override
protected boolean engineVerify(byte[] sigBytes) throws SignatureException {
try {
return x_engineVerify(sigBytes);
} finally {
reset();
}
}
private boolean x_engineVerify(byte[] sigBytes) throws SignatureException {
Curve curve = key.getParams().getCurve();
int b = curve.getField().getb();
if (sigBytes.length != b/4)
@@ -150,8 +263,24 @@ public class EdDSAEngine extends Signature {
digest.update(sigBytes, 0, b/8);
digest.update(((EdDSAPublicKey) key).getAbyte());
// h = H(Rbar,Abar,M)
byte[] message = baos.toByteArray();
byte[] h = digest.digest(message);
byte[] message;
int offset, length;
if (oneShotMode) {
if (oneShotBytes == null)
throw new SignatureException("update() not called first");
message = oneShotBytes;
offset = oneShotOffset;
length = oneShotLength;
} else {
if (baos == null)
message = new byte[0];
else
message = baos.toByteArray();
offset = 0;
length = message.length;
}
digest.update(message, offset, length);
byte[] h = digest.digest();
// h mod l
h = key.getParams().getScalarOps().reduce(h);
@@ -171,6 +300,140 @@ public class EdDSAEngine extends Signature {
return true;
}
/**
* To efficiently sign all the data in one shot, if it is available,
* use this method, which will avoid copying the data.
*
* Same as:
*<pre>
* setParameter(ONE_SHOT_MODE)
* update(data)
* sig = sign()
*</pre>
*
* @throws SignatureException if update() already called
* @see #ONE_SHOT_MODE
* @since 0.9.25
*/
public byte[] signOneShot(byte[] data) throws SignatureException {
return signOneShot(data, 0, data.length);
}
/**
* To efficiently sign all the data in one shot, if it is available,
* use this method, which will avoid copying the data.
*
* Same as:
*<pre>
* setParameter(ONE_SHOT_MODE)
* update(data, off, len)
* sig = sign()
*</pre>
*
* @throws SignatureException if update() already called
* @see #ONE_SHOT_MODE
* @since 0.9.25
*/
public byte[] signOneShot(byte[] data, int off, int len) throws SignatureException {
oneShotMode = true;
update(data, off, len);
return sign();
}
/**
* To efficiently verify all the data in one shot, if it is available,
* use this method, which will avoid copying the data.
*
* Same as:
*<pre>
* setParameter(ONE_SHOT_MODE)
* update(data)
* ok = verify(signature)
*</pre>
*
* @throws SignatureException if update() already called
* @see #ONE_SHOT_MODE
* @since 0.9.25
*/
public boolean verifyOneShot(byte[] data, byte[] signature) throws SignatureException {
return verifyOneShot(data, 0, data.length, signature, 0, signature.length);
}
/**
* To efficiently verify all the data in one shot, if it is available,
* use this method, which will avoid copying the data.
*
* Same as:
*<pre>
* setParameter(ONE_SHOT_MODE)
* update(data, off, len)
* ok = verify(signature)
*</pre>
*
* @throws SignatureException if update() already called
* @see #ONE_SHOT_MODE
* @since 0.9.25
*/
public boolean verifyOneShot(byte[] data, int off, int len, byte[] signature) throws SignatureException {
return verifyOneShot(data, off, len, signature, 0, signature.length);
}
/**
* To efficiently verify all the data in one shot, if it is available,
* use this method, which will avoid copying the data.
*
* Same as:
*<pre>
* setParameter(ONE_SHOT_MODE)
* update(data)
* ok = verify(signature, sigoff, siglen)
*</pre>
*
* @throws SignatureException if update() already called
* @see #ONE_SHOT_MODE
* @since 0.9.25
*/
public boolean verifyOneShot(byte[] data, byte[] signature, int sigoff, int siglen) throws SignatureException {
return verifyOneShot(data, 0, data.length, signature, sigoff, siglen);
}
/**
* To efficiently verify all the data in one shot, if it is available,
* use this method, which will avoid copying the data.
*
* Same as:
*<pre>
* setParameter(ONE_SHOT_MODE)
* update(data, off, len)
* ok = verify(signature, sigoff, siglen)
*</pre>
*
* @throws SignatureException if update() already called
* @see #ONE_SHOT_MODE
* @since 0.9.25
*/
public boolean verifyOneShot(byte[] data, int off, int len, byte[] signature, int sigoff, int siglen) throws SignatureException {
oneShotMode = true;
update(data, off, len);
return verify(signature, sigoff, siglen);
}
/**
* @throws InvalidAlgorithmParameterException if spec is ONE_SHOT_MODE and update() already called
* @see #ONE_SHOT_MODE
* @since 0.9.25
*/
@Override
protected void engineSetParameter(AlgorithmParameterSpec spec) throws InvalidAlgorithmParameterException {
if (spec.equals(ONE_SHOT_MODE)) {
if (oneShotBytes != null || (baos != null && baos.size() > 0))
throw new InvalidAlgorithmParameterException("update() already called");
oneShotMode = true;
} else {
super.engineSetParameter(spec);
}
}
/**
* @deprecated replaced with <a href="#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
*/

View File

@@ -1,13 +1,24 @@
package net.i2p.crypto.eddsa;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Arrays;
import net.i2p.crypto.eddsa.math.GroupElement;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
/**
* An EdDSA private key.
*<p>
* Warning: Private key encoding is not fully specified in the
* current IETF draft. This implementation uses PKCS#8 encoding,
* and is subject to change. See getEncoded().
*</p><p>
* Ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04
*</p>
*
* @since 0.9.15
* @author str4d
@@ -15,12 +26,12 @@ import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
*/
public class EdDSAPrivateKey implements EdDSAKey, PrivateKey {
private static final long serialVersionUID = 23495873459878957L;
private transient final byte[] seed;
private transient final byte[] h;
private transient final byte[] a;
private transient final GroupElement A;
private transient final byte[] Abyte;
private transient final EdDSAParameterSpec edDsaSpec;
private final byte[] seed;
private final byte[] h;
private final byte[] a;
private final GroupElement A;
private final byte[] Abyte;
private final EdDSAParameterSpec edDsaSpec;
public EdDSAPrivateKey(EdDSAPrivateKeySpec spec) {
this.seed = spec.getSeed();
@@ -31,6 +42,14 @@ public class EdDSAPrivateKey implements EdDSAKey, PrivateKey {
this.edDsaSpec = spec.getParams();
}
/**
* @since 0.9.25
*/
public EdDSAPrivateKey(PKCS8EncodedKeySpec spec) throws InvalidKeySpecException {
this(new EdDSAPrivateKeySpec(decode(spec.getEncoded()),
EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512)));
}
public String getAlgorithm() {
return "EdDSA";
}
@@ -39,9 +58,116 @@ public class EdDSAPrivateKey implements EdDSAKey, PrivateKey {
return "PKCS#8";
}
/**
* This follows the docs from
* java.security.spec.PKCS8EncodedKeySpec
* quote:
*<pre>
* The PrivateKeyInfo syntax is defined in the PKCS#8 standard as follows:
* PrivateKeyInfo ::= SEQUENCE {
* version Version,
* privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
* privateKey PrivateKey,
* attributes [0] IMPLICIT Attributes OPTIONAL }
* Version ::= INTEGER
* PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
* PrivateKey ::= OCTET STRING
* Attributes ::= SET OF Attribute
*</pre>
*
*<pre>
* AlgorithmIdentifier ::= SEQUENCE
* {
* algorithm OBJECT IDENTIFIER,
* parameters ANY OPTIONAL
* }
*</pre>
*
* Ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04
*
* Note that the private key encoding is not fully specified in the Josefsson draft version 04,
* and the example could be wrong, as it's lacking Version and AlgorithmIdentifier.
* This will hopefully be clarified in the next draft.
* But sun.security.pkcs.PKCS8Key expects them so we must include them for keytool to work.
*
* @return 49 bytes for Ed25519, null for other curves
* @since implemented in 0.9.25
*/
public byte[] getEncoded() {
// TODO Auto-generated method stub
return null;
if (!edDsaSpec.equals(EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512)))
return null;
int totlen = 17 + seed.length;
byte[] rv = new byte[totlen];
int idx = 0;
// sequence
rv[idx++] = 0x30;
rv[idx++] = (byte) (15 + seed.length);
// version
// not in the Josefsson example
rv[idx++] = 0x02;
rv[idx++] = 1;
rv[idx++] = 0;
// Algorithm Identifier
// sequence
// not in the Josefsson example
rv[idx++] = 0x30;
rv[idx++] = 8;
// OID 1.3.101.100
// https://msdn.microsoft.com/en-us/library/windows/desktop/bb540809%28v=vs.85%29.aspx
// not in the Josefsson example
rv[idx++] = 0x06;
rv[idx++] = 3;
rv[idx++] = (1 * 40) + 3;
rv[idx++] = 101;
rv[idx++] = 100;
// params
rv[idx++] = 0x0a;
rv[idx++] = 1;
rv[idx++] = 1; // Ed25519
// the key
rv[idx++] = 0x04; // octet string
rv[idx++] = (byte) seed.length;
System.arraycopy(seed, 0, rv, idx, seed.length);
return rv;
}
/**
* This is really dumb for now.
* See getEncoded().
*
* @return 32 bytes for Ed25519, throws for other curves
* @since 0.9.25
*/
private static byte[] decode(byte[] d) throws InvalidKeySpecException {
try {
int idx = 0;
if (d[idx++] != 0x30 ||
d[idx++] != 47 ||
d[idx++] != 0x02 ||
d[idx++] != 1 ||
d[idx++] != 0 ||
d[idx++] != 0x30 ||
d[idx++] != 8 ||
d[idx++] != 0x06 ||
d[idx++] != 3 ||
d[idx++] != (1 * 40) + 3 ||
d[idx++] != 101 ||
d[idx++] != 100 ||
d[idx++] != 0x0a ||
d[idx++] != 1 ||
d[idx++] != 1 ||
d[idx++] != 0x04 ||
d[idx++] != 32) {
throw new InvalidKeySpecException("unsupported key spec");
}
byte[] rv = new byte[32];
System.arraycopy(d, idx, rv, 0, 32);
return rv;
} catch (IndexOutOfBoundsException ioobe) {
throw new InvalidKeySpecException(ioobe);
}
}
public EdDSAParameterSpec getParams() {
@@ -67,4 +193,26 @@ public class EdDSAPrivateKey implements EdDSAKey, PrivateKey {
public byte[] getAbyte() {
return Abyte;
}
/**
* @since 0.9.25
*/
@Override
public int hashCode() {
return Arrays.hashCode(seed);
}
/**
* @since 0.9.25
*/
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof EdDSAPrivateKey))
return false;
EdDSAPrivateKey pk = (EdDSAPrivateKey) o;
return Arrays.equals(seed, pk.getSeed()) &&
edDsaSpec.equals(pk.getParams());
}
}

View File

@@ -1,13 +1,23 @@
package net.i2p.crypto.eddsa;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import net.i2p.crypto.eddsa.math.GroupElement;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
/**
* An EdDSA public key.
*<p>
* Warning: Public key encoding is is based on the
* current IETF draft, and is subject to change. See getEncoded().
*</p><p>
* Ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04
*</p>
*
* @since 0.9.15
* @author str4d
@@ -27,6 +37,14 @@ public class EdDSAPublicKey implements EdDSAKey, PublicKey {
this.edDsaSpec = spec.getParams();
}
/**
* @since 0.9.25
*/
public EdDSAPublicKey(X509EncodedKeySpec spec) throws InvalidKeySpecException {
this(new EdDSAPublicKeySpec(decode(spec.getEncoded()),
EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512)));
}
public String getAlgorithm() {
return "EdDSA";
}
@@ -35,9 +53,95 @@ public class EdDSAPublicKey implements EdDSAKey, PublicKey {
return "X.509";
}
/**
* This follows the spec at
* ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04
* which matches the docs from
* java.security.spec.X509EncodedKeySpec
* quote:
*<pre>
* The SubjectPublicKeyInfo syntax is defined in the X.509 standard as follows:
* SubjectPublicKeyInfo ::= SEQUENCE {
* algorithm AlgorithmIdentifier,
* subjectPublicKey BIT STRING }
*</pre>
*
*<pre>
* AlgorithmIdentifier ::= SEQUENCE
* {
* algorithm OBJECT IDENTIFIER,
* parameters ANY OPTIONAL
* }
*</pre>
*
* @return 47 bytes for Ed25519, null for other curves
* @since implemented in 0.9.25
*/
public byte[] getEncoded() {
// TODO Auto-generated method stub
return null;
if (!edDsaSpec.equals(EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512)))
return null;
int totlen = 15 + Abyte.length;
byte[] rv = new byte[totlen];
int idx = 0;
// sequence
rv[idx++] = 0x30;
rv[idx++] = (byte) (13 + Abyte.length);
// Algorithm Identifier
// sequence
rv[idx++] = 0x30;
rv[idx++] = 8;
// OID 1.3.101.100
// https://msdn.microsoft.com/en-us/library/windows/desktop/bb540809%28v=vs.85%29.aspx
rv[idx++] = 0x06;
rv[idx++] = 3;
rv[idx++] = (1 * 40) + 3;
rv[idx++] = 101;
rv[idx++] = 100;
// params
rv[idx++] = 0x0a;
rv[idx++] = 1;
rv[idx++] = 1; // Ed25519
// the key
rv[idx++] = 0x03; // bit string
rv[idx++] = (byte) (1 + Abyte.length);
rv[idx++] = 0; // number of trailing unused bits
System.arraycopy(Abyte, 0, rv, idx, Abyte.length);
return rv;
}
/**
* This is really dumb for now.
* See getEncoded().
*
* @return 32 bytes for Ed25519, throws for other curves
* @since 0.9.25
*/
private static byte[] decode(byte[] d) throws InvalidKeySpecException {
try {
int idx = 0;
if (d[idx++] != 0x30 ||
d[idx++] != 45 ||
d[idx++] != 0x30 ||
d[idx++] != 8 ||
d[idx++] != 0x06 ||
d[idx++] != 3 ||
d[idx++] != (1 * 40) + 3 ||
d[idx++] != 101 ||
d[idx++] != 100 ||
d[idx++] != 0x0a ||
d[idx++] != 1 ||
d[idx++] != 1 ||
d[idx++] != 0x03 ||
d[idx++] != 33 ||
d[idx++] != 0) {
throw new InvalidKeySpecException("unsupported key spec");
}
byte[] rv = new byte[32];
System.arraycopy(d, idx, rv, 0, 32);
return rv;
} catch (IndexOutOfBoundsException ioobe) {
throw new InvalidKeySpecException(ioobe);
}
}
public EdDSAParameterSpec getParams() {
@@ -55,4 +159,26 @@ public class EdDSAPublicKey implements EdDSAKey, PublicKey {
public byte[] getAbyte() {
return Abyte;
}
/**
* @since 0.9.25
*/
@Override
public int hashCode() {
return Arrays.hashCode(Abyte);
}
/**
* @since 0.9.25
*/
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof EdDSAPublicKey))
return false;
EdDSAPublicKey pk = (EdDSAPublicKey) o;
return Arrays.equals(Abyte, pk.getAbyte()) &&
edDsaSpec.equals(pk.getParams());
}
}

View File

@@ -7,6 +7,8 @@ import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
@@ -16,22 +18,34 @@ import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
* @author str4d
*
*/
public class KeyFactory extends KeyFactorySpi {
public final class KeyFactory extends KeyFactorySpi {
/**
* As of 0.9.25, supports PKCS8EncodedKeySpec
*/
protected PrivateKey engineGeneratePrivate(KeySpec keySpec)
throws InvalidKeySpecException {
if (keySpec instanceof EdDSAPrivateKeySpec) {
return new EdDSAPrivateKey((EdDSAPrivateKeySpec) keySpec);
}
throw new InvalidKeySpecException("key spec not recognised");
if (keySpec instanceof PKCS8EncodedKeySpec) {
return new EdDSAPrivateKey((PKCS8EncodedKeySpec) keySpec);
}
throw new InvalidKeySpecException("key spec not recognised: " + keySpec.getClass());
}
/**
* As of 0.9.25, supports X509EncodedKeySpec
*/
protected PublicKey engineGeneratePublic(KeySpec keySpec)
throws InvalidKeySpecException {
if (keySpec instanceof EdDSAPublicKeySpec) {
return new EdDSAPublicKey((EdDSAPublicKeySpec) keySpec);
}
throw new InvalidKeySpecException("key spec not recognised");
if (keySpec instanceof X509EncodedKeySpec) {
return new EdDSAPublicKey((X509EncodedKeySpec) keySpec);
}
throw new InvalidKeySpecException("key spec not recognised: " + keySpec.getClass());
}
@SuppressWarnings("unchecked")

View File

@@ -21,7 +21,7 @@ import net.i2p.util.RandomSource;
*
* @since 0.9.15
*/
public class KeyPairGenerator extends KeyPairGeneratorSpi {
public final class KeyPairGenerator extends KeyPairGeneratorSpi {
private static final int DEFAULT_STRENGTH = 256;
private EdDSAParameterSpec edParams;
private SecureRandom random;

View File

@@ -69,4 +69,29 @@ public class Curve implements Serializable {
ge.precompute(true);
return ge;
}
/**
* @since 0.9.25
*/
@Override
public int hashCode() {
return f.hashCode() ^
d.hashCode() ^
I.hashCode();
}
/**
* @since 0.9.25
*/
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Curve))
return false;
Curve c = (Curve) o;
return f.equals(c.getField()) &&
d.equals(c.getD()) &&
I.equals(c.getI());
}
}

View File

@@ -1,11 +1,17 @@
package net.i2p.crypto.eddsa.math;
import java.io.Serializable;
/**
*
* Note: concrete subclasses must implement hashCode() and equals()
*
* @since 0.9.15
*
*/
public abstract class FieldElement {
public abstract class FieldElement implements Serializable {
private static final long serialVersionUID = 1239527465875676L;
protected final Field f;
public FieldElement(Field f) {
@@ -56,4 +62,6 @@ public abstract class FieldElement {
public abstract FieldElement invert();
public abstract FieldElement pow22523();
// Note: concrete subclasses must implement hashCode() and equals()
}

View File

@@ -716,13 +716,15 @@ public class GroupElement implements Serializable {
@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
if (!(obj instanceof GroupElement))
return false;
GroupElement ge = (GroupElement) obj;
if (!this.repr.equals(ge.repr)) {
try {
ge = ge.toRep(this.repr);
} catch (Exception e) {
} catch (RuntimeException e) {
return false;
}
}

View File

@@ -15,7 +15,7 @@ public class BigIntegerLittleEndianEncoding extends Encoding implements Serializ
private BigInteger mask;
@Override
public void setField(Field f) {
public synchronized void setField(Field f) {
super.setField(f);
mask = BigInteger.ONE.shiftLeft(f.getb()-1).subtract(BigInteger.ONE);
}

View File

@@ -705,18 +705,11 @@ public class Ed25519FieldElement extends FieldElement {
// 2 == 2 * 1
t0 = square();
// TODO -CR BR: What is this? Is the author superstitious?
for (int i = 1; i < 1; ++i) { // Don't remove this
t0 = t0.square();
}
// 4 == 2 * 2
t1 = t0.square();
// 8 == 2 * 4
for (int i = 1; i < 2; ++i) {
t1 = t1.square();
}
t1 = t1.square();
// 9 == 8 + 1
t1 = multiply(t1);
@@ -727,11 +720,6 @@ public class Ed25519FieldElement extends FieldElement {
// 22 == 2 * 11
t2 = t0.square();
// TODO -CR BR: see above
for (int i = 1; i < 1; ++i) { // Don't remove this
t2 = t2.square();
}
// 31 == 22 + 9
t1 = t1.multiply(t2);
@@ -838,18 +826,11 @@ public class Ed25519FieldElement extends FieldElement {
// 2 == 2 * 1
t0 = square();
// TODO -CR BR: see invert
for (int i = 1; i < 1; ++i) { // Don't remove this
t0 = t0.square();
}
// 4 == 2 * 2
t1 = t0.square();
// 8 == 2 * 4
for (int i = 1; i < 2; ++i) {
t1 = t1.square();
}
t1 = t1.square();
// z9 = z1*z8
t1 = multiply(t1);
@@ -860,11 +841,6 @@ public class Ed25519FieldElement extends FieldElement {
// 22 == 2 * 11
t0 = t0.square();
// TODO -CR BR: see above
for (int i = 1; i < 1; ++i) { // Don't remove this
t0 = t0.square();
}
// 31 == 22 + 9
t0 = t1.multiply(t0);
@@ -949,9 +925,7 @@ public class Ed25519FieldElement extends FieldElement {
t0 = t0.square();
// 2^252 - 2^2
for (int i = 1; i < 2; ++i) {
t0 = t0.square();
}
t0 = t0.square();
// 2^252 - 3
return multiply(t0);

View File

@@ -70,7 +70,7 @@ public class Ed25519LittleEndianEncoding extends Encoding {
// Step 1:
// Calculate q
q = (19 * h9 + (((int) 1) << 24)) >> 25;
q = (19 * h9 + (1 << 24)) >> 25;
q = (h0 + q) >> 26;
q = (h1 + q) >> 25;
q = (h2 + q) >> 26;

View File

@@ -59,4 +59,29 @@ public class EdDSAParameterSpec implements AlgorithmParameterSpec, Serializable
public GroupElement getB() {
return B;
}
/**
* @since 0.9.25
*/
@Override
public int hashCode() {
return hashAlgo.hashCode() ^
curve.hashCode() ^
B.hashCode();
}
/**
* @since 0.9.25
*/
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof EdDSAParameterSpec))
return false;
EdDSAParameterSpec s = (EdDSAParameterSpec) o;
return hashAlgo.equals(s.getHashAlgorithm()) &&
curve.equals(s.getCurve()) &&
B.equals(s.getB());
}
}

View File

@@ -0,0 +1,11 @@
package net.i2p.crypto.elgamal;
import javax.crypto.interfaces.DHKey;
import net.i2p.crypto.elgamal.spec.ElGamalParameterSpec;
public interface ElGamalKey
extends DHKey
{
public ElGamalParameterSpec getParameters();
}

View File

@@ -0,0 +1,11 @@
package net.i2p.crypto.elgamal;
import java.math.BigInteger;
import javax.crypto.interfaces.DHPrivateKey;
public interface ElGamalPrivateKey
extends ElGamalKey, DHPrivateKey
{
public BigInteger getX();
}

View File

@@ -0,0 +1,11 @@
package net.i2p.crypto.elgamal;
import java.math.BigInteger;
import javax.crypto.interfaces.DHPublicKey;
public interface ElGamalPublicKey
extends ElGamalKey, DHPublicKey
{
public BigInteger getY();
}

Some files were not shown because too many files have changed in this diff Show More