diff --git a/LICENSE.txt b/LICENSE.txt index 7a6775bc7eceb76b175f381ae81c4ba4869b3ce6..0c63d86ed7d79fb793f13b5b770046ca694292a3 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -40,6 +40,10 @@ Public domain except as listed below: Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle 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: Copyright (c) 1995-2005 The Cryptix Foundation Limited. See licenses/LICENSE-Cryptix.txt diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java index f400352f4d3f90ae154e5a11f96774cfe141c2de..48bbca328c167253b4b329f66f3cbf3d5c797215 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java @@ -471,10 +471,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna portNum = "7654"; String msg; if (getTunnel().getContext().isRouterContext()) + msg = "Unable to build tunnels for the client"; + else msg = "Unable to connect to the router at " + getTunnel().host + ':' + portNum + " and build tunnels for the client"; - else - msg = "Unable to build tunnels for the client"; if (++retries < MAX_RETRIES) { if (log != null) log.log(msg + ", retrying in " + (RETRY_DELAY / 1000) + " seconds"); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/CodedIconRendererServlet.java b/apps/routerconsole/java/src/net/i2p/router/web/CodedIconRendererServlet.java new file mode 100644 index 0000000000000000000000000000000000000000..1d71421bf11bac8f486a19d8a5de4fce4f80489d --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/CodedIconRendererServlet.java @@ -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; + } + } + + } + } +} diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigFamilyHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigFamilyHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..8c472752c9c3a424d5b9133173251744272c2548 --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigFamilyHandler.java @@ -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 + '.'); + } +} diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigFamilyHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigFamilyHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..ede566ec63f8a81a05a9d9132138ca885981a451 --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigFamilyHelper.java @@ -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, ""); + } +} diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNavHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNavHelper.java index f731f0017b6eb7e1bf550f786a9c0d8c0deec7f3..d9f39227542df2f5e25fc60c47b4cc437fe44207 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNavHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNavHelper.java @@ -19,13 +19,13 @@ public class ConfigNavHelper extends HelperBase { private static final String pages[] = {"", "net", "ui", "sidebar", "home", "service", "update", "tunnels", "clients", "peer", "keyring", "logging", "stats", - "reseed", "advanced" }; + "reseed", "advanced", "family" }; private static final String titles[] = {_x("Bandwidth"), _x("Network"), _x("UI"), _x("Summary Bar"), _x("Home Page"), _x("Service"), _x("Update"), _x("Tunnels"), _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 */ private static class Tab { diff --git a/apps/routerconsole/java/src/net/i2p/router/web/HomeHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/HomeHelper.java index cc6f017a5b7545ceafb16a9704aa1aa63050bbfe..b8c91d19e449fb1dbeb976460e4acbdefe5273ba 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/HomeHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/HomeHelper.java @@ -67,6 +67,7 @@ public class HomeHelper extends HelperBase { //"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 + _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("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 + diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NavHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/NavHelper.java index 713520c94a0f3352372f2f96721137e64ef8fcd7..9e1393c233953387d363ecd4f96d4d55ce0b4fda 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/NavHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/NavHelper.java @@ -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> _tooltips = 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 @@ -40,6 +41,29 @@ public class NavHelper { _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 * @param ctx unused diff --git a/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java b/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java index 2af0d5df46e56c33bcccf93d1afb0a4b3e6e6e9b..1d6c0fa21711a44a45ea1b2cc8b29437a16e264e 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java @@ -22,6 +22,7 @@ import net.i2p.I2PAppContext; import net.i2p.app.ClientApp; import net.i2p.app.ClientAppState; import net.i2p.data.DataHelper; +import net.i2p.data.Base64; import net.i2p.router.RouterContext; import net.i2p.router.RouterVersion; 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 File clientConfig = new File(pluginDir, "clients.config"); if (clientConfig.exists()) { diff --git a/apps/routerconsole/jsp/configfamily.jsp b/apps/routerconsole/jsp/configfamily.jsp new file mode 100644 index 0000000000000000000000000000000000000000..7a4ddb03d9a0d977bbb56b7c767795306962a9e3 --- /dev/null +++ b/apps/routerconsole/jsp/configfamily.jsp @@ -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> diff --git a/apps/routerconsole/jsp/configreseed.jsp b/apps/routerconsole/jsp/configreseed.jsp index ebc94b9c9fd13a6c21ae27179d156852da16ae96..25b6704f9fac9a57f5f51f89a8521222d481e0d4 100644 --- a/apps/routerconsole/jsp/configreseed.jsp +++ b/apps/routerconsole/jsp/configreseed.jsp @@ -78,7 +78,7 @@ <b><%=intl._t("Use non-SSL only")%></b></td></tr> <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> -<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> <tr><td class="mediumtags" align="right"><b><%=intl._t("Enable HTTP Proxy?")%></b></td> diff --git a/apps/routerconsole/jsp/exportfamily.jsp b/apps/routerconsole/jsp/exportfamily.jsp new file mode 100644 index 0000000000000000000000000000000000000000..00ce9dc556689f623887cff9f2cbfb6dd74d1928 --- /dev/null +++ b/apps/routerconsole/jsp/exportfamily.jsp @@ -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 +%> diff --git a/apps/routerconsole/jsp/web.xml b/apps/routerconsole/jsp/web.xml index ea183c83564b125d4de28deb2d93f9492cb848e3..44e40c86e516820b97d21b4044c0973d63bd0128 100644 --- a/apps/routerconsole/jsp/web.xml +++ b/apps/routerconsole/jsp/web.xml @@ -14,6 +14,18 @@ </filter-mapping> <!-- 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.. --> <servlet-mapping> diff --git a/build.xml b/build.xml index 65bda9ed8269321bf02bad071fe2538f7d9c8589..cb46589a2763f29f1fa7cba016d7ba178f0f7429 100644 --- a/build.xml +++ b/build.xml @@ -883,6 +883,11 @@ <not><contains string="${javac.compilerargs}" substring="-bootclasspath"/></not> </condition> </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"> <condition> <equals arg1="${build.built-by}" arg2="unknown"/> diff --git a/core/java/src/gnu/crypto/hash/BaseHashStandalone.java b/core/java/src/gnu/crypto/hash/BaseHashStandalone.java index 26d51158f17a35974365496ed84e49d78fcede8f..4c77ff9d3f9ca95582a0fc2df1d9ce39e44bc6d7 100644 --- a/core/java/src/gnu/crypto/hash/BaseHashStandalone.java +++ b/core/java/src/gnu/crypto/hash/BaseHashStandalone.java @@ -51,7 +51,9 @@ package gnu.crypto.hash; * See SHA256Generator for more information. * * @version $Revision: 1.1 $ + * @deprecated to be removed in 0.9.27 */ +@Deprecated public abstract class BaseHashStandalone implements IMessageDigestStandalone { // Constants and variables diff --git a/core/java/src/gnu/crypto/hash/IMessageDigestStandalone.java b/core/java/src/gnu/crypto/hash/IMessageDigestStandalone.java index dbba10bb87193f80b2718720b883d0219437104f..b19b3ac8b24cd5983548dcd30715b60453873685 100644 --- a/core/java/src/gnu/crypto/hash/IMessageDigestStandalone.java +++ b/core/java/src/gnu/crypto/hash/IMessageDigestStandalone.java @@ -54,7 +54,9 @@ package gnu.crypto.hash; * See SHA256Generator for more information. * * @version $Revision: 1.1 $ + * @deprecated to be removed in 0.9.27 */ +@Deprecated public interface IMessageDigestStandalone extends Cloneable { // Constants diff --git a/core/java/src/gnu/crypto/hash/Sha256Standalone.java b/core/java/src/gnu/crypto/hash/Sha256Standalone.java index 465675cd3349cb02d6719bc740b1880fd4467662..a966b20cf6022fc0d45131bf05a28b4128903def 100644 --- a/core/java/src/gnu/crypto/hash/Sha256Standalone.java +++ b/core/java/src/gnu/crypto/hash/Sha256Standalone.java @@ -64,7 +64,9 @@ package gnu.crypto.hash; * See SHA256Generator for more information. * * @version $Revision: 1.2 $ + * @deprecated to be removed in 0.9.27 */ +@Deprecated public class Sha256Standalone extends BaseHashStandalone { // Constants and variables // ------------------------------------------------------------------------- diff --git a/core/java/src/net/i2p/crypto/CertUtil.java b/core/java/src/net/i2p/crypto/CertUtil.java index 0b5dfe66926c48c99564d59c999b0be67a4291a3..ecb6a5f5597ea7dcce8099985f2bab4f5b895b64 100644 --- a/core/java/src/net/i2p/crypto/CertUtil.java +++ b/core/java/src/net/i2p/crypto/CertUtil.java @@ -9,12 +9,21 @@ import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.security.GeneralSecurityException; import java.security.InvalidKeyException; +import java.security.KeyFactory; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.CertificateEncodingException; +import java.security.cert.CRLException; import java.security.cert.X509Certificate; +import java.security.cert.X509CRL; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.KeySpec; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; import java.util.Locale; import javax.naming.InvalidNameException; @@ -24,6 +33,7 @@ import javax.security.auth.x500.X500Principal; import net.i2p.I2PAppContext; import net.i2p.data.Base64; +import net.i2p.data.DataHelper; import net.i2p.util.Log; import net.i2p.util.SecureFileOutputStream; import net.i2p.util.SystemVersion; @@ -33,7 +43,7 @@ import net.i2p.util.SystemVersion; * * @since 0.9.9 */ -public class CertUtil { +public final class CertUtil { private static final int LINE_LENGTH = 64; @@ -93,16 +103,7 @@ public class CertUtil { throws IOException, CertificateEncodingException { // Get the encoded form which is suitable for exporting byte[] buf = cert.getEncoded(); - PrintWriter wr = new PrintWriter(new OutputStreamWriter(out, "UTF-8")); - wr.println("-----BEGIN CERTIFICATE-----"); - String b64 = Base64.encode(buf, true); // true = use standard alphabet - for (int i = 0; i < b64.length(); i += LINE_LENGTH) { - wr.println(b64.substring(i, Math.min(i + LINE_LENGTH, b64.length()))); - } - wr.println("-----END CERTIFICATE-----"); - wr.flush(); - if (wr.checkError()) - throw new IOException("Failed write to " + out); + writePEM(buf, "CERTIFICATE", out); } /** @@ -121,13 +122,27 @@ public class CertUtil { byte[] buf = pk.getEncoded(); if (buf == null) throw new InvalidKeyException("encoding unsupported for this key"); + writePEM(buf, "PRIVATE KEY", out); + } + + /** + * Modified from: + * http://www.exampledepot.com/egs/java.security.cert/ExportCert.html + * + * Writes data in base64 format. + * Does NOT close the stream. Throws on all errors. + * + * @since 0.9.25 consolidated from other methods + */ + private static void writePEM(byte[] buf, String what, OutputStream out) + throws IOException { PrintWriter wr = new PrintWriter(new OutputStreamWriter(out, "UTF-8")); - wr.println("-----BEGIN PRIVATE KEY-----"); + wr.println("-----BEGIN " + what + "-----"); String b64 = Base64.encode(buf, true); // true = use standard alphabet for (int i = 0; i < b64.length(); i += LINE_LENGTH) { wr.println(b64.substring(i, Math.min(i + LINE_LENGTH, b64.length()))); } - wr.println("-----END PRIVATE KEY-----"); + wr.println("-----END " + what + "-----"); wr.flush(); if (wr.checkError()) throw new IOException("Failed write to " + out); @@ -235,4 +250,145 @@ public class CertUtil { try { if (fis != null) fis.close(); } catch (IOException foo) {} } } + + /** + * Get a single Private Key from an input stream. + * Does NOT close the stream. + * + * @return non-null, non-empty, throws on all errors including certificate invalid + * @since 0.9.25 + */ + public static PrivateKey loadPrivateKey(InputStream in) throws IOException, GeneralSecurityException { + try { + String line; + while ((line = DataHelper.readLine(in)) != null) { + if (line.startsWith("---") && line.contains("BEGIN") && line.contains("PRIVATE")) + break; + } + if (line == null) + throw new IOException("no private key found"); + StringBuilder buf = new StringBuilder(128); + while ((line = DataHelper.readLine(in)) != null) { + if (line.startsWith("---")) + break; + buf.append(line.trim()); + } + if (buf.length() <= 0) + throw new IOException("no private key found"); + byte[] data = Base64.decode(buf.toString(), true); + if (data == null) + throw new CertificateEncodingException("bad base64 cert"); + PrivateKey rv = null; + // try all the types + for (SigAlgo algo : EnumSet.allOf(SigAlgo.class)) { + try { + KeySpec ks = new PKCS8EncodedKeySpec(data); + String alg = algo.getName(); + KeyFactory kf = KeyFactory.getInstance(alg); + rv = kf.generatePrivate(ks); + break; + } catch (GeneralSecurityException gse) { + //gse.printStackTrace(); + } + } + if (rv == null) + throw new InvalidKeyException("unsupported key type"); + return rv; + } catch (IllegalArgumentException iae) { + // java 1.8.0_40-b10, openSUSE + // Exception in thread "main" java.lang.IllegalArgumentException: Input byte array has wrong 4-byte ending unit + // at java.util.Base64$Decoder.decode0(Base64.java:704) + throw new GeneralSecurityException("key error", iae); + } + } + + /** + * Get one or more certificates from an input stream. + * Throws if any certificate is invalid (e.g. expired). + * Does NOT close the stream. + * + * @return non-null, non-empty, throws on all errors including certificate invalid + * @since 0.9.25 + */ + public static List<X509Certificate> loadCerts(InputStream in) throws IOException, GeneralSecurityException { + try { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Collection<? extends Certificate> certs = cf.generateCertificates(in); + List<X509Certificate> rv = new ArrayList<X509Certificate>(certs.size()); + for (Certificate cert : certs) { + if (!(cert instanceof X509Certificate)) + throw new GeneralSecurityException("not a X.509 cert"); + X509Certificate xcert = (X509Certificate) cert; + xcert.checkValidity(); + rv.add(xcert); + } + if (rv.isEmpty()) + throw new IOException("no certs found"); + return rv; + } catch (IllegalArgumentException iae) { + // java 1.8.0_40-b10, openSUSE + // Exception in thread "main" java.lang.IllegalArgumentException: Input byte array has wrong 4-byte ending unit + // at java.util.Base64$Decoder.decode0(Base64.java:704) + throw new GeneralSecurityException("cert error", iae); + } finally { + try { in.close(); } catch (IOException foo) {} + } + } + + /** + * Write a CRL to a file in base64 format. + * + * @return success + * @since 0.9.25 + */ + public static boolean saveCRL(X509CRL crl, File file) { + OutputStream os = null; + try { + os = new SecureFileOutputStream(file); + exportCRL(crl, os); + return true; + } catch (CRLException ce) { + error("Error writing X509 CRL " + file.getAbsolutePath(), ce); + return false; + } catch (IOException ioe) { + error("Error writing X509 CRL " + file.getAbsolutePath(), ioe); + return false; + } finally { + try { if (os != null) os.close(); } catch (IOException foo) {} + } + } + + /** + * Writes a CRL in base64 format. + * Does NOT close the stream. Throws on all errors. + * + * @throws CRLException if the crl does not support encoding + * @since 0.9.25 + */ + private static void exportCRL(X509CRL crl, OutputStream out) + throws IOException, CRLException { + byte[] buf = crl.getEncoded(); + writePEM(buf, "X509 CRL", out); + } + +/**** + public static final void main(String[] args) { + if (args.length < 2) { + System.out.println("Usage: [loadcert | loadcrl | loadprivatekey] file"); + System.exit(1); + } + try { + File f = new File(args[1]); + if (args[0].equals("loadcert")) { + loadCert(f); + } else if (args[0].equals("loadcrl")) { + } else { + } + + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + } +****/ } diff --git a/core/java/src/net/i2p/crypto/CryptixAESEngine.java b/core/java/src/net/i2p/crypto/CryptixAESEngine.java index e5cf7cb92532ad97eef08ca000a969536d580854..61b5386de77d8016ae422e955c73bd1f7b31fff2 100644 --- a/core/java/src/net/i2p/crypto/CryptixAESEngine.java +++ b/core/java/src/net/i2p/crypto/CryptixAESEngine.java @@ -36,7 +36,7 @@ import net.i2p.util.SystemVersion; * * @author jrandom, thecrypto */ -public class CryptixAESEngine extends AESEngine { +public final class CryptixAESEngine extends AESEngine { private final static CryptixRijndael_Algorithm _algo = new CryptixRijndael_Algorithm(); // keys are now cached in the SessionKey objects //private CryptixAESKeyCache _cache; diff --git a/core/java/src/net/i2p/crypto/CryptoConstants.java b/core/java/src/net/i2p/crypto/CryptoConstants.java index b9e0327dd385009896a94bf651046dde3348e763..00994d210ad1a91f0bace0f6afa9e4862b162dd6 100644 --- a/core/java/src/net/i2p/crypto/CryptoConstants.java +++ b/core/java/src/net/i2p/crypto/CryptoConstants.java @@ -34,6 +34,7 @@ import java.math.BigInteger; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.DSAParameterSpec; +import net.i2p.crypto.elgamal.spec.ElGamalParameterSpec; import net.i2p.util.NativeBigInteger; /** @@ -43,7 +44,7 @@ import net.i2p.util.NativeBigInteger; * See also: ECConstants, RSAConstants * */ -public class CryptoConstants { +public final class CryptoConstants { public static final BigInteger dsap = new NativeBigInteger( "9c05b2aa960d9b97b8931963c9cc9e8c3026e9b8ed92fad0a69cc886d5bf8015fcadae31" + "a0ad18fab3f01b00a358de237655c4964afaa2b337e96ad316b9fb1cc564b5aec5b69a9f" @@ -78,6 +79,11 @@ public class CryptoConstants { */ public static final DSAParameterSpec DSA_SHA1_SPEC = new DSAParameterSpec(dsap, dsaq, dsag); + /** + * @since 0.9.25 + */ + public static final ElGamalParameterSpec I2P_ELGAMAL_2048_SPEC = new ElGamalParameterSpec(elgp, elgg); + /** * This will be org.bouncycastle.jce.spec.ElgamalParameterSpec * if BC is available, otherwise it @@ -98,11 +104,11 @@ public class CryptoConstants { } catch (Exception e) { //System.out.println("BC ElG spec failed"); //e.printStackTrace(); - spec = new ElGamalParameterSpec(elgp, elgg); + spec = I2P_ELGAMAL_2048_SPEC; } } else { //System.out.println("BC not available"); - spec = new ElGamalParameterSpec(elgp, elgg); + spec = I2P_ELGAMAL_2048_SPEC; } ELGAMAL_2048_SPEC = spec; } diff --git a/core/java/src/net/i2p/crypto/DSAEngine.java b/core/java/src/net/i2p/crypto/DSAEngine.java index 76f2004f8b01c1c9ef0777531f0505102d2ea1eb..2b66887bbf0302eedfef0a9b20bbb83d11b5abd7 100644 --- a/core/java/src/net/i2p/crypto/DSAEngine.java +++ b/core/java/src/net/i2p/crypto/DSAEngine.java @@ -72,7 +72,7 @@ import net.i2p.util.NativeBigInteger; * * EdDSA support added in 0.9.15 */ -public class DSAEngine { +public final class DSAEngine { private final Log _log; private final I2PAppContext _context; @@ -234,7 +234,7 @@ public class DSAEngine { BigInteger s = new NativeBigInteger(1, sbytes); BigInteger r = new NativeBigInteger(1, rbytes); BigInteger y = new NativeBigInteger(1, verifyingKey.getData()); - BigInteger w = null; + BigInteger w; try { w = s.modInverse(CryptoConstants.dsaq); } catch (ArithmeticException ae) { @@ -402,8 +402,7 @@ public class DSAEngine { long start = _context.clock().now(); BigInteger k; - - boolean ok = false; + boolean ok; do { k = new BigInteger(160, _context.random()); ok = k.compareTo(CryptoConstants.dsaq) != 1; @@ -516,15 +515,20 @@ public class DSAEngine { if (type == SigType.DSA_SHA1) return altVerifySigSHA1(signature, data, offset, len, verifyingKey); - java.security.Signature jsig; - if (type.getBaseAlgorithm() == SigAlgo.EdDSA) - jsig = new EdDSAEngine(type.getDigestInstance()); - else - jsig = java.security.Signature.getInstance(type.getAlgorithmName()); PublicKey pubKey = SigUtil.toJavaKey(verifyingKey); - jsig.initVerify(pubKey); - jsig.update(data, offset, len); - boolean rv = jsig.verify(SigUtil.toJavaSig(signature)); + byte[] sigbytes = SigUtil.toJavaSig(signature); + boolean rv; + if (type.getBaseAlgorithm() == SigAlgo.EdDSA) { + // take advantage of one-shot mode + EdDSAEngine jsig = new EdDSAEngine(type.getDigestInstance()); + jsig.initVerify(pubKey); + rv = jsig.verifyOneShot(data, offset, len, sigbytes); + } else { + java.security.Signature jsig = java.security.Signature.getInstance(type.getAlgorithmName()); + jsig.initVerify(pubKey); + jsig.update(data, offset, len); + rv = jsig.verify(sigbytes); + } return rv; } @@ -564,15 +568,21 @@ public class DSAEngine { if (type.getHashLen() != hashlen) throw new IllegalArgumentException("type mismatch hash=" + hash.getClass() + " key=" + type); - String algo = getRawAlgo(type); - java.security.Signature jsig; - if (type.getBaseAlgorithm() == SigAlgo.EdDSA) - jsig = new EdDSAEngine(); // Ignore algo, EdDSAKey includes a hash specification. - else - jsig = java.security.Signature.getInstance(algo); - jsig.initVerify(pubKey); - jsig.update(hash.getData()); - boolean rv = jsig.verify(SigUtil.toJavaSig(signature)); + byte[] sigbytes = SigUtil.toJavaSig(signature); + boolean rv; + if (type.getBaseAlgorithm() == SigAlgo.EdDSA) { + // take advantage of one-shot mode + // Ignore algo, EdDSAKey includes a hash specification. + EdDSAEngine jsig = new EdDSAEngine(); + jsig.initVerify(pubKey); + rv = jsig.verifyOneShot(hash.getData(), sigbytes); + } else { + String algo = getRawAlgo(type); + java.security.Signature jsig = java.security.Signature.getInstance(algo); + jsig.initVerify(pubKey); + jsig.update(hash.getData()); + rv = jsig.verify(sigbytes); + } return rv; } @@ -607,15 +617,20 @@ public class DSAEngine { if (type == SigType.DSA_SHA1) return altSignSHA1(data, offset, len, privateKey); - java.security.Signature jsig; - if (type.getBaseAlgorithm() == SigAlgo.EdDSA) - jsig = new EdDSAEngine(type.getDigestInstance()); - else - jsig = java.security.Signature.getInstance(type.getAlgorithmName()); PrivateKey privKey = SigUtil.toJavaKey(privateKey); - jsig.initSign(privKey, _context.random()); - jsig.update(data, offset, len); - return SigUtil.fromJavaSig(jsig.sign(), type); + byte[] sigbytes; + if (type.getBaseAlgorithm() == SigAlgo.EdDSA) { + // take advantage of one-shot mode + EdDSAEngine jsig = new EdDSAEngine(type.getDigestInstance()); + jsig.initSign(privKey); + sigbytes = jsig.signOneShot(data, offset, len); + } else { + java.security.Signature jsig = java.security.Signature.getInstance(type.getAlgorithmName()); + jsig.initSign(privKey, _context.random()); + jsig.update(data, offset, len); + sigbytes = jsig.sign(); + } + return SigUtil.fromJavaSig(sigbytes, type); } /** @@ -650,14 +665,20 @@ public class DSAEngine { if (type.getHashLen() != hashlen) throw new IllegalArgumentException("type mismatch hash=" + hash.getClass() + " key=" + type); - java.security.Signature jsig; - if (type.getBaseAlgorithm() == SigAlgo.EdDSA) - jsig = new EdDSAEngine(); // Ignore algo, EdDSAKey includes a hash specification. - else - jsig = java.security.Signature.getInstance(algo); - jsig.initSign(privKey, _context.random()); - jsig.update(hash.getData()); - return SigUtil.fromJavaSig(jsig.sign(), type); + byte[] sigbytes; + if (type.getBaseAlgorithm() == SigAlgo.EdDSA) { + // take advantage of one-shot mode + // Ignore algo, EdDSAKey includes a hash specification. + EdDSAEngine jsig = new EdDSAEngine(); + jsig.initSign(privKey); + sigbytes = jsig.signOneShot(hash.getData()); + } else { + java.security.Signature jsig = java.security.Signature.getInstance(type.getAlgorithmName()); + jsig.initSign(privKey, _context.random()); + jsig.update(hash.getData()); + sigbytes = jsig.sign(); + } + return SigUtil.fromJavaSig(sigbytes, type); } /** diff --git a/core/java/src/net/i2p/crypto/ECConstants.java b/core/java/src/net/i2p/crypto/ECConstants.java index 8bca0b0ac04ce46de69afe762eefc0d15d565b8f..2518e34f9ed2c18afe02e3a1ccca20fbf771fba6 100644 --- a/core/java/src/net/i2p/crypto/ECConstants.java +++ b/core/java/src/net/i2p/crypto/ECConstants.java @@ -20,7 +20,7 @@ import net.i2p.util.NativeBigInteger; * * @since 0.9.9 */ -class ECConstants { +final class ECConstants { private static final boolean DEBUG = false; diff --git a/core/java/src/net/i2p/crypto/ECUtil.java b/core/java/src/net/i2p/crypto/ECUtil.java index 8d22284804bd835ef3793f252c6a6d0d353f8bf9..03fd77e0b8cb3000001b1147d1a1f5d103c9ed49 100644 --- a/core/java/src/net/i2p/crypto/ECUtil.java +++ b/core/java/src/net/i2p/crypto/ECUtil.java @@ -19,7 +19,7 @@ import net.i2p.util.NativeBigInteger; * * @since 0.9.16 */ -class ECUtil { +final class ECUtil { private static final BigInteger TWO = new BigInteger("2"); private static final BigInteger THREE = new BigInteger("3"); diff --git a/core/java/src/net/i2p/crypto/ElGamalAESEngine.java b/core/java/src/net/i2p/crypto/ElGamalAESEngine.java index 0fe652bb219305fc889a8b40358fe29d5d32f270..6f0c87ab351b813faa01dc8d74bfa89720c74bb5 100644 --- a/core/java/src/net/i2p/crypto/ElGamalAESEngine.java +++ b/core/java/src/net/i2p/crypto/ElGamalAESEngine.java @@ -32,7 +32,7 @@ import net.i2p.util.SimpleByteCache; * * No, this does not extend AESEngine or CryptixAESEngine. */ -public class ElGamalAESEngine { +public final class ElGamalAESEngine { private final Log _log; private final static int MIN_ENCRYPTED_SIZE = 80; // smallest possible resulting size private final I2PAppContext _context; diff --git a/core/java/src/net/i2p/crypto/ElGamalEngine.java b/core/java/src/net/i2p/crypto/ElGamalEngine.java index a80e0a99eae998bacdf7f6fa2690ecb1bbd1f6cb..9e25e8ad42ce3f43a47f879d592826e239c97e26 100644 --- a/core/java/src/net/i2p/crypto/ElGamalEngine.java +++ b/core/java/src/net/i2p/crypto/ElGamalEngine.java @@ -52,7 +52,7 @@ import net.i2p.util.SimpleByteCache; * @author thecrypto, jrandom */ -public class ElGamalEngine { +public final class ElGamalEngine { private final Log _log; private final I2PAppContext _context; private final YKGenerator _ykgen; diff --git a/core/java/src/net/i2p/crypto/ElGamalParameterSpec.java b/core/java/src/net/i2p/crypto/ElGamalParameterSpec.java deleted file mode 100644 index cee640804633809432703ae796ad21b93332e694..0000000000000000000000000000000000000000 --- a/core/java/src/net/i2p/crypto/ElGamalParameterSpec.java +++ /dev/null @@ -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; - } -} diff --git a/core/java/src/net/i2p/crypto/EncType.java b/core/java/src/net/i2p/crypto/EncType.java index fc07d5d5a1a5d06725acea3f719e659ef47d3ec6..ca59e0267b1667eb8f2f49671f857aac8d380dd8 100644 --- a/core/java/src/net/i2p/crypto/EncType.java +++ b/core/java/src/net/i2p/crypto/EncType.java @@ -27,7 +27,7 @@ public enum EncType { * This is the default. * Pubkey 256 bytes, privkey 256 bytes. */ - ELGAMAL_2048(0, 256, 256, EncAlgo.ELGAMAL, "ElGamal/None/NoPadding", CryptoConstants.ELGAMAL_2048_SPEC, "0"), + ELGAMAL_2048(0, 256, 256, EncAlgo.ELGAMAL, "ElGamal/None/NoPadding", CryptoConstants.I2P_ELGAMAL_2048_SPEC, "0"), /** Pubkey 64 bytes; privkey 32 bytes; */ EC_P256(1, 64, 32, EncAlgo.EC, "EC/None/NoPadding", ECConstants.P256_SPEC, "0.9.20"), diff --git a/core/java/src/net/i2p/crypto/HMAC256Generator.java b/core/java/src/net/i2p/crypto/HMAC256Generator.java index 688e3148dc479cb5d0387a2edfef2f786dbe04eb..bb5b62d155fb85d37b8eadcf43d66cf8fccf0474 100644 --- a/core/java/src/net/i2p/crypto/HMAC256Generator.java +++ b/core/java/src/net/i2p/crypto/HMAC256Generator.java @@ -21,7 +21,7 @@ import org.bouncycastle.oldcrypto.macs.I2PHMac; * * Deprecated, used only by Syndie. */ -public class HMAC256Generator extends HMACGenerator { +public final class HMAC256Generator extends HMACGenerator { /** * @param context unused diff --git a/core/java/src/net/i2p/crypto/KeyGenerator.java b/core/java/src/net/i2p/crypto/KeyGenerator.java index efe9bacac46dc6784b9c295b825bdbb9337db9ed..4297fcae20faa36e79a283378b5fff1d72431510 100644 --- a/core/java/src/net/i2p/crypto/KeyGenerator.java +++ b/core/java/src/net/i2p/crypto/KeyGenerator.java @@ -34,6 +34,7 @@ import net.i2p.I2PAppContext; import net.i2p.crypto.eddsa.EdDSAPrivateKey; import net.i2p.crypto.eddsa.EdDSAPublicKey; import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; +import net.i2p.crypto.provider.I2PProvider; import net.i2p.data.Hash; import net.i2p.data.PrivateKey; import net.i2p.data.PublicKey; @@ -55,9 +56,13 @@ import net.i2p.util.RandomSource; /** Define a way of generating asymmetrical key pairs as well as symmetrical keys * @author jrandom */ -public class KeyGenerator { +public final class KeyGenerator { private final I2PAppContext _context; + static { + I2PProvider.addProvider(); + } + public KeyGenerator(I2PAppContext context) { _context = context; } @@ -208,10 +213,10 @@ public class KeyGenerator { SimpleDataStructure[] keys = new SimpleDataStructure[2]; BigInteger x = null; - // make sure the random key is less than the DSA q + // make sure the random key is less than the DSA q and greater than zero do { x = new NativeBigInteger(160, _context.random()); - } while (x.compareTo(CryptoConstants.dsaq) >= 0); + } while (x.compareTo(CryptoConstants.dsaq) >= 0 || x.equals(BigInteger.ZERO)); BigInteger y = CryptoConstants.dsag.modPow(x, CryptoConstants.dsap); keys[0] = new SigningPublicKey(); diff --git a/core/java/src/net/i2p/crypto/KeyStoreUtil.java b/core/java/src/net/i2p/crypto/KeyStoreUtil.java index 316b123091a770e06f8896e60552aa25034bc023..1de4687de21ab7581236a99283848293cb9d3d7d 100644 --- a/core/java/src/net/i2p/crypto/KeyStoreUtil.java +++ b/core/java/src/net/i2p/crypto/KeyStoreUtil.java @@ -13,10 +13,13 @@ import java.security.cert.Certificate; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.Enumeration; +import java.util.List; import java.util.Locale; import net.i2p.I2PAppContext; +import net.i2p.crypto.provider.I2PProvider; import net.i2p.data.Base32; import net.i2p.util.Log; import net.i2p.util.SecureDirectory; @@ -29,7 +32,7 @@ import net.i2p.util.SystemVersion; * * @since 0.9.9 */ -public class KeyStoreUtil { +public final class KeyStoreUtil { public static boolean _blacklistLogged; @@ -38,6 +41,10 @@ public class KeyStoreUtil { private static final int DEFAULT_KEY_SIZE = 2048; private static final int DEFAULT_KEY_VALID_DAYS = 3652; // 10 years + static { + I2PProvider.addProvider(); + } + /** * No reports of some of these in a Java keystore but just to be safe... * CNNIC ones are in Ubuntu keystore. @@ -464,20 +471,29 @@ public class KeyStoreUtil { } } String keytool = (new File(System.getProperty("java.home"), "bin/keytool")).getAbsolutePath(); - String[] args = new String[] { - keytool, - "-genkey", // -genkeypair preferred in newer keytools, but this works with more - "-storetype", KeyStore.getDefaultType(), - "-keystore", ks.getAbsolutePath(), - "-storepass", ksPW, - "-alias", alias, - "-dname", "CN=" + cname + ",OU=" + ou + ",O=I2P Anonymous Network,L=XX,ST=XX,C=XX", - "-validity", Integer.toString(validDays), // 10 years - "-keyalg", keyAlg, - "-sigalg", getSigAlg(keySize, keyAlg), - "-keysize", Integer.toString(keySize), - "-keypass", keyPW - }; + List<String> a = new ArrayList<String>(32); + a.add(keytool); + a.add("-genkey"); // -genkeypair preferred in newer keytools, but this works with more + //a.add("-v"); // verbose, gives you a stack trace on exception + a.add("-storetype"); a.add(KeyStore.getDefaultType()); + a.add("-keystore"); a.add(ks.getAbsolutePath()); + a.add("-storepass"); a.add(ksPW); + a.add("-alias"); a.add(alias); + a.add("-dname"); a.add("CN=" + cname + ",OU=" + ou + ",O=I2P Anonymous Network,L=XX,ST=XX,C=XX"); + a.add("-validity"); a.add(Integer.toString(validDays)); // 10 years + a.add("-keyalg"); a.add(keyAlg); + a.add("-sigalg"); a.add(getSigAlg(keySize, keyAlg)); + a.add("-keysize"); a.add(Integer.toString(keySize)); + a.add("-keypass"); a.add(keyPW); + if (keyAlg.equals("Ed") || keyAlg.equals("EdDSA") || keyAlg.equals("ElGamal")) { + File f = I2PAppContext.getGlobalContext().getBaseDir(); + f = new File(f, "lib"); + f = new File(f, "i2p.jar"); + // providerpath is not in the man page; see keytool -genkey -help + a.add("-providerpath"); a.add(f.getAbsolutePath()); + a.add("-providerclass"); a.add("net.i2p.crypto.provider.I2PProvider"); + } + String[] args = a.toArray(new String[a.size()]); // TODO pipe key password to process; requires ShellCommand enhancements boolean success = (new ShellCommand()).executeSilentAndWaitTimed(args, 240); if (success) { @@ -514,6 +530,8 @@ public class KeyStoreUtil { private static String getSigAlg(int size, String keyalg) { if (keyalg.equals("EC")) keyalg = "ECDSA"; + else if (keyalg.equals("Ed")) + keyalg = "EdDSA"; String hash; if (keyalg.equals("ECDSA")) { if (size <= 256) @@ -522,6 +540,8 @@ public class KeyStoreUtil { hash = "SHA384"; else hash = "SHA512"; + } else if (keyalg.equals("EdDSA")) { + hash = "SHA512"; } else { if (size <= 1024) hash = "SHA1"; @@ -559,6 +579,103 @@ public class KeyStoreUtil { } } + /** + * Export the private key and certificate chain (if any) out of a keystore. + * Does NOT close the stream. Throws on all errors. + * + * @param ks path to the keystore + * @param ksPW the keystore password, may be null + * @param alias the name of the key + * @param keyPW the key password, must be at least 6 characters + * @since 0.9.25 + */ + public static void exportPrivateKey(File ks, String ksPW, String alias, String keyPW, + OutputStream out) + throws GeneralSecurityException, IOException { + InputStream fis = null; + try { + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + fis = new FileInputStream(ks); + char[] pwchars = ksPW != null ? ksPW.toCharArray() : null; + keyStore.load(fis, pwchars); + char[] keypwchars = keyPW.toCharArray(); + PrivateKey pk = (PrivateKey) keyStore.getKey(alias, keypwchars); + if (pk == null) + throw new GeneralSecurityException("private key not found: " + alias); + Certificate[] certs = keyStore.getCertificateChain(alias); + CertUtil.exportPrivateKey(pk, certs, out); + } finally { + if (fis != null) try { fis.close(); } catch (IOException ioe) {} + } + } + + /** + * Import the private key and certificate chain to a keystore. + * Keystore will be created if it does not exist. + * Private key MUST be first in the stream. + * Closes the stream. Throws on all errors. + * + * @param ks path to the keystore + * @param ksPW the keystore password, may be null + * @param alias the name of the key. If null, will be taken from the Subject CN + * of the first certificate in the chain. + * @param keyPW the key password, must be at least 6 characters + * @return the alias as specified or extracted + * @since 0.9.25 + */ + public static String importPrivateKey(File ks, String ksPW, String alias, String keyPW, + InputStream in) + throws GeneralSecurityException, IOException { + OutputStream fos = null; + try { + KeyStore keyStore = createKeyStore(ks, ksPW); + PrivateKey pk = CertUtil.loadPrivateKey(in); + List<X509Certificate> certs = CertUtil.loadCerts(in); + if (alias == null) { + alias = CertUtil.getSubjectValue(certs.get(0), "CN"); + if (alias == null) + throw new GeneralSecurityException("no alias specified and no Subject CN in cert"); + if (alias.endsWith(".family.i2p.net") && alias.length() > ".family.i2p.net".length()) + alias = alias.substring(0, ".family.i2p.net".length()); + } + keyStore.setKeyEntry(alias, pk, keyPW.toCharArray(), certs.toArray(new Certificate[certs.size()])); + char[] pwchars = ksPW != null ? ksPW.toCharArray() : null; + fos = new SecureFileOutputStream(ks); + keyStore.store(fos, pwchars); + return alias; + } finally { + if (fos != null) try { fos.close(); } catch (IOException ioe) {} + try { in.close(); } catch (IOException ioe) {} + } + } + + /** + * Import the private key and certificate chain to a keystore. + * Keystore will be created if it does not exist. + * Private key MUST be first in the stream. + * Closes the stream. Throws on all errors. + * + * @param ks path to the keystore + * @param ksPW the keystore password, may be null + * @param alias the name of the key, non-null. + * @param keyPW the key password, must be at least 6 characters + * @since 0.9.25 + */ + public static void storePrivateKey(File ks, String ksPW, String alias, String keyPW, + PrivateKey pk, List<X509Certificate> certs) + throws GeneralSecurityException, IOException { + OutputStream fos = null; + try { + KeyStore keyStore = createKeyStore(ks, ksPW); + keyStore.setKeyEntry(alias, pk, keyPW.toCharArray(), certs.toArray(new Certificate[certs.size()])); + char[] pwchars = ksPW != null ? ksPW.toCharArray() : null; + fos = new SecureFileOutputStream(ks); + keyStore.store(fos, pwchars); + } finally { + if (fos != null) try { fos.close(); } catch (IOException ioe) {} + } + } + /** * Get a cert out of a keystore * @@ -641,11 +758,31 @@ public class KeyStoreUtil { * Usage: KeyStoreUtil (loads from system keystore) * KeyStoreUtil foo.ks (loads from system keystore, and from foo.ks keystore if exists, else creates empty) * KeyStoreUtil certDir (loads from system keystore and all certs in certDir if exists) + * KeyStoreUtil import file.ks file.key alias keypw (imxports private key from file to keystore) + * KeyStoreUtil export file.ks alias keypw (exports private key from keystore) + * KeyStoreUtil keygen file.ks alias keypw (create keypair in keystore) + * KeyStoreUtil keygen2 file.ks alias keypw (create keypair using I2PProvider) */ /**** public static void main(String[] args) { - File ksf = (args.length > 0) ? new File(args[0]) : null; try { + if (args.length > 0 && "import".equals(args[0])) { + testImport(args); + return; + } + if (args.length > 0 && "export".equals(args[0])) { + testExport(args); + return; + } + if (args.length > 0 && "keygen".equals(args[0])) { + testKeygen(args); + return; + } + if (args.length > 0 && "keygen2".equals(args[0])) { + testKeygen2(args); + return; + } + File ksf = (args.length > 0) ? new File(args[0]) : null; if (ksf != null && !ksf.exists()) { createKeyStore(ksf, DEFAULT_KEYSTORE_PASSWORD); System.out.println("Created empty keystore " + ksf); @@ -674,5 +811,63 @@ public class KeyStoreUtil { e.printStackTrace(); } } + + private static void testImport(String[] args) throws Exception { + File ksf = new File(args[1]); + InputStream in = new FileInputStream(args[2]); + String alias = args[3]; + String pw = args[4]; + importPrivateKey(ksf, DEFAULT_KEYSTORE_PASSWORD, alias, pw, in); + } + + + private static void testExport(String[] args) throws Exception { + File ksf = new File(args[1]); + String alias = args[2]; + String pw = args[3]; + exportPrivateKey(ksf, DEFAULT_KEYSTORE_PASSWORD, alias, pw, System.out); + } + + private static void testKeygen(String[] args) throws Exception { + File ksf = new File(args[1]); + String alias = args[2]; + String pw = args[3]; + boolean ok = createKeys(ksf, DEFAULT_KEYSTORE_PASSWORD, alias, "test cname", "test ou", + //DEFAULT_KEY_VALID_DAYS, "EdDSA", 256, pw); + DEFAULT_KEY_VALID_DAYS, "ElGamal", 2048, pw); + System.out.println("genkey ok? " + ok); + } + + private static void testKeygen2(String[] args) throws Exception { + // keygen test using the I2PProvider + //SigType type = SigType.EdDSA_SHA512_Ed25519; + SigType type = SigType.ElGamal_SHA256_MODP2048; + java.security.KeyPairGenerator kpg = java.security.KeyPairGenerator.getInstance(type.getBaseAlgorithm().getName()); + kpg.initialize(type.getParams()); + java.security.KeyPair kp = kpg.generateKeyPair(); + java.security.PublicKey jpub = kp.getPublic(); + java.security.PrivateKey jpriv = kp.getPrivate(); + + System.out.println("Encoded private key:"); + System.out.println(net.i2p.util.HexDump.dump(jpriv.getEncoded())); + System.out.println("Encoded public key:"); + System.out.println(net.i2p.util.HexDump.dump(jpub.getEncoded())); + + java.security.Signature jsig = java.security.Signature.getInstance(type.getAlgorithmName()); + jsig.initSign(jpriv); + byte[] data = new byte[111]; + net.i2p.util.RandomSource.getInstance().nextBytes(data); + jsig.update(data); + byte[] bsig = jsig.sign(); + System.out.println("Encoded signature:"); + System.out.println(net.i2p.util.HexDump.dump(bsig)); + jsig.initVerify(jpub); + jsig.update(data); + boolean ok = jsig.verify(bsig); + System.out.println("verify passed? " + ok); + + net.i2p.data.Signature sig = SigUtil.fromJavaSig(bsig, type); + System.out.println("Signature test: " + sig); + } ****/ } diff --git a/core/java/src/net/i2p/crypto/RSAConstants.java b/core/java/src/net/i2p/crypto/RSAConstants.java index 3f77d09f19a0d6152fb234a96c9a3ad8346bae5d..c6e0234dc1062217a698ce5ef69bf9e0bf57b339 100644 --- a/core/java/src/net/i2p/crypto/RSAConstants.java +++ b/core/java/src/net/i2p/crypto/RSAConstants.java @@ -10,7 +10,7 @@ import net.i2p.util.NativeBigInteger; * * @since 0.9.9 */ -class RSAConstants { +final class RSAConstants { /** * Generate a spec diff --git a/core/java/src/net/i2p/crypto/SHA256Generator.java b/core/java/src/net/i2p/crypto/SHA256Generator.java index 66d630087fb3ea6bc4607b9067ee9b808548b3dd..e3facad9e455ce313b4f2df9ffd3a9f18d97f134 100644 --- a/core/java/src/net/i2p/crypto/SHA256Generator.java +++ b/core/java/src/net/i2p/crypto/SHA256Generator.java @@ -1,7 +1,5 @@ package net.i2p.crypto; -import gnu.crypto.hash.Sha256Standalone; - import java.security.DigestException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -14,25 +12,13 @@ import net.i2p.data.Hash; * Defines a wrapper for SHA-256 operation. * * As of release 0.8.7, uses java.security.MessageDigest by default. - * If that is unavailable, it uses + * As of release 0.9.25, uses only MessageDigest. * GNU-Crypto {@link gnu.crypto.hash.Sha256Standalone} + * is deprecated. */ public final class SHA256Generator { private final LinkedBlockingQueue<MessageDigest> _digests; - private static final boolean _useGnu; - - static { - boolean useGnu = false; - try { - MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - useGnu = true; - System.out.println("INFO: Using GNU SHA-256"); - } - _useGnu = useGnu; - } - /** * @param context unused */ @@ -96,45 +82,14 @@ public final class SHA256Generator { } /** - * Return a new MessageDigest from the system libs unless unavailable - * in this JVM, in that case return a wrapped GNU Sha256Standalone + * Return a new MessageDigest from the system libs. * @since 0.8.7, public since 0.8.8 for FortunaStandalone */ public static MessageDigest getDigestInstance() { - if (!_useGnu) { - try { - return MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) {} - } - return new GnuMessageDigest(); - } - - /** - * Wrapper to make Sha256Standalone a MessageDigest - * @since 0.8.7 - */ - private static class GnuMessageDigest extends MessageDigest { - private final Sha256Standalone _gnu; - - protected GnuMessageDigest() { - super("SHA-256"); - _gnu = new Sha256Standalone(); - } - - protected byte[] engineDigest() { - return _gnu.digest(); - } - - protected void engineReset() { - _gnu.reset(); - } - - protected void engineUpdate(byte input) { - _gnu.update(input); - } - - protected void engineUpdate(byte[] input, int offset, int len) { - _gnu.update(input, offset, len); + try { + return MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); } } diff --git a/core/java/src/net/i2p/crypto/SelfSignedGenerator.java b/core/java/src/net/i2p/crypto/SelfSignedGenerator.java new file mode 100644 index 0000000000000000000000000000000000000000..dd494792c654b2906a1d8d857fb205999a9c0351 --- /dev/null +++ b/core/java/src/net/i2p/crypto/SelfSignedGenerator.java @@ -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); + } +****/ +} diff --git a/core/java/src/net/i2p/crypto/SigAlgo.java b/core/java/src/net/i2p/crypto/SigAlgo.java index 8e0523c1bbd1222ffdac757b10d116d47b639e14..1195df148bab713c595fb35653be24ab7a36a46f 100644 --- a/core/java/src/net/i2p/crypto/SigAlgo.java +++ b/core/java/src/net/i2p/crypto/SigAlgo.java @@ -10,7 +10,15 @@ public enum SigAlgo { DSA("DSA"), EC("EC"), EdDSA("EdDSA"), - RSA("RSA") + /** + * For local use only, not for use in the network. + */ + RSA("RSA"), + /** + * For local use only, not for use in the network. + * @since 0.9.25 + */ + ElGamal("ElGamal") ; private final String name; diff --git a/core/java/src/net/i2p/crypto/SigType.java b/core/java/src/net/i2p/crypto/SigType.java index 05dd1906e7760c3e198853abf28175eac85c83c6..5da9e22b0475adf6b20817d229328ed0f11b270c 100644 --- a/core/java/src/net/i2p/crypto/SigType.java +++ b/core/java/src/net/i2p/crypto/SigType.java @@ -32,20 +32,20 @@ public enum SigType { * Pubkey 128 bytes; privkey 20 bytes; hash 20 bytes; sig 40 bytes * @since 0.9.8 */ - DSA_SHA1(0, 128, 20, 20, 40, SigAlgo.DSA, "SHA-1", "SHA1withDSA", CryptoConstants.DSA_SHA1_SPEC, "0"), + DSA_SHA1(0, 128, 20, 20, 40, SigAlgo.DSA, "SHA-1", "SHA1withDSA", CryptoConstants.DSA_SHA1_SPEC, "1.2.840.10040.4.3", "0"), /** Pubkey 64 bytes; privkey 32 bytes; hash 32 bytes; sig 64 bytes */ - ECDSA_SHA256_P256(1, 64, 32, 32, 64, SigAlgo.EC, "SHA-256", "SHA256withECDSA", ECConstants.P256_SPEC, "0.9.12"), + ECDSA_SHA256_P256(1, 64, 32, 32, 64, SigAlgo.EC, "SHA-256", "SHA256withECDSA", ECConstants.P256_SPEC, "1.2.840.10045.4.3.2", "0.9.12"), /** Pubkey 96 bytes; privkey 48 bytes; hash 48 bytes; sig 96 bytes */ - ECDSA_SHA384_P384(2, 96, 48, 48, 96, SigAlgo.EC, "SHA-384", "SHA384withECDSA", ECConstants.P384_SPEC, "0.9.12"), + ECDSA_SHA384_P384(2, 96, 48, 48, 96, SigAlgo.EC, "SHA-384", "SHA384withECDSA", ECConstants.P384_SPEC, "1.2.840.10045.4.3.3", "0.9.12"), /** Pubkey 132 bytes; privkey 66 bytes; hash 64 bytes; sig 132 bytes */ - ECDSA_SHA512_P521(3, 132, 66, 64, 132, SigAlgo.EC, "SHA-512", "SHA512withECDSA", ECConstants.P521_SPEC, "0.9.12"), + ECDSA_SHA512_P521(3, 132, 66, 64, 132, SigAlgo.EC, "SHA-512", "SHA512withECDSA", ECConstants.P521_SPEC, "1.2.840.10045.4.3.4", "0.9.12"), /** Pubkey 256 bytes; privkey 512 bytes; hash 32 bytes; sig 256 bytes */ - RSA_SHA256_2048(4, 256, 512, 32, 256, SigAlgo.RSA, "SHA-256", "SHA256withRSA", RSAConstants.F4_2048_SPEC, "0.9.12"), + RSA_SHA256_2048(4, 256, 512, 32, 256, SigAlgo.RSA, "SHA-256", "SHA256withRSA", RSAConstants.F4_2048_SPEC, "1.2.840.113549.1.1.11", "0.9.12"), /** Pubkey 384 bytes; privkey 768 bytes; hash 48 bytes; sig 384 bytes */ - RSA_SHA384_3072(5, 384, 768, 48, 384, SigAlgo.RSA, "SHA-384", "SHA384withRSA", RSAConstants.F4_3072_SPEC, "0.9.12"), + RSA_SHA384_3072(5, 384, 768, 48, 384, SigAlgo.RSA, "SHA-384", "SHA384withRSA", RSAConstants.F4_3072_SPEC, "1.2.840.113549.1.1.12", "0.9.12"), /** Pubkey 512 bytes; privkey 1024 bytes; hash 64 bytes; sig 512 bytes */ - RSA_SHA512_4096(6, 512, 1024, 64, 512, SigAlgo.RSA, "SHA-512", "SHA512withRSA", RSAConstants.F4_4096_SPEC, "0.9.12"), + RSA_SHA512_4096(6, 512, 1024, 64, 512, SigAlgo.RSA, "SHA-512", "SHA512withRSA", RSAConstants.F4_4096_SPEC, "1.2.840.113549.1.1.13", "0.9.12"), /** * Pubkey 32 bytes; privkey 32 bytes; hash 64 bytes; sig 64 bytes @@ -55,8 +55,17 @@ public enum SigType { * @since 0.9.15 */ EdDSA_SHA512_Ed25519(7, 32, 32, 64, 64, SigAlgo.EdDSA, "SHA-512", "SHA512withEdDSA", - EdDSANamedCurveTable.getByName("ed25519-sha-512"), "0.9.17"); + EdDSANamedCurveTable.getByName("ed25519-sha-512"), "1.3.101.101", "0.9.17"), + /** + * Prehash version (double hashing, for offline use such as su3, not for use on the network) + * Pubkey 32 bytes; privkey 32 bytes; hash 64 bytes; sig 64 bytes + * @since 0.9.25 + */ + EdDSA_SHA512_Ed25519ph(8, 32, 32, 64, 64, SigAlgo.EdDSA, "SHA-512", "NonewithEdDSA", + EdDSANamedCurveTable.getByName("ed25519-sha-512"), "1.3.101.101", "0.9.25"), + + ; // TESTING.................... @@ -99,12 +108,12 @@ public enum SigType { private final int code, pubkeyLen, privkeyLen, hashLen, sigLen; private final SigAlgo base; - private final String digestName, algoName, since; + private final String digestName, algoName, oid, since; private final AlgorithmParameterSpec params; private final boolean isAvail; SigType(int cod, int pubLen, int privLen, int hLen, int sLen, SigAlgo baseAlgo, - String mdName, String aName, AlgorithmParameterSpec pSpec, String supportedSince) { + String mdName, String aName, AlgorithmParameterSpec pSpec, String oid, String supportedSince) { code = cod; pubkeyLen = pubLen; privkeyLen = privLen; @@ -114,6 +123,7 @@ public enum SigType { digestName = mdName; algoName = aName; params = pSpec; + this.oid = oid; since = supportedSince; isAvail = x_isAvailable(); } @@ -183,6 +193,15 @@ public enum SigType { return since; } + /** + * The OID for the signature. + * + * @since 0.9.25 + */ + public String getOID() { + return oid; + } + /** * @since 0.9.12 * @return true if supported in this JVM @@ -274,6 +293,8 @@ public enum SigType { // handle mixed-case enum if (uc.equals("EDDSA_SHA512_ED25519")) return EdDSA_SHA512_Ed25519; + if (uc.equals("EDDSA_SHA512_ED25519PH")) + return EdDSA_SHA512_Ed25519ph; return valueOf(uc); } catch (IllegalArgumentException iae) { try { diff --git a/core/java/src/net/i2p/crypto/SigUtil.java b/core/java/src/net/i2p/crypto/SigUtil.java index d04abb438d125646c53760906d2bcd4beeeae13b..06d84770506aaee3c83c45097db5dc33ae3365d1 100644 --- a/core/java/src/net/i2p/crypto/SigUtil.java +++ b/core/java/src/net/i2p/crypto/SigUtil.java @@ -50,7 +50,7 @@ import net.i2p.util.NativeBigInteger; * * @since 0.9.9, public since 0.9.12 */ -public class SigUtil { +public final class SigUtil { private static final Map<SigningPublicKey, ECPublicKey> _ECPubkeyCache = new LHMCache<SigningPublicKey, ECPublicKey>(64); private static final Map<SigningPrivateKey, ECPrivateKey> _ECPrivkeyCache = new LHMCache<SigningPrivateKey, ECPrivateKey>(16); @@ -141,7 +141,7 @@ public class SigUtil { throw new IllegalArgumentException("Unknown RSA type"); return fromJavaKey(k, type); } - throw new IllegalArgumentException("Unknown type"); + throw new IllegalArgumentException("Unknown type: " + pk.getClass()); } /** @@ -161,7 +161,7 @@ public class SigUtil { case RSA: return fromJavaKey((RSAPublicKey) pk, type); default: - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Unknown type: " + type); } } @@ -209,7 +209,7 @@ public class SigUtil { throw new IllegalArgumentException("Unknown RSA type"); return fromJavaKey(k, type); } - throw new IllegalArgumentException("Unknown type"); + throw new IllegalArgumentException("Unknown type: " + pk.getClass()); } /** @@ -229,7 +229,7 @@ public class SigUtil { case RSA: return fromJavaKey((RSAPrivateKey) pk, type); default: - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Unknown type: " + type); } } @@ -549,9 +549,13 @@ public class SigUtil { /** * Split a byte array into two BigIntegers + * @param b length must be even * @return array of two BigIntegers + * @since 0.9.9 */ - private static BigInteger[] split(byte[] b) { + private static NativeBigInteger[] split(byte[] b) { + if ((b.length & 0x01) != 0) + throw new IllegalArgumentException("length must be even"); int sublen = b.length / 2; byte[] bx = new byte[sublen]; byte[] by = new byte[sublen]; @@ -565,9 +569,12 @@ public class SigUtil { /** * Combine two BigIntegers of nominal length = len / 2 * @return array of exactly len bytes + * @since 0.9.9 */ private static byte[] combine(BigInteger x, BigInteger y, int len) throws InvalidKeyException { + if ((len & 0x01) != 0) + throw new InvalidKeyException("length must be even"); int sublen = len / 2; byte[] b = new byte[len]; byte[] bx = rectify(x, sublen); @@ -609,7 +616,8 @@ public class SigUtil { /** * http://download.oracle.com/javase/1.5.0/docs/guide/security/CryptoSpec.html - * Signature Format ASN.1 sequence of two INTEGER values: r and s, in that order: + *<pre> + * Signature Format: ASN.1 sequence of two INTEGER values: r and s, in that order: * SEQUENCE ::= { r INTEGER, s INTEGER } * * http://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One @@ -619,6 +627,7 @@ public class SigUtil { * 02 -- tag indicating INTEGER * xx - length in octets * xxxxxx - value + *</pre> * * Convert to BigInteger and back so we have the minimum length representation, as required. * r and s are always non-negative. @@ -626,57 +635,107 @@ public class SigUtil { * Only supports sigs up to about 252 bytes. See code to fix BER encoding for this before you * add a SigType with bigger signatures. * + * @param sig length must be even * @throws IllegalArgumentException if too big * @since 0.8.7, moved to SigUtil in 0.9.9 */ private static byte[] sigBytesToASN1(byte[] sig) { - //System.out.println("pre TO asn1\n" + net.i2p.util.HexDump.dump(sig)); - int len = sig.length; - int sublen = len / 2; - byte[] tmp = new byte[sublen]; + BigInteger[] rs = split(sig); + return sigBytesToASN1(rs[0], rs[1]); + } - System.arraycopy(sig, 0, tmp, 0, sublen); - BigInteger r = new BigInteger(1, tmp); + /** + * http://download.oracle.com/javase/1.5.0/docs/guide/security/CryptoSpec.html + *<pre> + * Signature Format: ASN.1 sequence of two INTEGER values: r and s, in that order: + * SEQUENCE ::= { r INTEGER, s INTEGER } + * + * http://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One + * 30 -- tag indicating SEQUENCE + * xx - length in octets + * + * 02 -- tag indicating INTEGER + * xx - length in octets + * xxxxxx - value + *</pre> + * + * r and s are always non-negative. + * + * Only supports sigs up to about 65530 bytes. See code to fix BER encoding for this before you + * add a SigType with bigger signatures. + * + * @throws IllegalArgumentException if too big + * @since 0.9.25, split out from sigBytesToASN1(byte[]) + */ + public static byte[] sigBytesToASN1(BigInteger r, BigInteger s) { + int extra = 4; byte[] rb = r.toByteArray(); - if (rb.length > 127) - throw new IllegalArgumentException("FIXME R length > 127"); - System.arraycopy(sig, sublen, tmp, 0, sublen); - BigInteger s = new BigInteger(1, tmp); + if (rb.length > 127) { + extra++; + if (rb.length > 255) + extra++; + } byte[] sb = s.toByteArray(); - if (sb.length > 127) - throw new IllegalArgumentException("FIXME S length > 127"); - int seqlen = rb.length + sb.length + 4; - if (seqlen > 255) - throw new IllegalArgumentException("FIXME seq length > 255"); + if (sb.length > 127) { + extra++; + if (sb.length > 255) + extra++; + } + int seqlen = rb.length + sb.length + extra; int totlen = seqlen + 2; - if (seqlen > 127) + if (seqlen > 127) { totlen++; + if (seqlen > 255) + totlen++; + } byte[] rv = new byte[totlen]; int idx = 0; rv[idx++] = 0x30; - if (seqlen > 127) - rv[idx++] =(byte) 0x81; - rv[idx++] = (byte) seqlen; + idx = intToASN1(rv, idx, seqlen); rv[idx++] = 0x02; - rv[idx++] = (byte) rb.length; + idx = intToASN1(rv, idx, rb.length); System.arraycopy(rb, 0, rv, idx, rb.length); idx += rb.length; rv[idx++] = 0x02; - rv[idx++] = (byte) sb.length; + idx = intToASN1(rv, idx, sb.length); System.arraycopy(sb, 0, rv, idx, sb.length); //System.out.println("post TO asn1\n" + net.i2p.util.HexDump.dump(rv)); return rv; } + /** + * Output an length or integer value in ASN.1 + * Does NOT output the tag e.g. 0x02 / 0x30 + * + * @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 252 bytes. See code to fix BER encoding for bigger than that. + * Only supports sigs up to about 65530 bytes. See code to fix BER encoding for bigger than that. * - * @return len bytes + * @param len must be even, twice the nominal length of each BigInteger + * @return len bytes, call split() on the result to get two BigIntegers * @since 0.8.7, moved to SigUtil in 0.9.9 */ private static byte[] aSN1ToSigBytes(byte[] asn, int len) @@ -693,8 +752,17 @@ public class SigUtil { byte[] rv = new byte[len]; int sublen = len / 2; int rlen = asn[++idx]; - if ((rlen & 0x80) != 0) - throw new SignatureException("FIXME R length > 127"); + if ((rlen & 0x80) != 0) { + if ((rlen & 0xff) == 0x81) { + rlen = asn[++idx] & 0xff; + } else if ((rlen & 0xff) == 0x82) { + rlen = asn[++idx] & 0xff; + rlen <<= 8; + rlen |= asn[++idx] & 0xff; + } else { + throw new SignatureException("FIXME R length > 65535"); + } + } if ((asn[++idx] & 0x80) != 0) throw new SignatureException("R is negative"); if (rlen > sublen + 1) @@ -704,24 +772,47 @@ public class SigUtil { else System.arraycopy(asn, idx, rv, sublen - rlen, rlen); idx += rlen; - int slenloc = idx + 1; + if (asn[idx] != 0x02) throw new SignatureException("asn[s] = " + (asn[idx] & 0xff)); - int slen = asn[slenloc]; - if ((slen & 0x80) != 0) - throw new SignatureException("FIXME S length > 127"); - if ((asn[slenloc + 1] & 0x80) != 0) + int slen = asn[++idx]; + if ((slen & 0x80) != 0) { + if ((slen & 0xff) == 0x81) { + slen = asn[++idx] & 0xff; + } else if ((slen & 0xff) == 0x82) { + slen = asn[++idx] & 0xff; + slen <<= 8; + slen |= asn[++idx] & 0xff; + } else { + throw new SignatureException("FIXME S length > 65535"); + } + } + if ((asn[++idx] & 0x80) != 0) throw new SignatureException("S is negative"); if (slen > sublen + 1) throw new SignatureException("S too big " + slen); if (slen == sublen + 1) - System.arraycopy(asn, slenloc + 2, rv, sublen, sublen); + System.arraycopy(asn, idx + 1, rv, sublen, sublen); else - System.arraycopy(asn, slenloc + 1, rv, len - slen, slen); + System.arraycopy(asn, idx, rv, len - slen, slen); //System.out.println("post from asn1\n" + net.i2p.util.HexDump.dump(rv)); return rv; } + /** + * See above. + * Only supports sigs up to about 65530 bytes. See code to fix BER encoding for bigger than that. + * + * @param len nominal length of each BigInteger + * @return two BigIntegers + * @since 0.9.25 + */ + public static NativeBigInteger[] aSN1ToBigInteger(byte[] asn, int len) + throws SignatureException { + byte[] sig = aSN1ToSigBytes(asn, len * 2); + return split(sig); + } + public static void clearCaches() { synchronized(_ECPubkeyCache) { _ECPubkeyCache.clear(); diff --git a/core/java/src/net/i2p/crypto/YKGenerator.java b/core/java/src/net/i2p/crypto/YKGenerator.java index 30245c22992d0b93ded777188f894733c83916e2..7a69eee367e828c645489294a87f3567dff2c26f 100644 --- a/core/java/src/net/i2p/crypto/YKGenerator.java +++ b/core/java/src/net/i2p/crypto/YKGenerator.java @@ -35,7 +35,7 @@ import net.i2p.util.SystemVersion; * * @author jrandom */ -class YKGenerator { +final class YKGenerator { //private final static Log _log = new Log(YKGenerator.class); private final int MIN_NUM_BUILDERS; private final int MAX_NUM_BUILDERS; diff --git a/core/java/src/net/i2p/crypto/eddsa/EdDSAEngine.java b/core/java/src/net/i2p/crypto/eddsa/EdDSAEngine.java index 6cf9b4d859d371dbc50af63e074799354c7faab9..d39863c38dfb8b66a547648fbd93602a7a4eff8f 100644 --- a/core/java/src/net/i2p/crypto/eddsa/EdDSAEngine.java +++ b/core/java/src/net/i2p/crypto/eddsa/EdDSAEngine.java @@ -2,6 +2,7 @@ package net.i2p.crypto.eddsa; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; +import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -9,6 +10,7 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; +import java.security.spec.AlgorithmParameterSpec; import java.util.Arrays; import net.i2p.crypto.eddsa.math.Curve; @@ -16,21 +18,68 @@ import net.i2p.crypto.eddsa.math.GroupElement; import net.i2p.crypto.eddsa.math.ScalarOps; /** + * Signing and verification for EdDSA. + *<p> + * The EdDSA sign and verify algorithms do not interact well with + * the Java Signature API, as one or more update() methods must be + * called before sign() or verify(). Using the standard API, + * this implementation must copy and buffer all data passed in + * via update(). + *</p><p> + * This implementation offers two ways to avoid this copying, + * but only if all data to be signed or verified is available + * in a single byte array. + *</p><p> + *Option 1: + *</p><ol> + *<li>Call initSign() or initVerify() as usual. + *</li><li>Call setParameter(ONE_SHOT_MOE) + *</li><li>Call update(byte[]) or update(byte[], int, int) exactly once + *</li><li>Call sign() or verify() as usual. + *</li><li>If doing additional one-shot signs or verifies with this object, you must + * call setParameter(ONE_SHOT_MODE) each time + *</li></ol> + * + *<p> + *Option 2: + *</p><ol> + *<li>Call initSign() or initVerify() as usual. + *</li><li>Call one of the signOneShot() or verifyOneShot() methods. + *</li><li>If doing additional one-shot signs or verifies with this object, + * just call signOneShot() or verifyOneShot() again. + *</li></ol> + * * @since 0.9.15 * @author str4d * */ -public class EdDSAEngine extends Signature { +public final class EdDSAEngine extends Signature { private MessageDigest digest; - private final ByteArrayOutputStream baos; + private ByteArrayOutputStream baos; private EdDSAKey key; + private boolean oneShotMode; + private byte[] oneShotBytes; + private int oneShotOffset; + private int oneShotLength; + + /** + * To efficiently sign or verify data in one shot, pass this to setParameters() + * after initSign() or initVerify() but BEFORE THE FIRST AND ONLY + * update(data) or update(data, off, len). The data reference will be saved + * and then used in sign() or verify() without copying the data. + * Violate these rules and you will get a SignatureException. + * + * @since 0.9.25 + */ + public static final AlgorithmParameterSpec ONE_SHOT_MODE = new OneShotSpec(); + + private static class OneShotSpec implements AlgorithmParameterSpec {} /** * No specific hash requested, allows any EdDSA key. */ public EdDSAEngine() { super("EdDSA"); - baos = new ByteArrayOutputStream(256); } /** @@ -42,12 +91,21 @@ public class EdDSAEngine extends Signature { this.digest = digest; } - @Override - protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException { + /** + * @since 0.9.25 + */ + private void reset() { if (digest != null) digest.reset(); - baos.reset(); + if (baos != null) + baos.reset(); + oneShotMode = false; + oneShotBytes = null; + } + @Override + protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException { + reset(); if (privateKey instanceof EdDSAPrivateKey) { EdDSAPrivateKey privKey = (EdDSAPrivateKey) privateKey; key = privKey; @@ -61,21 +119,22 @@ public class EdDSAEngine extends Signature { } } else if (!key.getParams().getHashAlgorithm().equals(digest.getAlgorithm())) throw new InvalidKeyException("Key hash algorithm does not match chosen digest"); + digestInitSign(privKey); + } else { + throw new InvalidKeyException("cannot identify EdDSA private key: " + privateKey.getClass()); + } + } - // Preparing for hash - // r = H(h_b,...,h_2b-1,M) - int b = privKey.getParams().getCurve().getField().getb(); - digest.update(privKey.getH(), b/8, b/4 - b/8); - } else - throw new InvalidKeyException("cannot identify EdDSA private key."); + private void digestInitSign(EdDSAPrivateKey privKey) { + // Preparing for hash + // r = H(h_b,...,h_2b-1,M) + int b = privKey.getParams().getCurve().getField().getb(); + digest.update(privKey.getH(), b/8, b/4 - b/8); } @Override protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException { - if (digest != null) - digest.reset(); - baos.reset(); - + reset(); if (publicKey instanceof EdDSAPublicKey) { key = (EdDSAPublicKey) publicKey; @@ -88,34 +147,79 @@ public class EdDSAEngine extends Signature { } } else if (!key.getParams().getHashAlgorithm().equals(digest.getAlgorithm())) throw new InvalidKeyException("Key hash algorithm does not match chosen digest"); - } else - throw new InvalidKeyException("cannot identify EdDSA public key."); + } else { + throw new InvalidKeyException("cannot identify EdDSA public key: " + publicKey.getClass()); + } } + /** + * @throws SignatureException if in one-shot mode + */ @Override protected void engineUpdate(byte b) throws SignatureException { - // We need to store the message because it is used in several hashes - // XXX Can this be done more efficiently? + if (oneShotMode) + throw new SignatureException("unsupported in one-shot mode"); + if (baos == null) + baos = new ByteArrayOutputStream(256); baos.write(b); } + /** + * @throws SignatureException if one-shot rules are violated + */ @Override protected void engineUpdate(byte[] b, int off, int len) throws SignatureException { - // We need to store the message because it is used in several hashes - // XXX Can this be done more efficiently? - baos.write(b, off, len); + if (oneShotMode) { + if (oneShotBytes != null) + throw new SignatureException("update() already called"); + oneShotBytes = b; + oneShotOffset = off; + oneShotLength = len; + } else { + if (baos == null) + baos = new ByteArrayOutputStream(256); + baos.write(b, off, len); + } } @Override protected byte[] engineSign() throws SignatureException { + try { + return x_engineSign(); + } finally { + reset(); + // must leave the object ready to sign again with + // the same key, as required by the API + EdDSAPrivateKey privKey = (EdDSAPrivateKey) key; + digestInitSign(privKey); + } + } + + private byte[] x_engineSign() throws SignatureException { Curve curve = key.getParams().getCurve(); ScalarOps sc = key.getParams().getScalarOps(); byte[] a = ((EdDSAPrivateKey) key).geta(); - byte[] message = baos.toByteArray(); + byte[] message; + int offset, length; + if (oneShotMode) { + if (oneShotBytes == null) + throw new SignatureException("update() not called first"); + message = oneShotBytes; + offset = oneShotOffset; + length = oneShotLength; + } else { + if (baos == null) + message = new byte[0]; + else + message = baos.toByteArray(); + offset = 0; + length = message.length; + } // r = H(h_b,...,h_2b-1,M) - byte[] r = digest.digest(message); + digest.update(message, offset, length); + byte[] r = digest.digest(); // r mod l // Reduces r from 64 bytes to 32 bytes @@ -128,7 +232,8 @@ public class EdDSAEngine extends Signature { // S = (r + H(Rbar,Abar,M)*a) mod l digest.update(Rbyte); digest.update(((EdDSAPrivateKey) key).getAbyte()); - byte[] h = digest.digest(message); + digest.update(message, offset, length); + byte[] h = digest.digest(); h = sc.reduce(h); byte[] S = sc.multiplyAndAdd(h, a, r); @@ -141,6 +246,14 @@ public class EdDSAEngine extends Signature { @Override protected boolean engineVerify(byte[] sigBytes) throws SignatureException { + try { + return x_engineVerify(sigBytes); + } finally { + reset(); + } + } + + private boolean x_engineVerify(byte[] sigBytes) throws SignatureException { Curve curve = key.getParams().getCurve(); int b = curve.getField().getb(); if (sigBytes.length != b/4) @@ -150,8 +263,24 @@ public class EdDSAEngine extends Signature { digest.update(sigBytes, 0, b/8); digest.update(((EdDSAPublicKey) key).getAbyte()); // h = H(Rbar,Abar,M) - byte[] message = baos.toByteArray(); - byte[] h = digest.digest(message); + byte[] message; + int offset, length; + if (oneShotMode) { + if (oneShotBytes == null) + throw new SignatureException("update() not called first"); + message = oneShotBytes; + offset = oneShotOffset; + length = oneShotLength; + } else { + if (baos == null) + message = new byte[0]; + else + message = baos.toByteArray(); + offset = 0; + length = message.length; + } + digest.update(message, offset, length); + byte[] h = digest.digest(); // h mod l h = key.getParams().getScalarOps().reduce(h); @@ -171,6 +300,140 @@ public class EdDSAEngine extends Signature { return true; } + /** + * To efficiently sign all the data in one shot, if it is available, + * use this method, which will avoid copying the data. + * + * Same as: + *<pre> + * setParameter(ONE_SHOT_MODE) + * update(data) + * sig = sign() + *</pre> + * + * @throws SignatureException if update() already called + * @see #ONE_SHOT_MODE + * @since 0.9.25 + */ + public byte[] signOneShot(byte[] data) throws SignatureException { + return signOneShot(data, 0, data.length); + } + + /** + * To efficiently sign all the data in one shot, if it is available, + * use this method, which will avoid copying the data. + * + * Same as: + *<pre> + * setParameter(ONE_SHOT_MODE) + * update(data, off, len) + * sig = sign() + *</pre> + * + * @throws SignatureException if update() already called + * @see #ONE_SHOT_MODE + * @since 0.9.25 + */ + public byte[] signOneShot(byte[] data, int off, int len) throws SignatureException { + oneShotMode = true; + update(data, off, len); + return sign(); + } + + /** + * To efficiently verify all the data in one shot, if it is available, + * use this method, which will avoid copying the data. + * + * Same as: + *<pre> + * setParameter(ONE_SHOT_MODE) + * update(data) + * ok = verify(signature) + *</pre> + * + * @throws SignatureException if update() already called + * @see #ONE_SHOT_MODE + * @since 0.9.25 + */ + public boolean verifyOneShot(byte[] data, byte[] signature) throws SignatureException { + return verifyOneShot(data, 0, data.length, signature, 0, signature.length); + } + + /** + * To efficiently verify all the data in one shot, if it is available, + * use this method, which will avoid copying the data. + * + * Same as: + *<pre> + * setParameter(ONE_SHOT_MODE) + * update(data, off, len) + * ok = verify(signature) + *</pre> + * + * @throws SignatureException if update() already called + * @see #ONE_SHOT_MODE + * @since 0.9.25 + */ + public boolean verifyOneShot(byte[] data, int off, int len, byte[] signature) throws SignatureException { + return verifyOneShot(data, off, len, signature, 0, signature.length); + } + + /** + * To efficiently verify all the data in one shot, if it is available, + * use this method, which will avoid copying the data. + * + * Same as: + *<pre> + * setParameter(ONE_SHOT_MODE) + * update(data) + * ok = verify(signature, sigoff, siglen) + *</pre> + * + * @throws SignatureException if update() already called + * @see #ONE_SHOT_MODE + * @since 0.9.25 + */ + public boolean verifyOneShot(byte[] data, byte[] signature, int sigoff, int siglen) throws SignatureException { + return verifyOneShot(data, 0, data.length, signature, sigoff, siglen); + } + + /** + * To efficiently verify all the data in one shot, if it is available, + * use this method, which will avoid copying the data. + * + * Same as: + *<pre> + * setParameter(ONE_SHOT_MODE) + * update(data, off, len) + * ok = verify(signature, sigoff, siglen) + *</pre> + * + * @throws SignatureException if update() already called + * @see #ONE_SHOT_MODE + * @since 0.9.25 + */ + public boolean verifyOneShot(byte[] data, int off, int len, byte[] signature, int sigoff, int siglen) throws SignatureException { + oneShotMode = true; + update(data, off, len); + return verify(signature, sigoff, siglen); + } + + /** + * @throws InvalidAlgorithmParameterException if spec is ONE_SHOT_MODE and update() already called + * @see #ONE_SHOT_MODE + * @since 0.9.25 + */ + @Override + protected void engineSetParameter(AlgorithmParameterSpec spec) throws InvalidAlgorithmParameterException { + if (spec.equals(ONE_SHOT_MODE)) { + if (oneShotBytes != null || baos != null && baos.size() > 0) + throw new InvalidAlgorithmParameterException("update() already called"); + oneShotMode = true; + } else { + super.engineSetParameter(spec); + } + } + /** * @deprecated replaced with <a href="#engineSetParameter(java.security.spec.AlgorithmParameterSpec)"> */ diff --git a/core/java/src/net/i2p/crypto/eddsa/EdDSAPrivateKey.java b/core/java/src/net/i2p/crypto/eddsa/EdDSAPrivateKey.java index 4136b321a72222ce14374efcca9029680227c4cc..3092ee12ad1a6615a81646a29d9a93fd5fe9e1c0 100644 --- a/core/java/src/net/i2p/crypto/eddsa/EdDSAPrivateKey.java +++ b/core/java/src/net/i2p/crypto/eddsa/EdDSAPrivateKey.java @@ -1,13 +1,24 @@ package net.i2p.crypto.eddsa; import java.security.PrivateKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Arrays; import net.i2p.crypto.eddsa.math.GroupElement; +import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec; import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; /** * An EdDSA private key. + *<p> + * Warning: Private key encoding is not fully specified in the + * current IETF draft. This implementation uses PKCS#8 encoding, + * and is subject to change. See getEncoded(). + *</p><p> + * Ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04 + *</p> * * @since 0.9.15 * @author str4d @@ -31,6 +42,14 @@ public class EdDSAPrivateKey implements EdDSAKey, PrivateKey { this.edDsaSpec = spec.getParams(); } + /** + * @since 0.9.25 + */ + public EdDSAPrivateKey(PKCS8EncodedKeySpec spec) throws InvalidKeySpecException { + this(new EdDSAPrivateKeySpec(decode(spec.getEncoded()), + EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512))); + } + public String getAlgorithm() { return "EdDSA"; } @@ -39,9 +58,116 @@ public class EdDSAPrivateKey implements EdDSAKey, PrivateKey { return "PKCS#8"; } + /** + * This follows the docs from + * java.security.spec.PKCS8EncodedKeySpec + * quote: + *<pre> + * The PrivateKeyInfo syntax is defined in the PKCS#8 standard as follows: + * PrivateKeyInfo ::= SEQUENCE { + * version Version, + * privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, + * privateKey PrivateKey, + * attributes [0] IMPLICIT Attributes OPTIONAL } + * Version ::= INTEGER + * PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier + * PrivateKey ::= OCTET STRING + * Attributes ::= SET OF Attribute + *</pre> + * + *<pre> + * AlgorithmIdentifier ::= SEQUENCE + * { + * algorithm OBJECT IDENTIFIER, + * parameters ANY OPTIONAL + * } + *</pre> + * + * Ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04 + * + * Note that the private key encoding is not fully specified in the Josefsson draft version 04, + * and the example could be wrong, as it's lacking Version and AlgorithmIdentifier. + * This will hopefully be clarified in the next draft. + * But sun.security.pkcs.PKCS8Key expects them so we must include them for keytool to work. + * + * @return 49 bytes for Ed25519, null for other curves + * @since implemented in 0.9.25 + */ public byte[] getEncoded() { - // TODO Auto-generated method stub - return null; + if (!edDsaSpec.equals(EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512))) + return null; + int totlen = 17 + seed.length; + byte[] rv = new byte[totlen]; + int idx = 0; + // sequence + rv[idx++] = 0x30; + rv[idx++] = (byte) (15 + seed.length); + + // version + // not in the Josefsson example + rv[idx++] = 0x02; + rv[idx++] = 1; + rv[idx++] = 0; + + // Algorithm Identifier + // sequence + // not in the Josefsson example + rv[idx++] = 0x30; + rv[idx++] = 8; + // OID 1.3.101.100 + // https://msdn.microsoft.com/en-us/library/windows/desktop/bb540809%28v=vs.85%29.aspx + // not in the Josefsson example + rv[idx++] = 0x06; + rv[idx++] = 3; + rv[idx++] = (1 * 40) + 3; + rv[idx++] = 101; + rv[idx++] = 100; + // params + rv[idx++] = 0x0a; + rv[idx++] = 1; + rv[idx++] = 1; // Ed25519 + // the key + rv[idx++] = 0x04; // octet string + rv[idx++] = (byte) seed.length; + System.arraycopy(seed, 0, rv, idx, seed.length); + return rv; + } + + /** + * This is really dumb for now. + * See getEncoded(). + * + * @return 32 bytes for Ed25519, throws for other curves + * @since 0.9.25 + */ + private static byte[] decode(byte[] d) throws InvalidKeySpecException { + try { + int idx = 0; + if (d[idx++] != 0x30 || + d[idx++] != 47 || + d[idx++] != 0x02 || + d[idx++] != 1 || + d[idx++] != 0 || + d[idx++] != 0x30 || + d[idx++] != 8 || + d[idx++] != 0x06 || + d[idx++] != 3 || + d[idx++] != (1 * 40) + 3 || + d[idx++] != 101 || + d[idx++] != 100 || + d[idx++] != 0x0a || + d[idx++] != 1 || + d[idx++] != 1 || + d[idx++] != 0x04 || + d[idx++] != 32) { + throw new InvalidKeySpecException("unsupported key spec"); + } + byte[] rv = new byte[32]; + System.arraycopy(d, idx, rv, 0, 32); + return rv; + } catch (IndexOutOfBoundsException ioobe) { + throw new InvalidKeySpecException(ioobe); + } } public EdDSAParameterSpec getParams() { @@ -67,4 +193,26 @@ public class EdDSAPrivateKey implements EdDSAKey, PrivateKey { public byte[] getAbyte() { return Abyte; } + + /** + * @since 0.9.25 + */ + @Override + public int hashCode() { + return Arrays.hashCode(seed); + } + + /** + * @since 0.9.25 + */ + @Override + public boolean equals(Object o) { + if (o == this) + return true; + if (!(o instanceof EdDSAPrivateKey)) + return false; + EdDSAPrivateKey pk = (EdDSAPrivateKey) o; + return Arrays.equals(seed, pk.getSeed()) && + edDsaSpec.equals(pk.getParams()); + } } diff --git a/core/java/src/net/i2p/crypto/eddsa/EdDSAPublicKey.java b/core/java/src/net/i2p/crypto/eddsa/EdDSAPublicKey.java index 8e436bd1d8b305cd761fc6d4334748427e8efb3f..747d92b7be3356874bf5e11bb9ef9651ebceafc2 100644 --- a/core/java/src/net/i2p/crypto/eddsa/EdDSAPublicKey.java +++ b/core/java/src/net/i2p/crypto/eddsa/EdDSAPublicKey.java @@ -1,13 +1,23 @@ package net.i2p.crypto.eddsa; import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; import net.i2p.crypto.eddsa.math.GroupElement; +import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec; import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; /** * An EdDSA public key. + *<p> + * Warning: Public key encoding is is based on the + * current IETF draft, and is subject to change. See getEncoded(). + *</p><p> + * Ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04 + *</p> * * @since 0.9.15 * @author str4d @@ -27,6 +37,14 @@ public class EdDSAPublicKey implements EdDSAKey, PublicKey { this.edDsaSpec = spec.getParams(); } + /** + * @since 0.9.25 + */ + public EdDSAPublicKey(X509EncodedKeySpec spec) throws InvalidKeySpecException { + this(new EdDSAPublicKeySpec(decode(spec.getEncoded()), + EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512))); + } + public String getAlgorithm() { return "EdDSA"; } @@ -35,9 +53,95 @@ public class EdDSAPublicKey implements EdDSAKey, PublicKey { return "X.509"; } + /** + * This follows the spec at + * ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04 + * which matches the docs from + * java.security.spec.X509EncodedKeySpec + * quote: + *<pre> + * The SubjectPublicKeyInfo syntax is defined in the X.509 standard as follows: + * SubjectPublicKeyInfo ::= SEQUENCE { + * algorithm AlgorithmIdentifier, + * subjectPublicKey BIT STRING } + *</pre> + * + *<pre> + * AlgorithmIdentifier ::= SEQUENCE + * { + * algorithm OBJECT IDENTIFIER, + * parameters ANY OPTIONAL + * } + *</pre> + * + * @return 47 bytes for Ed25519, null for other curves + * @since implemented in 0.9.25 + */ public byte[] getEncoded() { - // TODO Auto-generated method stub - return null; + if (!edDsaSpec.equals(EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512))) + return null; + int totlen = 15 + Abyte.length; + byte[] rv = new byte[totlen]; + int idx = 0; + // sequence + rv[idx++] = 0x30; + rv[idx++] = (byte) (13 + Abyte.length); + // Algorithm Identifier + // sequence + rv[idx++] = 0x30; + rv[idx++] = 8; + // OID 1.3.101.100 + // https://msdn.microsoft.com/en-us/library/windows/desktop/bb540809%28v=vs.85%29.aspx + rv[idx++] = 0x06; + rv[idx++] = 3; + rv[idx++] = (1 * 40) + 3; + rv[idx++] = 101; + rv[idx++] = 100; + // params + rv[idx++] = 0x0a; + rv[idx++] = 1; + rv[idx++] = 1; // Ed25519 + // the key + rv[idx++] = 0x03; // bit string + rv[idx++] = (byte) (1 + Abyte.length); + rv[idx++] = 0; // number of trailing unused bits + System.arraycopy(Abyte, 0, rv, idx, Abyte.length); + return rv; + } + + /** + * This is really dumb for now. + * See getEncoded(). + * + * @return 32 bytes for Ed25519, throws for other curves + * @since 0.9.25 + */ + private static byte[] decode(byte[] d) throws InvalidKeySpecException { + try { + int idx = 0; + if (d[idx++] != 0x30 || + d[idx++] != 45 || + d[idx++] != 0x30 || + d[idx++] != 8 || + d[idx++] != 0x06 || + d[idx++] != 3 || + d[idx++] != (1 * 40) + 3 || + d[idx++] != 101 || + d[idx++] != 100 || + d[idx++] != 0x0a || + d[idx++] != 1 || + d[idx++] != 1 || + d[idx++] != 0x03 || + d[idx++] != 33 || + d[idx++] != 0) { + throw new InvalidKeySpecException("unsupported key spec"); + } + byte[] rv = new byte[32]; + System.arraycopy(d, idx, rv, 0, 32); + return rv; + } catch (IndexOutOfBoundsException ioobe) { + throw new InvalidKeySpecException(ioobe); + } } public EdDSAParameterSpec getParams() { @@ -55,4 +159,26 @@ public class EdDSAPublicKey implements EdDSAKey, PublicKey { public byte[] getAbyte() { return Abyte; } + + /** + * @since 0.9.25 + */ + @Override + public int hashCode() { + return Arrays.hashCode(Abyte); + } + + /** + * @since 0.9.25 + */ + @Override + public boolean equals(Object o) { + if (o == this) + return true; + if (!(o instanceof EdDSAPublicKey)) + return false; + EdDSAPublicKey pk = (EdDSAPublicKey) o; + return Arrays.equals(Abyte, pk.getAbyte()) && + edDsaSpec.equals(pk.getParams()); + } } diff --git a/core/java/src/net/i2p/crypto/eddsa/KeyFactory.java b/core/java/src/net/i2p/crypto/eddsa/KeyFactory.java index 950130ac987fd290c835117a281cb58a8656e04d..72fd03152ec098058722cfc2f88c9d73abb00eef 100644 --- a/core/java/src/net/i2p/crypto/eddsa/KeyFactory.java +++ b/core/java/src/net/i2p/crypto/eddsa/KeyFactory.java @@ -7,6 +7,8 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; @@ -16,22 +18,34 @@ import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; * @author str4d * */ -public class KeyFactory extends KeyFactorySpi { +public final class KeyFactory extends KeyFactorySpi { + /** + * As of 0.9.25, supports PKCS8EncodedKeySpec + */ protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws InvalidKeySpecException { if (keySpec instanceof EdDSAPrivateKeySpec) { return new EdDSAPrivateKey((EdDSAPrivateKeySpec) keySpec); } - throw new InvalidKeySpecException("key spec not recognised"); + if (keySpec instanceof PKCS8EncodedKeySpec) { + return new EdDSAPrivateKey((PKCS8EncodedKeySpec) keySpec); + } + throw new InvalidKeySpecException("key spec not recognised: " + keySpec.getClass()); } + /** + * As of 0.9.25, supports X509EncodedKeySpec + */ protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecException { if (keySpec instanceof EdDSAPublicKeySpec) { return new EdDSAPublicKey((EdDSAPublicKeySpec) keySpec); } - throw new InvalidKeySpecException("key spec not recognised"); + if (keySpec instanceof X509EncodedKeySpec) { + return new EdDSAPublicKey((X509EncodedKeySpec) keySpec); + } + throw new InvalidKeySpecException("key spec not recognised: " + keySpec.getClass()); } @SuppressWarnings("unchecked") diff --git a/core/java/src/net/i2p/crypto/eddsa/KeyPairGenerator.java b/core/java/src/net/i2p/crypto/eddsa/KeyPairGenerator.java index 1de1f5b2448e505e4fcf7a8220518334955f8d97..6fd13d865ce643d10c13287468d9cc8ca0d8a3a1 100644 --- a/core/java/src/net/i2p/crypto/eddsa/KeyPairGenerator.java +++ b/core/java/src/net/i2p/crypto/eddsa/KeyPairGenerator.java @@ -21,7 +21,7 @@ import net.i2p.util.RandomSource; * * @since 0.9.15 */ -public class KeyPairGenerator extends KeyPairGeneratorSpi { +public final class KeyPairGenerator extends KeyPairGeneratorSpi { private static final int DEFAULT_STRENGTH = 256; private EdDSAParameterSpec edParams; private SecureRandom random; diff --git a/core/java/src/net/i2p/crypto/eddsa/math/Curve.java b/core/java/src/net/i2p/crypto/eddsa/math/Curve.java index 2c6ade4ea17c3e67efce84a356fbd67435b77f44..fba8f29aa8e0c1fa81fba86ac5a17093d5cca30d 100644 --- a/core/java/src/net/i2p/crypto/eddsa/math/Curve.java +++ b/core/java/src/net/i2p/crypto/eddsa/math/Curve.java @@ -69,4 +69,29 @@ public class Curve implements Serializable { ge.precompute(true); return ge; } + + /** + * @since 0.9.25 + */ + @Override + public int hashCode() { + return f.hashCode() ^ + d.hashCode() ^ + I.hashCode(); + } + + /** + * @since 0.9.25 + */ + @Override + public boolean equals(Object o) { + if (o == this) + return true; + if (!(o instanceof Curve)) + return false; + Curve c = (Curve) o; + return f.equals(c.getField()) && + d.equals(c.getD()) && + I.equals(c.getI()); + } } diff --git a/core/java/src/net/i2p/crypto/eddsa/math/FieldElement.java b/core/java/src/net/i2p/crypto/eddsa/math/FieldElement.java index 4bea7c39e955f5e1f5c1c103be5420831775bbb3..b55d8705a5c8c3b720d2b71e4f47189914fb7656 100644 --- a/core/java/src/net/i2p/crypto/eddsa/math/FieldElement.java +++ b/core/java/src/net/i2p/crypto/eddsa/math/FieldElement.java @@ -3,6 +3,8 @@ package net.i2p.crypto.eddsa.math; import java.io.Serializable; /** + * + * Note: concrete subclasses must implement hashCode() and equals() * * @since 0.9.15 * @@ -60,4 +62,6 @@ public abstract class FieldElement implements Serializable { public abstract FieldElement invert(); public abstract FieldElement pow22523(); + + // Note: concrete subclasses must implement hashCode() and equals() } diff --git a/core/java/src/net/i2p/crypto/eddsa/math/GroupElement.java b/core/java/src/net/i2p/crypto/eddsa/math/GroupElement.java index ec81b5d408449da9ef91de337f422fb1f234ef3f..3a6c57b8249ffaa324b58a90cf612e024ace075b 100644 --- a/core/java/src/net/i2p/crypto/eddsa/math/GroupElement.java +++ b/core/java/src/net/i2p/crypto/eddsa/math/GroupElement.java @@ -716,6 +716,8 @@ public class GroupElement implements Serializable { @Override public boolean equals(Object obj) { + if (obj == this) + return true; if (!(obj instanceof GroupElement)) return false; GroupElement ge = (GroupElement) obj; diff --git a/core/java/src/net/i2p/crypto/eddsa/math/bigint/BigIntegerLittleEndianEncoding.java b/core/java/src/net/i2p/crypto/eddsa/math/bigint/BigIntegerLittleEndianEncoding.java index 06d3fd6a7c46cb61264f83e738b04f2cbe18b35d..e3088daca235dc9e7f684772d075fc0e0d85bbb1 100644 --- a/core/java/src/net/i2p/crypto/eddsa/math/bigint/BigIntegerLittleEndianEncoding.java +++ b/core/java/src/net/i2p/crypto/eddsa/math/bigint/BigIntegerLittleEndianEncoding.java @@ -15,7 +15,7 @@ public class BigIntegerLittleEndianEncoding extends Encoding implements Serializ private BigInteger mask; @Override - public void setField(Field f) { + public synchronized void setField(Field f) { super.setField(f); mask = BigInteger.ONE.shiftLeft(f.getb()-1).subtract(BigInteger.ONE); } diff --git a/core/java/src/net/i2p/crypto/eddsa/spec/EdDSAParameterSpec.java b/core/java/src/net/i2p/crypto/eddsa/spec/EdDSAParameterSpec.java index 4ace0fb62c699355c1d2712c3e0cabb39648c9b6..75b0140c6ac461a154f58113e5eeda8b29a75746 100644 --- a/core/java/src/net/i2p/crypto/eddsa/spec/EdDSAParameterSpec.java +++ b/core/java/src/net/i2p/crypto/eddsa/spec/EdDSAParameterSpec.java @@ -59,4 +59,29 @@ public class EdDSAParameterSpec implements AlgorithmParameterSpec, Serializable public GroupElement getB() { return B; } + + /** + * @since 0.9.25 + */ + @Override + public int hashCode() { + return hashAlgo.hashCode() ^ + curve.hashCode() ^ + B.hashCode(); + } + + /** + * @since 0.9.25 + */ + @Override + public boolean equals(Object o) { + if (o == this) + return true; + if (!(o instanceof EdDSAParameterSpec)) + return false; + EdDSAParameterSpec s = (EdDSAParameterSpec) o; + return hashAlgo.equals(s.getHashAlgorithm()) && + curve.equals(s.getCurve()) && + B.equals(s.getB()); + } } diff --git a/core/java/src/net/i2p/crypto/elgamal/ElGamalKey.java b/core/java/src/net/i2p/crypto/elgamal/ElGamalKey.java new file mode 100644 index 0000000000000000000000000000000000000000..0d2520709ffe63a4248ce5af000a25628060b2e5 --- /dev/null +++ b/core/java/src/net/i2p/crypto/elgamal/ElGamalKey.java @@ -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(); +} diff --git a/core/java/src/net/i2p/crypto/elgamal/ElGamalPrivateKey.java b/core/java/src/net/i2p/crypto/elgamal/ElGamalPrivateKey.java new file mode 100644 index 0000000000000000000000000000000000000000..b7093a7dae464736fa605bcd6ce924a6a2ded763 --- /dev/null +++ b/core/java/src/net/i2p/crypto/elgamal/ElGamalPrivateKey.java @@ -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(); +} diff --git a/core/java/src/net/i2p/crypto/elgamal/ElGamalPublicKey.java b/core/java/src/net/i2p/crypto/elgamal/ElGamalPublicKey.java new file mode 100644 index 0000000000000000000000000000000000000000..fc1b981459e9c743410f0843e082eaca2c9f97f6 --- /dev/null +++ b/core/java/src/net/i2p/crypto/elgamal/ElGamalPublicKey.java @@ -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(); +} diff --git a/core/java/src/net/i2p/crypto/elgamal/ElGamalSigEngine.java b/core/java/src/net/i2p/crypto/elgamal/ElGamalSigEngine.java new file mode 100644 index 0000000000000000000000000000000000000000..b44dc5fbe08f1c4a4000b03c505710d65f786217 --- /dev/null +++ b/core/java/src/net/i2p/crypto/elgamal/ElGamalSigEngine.java @@ -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"); + } +} diff --git a/core/java/src/net/i2p/crypto/elgamal/KeyFactory.java b/core/java/src/net/i2p/crypto/elgamal/KeyFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..7c7809fb4dd3d2ba63fd8eea95a18007649067cb --- /dev/null +++ b/core/java/src/net/i2p/crypto/elgamal/KeyFactory.java @@ -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"); + } +} diff --git a/core/java/src/net/i2p/crypto/elgamal/KeyPairGenerator.java b/core/java/src/net/i2p/crypto/elgamal/KeyPairGenerator.java new file mode 100644 index 0000000000000000000000000000000000000000..0819c8a6be9f4415d67727d7f7a5f23105255334 --- /dev/null +++ b/core/java/src/net/i2p/crypto/elgamal/KeyPairGenerator.java @@ -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); + } +} diff --git a/core/java/src/net/i2p/crypto/elgamal/impl/ElGamalPrivateKeyImpl.java b/core/java/src/net/i2p/crypto/elgamal/impl/ElGamalPrivateKeyImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..0076c58e00791a737d0cf728ee6768a5ea9671f8 --- /dev/null +++ b/core/java/src/net/i2p/crypto/elgamal/impl/ElGamalPrivateKeyImpl.java @@ -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()); + } +} diff --git a/core/java/src/net/i2p/crypto/elgamal/impl/ElGamalPublicKeyImpl.java b/core/java/src/net/i2p/crypto/elgamal/impl/ElGamalPublicKeyImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..8df61ec50ebd40cd7a921b5076db6f027579b8f9 --- /dev/null +++ b/core/java/src/net/i2p/crypto/elgamal/impl/ElGamalPublicKeyImpl.java @@ -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()); + } +} diff --git a/core/java/src/net/i2p/crypto/elgamal/impl/package.html b/core/java/src/net/i2p/crypto/elgamal/impl/package.html new file mode 100644 index 0000000000000000000000000000000000000000..b977d38db02980c5afcd9f84ac8d199eef5ef678 --- /dev/null +++ b/core/java/src/net/i2p/crypto/elgamal/impl/package.html @@ -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> diff --git a/core/java/src/net/i2p/crypto/elgamal/package.html b/core/java/src/net/i2p/crypto/elgamal/package.html new file mode 100644 index 0000000000000000000000000000000000000000..f3fc75522d5d528e6301bece132eeb9d4e623866 --- /dev/null +++ b/core/java/src/net/i2p/crypto/elgamal/package.html @@ -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> diff --git a/core/java/src/net/i2p/crypto/elgamal/spec/ElGamalGenParameterSpec.java b/core/java/src/net/i2p/crypto/elgamal/spec/ElGamalGenParameterSpec.java new file mode 100644 index 0000000000000000000000000000000000000000..8a404d09142e6123045cf0dc94810590581268cf --- /dev/null +++ b/core/java/src/net/i2p/crypto/elgamal/spec/ElGamalGenParameterSpec.java @@ -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; + } +} diff --git a/core/java/src/net/i2p/crypto/elgamal/spec/ElGamalKeySpec.java b/core/java/src/net/i2p/crypto/elgamal/spec/ElGamalKeySpec.java new file mode 100644 index 0000000000000000000000000000000000000000..4d20d32a4cf33c96e5ba1c5cd947cd8c35676c7f --- /dev/null +++ b/core/java/src/net/i2p/crypto/elgamal/spec/ElGamalKeySpec.java @@ -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; + } +} diff --git a/core/java/src/net/i2p/crypto/elgamal/spec/ElGamalParameterSpec.java b/core/java/src/net/i2p/crypto/elgamal/spec/ElGamalParameterSpec.java new file mode 100644 index 0000000000000000000000000000000000000000..50284884a67260f82002409989a28734b4dca7e2 --- /dev/null +++ b/core/java/src/net/i2p/crypto/elgamal/spec/ElGamalParameterSpec.java @@ -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); + } +} diff --git a/core/java/src/net/i2p/crypto/elgamal/spec/ElGamalPrivateKeySpec.java b/core/java/src/net/i2p/crypto/elgamal/spec/ElGamalPrivateKeySpec.java new file mode 100644 index 0000000000000000000000000000000000000000..014c57941fd47603f0fc0970473d7ce4906a4cea --- /dev/null +++ b/core/java/src/net/i2p/crypto/elgamal/spec/ElGamalPrivateKeySpec.java @@ -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; + } +} diff --git a/core/java/src/net/i2p/crypto/elgamal/spec/ElGamalPublicKeySpec.java b/core/java/src/net/i2p/crypto/elgamal/spec/ElGamalPublicKeySpec.java new file mode 100644 index 0000000000000000000000000000000000000000..4b68686e1bad92b3b887c7c4bb350c003b613a57 --- /dev/null +++ b/core/java/src/net/i2p/crypto/elgamal/spec/ElGamalPublicKeySpec.java @@ -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; + } +} diff --git a/core/java/src/net/i2p/crypto/elgamal/spec/package.html b/core/java/src/net/i2p/crypto/elgamal/spec/package.html new file mode 100644 index 0000000000000000000000000000000000000000..4c767ee4c8e382a3ce3fc244323bc86e391b793d --- /dev/null +++ b/core/java/src/net/i2p/crypto/elgamal/spec/package.html @@ -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> diff --git a/core/java/src/net/i2p/crypto/provider/I2PProvider.java b/core/java/src/net/i2p/crypto/provider/I2PProvider.java index d24b80d369c752d7939dc3d6e42c7c3f2cefd422..b6b26d396d779b8a04d9414b798934b64b8ef539 100644 --- a/core/java/src/net/i2p/crypto/provider/I2PProvider.java +++ b/core/java/src/net/i2p/crypto/provider/I2PProvider.java @@ -3,6 +3,7 @@ package net.i2p.crypto.provider; import java.security.AccessController; import java.security.PrivilegedAction; import java.security.Provider; +import java.security.Security; /** * @since 0.9.15 @@ -11,6 +12,7 @@ public final class I2PProvider extends Provider { public static final String PROVIDER_NAME = "I2P"; private static final String INFO = "I2P Security Provider v0.1, implementing" + "several algorithms used by I2P."; + private static boolean _installed; /** * Construct a new provider. This should only be required when @@ -31,15 +33,89 @@ public final class I2PProvider extends Provider { private void setup() { // TODO: Implement SPIs for existing code + // However - + // quote + // http://docs.oracle.com/javase/7/docs/technotes/guides/security/crypto/HowToImplAProvider.html + // + // If your provider is supplying encryption algorithms through the + // Cipher, KeyAgreement, KeyGenerator, Mac, or SecretKeyFactory classes, + // you will need to sign your JAR file so that the JCA can authenticate the code at runtime. + // If you are NOT providing an implementation of this type you can skip this step. + // //put("Cipher.AES", "net.i2p.crypto.provider.CipherSpi$aesCBC"); //put("Cipher.ElGamal", "net.i2p.crypto.provider.CipherSpi$elGamal"); //put("Mac.HmacMD5-I2P", "net.i2p.crypto.provider.MacSpi"); + put("MessageDigest.SHA-1", "net.i2p.crypto.SHA1"); //put("Signature.SHA1withDSA", "net.i2p.crypto.provider.SignatureSpi"); // EdDSA + // Key OID: 1.3.101.100; Sig OID: 1.3.101.101 put("KeyFactory.EdDSA", "net.i2p.crypto.eddsa.KeyFactory"); put("KeyPairGenerator.EdDSA", "net.i2p.crypto.eddsa.KeyPairGenerator"); put("Signature.SHA512withEdDSA", "net.i2p.crypto.eddsa.EdDSAEngine"); + // Didn't find much documentation on these at all, + // see http://docs.oracle.com/javase/7/docs/technotes/guides/security/crypto/HowToImplAProvider.html + // section "Mapping from OID to name" + // without these, Certificate.verify() fails + put("Alg.Alias.KeyFactory.1.3.101.100", "EdDSA"); + put("Alg.Alias.KeyFactory.OID.1.3.101.100", "EdDSA"); + // Without these, keytool fails with: + // keytool error: java.security.NoSuchAlgorithmException: unrecognized algorithm name: SHA512withEdDSA + put("Alg.Alias.KeyPairGenerator.1.3.101.100", "EdDSA"); + put("Alg.Alias.KeyPairGenerator.OID.1.3.101.100", "EdDSA"); + // with this setting, keytool keygen doesn't work + // java.security.cert.CertificateException: Signature algorithm mismatch + // it must match the key setting (1.3.101.100) to work + // but this works fine with programmatic cert generation + put("Alg.Alias.Signature.1.3.101.101", "SHA512withEdDSA"); + put("Alg.Alias.Signature.OID.1.3.101.101", "SHA512withEdDSA"); + // TODO Ed25519ph + // OID: 1.3.101.101 + + // ElGamal + // OID: 1.3.14.7.2.1.1 + put("KeyFactory.DH", "net.i2p.crypto.elgamal.KeyFactory"); + put("KeyFactory.DiffieHellman", "net.i2p.crypto.elgamal.KeyFactory"); + put("KeyFactory.ElGamal", "net.i2p.crypto.elgamal.KeyFactory"); + put("KeyPairGenerator.DH", "net.i2p.crypto.elgamal.KeyPairGenerator"); + put("KeyPairGenerator.DiffieHellman", "net.i2p.crypto.elgamal.KeyPairGenerator"); + put("KeyPairGenerator.ElGamal", "net.i2p.crypto.elgamal.KeyPairGenerator"); + put("Signature.SHA256withElGamal", "net.i2p.crypto.elgamal.ElGamalSigEngine"); + put("Alg.Alias.KeyFactory.1.3.14.7.2.1.1", "ElGamal"); + put("Alg.Alias.KeyFactory.OID.1.3.14.7.2.1.1", "ElGamal"); + put("Alg.Alias.KeyPairGenerator.1.3.14.7.2.1.1", "ElGamal"); + put("Alg.Alias.KeyPairGenerator.OID.1.3.14.7.2.1.1", "ElGamal"); + put("Alg.Alias.Signature.1.3.14.7.2.1.1", "SHA256withElGamal"); + put("Alg.Alias.Signature.OID.1.3.14.7.2.1.1", "SHA256withElGamal"); + } + + /** + * Install the I2PProvider. + * Harmless to call multiple times. + * @since 0.9.25 + */ + public static void addProvider() { + synchronized(I2PProvider.class) { + if (!_installed) { + try { + Provider us = new I2PProvider(); + // put ours ahead of BC, if installed, because our ElGamal + // implementation may not be fully compatible with BC + Provider[] provs = Security.getProviders(); + for (int i = 0; i < provs.length; i++) { + if (provs[i].getName().equals("BC")) { + Security.insertProviderAt(us, i); + _installed = true; + return; + } + } + Security.addProvider(us); + _installed = true; + } catch (SecurityException se) { + System.out.println("WARN: Could not install I2P provider: " + se); + } + } + } } } diff --git a/core/java/src/net/i2p/data/Base64.java b/core/java/src/net/i2p/data/Base64.java index 3babe10ee11416ecb972ae13923645b729700aa1..210bf9efc2b7e6d6178699763bafa22712cb38a1 100644 --- a/core/java/src/net/i2p/data/Base64.java +++ b/core/java/src/net/i2p/data/Base64.java @@ -104,6 +104,17 @@ public class Base64 { return safeDecode(s, false); } + /** + * Decodes data from Base64 notation using the I2P alphabet. + * + * @param useStandardAlphabet Warning, must be false for I2P compatibility + * @return the decoded data, null on error + * @since 0.9.25 + */ + public static byte[] decode(String s, boolean useStandardAlphabet) { + return safeDecode(s, useStandardAlphabet); + } + /** Maximum line length (76) of Base64 output. */ private final static int MAX_LINE_LENGTH = 76; diff --git a/core/java/src/net/i2p/data/DataHelper.java b/core/java/src/net/i2p/data/DataHelper.java index a9689add73cfe4e78f0097175433b0152bc16b6c..dcd1ce71cf8ac648d5e12fa4af1f3cfbf2001da1 100644 --- a/core/java/src/net/i2p/data/DataHelper.java +++ b/core/java/src/net/i2p/data/DataHelper.java @@ -1324,8 +1324,9 @@ public class DataHelper { * * @param hash null OK * @return null on EOF - * @deprecated use MessageDigest version + * @deprecated use MessageDigest version to be removed in 0.9.27 */ + @Deprecated public static String readLine(InputStream in, Sha256Standalone hash) throws IOException { StringBuilder buf = new StringBuilder(128); boolean ok = readLine(in, buf, hash); @@ -1380,7 +1381,7 @@ public class DataHelper { * * @return true if the line was read, false if eof was reached on an empty line * (returns true for non-empty last line without a newline) - * @deprecated use StringBuilder / MessageDigest version + * @deprecated use StringBuilder / MessageDigest version, to be removed in 0.9.27 */ @Deprecated public static boolean readLine(InputStream in, StringBuffer buf, Sha256Standalone hash) throws IOException { @@ -1420,8 +1421,9 @@ public class DataHelper { * @param hash null OK * @return true if the line was read, false if eof was reached on an empty line * (returns true for non-empty last line without a newline) - * @deprecated use MessageDigest version + * @deprecated use MessageDigest version, to be removed in 0.9.27 */ + @Deprecated public static boolean readLine(InputStream in, StringBuilder buf, Sha256Standalone hash) throws IOException { int c = -1; int i = 0; @@ -1463,8 +1465,9 @@ public class DataHelper { /** * update the hash along the way - * @deprecated use MessageDigest version + * @deprecated use MessageDigest version, to be removed in 0.9.27 */ + @Deprecated public static void write(OutputStream out, byte data[], Sha256Standalone hash) throws IOException { hash.update(data); out.write(data); diff --git a/core/java/src/net/i2p/util/RandomSource.java b/core/java/src/net/i2p/util/RandomSource.java index 9c522ddd0b2d5a1431922cf08c1b2baf240042ec..b92295e633852983f489b8240a2fdabb15e1f75d 100644 --- a/core/java/src/net/i2p/util/RandomSource.java +++ b/core/java/src/net/i2p/util/RandomSource.java @@ -201,7 +201,8 @@ public class RandomSource extends SecureRandom implements EntropyHarvester { } catch (InterruptedException ie) {} // why urandom? because /dev/random blocks - ok = seedFromFile(new File("/dev/urandom"), buf) || ok; + if (!SystemVersion.isWindows()) + ok = seedFromFile(new File("/dev/urandom"), buf) || ok; // we merge (XOR) in the data from /dev/urandom with our own seedfile File localFile = new File(_context.getConfigDir(), SEEDFILE); ok = seedFromFile(localFile, buf) || ok; diff --git a/installer/resources/checklist.md b/installer/resources/checklist.md index 3a36f232af4e848846eb886eba62cf1d645419dd..e3b4e3b536e6f6b9158d9c1c679462de937222cb 100644 --- a/installer/resources/checklist.md +++ b/installer/resources/checklist.md @@ -51,6 +51,7 @@ release.signer.su3=xxx@mail.i2p 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.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 @@ -169,7 +170,7 @@ - In the i2p.newsxml branch, edit magnet links, release dates and release 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: - All in-network update hosts diff --git a/installer/resources/eepsite/contexts/base-context.xml b/installer/resources/eepsite/contexts/base-context.xml index ee995738b886e0eb51973b651f9f464c2444a5d0..99f788d3fe84c40d52b740c266a6c80272c1c6aa 100644 --- a/installer/resources/eepsite/contexts/base-context.xml +++ b/installer/resources/eepsite/contexts/base-context.xml @@ -37,5 +37,46 @@ to serve static html files and images. <Arg>org.eclipse.jetty.servlet.DefaultServlet</Arg> <Arg>/</Arg> </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> diff --git a/installer/resources/eepsite/contexts/cgi-context.xml b/installer/resources/eepsite/contexts/cgi-context.xml index 3ae8f390d12dc3758f70577a4185b2fd37475fd6..01df01123406a092fd08af2a55423b707cdeb182 100644 --- a/installer/resources/eepsite/contexts/cgi-context.xml +++ b/installer/resources/eepsite/contexts/cgi-context.xml @@ -33,4 +33,35 @@ Configure a custom context for the eepsite. <Arg>org.eclipse.jetty.servlets.CGI</Arg> <Arg>/</Arg> </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> diff --git a/installer/resources/hosts.txt b/installer/resources/hosts.txt index 34987b08f82e4edbfc264d7e4053d565bf1ad4d3..ea67a2cdc0bfd54af8709f10e7e0b0c88649f232 100644 --- a/installer/resources/hosts.txt +++ b/installer/resources/hosts.txt @@ -364,3 +364,4 @@ i2pnews.i2p=XHS99uhrvijk3KxU438LjNf-SMXXiNXsbV8uwHFXdqsDsHPZRdc6LH-hEMGWDR5g2b65 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== 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= diff --git a/installer/resources/themes/console/images/thetinhat.png b/installer/resources/themes/console/images/thetinhat.png new file mode 100644 index 0000000000000000000000000000000000000000..0c98f424b3f5f842b048ee6c9e6881382587f434 Binary files /dev/null and b/installer/resources/themes/console/images/thetinhat.png differ diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java index 410890547a2e68f2f140a0285bfbf3cc8f22107a..03183a9949342d78a3954575752f5f3140d9149e 100644 --- a/router/java/src/net/i2p/router/Router.java +++ b/router/java/src/net/i2p/router/Router.java @@ -100,7 +100,9 @@ public class Router implements RouterClock.ClockShiftListener { public final static long CLOCK_FUDGE_FACTOR = 1*60*1000; /** 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 */ public static final int COALESCE_TIME = 50*1000; @@ -347,6 +349,14 @@ public class Router implements RouterClock.ClockShiftListener { _config.put("router.previousVersion", RouterVersion.VERSION); 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); // ********* Start no threads before here ********* // } @@ -536,6 +546,14 @@ public class Router implements RouterClock.ClockShiftListener { if (_started <= 0) return 1000; // racing on startup 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 diff --git a/router/java/src/net/i2p/router/RouterContext.java b/router/java/src/net/i2p/router/RouterContext.java index 2698faa0cc8fabd8062b4ea31bbefc5c2435e077..e29d2bb43259c5188734adf2eaeb03ff3d438815 100644 --- a/router/java/src/net/i2p/router/RouterContext.java +++ b/router/java/src/net/i2p/router/RouterContext.java @@ -120,7 +120,8 @@ public class RouterContext extends I2PAppContext { // or about 2 seconds per buffer - so about 200x faster // to fill than to drain - so we don't need too many 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); } return envProps; diff --git a/router/java/src/net/i2p/router/StatisticsManager.java b/router/java/src/net/i2p/router/StatisticsManager.java index 21e0813cc36055166285e45f80a5811a2c46c571..9379df3504472df9a640d5837218394b46a34bde 100644 --- a/router/java/src/net/i2p/router/StatisticsManager.java +++ b/router/java/src/net/i2p/router/StatisticsManager.java @@ -32,6 +32,7 @@ import net.i2p.util.Log; public class StatisticsManager { private final Log _log; private final RouterContext _context; + private final String _networkID; public final static String PROP_PUBLISH_RANKINGS = "router.publishPeerRankings"; 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)); _pct = new DecimalFormat("#0.00%", new DecimalFormatSymbols(Locale.UK)); _log = context.logManager().getLog(StatisticsManager.class); + _networkID = Integer.toString(context.router().getNetworkID()); } /** @@ -72,7 +74,7 @@ public class StatisticsManager { // scheduled for removal, never used if (CoreVersion.VERSION.equals("0.9.23")) 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()); // No longer expose, to make build tracking more expensive diff --git a/router/java/src/net/i2p/router/crypto/FamilyKeyCrypto.java b/router/java/src/net/i2p/router/crypto/FamilyKeyCrypto.java index f69e62752d791f0b7dfc7b023579013a0a85ab78..39b81458a243744683d001dcabc7957ffdb684a0 100644 --- a/router/java/src/net/i2p/router/crypto/FamilyKeyCrypto.java +++ b/router/java/src/net/i2p/router/crypto/FamilyKeyCrypto.java @@ -47,12 +47,13 @@ public class FamilyKeyCrypto { private final SigningPrivateKey _privkey; 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"; - private static final String PROP_KEY_PASSWORD = "netdb.family.keyPassword"; - private static final String CERT_SUFFIX = ".crt"; - private static final String KEYSTORE_PREFIX = "family-"; - private static final String KEYSTORE_SUFFIX = ".ks"; + public static final String PROP_KEY_PASSWORD = "netdb.family.keyPassword"; + public static final String CERT_SUFFIX = ".crt"; + public static final String KEYSTORE_PREFIX = "family-"; + 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 // 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 @@ -289,7 +290,7 @@ public class FamilyKeyCrypto { // make a random 48 character password (30 * 8 / 5) String keyPassword = KeyStoreUtil.randomString(); // 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", DEFAULT_KEY_VALID_DAYS, DEFAULT_KEY_ALGORITHM, diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java index 0df3b32902ded2216089a7aa2382dad0834439fa..5e66134f152b8e8783c3b1d2f483a79329d25eb0 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java @@ -479,7 +479,7 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad // drop the peer in these cases // yikes don't do this - stack overflow // getFloodfillPeers().size() == 0 || // 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 || _context.router().getUptime() < DONT_FAIL_PERIOD || _context.commSystem().countActivePeers() <= MIN_ACTIVE_PEERS)) { diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java index ea0aa69084e49c44561d17dd09c84ecd8e9048ef..f1b13aefe8c5c7457f2619c3f370fa76e3207bff 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java @@ -70,6 +70,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { private final ReseedChecker _reseedChecker; private volatile long _lastRIPublishTime; private NegativeLookupCache _negativeCache; + protected final int _networkID; /** * Map of Hash to RepublishLeaseSetJob for leases we'realready managing. @@ -156,6 +157,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { public KademliaNetworkDatabaseFacade(RouterContext context) { _context = context; _log = _context.logManager().getLog(getClass()); + _networkID = context.router().getNetworkID(); _peerSelector = createPeerSelector(); _publishingLeaseSets = new HashMap<Hash, RepublishLeaseSetJob>(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); return "Invalid routerInfo signature"; } - if (routerInfo.getNetworkId() != Router.NETWORK_ID){ + if (routerInfo.getNetworkId() != _networkID){ _context.banlist().banlistRouter(key, "Not in our network"); if (_log.shouldLog(Log.WARN)) _log.warn("Bad network: " + routerInfo); diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java b/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java index 80aae33e2d294085f4f5a49a2e2dd01a854e0391..b1bbcf06540b35c48d56f417cc01e738af085548 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java @@ -54,6 +54,7 @@ public class PersistentDataStore extends TransientDataStore { private final ReadJob _readJob; private volatile boolean _initialized; private final boolean _flat; + private final int _networkID; private final static int READ_DELAY = 2*60*1000; 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 { super(ctx); + _networkID = ctx.router().getNetworkID(); _flat = ctx.getBooleanProperty(PROP_FLAT); _dbDir = getDbDir(dbDir); _facade = facade; @@ -505,7 +507,7 @@ public class PersistentDataStore extends TransientDataStore { fis = new BufferedInputStream(fis); RouterInfo ri = new RouterInfo(); ri.readBytes(fis, true); // true = verify sig on read - if (ri.getNetworkId() != Router.NETWORK_ID) { + if (ri.getNetworkId() != _networkID) { corrupt = true; if (_log.shouldLog(Log.ERROR)) _log.error("The router " diff --git a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java index 075eb04b982278a2719c2621d22a943fec1d609a..15699943d81444434c261e2764bc5f44177adcb1 100644 --- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java +++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java @@ -42,6 +42,7 @@ class EstablishmentManager { private final Log _log; private final UDPTransport _transport; private final PacketBuilder _builder; + private final int _networkID; /** map of RemoteHostId to InboundEstablishState */ private final ConcurrentHashMap<RemoteHostId, InboundEstablishState> _inboundStates; @@ -140,6 +141,7 @@ class EstablishmentManager { public EstablishmentManager(RouterContext ctx, UDPTransport transport) { _context = ctx; _log = ctx.logManager().getLog(EstablishmentManager.class); + _networkID = ctx.router().getNetworkID(); _transport = transport; _builder = new PacketBuilder(ctx, transport); _inboundStates = new ConcurrentHashMap<RemoteHostId, InboundEstablishState>(); @@ -249,7 +251,7 @@ class EstablishmentManager { } RouterIdentity toIdentity = toRouterInfo.getIdentity(); Hash toHash = toIdentity.calculateHash(); - if (toRouterInfo.getNetworkId() != Router.NETWORK_ID) { + if (toRouterInfo.getNetworkId() != _networkID) { _context.banlist().banlistRouter(toHash); _transport.markUnreachable(toHash); _transport.failed(msg, "Remote peer is on the wrong network, cannot establish"); @@ -762,7 +764,7 @@ class EstablishmentManager { if (_log.shouldLog(Log.INFO)) _log.info("Completing to the peer after IB confirm: " + peer); 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 // so it needs to be caught in InNetMessagePool. dsm.setMessageExpiration(_context.clock().now() + DATA_MESSAGE_TIMEOUT); diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index f0fa96e2b5eeff5cc301019964fbf18b98aff463..0202ee39fa8f72350d9e362d623aa92f471b0a51 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -87,6 +87,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority private int _mtu; private int _mtu_ipv6; private boolean _mismatchLogged; + private final int _networkID; /** * 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) { super(ctx); + _networkID = ctx.router().getNetworkID(); _dhFactory = dh; _log = ctx.logManager().getLog(UDPTransport.class); _peersByIdent = new ConcurrentHashMap<Hash, PeerState>(128); @@ -1289,7 +1291,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority if (entry == null) return; 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 /* diff --git a/router/java/src/net/i2p/router/util/ArraySet.java b/router/java/src/net/i2p/router/util/ArraySet.java new file mode 100644 index 0000000000000000000000000000000000000000..a471e0e5f339a55e99da3c4a0c0b1fa4666606fc --- /dev/null +++ b/router/java/src/net/i2p/router/util/ArraySet.java @@ -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)); + } +****/ +}