forked from I2P_Developers/i2p.i2p
propagate from branch 'i2p.i2p.zzz.test2' (head 70ae5494bd7255a03f80838a2f3d8e7c0ce86634)
to branch 'i2p.i2p' (head 05a201cc5c1bd841f32e9268b3019b3a3447f4f3)
This commit is contained in:
@@ -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
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -9,12 +9,21 @@ 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.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.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.naming.InvalidNameException;
|
||||
@@ -24,6 +33,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;
|
||||
@@ -33,7 +43,7 @@ import net.i2p.util.SystemVersion;
|
||||
*
|
||||
* @since 0.9.9
|
||||
*/
|
||||
public class CertUtil {
|
||||
public final class CertUtil {
|
||||
|
||||
private static final int LINE_LENGTH = 64;
|
||||
|
||||
@@ -93,16 +103,7 @@ public class CertUtil {
|
||||
throws IOException, CertificateEncodingException {
|
||||
// Get the encoded form which is suitable for exporting
|
||||
byte[] buf = cert.getEncoded();
|
||||
PrintWriter wr = new PrintWriter(new OutputStreamWriter(out, "UTF-8"));
|
||||
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();
|
||||
if (wr.checkError())
|
||||
throw new IOException("Failed write to " + out);
|
||||
writePEM(buf, "CERTIFICATE", out);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,13 +122,27 @@ public class CertUtil {
|
||||
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 PRIVATE KEY-----");
|
||||
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 PRIVATE KEY-----");
|
||||
wr.println("-----END " + what + "-----");
|
||||
wr.flush();
|
||||
if (wr.checkError())
|
||||
throw new IOException("Failed write to " + out);
|
||||
@@ -235,4 +250,145 @@ public class CertUtil {
|
||||
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.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
private static void exportCRL(X509CRL crl, OutputStream out)
|
||||
throws IOException, CRLException {
|
||||
byte[] buf = crl.getEncoded();
|
||||
writePEM(buf, "X509 CRL", out);
|
||||
}
|
||||
|
||||
/****
|
||||
public static final void main(String[] args) {
|
||||
if (args.length < 2) {
|
||||
System.out.println("Usage: [loadcert | loadcrl | 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")) {
|
||||
} else {
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
****/
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ 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();
|
||||
// keys are now cached in the SessionKey objects
|
||||
//private CryptixAESKeyCache _cache;
|
||||
|
||||
@@ -34,6 +34,7 @@ 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;
|
||||
|
||||
/**
|
||||
@@ -43,7 +44,7 @@ import net.i2p.util.NativeBigInteger;
|
||||
* See also: ECConstants, RSAConstants
|
||||
*
|
||||
*/
|
||||
public class CryptoConstants {
|
||||
public final class CryptoConstants {
|
||||
public static final BigInteger dsap = new NativeBigInteger(
|
||||
"9c05b2aa960d9b97b8931963c9cc9e8c3026e9b8ed92fad0a69cc886d5bf8015fcadae31"
|
||||
+ "a0ad18fab3f01b00a358de237655c4964afaa2b337e96ad316b9fb1cc564b5aec5b69a9f"
|
||||
@@ -78,6 +79,11 @@ public class CryptoConstants {
|
||||
*/
|
||||
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
|
||||
@@ -98,11 +104,11 @@ public class CryptoConstants {
|
||||
} catch (Exception e) {
|
||||
//System.out.println("BC ElG spec failed");
|
||||
//e.printStackTrace();
|
||||
spec = new ElGamalParameterSpec(elgp, elgg);
|
||||
spec = I2P_ELGAMAL_2048_SPEC;
|
||||
}
|
||||
} else {
|
||||
//System.out.println("BC not available");
|
||||
spec = new ElGamalParameterSpec(elgp, elgg);
|
||||
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;
|
||||
|
||||
@@ -234,7 +234,7 @@ public class DSAEngine {
|
||||
BigInteger s = new NativeBigInteger(1, sbytes);
|
||||
BigInteger r = new NativeBigInteger(1, rbytes);
|
||||
BigInteger y = new NativeBigInteger(1, verifyingKey.getData());
|
||||
BigInteger w = null;
|
||||
BigInteger w;
|
||||
try {
|
||||
w = s.modInverse(CryptoConstants.dsaq);
|
||||
} catch (ArithmeticException ae) {
|
||||
@@ -402,8 +402,7 @@ public class DSAEngine {
|
||||
long start = _context.clock().now();
|
||||
|
||||
BigInteger k;
|
||||
|
||||
boolean ok = false;
|
||||
boolean ok;
|
||||
do {
|
||||
k = new BigInteger(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(type.getAlgorithmName());
|
||||
jsig.initSign(privKey, _context.random());
|
||||
jsig.update(hash.getData());
|
||||
sigbytes = jsig.sign();
|
||||
}
|
||||
return SigUtil.fromJavaSig(sigbytes, type);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,7 +20,7 @@ import net.i2p.util.NativeBigInteger;
|
||||
*
|
||||
* @since 0.9.9
|
||||
*/
|
||||
class ECConstants {
|
||||
final class ECConstants {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
package net.i2p.crypto;
|
||||
|
||||
/*
|
||||
* Copyright (c) 2000 - 2013 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
* and associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
|
||||
* is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software.
|
||||
*
|
||||
*THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
|
||||
* AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
|
||||
/**
|
||||
* Copied from org.bouncycastle.jce.spec
|
||||
* This can't actually be passed to the BC provider, we would have to
|
||||
* use reflection to create a "real" org.bouncycasle.jce.spec.ElGamalParameterSpec.
|
||||
*
|
||||
* @since 0.9.18
|
||||
*/
|
||||
public class ElGamalParameterSpec implements AlgorithmParameterSpec {
|
||||
private final BigInteger p;
|
||||
private final BigInteger g;
|
||||
|
||||
/**
|
||||
* Constructs a parameter set for Diffie-Hellman, using a prime modulus
|
||||
* <code>p</code> and a base generator <code>g</code>.
|
||||
*
|
||||
* @param p the prime modulus
|
||||
* @param g the base generator
|
||||
*/
|
||||
public ElGamalParameterSpec(BigInteger p, BigInteger g) {
|
||||
this.p = p;
|
||||
this.g = g;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the prime modulus <code>p</code>.
|
||||
*
|
||||
* @return the prime modulus <code>p</code>
|
||||
*/
|
||||
public BigInteger getP() {
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base generator <code>g</code>.
|
||||
*
|
||||
* @return the base generator <code>g</code>
|
||||
*/
|
||||
public BigInteger getG() {
|
||||
return g;
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ public enum EncType {
|
||||
* This is the default.
|
||||
* Pubkey 256 bytes, privkey 256 bytes.
|
||||
*/
|
||||
ELGAMAL_2048(0, 256, 256, EncAlgo.ELGAMAL, "ElGamal/None/NoPadding", CryptoConstants.ELGAMAL_2048_SPEC, "0"),
|
||||
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"),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -34,6 +34,7 @@ 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;
|
||||
@@ -55,9 +56,13 @@ import net.i2p.util.RandomSource;
|
||||
/** Define a way of generating asymmetrical key pairs as well as symmetrical keys
|
||||
* @author jrandom
|
||||
*/
|
||||
public class KeyGenerator {
|
||||
public final class KeyGenerator {
|
||||
private final I2PAppContext _context;
|
||||
|
||||
static {
|
||||
I2PProvider.addProvider();
|
||||
}
|
||||
|
||||
public KeyGenerator(I2PAppContext context) {
|
||||
_context = context;
|
||||
}
|
||||
@@ -208,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();
|
||||
|
||||
@@ -13,10 +13,13 @@ import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateExpiredException;
|
||||
import java.security.cert.CertificateNotYetValidException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
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,7 +32,7 @@ import net.i2p.util.SystemVersion;
|
||||
*
|
||||
* @since 0.9.9
|
||||
*/
|
||||
public class KeyStoreUtil {
|
||||
public final class KeyStoreUtil {
|
||||
|
||||
public static boolean _blacklistLogged;
|
||||
|
||||
@@ -38,6 +41,10 @@ public class KeyStoreUtil {
|
||||
private static final int DEFAULT_KEY_SIZE = 2048;
|
||||
private static final int DEFAULT_KEY_VALID_DAYS = 3652; // 10 years
|
||||
|
||||
static {
|
||||
I2PProvider.addProvider();
|
||||
}
|
||||
|
||||
/**
|
||||
* No reports of some of these in a Java keystore but just to be safe...
|
||||
* CNNIC ones are in Ubuntu keystore.
|
||||
@@ -464,20 +471,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) {
|
||||
@@ -514,6 +530,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)
|
||||
@@ -522,6 +540,8 @@ public class KeyStoreUtil {
|
||||
hash = "SHA384";
|
||||
else
|
||||
hash = "SHA512";
|
||||
} else if (keyalg.equals("EdDSA")) {
|
||||
hash = "SHA512";
|
||||
} else {
|
||||
if (size <= 1024)
|
||||
hash = "SHA1";
|
||||
@@ -559,6 +579,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
|
||||
*
|
||||
@@ -641,11 +758,31 @@ public class KeyStoreUtil {
|
||||
* 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) {
|
||||
File ksf = (args.length > 0) ? new File(args[0]) : null;
|
||||
try {
|
||||
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);
|
||||
@@ -674,5 +811,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,5 @@
|
||||
package net.i2p.crypto;
|
||||
|
||||
import gnu.crypto.hash.Sha256Standalone;
|
||||
|
||||
import java.security.DigestException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
@@ -14,25 +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
|
||||
*/
|
||||
@@ -96,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
|
||||
@@ -32,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
|
||||
@@ -55,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....................
|
||||
|
||||
@@ -99,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;
|
||||
@@ -114,6 +123,7 @@ public enum SigType {
|
||||
digestName = mdName;
|
||||
algoName = aName;
|
||||
params = pSpec;
|
||||
this.oid = oid;
|
||||
since = supportedSince;
|
||||
isAvail = x_isAvailable();
|
||||
}
|
||||
@@ -183,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
|
||||
@@ -274,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 {
|
||||
|
||||
@@ -50,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);
|
||||
@@ -141,7 +141,7 @@ public class SigUtil {
|
||||
throw new IllegalArgumentException("Unknown RSA type");
|
||||
return fromJavaKey(k, type);
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown type");
|
||||
throw new IllegalArgumentException("Unknown type: " + pk.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,7 +161,7 @@ public class SigUtil {
|
||||
case RSA:
|
||||
return fromJavaKey((RSAPublicKey) pk, type);
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
throw new IllegalArgumentException("Unknown type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ public class SigUtil {
|
||||
throw new IllegalArgumentException("Unknown RSA type");
|
||||
return fromJavaKey(k, type);
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown type");
|
||||
throw new IllegalArgumentException("Unknown type: " + pk.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,7 +229,7 @@ public class SigUtil {
|
||||
case RSA:
|
||||
return fromJavaKey((RSAPrivateKey) pk, type);
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
throw new IllegalArgumentException("Unknown type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,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];
|
||||
@@ -565,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);
|
||||
@@ -609,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
|
||||
@@ -619,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.
|
||||
@@ -626,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));
|
||||
@@ -673,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)
|
||||
@@ -693,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)
|
||||
@@ -704,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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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_MOE)
|
||||
*</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
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package net.i2p.crypto.eddsa.math;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
*
|
||||
* Note: concrete subclasses must implement hashCode() and equals()
|
||||
*
|
||||
* @since 0.9.15
|
||||
*
|
||||
@@ -60,4 +62,6 @@ public abstract class FieldElement implements Serializable {
|
||||
public abstract FieldElement invert();
|
||||
|
||||
public abstract FieldElement pow22523();
|
||||
|
||||
// Note: concrete subclasses must implement hashCode() and equals()
|
||||
}
|
||||
|
||||
@@ -716,6 +716,8 @@ 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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
164
core/java/src/net/i2p/crypto/elgamal/ElGamalSigEngine.java
Normal file
164
core/java/src/net/i2p/crypto/elgamal/ElGamalSigEngine.java
Normal file
@@ -0,0 +1,164 @@
|
||||
package net.i2p.crypto.elgamal;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import net.i2p.crypto.SHA256Generator;
|
||||
import net.i2p.crypto.SigUtil;
|
||||
import net.i2p.util.NativeBigInteger;
|
||||
import net.i2p.util.RandomSource;
|
||||
|
||||
/**
|
||||
* ElG signatures with SHA-256
|
||||
*
|
||||
* ref: https://en.wikipedia.org/wiki/ElGamal_signature_scheme
|
||||
*
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public final class ElGamalSigEngine extends Signature {
|
||||
private final MessageDigest digest;
|
||||
private ElGamalKey key;
|
||||
|
||||
/**
|
||||
* No specific hash requested, allows any ElGamal key.
|
||||
*/
|
||||
public ElGamalSigEngine() {
|
||||
this(SHA256Generator.getDigestInstance());
|
||||
}
|
||||
|
||||
/**
|
||||
* Specific hash requested, only matching keys will be allowed.
|
||||
* @param digest the hash algorithm that keys must have to sign or verify.
|
||||
*/
|
||||
public ElGamalSigEngine(MessageDigest digest) {
|
||||
super("ElGamal");
|
||||
this.digest = digest;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException {
|
||||
digest.reset();
|
||||
if (privateKey instanceof ElGamalPrivateKey) {
|
||||
ElGamalPrivateKey privKey = (ElGamalPrivateKey) privateKey;
|
||||
key = privKey;
|
||||
} else {
|
||||
throw new InvalidKeyException("cannot identify ElGamal private key: " + privateKey.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {
|
||||
digest.reset();
|
||||
if (publicKey instanceof ElGamalPublicKey) {
|
||||
key = (ElGamalPublicKey) publicKey;
|
||||
} else {
|
||||
throw new InvalidKeyException("cannot identify ElGamal public key: " + publicKey.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineUpdate(byte b) throws SignatureException {
|
||||
digest.update(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineUpdate(byte[] b, int off, int len)
|
||||
throws SignatureException {
|
||||
digest.update(b, off, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ASN.1 R,S
|
||||
*/
|
||||
@Override
|
||||
protected byte[] engineSign() throws SignatureException {
|
||||
BigInteger elgp = key.getParams().getP();
|
||||
BigInteger pm1 = elgp.subtract(BigInteger.ONE);
|
||||
BigInteger elgg = key.getParams().getG();
|
||||
BigInteger x = ((ElGamalPrivateKey) key).getX();
|
||||
if (!(x instanceof NativeBigInteger))
|
||||
x = new NativeBigInteger(x);
|
||||
byte[] data = digest.digest();
|
||||
|
||||
BigInteger k;
|
||||
boolean ok;
|
||||
do {
|
||||
k = new BigInteger(2048, RandomSource.getInstance());
|
||||
ok = k.compareTo(pm1) == -1;
|
||||
ok = ok && k.compareTo(BigInteger.ONE) == 1;
|
||||
ok = ok && k.gcd(pm1).equals(BigInteger.ONE);
|
||||
} while (!ok);
|
||||
|
||||
BigInteger r = elgg.modPow(k, elgp);
|
||||
BigInteger kinv = k.modInverse(pm1);
|
||||
BigInteger h = new NativeBigInteger(1, data);
|
||||
BigInteger s = (kinv.multiply(h.subtract(x.multiply(r)))).mod(pm1);
|
||||
// todo if s == 0 go around again
|
||||
|
||||
byte[] rv;
|
||||
try {
|
||||
rv = SigUtil.sigBytesToASN1(r, s);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
throw new SignatureException("ASN1", iae);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sigBytes ASN.1 R,S
|
||||
*/
|
||||
@Override
|
||||
protected boolean engineVerify(byte[] sigBytes) throws SignatureException {
|
||||
BigInteger elgp = key.getParams().getP();
|
||||
BigInteger pm1 = elgp.subtract(BigInteger.ONE);
|
||||
BigInteger elgg = key.getParams().getG();
|
||||
BigInteger y = ((ElGamalPublicKey) key).getY();
|
||||
if (!(y instanceof NativeBigInteger))
|
||||
y = new NativeBigInteger(y);
|
||||
byte[] data = digest.digest();
|
||||
|
||||
try {
|
||||
BigInteger[] rs = SigUtil.aSN1ToBigInteger(sigBytes, 256);
|
||||
BigInteger r = rs[0];
|
||||
BigInteger s = rs[1];
|
||||
if (r.signum() != 1 || s.signum() != 1 ||
|
||||
r.compareTo(elgp) != -1 || s.compareTo(pm1) != -1)
|
||||
return false;
|
||||
NativeBigInteger h = new NativeBigInteger(1, data);
|
||||
BigInteger modvalr = r.modPow(s, elgp);
|
||||
BigInteger modvaly = y.modPow(r, elgp);
|
||||
BigInteger modmulval = modvalr.multiply(modvaly).mod(elgp);
|
||||
BigInteger v = elgg.modPow(h, elgp);
|
||||
|
||||
boolean ok = v.compareTo(modmulval) == 0;
|
||||
return ok;
|
||||
} catch (RuntimeException e) {
|
||||
throw new SignatureException("verify", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated replaced with <a href="#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
|
||||
*/
|
||||
@Override
|
||||
protected void engineSetParameter(String param, Object value) {
|
||||
throw new UnsupportedOperationException("engineSetParameter unsupported");
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Override
|
||||
protected Object engineGetParameter(String param) {
|
||||
throw new UnsupportedOperationException("engineSetParameter unsupported");
|
||||
}
|
||||
}
|
||||
80
core/java/src/net/i2p/crypto/elgamal/KeyFactory.java
Normal file
80
core/java/src/net/i2p/crypto/elgamal/KeyFactory.java
Normal file
@@ -0,0 +1,80 @@
|
||||
package net.i2p.crypto.elgamal;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyFactorySpi;
|
||||
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 javax.crypto.spec.DHParameterSpec;
|
||||
|
||||
import static net.i2p.crypto.CryptoConstants.I2P_ELGAMAL_2048_SPEC;
|
||||
import net.i2p.crypto.elgamal.impl.ElGamalPrivateKeyImpl;
|
||||
import net.i2p.crypto.elgamal.impl.ElGamalPrivateKeyImpl;
|
||||
import net.i2p.crypto.elgamal.impl.ElGamalPublicKeyImpl;
|
||||
import net.i2p.crypto.elgamal.spec.ElGamalParameterSpec;
|
||||
import net.i2p.crypto.elgamal.spec.ElGamalPrivateKeySpec;
|
||||
import net.i2p.crypto.elgamal.spec.ElGamalPublicKeySpec;
|
||||
|
||||
/**
|
||||
* Modified from eddsa
|
||||
*
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public final class KeyFactory extends KeyFactorySpi {
|
||||
|
||||
/**
|
||||
* Supports PKCS8EncodedKeySpec
|
||||
*/
|
||||
protected PrivateKey engineGeneratePrivate(KeySpec keySpec)
|
||||
throws InvalidKeySpecException {
|
||||
if (keySpec instanceof ElGamalPrivateKeySpec) {
|
||||
return new ElGamalPrivateKeyImpl((ElGamalPrivateKeySpec) keySpec);
|
||||
}
|
||||
if (keySpec instanceof PKCS8EncodedKeySpec) {
|
||||
return new ElGamalPrivateKeyImpl((PKCS8EncodedKeySpec) keySpec);
|
||||
}
|
||||
throw new InvalidKeySpecException("key spec not recognised");
|
||||
}
|
||||
|
||||
/**
|
||||
* Supports X509EncodedKeySpec
|
||||
*/
|
||||
protected PublicKey engineGeneratePublic(KeySpec keySpec)
|
||||
throws InvalidKeySpecException {
|
||||
if (keySpec instanceof ElGamalPublicKeySpec) {
|
||||
return new ElGamalPublicKeyImpl((ElGamalPublicKeySpec) keySpec);
|
||||
}
|
||||
if (keySpec instanceof X509EncodedKeySpec) {
|
||||
return new ElGamalPublicKeyImpl((X509EncodedKeySpec) keySpec);
|
||||
}
|
||||
throw new InvalidKeySpecException("key spec not recognised");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpec)
|
||||
throws InvalidKeySpecException {
|
||||
if (keySpec.isAssignableFrom(ElGamalPublicKeySpec.class) && key instanceof ElGamalPublicKey) {
|
||||
ElGamalPublicKey k = (ElGamalPublicKey) key;
|
||||
ElGamalParameterSpec egp = k.getParameters();
|
||||
if (egp != null) {
|
||||
return (T) new ElGamalPrivateKeySpec(k.getY(), egp);
|
||||
}
|
||||
} else if (keySpec.isAssignableFrom(ElGamalPrivateKeySpec.class) && key instanceof ElGamalPrivateKey) {
|
||||
ElGamalPrivateKey k = (ElGamalPrivateKey) key;
|
||||
ElGamalParameterSpec egp = k.getParameters();
|
||||
if (egp != null) {
|
||||
return (T) new ElGamalPrivateKeySpec(k.getX(), egp);
|
||||
}
|
||||
}
|
||||
throw new InvalidKeySpecException("not implemented yet " + key + " " + keySpec);
|
||||
}
|
||||
|
||||
protected Key engineTranslateKey(Key key) throws InvalidKeyException {
|
||||
throw new InvalidKeyException("No other ElGamal key providers known");
|
||||
}
|
||||
}
|
||||
84
core/java/src/net/i2p/crypto/elgamal/KeyPairGenerator.java
Normal file
84
core/java/src/net/i2p/crypto/elgamal/KeyPairGenerator.java
Normal file
@@ -0,0 +1,84 @@
|
||||
package net.i2p.crypto.elgamal;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidParameterException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGeneratorSpi;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
|
||||
import static net.i2p.crypto.CryptoConstants.I2P_ELGAMAL_2048_SPEC;
|
||||
import net.i2p.crypto.KeyGenerator;
|
||||
import net.i2p.crypto.elgamal.impl.ElGamalPrivateKeyImpl;
|
||||
import net.i2p.crypto.elgamal.impl.ElGamalPublicKeyImpl;
|
||||
import net.i2p.crypto.elgamal.spec.ElGamalGenParameterSpec;
|
||||
import net.i2p.crypto.elgamal.spec.ElGamalParameterSpec;
|
||||
import net.i2p.crypto.elgamal.spec.ElGamalPrivateKeySpec;
|
||||
import net.i2p.crypto.elgamal.spec.ElGamalPublicKeySpec;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SimpleDataStructure;
|
||||
import net.i2p.util.NativeBigInteger;
|
||||
import net.i2p.util.RandomSource;
|
||||
|
||||
/**
|
||||
* Modified from eddsa
|
||||
* Only supported strength is 2048
|
||||
*
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public final class KeyPairGenerator extends KeyPairGeneratorSpi {
|
||||
// always long, don't use short key
|
||||
private static final int DEFAULT_STRENGTH = 2048;
|
||||
private ElGamalParameterSpec elgParams;
|
||||
//private SecureRandom random;
|
||||
private boolean initialized;
|
||||
|
||||
/**
|
||||
* @param strength must be 2048
|
||||
* @param random ignored
|
||||
*/
|
||||
public void initialize(int strength, SecureRandom random) {
|
||||
if (strength != DEFAULT_STRENGTH)
|
||||
throw new InvalidParameterException("unknown key type.");
|
||||
elgParams = I2P_ELGAMAL_2048_SPEC;
|
||||
try {
|
||||
initialize(elgParams, random);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
throw new InvalidParameterException("key type not configurable.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param random ignored
|
||||
*/
|
||||
@Override
|
||||
public void initialize(AlgorithmParameterSpec params, SecureRandom random) throws InvalidAlgorithmParameterException {
|
||||
if (params instanceof ElGamalParameterSpec) {
|
||||
elgParams = (ElGamalParameterSpec) params;
|
||||
if (!elgParams.equals(I2P_ELGAMAL_2048_SPEC))
|
||||
throw new InvalidAlgorithmParameterException("unsupported ElGamalParameterSpec");
|
||||
} else if (params instanceof ElGamalGenParameterSpec) {
|
||||
ElGamalGenParameterSpec elgGPS = (ElGamalGenParameterSpec) params;
|
||||
if (elgGPS.getPrimeSize() != DEFAULT_STRENGTH)
|
||||
throw new InvalidAlgorithmParameterException("unsupported prime size");
|
||||
elgParams = I2P_ELGAMAL_2048_SPEC;
|
||||
} else {
|
||||
throw new InvalidAlgorithmParameterException("parameter object not a ElGamalParameterSpec");
|
||||
}
|
||||
//this.random = random;
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
public KeyPair generateKeyPair() {
|
||||
if (!initialized)
|
||||
initialize(DEFAULT_STRENGTH, RandomSource.getInstance());
|
||||
KeyGenerator kg = KeyGenerator.getInstance();
|
||||
SimpleDataStructure[] keys = kg.generatePKIKeys();
|
||||
PublicKey pubKey = (PublicKey) keys[0];
|
||||
PrivateKey privKey = (PrivateKey) keys[1];
|
||||
ElGamalPublicKey epubKey = new ElGamalPublicKeyImpl(new NativeBigInteger(1, pubKey.getData()), elgParams);
|
||||
ElGamalPrivateKey eprivKey = new ElGamalPrivateKeyImpl(new NativeBigInteger(1, privKey.getData()), elgParams);
|
||||
return new KeyPair(epubKey, eprivKey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package net.i2p.crypto.elgamal.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
|
||||
import javax.crypto.interfaces.DHPrivateKey;
|
||||
import javax.crypto.spec.DHParameterSpec;
|
||||
import javax.crypto.spec.DHPrivateKeySpec;
|
||||
|
||||
import static net.i2p.crypto.SigUtil.intToASN1;
|
||||
import net.i2p.crypto.elgamal.ElGamalPrivateKey;
|
||||
import static net.i2p.crypto.elgamal.impl.ElGamalPublicKeyImpl.spaceFor;
|
||||
import net.i2p.crypto.elgamal.spec.ElGamalParameterSpec;
|
||||
import net.i2p.crypto.elgamal.spec.ElGamalPrivateKeySpec;
|
||||
|
||||
public class ElGamalPrivateKeyImpl
|
||||
implements ElGamalPrivateKey, DHPrivateKey
|
||||
{
|
||||
private static final long serialVersionUID = 4819350091141529678L;
|
||||
|
||||
private BigInteger x;
|
||||
private ElGamalParameterSpec elSpec;
|
||||
|
||||
protected ElGamalPrivateKeyImpl()
|
||||
{
|
||||
}
|
||||
|
||||
public ElGamalPrivateKeyImpl(
|
||||
ElGamalPrivateKey key)
|
||||
{
|
||||
this.x = key.getX();
|
||||
this.elSpec = key.getParameters();
|
||||
}
|
||||
|
||||
public ElGamalPrivateKeyImpl(
|
||||
DHPrivateKey key)
|
||||
{
|
||||
this.x = key.getX();
|
||||
this.elSpec = new ElGamalParameterSpec(key.getParams().getP(), key.getParams().getG());
|
||||
}
|
||||
|
||||
public ElGamalPrivateKeyImpl(
|
||||
ElGamalPrivateKeySpec spec)
|
||||
{
|
||||
this.x = spec.getX();
|
||||
this.elSpec = new ElGamalParameterSpec(spec.getParams().getP(), spec.getParams().getG());
|
||||
}
|
||||
|
||||
public ElGamalPrivateKeyImpl(
|
||||
DHPrivateKeySpec spec)
|
||||
{
|
||||
this.x = spec.getX();
|
||||
this.elSpec = new ElGamalParameterSpec(spec.getP(), spec.getG());
|
||||
}
|
||||
|
||||
public ElGamalPrivateKeyImpl(
|
||||
BigInteger x,
|
||||
ElGamalParameterSpec elSpec)
|
||||
{
|
||||
this.x = x;
|
||||
this.elSpec = elSpec;
|
||||
}
|
||||
|
||||
public ElGamalPrivateKeyImpl(
|
||||
PKCS8EncodedKeySpec spec)
|
||||
{
|
||||
throw new UnsupportedOperationException("todo");
|
||||
//this.x = spec.getX();
|
||||
//this.elSpec = new ElGamalParameterSpec(spec.getP(), spec.getG());
|
||||
}
|
||||
|
||||
public String getAlgorithm()
|
||||
{
|
||||
return "ElGamal";
|
||||
}
|
||||
|
||||
/**
|
||||
* return the encoding format we produce in getEncoded().
|
||||
*
|
||||
* @return the string "PKCS#8"
|
||||
*/
|
||||
public String getFormat()
|
||||
{
|
||||
return "PKCS#8";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a PKCS8 representation of the key. The sequence returned
|
||||
* represents a full PrivateKeyInfo object.
|
||||
*
|
||||
* @return a PKCS8 representation of the key.
|
||||
*/
|
||||
public byte[] getEncoded()
|
||||
{
|
||||
byte[] pb = elSpec.getP().toByteArray();
|
||||
byte[] gb = elSpec.getG().toByteArray();
|
||||
byte[] xb = x.toByteArray();
|
||||
int seq3len = spaceFor(pb.length) + spaceFor(gb.length);
|
||||
int seq2len = 8 + spaceFor(seq3len);
|
||||
int seq1len = 3 + spaceFor(seq2len) + spaceFor(xb.length);
|
||||
int totlen = spaceFor(seq1len);
|
||||
byte[] rv = new byte[totlen];
|
||||
int idx = 0;
|
||||
// sequence 1
|
||||
rv[idx++] = 0x30;
|
||||
idx = intToASN1(rv, idx, seq1len);
|
||||
|
||||
// version
|
||||
rv[idx++] = 0x02;
|
||||
rv[idx++] = 1;
|
||||
rv[idx++] = 0;
|
||||
|
||||
// Algorithm Identifier
|
||||
// sequence 2
|
||||
rv[idx++] = 0x30;
|
||||
idx = intToASN1(rv, idx, seq2len);
|
||||
// OID: 1.3.14.7.2.1.1
|
||||
rv[idx++] = 0x06;
|
||||
rv[idx++] = 6;
|
||||
rv[idx++] = (1 * 40) + 3;
|
||||
rv[idx++] = 14;
|
||||
rv[idx++] = 7;
|
||||
rv[idx++] = 2;
|
||||
rv[idx++] = 1;
|
||||
rv[idx++] = 1;
|
||||
|
||||
// params
|
||||
// sequence 3
|
||||
rv[idx++] = 0x30;
|
||||
idx = intToASN1(rv, idx, seq3len);
|
||||
// P
|
||||
// integer
|
||||
rv[idx++] = 0x02;
|
||||
idx = intToASN1(rv, idx, pb.length);
|
||||
System.arraycopy(pb, 0, rv, idx, pb.length);
|
||||
idx += pb.length;
|
||||
// G
|
||||
// integer
|
||||
rv[idx++] = 0x02;
|
||||
idx = intToASN1(rv, idx, gb.length);
|
||||
System.arraycopy(gb, 0, rv, idx, gb.length);
|
||||
idx += gb.length;
|
||||
|
||||
// the key
|
||||
// octet string
|
||||
rv[idx++] = 0x04;
|
||||
idx = intToASN1(rv, idx, xb.length);
|
||||
// BC puts an integer in the bit string, we're not going to do that
|
||||
System.arraycopy(xb, 0, rv, idx, xb.length);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public ElGamalParameterSpec getParameters()
|
||||
{
|
||||
return elSpec;
|
||||
}
|
||||
|
||||
public DHParameterSpec getParams()
|
||||
{
|
||||
return new DHParameterSpec(elSpec.getP(), elSpec.getG());
|
||||
}
|
||||
|
||||
public BigInteger getX()
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
private void readObject(
|
||||
ObjectInputStream in)
|
||||
throws IOException, ClassNotFoundException
|
||||
{
|
||||
x = (BigInteger)in.readObject();
|
||||
|
||||
this.elSpec = new ElGamalParameterSpec((BigInteger)in.readObject(), (BigInteger)in.readObject());
|
||||
}
|
||||
|
||||
private void writeObject(
|
||||
ObjectOutputStream out)
|
||||
throws IOException
|
||||
{
|
||||
out.writeObject(this.getX());
|
||||
out.writeObject(elSpec.getP());
|
||||
out.writeObject(elSpec.getG());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package net.i2p.crypto.elgamal.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
|
||||
import javax.crypto.interfaces.DHPublicKey;
|
||||
import javax.crypto.spec.DHParameterSpec;
|
||||
import javax.crypto.spec.DHPublicKeySpec;
|
||||
|
||||
import static net.i2p.crypto.SigUtil.intToASN1;
|
||||
import net.i2p.crypto.elgamal.ElGamalPublicKey;
|
||||
import net.i2p.crypto.elgamal.spec.ElGamalParameterSpec;
|
||||
import net.i2p.crypto.elgamal.spec.ElGamalPublicKeySpec;
|
||||
|
||||
public class ElGamalPublicKeyImpl
|
||||
implements ElGamalPublicKey, DHPublicKey
|
||||
{
|
||||
private static final long serialVersionUID = 8712728417091216948L;
|
||||
|
||||
private BigInteger y;
|
||||
private ElGamalParameterSpec elSpec;
|
||||
|
||||
public ElGamalPublicKeyImpl(
|
||||
ElGamalPublicKeySpec spec)
|
||||
{
|
||||
this.y = spec.getY();
|
||||
this.elSpec = new ElGamalParameterSpec(spec.getParams().getP(), spec.getParams().getG());
|
||||
}
|
||||
|
||||
public ElGamalPublicKeyImpl(
|
||||
DHPublicKeySpec spec)
|
||||
{
|
||||
this.y = spec.getY();
|
||||
this.elSpec = new ElGamalParameterSpec(spec.getP(), spec.getG());
|
||||
}
|
||||
|
||||
public ElGamalPublicKeyImpl(
|
||||
ElGamalPublicKey key)
|
||||
{
|
||||
this.y = key.getY();
|
||||
this.elSpec = key.getParameters();
|
||||
}
|
||||
|
||||
public ElGamalPublicKeyImpl(
|
||||
DHPublicKey key)
|
||||
{
|
||||
this.y = key.getY();
|
||||
this.elSpec = new ElGamalParameterSpec(key.getParams().getP(), key.getParams().getG());
|
||||
}
|
||||
|
||||
public ElGamalPublicKeyImpl(
|
||||
BigInteger y,
|
||||
ElGamalParameterSpec elSpec)
|
||||
{
|
||||
this.y = y;
|
||||
this.elSpec = elSpec;
|
||||
}
|
||||
|
||||
public ElGamalPublicKeyImpl(
|
||||
X509EncodedKeySpec spec)
|
||||
{
|
||||
throw new UnsupportedOperationException("todo");
|
||||
//this.y = y;
|
||||
//this.elSpec = elSpec;
|
||||
}
|
||||
|
||||
public String getAlgorithm()
|
||||
{
|
||||
return "ElGamal";
|
||||
}
|
||||
|
||||
public String getFormat()
|
||||
{
|
||||
return "X.509";
|
||||
}
|
||||
|
||||
public byte[] getEncoded()
|
||||
{
|
||||
byte[] pb = elSpec.getP().toByteArray();
|
||||
byte[] gb = elSpec.getG().toByteArray();
|
||||
byte[] yb = y.toByteArray();
|
||||
int seq3len = spaceFor(pb.length) + spaceFor(gb.length);
|
||||
int seq2len = 8 + spaceFor(seq3len);
|
||||
int seq1len = spaceFor(seq2len) + spaceFor(yb.length + 1);
|
||||
int totlen = spaceFor(seq1len);
|
||||
byte[] rv = new byte[totlen];
|
||||
int idx = 0;
|
||||
// sequence 1
|
||||
rv[idx++] = 0x30;
|
||||
idx = intToASN1(rv, idx, seq1len);
|
||||
|
||||
// Algorithm Identifier
|
||||
// sequence 2
|
||||
rv[idx++] = 0x30;
|
||||
idx = intToASN1(rv, idx, seq2len);
|
||||
// OID: 1.3.14.7.2.1.1
|
||||
rv[idx++] = 0x06;
|
||||
rv[idx++] = 6;
|
||||
rv[idx++] = (1 * 40) + 3;
|
||||
rv[idx++] = 14;
|
||||
rv[idx++] = 7;
|
||||
rv[idx++] = 2;
|
||||
rv[idx++] = 1;
|
||||
rv[idx++] = 1;
|
||||
|
||||
// params
|
||||
// sequence 3
|
||||
rv[idx++] = 0x30;
|
||||
idx = intToASN1(rv, idx, seq3len);
|
||||
// P
|
||||
// integer
|
||||
rv[idx++] = 0x02;
|
||||
idx = intToASN1(rv, idx, pb.length);
|
||||
System.arraycopy(pb, 0, rv, idx, pb.length);
|
||||
idx += pb.length;
|
||||
// G
|
||||
// integer
|
||||
rv[idx++] = 0x02;
|
||||
idx = intToASN1(rv, idx, gb.length);
|
||||
System.arraycopy(gb, 0, rv, idx, gb.length);
|
||||
idx += gb.length;
|
||||
|
||||
// the key
|
||||
// bit string
|
||||
rv[idx++] = 0x03;
|
||||
idx = intToASN1(rv, idx, yb.length + 1);
|
||||
rv[idx++] = 0; // number of trailing unused bits
|
||||
// BC puts an integer in the bit string, we're not going to do that
|
||||
System.arraycopy(yb, 0, rv, idx, yb.length);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param val the length of the value, 65535 max
|
||||
* @return the length of the TLV
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
public ElGamalParameterSpec getParameters()
|
||||
{
|
||||
return elSpec;
|
||||
}
|
||||
|
||||
public DHParameterSpec getParams()
|
||||
{
|
||||
return new DHParameterSpec(elSpec.getP(), elSpec.getG());
|
||||
}
|
||||
|
||||
public BigInteger getY()
|
||||
{
|
||||
return y;
|
||||
}
|
||||
|
||||
private void readObject(
|
||||
ObjectInputStream in)
|
||||
throws IOException, ClassNotFoundException
|
||||
{
|
||||
this.y = (BigInteger)in.readObject();
|
||||
this.elSpec = new ElGamalParameterSpec((BigInteger)in.readObject(), (BigInteger)in.readObject());
|
||||
}
|
||||
|
||||
private void writeObject(
|
||||
ObjectOutputStream out)
|
||||
throws IOException
|
||||
{
|
||||
out.writeObject(this.getY());
|
||||
out.writeObject(elSpec.getP());
|
||||
out.writeObject(elSpec.getG());
|
||||
}
|
||||
}
|
||||
9
core/java/src/net/i2p/crypto/elgamal/impl/package.html
Normal file
9
core/java/src/net/i2p/crypto/elgamal/impl/package.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<html><body>
|
||||
<p>
|
||||
Implementation of ElGamal keys, used for I2PProvider.
|
||||
Modified from Bouncy Castle 1.53.
|
||||
See net.i2p.crypto.elgamal for license info.
|
||||
</p><p>
|
||||
Since 0.9.25.
|
||||
</p>
|
||||
</body></html>
|
||||
29
core/java/src/net/i2p/crypto/elgamal/package.html
Normal file
29
core/java/src/net/i2p/crypto/elgamal/package.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<html><body>
|
||||
<p>
|
||||
Interfaces for ElGamal keys, used for I2PProvider.
|
||||
Copied from Bouncy Castle 1.53.
|
||||
</p><p>
|
||||
Since 0.9.25.
|
||||
</p><p><pre>
|
||||
|
||||
|
||||
Copyright (c) 2000 - 2013 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
|
||||
AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
</pre></p>
|
||||
</body></html>
|
||||
@@ -0,0 +1,28 @@
|
||||
package net.i2p.crypto.elgamal.spec;
|
||||
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
|
||||
public class ElGamalGenParameterSpec
|
||||
implements AlgorithmParameterSpec
|
||||
{
|
||||
private final int primeSize;
|
||||
|
||||
/*
|
||||
* @param primeSize the size (in bits) of the prime modulus.
|
||||
*/
|
||||
public ElGamalGenParameterSpec(
|
||||
int primeSize)
|
||||
{
|
||||
this.primeSize = primeSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size in bits of the prime modulus.
|
||||
*
|
||||
* @return the size in bits of the prime modulus
|
||||
*/
|
||||
public int getPrimeSize()
|
||||
{
|
||||
return primeSize;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package net.i2p.crypto.elgamal.spec;
|
||||
|
||||
import java.security.spec.KeySpec;
|
||||
|
||||
public class ElGamalKeySpec
|
||||
implements KeySpec
|
||||
{
|
||||
private final ElGamalParameterSpec spec;
|
||||
|
||||
public ElGamalKeySpec(
|
||||
ElGamalParameterSpec spec)
|
||||
{
|
||||
this.spec = spec;
|
||||
}
|
||||
|
||||
public ElGamalParameterSpec getParams()
|
||||
{
|
||||
return spec;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package net.i2p.crypto.elgamal.spec;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
|
||||
/**
|
||||
* Copied from org.bouncycastle.jce.spec
|
||||
* This can't actually be passed to the BC provider, we would have to
|
||||
* use reflection to create a "real" org.bouncycasle.jce.spec.ElGamalParameterSpec.
|
||||
*
|
||||
* @since 0.9.18, moved from net.i2p.crypto in 0.9.25
|
||||
*/
|
||||
public class ElGamalParameterSpec implements AlgorithmParameterSpec {
|
||||
private final BigInteger p;
|
||||
private final BigInteger g;
|
||||
|
||||
/**
|
||||
* Constructs a parameter set for Diffie-Hellman, using a prime modulus
|
||||
* <code>p</code> and a base generator <code>g</code>.
|
||||
*
|
||||
* @param p the prime modulus
|
||||
* @param g the base generator
|
||||
*/
|
||||
public ElGamalParameterSpec(BigInteger p, BigInteger g) {
|
||||
this.p = p;
|
||||
this.g = g;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the prime modulus <code>p</code>.
|
||||
*
|
||||
* @return the prime modulus <code>p</code>
|
||||
*/
|
||||
public BigInteger getP() {
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base generator <code>g</code>.
|
||||
*
|
||||
* @return the base generator <code>g</code>
|
||||
*/
|
||||
public BigInteger getG() {
|
||||
return g;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.25
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return p.hashCode() ^ g.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.25
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null)
|
||||
return false;
|
||||
BigInteger op, og;
|
||||
if (obj instanceof ElGamalParameterSpec) {
|
||||
ElGamalParameterSpec egps = (ElGamalParameterSpec) obj;
|
||||
op = egps.getP();
|
||||
og = egps.getG();
|
||||
//} else if (obj.getClass().getName().equals("org.bouncycastle.jce.spec.ElGamalParameterSpec")) {
|
||||
//reflection... no...
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return p.equals(op) && g.equals(og);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package net.i2p.crypto.elgamal.spec;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* This class specifies an ElGamal private key with its associated parameters.
|
||||
*
|
||||
* @see ElGamalPublicKeySpec
|
||||
*/
|
||||
public class ElGamalPrivateKeySpec
|
||||
extends ElGamalKeySpec
|
||||
{
|
||||
private final BigInteger x;
|
||||
|
||||
public ElGamalPrivateKeySpec(
|
||||
BigInteger x,
|
||||
ElGamalParameterSpec spec)
|
||||
{
|
||||
super(spec);
|
||||
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the private value <code>x</code>.
|
||||
*
|
||||
* @return the private value <code>x</code>
|
||||
*/
|
||||
public BigInteger getX()
|
||||
{
|
||||
return x;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package net.i2p.crypto.elgamal.spec;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* This class specifies an ElGamal public key with its associated parameters.
|
||||
*
|
||||
* @see ElGamalPrivateKeySpec
|
||||
*/
|
||||
public class ElGamalPublicKeySpec
|
||||
extends ElGamalKeySpec
|
||||
{
|
||||
private final BigInteger y;
|
||||
|
||||
public ElGamalPublicKeySpec(
|
||||
BigInteger y,
|
||||
ElGamalParameterSpec spec)
|
||||
{
|
||||
super(spec);
|
||||
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the public value <code>y</code>.
|
||||
*
|
||||
* @return the public value <code>y</code>
|
||||
*/
|
||||
public BigInteger getY()
|
||||
{
|
||||
return y;
|
||||
}
|
||||
}
|
||||
9
core/java/src/net/i2p/crypto/elgamal/spec/package.html
Normal file
9
core/java/src/net/i2p/crypto/elgamal/spec/package.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<html><body>
|
||||
<p>
|
||||
Specs ElGamal keys, used for I2PProvider.
|
||||
Copied from Bouncy Castle 1.53.
|
||||
See net.i2p.crypto.elgamal for license info.
|
||||
</p><p>
|
||||
Since 0.9.25.
|
||||
</p>
|
||||
</body></html>
|
||||
@@ -3,6 +3,7 @@ package net.i2p.crypto.provider;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.security.Provider;
|
||||
import java.security.Security;
|
||||
|
||||
/**
|
||||
* @since 0.9.15
|
||||
@@ -11,6 +12,7 @@ public final class I2PProvider extends Provider {
|
||||
public static final String PROVIDER_NAME = "I2P";
|
||||
private static final String INFO = "I2P Security Provider v0.1, implementing" +
|
||||
"several algorithms used by I2P.";
|
||||
private static boolean _installed;
|
||||
|
||||
/**
|
||||
* Construct a new provider. This should only be required when
|
||||
@@ -31,15 +33,89 @@ public final class I2PProvider extends Provider {
|
||||
|
||||
private void setup() {
|
||||
// TODO: Implement SPIs for existing code
|
||||
// However -
|
||||
// quote
|
||||
// http://docs.oracle.com/javase/7/docs/technotes/guides/security/crypto/HowToImplAProvider.html
|
||||
//
|
||||
// If your provider is supplying encryption algorithms through the
|
||||
// Cipher, KeyAgreement, KeyGenerator, Mac, or SecretKeyFactory classes,
|
||||
// you will need to sign your JAR file so that the JCA can authenticate the code at runtime.
|
||||
// If you are NOT providing an implementation of this type you can skip this step.
|
||||
//
|
||||
//put("Cipher.AES", "net.i2p.crypto.provider.CipherSpi$aesCBC");
|
||||
//put("Cipher.ElGamal", "net.i2p.crypto.provider.CipherSpi$elGamal");
|
||||
//put("Mac.HmacMD5-I2P", "net.i2p.crypto.provider.MacSpi");
|
||||
|
||||
put("MessageDigest.SHA-1", "net.i2p.crypto.SHA1");
|
||||
//put("Signature.SHA1withDSA", "net.i2p.crypto.provider.SignatureSpi");
|
||||
|
||||
// EdDSA
|
||||
// Key OID: 1.3.101.100; Sig OID: 1.3.101.101
|
||||
put("KeyFactory.EdDSA", "net.i2p.crypto.eddsa.KeyFactory");
|
||||
put("KeyPairGenerator.EdDSA", "net.i2p.crypto.eddsa.KeyPairGenerator");
|
||||
put("Signature.SHA512withEdDSA", "net.i2p.crypto.eddsa.EdDSAEngine");
|
||||
// Didn't find much documentation on these at all,
|
||||
// see http://docs.oracle.com/javase/7/docs/technotes/guides/security/crypto/HowToImplAProvider.html
|
||||
// section "Mapping from OID to name"
|
||||
// without these, Certificate.verify() fails
|
||||
put("Alg.Alias.KeyFactory.1.3.101.100", "EdDSA");
|
||||
put("Alg.Alias.KeyFactory.OID.1.3.101.100", "EdDSA");
|
||||
// Without these, keytool fails with:
|
||||
// keytool error: java.security.NoSuchAlgorithmException: unrecognized algorithm name: SHA512withEdDSA
|
||||
put("Alg.Alias.KeyPairGenerator.1.3.101.100", "EdDSA");
|
||||
put("Alg.Alias.KeyPairGenerator.OID.1.3.101.100", "EdDSA");
|
||||
// with this setting, keytool keygen doesn't work
|
||||
// java.security.cert.CertificateException: Signature algorithm mismatch
|
||||
// it must match the key setting (1.3.101.100) to work
|
||||
// but this works fine with programmatic cert generation
|
||||
put("Alg.Alias.Signature.1.3.101.101", "SHA512withEdDSA");
|
||||
put("Alg.Alias.Signature.OID.1.3.101.101", "SHA512withEdDSA");
|
||||
// TODO Ed25519ph
|
||||
// OID: 1.3.101.101
|
||||
|
||||
// ElGamal
|
||||
// OID: 1.3.14.7.2.1.1
|
||||
put("KeyFactory.DH", "net.i2p.crypto.elgamal.KeyFactory");
|
||||
put("KeyFactory.DiffieHellman", "net.i2p.crypto.elgamal.KeyFactory");
|
||||
put("KeyFactory.ElGamal", "net.i2p.crypto.elgamal.KeyFactory");
|
||||
put("KeyPairGenerator.DH", "net.i2p.crypto.elgamal.KeyPairGenerator");
|
||||
put("KeyPairGenerator.DiffieHellman", "net.i2p.crypto.elgamal.KeyPairGenerator");
|
||||
put("KeyPairGenerator.ElGamal", "net.i2p.crypto.elgamal.KeyPairGenerator");
|
||||
put("Signature.SHA256withElGamal", "net.i2p.crypto.elgamal.ElGamalSigEngine");
|
||||
put("Alg.Alias.KeyFactory.1.3.14.7.2.1.1", "ElGamal");
|
||||
put("Alg.Alias.KeyFactory.OID.1.3.14.7.2.1.1", "ElGamal");
|
||||
put("Alg.Alias.KeyPairGenerator.1.3.14.7.2.1.1", "ElGamal");
|
||||
put("Alg.Alias.KeyPairGenerator.OID.1.3.14.7.2.1.1", "ElGamal");
|
||||
put("Alg.Alias.Signature.1.3.14.7.2.1.1", "SHA256withElGamal");
|
||||
put("Alg.Alias.Signature.OID.1.3.14.7.2.1.1", "SHA256withElGamal");
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the I2PProvider.
|
||||
* Harmless to call multiple times.
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public static void addProvider() {
|
||||
synchronized(I2PProvider.class) {
|
||||
if (!_installed) {
|
||||
try {
|
||||
Provider us = new I2PProvider();
|
||||
// put ours ahead of BC, if installed, because our ElGamal
|
||||
// implementation may not be fully compatible with BC
|
||||
Provider[] provs = Security.getProviders();
|
||||
for (int i = 0; i < provs.length; i++) {
|
||||
if (provs[i].getName().equals("BC")) {
|
||||
Security.insertProviderAt(us, i);
|
||||
_installed = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
Security.addProvider(us);
|
||||
_installed = true;
|
||||
} catch (SecurityException se) {
|
||||
System.out.println("WARN: Could not install I2P provider: " + se);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,6 +104,17 @@ public class Base64 {
|
||||
return safeDecode(s, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes data from Base64 notation using the I2P alphabet.
|
||||
*
|
||||
* @param useStandardAlphabet Warning, must be false for I2P compatibility
|
||||
* @return the decoded data, null on error
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public static byte[] decode(String s, boolean useStandardAlphabet) {
|
||||
return safeDecode(s, useStandardAlphabet);
|
||||
}
|
||||
|
||||
/** Maximum line length (76) of Base64 output. */
|
||||
private final static int MAX_LINE_LENGTH = 76;
|
||||
|
||||
|
||||
@@ -1324,8 +1324,9 @@ public class DataHelper {
|
||||
*
|
||||
* @param hash null OK
|
||||
* @return null on EOF
|
||||
* @deprecated use MessageDigest version
|
||||
* @deprecated use MessageDigest version to be removed in 0.9.27
|
||||
*/
|
||||
@Deprecated
|
||||
public static String readLine(InputStream in, Sha256Standalone hash) throws IOException {
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
boolean ok = readLine(in, buf, hash);
|
||||
@@ -1380,7 +1381,7 @@ public class DataHelper {
|
||||
*
|
||||
* @return true if the line was read, false if eof was reached on an empty line
|
||||
* (returns true for non-empty last line without a newline)
|
||||
* @deprecated use StringBuilder / MessageDigest version
|
||||
* @deprecated use StringBuilder / MessageDigest version, to be removed in 0.9.27
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean readLine(InputStream in, StringBuffer buf, Sha256Standalone hash) throws IOException {
|
||||
@@ -1420,8 +1421,9 @@ public class DataHelper {
|
||||
* @param hash null OK
|
||||
* @return true if the line was read, false if eof was reached on an empty line
|
||||
* (returns true for non-empty last line without a newline)
|
||||
* @deprecated use MessageDigest version
|
||||
* @deprecated use MessageDigest version, to be removed in 0.9.27
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean readLine(InputStream in, StringBuilder buf, Sha256Standalone hash) throws IOException {
|
||||
int c = -1;
|
||||
int i = 0;
|
||||
@@ -1463,8 +1465,9 @@ public class DataHelper {
|
||||
|
||||
/**
|
||||
* update the hash along the way
|
||||
* @deprecated use MessageDigest version
|
||||
* @deprecated use MessageDigest version, to be removed in 0.9.27
|
||||
*/
|
||||
@Deprecated
|
||||
public static void write(OutputStream out, byte data[], Sha256Standalone hash) throws IOException {
|
||||
hash.update(data);
|
||||
out.write(data);
|
||||
|
||||
@@ -201,7 +201,8 @@ public class RandomSource extends SecureRandom implements EntropyHarvester {
|
||||
} catch (InterruptedException ie) {}
|
||||
|
||||
// why urandom? because /dev/random blocks
|
||||
ok = seedFromFile(new File("/dev/urandom"), buf) || ok;
|
||||
if (!SystemVersion.isWindows())
|
||||
ok = seedFromFile(new File("/dev/urandom"), buf) || ok;
|
||||
// we merge (XOR) in the data from /dev/urandom with our own seedfile
|
||||
File localFile = new File(_context.getConfigDir(), SEEDFILE);
|
||||
ok = seedFromFile(localFile, buf) || ok;
|
||||
|
||||
Reference in New Issue
Block a user