forked from I2P_Developers/i2p.i2p
propagate from branch 'i2p.i2p.zzz.gmp6-prop' (head 416ef26df4b91fb9de3e27623551c7f87ec2bfe0)
to branch 'i2p.i2p' (head 9466fdeae338d6b0bf049d86975db9b6ddbd3064)
This commit is contained in:
@@ -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);
|
||||
|
||||
7
core/java/src/com/nettgryppa/security/package.html
Normal file
7
core/java/src/com/nettgryppa/security/package.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
HashCash implementation. Unused.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
7
core/java/src/gnu/crypto/prng/package.html
Normal file
7
core/java/src/gnu/crypto/prng/package.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
The Fortuna PRNG from GNU Crypto, updated and modifed for I2P.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
31
core/java/src/gnu/getopt/MessagesBundle_fi.properties
Normal file
31
core/java/src/gnu/getopt/MessagesBundle_fi.properties
Normal 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'
|
||||
31
core/java/src/gnu/getopt/MessagesBundle_in.properties
Normal file
31
core/java/src/gnu/getopt/MessagesBundle_in.properties
Normal 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'
|
||||
31
core/java/src/gnu/getopt/MessagesBundle_ko.properties
Normal file
31
core/java/src/gnu/getopt/MessagesBundle_ko.properties
Normal 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}
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
31
core/java/src/gnu/getopt/MessagesBundle_uk.properties
Normal file
31
core/java/src/gnu/getopt/MessagesBundle_uk.properties
Normal 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'
|
||||
8
core/java/src/gnu/gettext/package.html
Normal file
8
core/java/src/gnu/gettext/package.html
Normal 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>
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.i2p.client;
|
||||
package net.i2p.client.impl;
|
||||
|
||||
/*
|
||||
* Released into the public domain
|
||||
@@ -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;
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.i2p.client;
|
||||
package net.i2p.client.impl;
|
||||
|
||||
/*
|
||||
* Released into the public domain
|
||||
@@ -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;
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.i2p.client;
|
||||
package net.i2p.client.impl;
|
||||
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.i2p.client;
|
||||
package net.i2p.client.impl;
|
||||
|
||||
/*
|
||||
* Released into the public domain
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.i2p.client;
|
||||
package net.i2p.client.impl;
|
||||
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.i2p.client;
|
||||
package net.i2p.client.impl;
|
||||
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
@@ -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) {
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.i2p.client;
|
||||
package net.i2p.client.impl;
|
||||
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.i2p.client;
|
||||
package net.i2p.client.impl;
|
||||
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
347
core/java/src/net/i2p/client/impl/SubSession.java
Normal file
347
core/java/src/net/i2p/client/impl/SubSession.java
Normal 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();
|
||||
}
|
||||
}
|
||||
11
core/java/src/net/i2p/client/impl/package.html
Normal file
11
core/java/src/net/i2p/client/impl/package.html
Normal 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
@@ -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)
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 {
|
||||
|
||||
534
core/java/src/net/i2p/client/naming/HostTxtEntry.java
Normal file
534
core/java/src/net/i2p/client/naming/HostTxtEntry.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
****/
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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. */
|
||||
|
||||
47
core/java/src/net/i2p/crypto/CryptoCheck.java
Normal file
47
core/java/src/net/i2p/crypto/CryptoCheck.java
Normal 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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
22
core/java/src/net/i2p/crypto/EncAlgo.java
Normal file
22
core/java/src/net/i2p/crypto/EncAlgo.java
Normal 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; }
|
||||
}
|
||||
171
core/java/src/net/i2p/crypto/EncType.java
Normal file
171
core/java/src/net/i2p/crypto/EncType.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
****/
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import net.i2p.util.NativeBigInteger;
|
||||
*
|
||||
* @since 0.9.9
|
||||
*/
|
||||
class RSAConstants {
|
||||
final class RSAConstants {
|
||||
|
||||
/**
|
||||
* Generate a spec
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
605
core/java/src/net/i2p/crypto/SelfSignedGenerator.java
Normal file
605
core/java/src/net/i2p/crypto/SelfSignedGenerator.java
Normal 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);
|
||||
}
|
||||
****/
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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)">
|
||||
*/
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
11
core/java/src/net/i2p/crypto/elgamal/ElGamalKey.java
Normal file
11
core/java/src/net/i2p/crypto/elgamal/ElGamalKey.java
Normal 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();
|
||||
}
|
||||
11
core/java/src/net/i2p/crypto/elgamal/ElGamalPrivateKey.java
Normal file
11
core/java/src/net/i2p/crypto/elgamal/ElGamalPrivateKey.java
Normal 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();
|
||||
}
|
||||
11
core/java/src/net/i2p/crypto/elgamal/ElGamalPublicKey.java
Normal file
11
core/java/src/net/i2p/crypto/elgamal/ElGamalPublicKey.java
Normal 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
Reference in New Issue
Block a user