propagate from branch 'i2p.i2p.zzz.test2' (head 70ae5494bd7255a03f80838a2f3d8e7c0ce86634)

to branch 'i2p.i2p' (head 05a201cc5c1bd841f32e9268b3019b3a3447f4f3)
This commit is contained in:
zzz
2016-02-03 20:02:34 +00:00
82 changed files with 3766 additions and 313 deletions

View File

@ -40,6 +40,10 @@ Public domain except as listed below:
Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle
See licenses/LICENSE-SHA256.txt See licenses/LICENSE-SHA256.txt
ElGamal:
Copyright (c) 2000 - 2013 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org)
See licenses/LICENSE-SHA256.txt
AES code: AES code:
Copyright (c) 1995-2005 The Cryptix Foundation Limited. Copyright (c) 1995-2005 The Cryptix Foundation Limited.
See licenses/LICENSE-Cryptix.txt See licenses/LICENSE-Cryptix.txt

View File

@ -471,10 +471,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
portNum = "7654"; portNum = "7654";
String msg; String msg;
if (getTunnel().getContext().isRouterContext()) if (getTunnel().getContext().isRouterContext())
msg = "Unable to build tunnels for the client";
else
msg = "Unable to connect to the router at " + getTunnel().host + ':' + portNum + msg = "Unable to connect to the router at " + getTunnel().host + ':' + portNum +
" and build tunnels for the client"; " and build tunnels for the client";
else
msg = "Unable to build tunnels for the client";
if (++retries < MAX_RETRIES) { if (++retries < MAX_RETRIES) {
if (log != null) if (log != null)
log.log(msg + ", retrying in " + (RETRY_DELAY / 1000) + " seconds"); log.log(msg + ", retrying in " + (RETRY_DELAY / 1000) + " seconds");

View File

@ -0,0 +1,93 @@
package net.i2p.router.web;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.util.FileUtil;
/**
* Serve plugin icons, at /Plugins/pluginicon?plugin=foo
*
* @author cacapo
* @since 0.9.25
*/
public class CodedIconRendererServlet extends HttpServlet {
private static final long serialVersionUID = 16851750L;
private static final String base = I2PAppContext.getGlobalContext().getBaseDir().getAbsolutePath();
private static final String file = "docs" + File.separatorChar + "themes" + File.separatorChar + "console" + File.separatorChar + "images" + File.separatorChar + "plugin.png";
@Override
protected void service(HttpServletRequest srq, HttpServletResponse srs) throws ServletException, IOException {
byte[] data;
String name = srq.getParameter("plugin");
data = NavHelper.getBinary(name);
//set as many headers as are common to any outcome
srs.setContentType("image/png");
srs.setDateHeader("Expires", I2PAppContext.getGlobalContext().clock().now() + 86400000l);
srs.setHeader("Cache-Control", "public, max-age=86400");
OutputStream os = srs.getOutputStream();
//Binary data is present
if(data != null){
srs.setHeader("Content-Length", Integer.toString(data.length));
int content = Arrays.hashCode(data);
int chksum = srq.getIntHeader("If-None-Match");//returns -1 if no such header
//Don't render if icon already present
if(content != chksum){
srs.setIntHeader("ETag", content);
try{
os.write(data);
os.flush();
os.close();
}catch(IOException e){
I2PAppContext.getGlobalContext().logManager().getLog(getClass()).warn("Error writing binary image data for plugin", e);
}
} else {
srs.sendError(304, "Not Modified");
}
} else {
//Binary data is not present but must be substituted by file on disk
File pfile = new File(base, file);
srs.setHeader("Content-Length", Long.toString(pfile.length()));
try{
long lastmod = pfile.lastModified();
if(lastmod > 0){
long iflast = srq.getDateHeader("If-Modified-Since");
if(iflast >= ((lastmod/1000) * 1000)){
srs.sendError(304, "Not Modified");
} else {
srs.setDateHeader("Last-Modified", lastmod);
FileUtil.readFile(file, base, os);
}
}
} catch(IOException e) {
if (!srs.isCommitted()) {
srs.sendError(403, e.toString());
} else {
I2PAppContext.getGlobalContext().logManager().getLog(getClass()).warn("Error serving plugin.png", e);
throw e;
}
}
}
}
}

View File

@ -0,0 +1,109 @@
package net.i2p.router.web;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.i2p.crypto.CertUtil;
import net.i2p.crypto.KeyStoreUtil;
import net.i2p.router.crypto.FamilyKeyCrypto;
import net.i2p.util.SecureDirectory;
/**
* @since 0.9.25
*/
public class ConfigFamilyHandler extends FormHandler {
@Override
protected void processForm() {
if (_action.equals(_t("Create Router Family"))) {
String family = getJettyString("family");
String old = _context.getProperty(FamilyKeyCrypto.PROP_FAMILY_NAME);
if (family == null || family.trim().length() <= 0) {
addFormError(_t("You must enter a family name"));
} else if (old != null) {
addFormError("Family already configured: " + family);
} else if (family.contains("/") || family.contains("\\")) {
addFormError("Bad characters in Family: " + family);
} else if (_context.router().saveConfig(FamilyKeyCrypto.PROP_FAMILY_NAME, family.trim())) {
addFormNotice(_t("Configuration saved successfully."));
addFormError(_t("Restart required to take effect"));
} else {
addFormError(_t("Error saving the configuration (applied but not saved) - please see the error logs"));
}
} else if (_action.equals(_t("Join Router Family"))) {
InputStream in = _requestWrapper.getInputStream("file");
try {
// non-null but zero bytes if no file entered, don't know why
if (in == null || in.available() <= 0) {
addFormError(_t("You must enter a file"));
return;
}
// load data
PrivateKey pk = CertUtil.loadPrivateKey(in);
List<X509Certificate> certs = CertUtil.loadCerts(in);
String family = CertUtil.getSubjectValue(certs.get(0), "CN");
if (family == null) {
addFormError("Bad certificate - No Subject CN");
}
if (family.endsWith(FamilyKeyCrypto.CN_SUFFIX) && family.length() > FamilyKeyCrypto.CN_SUFFIX.length())
family = family.substring(0, family.length() - FamilyKeyCrypto.CN_SUFFIX.length());
// store to keystore
File ks = new SecureDirectory(_context.getConfigDir(), "keystore");
if (!ks.exists());
ks.mkdirs();
ks = new File(ks, FamilyKeyCrypto.KEYSTORE_PREFIX + family + FamilyKeyCrypto.KEYSTORE_SUFFIX);
String keypw = KeyStoreUtil.randomString();
KeyStoreUtil.storePrivateKey(ks, KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD, family, keypw, pk, certs);
// store certificate
File cf = new SecureDirectory(_context.getConfigDir(), "certificates");
if (!cf.exists());
cf.mkdirs();
cf = new SecureDirectory(cf, "family");
if (!ks.exists());
ks.mkdirs();
cf = new File(cf, family + FamilyKeyCrypto.CERT_SUFFIX);
// ignore failure
KeyStoreUtil.exportCert(ks, KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD, family, cf);
// save config
Map<String, String> changes = new HashMap<String, String>();
changes.put(FamilyKeyCrypto.PROP_FAMILY_NAME, family);
changes.put(FamilyKeyCrypto.PROP_KEY_PASSWORD, keypw);
changes.put(FamilyKeyCrypto.PROP_KEYSTORE_PASSWORD, KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD);
if (_context.router().saveConfig(changes, null)) {
addFormNotice("Family key configured for router family: " + family);
addFormError(_t("Restart required to take effect"));
} else {
addFormError(_t("Error saving the configuration (applied but not saved) - please see the error logs"));
}
} catch (GeneralSecurityException gse) {
addFormError(_t("Load from file failed") + " - " + gse);
} catch (IOException ioe) {
addFormError(_t("Load from file failed") + " - " + ioe);
} finally {
// it's really a ByteArrayInputStream but we'll play along...
try { in.close(); } catch (IOException ioe) {}
}
} else if (_action.equals(_t("Leave Router Family"))) {
List<String> removes = new ArrayList<String>();
removes.add(FamilyKeyCrypto.PROP_FAMILY_NAME);
removes.add(FamilyKeyCrypto.PROP_KEY_PASSWORD);
removes.add(FamilyKeyCrypto.PROP_KEYSTORE_PASSWORD);
if (_context.router().saveConfig(null, removes)) {
addFormNotice(_t("Configuration saved successfully."));
addFormError(_t("Restart required to take effect"));
} else {
addFormError(_t("Error saving the configuration (applied but not saved) - please see the error logs"));
}
}
//addFormError(_t("Unsupported") + ' ' + _action + '.');
}
}

View File

@ -0,0 +1,17 @@
package net.i2p.router.web;
import net.i2p.router.crypto.FamilyKeyCrypto;
/**
* @since 0.9.25
*/
public class ConfigFamilyHelper extends HelperBase {
public String getFamily() {
return _context.getProperty(FamilyKeyCrypto.PROP_FAMILY_NAME, "");
}
public String getKeyPW() {
return _context.getProperty(FamilyKeyCrypto.PROP_KEY_PASSWORD, "");
}
}

View File

@ -19,13 +19,13 @@ public class ConfigNavHelper extends HelperBase {
private static final String pages[] = private static final String pages[] =
{"", "net", "ui", "sidebar", "home", "service", "update", "tunnels", {"", "net", "ui", "sidebar", "home", "service", "update", "tunnels",
"clients", "peer", "keyring", "logging", "stats", "clients", "peer", "keyring", "logging", "stats",
"reseed", "advanced" }; "reseed", "advanced", "family" };
private static final String titles[] = private static final String titles[] =
{_x("Bandwidth"), _x("Network"), _x("UI"), _x("Summary Bar"), _x("Home Page"), {_x("Bandwidth"), _x("Network"), _x("UI"), _x("Summary Bar"), _x("Home Page"),
_x("Service"), _x("Update"), _x("Tunnels"), _x("Service"), _x("Update"), _x("Tunnels"),
_x("Clients"), _x("Peers"), _x("Keyring"), _x("Logging"), _x("Stats"), _x("Clients"), _x("Peers"), _x("Keyring"), _x("Logging"), _x("Stats"),
_x("Reseeding"), _x("Advanced") }; _x("Reseeding"), _x("Advanced"), _x("Router Family") };
/** @since 0.9.19 */ /** @since 0.9.19 */
private static class Tab { private static class Tab {

View File

@ -67,6 +67,7 @@ public class HomeHelper extends HelperBase {
//"Salt" + S + "salt.i2p" + S + "http://salt.i2p/" + S + I + "salt_console.png" + S + //"Salt" + S + "salt.i2p" + S + "http://salt.i2p/" + S + I + "salt_console.png" + S +
"stats.i2p" + S + _x("I2P Network Statistics") + S + "http://stats.i2p/cgi-bin/dashboard.cgi" + S + I + "chart_line.png" + S + "stats.i2p" + S + _x("I2P Network Statistics") + S + "http://stats.i2p/cgi-bin/dashboard.cgi" + S + I + "chart_line.png" + S +
_x("Technical Docs") + S + _x("Technical documentation") + S + "http://i2p-projekt.i2p/how" + S + I + "education.png" + S + _x("Technical Docs") + S + _x("Technical documentation") + S + "http://i2p-projekt.i2p/how" + S + I + "education.png" + S +
_x("The Tin Hat") + S + _x("Privacy guides and tutorials") + S + "http://secure.thetinhat.i2p/" + S + I + "thetinhat.png" + S +
_x("Trac Wiki") + S + S + "http://trac.i2p2.i2p/" + S + I + "billiard_marker.png" + S + _x("Trac Wiki") + S + S + "http://trac.i2p2.i2p/" + S + I + "billiard_marker.png" + S +
//_x("Ugha's Wiki") + S + S + "http://ugha.i2p/" + S + I + "billiard_marker.png" + S + //_x("Ugha's Wiki") + S + S + "http://ugha.i2p/" + S + I + "billiard_marker.png" + S +
_x("Sponge's main site") + S + _x("Seedless and the Robert BitTorrent applications") + S + "http://sponge.i2p/" + S + I + "user_astronaut.png" + S + _x("Sponge's main site") + S + _x("Seedless and the Robert BitTorrent applications") + S + "http://sponge.i2p/" + S + I + "user_astronaut.png" + S +

View File

@ -13,6 +13,7 @@ public class NavHelper {
private static final Map<String, String> _apps = new ConcurrentHashMap<String, String>(4); private static final Map<String, String> _apps = new ConcurrentHashMap<String, String>(4);
private static final Map<String, String> _tooltips = new ConcurrentHashMap<String, String>(4); private static final Map<String, String> _tooltips = new ConcurrentHashMap<String, String>(4);
private static final Map<String, String> _icons = new ConcurrentHashMap<String, String>(4); private static final Map<String, String> _icons = new ConcurrentHashMap<String, String>(4);
private static final Map<String, byte[]> _binary = new ConcurrentHashMap<String, byte[]>(4);
/** /**
* To register a new client application so that it shows up on the router * To register a new client application so that it shows up on the router
@ -40,6 +41,29 @@ public class NavHelper {
_icons.remove(name); _icons.remove(name);
} }
/**
* Retrieve binary icon for a plugin
* @param name plugin name
* @return null if not found
* @since 0.9.25
*/
public static byte[] getBinary(String name){
if(name != null)
return _binary.get(name);
else
return null;
}
/**
* Store binary icon for a plugin
* @param name plugin name
* @since 0.9.25
*/
public static void setBinary(String name, byte[] arr){
_binary.put(name, arr);
}
/** /**
* Translated string is loaded by PluginStarter * Translated string is loaded by PluginStarter
* @param ctx unused * @param ctx unused

View File

@ -22,6 +22,7 @@ import net.i2p.I2PAppContext;
import net.i2p.app.ClientApp; import net.i2p.app.ClientApp;
import net.i2p.app.ClientAppState; import net.i2p.app.ClientAppState;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
import net.i2p.data.Base64;
import net.i2p.router.RouterContext; import net.i2p.router.RouterContext;
import net.i2p.router.RouterVersion; import net.i2p.router.RouterVersion;
import net.i2p.router.startup.ClientAppConfig; import net.i2p.router.startup.ClientAppConfig;
@ -353,6 +354,18 @@ public class PluginStarter implements Runnable {
} }
} }
//handle console icons for plugins without web-resources through prop icon-code
String fullprop = props.getProperty("icon-code");
if(fullprop != null && fullprop.length() > 1){
byte[] decoded = Base64.decode(fullprop);
if(decoded != null) {
NavHelper.setBinary(appName, decoded);
iconfile = "/Plugins/pluginicon?plugin=" + appName;
} else {
iconfile = "/themes/console/images/plugin.png";
}
}
// load and start things in clients.config // load and start things in clients.config
File clientConfig = new File(pluginDir, "clients.config"); File clientConfig = new File(pluginDir, "clients.config");
if (clientConfig.exists()) { if (clientConfig.exists()) {

View File

@ -0,0 +1,88 @@
<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
<%@include file="css.jsi" %>
<%=intl.title("config router family")%>
<script src="/js/ajax.js" type="text/javascript"></script>
<%@include file="summaryajax.jsi" %>
</head><body onload="initAjax()">
<%@include file="summary.jsi" %>
<jsp:useBean class="net.i2p.router.web.ConfigFamilyHelper" id="familyHelper" scope="request" />
<jsp:setProperty name="familyHelper" property="contextId" value="<%=(String)session.getAttribute(\"i2p.contextId\")%>" />
<h1><%=intl._t("I2P Router Family Configuration")%></h1>
<div class="main" id="main">
<%@include file="confignav.jsi" %>
<jsp:useBean class="net.i2p.router.web.ConfigFamilyHandler" id="formhandler" scope="request" />
<%@include file="formhandler.jsi" %>
<p><%=intl._t("Routers in the same family share a family key.")%>
<%=intl._t("To start a new family, enter a family name.")%>
<%=intl._t("To join an existing family, import the private key you exported from a router in the family.")%>
</p>
<%
String family = familyHelper.getFamily();
if (family.length() <= 0) {
// no family yet
%>
<div class="configure"><form action="" method="POST">
<input type="hidden" name="nonce" value="<%=pageNonce%>" >
<h3><%=intl._t("Create Router Family")%></h3>
<p><%=intl._t("Family Name")%> :
<input name="family" type="text" size="30" value="" />
</p>
<div class="formaction">
<input type="submit" name="action" class="accept" value="<%=intl._t("Create Router Family")%>" />
</div></form></div>
<div class="configure">
<form action="" method="POST" enctype="multipart/form-data" accept-charset="UTF-8">
<input type="hidden" name="nonce" value="<%=pageNonce%>" >
<h3><%=intl._t("Join Router Family")%></h3>
<p><%=intl._t("Import the secret family key that you exported from an existing router in the family.")%>
<p><%=intl._t("Select secret key file")%> :
<input name="file" type="file" value="" />
</p>
<div class="formaction">
<input type="submit" name="action" class="download" value="<%=intl._t("Join Router Family")%>" />
</div></form></div>
<%
} else {
// family is configured
String keypw = familyHelper.getKeyPW();
if (keypw.length() > 0) {
// family is active
%>
<div class="configure">
<form action="/exportfamily" method="GET">
<h3><%=intl._t("Export Family Key")%></h3>
<p><%=intl._t("Export the secret family key to be imported into other routers you control.")%>
</p>
<div class="formaction">
<input type="submit" name="action" class="go" value="<%=intl._t("Export Family Key")%>" />
</div></form></div>
<%
} else {
// family is not active
%>
<p><b><%=intl._t("Restart required to activate family {0}.", '"' + family + '"')%>
<%=intl._t("After restarting, you may export the family key.")%></b></p>
<%
}
%>
<div class="configure"><form action="" method="POST">
<input type="hidden" name="nonce" value="<%=pageNonce%>" >
<h3><%=intl._t("Leave Router Family")%></h3>
<p><%=intl._t("No longer be a member of the family {0}.", '"' + family + '"')%>
<div class="formaction">
<input type="submit" name="action" class="delete" value="<%=intl._t("Leave Router Family")%>" />
</div></form></div>
<%
}
%>
</div></body></html>

View File

@ -78,7 +78,7 @@
<b><%=intl._t("Use non-SSL only")%></b></td></tr> <b><%=intl._t("Use non-SSL only")%></b></td></tr>
<tr><td class="mediumtags" align="right"><b><%=intl._t("Reseed URLs")%>:</b></td> <tr><td class="mediumtags" align="right"><b><%=intl._t("Reseed URLs")%>:</b></td>
<td><textarea wrap="off" name="reseedURL" cols="60" rows="7" spellcheck="false"><jsp:getProperty name="reseedHelper" property="reseedURL" /></textarea> <td><textarea wrap="off" name="reseedURL" cols="60" rows="7" spellcheck="false"><jsp:getProperty name="reseedHelper" property="reseedURL" /></textarea>
<div class="formaction"><input type="submit" name="action" value="<%=intl._t("Reset URL list")%>" /></div> <div class="formaction"><input type="submit" name="action" class="reload" value="<%=intl._t("Reset URL list")%>" /></div>
</td></tr> </td></tr>
<tr><td class="mediumtags" align="right"><b><%=intl._t("Enable HTTP Proxy?")%></b></td> <tr><td class="mediumtags" align="right"><b><%=intl._t("Enable HTTP Proxy?")%></b></td>

View File

@ -0,0 +1,35 @@
<%
try {
net.i2p.I2PAppContext ctx = net.i2p.I2PAppContext.getGlobalContext();
String family = ctx.getProperty("netdb.family.name");
String keypw = ctx.getProperty("netdb.family.keyPassword");
String kspw = ctx.getProperty("netdb.family.keystorePassword", "changeit");
if (family == null || keypw == null) {
response.sendError(404);
return;
}
try {
response.setDateHeader("Expires", 0);
response.addHeader("Cache-Control", "no-store, max-age=0, no-cache, must-revalidate");
response.addHeader("Pragma", "no-cache");
String name = "family-" + family + "-secret.crt";
response.setContentType("application/x-x509-ca-cert; name=\"" + name + '"');
response.addHeader("Content-Disposition", "attachment; filename=\"" + name + '"');
java.io.File ks = new java.io.File(ctx.getConfigDir(), "keystore");
ks = new java.io.File(ks, "family-" + family + ".ks");
java.io.OutputStream cout = response.getOutputStream();
net.i2p.crypto.KeyStoreUtil.exportPrivateKey(ks, kspw, family, keypw, cout);
} catch (java.security.GeneralSecurityException gse) {
throw new java.io.IOException("key error", gse);
}
} catch (java.io.IOException ioe) {
// prevent 'Committed' IllegalStateException from Jetty
if (!response.isCommitted()) {
response.sendError(403, ioe.toString());
} else {
// Jetty doesn't log this
throw ioe;
}
}
// don't worry about a newline after this
%>

View File

@ -15,6 +15,18 @@
<!-- precompiled servlets --> <!-- precompiled servlets -->
<servlet>
<servlet-name>net.i2p.router.web.CodedIconRendererServlet</servlet-name>
<servlet-class>net.i2p.router.web.CodedIconRendererServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>net.i2p.router.web.CodedIconRendererServlet</servlet-name>
<url-pattern>/Plugins/*</url-pattern>
</servlet-mapping>
<!-- yeah, i'm lazy, using a jsp instead of a servlet.. --> <!-- yeah, i'm lazy, using a jsp instead of a servlet.. -->
<servlet-mapping> <servlet-mapping>
<servlet-name>net.i2p.router.web.jsp.viewtheme_jsp</servlet-name> <servlet-name>net.i2p.router.web.jsp.viewtheme_jsp</servlet-name>

View File

@ -883,6 +883,11 @@
<not><contains string="${javac.compilerargs}" substring="-bootclasspath"/></not> <not><contains string="${javac.compilerargs}" substring="-bootclasspath"/></not>
</condition> </condition>
</fail> </fail>
<fail message="javac.compilerargs7 must contain a -bootclasspath option in override.properties">
<condition>
<not><contains string="${javac.compilerargs7}" substring="-bootclasspath"/></not>
</condition>
</fail>
<fail message="build.built-by must be set in override.properties"> <fail message="build.built-by must be set in override.properties">
<condition> <condition>
<equals arg1="${build.built-by}" arg2="unknown"/> <equals arg1="${build.built-by}" arg2="unknown"/>

View File

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

View File

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

View File

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

View File

@ -9,12 +9,21 @@ import java.io.OutputStreamWriter;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.cert.Certificate; import java.security.cert.Certificate;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateEncodingException;
import java.security.cert.CRLException;
import java.security.cert.X509Certificate; 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 java.util.Locale;
import javax.naming.InvalidNameException; import javax.naming.InvalidNameException;
@ -24,6 +33,7 @@ import javax.security.auth.x500.X500Principal;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.data.Base64; import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.util.Log; import net.i2p.util.Log;
import net.i2p.util.SecureFileOutputStream; import net.i2p.util.SecureFileOutputStream;
import net.i2p.util.SystemVersion; import net.i2p.util.SystemVersion;
@ -33,7 +43,7 @@ import net.i2p.util.SystemVersion;
* *
* @since 0.9.9 * @since 0.9.9
*/ */
public class CertUtil { public final class CertUtil {
private static final int LINE_LENGTH = 64; private static final int LINE_LENGTH = 64;
@ -93,16 +103,7 @@ public class CertUtil {
throws IOException, CertificateEncodingException { throws IOException, CertificateEncodingException {
// Get the encoded form which is suitable for exporting // Get the encoded form which is suitable for exporting
byte[] buf = cert.getEncoded(); byte[] buf = cert.getEncoded();
PrintWriter wr = new PrintWriter(new OutputStreamWriter(out, "UTF-8")); writePEM(buf, "CERTIFICATE", out);
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);
} }
/** /**
@ -121,13 +122,27 @@ public class CertUtil {
byte[] buf = pk.getEncoded(); byte[] buf = pk.getEncoded();
if (buf == null) if (buf == null)
throw new InvalidKeyException("encoding unsupported for this key"); 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")); 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 String b64 = Base64.encode(buf, true); // true = use standard alphabet
for (int i = 0; i < b64.length(); i += LINE_LENGTH) { for (int i = 0; i < b64.length(); i += LINE_LENGTH) {
wr.println(b64.substring(i, Math.min(i + LINE_LENGTH, b64.length()))); wr.println(b64.substring(i, Math.min(i + LINE_LENGTH, b64.length())));
} }
wr.println("-----END PRIVATE KEY-----"); wr.println("-----END " + what + "-----");
wr.flush(); wr.flush();
if (wr.checkError()) if (wr.checkError())
throw new IOException("Failed write to " + out); throw new IOException("Failed write to " + out);
@ -235,4 +250,145 @@ public class CertUtil {
try { if (fis != null) fis.close(); } catch (IOException foo) {} 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);
}
}
****/
} }

View File

@ -36,7 +36,7 @@ import net.i2p.util.SystemVersion;
* *
* @author jrandom, thecrypto * @author jrandom, thecrypto
*/ */
public class CryptixAESEngine extends AESEngine { public final class CryptixAESEngine extends AESEngine {
private final static CryptixRijndael_Algorithm _algo = new CryptixRijndael_Algorithm(); private final static CryptixRijndael_Algorithm _algo = new CryptixRijndael_Algorithm();
// keys are now cached in the SessionKey objects // keys are now cached in the SessionKey objects
//private CryptixAESKeyCache _cache; //private CryptixAESKeyCache _cache;

View File

@ -34,6 +34,7 @@ import java.math.BigInteger;
import java.security.spec.AlgorithmParameterSpec; import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.DSAParameterSpec; import java.security.spec.DSAParameterSpec;
import net.i2p.crypto.elgamal.spec.ElGamalParameterSpec;
import net.i2p.util.NativeBigInteger; import net.i2p.util.NativeBigInteger;
/** /**
@ -43,7 +44,7 @@ import net.i2p.util.NativeBigInteger;
* See also: ECConstants, RSAConstants * See also: ECConstants, RSAConstants
* *
*/ */
public class CryptoConstants { public final class CryptoConstants {
public static final BigInteger dsap = new NativeBigInteger( public static final BigInteger dsap = new NativeBigInteger(
"9c05b2aa960d9b97b8931963c9cc9e8c3026e9b8ed92fad0a69cc886d5bf8015fcadae31" "9c05b2aa960d9b97b8931963c9cc9e8c3026e9b8ed92fad0a69cc886d5bf8015fcadae31"
+ "a0ad18fab3f01b00a358de237655c4964afaa2b337e96ad316b9fb1cc564b5aec5b69a9f" + "a0ad18fab3f01b00a358de237655c4964afaa2b337e96ad316b9fb1cc564b5aec5b69a9f"
@ -78,6 +79,11 @@ public class CryptoConstants {
*/ */
public static final DSAParameterSpec DSA_SHA1_SPEC = new DSAParameterSpec(dsap, dsaq, dsag); 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 * This will be org.bouncycastle.jce.spec.ElgamalParameterSpec
* if BC is available, otherwise it * if BC is available, otherwise it
@ -98,11 +104,11 @@ public class CryptoConstants {
} catch (Exception e) { } catch (Exception e) {
//System.out.println("BC ElG spec failed"); //System.out.println("BC ElG spec failed");
//e.printStackTrace(); //e.printStackTrace();
spec = new ElGamalParameterSpec(elgp, elgg); spec = I2P_ELGAMAL_2048_SPEC;
} }
} else { } else {
//System.out.println("BC not available"); //System.out.println("BC not available");
spec = new ElGamalParameterSpec(elgp, elgg); spec = I2P_ELGAMAL_2048_SPEC;
} }
ELGAMAL_2048_SPEC = spec; ELGAMAL_2048_SPEC = spec;
} }

View File

@ -72,7 +72,7 @@ import net.i2p.util.NativeBigInteger;
* *
* EdDSA support added in 0.9.15 * EdDSA support added in 0.9.15
*/ */
public class DSAEngine { public final class DSAEngine {
private final Log _log; private final Log _log;
private final I2PAppContext _context; private final I2PAppContext _context;
@ -234,7 +234,7 @@ public class DSAEngine {
BigInteger s = new NativeBigInteger(1, sbytes); BigInteger s = new NativeBigInteger(1, sbytes);
BigInteger r = new NativeBigInteger(1, rbytes); BigInteger r = new NativeBigInteger(1, rbytes);
BigInteger y = new NativeBigInteger(1, verifyingKey.getData()); BigInteger y = new NativeBigInteger(1, verifyingKey.getData());
BigInteger w = null; BigInteger w;
try { try {
w = s.modInverse(CryptoConstants.dsaq); w = s.modInverse(CryptoConstants.dsaq);
} catch (ArithmeticException ae) { } catch (ArithmeticException ae) {
@ -402,8 +402,7 @@ public class DSAEngine {
long start = _context.clock().now(); long start = _context.clock().now();
BigInteger k; BigInteger k;
boolean ok;
boolean ok = false;
do { do {
k = new BigInteger(160, _context.random()); k = new BigInteger(160, _context.random());
ok = k.compareTo(CryptoConstants.dsaq) != 1; ok = k.compareTo(CryptoConstants.dsaq) != 1;
@ -516,15 +515,20 @@ public class DSAEngine {
if (type == SigType.DSA_SHA1) if (type == SigType.DSA_SHA1)
return altVerifySigSHA1(signature, data, offset, len, verifyingKey); 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); PublicKey pubKey = SigUtil.toJavaKey(verifyingKey);
jsig.initVerify(pubKey); byte[] sigbytes = SigUtil.toJavaSig(signature);
jsig.update(data, offset, len); boolean rv;
boolean rv = jsig.verify(SigUtil.toJavaSig(signature)); 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; return rv;
} }
@ -564,15 +568,21 @@ public class DSAEngine {
if (type.getHashLen() != hashlen) if (type.getHashLen() != hashlen)
throw new IllegalArgumentException("type mismatch hash=" + hash.getClass() + " key=" + type); throw new IllegalArgumentException("type mismatch hash=" + hash.getClass() + " key=" + type);
String algo = getRawAlgo(type); byte[] sigbytes = SigUtil.toJavaSig(signature);
java.security.Signature jsig; boolean rv;
if (type.getBaseAlgorithm() == SigAlgo.EdDSA) if (type.getBaseAlgorithm() == SigAlgo.EdDSA) {
jsig = new EdDSAEngine(); // Ignore algo, EdDSAKey includes a hash specification. // take advantage of one-shot mode
else // Ignore algo, EdDSAKey includes a hash specification.
jsig = java.security.Signature.getInstance(algo); EdDSAEngine jsig = new EdDSAEngine();
jsig.initVerify(pubKey); jsig.initVerify(pubKey);
jsig.update(hash.getData()); rv = jsig.verifyOneShot(hash.getData(), sigbytes);
boolean rv = jsig.verify(SigUtil.toJavaSig(signature)); } 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; return rv;
} }
@ -607,15 +617,20 @@ public class DSAEngine {
if (type == SigType.DSA_SHA1) if (type == SigType.DSA_SHA1)
return altSignSHA1(data, offset, len, privateKey); 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); PrivateKey privKey = SigUtil.toJavaKey(privateKey);
jsig.initSign(privKey, _context.random()); byte[] sigbytes;
jsig.update(data, offset, len); if (type.getBaseAlgorithm() == SigAlgo.EdDSA) {
return SigUtil.fromJavaSig(jsig.sign(), type); // 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) if (type.getHashLen() != hashlen)
throw new IllegalArgumentException("type mismatch hash=" + hash.getClass() + " key=" + type); throw new IllegalArgumentException("type mismatch hash=" + hash.getClass() + " key=" + type);
java.security.Signature jsig; byte[] sigbytes;
if (type.getBaseAlgorithm() == SigAlgo.EdDSA) if (type.getBaseAlgorithm() == SigAlgo.EdDSA) {
jsig = new EdDSAEngine(); // Ignore algo, EdDSAKey includes a hash specification. // take advantage of one-shot mode
else // Ignore algo, EdDSAKey includes a hash specification.
jsig = java.security.Signature.getInstance(algo); EdDSAEngine jsig = new EdDSAEngine();
jsig.initSign(privKey, _context.random()); jsig.initSign(privKey);
jsig.update(hash.getData()); sigbytes = jsig.signOneShot(hash.getData());
return SigUtil.fromJavaSig(jsig.sign(), type); } 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);
} }
/** /**

View File

@ -20,7 +20,7 @@ import net.i2p.util.NativeBigInteger;
* *
* @since 0.9.9 * @since 0.9.9
*/ */
class ECConstants { final class ECConstants {
private static final boolean DEBUG = false; private static final boolean DEBUG = false;

View File

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

View File

@ -32,7 +32,7 @@ import net.i2p.util.SimpleByteCache;
* *
* No, this does not extend AESEngine or CryptixAESEngine. * No, this does not extend AESEngine or CryptixAESEngine.
*/ */
public class ElGamalAESEngine { public final class ElGamalAESEngine {
private final Log _log; private final Log _log;
private final static int MIN_ENCRYPTED_SIZE = 80; // smallest possible resulting size private final static int MIN_ENCRYPTED_SIZE = 80; // smallest possible resulting size
private final I2PAppContext _context; private final I2PAppContext _context;

View File

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

View File

@ -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;
}
}

View File

@ -27,7 +27,7 @@ public enum EncType {
* This is the default. * This is the default.
* Pubkey 256 bytes, privkey 256 bytes. * 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; */ /** Pubkey 64 bytes; privkey 32 bytes; */
EC_P256(1, 64, 32, EncAlgo.EC, "EC/None/NoPadding", ECConstants.P256_SPEC, "0.9.20"), EC_P256(1, 64, 32, EncAlgo.EC, "EC/None/NoPadding", ECConstants.P256_SPEC, "0.9.20"),

View File

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

View File

@ -34,6 +34,7 @@ import net.i2p.I2PAppContext;
import net.i2p.crypto.eddsa.EdDSAPrivateKey; import net.i2p.crypto.eddsa.EdDSAPrivateKey;
import net.i2p.crypto.eddsa.EdDSAPublicKey; import net.i2p.crypto.eddsa.EdDSAPublicKey;
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
import net.i2p.crypto.provider.I2PProvider;
import net.i2p.data.Hash; import net.i2p.data.Hash;
import net.i2p.data.PrivateKey; import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey; 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 /** Define a way of generating asymmetrical key pairs as well as symmetrical keys
* @author jrandom * @author jrandom
*/ */
public class KeyGenerator { public final class KeyGenerator {
private final I2PAppContext _context; private final I2PAppContext _context;
static {
I2PProvider.addProvider();
}
public KeyGenerator(I2PAppContext context) { public KeyGenerator(I2PAppContext context) {
_context = context; _context = context;
} }
@ -208,10 +213,10 @@ public class KeyGenerator {
SimpleDataStructure[] keys = new SimpleDataStructure[2]; SimpleDataStructure[] keys = new SimpleDataStructure[2];
BigInteger x = null; 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 { do {
x = new NativeBigInteger(160, _context.random()); 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); BigInteger y = CryptoConstants.dsag.modPow(x, CryptoConstants.dsap);
keys[0] = new SigningPublicKey(); keys[0] = new SigningPublicKey();

View File

@ -13,10 +13,13 @@ import java.security.cert.Certificate;
import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException; import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.crypto.provider.I2PProvider;
import net.i2p.data.Base32; import net.i2p.data.Base32;
import net.i2p.util.Log; import net.i2p.util.Log;
import net.i2p.util.SecureDirectory; import net.i2p.util.SecureDirectory;
@ -29,7 +32,7 @@ import net.i2p.util.SystemVersion;
* *
* @since 0.9.9 * @since 0.9.9
*/ */
public class KeyStoreUtil { public final class KeyStoreUtil {
public static boolean _blacklistLogged; 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_SIZE = 2048;
private static final int DEFAULT_KEY_VALID_DAYS = 3652; // 10 years 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... * No reports of some of these in a Java keystore but just to be safe...
* CNNIC ones are in Ubuntu keystore. * 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 keytool = (new File(System.getProperty("java.home"), "bin/keytool")).getAbsolutePath();
String[] args = new String[] { List<String> a = new ArrayList<String>(32);
keytool, a.add(keytool);
"-genkey", // -genkeypair preferred in newer keytools, but this works with more a.add("-genkey"); // -genkeypair preferred in newer keytools, but this works with more
"-storetype", KeyStore.getDefaultType(), //a.add("-v"); // verbose, gives you a stack trace on exception
"-keystore", ks.getAbsolutePath(), a.add("-storetype"); a.add(KeyStore.getDefaultType());
"-storepass", ksPW, a.add("-keystore"); a.add(ks.getAbsolutePath());
"-alias", alias, a.add("-storepass"); a.add(ksPW);
"-dname", "CN=" + cname + ",OU=" + ou + ",O=I2P Anonymous Network,L=XX,ST=XX,C=XX", a.add("-alias"); a.add(alias);
"-validity", Integer.toString(validDays), // 10 years a.add("-dname"); a.add("CN=" + cname + ",OU=" + ou + ",O=I2P Anonymous Network,L=XX,ST=XX,C=XX");
"-keyalg", keyAlg, a.add("-validity"); a.add(Integer.toString(validDays)); // 10 years
"-sigalg", getSigAlg(keySize, keyAlg), a.add("-keyalg"); a.add(keyAlg);
"-keysize", Integer.toString(keySize), a.add("-sigalg"); a.add(getSigAlg(keySize, keyAlg));
"-keypass", keyPW 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 // TODO pipe key password to process; requires ShellCommand enhancements
boolean success = (new ShellCommand()).executeSilentAndWaitTimed(args, 240); boolean success = (new ShellCommand()).executeSilentAndWaitTimed(args, 240);
if (success) { if (success) {
@ -514,6 +530,8 @@ public class KeyStoreUtil {
private static String getSigAlg(int size, String keyalg) { private static String getSigAlg(int size, String keyalg) {
if (keyalg.equals("EC")) if (keyalg.equals("EC"))
keyalg = "ECDSA"; keyalg = "ECDSA";
else if (keyalg.equals("Ed"))
keyalg = "EdDSA";
String hash; String hash;
if (keyalg.equals("ECDSA")) { if (keyalg.equals("ECDSA")) {
if (size <= 256) if (size <= 256)
@ -522,6 +540,8 @@ public class KeyStoreUtil {
hash = "SHA384"; hash = "SHA384";
else else
hash = "SHA512"; hash = "SHA512";
} else if (keyalg.equals("EdDSA")) {
hash = "SHA512";
} else { } else {
if (size <= 1024) if (size <= 1024)
hash = "SHA1"; 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 * Get a cert out of a keystore
* *
@ -641,11 +758,31 @@ public class KeyStoreUtil {
* Usage: KeyStoreUtil (loads from system keystore) * Usage: KeyStoreUtil (loads from system keystore)
* KeyStoreUtil foo.ks (loads from system keystore, and from foo.ks keystore if exists, else creates empty) * 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 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) { public static void main(String[] args) {
File ksf = (args.length > 0) ? new File(args[0]) : null;
try { 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()) { if (ksf != null && !ksf.exists()) {
createKeyStore(ksf, DEFAULT_KEYSTORE_PASSWORD); createKeyStore(ksf, DEFAULT_KEYSTORE_PASSWORD);
System.out.println("Created empty keystore " + ksf); System.out.println("Created empty keystore " + ksf);
@ -674,5 +811,63 @@ public class KeyStoreUtil {
e.printStackTrace(); e.printStackTrace();
} }
} }
private static void testImport(String[] args) throws Exception {
File ksf = new File(args[1]);
InputStream in = new FileInputStream(args[2]);
String alias = args[3];
String pw = args[4];
importPrivateKey(ksf, DEFAULT_KEYSTORE_PASSWORD, alias, pw, in);
}
private static void testExport(String[] args) throws Exception {
File ksf = new File(args[1]);
String alias = args[2];
String pw = args[3];
exportPrivateKey(ksf, DEFAULT_KEYSTORE_PASSWORD, alias, pw, System.out);
}
private static void testKeygen(String[] args) throws Exception {
File ksf = new File(args[1]);
String alias = args[2];
String pw = args[3];
boolean ok = createKeys(ksf, DEFAULT_KEYSTORE_PASSWORD, alias, "test cname", "test ou",
//DEFAULT_KEY_VALID_DAYS, "EdDSA", 256, pw);
DEFAULT_KEY_VALID_DAYS, "ElGamal", 2048, pw);
System.out.println("genkey ok? " + ok);
}
private static void testKeygen2(String[] args) throws Exception {
// keygen test using the I2PProvider
//SigType type = SigType.EdDSA_SHA512_Ed25519;
SigType type = SigType.ElGamal_SHA256_MODP2048;
java.security.KeyPairGenerator kpg = java.security.KeyPairGenerator.getInstance(type.getBaseAlgorithm().getName());
kpg.initialize(type.getParams());
java.security.KeyPair kp = kpg.generateKeyPair();
java.security.PublicKey jpub = kp.getPublic();
java.security.PrivateKey jpriv = kp.getPrivate();
System.out.println("Encoded private key:");
System.out.println(net.i2p.util.HexDump.dump(jpriv.getEncoded()));
System.out.println("Encoded public key:");
System.out.println(net.i2p.util.HexDump.dump(jpub.getEncoded()));
java.security.Signature jsig = java.security.Signature.getInstance(type.getAlgorithmName());
jsig.initSign(jpriv);
byte[] data = new byte[111];
net.i2p.util.RandomSource.getInstance().nextBytes(data);
jsig.update(data);
byte[] bsig = jsig.sign();
System.out.println("Encoded signature:");
System.out.println(net.i2p.util.HexDump.dump(bsig));
jsig.initVerify(jpub);
jsig.update(data);
boolean ok = jsig.verify(bsig);
System.out.println("verify passed? " + ok);
net.i2p.data.Signature sig = SigUtil.fromJavaSig(bsig, type);
System.out.println("Signature test: " + sig);
}
****/ ****/
} }

View File

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

View File

@ -1,7 +1,5 @@
package net.i2p.crypto; package net.i2p.crypto;
import gnu.crypto.hash.Sha256Standalone;
import java.security.DigestException; import java.security.DigestException;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
@ -14,25 +12,13 @@ import net.i2p.data.Hash;
* Defines a wrapper for SHA-256 operation. * Defines a wrapper for SHA-256 operation.
* *
* As of release 0.8.7, uses java.security.MessageDigest by default. * 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} * GNU-Crypto {@link gnu.crypto.hash.Sha256Standalone}
* is deprecated.
*/ */
public final class SHA256Generator { public final class SHA256Generator {
private final LinkedBlockingQueue<MessageDigest> _digests; 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 * @param context unused
*/ */
@ -96,45 +82,14 @@ public final class SHA256Generator {
} }
/** /**
* Return a new MessageDigest from the system libs unless unavailable * Return a new MessageDigest from the system libs.
* in this JVM, in that case return a wrapped GNU Sha256Standalone
* @since 0.8.7, public since 0.8.8 for FortunaStandalone * @since 0.8.7, public since 0.8.8 for FortunaStandalone
*/ */
public static MessageDigest getDigestInstance() { public static MessageDigest getDigestInstance() {
if (!_useGnu) { try {
try { return MessageDigest.getInstance("SHA-256");
return MessageDigest.getInstance("SHA-256"); } catch (NoSuchAlgorithmException e) {
} catch (NoSuchAlgorithmException e) {} throw new RuntimeException(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);
} }
} }

View File

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

View File

@ -10,7 +10,15 @@ public enum SigAlgo {
DSA("DSA"), DSA("DSA"),
EC("EC"), EC("EC"),
EdDSA("EdDSA"), 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; private final String name;

View File

@ -32,20 +32,20 @@ public enum SigType {
* Pubkey 128 bytes; privkey 20 bytes; hash 20 bytes; sig 40 bytes * Pubkey 128 bytes; privkey 20 bytes; hash 20 bytes; sig 40 bytes
* @since 0.9.8 * @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 */ /** 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 */ /** 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 */ /** 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 */ /** 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 */ /** 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 */ /** 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 * Pubkey 32 bytes; privkey 32 bytes; hash 64 bytes; sig 64 bytes
@ -55,8 +55,17 @@ public enum SigType {
* @since 0.9.15 * @since 0.9.15
*/ */
EdDSA_SHA512_Ed25519(7, 32, 32, 64, 64, SigAlgo.EdDSA, "SHA-512", "SHA512withEdDSA", 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.................... // TESTING....................
@ -99,12 +108,12 @@ public enum SigType {
private final int code, pubkeyLen, privkeyLen, hashLen, sigLen; private final int code, pubkeyLen, privkeyLen, hashLen, sigLen;
private final SigAlgo base; private final SigAlgo base;
private final String digestName, algoName, since; private final String digestName, algoName, oid, since;
private final AlgorithmParameterSpec params; private final AlgorithmParameterSpec params;
private final boolean isAvail; private final boolean isAvail;
SigType(int cod, int pubLen, int privLen, int hLen, int sLen, SigAlgo baseAlgo, 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; code = cod;
pubkeyLen = pubLen; pubkeyLen = pubLen;
privkeyLen = privLen; privkeyLen = privLen;
@ -114,6 +123,7 @@ public enum SigType {
digestName = mdName; digestName = mdName;
algoName = aName; algoName = aName;
params = pSpec; params = pSpec;
this.oid = oid;
since = supportedSince; since = supportedSince;
isAvail = x_isAvailable(); isAvail = x_isAvailable();
} }
@ -183,6 +193,15 @@ public enum SigType {
return since; return since;
} }
/**
* The OID for the signature.
*
* @since 0.9.25
*/
public String getOID() {
return oid;
}
/** /**
* @since 0.9.12 * @since 0.9.12
* @return true if supported in this JVM * @return true if supported in this JVM
@ -274,6 +293,8 @@ public enum SigType {
// handle mixed-case enum // handle mixed-case enum
if (uc.equals("EDDSA_SHA512_ED25519")) if (uc.equals("EDDSA_SHA512_ED25519"))
return EdDSA_SHA512_Ed25519; return EdDSA_SHA512_Ed25519;
if (uc.equals("EDDSA_SHA512_ED25519PH"))
return EdDSA_SHA512_Ed25519ph;
return valueOf(uc); return valueOf(uc);
} catch (IllegalArgumentException iae) { } catch (IllegalArgumentException iae) {
try { try {

View File

@ -50,7 +50,7 @@ import net.i2p.util.NativeBigInteger;
* *
* @since 0.9.9, public since 0.9.12 * @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<SigningPublicKey, ECPublicKey> _ECPubkeyCache = new LHMCache<SigningPublicKey, ECPublicKey>(64);
private static final Map<SigningPrivateKey, ECPrivateKey> _ECPrivkeyCache = new LHMCache<SigningPrivateKey, ECPrivateKey>(16); 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"); throw new IllegalArgumentException("Unknown RSA type");
return fromJavaKey(k, 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: case RSA:
return fromJavaKey((RSAPublicKey) pk, type); return fromJavaKey((RSAPublicKey) pk, type);
default: default:
throw new IllegalArgumentException(); throw new IllegalArgumentException("Unknown type: " + type);
} }
} }
@ -209,7 +209,7 @@ public class SigUtil {
throw new IllegalArgumentException("Unknown RSA type"); throw new IllegalArgumentException("Unknown RSA type");
return fromJavaKey(k, 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: case RSA:
return fromJavaKey((RSAPrivateKey) pk, type); return fromJavaKey((RSAPrivateKey) pk, type);
default: default:
throw new IllegalArgumentException(); throw new IllegalArgumentException("Unknown type: " + type);
} }
} }
@ -549,9 +549,13 @@ public class SigUtil {
/** /**
* Split a byte array into two BigIntegers * Split a byte array into two BigIntegers
* @param b length must be even
* @return array of two BigIntegers * @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; int sublen = b.length / 2;
byte[] bx = new byte[sublen]; byte[] bx = new byte[sublen];
byte[] by = new byte[sublen]; byte[] by = new byte[sublen];
@ -565,9 +569,12 @@ public class SigUtil {
/** /**
* Combine two BigIntegers of nominal length = len / 2 * Combine two BigIntegers of nominal length = len / 2
* @return array of exactly len bytes * @return array of exactly len bytes
* @since 0.9.9
*/ */
private static byte[] combine(BigInteger x, BigInteger y, int len) private static byte[] combine(BigInteger x, BigInteger y, int len)
throws InvalidKeyException { throws InvalidKeyException {
if ((len & 0x01) != 0)
throw new InvalidKeyException("length must be even");
int sublen = len / 2; int sublen = len / 2;
byte[] b = new byte[len]; byte[] b = new byte[len];
byte[] bx = rectify(x, sublen); 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 * 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 } * SEQUENCE ::= { r INTEGER, s INTEGER }
* *
* http://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One * http://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One
@ -619,6 +627,7 @@ public class SigUtil {
* 02 -- tag indicating INTEGER * 02 -- tag indicating INTEGER
* xx - length in octets * xx - length in octets
* xxxxxx - value * xxxxxx - value
*</pre>
* *
* Convert to BigInteger and back so we have the minimum length representation, as required. * Convert to BigInteger and back so we have the minimum length representation, as required.
* r and s are always non-negative. * 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 * Only supports sigs up to about 252 bytes. See code to fix BER encoding for this before you
* add a SigType with bigger signatures. * add a SigType with bigger signatures.
* *
* @param sig length must be even
* @throws IllegalArgumentException if too big * @throws IllegalArgumentException if too big
* @since 0.8.7, moved to SigUtil in 0.9.9 * @since 0.8.7, moved to SigUtil in 0.9.9
*/ */
private static byte[] sigBytesToASN1(byte[] sig) { private static byte[] sigBytesToASN1(byte[] sig) {
//System.out.println("pre TO asn1\n" + net.i2p.util.HexDump.dump(sig)); BigInteger[] rs = split(sig);
int len = sig.length; return sigBytesToASN1(rs[0], rs[1]);
int sublen = len / 2; }
byte[] tmp = new byte[sublen];
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(); byte[] rb = r.toByteArray();
if (rb.length > 127) if (rb.length > 127) {
throw new IllegalArgumentException("FIXME R length > 127"); extra++;
System.arraycopy(sig, sublen, tmp, 0, sublen); if (rb.length > 255)
BigInteger s = new BigInteger(1, tmp); extra++;
}
byte[] sb = s.toByteArray(); byte[] sb = s.toByteArray();
if (sb.length > 127) if (sb.length > 127) {
throw new IllegalArgumentException("FIXME S length > 127"); extra++;
int seqlen = rb.length + sb.length + 4; if (sb.length > 255)
if (seqlen > 255) extra++;
throw new IllegalArgumentException("FIXME seq length > 255"); }
int seqlen = rb.length + sb.length + extra;
int totlen = seqlen + 2; int totlen = seqlen + 2;
if (seqlen > 127) if (seqlen > 127) {
totlen++; totlen++;
if (seqlen > 255)
totlen++;
}
byte[] rv = new byte[totlen]; byte[] rv = new byte[totlen];
int idx = 0; int idx = 0;
rv[idx++] = 0x30; rv[idx++] = 0x30;
if (seqlen > 127) idx = intToASN1(rv, idx, seqlen);
rv[idx++] =(byte) 0x81;
rv[idx++] = (byte) seqlen;
rv[idx++] = 0x02; rv[idx++] = 0x02;
rv[idx++] = (byte) rb.length; idx = intToASN1(rv, idx, rb.length);
System.arraycopy(rb, 0, rv, idx, rb.length); System.arraycopy(rb, 0, rv, idx, rb.length);
idx += rb.length; idx += rb.length;
rv[idx++] = 0x02; rv[idx++] = 0x02;
rv[idx++] = (byte) sb.length; idx = intToASN1(rv, idx, sb.length);
System.arraycopy(sb, 0, 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)); //System.out.println("post TO asn1\n" + net.i2p.util.HexDump.dump(rv));
@ -673,10 +708,34 @@ public class SigUtil {
} }
/** /**
* See above. * Output an length or integer value in ASN.1
* Only supports sigs up to about 252 bytes. See code to fix BER encoding for bigger than that. * 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 * @since 0.8.7, moved to SigUtil in 0.9.9
*/ */
private static byte[] aSN1ToSigBytes(byte[] asn, int len) private static byte[] aSN1ToSigBytes(byte[] asn, int len)
@ -693,8 +752,17 @@ public class SigUtil {
byte[] rv = new byte[len]; byte[] rv = new byte[len];
int sublen = len / 2; int sublen = len / 2;
int rlen = asn[++idx]; int rlen = asn[++idx];
if ((rlen & 0x80) != 0) if ((rlen & 0x80) != 0) {
throw new SignatureException("FIXME R length > 127"); 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) if ((asn[++idx] & 0x80) != 0)
throw new SignatureException("R is negative"); throw new SignatureException("R is negative");
if (rlen > sublen + 1) if (rlen > sublen + 1)
@ -704,24 +772,47 @@ public class SigUtil {
else else
System.arraycopy(asn, idx, rv, sublen - rlen, rlen); System.arraycopy(asn, idx, rv, sublen - rlen, rlen);
idx += rlen; idx += rlen;
int slenloc = idx + 1;
if (asn[idx] != 0x02) if (asn[idx] != 0x02)
throw new SignatureException("asn[s] = " + (asn[idx] & 0xff)); throw new SignatureException("asn[s] = " + (asn[idx] & 0xff));
int slen = asn[slenloc]; int slen = asn[++idx];
if ((slen & 0x80) != 0) if ((slen & 0x80) != 0) {
throw new SignatureException("FIXME S length > 127"); if ((slen & 0xff) == 0x81) {
if ((asn[slenloc + 1] & 0x80) != 0) 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"); throw new SignatureException("S is negative");
if (slen > sublen + 1) if (slen > sublen + 1)
throw new SignatureException("S too big " + slen); throw new SignatureException("S too big " + slen);
if (slen == sublen + 1) if (slen == sublen + 1)
System.arraycopy(asn, slenloc + 2, rv, sublen, sublen); System.arraycopy(asn, idx + 1, rv, sublen, sublen);
else 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)); //System.out.println("post from asn1\n" + net.i2p.util.HexDump.dump(rv));
return 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() { public static void clearCaches() {
synchronized(_ECPubkeyCache) { synchronized(_ECPubkeyCache) {
_ECPubkeyCache.clear(); _ECPubkeyCache.clear();

View File

@ -35,7 +35,7 @@ import net.i2p.util.SystemVersion;
* *
* @author jrandom * @author jrandom
*/ */
class YKGenerator { final class YKGenerator {
//private final static Log _log = new Log(YKGenerator.class); //private final static Log _log = new Log(YKGenerator.class);
private final int MIN_NUM_BUILDERS; private final int MIN_NUM_BUILDERS;
private final int MAX_NUM_BUILDERS; private final int MAX_NUM_BUILDERS;

View File

@ -2,6 +2,7 @@ package net.i2p.crypto.eddsa;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
@ -9,6 +10,7 @@ import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.Signature; import java.security.Signature;
import java.security.SignatureException; import java.security.SignatureException;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Arrays; import java.util.Arrays;
import net.i2p.crypto.eddsa.math.Curve; 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; 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 * @since 0.9.15
* @author str4d * @author str4d
* *
*/ */
public class EdDSAEngine extends Signature { public final class EdDSAEngine extends Signature {
private MessageDigest digest; private MessageDigest digest;
private final ByteArrayOutputStream baos; private ByteArrayOutputStream baos;
private EdDSAKey key; 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. * No specific hash requested, allows any EdDSA key.
*/ */
public EdDSAEngine() { public EdDSAEngine() {
super("EdDSA"); super("EdDSA");
baos = new ByteArrayOutputStream(256);
} }
/** /**
@ -42,12 +91,21 @@ public class EdDSAEngine extends Signature {
this.digest = digest; this.digest = digest;
} }
@Override /**
protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException { * @since 0.9.25
*/
private void reset() {
if (digest != null) if (digest != null)
digest.reset(); 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) { if (privateKey instanceof EdDSAPrivateKey) {
EdDSAPrivateKey privKey = (EdDSAPrivateKey) privateKey; EdDSAPrivateKey privKey = (EdDSAPrivateKey) privateKey;
key = privKey; key = privKey;
@ -61,21 +119,22 @@ public class EdDSAEngine extends Signature {
} }
} else if (!key.getParams().getHashAlgorithm().equals(digest.getAlgorithm())) } else if (!key.getParams().getHashAlgorithm().equals(digest.getAlgorithm()))
throw new InvalidKeyException("Key hash algorithm does not match chosen digest"); 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 private void digestInitSign(EdDSAPrivateKey privKey) {
// r = H(h_b,...,h_2b-1,M) // Preparing for hash
int b = privKey.getParams().getCurve().getField().getb(); // r = H(h_b,...,h_2b-1,M)
digest.update(privKey.getH(), b/8, b/4 - b/8); int b = privKey.getParams().getCurve().getField().getb();
} else digest.update(privKey.getH(), b/8, b/4 - b/8);
throw new InvalidKeyException("cannot identify EdDSA private key.");
} }
@Override @Override
protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException { protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {
if (digest != null) reset();
digest.reset();
baos.reset();
if (publicKey instanceof EdDSAPublicKey) { if (publicKey instanceof EdDSAPublicKey) {
key = (EdDSAPublicKey) publicKey; key = (EdDSAPublicKey) publicKey;
@ -88,34 +147,79 @@ public class EdDSAEngine extends Signature {
} }
} else if (!key.getParams().getHashAlgorithm().equals(digest.getAlgorithm())) } else if (!key.getParams().getHashAlgorithm().equals(digest.getAlgorithm()))
throw new InvalidKeyException("Key hash algorithm does not match chosen digest"); throw new InvalidKeyException("Key hash algorithm does not match chosen digest");
} else } else {
throw new InvalidKeyException("cannot identify EdDSA public key."); throw new InvalidKeyException("cannot identify EdDSA public key: " + publicKey.getClass());
}
} }
/**
* @throws SignatureException if in one-shot mode
*/
@Override @Override
protected void engineUpdate(byte b) throws SignatureException { protected void engineUpdate(byte b) throws SignatureException {
// We need to store the message because it is used in several hashes if (oneShotMode)
// XXX Can this be done more efficiently? throw new SignatureException("unsupported in one-shot mode");
if (baos == null)
baos = new ByteArrayOutputStream(256);
baos.write(b); baos.write(b);
} }
/**
* @throws SignatureException if one-shot rules are violated
*/
@Override @Override
protected void engineUpdate(byte[] b, int off, int len) protected void engineUpdate(byte[] b, int off, int len)
throws SignatureException { throws SignatureException {
// We need to store the message because it is used in several hashes if (oneShotMode) {
// XXX Can this be done more efficiently? if (oneShotBytes != null)
baos.write(b, off, len); 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 @Override
protected byte[] engineSign() throws SignatureException { 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(); Curve curve = key.getParams().getCurve();
ScalarOps sc = key.getParams().getScalarOps(); ScalarOps sc = key.getParams().getScalarOps();
byte[] a = ((EdDSAPrivateKey) key).geta(); 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) // 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 // r mod l
// Reduces r from 64 bytes to 32 bytes // 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 // S = (r + H(Rbar,Abar,M)*a) mod l
digest.update(Rbyte); digest.update(Rbyte);
digest.update(((EdDSAPrivateKey) key).getAbyte()); digest.update(((EdDSAPrivateKey) key).getAbyte());
byte[] h = digest.digest(message); digest.update(message, offset, length);
byte[] h = digest.digest();
h = sc.reduce(h); h = sc.reduce(h);
byte[] S = sc.multiplyAndAdd(h, a, r); byte[] S = sc.multiplyAndAdd(h, a, r);
@ -141,6 +246,14 @@ public class EdDSAEngine extends Signature {
@Override @Override
protected boolean engineVerify(byte[] sigBytes) throws SignatureException { 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(); Curve curve = key.getParams().getCurve();
int b = curve.getField().getb(); int b = curve.getField().getb();
if (sigBytes.length != b/4) if (sigBytes.length != b/4)
@ -150,8 +263,24 @@ public class EdDSAEngine extends Signature {
digest.update(sigBytes, 0, b/8); digest.update(sigBytes, 0, b/8);
digest.update(((EdDSAPublicKey) key).getAbyte()); digest.update(((EdDSAPublicKey) key).getAbyte());
// h = H(Rbar,Abar,M) // h = H(Rbar,Abar,M)
byte[] message = baos.toByteArray(); byte[] message;
byte[] h = digest.digest(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 mod l
h = key.getParams().getScalarOps().reduce(h); h = key.getParams().getScalarOps().reduce(h);
@ -171,6 +300,140 @@ public class EdDSAEngine extends Signature {
return true; 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)"> * @deprecated replaced with <a href="#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
*/ */

View File

@ -1,13 +1,24 @@
package net.i2p.crypto.eddsa; package net.i2p.crypto.eddsa;
import java.security.PrivateKey; 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.math.GroupElement;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec; import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
/** /**
* An EdDSA private key. * 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 * @since 0.9.15
* @author str4d * @author str4d
@ -31,6 +42,14 @@ public class EdDSAPrivateKey implements EdDSAKey, PrivateKey {
this.edDsaSpec = spec.getParams(); 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() { public String getAlgorithm() {
return "EdDSA"; return "EdDSA";
} }
@ -39,9 +58,116 @@ public class EdDSAPrivateKey implements EdDSAKey, PrivateKey {
return "PKCS#8"; 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() { public byte[] getEncoded() {
// TODO Auto-generated method stub if (!edDsaSpec.equals(EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512)))
return null; 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() { public EdDSAParameterSpec getParams() {
@ -67,4 +193,26 @@ public class EdDSAPrivateKey implements EdDSAKey, PrivateKey {
public byte[] getAbyte() { public byte[] getAbyte() {
return Abyte; return Abyte;
} }
/**
* @since 0.9.25
*/
@Override
public int hashCode() {
return Arrays.hashCode(seed);
}
/**
* @since 0.9.25
*/
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof EdDSAPrivateKey))
return false;
EdDSAPrivateKey pk = (EdDSAPrivateKey) o;
return Arrays.equals(seed, pk.getSeed()) &&
edDsaSpec.equals(pk.getParams());
}
} }

View File

@ -1,13 +1,23 @@
package net.i2p.crypto.eddsa; package net.i2p.crypto.eddsa;
import java.security.PublicKey; 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.math.GroupElement;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec; import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
/** /**
* An EdDSA public key. * 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 * @since 0.9.15
* @author str4d * @author str4d
@ -27,6 +37,14 @@ public class EdDSAPublicKey implements EdDSAKey, PublicKey {
this.edDsaSpec = spec.getParams(); 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() { public String getAlgorithm() {
return "EdDSA"; return "EdDSA";
} }
@ -35,9 +53,95 @@ public class EdDSAPublicKey implements EdDSAKey, PublicKey {
return "X.509"; 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() { public byte[] getEncoded() {
// TODO Auto-generated method stub if (!edDsaSpec.equals(EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512)))
return null; 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() { public EdDSAParameterSpec getParams() {
@ -55,4 +159,26 @@ public class EdDSAPublicKey implements EdDSAKey, PublicKey {
public byte[] getAbyte() { public byte[] getAbyte() {
return Abyte; return Abyte;
} }
/**
* @since 0.9.25
*/
@Override
public int hashCode() {
return Arrays.hashCode(Abyte);
}
/**
* @since 0.9.25
*/
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof EdDSAPublicKey))
return false;
EdDSAPublicKey pk = (EdDSAPublicKey) o;
return Arrays.equals(Abyte, pk.getAbyte()) &&
edDsaSpec.equals(pk.getParams());
}
} }

View File

@ -7,6 +7,8 @@ import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec; 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.EdDSAPrivateKeySpec;
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
@ -16,22 +18,34 @@ import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
* @author str4d * @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) protected PrivateKey engineGeneratePrivate(KeySpec keySpec)
throws InvalidKeySpecException { throws InvalidKeySpecException {
if (keySpec instanceof EdDSAPrivateKeySpec) { if (keySpec instanceof EdDSAPrivateKeySpec) {
return new EdDSAPrivateKey((EdDSAPrivateKeySpec) keySpec); 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) protected PublicKey engineGeneratePublic(KeySpec keySpec)
throws InvalidKeySpecException { throws InvalidKeySpecException {
if (keySpec instanceof EdDSAPublicKeySpec) { if (keySpec instanceof EdDSAPublicKeySpec) {
return new EdDSAPublicKey((EdDSAPublicKeySpec) keySpec); 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") @SuppressWarnings("unchecked")

View File

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

View File

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

View File

@ -3,6 +3,8 @@ package net.i2p.crypto.eddsa.math;
import java.io.Serializable; import java.io.Serializable;
/** /**
*
* Note: concrete subclasses must implement hashCode() and equals()
* *
* @since 0.9.15 * @since 0.9.15
* *
@ -60,4 +62,6 @@ public abstract class FieldElement implements Serializable {
public abstract FieldElement invert(); public abstract FieldElement invert();
public abstract FieldElement pow22523(); public abstract FieldElement pow22523();
// Note: concrete subclasses must implement hashCode() and equals()
} }

View File

@ -716,6 +716,8 @@ public class GroupElement implements Serializable {
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj == this)
return true;
if (!(obj instanceof GroupElement)) if (!(obj instanceof GroupElement))
return false; return false;
GroupElement ge = (GroupElement) obj; GroupElement ge = (GroupElement) obj;

View File

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

View File

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

View File

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

View File

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

View File

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

View 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");
}
}

View 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");
}
}

View 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);
}
}

View File

@ -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());
}
}

View File

@ -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());
}
}

View 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>

View 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>

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View 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>

View File

@ -3,6 +3,7 @@ package net.i2p.crypto.provider;
import java.security.AccessController; import java.security.AccessController;
import java.security.PrivilegedAction; import java.security.PrivilegedAction;
import java.security.Provider; import java.security.Provider;
import java.security.Security;
/** /**
* @since 0.9.15 * @since 0.9.15
@ -11,6 +12,7 @@ public final class I2PProvider extends Provider {
public static final String PROVIDER_NAME = "I2P"; public static final String PROVIDER_NAME = "I2P";
private static final String INFO = "I2P Security Provider v0.1, implementing" + private static final String INFO = "I2P Security Provider v0.1, implementing" +
"several algorithms used by I2P."; "several algorithms used by I2P.";
private static boolean _installed;
/** /**
* Construct a new provider. This should only be required when * Construct a new provider. This should only be required when
@ -31,15 +33,89 @@ public final class I2PProvider extends Provider {
private void setup() { private void setup() {
// TODO: Implement SPIs for existing code // 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.AES", "net.i2p.crypto.provider.CipherSpi$aesCBC");
//put("Cipher.ElGamal", "net.i2p.crypto.provider.CipherSpi$elGamal"); //put("Cipher.ElGamal", "net.i2p.crypto.provider.CipherSpi$elGamal");
//put("Mac.HmacMD5-I2P", "net.i2p.crypto.provider.MacSpi"); //put("Mac.HmacMD5-I2P", "net.i2p.crypto.provider.MacSpi");
put("MessageDigest.SHA-1", "net.i2p.crypto.SHA1"); put("MessageDigest.SHA-1", "net.i2p.crypto.SHA1");
//put("Signature.SHA1withDSA", "net.i2p.crypto.provider.SignatureSpi"); //put("Signature.SHA1withDSA", "net.i2p.crypto.provider.SignatureSpi");
// EdDSA // EdDSA
// Key OID: 1.3.101.100; Sig OID: 1.3.101.101
put("KeyFactory.EdDSA", "net.i2p.crypto.eddsa.KeyFactory"); put("KeyFactory.EdDSA", "net.i2p.crypto.eddsa.KeyFactory");
put("KeyPairGenerator.EdDSA", "net.i2p.crypto.eddsa.KeyPairGenerator"); put("KeyPairGenerator.EdDSA", "net.i2p.crypto.eddsa.KeyPairGenerator");
put("Signature.SHA512withEdDSA", "net.i2p.crypto.eddsa.EdDSAEngine"); 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);
}
}
}
} }
} }

View File

@ -104,6 +104,17 @@ public class Base64 {
return safeDecode(s, false); 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. */ /** Maximum line length (76) of Base64 output. */
private final static int MAX_LINE_LENGTH = 76; private final static int MAX_LINE_LENGTH = 76;

View File

@ -1324,8 +1324,9 @@ public class DataHelper {
* *
* @param hash null OK * @param hash null OK
* @return null on EOF * @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 { public static String readLine(InputStream in, Sha256Standalone hash) throws IOException {
StringBuilder buf = new StringBuilder(128); StringBuilder buf = new StringBuilder(128);
boolean ok = readLine(in, buf, hash); 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 * @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) * (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 @Deprecated
public static boolean readLine(InputStream in, StringBuffer buf, Sha256Standalone hash) throws IOException { public static boolean readLine(InputStream in, StringBuffer buf, Sha256Standalone hash) throws IOException {
@ -1420,8 +1421,9 @@ public class DataHelper {
* @param hash null OK * @param hash null OK
* @return true if the line was read, false if eof was reached on an empty line * @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) * (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 { public static boolean readLine(InputStream in, StringBuilder buf, Sha256Standalone hash) throws IOException {
int c = -1; int c = -1;
int i = 0; int i = 0;
@ -1463,8 +1465,9 @@ public class DataHelper {
/** /**
* update the hash along the way * 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 { public static void write(OutputStream out, byte data[], Sha256Standalone hash) throws IOException {
hash.update(data); hash.update(data);
out.write(data); out.write(data);

View File

@ -201,7 +201,8 @@ public class RandomSource extends SecureRandom implements EntropyHarvester {
} catch (InterruptedException ie) {} } catch (InterruptedException ie) {}
// why urandom? because /dev/random blocks // 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 // we merge (XOR) in the data from /dev/urandom with our own seedfile
File localFile = new File(_context.getConfigDir(), SEEDFILE); File localFile = new File(_context.getConfigDir(), SEEDFILE);
ok = seedFromFile(localFile, buf) || ok; ok = seedFromFile(localFile, buf) || ok;

View File

@ -51,6 +51,7 @@
release.signer.su3=xxx@mail.i2p release.signer.su3=xxx@mail.i2p
build.built-by=xxx build.built-by=xxx
javac.compilerargs=-bootclasspath /usr/lib/jvm/java-6-openjdk-amd64/jre/lib/rt.jar:/usr/lib/jvm/java-6-openjdk-amd64/jre/lib/jce.jar javac.compilerargs=-bootclasspath /usr/lib/jvm/java-6-openjdk-amd64/jre/lib/rt.jar:/usr/lib/jvm/java-6-openjdk-amd64/jre/lib/jce.jar
javac.compilerargs7=-bootclasspath /usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar:/usr/lib/jvm/java-7-openjdk-amd64/jre/lib/jce.jar
``` ```
5. Copy latest trust list _MTN/monotonerc from website or some other workspace 5. Copy latest trust list _MTN/monotonerc from website or some other workspace
@ -169,7 +170,7 @@
- In the i2p.newsxml branch, edit magnet links, release dates and release - In the i2p.newsxml branch, edit magnet links, release dates and release
number in data/releases.json, and check in number in data/releases.json, and check in
2. Add update torrents to tracker2.postman.i2p and start seeding (su2 and su3) 2. Add i2pupdate-0.9.xx.su3 torrent to tracker2.postman.i2p and start seeding
3. Notify the following people: 3. Notify the following people:
- All in-network update hosts - All in-network update hosts

View File

@ -37,5 +37,46 @@ to serve static html files and images.
<Arg>org.eclipse.jetty.servlet.DefaultServlet</Arg> <Arg>org.eclipse.jetty.servlet.DefaultServlet</Arg>
<Arg>/</Arg> <Arg>/</Arg>
</Call> </Call>
<Call name="addServlet">
<Arg>org.eclipse.jetty.servlet.DefaultServlet</Arg>
<Arg>/</Arg>
</Call>
<Call name="addFilter">
<!-- Add a filter to gzip on-the fly, since if we don't do it, I2P will.
- This lowers the resource usage in the Java process on the client side,
- by pushing the decompression out of Java and into the browser.
- For all the reasons noted in the GzipFilter javadocs, this is normally
- a bad idea for static content, but this is I2P.
- See I2PTunnelHTTPServer for the I2P compressor.
-->
<Arg>
<New class="org.eclipse.jetty.servlet.FilterHolder" >
<Arg>
<New class="org.eclipse.jetty.servlets.GzipFilter" />
</Arg>
<Call name="setInitParameter">
<!-- minimum in Java is 1300 -->
<Arg>minGzipSize</Arg>
<Arg>512</Arg>
</Call>
<Call name="setInitParameter">
<!-- In Java we have a blacklist. This covers the most common cases. -->
<Arg>mimeTypes</Arg>
<Arg>application/pdf,application/x-javascript,application/xhtml+xml,application/xml,image/svg+xml,text/css,text/html,text/plain</Arg>
</Call>
</New>
</Arg>
<Arg>/*</Arg>
<Arg>
<!-- just guessing here -->
<Call class="java.util.EnumSet" name="of" >
<Arg>
<Call class="javax.servlet.DispatcherType" name="valueOf" >
<Arg>REQUEST</Arg>
</Call>
</Arg>
</Call>
</Arg>
</Call>
</Configure> </Configure>

View File

@ -33,4 +33,35 @@ Configure a custom context for the eepsite.
<Arg>org.eclipse.jetty.servlets.CGI</Arg> <Arg>org.eclipse.jetty.servlets.CGI</Arg>
<Arg>/</Arg> <Arg>/</Arg>
</Call> </Call>
<Call name="addFilter">
<!-- See base-context.xml for info.
Unlike for DefaultServlet, there's not even a theoretical
inefficiency for using this.
-->
<Arg>
<New class="org.eclipse.jetty.servlet.FilterHolder" >
<Arg>
<New class="org.eclipse.jetty.servlets.GzipFilter" />
</Arg>
<Call name="setInitParameter">
<Arg>minGzipSize</Arg>
<Arg>512</Arg>
</Call>
<Call name="setInitParameter">
<Arg>mimeTypes</Arg>
<Arg>application/pdf,application/x-javascript,application/xhtml+xml,application/xml,image/svg+xml,text/css,text/html,text/plain</Arg>
</Call>
</New>
</Arg>
<Arg>/*</Arg>
<Arg>
<Call class="java.util.EnumSet" name="of" >
<Arg>
<Call class="javax.servlet.DispatcherType" name="valueOf" >
<Arg>REQUEST</Arg>
</Call>
</Arg>
</Call>
</Arg>
</Call>
</Configure> </Configure>

View File

@ -364,3 +364,4 @@ i2pnews.i2p=XHS99uhrvijk3KxU438LjNf-SMXXiNXsbV8uwHFXdqsDsHPZRdc6LH-hEMGWDR5g2b65
exchanged.i2p=rLFzsOqsfSmJZvg6xvl~ctUulYWwGaM8K~rGs-e4WXkKePTCMOpU8l6LIU-wwDOiUZ7Ve8Y-zWPBVYQjH8~~lgT-BJ81zjP5I6H051KOVaXDChdx5F99mZu0sEjnYoFX484QHsUkFc5GUypqhpv1iwWwdPL7bVNzr1fS6sIZvq7tYWEOymbnifxk2jC0BnjultNPCq1wiI2Y-G66iOHDvuLu5f7RvNGJYlpw0UYNv6g8jUu3gXYjDRMBD5OIxFUJaksfmml2CiaGjrPfXKEXBR4q1CogVruq3r~447VHb32f52aeYszcslNzQjYyFCdipnAi5JiNTFpzTZPMEglt2J3KZYB3SMCmxSLktFI7376c7mT7EbMIFFv1GrmcUy9oIyYasbb82Sec9y0nJ9ahZt0x3iGokAYekXKXq-rGPzgFeBwfuCHzQnLzm1akVyJHoGDdaG0QHJfqfW1sY3F2n1xaWrnKcqIz2ypemxVnTMFKQqm2pdG-dMsXNYiGmZtaBQAEAAcAAA== exchanged.i2p=rLFzsOqsfSmJZvg6xvl~ctUulYWwGaM8K~rGs-e4WXkKePTCMOpU8l6LIU-wwDOiUZ7Ve8Y-zWPBVYQjH8~~lgT-BJ81zjP5I6H051KOVaXDChdx5F99mZu0sEjnYoFX484QHsUkFc5GUypqhpv1iwWwdPL7bVNzr1fS6sIZvq7tYWEOymbnifxk2jC0BnjultNPCq1wiI2Y-G66iOHDvuLu5f7RvNGJYlpw0UYNv6g8jUu3gXYjDRMBD5OIxFUJaksfmml2CiaGjrPfXKEXBR4q1CogVruq3r~447VHb32f52aeYszcslNzQjYyFCdipnAi5JiNTFpzTZPMEglt2J3KZYB3SMCmxSLktFI7376c7mT7EbMIFFv1GrmcUy9oIyYasbb82Sec9y0nJ9ahZt0x3iGokAYekXKXq-rGPzgFeBwfuCHzQnLzm1akVyJHoGDdaG0QHJfqfW1sY3F2n1xaWrnKcqIz2ypemxVnTMFKQqm2pdG-dMsXNYiGmZtaBQAEAAcAAA==
i2pwiki.i2p=Zr1YUKIKooxymZRqLvtfSYuZggqv5txkLE8Ks~FTh3A8JDmhOV8C82dUBKJhzst~Pcbqz7rXc~TPyrqasaQ~LioAji~WLSs8PpmCLVF83edhYpx75Fp23ELboEszHduhKWuibVchPLNa-6nP4F0Ttcr4krTlphe3KveNfZDAbm511zFFlNzPcY4PqOdCRBrp7INiWkVwQxwGWO7jiNYTYa5x4QXJmxIN1YOiNRYQST7THz1aV6219ThsfT9FE5JtiX-epli6PF5ZX9TcVSjHUKZnr8uJLXfh5T4RMVNe1n~KXutMUZwxpFE0scOIez9vhDFd7t0HPIsQUsv7MUBzrz4FM9qol7UUPueSGDRgTOOfXMfj4BDsouiWQC4GcSmH3SflR0Ith9QWKC4u3XYvB7Tw-70QWBEV53hUo6I8YKidV13WgeN9JI3KWTYkMyX-TYjmY9y2q6Xd-Maszv4Tb~NzxQs9CNdu0W-JRSUFOqzgt3l4cx0K1pmx4p0tM5dLBQAEAAEAAA== i2pwiki.i2p=Zr1YUKIKooxymZRqLvtfSYuZggqv5txkLE8Ks~FTh3A8JDmhOV8C82dUBKJhzst~Pcbqz7rXc~TPyrqasaQ~LioAji~WLSs8PpmCLVF83edhYpx75Fp23ELboEszHduhKWuibVchPLNa-6nP4F0Ttcr4krTlphe3KveNfZDAbm511zFFlNzPcY4PqOdCRBrp7INiWkVwQxwGWO7jiNYTYa5x4QXJmxIN1YOiNRYQST7THz1aV6219ThsfT9FE5JtiX-epli6PF5ZX9TcVSjHUKZnr8uJLXfh5T4RMVNe1n~KXutMUZwxpFE0scOIez9vhDFd7t0HPIsQUsv7MUBzrz4FM9qol7UUPueSGDRgTOOfXMfj4BDsouiWQC4GcSmH3SflR0Ith9QWKC4u3XYvB7Tw-70QWBEV53hUo6I8YKidV13WgeN9JI3KWTYkMyX-TYjmY9y2q6Xd-Maszv4Tb~NzxQs9CNdu0W-JRSUFOqzgt3l4cx0K1pmx4p0tM5dLBQAEAAEAAA==
lenta.i2p=DnW8NqbKilYLcIx5g5CG4mWVHkzrCkl0MbV4a5rGJku4BSs7HjvzjZpCoXWFky9JCUlHzjFotMETxQBhaKl0Q46vu-plKQ4BLnYyo45p7j2lTiejWvV4SDuXU4IAdmug27i~Jl4N44zwe9KYy~gMfY1Vsgv4bH9ov7X7l2iS-bycfcd9nE7JfycwFc4e0XU-dx7xf~tHw7I5--25dp-SsRX3-UYz4ygb58aD8UsKfQaFZtMy4x~Z1ufNEftuekb1HH9g2Rhhq8Bl62ad8PWSDa9Ne-SkCQsqTYjrCsvMY2DMvWgmZxI1hJYqzjRdFV6JEprrr~CJgHGJXr~KdnZhX12Vm4bKisZK847wBm42CoBQBT5HRzDkeflkbsliirRuKSUxVYMoZ1vic~avPZZl~pvIKZsz-YtiKha4vjCNE1zD-tHIS~2qq4uEO546Ol9pNokPaNttV6r7D2-zurEDx~9grJ8LhBozTxtdTdfZv2OqN4bVhrE7xUrxe0flIFKEAAAA lenta.i2p=DnW8NqbKilYLcIx5g5CG4mWVHkzrCkl0MbV4a5rGJku4BSs7HjvzjZpCoXWFky9JCUlHzjFotMETxQBhaKl0Q46vu-plKQ4BLnYyo45p7j2lTiejWvV4SDuXU4IAdmug27i~Jl4N44zwe9KYy~gMfY1Vsgv4bH9ov7X7l2iS-bycfcd9nE7JfycwFc4e0XU-dx7xf~tHw7I5--25dp-SsRX3-UYz4ygb58aD8UsKfQaFZtMy4x~Z1ufNEftuekb1HH9g2Rhhq8Bl62ad8PWSDa9Ne-SkCQsqTYjrCsvMY2DMvWgmZxI1hJYqzjRdFV6JEprrr~CJgHGJXr~KdnZhX12Vm4bKisZK847wBm42CoBQBT5HRzDkeflkbsliirRuKSUxVYMoZ1vic~avPZZl~pvIKZsz-YtiKha4vjCNE1zD-tHIS~2qq4uEO546Ol9pNokPaNttV6r7D2-zurEDx~9grJ8LhBozTxtdTdfZv2OqN4bVhrE7xUrxe0flIFKEAAAA
secure.thetinhat.i2p=0ncSrtVS20zwfcM7h2S6SSF56uVM2bftQwf40jsWKASNQnzyDVEzXpS04y-DJpm9EwKMGkgvx8ICBX-80W4E9xPJEdGFbb2u34fWmpTVMc3vwwB9ywmSXoxFbwiFx2sm7-HCcdALZwrjU3J41AfBvpEVkB5dXklTZIh~bU0JBTK2JIvQMD0XrSOztEruTc5kYymtkiCUpJaJJFXyIM3lKRcNlZ76UidE8AyQxHX7s9OR02pk7FhYV8Uh-Bs8loAZg6IPZzoYnnBYyi--b1-N8Ipv3aKmqSZPbQEzfQxU8-BE74xBLNEWAJtB8ptKMiKfHphO7qDKWqTzOU-7BtGXZAEOA3oblRAQcgqUbi~aICj0V0MAuYAdj7f-8BIi2k3Qfcl6k6XOFEpZqYFle71LeCjIZN~0mDDzxlr0Scx6LKMGnQAtYlGXFq99urp1MutPDZEu47mdxGWqc9CoNNNsE2UgS9ykvWygefNpZhkmceBXmDxWhuAPD1M2~eNF-fCMBQAIAAMAADZv~vU=

Binary file not shown.

After

Width:  |  Height:  |  Size: 622 B

View File

@ -100,7 +100,9 @@ public class Router implements RouterClock.ClockShiftListener {
public final static long CLOCK_FUDGE_FACTOR = 1*60*1000; public final static long CLOCK_FUDGE_FACTOR = 1*60*1000;
/** used to differentiate routerInfo files on different networks */ /** used to differentiate routerInfo files on different networks */
public static final int NETWORK_ID = 2; private static final int DEFAULT_NETWORK_ID = 2;
private static final String PROP_NETWORK_ID = "router.networkID";
private final int _networkID;
/** coalesce stats this often - should be a little less than one minute, so the graphs get updated */ /** coalesce stats this often - should be a little less than one minute, so the graphs get updated */
public static final int COALESCE_TIME = 50*1000; public static final int COALESCE_TIME = 50*1000;
@ -347,6 +349,14 @@ public class Router implements RouterClock.ClockShiftListener {
_config.put("router.previousVersion", RouterVersion.VERSION); _config.put("router.previousVersion", RouterVersion.VERSION);
saveConfig(); saveConfig();
} }
int id = DEFAULT_NETWORK_ID;
String sid = _config.get(PROP_NETWORK_ID);
if (sid != null) {
try {
id = Integer.parseInt(sid);
} catch (NumberFormatException nfe) {}
}
_networkID = id;
changeState(State.INITIALIZED); changeState(State.INITIALIZED);
// ********* Start no threads before here ********* // // ********* Start no threads before here ********* //
} }
@ -537,6 +547,14 @@ public class Router implements RouterClock.ClockShiftListener {
return Math.max(1000, System.currentTimeMillis() - _started); return Math.max(1000, System.currentTimeMillis() - _started);
} }
/**
* The network ID. Default 2.
* May be changed with the config property router.networkID (restart required).
* Change only if running a test network to prevent cross-network contamination.
* @since 0.9.25
*/
public int getNetworkID() { return _networkID; }
/** /**
* Non-null, but take care when accessing context items before runRouter() is called * Non-null, but take care when accessing context items before runRouter() is called
* as the context will not be initialized. * as the context will not be initialized.

View File

@ -120,7 +120,8 @@ public class RouterContext extends I2PAppContext {
// or about 2 seconds per buffer - so about 200x faster // or about 2 seconds per buffer - so about 200x faster
// to fill than to drain - so we don't need too many // to fill than to drain - so we don't need too many
long maxMemory = SystemVersion.getMaxMemory(); long maxMemory = SystemVersion.getMaxMemory();
long buffs = Math.min(16, Math.max(2, maxMemory / (14 * 1024 * 1024))); long maxBuffs = (SystemVersion.isAndroid() || SystemVersion.isARM()) ? 4 : 8;
long buffs = Math.min(maxBuffs, Math.max(2, maxMemory / (21 * 1024 * 1024)));
envProps.setProperty("prng.buffers", "" + buffs); envProps.setProperty("prng.buffers", "" + buffs);
} }
return envProps; return envProps;

View File

@ -32,6 +32,7 @@ import net.i2p.util.Log;
public class StatisticsManager { public class StatisticsManager {
private final Log _log; private final Log _log;
private final RouterContext _context; private final RouterContext _context;
private final String _networkID;
public final static String PROP_PUBLISH_RANKINGS = "router.publishPeerRankings"; public final static String PROP_PUBLISH_RANKINGS = "router.publishPeerRankings";
private static final String PROP_CONTACT_NAME = "netdb.contact"; private static final String PROP_CONTACT_NAME = "netdb.contact";
@ -46,6 +47,7 @@ public class StatisticsManager {
_fmt = new DecimalFormat("###,##0.00", new DecimalFormatSymbols(Locale.UK)); _fmt = new DecimalFormat("###,##0.00", new DecimalFormatSymbols(Locale.UK));
_pct = new DecimalFormat("#0.00%", new DecimalFormatSymbols(Locale.UK)); _pct = new DecimalFormat("#0.00%", new DecimalFormatSymbols(Locale.UK));
_log = context.logManager().getLog(StatisticsManager.class); _log = context.logManager().getLog(StatisticsManager.class);
_networkID = Integer.toString(context.router().getNetworkID());
} }
/** /**
@ -72,7 +74,7 @@ public class StatisticsManager {
// scheduled for removal, never used // scheduled for removal, never used
if (CoreVersion.VERSION.equals("0.9.23")) if (CoreVersion.VERSION.equals("0.9.23"))
stats.setProperty("coreVersion", CoreVersion.VERSION); stats.setProperty("coreVersion", CoreVersion.VERSION);
stats.setProperty(RouterInfo.PROP_NETWORK_ID, Integer.toString(Router.NETWORK_ID)); stats.setProperty(RouterInfo.PROP_NETWORK_ID, _networkID);
stats.setProperty(RouterInfo.PROP_CAPABILITIES, _context.router().getCapabilities()); stats.setProperty(RouterInfo.PROP_CAPABILITIES, _context.router().getCapabilities());
// No longer expose, to make build tracking more expensive // No longer expose, to make build tracking more expensive

View File

@ -47,12 +47,13 @@ public class FamilyKeyCrypto {
private final SigningPrivateKey _privkey; private final SigningPrivateKey _privkey;
private final SigningPublicKey _pubkey; private final SigningPublicKey _pubkey;
private static final String PROP_KEYSTORE_PASSWORD = "netdb.family.keystorePassword"; public static final String PROP_KEYSTORE_PASSWORD = "netdb.family.keystorePassword";
public static final String PROP_FAMILY_NAME = "netdb.family.name"; public static final String PROP_FAMILY_NAME = "netdb.family.name";
private static final String PROP_KEY_PASSWORD = "netdb.family.keyPassword"; public static final String PROP_KEY_PASSWORD = "netdb.family.keyPassword";
private static final String CERT_SUFFIX = ".crt"; public static final String CERT_SUFFIX = ".crt";
private static final String KEYSTORE_PREFIX = "family-"; public static final String KEYSTORE_PREFIX = "family-";
private static final String KEYSTORE_SUFFIX = ".ks"; public static final String KEYSTORE_SUFFIX = ".ks";
public static final String CN_SUFFIX = ".family.i2p.net";
private static final int DEFAULT_KEY_VALID_DAYS = 3652; // 10 years private static final int DEFAULT_KEY_VALID_DAYS = 3652; // 10 years
// Note that we can't use RSA here, as the b64 sig would exceed the 255 char limit for a Mapping // Note that we can't use RSA here, as the b64 sig would exceed the 255 char limit for a Mapping
// Note that we can't use EdDSA here, as keystore doesn't know how, and encoding/decoding is unimplemented // Note that we can't use EdDSA here, as keystore doesn't know how, and encoding/decoding is unimplemented
@ -289,7 +290,7 @@ public class FamilyKeyCrypto {
// make a random 48 character password (30 * 8 / 5) // make a random 48 character password (30 * 8 / 5)
String keyPassword = KeyStoreUtil.randomString(); String keyPassword = KeyStoreUtil.randomString();
// and one for the cname // and one for the cname
String cname = _fname + ".family.i2p.net"; String cname = _fname + CN_SUFFIX;
boolean success = KeyStoreUtil.createKeys(ks, KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD, _fname, cname, "family", boolean success = KeyStoreUtil.createKeys(ks, KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD, _fname, cname, "family",
DEFAULT_KEY_VALID_DAYS, DEFAULT_KEY_ALGORITHM, DEFAULT_KEY_VALID_DAYS, DEFAULT_KEY_ALGORITHM,

View File

@ -479,7 +479,7 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
// drop the peer in these cases // drop the peer in these cases
// yikes don't do this - stack overflow // getFloodfillPeers().size() == 0 || // yikes don't do this - stack overflow // getFloodfillPeers().size() == 0 ||
// yikes2 don't do this either - deadlock! // getKnownRouters() < MIN_REMAINING_ROUTERS || // yikes2 don't do this either - deadlock! // getKnownRouters() < MIN_REMAINING_ROUTERS ||
if (info.getNetworkId() == Router.NETWORK_ID && if (info.getNetworkId() == _networkID &&
(getKBucketSetSize() < MIN_REMAINING_ROUTERS || (getKBucketSetSize() < MIN_REMAINING_ROUTERS ||
_context.router().getUptime() < DONT_FAIL_PERIOD || _context.router().getUptime() < DONT_FAIL_PERIOD ||
_context.commSystem().countActivePeers() <= MIN_ACTIVE_PEERS)) { _context.commSystem().countActivePeers() <= MIN_ACTIVE_PEERS)) {

View File

@ -70,6 +70,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
private final ReseedChecker _reseedChecker; private final ReseedChecker _reseedChecker;
private volatile long _lastRIPublishTime; private volatile long _lastRIPublishTime;
private NegativeLookupCache _negativeCache; private NegativeLookupCache _negativeCache;
protected final int _networkID;
/** /**
* Map of Hash to RepublishLeaseSetJob for leases we'realready managing. * Map of Hash to RepublishLeaseSetJob for leases we'realready managing.
@ -156,6 +157,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
public KademliaNetworkDatabaseFacade(RouterContext context) { public KademliaNetworkDatabaseFacade(RouterContext context) {
_context = context; _context = context;
_log = _context.logManager().getLog(getClass()); _log = _context.logManager().getLog(getClass());
_networkID = context.router().getNetworkID();
_peerSelector = createPeerSelector(); _peerSelector = createPeerSelector();
_publishingLeaseSets = new HashMap<Hash, RepublishLeaseSetJob>(8); _publishingLeaseSets = new HashMap<Hash, RepublishLeaseSetJob>(8);
_activeRequests = new HashMap<Hash, SearchJob>(8); _activeRequests = new HashMap<Hash, SearchJob>(8);
@ -889,7 +891,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
_log.warn("Invalid routerInfo signature! forged router structure! router = " + routerInfo); _log.warn("Invalid routerInfo signature! forged router structure! router = " + routerInfo);
return "Invalid routerInfo signature"; return "Invalid routerInfo signature";
} }
if (routerInfo.getNetworkId() != Router.NETWORK_ID){ if (routerInfo.getNetworkId() != _networkID){
_context.banlist().banlistRouter(key, "Not in our network"); _context.banlist().banlistRouter(key, "Not in our network");
if (_log.shouldLog(Log.WARN)) if (_log.shouldLog(Log.WARN))
_log.warn("Bad network: " + routerInfo); _log.warn("Bad network: " + routerInfo);

View File

@ -54,6 +54,7 @@ public class PersistentDataStore extends TransientDataStore {
private final ReadJob _readJob; private final ReadJob _readJob;
private volatile boolean _initialized; private volatile boolean _initialized;
private final boolean _flat; private final boolean _flat;
private final int _networkID;
private final static int READ_DELAY = 2*60*1000; private final static int READ_DELAY = 2*60*1000;
private static final String PROP_FLAT = "router.networkDatabase.flat"; private static final String PROP_FLAT = "router.networkDatabase.flat";
@ -65,6 +66,7 @@ public class PersistentDataStore extends TransientDataStore {
*/ */
public PersistentDataStore(RouterContext ctx, String dbDir, KademliaNetworkDatabaseFacade facade) throws IOException { public PersistentDataStore(RouterContext ctx, String dbDir, KademliaNetworkDatabaseFacade facade) throws IOException {
super(ctx); super(ctx);
_networkID = ctx.router().getNetworkID();
_flat = ctx.getBooleanProperty(PROP_FLAT); _flat = ctx.getBooleanProperty(PROP_FLAT);
_dbDir = getDbDir(dbDir); _dbDir = getDbDir(dbDir);
_facade = facade; _facade = facade;
@ -505,7 +507,7 @@ public class PersistentDataStore extends TransientDataStore {
fis = new BufferedInputStream(fis); fis = new BufferedInputStream(fis);
RouterInfo ri = new RouterInfo(); RouterInfo ri = new RouterInfo();
ri.readBytes(fis, true); // true = verify sig on read ri.readBytes(fis, true); // true = verify sig on read
if (ri.getNetworkId() != Router.NETWORK_ID) { if (ri.getNetworkId() != _networkID) {
corrupt = true; corrupt = true;
if (_log.shouldLog(Log.ERROR)) if (_log.shouldLog(Log.ERROR))
_log.error("The router " _log.error("The router "

View File

@ -42,6 +42,7 @@ class EstablishmentManager {
private final Log _log; private final Log _log;
private final UDPTransport _transport; private final UDPTransport _transport;
private final PacketBuilder _builder; private final PacketBuilder _builder;
private final int _networkID;
/** map of RemoteHostId to InboundEstablishState */ /** map of RemoteHostId to InboundEstablishState */
private final ConcurrentHashMap<RemoteHostId, InboundEstablishState> _inboundStates; private final ConcurrentHashMap<RemoteHostId, InboundEstablishState> _inboundStates;
@ -140,6 +141,7 @@ class EstablishmentManager {
public EstablishmentManager(RouterContext ctx, UDPTransport transport) { public EstablishmentManager(RouterContext ctx, UDPTransport transport) {
_context = ctx; _context = ctx;
_log = ctx.logManager().getLog(EstablishmentManager.class); _log = ctx.logManager().getLog(EstablishmentManager.class);
_networkID = ctx.router().getNetworkID();
_transport = transport; _transport = transport;
_builder = new PacketBuilder(ctx, transport); _builder = new PacketBuilder(ctx, transport);
_inboundStates = new ConcurrentHashMap<RemoteHostId, InboundEstablishState>(); _inboundStates = new ConcurrentHashMap<RemoteHostId, InboundEstablishState>();
@ -249,7 +251,7 @@ class EstablishmentManager {
} }
RouterIdentity toIdentity = toRouterInfo.getIdentity(); RouterIdentity toIdentity = toRouterInfo.getIdentity();
Hash toHash = toIdentity.calculateHash(); Hash toHash = toIdentity.calculateHash();
if (toRouterInfo.getNetworkId() != Router.NETWORK_ID) { if (toRouterInfo.getNetworkId() != _networkID) {
_context.banlist().banlistRouter(toHash); _context.banlist().banlistRouter(toHash);
_transport.markUnreachable(toHash); _transport.markUnreachable(toHash);
_transport.failed(msg, "Remote peer is on the wrong network, cannot establish"); _transport.failed(msg, "Remote peer is on the wrong network, cannot establish");
@ -762,7 +764,7 @@ class EstablishmentManager {
if (_log.shouldLog(Log.INFO)) if (_log.shouldLog(Log.INFO))
_log.info("Completing to the peer after IB confirm: " + peer); _log.info("Completing to the peer after IB confirm: " + peer);
DeliveryStatusMessage dsm = new DeliveryStatusMessage(_context); DeliveryStatusMessage dsm = new DeliveryStatusMessage(_context);
dsm.setArrival(Router.NETWORK_ID); // overloaded, sure, but future versions can check this dsm.setArrival(_networkID); // overloaded, sure, but future versions can check this
// This causes huge values in the inNetPool.droppedDeliveryStatusDelay stat // This causes huge values in the inNetPool.droppedDeliveryStatusDelay stat
// so it needs to be caught in InNetMessagePool. // so it needs to be caught in InNetMessagePool.
dsm.setMessageExpiration(_context.clock().now() + DATA_MESSAGE_TIMEOUT); dsm.setMessageExpiration(_context.clock().now() + DATA_MESSAGE_TIMEOUT);

View File

@ -87,6 +87,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
private int _mtu; private int _mtu;
private int _mtu_ipv6; private int _mtu_ipv6;
private boolean _mismatchLogged; private boolean _mismatchLogged;
private final int _networkID;
/** /**
* Do we have a public IPv6 address? * Do we have a public IPv6 address?
@ -218,6 +219,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
public UDPTransport(RouterContext ctx, DHSessionKeyBuilder.Factory dh) { public UDPTransport(RouterContext ctx, DHSessionKeyBuilder.Factory dh) {
super(ctx); super(ctx);
_networkID = ctx.router().getNetworkID();
_dhFactory = dh; _dhFactory = dh;
_log = ctx.logManager().getLog(UDPTransport.class); _log = ctx.logManager().getLog(UDPTransport.class);
_peersByIdent = new ConcurrentHashMap<Hash, PeerState>(128); _peersByIdent = new ConcurrentHashMap<Hash, PeerState>(128);
@ -1289,7 +1291,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
if (entry == null) if (entry == null)
return; return;
if (entry.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO && if (entry.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO &&
((RouterInfo) entry).getNetworkId() != Router.NETWORK_ID) { ((RouterInfo) entry).getNetworkId() != _networkID) {
// this is pre-0.6.1.10, so it isn't going to happen any more // this is pre-0.6.1.10, so it isn't going to happen any more
/* /*

View File

@ -0,0 +1,281 @@
package net.i2p.router.util;
import java.io.Serializable;
import java.util.AbstractSet;
import java.util.ConcurrentModificationException;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* A small, fast Set with a maximum size, backed by a fixed-size array.
* Unsynchronized, not thread-safe.
* Null elements are not permitted.
* Not appropriate for large Sets.
*
* @since 0.9.25
*/
public class ArraySet<E> extends AbstractSet<E> implements Set<E> {
public static final int MAX_CAPACITY = 32;
private final Object[] _entries;
private final boolean _throwOnFull;
private int _size;
private int _overflowIndex;
private transient int modCount;
/**
* A fixed capacity of MAX_CAPACITY.
* Adds over capacity will throw a SetFullException.
*/
public ArraySet() {
this(MAX_CAPACITY);
}
/**
* A fixed capacity of MAX_CAPACITY.
* Adds over capacity will throw a SetFullException.
* @throws SetFullException if more than MAX_CAPACITY unique elements in c.
*/
public ArraySet(Collection<? extends E> c) {
this();
addAll(c);
}
/**
* Adds over capacity will throw a SetFullException.
*
* @param capacity the maximum size
* @throws IllegalArgumentException if capacity less than 1 or more than MAX_CAPACITY.
*/
public ArraySet(int capacity) {
this(capacity, true);
}
/**
* If throwOnFull is false,
* adds over capacity will overwrite starting at slot zero.
* This breaks the AbstractCollection invariant that
* "a Collection will always contain the specified element after add() returns",
* but it prevents unexpected exceptions.
* If throwOnFull is true, adds over capacity will throw a SetFullException.
*
* @param capacity the maximum size
* @throws IllegalArgumentException if capacity less than 1 or more than MAX_CAPACITY.
*/
public ArraySet(int capacity, boolean throwOnFull) {
if (capacity <= 0 || capacity > MAX_CAPACITY)
throw new IllegalArgumentException("bad capacity");
_entries = new Object[capacity];
_throwOnFull = throwOnFull;
}
/**
* @return -1 if not found or if o is null
*/
private int indexOf(Object o) {
if (o != null) {
for (int i = 0; i < _size; i++) {
if (o.equals(_entries[i]))
return i;
}
}
return -1;
}
/**
* @throws SetFullException if throwOnFull was true in constructor
* @throws NullPointerException if o is null
*/
@Override
public boolean add(E o) {
if (o == null)
throw new NullPointerException();
int i = indexOf(o);
if (i >= 0) {
_entries[i] = o;
return false;
}
if (_size >= _entries.length) {
if (_throwOnFull)
throw new SetFullException();
i = _overflowIndex++;
if (i >= _entries.length) {
i = 0;
_overflowIndex = 0;
}
} else {
modCount++;
i = _size++;
}
_entries[i] = o;
return true;
}
@Override
public void clear() {
if (_size != 0) {
modCount++;
for (int i = 0; i < _size; i++) {
_entries[i] = null;
}
_size = 0;
}
}
@Override
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
@Override
public boolean isEmpty() {
return _size <= 0;
}
@Override
public boolean remove(Object o) {
int i = indexOf(o);
if (i < 0)
return false;
modCount++;
_size--;
for (int j = i; j < _size; j++) {
_entries[j] = _entries[j + 1];
}
_entries[_size] = null;
return true;
}
public int size() {
return _size;
}
/**
* Supports remove.
* Supports comodification checks.
*/
public Iterator<E> iterator() {
return new ASIterator();
}
public static class SetFullException extends IllegalStateException {
private static final long serialVersionUID = 9087390587254111L;
}
/**
* Modified from CachedIteratorArrayList
*/
private class ASIterator implements Iterator<E>, Serializable {
/**
* Index of element to be returned by subsequent call to next.
*/
int cursor = 0;
/**
* Index of element returned by most recent call to next or
* previous. Reset to -1 if this element is deleted by a call
* to remove.
*/
int lastRet = -1;
/**
* The modCount value that the iterator believes that the backing
* List should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != _size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
try {
int i = cursor;
E next = (E) _entries[i];
lastRet = i;
cursor = i + 1;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArraySet.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
/**
* About 3x faster than HashSet.
*/
/****
@SuppressWarnings({ "unchecked", "rawtypes" })
public static void main(String[] args) {
if (args.length > 0) {
System.out.println("Test with overwrite");
Set s = new ArraySet(4, false);
for (int i = 0; i < args.length; i++) {
System.out.println("Added " + args[i] + "? " + s.add(args[i]));
System.out.println("Size is now " + s.size());
}
// toString tests the iterator
System.out.println("Set now contains" + s);
for (int i = 0; i < args.length; i++) {
System.out.println("Removed " + args[i] + "? " + s.remove(args[i]));
System.out.println("Size is now " + s.size());
}
System.out.println("\nTest with throw on full");
s = new ArraySet(4);
for (int i = 0; i < args.length; i++) {
System.out.println("Added " + args[i] + "? " + s.add(args[i]));
System.out.println("Size is now " + s.size());
}
// toString tests the iterator
System.out.println("Set now contains" + s);
for (int i = 0; i < args.length; i++) {
System.out.println("Removed " + args[i] + "? " + s.remove(args[i]));
System.out.println("Size is now " + s.size());
}
}
//java.util.List c = java.util.Arrays.asList(new String[] {"foo", "bar", "baz", "splat", "barf", "baz", "moose", "bear", "cat", "dog"} );
java.util.List c = java.util.Arrays.asList(new String[] {"foo", "bar"} );
long start = System.currentTimeMillis();
Set s = new java.util.HashSet(c);
int runs = 10000000;
for (int i = 0; i < runs; i++) {
s = new java.util.HashSet(s);
}
System.out.println("HashSet took " + (System.currentTimeMillis() - start));
start = System.currentTimeMillis();
s = new ArraySet(c);
for (int i = 0; i < runs; i++) {
s = new ArraySet(s);
}
System.out.println("ArraySet took " + (System.currentTimeMillis() - start));
}
****/
}