forked from I2P_Developers/i2p.i2p
propagate from branch 'i2p.i2p.zzz.test2' (head 70ae5494bd7255a03f80838a2f3d8e7c0ce86634)
to branch 'i2p.i2p' (head 05a201cc5c1bd841f32e9268b3019b3a3447f4f3)
This commit is contained in:
@ -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
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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 + '.');
|
||||
}
|
||||
}
|
@ -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, "");
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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 +
|
||||
|
@ -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
|
||||
|
@ -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()) {
|
||||
|
88
apps/routerconsole/jsp/configfamily.jsp
Normal file
88
apps/routerconsole/jsp/configfamily.jsp
Normal file
@ -0,0 +1,88 @@
|
||||
<%@page contentType="text/html"%>
|
||||
<%@page pageEncoding="UTF-8"%>
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
|
||||
<html><head>
|
||||
<%@include file="css.jsi" %>
|
||||
<%=intl.title("config router family")%>
|
||||
<script src="/js/ajax.js" type="text/javascript"></script>
|
||||
<%@include file="summaryajax.jsi" %>
|
||||
</head><body onload="initAjax()">
|
||||
|
||||
<%@include file="summary.jsi" %>
|
||||
|
||||
<jsp:useBean class="net.i2p.router.web.ConfigFamilyHelper" id="familyHelper" scope="request" />
|
||||
<jsp:setProperty name="familyHelper" property="contextId" value="<%=(String)session.getAttribute(\"i2p.contextId\")%>" />
|
||||
<h1><%=intl._t("I2P Router Family Configuration")%></h1>
|
||||
<div class="main" id="main">
|
||||
<%@include file="confignav.jsi" %>
|
||||
|
||||
<jsp:useBean class="net.i2p.router.web.ConfigFamilyHandler" id="formhandler" scope="request" />
|
||||
<%@include file="formhandler.jsi" %>
|
||||
|
||||
<p><%=intl._t("Routers in the same family share a family key.")%>
|
||||
<%=intl._t("To start a new family, enter a family name.")%>
|
||||
<%=intl._t("To join an existing family, import the private key you exported from a router in the family.")%>
|
||||
</p>
|
||||
|
||||
<%
|
||||
String family = familyHelper.getFamily();
|
||||
if (family.length() <= 0) {
|
||||
// no family yet
|
||||
%>
|
||||
<div class="configure"><form action="" method="POST">
|
||||
<input type="hidden" name="nonce" value="<%=pageNonce%>" >
|
||||
<h3><%=intl._t("Create Router Family")%></h3>
|
||||
<p><%=intl._t("Family Name")%> :
|
||||
<input name="family" type="text" size="30" value="" />
|
||||
</p>
|
||||
<div class="formaction">
|
||||
<input type="submit" name="action" class="accept" value="<%=intl._t("Create Router Family")%>" />
|
||||
</div></form></div>
|
||||
|
||||
<div class="configure">
|
||||
<form action="" method="POST" enctype="multipart/form-data" accept-charset="UTF-8">
|
||||
<input type="hidden" name="nonce" value="<%=pageNonce%>" >
|
||||
<h3><%=intl._t("Join Router Family")%></h3>
|
||||
<p><%=intl._t("Import the secret family key that you exported from an existing router in the family.")%>
|
||||
<p><%=intl._t("Select secret key file")%> :
|
||||
<input name="file" type="file" value="" />
|
||||
</p>
|
||||
<div class="formaction">
|
||||
<input type="submit" name="action" class="download" value="<%=intl._t("Join Router Family")%>" />
|
||||
</div></form></div>
|
||||
<%
|
||||
} else {
|
||||
// family is configured
|
||||
String keypw = familyHelper.getKeyPW();
|
||||
if (keypw.length() > 0) {
|
||||
// family is active
|
||||
%>
|
||||
<div class="configure">
|
||||
<form action="/exportfamily" method="GET">
|
||||
<h3><%=intl._t("Export Family Key")%></h3>
|
||||
<p><%=intl._t("Export the secret family key to be imported into other routers you control.")%>
|
||||
</p>
|
||||
<div class="formaction">
|
||||
<input type="submit" name="action" class="go" value="<%=intl._t("Export Family Key")%>" />
|
||||
</div></form></div>
|
||||
<%
|
||||
} else {
|
||||
// family is not active
|
||||
%>
|
||||
<p><b><%=intl._t("Restart required to activate family {0}.", '"' + family + '"')%>
|
||||
<%=intl._t("After restarting, you may export the family key.")%></b></p>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
<div class="configure"><form action="" method="POST">
|
||||
<input type="hidden" name="nonce" value="<%=pageNonce%>" >
|
||||
<h3><%=intl._t("Leave Router Family")%></h3>
|
||||
<p><%=intl._t("No longer be a member of the family {0}.", '"' + family + '"')%>
|
||||
<div class="formaction">
|
||||
<input type="submit" name="action" class="delete" value="<%=intl._t("Leave Router Family")%>" />
|
||||
</div></form></div>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
</div></body></html>
|
@ -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>
|
||||
|
35
apps/routerconsole/jsp/exportfamily.jsp
Normal file
35
apps/routerconsole/jsp/exportfamily.jsp
Normal file
@ -0,0 +1,35 @@
|
||||
<%
|
||||
try {
|
||||
net.i2p.I2PAppContext ctx = net.i2p.I2PAppContext.getGlobalContext();
|
||||
String family = ctx.getProperty("netdb.family.name");
|
||||
String keypw = ctx.getProperty("netdb.family.keyPassword");
|
||||
String kspw = ctx.getProperty("netdb.family.keystorePassword", "changeit");
|
||||
if (family == null || keypw == null) {
|
||||
response.sendError(404);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
response.setDateHeader("Expires", 0);
|
||||
response.addHeader("Cache-Control", "no-store, max-age=0, no-cache, must-revalidate");
|
||||
response.addHeader("Pragma", "no-cache");
|
||||
String name = "family-" + family + "-secret.crt";
|
||||
response.setContentType("application/x-x509-ca-cert; name=\"" + name + '"');
|
||||
response.addHeader("Content-Disposition", "attachment; filename=\"" + name + '"');
|
||||
java.io.File ks = new java.io.File(ctx.getConfigDir(), "keystore");
|
||||
ks = new java.io.File(ks, "family-" + family + ".ks");
|
||||
java.io.OutputStream cout = response.getOutputStream();
|
||||
net.i2p.crypto.KeyStoreUtil.exportPrivateKey(ks, kspw, family, keypw, cout);
|
||||
} catch (java.security.GeneralSecurityException gse) {
|
||||
throw new java.io.IOException("key error", gse);
|
||||
}
|
||||
} catch (java.io.IOException ioe) {
|
||||
// prevent 'Committed' IllegalStateException from Jetty
|
||||
if (!response.isCommitted()) {
|
||||
response.sendError(403, ioe.toString());
|
||||
} else {
|
||||
// Jetty doesn't log this
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
// don't worry about a newline after this
|
||||
%>
|
@ -15,6 +15,18 @@
|
||||
|
||||
<!-- 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>
|
||||
<servlet-name>net.i2p.router.web.jsp.viewtheme_jsp</servlet-name>
|
||||
|
@ -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"/>
|
||||
|
@ -51,7 +51,9 @@ package gnu.crypto.hash;
|
||||
* See SHA256Generator for more information.
|
||||
*
|
||||
* @version $Revision: 1.1 $
|
||||
* @deprecated to be removed in 0.9.27
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class BaseHashStandalone implements IMessageDigestStandalone {
|
||||
|
||||
// Constants and variables
|
||||
|
@ -54,7 +54,9 @@ package gnu.crypto.hash;
|
||||
* See SHA256Generator for more information.
|
||||
*
|
||||
* @version $Revision: 1.1 $
|
||||
* @deprecated to be removed in 0.9.27
|
||||
*/
|
||||
@Deprecated
|
||||
public interface IMessageDigestStandalone extends Cloneable {
|
||||
|
||||
// Constants
|
||||
|
@ -64,7 +64,9 @@ package gnu.crypto.hash;
|
||||
* See SHA256Generator for more information.
|
||||
*
|
||||
* @version $Revision: 1.2 $
|
||||
* @deprecated to be removed in 0.9.27
|
||||
*/
|
||||
@Deprecated
|
||||
public class Sha256Standalone extends BaseHashStandalone {
|
||||
// Constants and variables
|
||||
// -------------------------------------------------------------------------
|
||||
|
@ -9,12 +9,21 @@ import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CRLException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.cert.X509CRL;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.naming.InvalidNameException;
|
||||
@ -24,6 +33,7 @@ import javax.security.auth.x500.X500Principal;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SecureFileOutputStream;
|
||||
import net.i2p.util.SystemVersion;
|
||||
@ -33,7 +43,7 @@ import net.i2p.util.SystemVersion;
|
||||
*
|
||||
* @since 0.9.9
|
||||
*/
|
||||
public class CertUtil {
|
||||
public final class CertUtil {
|
||||
|
||||
private static final int LINE_LENGTH = 64;
|
||||
|
||||
@ -93,16 +103,7 @@ public class CertUtil {
|
||||
throws IOException, CertificateEncodingException {
|
||||
// Get the encoded form which is suitable for exporting
|
||||
byte[] buf = cert.getEncoded();
|
||||
PrintWriter wr = new PrintWriter(new OutputStreamWriter(out, "UTF-8"));
|
||||
wr.println("-----BEGIN CERTIFICATE-----");
|
||||
String b64 = Base64.encode(buf, true); // true = use standard alphabet
|
||||
for (int i = 0; i < b64.length(); i += LINE_LENGTH) {
|
||||
wr.println(b64.substring(i, Math.min(i + LINE_LENGTH, b64.length())));
|
||||
}
|
||||
wr.println("-----END CERTIFICATE-----");
|
||||
wr.flush();
|
||||
if (wr.checkError())
|
||||
throw new IOException("Failed write to " + out);
|
||||
writePEM(buf, "CERTIFICATE", out);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -121,13 +122,27 @@ public class CertUtil {
|
||||
byte[] buf = pk.getEncoded();
|
||||
if (buf == null)
|
||||
throw new InvalidKeyException("encoding unsupported for this key");
|
||||
writePEM(buf, "PRIVATE KEY", out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modified from:
|
||||
* http://www.exampledepot.com/egs/java.security.cert/ExportCert.html
|
||||
*
|
||||
* Writes data in base64 format.
|
||||
* Does NOT close the stream. Throws on all errors.
|
||||
*
|
||||
* @since 0.9.25 consolidated from other methods
|
||||
*/
|
||||
private static void writePEM(byte[] buf, String what, OutputStream out)
|
||||
throws IOException {
|
||||
PrintWriter wr = new PrintWriter(new OutputStreamWriter(out, "UTF-8"));
|
||||
wr.println("-----BEGIN PRIVATE KEY-----");
|
||||
wr.println("-----BEGIN " + what + "-----");
|
||||
String b64 = Base64.encode(buf, true); // true = use standard alphabet
|
||||
for (int i = 0; i < b64.length(); i += LINE_LENGTH) {
|
||||
wr.println(b64.substring(i, Math.min(i + LINE_LENGTH, b64.length())));
|
||||
}
|
||||
wr.println("-----END PRIVATE KEY-----");
|
||||
wr.println("-----END " + what + "-----");
|
||||
wr.flush();
|
||||
if (wr.checkError())
|
||||
throw new IOException("Failed write to " + out);
|
||||
@ -235,4 +250,145 @@ public class CertUtil {
|
||||
try { if (fis != null) fis.close(); } catch (IOException foo) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single Private Key from an input stream.
|
||||
* Does NOT close the stream.
|
||||
*
|
||||
* @return non-null, non-empty, throws on all errors including certificate invalid
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public static PrivateKey loadPrivateKey(InputStream in) throws IOException, GeneralSecurityException {
|
||||
try {
|
||||
String line;
|
||||
while ((line = DataHelper.readLine(in)) != null) {
|
||||
if (line.startsWith("---") && line.contains("BEGIN") && line.contains("PRIVATE"))
|
||||
break;
|
||||
}
|
||||
if (line == null)
|
||||
throw new IOException("no private key found");
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
while ((line = DataHelper.readLine(in)) != null) {
|
||||
if (line.startsWith("---"))
|
||||
break;
|
||||
buf.append(line.trim());
|
||||
}
|
||||
if (buf.length() <= 0)
|
||||
throw new IOException("no private key found");
|
||||
byte[] data = Base64.decode(buf.toString(), true);
|
||||
if (data == null)
|
||||
throw new CertificateEncodingException("bad base64 cert");
|
||||
PrivateKey rv = null;
|
||||
// try all the types
|
||||
for (SigAlgo algo : EnumSet.allOf(SigAlgo.class)) {
|
||||
try {
|
||||
KeySpec ks = new PKCS8EncodedKeySpec(data);
|
||||
String alg = algo.getName();
|
||||
KeyFactory kf = KeyFactory.getInstance(alg);
|
||||
rv = kf.generatePrivate(ks);
|
||||
break;
|
||||
} catch (GeneralSecurityException gse) {
|
||||
//gse.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (rv == null)
|
||||
throw new InvalidKeyException("unsupported key type");
|
||||
return rv;
|
||||
} catch (IllegalArgumentException iae) {
|
||||
// java 1.8.0_40-b10, openSUSE
|
||||
// Exception in thread "main" java.lang.IllegalArgumentException: Input byte array has wrong 4-byte ending unit
|
||||
// at java.util.Base64$Decoder.decode0(Base64.java:704)
|
||||
throw new GeneralSecurityException("key error", iae);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get one or more certificates from an input stream.
|
||||
* Throws if any certificate is invalid (e.g. expired).
|
||||
* Does NOT close the stream.
|
||||
*
|
||||
* @return non-null, non-empty, throws on all errors including certificate invalid
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public static List<X509Certificate> loadCerts(InputStream in) throws IOException, GeneralSecurityException {
|
||||
try {
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
Collection<? extends Certificate> certs = cf.generateCertificates(in);
|
||||
List<X509Certificate> rv = new ArrayList<X509Certificate>(certs.size());
|
||||
for (Certificate cert : certs) {
|
||||
if (!(cert instanceof X509Certificate))
|
||||
throw new GeneralSecurityException("not a X.509 cert");
|
||||
X509Certificate xcert = (X509Certificate) cert;
|
||||
xcert.checkValidity();
|
||||
rv.add(xcert);
|
||||
}
|
||||
if (rv.isEmpty())
|
||||
throw new IOException("no certs found");
|
||||
return rv;
|
||||
} catch (IllegalArgumentException iae) {
|
||||
// java 1.8.0_40-b10, openSUSE
|
||||
// Exception in thread "main" java.lang.IllegalArgumentException: Input byte array has wrong 4-byte ending unit
|
||||
// at java.util.Base64$Decoder.decode0(Base64.java:704)
|
||||
throw new GeneralSecurityException("cert error", iae);
|
||||
} finally {
|
||||
try { in.close(); } catch (IOException foo) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a CRL to a file in base64 format.
|
||||
*
|
||||
* @return success
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public static boolean saveCRL(X509CRL crl, File file) {
|
||||
OutputStream os = null;
|
||||
try {
|
||||
os = new SecureFileOutputStream(file);
|
||||
exportCRL(crl, os);
|
||||
return true;
|
||||
} catch (CRLException ce) {
|
||||
error("Error writing X509 CRL " + file.getAbsolutePath(), ce);
|
||||
return false;
|
||||
} catch (IOException ioe) {
|
||||
error("Error writing X509 CRL " + file.getAbsolutePath(), ioe);
|
||||
return false;
|
||||
} finally {
|
||||
try { if (os != null) os.close(); } catch (IOException foo) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a CRL in base64 format.
|
||||
* Does NOT close the stream. Throws on all errors.
|
||||
*
|
||||
* @throws CRLException if the crl does not support encoding
|
||||
* @since 0.9.25
|
||||
*/
|
||||
private static void exportCRL(X509CRL crl, OutputStream out)
|
||||
throws IOException, CRLException {
|
||||
byte[] buf = crl.getEncoded();
|
||||
writePEM(buf, "X509 CRL", out);
|
||||
}
|
||||
|
||||
/****
|
||||
public static final void main(String[] args) {
|
||||
if (args.length < 2) {
|
||||
System.out.println("Usage: [loadcert | loadcrl | loadprivatekey] file");
|
||||
System.exit(1);
|
||||
}
|
||||
try {
|
||||
File f = new File(args[1]);
|
||||
if (args[0].equals("loadcert")) {
|
||||
loadCert(f);
|
||||
} else if (args[0].equals("loadcrl")) {
|
||||
} else {
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
****/
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ import net.i2p.util.SystemVersion;
|
||||
*
|
||||
* @author jrandom, thecrypto
|
||||
*/
|
||||
public class CryptixAESEngine extends AESEngine {
|
||||
public final class CryptixAESEngine extends AESEngine {
|
||||
private final static CryptixRijndael_Algorithm _algo = new CryptixRijndael_Algorithm();
|
||||
// keys are now cached in the SessionKey objects
|
||||
//private CryptixAESKeyCache _cache;
|
||||
|
@ -34,6 +34,7 @@ import java.math.BigInteger;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
import java.security.spec.DSAParameterSpec;
|
||||
|
||||
import net.i2p.crypto.elgamal.spec.ElGamalParameterSpec;
|
||||
import net.i2p.util.NativeBigInteger;
|
||||
|
||||
/**
|
||||
@ -43,7 +44,7 @@ import net.i2p.util.NativeBigInteger;
|
||||
* See also: ECConstants, RSAConstants
|
||||
*
|
||||
*/
|
||||
public class CryptoConstants {
|
||||
public final class CryptoConstants {
|
||||
public static final BigInteger dsap = new NativeBigInteger(
|
||||
"9c05b2aa960d9b97b8931963c9cc9e8c3026e9b8ed92fad0a69cc886d5bf8015fcadae31"
|
||||
+ "a0ad18fab3f01b00a358de237655c4964afaa2b337e96ad316b9fb1cc564b5aec5b69a9f"
|
||||
@ -78,6 +79,11 @@ public class CryptoConstants {
|
||||
*/
|
||||
public static final DSAParameterSpec DSA_SHA1_SPEC = new DSAParameterSpec(dsap, dsaq, dsag);
|
||||
|
||||
/**
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public static final ElGamalParameterSpec I2P_ELGAMAL_2048_SPEC = new ElGamalParameterSpec(elgp, elgg);
|
||||
|
||||
/**
|
||||
* This will be org.bouncycastle.jce.spec.ElgamalParameterSpec
|
||||
* if BC is available, otherwise it
|
||||
@ -98,11 +104,11 @@ public class CryptoConstants {
|
||||
} catch (Exception e) {
|
||||
//System.out.println("BC ElG spec failed");
|
||||
//e.printStackTrace();
|
||||
spec = new ElGamalParameterSpec(elgp, elgg);
|
||||
spec = I2P_ELGAMAL_2048_SPEC;
|
||||
}
|
||||
} else {
|
||||
//System.out.println("BC not available");
|
||||
spec = new ElGamalParameterSpec(elgp, elgg);
|
||||
spec = I2P_ELGAMAL_2048_SPEC;
|
||||
}
|
||||
ELGAMAL_2048_SPEC = spec;
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ import net.i2p.util.NativeBigInteger;
|
||||
*
|
||||
* EdDSA support added in 0.9.15
|
||||
*/
|
||||
public class DSAEngine {
|
||||
public final class DSAEngine {
|
||||
private final Log _log;
|
||||
private final I2PAppContext _context;
|
||||
|
||||
@ -234,7 +234,7 @@ public class DSAEngine {
|
||||
BigInteger s = new NativeBigInteger(1, sbytes);
|
||||
BigInteger r = new NativeBigInteger(1, rbytes);
|
||||
BigInteger y = new NativeBigInteger(1, verifyingKey.getData());
|
||||
BigInteger w = null;
|
||||
BigInteger w;
|
||||
try {
|
||||
w = s.modInverse(CryptoConstants.dsaq);
|
||||
} catch (ArithmeticException ae) {
|
||||
@ -402,8 +402,7 @@ public class DSAEngine {
|
||||
long start = _context.clock().now();
|
||||
|
||||
BigInteger k;
|
||||
|
||||
boolean ok = false;
|
||||
boolean ok;
|
||||
do {
|
||||
k = new BigInteger(160, _context.random());
|
||||
ok = k.compareTo(CryptoConstants.dsaq) != 1;
|
||||
@ -516,15 +515,20 @@ public class DSAEngine {
|
||||
if (type == SigType.DSA_SHA1)
|
||||
return altVerifySigSHA1(signature, data, offset, len, verifyingKey);
|
||||
|
||||
java.security.Signature jsig;
|
||||
if (type.getBaseAlgorithm() == SigAlgo.EdDSA)
|
||||
jsig = new EdDSAEngine(type.getDigestInstance());
|
||||
else
|
||||
jsig = java.security.Signature.getInstance(type.getAlgorithmName());
|
||||
PublicKey pubKey = SigUtil.toJavaKey(verifyingKey);
|
||||
jsig.initVerify(pubKey);
|
||||
jsig.update(data, offset, len);
|
||||
boolean rv = jsig.verify(SigUtil.toJavaSig(signature));
|
||||
byte[] sigbytes = SigUtil.toJavaSig(signature);
|
||||
boolean rv;
|
||||
if (type.getBaseAlgorithm() == SigAlgo.EdDSA) {
|
||||
// take advantage of one-shot mode
|
||||
EdDSAEngine jsig = new EdDSAEngine(type.getDigestInstance());
|
||||
jsig.initVerify(pubKey);
|
||||
rv = jsig.verifyOneShot(data, offset, len, sigbytes);
|
||||
} else {
|
||||
java.security.Signature jsig = java.security.Signature.getInstance(type.getAlgorithmName());
|
||||
jsig.initVerify(pubKey);
|
||||
jsig.update(data, offset, len);
|
||||
rv = jsig.verify(sigbytes);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
@ -564,15 +568,21 @@ public class DSAEngine {
|
||||
if (type.getHashLen() != hashlen)
|
||||
throw new IllegalArgumentException("type mismatch hash=" + hash.getClass() + " key=" + type);
|
||||
|
||||
String algo = getRawAlgo(type);
|
||||
java.security.Signature jsig;
|
||||
if (type.getBaseAlgorithm() == SigAlgo.EdDSA)
|
||||
jsig = new EdDSAEngine(); // Ignore algo, EdDSAKey includes a hash specification.
|
||||
else
|
||||
jsig = java.security.Signature.getInstance(algo);
|
||||
jsig.initVerify(pubKey);
|
||||
jsig.update(hash.getData());
|
||||
boolean rv = jsig.verify(SigUtil.toJavaSig(signature));
|
||||
byte[] sigbytes = SigUtil.toJavaSig(signature);
|
||||
boolean rv;
|
||||
if (type.getBaseAlgorithm() == SigAlgo.EdDSA) {
|
||||
// take advantage of one-shot mode
|
||||
// Ignore algo, EdDSAKey includes a hash specification.
|
||||
EdDSAEngine jsig = new EdDSAEngine();
|
||||
jsig.initVerify(pubKey);
|
||||
rv = jsig.verifyOneShot(hash.getData(), sigbytes);
|
||||
} else {
|
||||
String algo = getRawAlgo(type);
|
||||
java.security.Signature jsig = java.security.Signature.getInstance(algo);
|
||||
jsig.initVerify(pubKey);
|
||||
jsig.update(hash.getData());
|
||||
rv = jsig.verify(sigbytes);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
@ -607,15 +617,20 @@ public class DSAEngine {
|
||||
if (type == SigType.DSA_SHA1)
|
||||
return altSignSHA1(data, offset, len, privateKey);
|
||||
|
||||
java.security.Signature jsig;
|
||||
if (type.getBaseAlgorithm() == SigAlgo.EdDSA)
|
||||
jsig = new EdDSAEngine(type.getDigestInstance());
|
||||
else
|
||||
jsig = java.security.Signature.getInstance(type.getAlgorithmName());
|
||||
PrivateKey privKey = SigUtil.toJavaKey(privateKey);
|
||||
jsig.initSign(privKey, _context.random());
|
||||
jsig.update(data, offset, len);
|
||||
return SigUtil.fromJavaSig(jsig.sign(), type);
|
||||
byte[] sigbytes;
|
||||
if (type.getBaseAlgorithm() == SigAlgo.EdDSA) {
|
||||
// take advantage of one-shot mode
|
||||
EdDSAEngine jsig = new EdDSAEngine(type.getDigestInstance());
|
||||
jsig.initSign(privKey);
|
||||
sigbytes = jsig.signOneShot(data, offset, len);
|
||||
} else {
|
||||
java.security.Signature jsig = java.security.Signature.getInstance(type.getAlgorithmName());
|
||||
jsig.initSign(privKey, _context.random());
|
||||
jsig.update(data, offset, len);
|
||||
sigbytes = jsig.sign();
|
||||
}
|
||||
return SigUtil.fromJavaSig(sigbytes, type);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -650,14 +665,20 @@ public class DSAEngine {
|
||||
if (type.getHashLen() != hashlen)
|
||||
throw new IllegalArgumentException("type mismatch hash=" + hash.getClass() + " key=" + type);
|
||||
|
||||
java.security.Signature jsig;
|
||||
if (type.getBaseAlgorithm() == SigAlgo.EdDSA)
|
||||
jsig = new EdDSAEngine(); // Ignore algo, EdDSAKey includes a hash specification.
|
||||
else
|
||||
jsig = java.security.Signature.getInstance(algo);
|
||||
jsig.initSign(privKey, _context.random());
|
||||
jsig.update(hash.getData());
|
||||
return SigUtil.fromJavaSig(jsig.sign(), type);
|
||||
byte[] sigbytes;
|
||||
if (type.getBaseAlgorithm() == SigAlgo.EdDSA) {
|
||||
// take advantage of one-shot mode
|
||||
// Ignore algo, EdDSAKey includes a hash specification.
|
||||
EdDSAEngine jsig = new EdDSAEngine();
|
||||
jsig.initSign(privKey);
|
||||
sigbytes = jsig.signOneShot(hash.getData());
|
||||
} else {
|
||||
java.security.Signature jsig = java.security.Signature.getInstance(type.getAlgorithmName());
|
||||
jsig.initSign(privKey, _context.random());
|
||||
jsig.update(hash.getData());
|
||||
sigbytes = jsig.sign();
|
||||
}
|
||||
return SigUtil.fromJavaSig(sigbytes, type);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -20,7 +20,7 @@ import net.i2p.util.NativeBigInteger;
|
||||
*
|
||||
* @since 0.9.9
|
||||
*/
|
||||
class ECConstants {
|
||||
final class ECConstants {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
|
@ -19,7 +19,7 @@ import net.i2p.util.NativeBigInteger;
|
||||
*
|
||||
* @since 0.9.16
|
||||
*/
|
||||
class ECUtil {
|
||||
final class ECUtil {
|
||||
|
||||
private static final BigInteger TWO = new BigInteger("2");
|
||||
private static final BigInteger THREE = new BigInteger("3");
|
||||
|
@ -32,7 +32,7 @@ import net.i2p.util.SimpleByteCache;
|
||||
*
|
||||
* No, this does not extend AESEngine or CryptixAESEngine.
|
||||
*/
|
||||
public class ElGamalAESEngine {
|
||||
public final class ElGamalAESEngine {
|
||||
private final Log _log;
|
||||
private final static int MIN_ENCRYPTED_SIZE = 80; // smallest possible resulting size
|
||||
private final I2PAppContext _context;
|
||||
|
@ -52,7 +52,7 @@ import net.i2p.util.SimpleByteCache;
|
||||
* @author thecrypto, jrandom
|
||||
*/
|
||||
|
||||
public class ElGamalEngine {
|
||||
public final class ElGamalEngine {
|
||||
private final Log _log;
|
||||
private final I2PAppContext _context;
|
||||
private final YKGenerator _ykgen;
|
||||
|
@ -1,66 +0,0 @@
|
||||
package net.i2p.crypto;
|
||||
|
||||
/*
|
||||
* Copyright (c) 2000 - 2013 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
* and associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
|
||||
* is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software.
|
||||
*
|
||||
*THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
|
||||
* AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
|
||||
/**
|
||||
* Copied from org.bouncycastle.jce.spec
|
||||
* This can't actually be passed to the BC provider, we would have to
|
||||
* use reflection to create a "real" org.bouncycasle.jce.spec.ElGamalParameterSpec.
|
||||
*
|
||||
* @since 0.9.18
|
||||
*/
|
||||
public class ElGamalParameterSpec implements AlgorithmParameterSpec {
|
||||
private final BigInteger p;
|
||||
private final BigInteger g;
|
||||
|
||||
/**
|
||||
* Constructs a parameter set for Diffie-Hellman, using a prime modulus
|
||||
* <code>p</code> and a base generator <code>g</code>.
|
||||
*
|
||||
* @param p the prime modulus
|
||||
* @param g the base generator
|
||||
*/
|
||||
public ElGamalParameterSpec(BigInteger p, BigInteger g) {
|
||||
this.p = p;
|
||||
this.g = g;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the prime modulus <code>p</code>.
|
||||
*
|
||||
* @return the prime modulus <code>p</code>
|
||||
*/
|
||||
public BigInteger getP() {
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base generator <code>g</code>.
|
||||
*
|
||||
* @return the base generator <code>g</code>
|
||||
*/
|
||||
public BigInteger getG() {
|
||||
return g;
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ public enum EncType {
|
||||
* This is the default.
|
||||
* Pubkey 256 bytes, privkey 256 bytes.
|
||||
*/
|
||||
ELGAMAL_2048(0, 256, 256, EncAlgo.ELGAMAL, "ElGamal/None/NoPadding", CryptoConstants.ELGAMAL_2048_SPEC, "0"),
|
||||
ELGAMAL_2048(0, 256, 256, EncAlgo.ELGAMAL, "ElGamal/None/NoPadding", CryptoConstants.I2P_ELGAMAL_2048_SPEC, "0"),
|
||||
|
||||
/** Pubkey 64 bytes; privkey 32 bytes; */
|
||||
EC_P256(1, 64, 32, EncAlgo.EC, "EC/None/NoPadding", ECConstants.P256_SPEC, "0.9.20"),
|
||||
|
@ -21,7 +21,7 @@ import org.bouncycastle.oldcrypto.macs.I2PHMac;
|
||||
*
|
||||
* Deprecated, used only by Syndie.
|
||||
*/
|
||||
public class HMAC256Generator extends HMACGenerator {
|
||||
public final class HMAC256Generator extends HMACGenerator {
|
||||
|
||||
/**
|
||||
* @param context unused
|
||||
|
@ -34,6 +34,7 @@ import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
|
||||
import net.i2p.crypto.provider.I2PProvider;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.PublicKey;
|
||||
@ -55,9 +56,13 @@ import net.i2p.util.RandomSource;
|
||||
/** Define a way of generating asymmetrical key pairs as well as symmetrical keys
|
||||
* @author jrandom
|
||||
*/
|
||||
public class KeyGenerator {
|
||||
public final class KeyGenerator {
|
||||
private final I2PAppContext _context;
|
||||
|
||||
static {
|
||||
I2PProvider.addProvider();
|
||||
}
|
||||
|
||||
public KeyGenerator(I2PAppContext context) {
|
||||
_context = context;
|
||||
}
|
||||
@ -208,10 +213,10 @@ public class KeyGenerator {
|
||||
SimpleDataStructure[] keys = new SimpleDataStructure[2];
|
||||
BigInteger x = null;
|
||||
|
||||
// make sure the random key is less than the DSA q
|
||||
// make sure the random key is less than the DSA q and greater than zero
|
||||
do {
|
||||
x = new NativeBigInteger(160, _context.random());
|
||||
} while (x.compareTo(CryptoConstants.dsaq) >= 0);
|
||||
} while (x.compareTo(CryptoConstants.dsaq) >= 0 || x.equals(BigInteger.ZERO));
|
||||
|
||||
BigInteger y = CryptoConstants.dsag.modPow(x, CryptoConstants.dsap);
|
||||
keys[0] = new SigningPublicKey();
|
||||
|
@ -13,10 +13,13 @@ import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateExpiredException;
|
||||
import java.security.cert.CertificateNotYetValidException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.provider.I2PProvider;
|
||||
import net.i2p.data.Base32;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SecureDirectory;
|
||||
@ -29,7 +32,7 @@ import net.i2p.util.SystemVersion;
|
||||
*
|
||||
* @since 0.9.9
|
||||
*/
|
||||
public class KeyStoreUtil {
|
||||
public final class KeyStoreUtil {
|
||||
|
||||
public static boolean _blacklistLogged;
|
||||
|
||||
@ -38,6 +41,10 @@ public class KeyStoreUtil {
|
||||
private static final int DEFAULT_KEY_SIZE = 2048;
|
||||
private static final int DEFAULT_KEY_VALID_DAYS = 3652; // 10 years
|
||||
|
||||
static {
|
||||
I2PProvider.addProvider();
|
||||
}
|
||||
|
||||
/**
|
||||
* No reports of some of these in a Java keystore but just to be safe...
|
||||
* CNNIC ones are in Ubuntu keystore.
|
||||
@ -464,20 +471,29 @@ public class KeyStoreUtil {
|
||||
}
|
||||
}
|
||||
String keytool = (new File(System.getProperty("java.home"), "bin/keytool")).getAbsolutePath();
|
||||
String[] args = new String[] {
|
||||
keytool,
|
||||
"-genkey", // -genkeypair preferred in newer keytools, but this works with more
|
||||
"-storetype", KeyStore.getDefaultType(),
|
||||
"-keystore", ks.getAbsolutePath(),
|
||||
"-storepass", ksPW,
|
||||
"-alias", alias,
|
||||
"-dname", "CN=" + cname + ",OU=" + ou + ",O=I2P Anonymous Network,L=XX,ST=XX,C=XX",
|
||||
"-validity", Integer.toString(validDays), // 10 years
|
||||
"-keyalg", keyAlg,
|
||||
"-sigalg", getSigAlg(keySize, keyAlg),
|
||||
"-keysize", Integer.toString(keySize),
|
||||
"-keypass", keyPW
|
||||
};
|
||||
List<String> a = new ArrayList<String>(32);
|
||||
a.add(keytool);
|
||||
a.add("-genkey"); // -genkeypair preferred in newer keytools, but this works with more
|
||||
//a.add("-v"); // verbose, gives you a stack trace on exception
|
||||
a.add("-storetype"); a.add(KeyStore.getDefaultType());
|
||||
a.add("-keystore"); a.add(ks.getAbsolutePath());
|
||||
a.add("-storepass"); a.add(ksPW);
|
||||
a.add("-alias"); a.add(alias);
|
||||
a.add("-dname"); a.add("CN=" + cname + ",OU=" + ou + ",O=I2P Anonymous Network,L=XX,ST=XX,C=XX");
|
||||
a.add("-validity"); a.add(Integer.toString(validDays)); // 10 years
|
||||
a.add("-keyalg"); a.add(keyAlg);
|
||||
a.add("-sigalg"); a.add(getSigAlg(keySize, keyAlg));
|
||||
a.add("-keysize"); a.add(Integer.toString(keySize));
|
||||
a.add("-keypass"); a.add(keyPW);
|
||||
if (keyAlg.equals("Ed") || keyAlg.equals("EdDSA") || keyAlg.equals("ElGamal")) {
|
||||
File f = I2PAppContext.getGlobalContext().getBaseDir();
|
||||
f = new File(f, "lib");
|
||||
f = new File(f, "i2p.jar");
|
||||
// providerpath is not in the man page; see keytool -genkey -help
|
||||
a.add("-providerpath"); a.add(f.getAbsolutePath());
|
||||
a.add("-providerclass"); a.add("net.i2p.crypto.provider.I2PProvider");
|
||||
}
|
||||
String[] args = a.toArray(new String[a.size()]);
|
||||
// TODO pipe key password to process; requires ShellCommand enhancements
|
||||
boolean success = (new ShellCommand()).executeSilentAndWaitTimed(args, 240);
|
||||
if (success) {
|
||||
@ -514,6 +530,8 @@ public class KeyStoreUtil {
|
||||
private static String getSigAlg(int size, String keyalg) {
|
||||
if (keyalg.equals("EC"))
|
||||
keyalg = "ECDSA";
|
||||
else if (keyalg.equals("Ed"))
|
||||
keyalg = "EdDSA";
|
||||
String hash;
|
||||
if (keyalg.equals("ECDSA")) {
|
||||
if (size <= 256)
|
||||
@ -522,6 +540,8 @@ public class KeyStoreUtil {
|
||||
hash = "SHA384";
|
||||
else
|
||||
hash = "SHA512";
|
||||
} else if (keyalg.equals("EdDSA")) {
|
||||
hash = "SHA512";
|
||||
} else {
|
||||
if (size <= 1024)
|
||||
hash = "SHA1";
|
||||
@ -559,6 +579,103 @@ public class KeyStoreUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the private key and certificate chain (if any) out of a keystore.
|
||||
* Does NOT close the stream. Throws on all errors.
|
||||
*
|
||||
* @param ks path to the keystore
|
||||
* @param ksPW the keystore password, may be null
|
||||
* @param alias the name of the key
|
||||
* @param keyPW the key password, must be at least 6 characters
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public static void exportPrivateKey(File ks, String ksPW, String alias, String keyPW,
|
||||
OutputStream out)
|
||||
throws GeneralSecurityException, IOException {
|
||||
InputStream fis = null;
|
||||
try {
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
fis = new FileInputStream(ks);
|
||||
char[] pwchars = ksPW != null ? ksPW.toCharArray() : null;
|
||||
keyStore.load(fis, pwchars);
|
||||
char[] keypwchars = keyPW.toCharArray();
|
||||
PrivateKey pk = (PrivateKey) keyStore.getKey(alias, keypwchars);
|
||||
if (pk == null)
|
||||
throw new GeneralSecurityException("private key not found: " + alias);
|
||||
Certificate[] certs = keyStore.getCertificateChain(alias);
|
||||
CertUtil.exportPrivateKey(pk, certs, out);
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import the private key and certificate chain to a keystore.
|
||||
* Keystore will be created if it does not exist.
|
||||
* Private key MUST be first in the stream.
|
||||
* Closes the stream. Throws on all errors.
|
||||
*
|
||||
* @param ks path to the keystore
|
||||
* @param ksPW the keystore password, may be null
|
||||
* @param alias the name of the key. If null, will be taken from the Subject CN
|
||||
* of the first certificate in the chain.
|
||||
* @param keyPW the key password, must be at least 6 characters
|
||||
* @return the alias as specified or extracted
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public static String importPrivateKey(File ks, String ksPW, String alias, String keyPW,
|
||||
InputStream in)
|
||||
throws GeneralSecurityException, IOException {
|
||||
OutputStream fos = null;
|
||||
try {
|
||||
KeyStore keyStore = createKeyStore(ks, ksPW);
|
||||
PrivateKey pk = CertUtil.loadPrivateKey(in);
|
||||
List<X509Certificate> certs = CertUtil.loadCerts(in);
|
||||
if (alias == null) {
|
||||
alias = CertUtil.getSubjectValue(certs.get(0), "CN");
|
||||
if (alias == null)
|
||||
throw new GeneralSecurityException("no alias specified and no Subject CN in cert");
|
||||
if (alias.endsWith(".family.i2p.net") && alias.length() > ".family.i2p.net".length())
|
||||
alias = alias.substring(0, ".family.i2p.net".length());
|
||||
}
|
||||
keyStore.setKeyEntry(alias, pk, keyPW.toCharArray(), certs.toArray(new Certificate[certs.size()]));
|
||||
char[] pwchars = ksPW != null ? ksPW.toCharArray() : null;
|
||||
fos = new SecureFileOutputStream(ks);
|
||||
keyStore.store(fos, pwchars);
|
||||
return alias;
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
|
||||
try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import the private key and certificate chain to a keystore.
|
||||
* Keystore will be created if it does not exist.
|
||||
* Private key MUST be first in the stream.
|
||||
* Closes the stream. Throws on all errors.
|
||||
*
|
||||
* @param ks path to the keystore
|
||||
* @param ksPW the keystore password, may be null
|
||||
* @param alias the name of the key, non-null.
|
||||
* @param keyPW the key password, must be at least 6 characters
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public static void storePrivateKey(File ks, String ksPW, String alias, String keyPW,
|
||||
PrivateKey pk, List<X509Certificate> certs)
|
||||
throws GeneralSecurityException, IOException {
|
||||
OutputStream fos = null;
|
||||
try {
|
||||
KeyStore keyStore = createKeyStore(ks, ksPW);
|
||||
keyStore.setKeyEntry(alias, pk, keyPW.toCharArray(), certs.toArray(new Certificate[certs.size()]));
|
||||
char[] pwchars = ksPW != null ? ksPW.toCharArray() : null;
|
||||
fos = new SecureFileOutputStream(ks);
|
||||
keyStore.store(fos, pwchars);
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a cert out of a keystore
|
||||
*
|
||||
@ -641,11 +758,31 @@ public class KeyStoreUtil {
|
||||
* Usage: KeyStoreUtil (loads from system keystore)
|
||||
* KeyStoreUtil foo.ks (loads from system keystore, and from foo.ks keystore if exists, else creates empty)
|
||||
* KeyStoreUtil certDir (loads from system keystore and all certs in certDir if exists)
|
||||
* KeyStoreUtil import file.ks file.key alias keypw (imxports private key from file to keystore)
|
||||
* KeyStoreUtil export file.ks alias keypw (exports private key from keystore)
|
||||
* KeyStoreUtil keygen file.ks alias keypw (create keypair in keystore)
|
||||
* KeyStoreUtil keygen2 file.ks alias keypw (create keypair using I2PProvider)
|
||||
*/
|
||||
/****
|
||||
public static void main(String[] args) {
|
||||
File ksf = (args.length > 0) ? new File(args[0]) : null;
|
||||
try {
|
||||
if (args.length > 0 && "import".equals(args[0])) {
|
||||
testImport(args);
|
||||
return;
|
||||
}
|
||||
if (args.length > 0 && "export".equals(args[0])) {
|
||||
testExport(args);
|
||||
return;
|
||||
}
|
||||
if (args.length > 0 && "keygen".equals(args[0])) {
|
||||
testKeygen(args);
|
||||
return;
|
||||
}
|
||||
if (args.length > 0 && "keygen2".equals(args[0])) {
|
||||
testKeygen2(args);
|
||||
return;
|
||||
}
|
||||
File ksf = (args.length > 0) ? new File(args[0]) : null;
|
||||
if (ksf != null && !ksf.exists()) {
|
||||
createKeyStore(ksf, DEFAULT_KEYSTORE_PASSWORD);
|
||||
System.out.println("Created empty keystore " + ksf);
|
||||
@ -674,5 +811,63 @@ public class KeyStoreUtil {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static void testImport(String[] args) throws Exception {
|
||||
File ksf = new File(args[1]);
|
||||
InputStream in = new FileInputStream(args[2]);
|
||||
String alias = args[3];
|
||||
String pw = args[4];
|
||||
importPrivateKey(ksf, DEFAULT_KEYSTORE_PASSWORD, alias, pw, in);
|
||||
}
|
||||
|
||||
|
||||
private static void testExport(String[] args) throws Exception {
|
||||
File ksf = new File(args[1]);
|
||||
String alias = args[2];
|
||||
String pw = args[3];
|
||||
exportPrivateKey(ksf, DEFAULT_KEYSTORE_PASSWORD, alias, pw, System.out);
|
||||
}
|
||||
|
||||
private static void testKeygen(String[] args) throws Exception {
|
||||
File ksf = new File(args[1]);
|
||||
String alias = args[2];
|
||||
String pw = args[3];
|
||||
boolean ok = createKeys(ksf, DEFAULT_KEYSTORE_PASSWORD, alias, "test cname", "test ou",
|
||||
//DEFAULT_KEY_VALID_DAYS, "EdDSA", 256, pw);
|
||||
DEFAULT_KEY_VALID_DAYS, "ElGamal", 2048, pw);
|
||||
System.out.println("genkey ok? " + ok);
|
||||
}
|
||||
|
||||
private static void testKeygen2(String[] args) throws Exception {
|
||||
// keygen test using the I2PProvider
|
||||
//SigType type = SigType.EdDSA_SHA512_Ed25519;
|
||||
SigType type = SigType.ElGamal_SHA256_MODP2048;
|
||||
java.security.KeyPairGenerator kpg = java.security.KeyPairGenerator.getInstance(type.getBaseAlgorithm().getName());
|
||||
kpg.initialize(type.getParams());
|
||||
java.security.KeyPair kp = kpg.generateKeyPair();
|
||||
java.security.PublicKey jpub = kp.getPublic();
|
||||
java.security.PrivateKey jpriv = kp.getPrivate();
|
||||
|
||||
System.out.println("Encoded private key:");
|
||||
System.out.println(net.i2p.util.HexDump.dump(jpriv.getEncoded()));
|
||||
System.out.println("Encoded public key:");
|
||||
System.out.println(net.i2p.util.HexDump.dump(jpub.getEncoded()));
|
||||
|
||||
java.security.Signature jsig = java.security.Signature.getInstance(type.getAlgorithmName());
|
||||
jsig.initSign(jpriv);
|
||||
byte[] data = new byte[111];
|
||||
net.i2p.util.RandomSource.getInstance().nextBytes(data);
|
||||
jsig.update(data);
|
||||
byte[] bsig = jsig.sign();
|
||||
System.out.println("Encoded signature:");
|
||||
System.out.println(net.i2p.util.HexDump.dump(bsig));
|
||||
jsig.initVerify(jpub);
|
||||
jsig.update(data);
|
||||
boolean ok = jsig.verify(bsig);
|
||||
System.out.println("verify passed? " + ok);
|
||||
|
||||
net.i2p.data.Signature sig = SigUtil.fromJavaSig(bsig, type);
|
||||
System.out.println("Signature test: " + sig);
|
||||
}
|
||||
****/
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import net.i2p.util.NativeBigInteger;
|
||||
*
|
||||
* @since 0.9.9
|
||||
*/
|
||||
class RSAConstants {
|
||||
final class RSAConstants {
|
||||
|
||||
/**
|
||||
* Generate a spec
|
||||
|
@ -1,7 +1,5 @@
|
||||
package net.i2p.crypto;
|
||||
|
||||
import gnu.crypto.hash.Sha256Standalone;
|
||||
|
||||
import java.security.DigestException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
@ -14,25 +12,13 @@ import net.i2p.data.Hash;
|
||||
* Defines a wrapper for SHA-256 operation.
|
||||
*
|
||||
* As of release 0.8.7, uses java.security.MessageDigest by default.
|
||||
* If that is unavailable, it uses
|
||||
* As of release 0.9.25, uses only MessageDigest.
|
||||
* GNU-Crypto {@link gnu.crypto.hash.Sha256Standalone}
|
||||
* is deprecated.
|
||||
*/
|
||||
public final class SHA256Generator {
|
||||
private final LinkedBlockingQueue<MessageDigest> _digests;
|
||||
|
||||
private static final boolean _useGnu;
|
||||
|
||||
static {
|
||||
boolean useGnu = false;
|
||||
try {
|
||||
MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
useGnu = true;
|
||||
System.out.println("INFO: Using GNU SHA-256");
|
||||
}
|
||||
_useGnu = useGnu;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context unused
|
||||
*/
|
||||
@ -96,45 +82,14 @@ public final class SHA256Generator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new MessageDigest from the system libs unless unavailable
|
||||
* in this JVM, in that case return a wrapped GNU Sha256Standalone
|
||||
* Return a new MessageDigest from the system libs.
|
||||
* @since 0.8.7, public since 0.8.8 for FortunaStandalone
|
||||
*/
|
||||
public static MessageDigest getDigestInstance() {
|
||||
if (!_useGnu) {
|
||||
try {
|
||||
return MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {}
|
||||
}
|
||||
return new GnuMessageDigest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper to make Sha256Standalone a MessageDigest
|
||||
* @since 0.8.7
|
||||
*/
|
||||
private static class GnuMessageDigest extends MessageDigest {
|
||||
private final Sha256Standalone _gnu;
|
||||
|
||||
protected GnuMessageDigest() {
|
||||
super("SHA-256");
|
||||
_gnu = new Sha256Standalone();
|
||||
}
|
||||
|
||||
protected byte[] engineDigest() {
|
||||
return _gnu.digest();
|
||||
}
|
||||
|
||||
protected void engineReset() {
|
||||
_gnu.reset();
|
||||
}
|
||||
|
||||
protected void engineUpdate(byte input) {
|
||||
_gnu.update(input);
|
||||
}
|
||||
|
||||
protected void engineUpdate(byte[] input, int offset, int len) {
|
||||
_gnu.update(input, offset, len);
|
||||
try {
|
||||
return MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
605
core/java/src/net/i2p/crypto/SelfSignedGenerator.java
Normal file
605
core/java/src/net/i2p/crypto/SelfSignedGenerator.java
Normal file
@ -0,0 +1,605 @@
|
||||
package net.i2p.crypto;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.cert.X509CRL;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import javax.crypto.interfaces.DHPublicKey;
|
||||
import javax.crypto.spec.DHParameterSpec;
|
||||
import javax.crypto.spec.DHPublicKeySpec;
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
import static net.i2p.crypto.SigUtil.intToASN1;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Signature;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.data.SigningPublicKey;
|
||||
import net.i2p.data.SimpleDataStructure;
|
||||
import net.i2p.util.HexDump;
|
||||
import net.i2p.util.RandomSource;
|
||||
import net.i2p.util.SecureFileOutputStream;
|
||||
import net.i2p.util.SystemVersion;
|
||||
|
||||
/**
|
||||
* Generate keys and a selfsigned certificate, suitable for
|
||||
* storing in a Keystore with KeyStoreUtil.storePrivateKey().
|
||||
* All done programatically, no keytool, no BC libs, no sun classes.
|
||||
* Ref: RFC 2459
|
||||
*
|
||||
* This is coded to create a cert that matches what comes out of keytool
|
||||
* exactly, even if I don't understand all of it.
|
||||
*
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public final class SelfSignedGenerator {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final String OID_CN = "2.5.4.3";
|
||||
private static final String OID_C = "2.5.4.6";
|
||||
private static final String OID_L = "2.5.4.7";
|
||||
private static final String OID_ST = "2.5.4.8";
|
||||
private static final String OID_O = "2.5.4.10";
|
||||
private static final String OID_OU = "2.5.4.11";
|
||||
// Subject Key Identifier
|
||||
private static final String OID_SKI = "2.5.29.14";
|
||||
// CRL number
|
||||
private static final String OID_CRLNUM = "2.5.29.20";
|
||||
|
||||
private static final Map<String, String> OIDS;
|
||||
static {
|
||||
OIDS = new HashMap<String, String>(16);
|
||||
OIDS.put(OID_CN, "CN");
|
||||
OIDS.put(OID_C, "C");
|
||||
OIDS.put(OID_L, "L");
|
||||
OIDS.put(OID_ST, "ST");
|
||||
OIDS.put(OID_O, "O");
|
||||
OIDS.put(OID_OU, "OU");
|
||||
OIDS.put(OID_SKI, "SKI");
|
||||
}
|
||||
|
||||
/**
|
||||
* rv[0] is a Java PublicKey
|
||||
* rv[1] is a Java PrivateKey
|
||||
* rv[2] is a Java X509Certificate
|
||||
* rv[3] is a Java X509CRL
|
||||
*/
|
||||
public static Object[] generate(String cname, String ou, String o, String l, String st, String c,
|
||||
int validDays, SigType type) throws GeneralSecurityException {
|
||||
SimpleDataStructure[] keys = KeyGenerator.getInstance().generateSigningKeys(type);
|
||||
SigningPublicKey pub = (SigningPublicKey) keys[0];
|
||||
SigningPrivateKey priv = (SigningPrivateKey) keys[1];
|
||||
PublicKey jpub = SigUtil.toJavaKey(pub);
|
||||
PrivateKey jpriv = SigUtil.toJavaKey(priv);
|
||||
|
||||
String oid;
|
||||
switch (type) {
|
||||
case DSA_SHA1:
|
||||
case ECDSA_SHA256_P256:
|
||||
case ECDSA_SHA384_P384:
|
||||
case ECDSA_SHA512_P521:
|
||||
case RSA_SHA256_2048:
|
||||
case RSA_SHA384_3072:
|
||||
case RSA_SHA512_4096:
|
||||
case EdDSA_SHA512_Ed25519:
|
||||
case EdDSA_SHA512_Ed25519ph:
|
||||
oid = type.getOID();
|
||||
break;
|
||||
default:
|
||||
throw new GeneralSecurityException("Unsupported: " + type);
|
||||
}
|
||||
byte[] sigoid = getEncodedOIDSeq(oid);
|
||||
|
||||
byte[] tbs = genTBS(cname, ou, o, l, st, c, validDays, sigoid, jpub);
|
||||
int tbslen = tbs.length;
|
||||
|
||||
Signature sig = DSAEngine.getInstance().sign(tbs, priv);
|
||||
if (sig == null)
|
||||
throw new GeneralSecurityException("sig failed");
|
||||
byte[] sigbytes= SigUtil.toJavaSig(sig);
|
||||
|
||||
int seqlen = tbslen + sigoid.length + spaceFor(sigbytes.length + 1);
|
||||
int totlen = spaceFor(seqlen);
|
||||
byte[] cb = new byte[totlen];
|
||||
int idx = 0;
|
||||
|
||||
// construct the whole encoded cert
|
||||
cb[idx++] = 0x30;
|
||||
idx = intToASN1(cb, idx, seqlen);
|
||||
|
||||
// TBS cert
|
||||
System.arraycopy(tbs, 0, cb, idx, tbs.length);
|
||||
idx += tbs.length;
|
||||
|
||||
// sig algo
|
||||
System.arraycopy(sigoid, 0, cb, idx, sigoid.length);
|
||||
idx += sigoid.length;
|
||||
|
||||
// sig (bit string)
|
||||
cb[idx++] = 0x03;
|
||||
idx = intToASN1(cb, idx, sigbytes.length + 1);
|
||||
cb[idx++] = 0;
|
||||
System.arraycopy(sigbytes, 0, cb, idx, sigbytes.length);
|
||||
|
||||
if (DEBUG) {
|
||||
System.out.println("Sig OID");
|
||||
System.out.println(HexDump.dump(sigoid));
|
||||
System.out.println("Signature");
|
||||
System.out.println(HexDump.dump(sigbytes));
|
||||
System.out.println("Whole cert");
|
||||
System.out.println(HexDump.dump(cb));
|
||||
}
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(cb);
|
||||
|
||||
X509Certificate cert;
|
||||
try {
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
cert = (X509Certificate)cf.generateCertificate(bais);
|
||||
cert.checkValidity();
|
||||
} catch (IllegalArgumentException iae) {
|
||||
throw new GeneralSecurityException("cert error", iae);
|
||||
}
|
||||
X509CRL crl = generateCRL(cert, validDays, 1, sigoid, jpriv);
|
||||
|
||||
// some simple tests
|
||||
PublicKey cpub = cert.getPublicKey();
|
||||
cert.verify(cpub);
|
||||
if (!cpub.equals(jpub))
|
||||
throw new GeneralSecurityException("pubkey mismatch");
|
||||
// todo crl tests
|
||||
|
||||
Object[] rv = { jpub, jpriv, cert, crl };
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a CRL for the given cert, signed with the given private key
|
||||
*/
|
||||
private static X509CRL generateCRL(X509Certificate cert, int validDays, int crlNum,
|
||||
byte[] sigoid, PrivateKey jpriv) throws GeneralSecurityException {
|
||||
|
||||
SigningPrivateKey priv = SigUtil.fromJavaKey(jpriv);
|
||||
|
||||
byte[] tbs = genTBSCRL(cert, validDays, crlNum, sigoid);
|
||||
int tbslen = tbs.length;
|
||||
|
||||
Signature sig = DSAEngine.getInstance().sign(tbs, priv);
|
||||
if (sig == null)
|
||||
throw new GeneralSecurityException("sig failed");
|
||||
byte[] sigbytes= SigUtil.toJavaSig(sig);
|
||||
|
||||
int seqlen = tbslen + sigoid.length + spaceFor(sigbytes.length + 1);
|
||||
int totlen = spaceFor(seqlen);
|
||||
byte[] cb = new byte[totlen];
|
||||
int idx = 0;
|
||||
|
||||
// construct the whole encoded cert
|
||||
cb[idx++] = 0x30;
|
||||
idx = intToASN1(cb, idx, seqlen);
|
||||
|
||||
// TBS cert
|
||||
System.arraycopy(tbs, 0, cb, idx, tbs.length);
|
||||
idx += tbs.length;
|
||||
|
||||
// sig algo
|
||||
System.arraycopy(sigoid, 0, cb, idx, sigoid.length);
|
||||
idx += sigoid.length;
|
||||
|
||||
// sig (bit string)
|
||||
cb[idx++] = 0x03;
|
||||
idx = intToASN1(cb, idx, sigbytes.length + 1);
|
||||
cb[idx++] = 0;
|
||||
System.arraycopy(sigbytes, 0, cb, idx, sigbytes.length);
|
||||
|
||||
/****
|
||||
if (DEBUG) {
|
||||
System.out.println("CRL Sig OID");
|
||||
System.out.println(HexDump.dump(sigoid));
|
||||
System.out.println("CRL Signature");
|
||||
System.out.println(HexDump.dump(sigbytes));
|
||||
System.out.println("Whole CRL");
|
||||
System.out.println(HexDump.dump(cb));
|
||||
}
|
||||
****/
|
||||
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(cb);
|
||||
|
||||
X509CRL rv;
|
||||
try {
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
// wow, unlike for x509Certificates, there's no validation here at all
|
||||
// ASN.1 errors don't cause any exceptions
|
||||
rv = (X509CRL)cf.generateCRL(bais);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
throw new GeneralSecurityException("cert error", iae);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
private static byte[] genTBS(String cname, String ou, String o, String l, String st, String c,
|
||||
int validDays, byte[] sigoid, PublicKey jpub) throws GeneralSecurityException {
|
||||
// a0 ???, int = 2
|
||||
byte[] version = { (byte) 0xa0, 3, 2, 1, 2 };
|
||||
|
||||
// postive serial number (int)
|
||||
byte[] serial = new byte[6];
|
||||
serial[0] = 2;
|
||||
serial[1] = 4;
|
||||
RandomSource.getInstance().nextBytes(serial, 2, 4);
|
||||
serial[2] &= 0x7f;
|
||||
|
||||
// going to use this for both issuer and subject
|
||||
String dname = "CN=" + cname + ",OU=" + ou + ",O=" + o + ",L=" + l + ",ST=" + st + ",C=" + c;
|
||||
byte[] issuer = (new X500Principal(dname, OIDS)).getEncoded();
|
||||
byte[] validity = getValidity(validDays);
|
||||
byte[] subject = issuer;
|
||||
|
||||
byte[] pubbytes = jpub.getEncoded();
|
||||
byte[] extbytes = getExtensions(pubbytes);
|
||||
|
||||
int len = version.length + serial.length + sigoid.length + issuer.length +
|
||||
validity.length + subject.length + pubbytes.length + extbytes.length;
|
||||
|
||||
int totlen = spaceFor(len);
|
||||
byte[] rv = new byte[totlen];
|
||||
int idx = 0;
|
||||
rv[idx++] = 0x30;
|
||||
idx = intToASN1(rv, idx, len);
|
||||
System.arraycopy(version, 0, rv, idx, version.length);
|
||||
idx += version.length;
|
||||
System.arraycopy(serial, 0, rv, idx, serial.length);
|
||||
idx += serial.length;
|
||||
System.arraycopy(sigoid, 0, rv, idx, sigoid.length);
|
||||
idx += sigoid.length;
|
||||
System.arraycopy(issuer, 0, rv, idx, issuer.length);
|
||||
idx += issuer.length;
|
||||
System.arraycopy(validity, 0, rv, idx, validity.length);
|
||||
idx += validity.length;
|
||||
System.arraycopy(subject, 0, rv, idx, subject.length);
|
||||
idx += subject.length;
|
||||
System.arraycopy(pubbytes, 0, rv, idx, pubbytes.length);
|
||||
idx += pubbytes.length;
|
||||
System.arraycopy(extbytes, 0, rv, idx, extbytes.length);
|
||||
|
||||
if (DEBUG) {
|
||||
System.out.println(HexDump.dump(version));
|
||||
System.out.println("serial");
|
||||
System.out.println(HexDump.dump(serial));
|
||||
System.out.println("oid");
|
||||
System.out.println(HexDump.dump(sigoid));
|
||||
System.out.println("issuer");
|
||||
System.out.println(HexDump.dump(issuer));
|
||||
System.out.println("valid");
|
||||
System.out.println(HexDump.dump(validity));
|
||||
System.out.println("subject");
|
||||
System.out.println(HexDump.dump(subject));
|
||||
System.out.println("pub");
|
||||
System.out.println(HexDump.dump(pubbytes));
|
||||
System.out.println("extensions");
|
||||
System.out.println(HexDump.dump(extbytes));
|
||||
System.out.println("TBS cert");
|
||||
System.out.println(HexDump.dump(rv));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param crlNum 0-255 because lazy
|
||||
* @return ASN.1 encoded object
|
||||
*/
|
||||
private static byte[] genTBSCRL(X509Certificate cert, int validDays,
|
||||
int crlNum, byte[] sigalg) throws GeneralSecurityException {
|
||||
// a0 ???, int = 2
|
||||
byte[] version = { 2, 1, 1 };
|
||||
byte[] issuer = cert.getIssuerX500Principal().getEncoded();
|
||||
|
||||
byte[] serial = cert.getSerialNumber().toByteArray();
|
||||
if (serial.length > 255)
|
||||
throw new IllegalArgumentException();
|
||||
long now = System.currentTimeMillis();
|
||||
long then = now + (validDays * 24L * 60 * 60 * 1000);
|
||||
// used for CRL time and revocation time
|
||||
byte[] nowbytes = getDate(now);
|
||||
// used for next CRL time
|
||||
byte[] thenbytes = getDate(then);
|
||||
|
||||
byte[] extbytes = getCRLExtensions(crlNum);
|
||||
|
||||
int revlen = 2 + serial.length + nowbytes.length;
|
||||
int revseqlen = spaceFor(revlen);
|
||||
int revsseqlen = spaceFor(revseqlen);
|
||||
|
||||
|
||||
int len = version.length + sigalg.length + issuer.length + nowbytes.length +
|
||||
thenbytes.length + revsseqlen + extbytes.length;
|
||||
|
||||
int totlen = spaceFor(len);
|
||||
byte[] rv = new byte[totlen];
|
||||
int idx = 0;
|
||||
rv[idx++] = 0x30;
|
||||
idx = intToASN1(rv, idx, len);
|
||||
System.arraycopy(version, 0, rv, idx, version.length);
|
||||
idx += version.length;
|
||||
System.arraycopy(sigalg, 0, rv, idx, sigalg.length);
|
||||
idx += sigalg.length;
|
||||
System.arraycopy(issuer, 0, rv, idx, issuer.length);
|
||||
idx += issuer.length;
|
||||
System.arraycopy(nowbytes, 0, rv, idx, nowbytes.length);
|
||||
idx += nowbytes.length;
|
||||
System.arraycopy(thenbytes, 0, rv, idx, thenbytes.length);
|
||||
idx += thenbytes.length;
|
||||
// the certs
|
||||
rv[idx++] = 0x30;
|
||||
idx = intToASN1(rv, idx, revseqlen);
|
||||
// the cert
|
||||
rv[idx++] = 0x30;
|
||||
idx = intToASN1(rv, idx, revlen);
|
||||
rv[idx++] = 0x02;
|
||||
rv[idx++] = (byte) serial.length;
|
||||
System.arraycopy(serial, 0, rv, idx, serial.length);
|
||||
idx += serial.length;
|
||||
System.arraycopy(nowbytes, 0, rv, idx, nowbytes.length);
|
||||
idx += nowbytes.length;
|
||||
// extensions
|
||||
System.arraycopy(extbytes, 0, rv, idx, extbytes.length);
|
||||
|
||||
if (DEBUG) {
|
||||
System.out.println("version");
|
||||
System.out.println(HexDump.dump(version));
|
||||
System.out.println("sigalg");
|
||||
System.out.println(HexDump.dump(sigalg));
|
||||
System.out.println("issuer");
|
||||
System.out.println(HexDump.dump(issuer));
|
||||
System.out.println("now");
|
||||
System.out.println(HexDump.dump(nowbytes));
|
||||
System.out.println("then");
|
||||
System.out.println(HexDump.dump(thenbytes));
|
||||
System.out.println("serial");
|
||||
System.out.println(HexDump.dump(serial));
|
||||
System.out.println("extensions");
|
||||
System.out.println(HexDump.dump(extbytes));
|
||||
System.out.println("TBS CRL");
|
||||
System.out.println(HexDump.dump(rv));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param val the length of the value, 65535 max
|
||||
* @return the length of the TLV
|
||||
*/
|
||||
private static int spaceFor(int val) {
|
||||
int rv;
|
||||
if (val > 255)
|
||||
rv = 3;
|
||||
else if (val > 127)
|
||||
rv = 2;
|
||||
else
|
||||
rv = 1;
|
||||
return 1 + rv + val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sequence of two UTCDates
|
||||
* @return 32 bytes ASN.1 encoded object
|
||||
*/
|
||||
private static byte[] getValidity(int validDays) {
|
||||
byte[] rv = new byte[32];
|
||||
rv[0] = 0x30;
|
||||
rv[1] = 30;
|
||||
long now = System.currentTimeMillis();
|
||||
long then = now + (validDays * 24L * 60 * 60 * 1000);
|
||||
byte[] nowbytes = getDate(now);
|
||||
byte[] thenbytes = getDate(then);
|
||||
System.arraycopy(nowbytes, 0, rv, 2, 15);
|
||||
System.arraycopy(thenbytes, 0, rv, 17, 15);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* A single UTCDate
|
||||
* @return 15 bytes ASN.1 encoded object
|
||||
*/
|
||||
private static byte[] getDate(long now) {
|
||||
// UTCDate format (HH 0-23)
|
||||
SimpleDateFormat fmt = new SimpleDateFormat("yyMMddHHmmss");
|
||||
fmt.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
byte[] nowbytes = DataHelper.getASCII(fmt.format(new Date(now)));
|
||||
if (nowbytes.length != 12)
|
||||
throw new IllegalArgumentException();
|
||||
byte[] rv = new byte[15];
|
||||
rv[0] = 0x17;
|
||||
rv[1] = 13;
|
||||
System.arraycopy(nowbytes, 0, rv, 2, 12);
|
||||
rv[14] = (byte) 'Z';
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param pubbytes bit string
|
||||
* @return 35 bytes ASN.1 encoded object
|
||||
*/
|
||||
private static byte[] getExtensions(byte[] pubbytes) {
|
||||
// RFC 2549 sec. 4.2.1.2
|
||||
// subject public key identifier is the sha1 hash of the bit string of the public key
|
||||
// without the tag, length, and igore fields
|
||||
int pidx = 1;
|
||||
int skip = pubbytes[pidx++];
|
||||
if ((skip & 0x80)!= 0)
|
||||
pidx += skip & 0x80;
|
||||
pidx++; // ignore
|
||||
MessageDigest md = SHA1.getInstance();
|
||||
md.update(pubbytes, pidx, pubbytes.length - pidx);
|
||||
byte[] sha = md.digest();
|
||||
byte[] oid = getEncodedOID(OID_SKI);
|
||||
|
||||
int wraplen = spaceFor(sha.length);
|
||||
int extlen = oid.length + spaceFor(wraplen);
|
||||
int extslen = spaceFor(extlen);
|
||||
int seqlen = spaceFor(extslen);
|
||||
int totlen = spaceFor(seqlen);
|
||||
byte[] rv = new byte[totlen];
|
||||
int idx = 0;
|
||||
rv[idx++] = (byte) 0xa3;
|
||||
idx = intToASN1(rv, idx, seqlen);
|
||||
rv[idx++] = (byte) 0x30;
|
||||
idx = intToASN1(rv, idx, extslen);
|
||||
rv[idx++] = (byte) 0x30;
|
||||
idx = intToASN1(rv, idx, extlen);
|
||||
System.arraycopy(oid, 0, rv, idx, oid.length);
|
||||
idx += oid.length;
|
||||
// don't know why we wrap the octet string in an octet string
|
||||
rv[idx++] = (byte) 0x04;
|
||||
idx = intToASN1(rv, idx, wraplen);
|
||||
rv[idx++] = (byte) 0x04;
|
||||
idx = intToASN1(rv, idx, sha.length);
|
||||
System.arraycopy(sha, 0, rv, idx, sha.length);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param crlNum 0-255 because lazy
|
||||
* @return 16 bytes ASN.1 encoded object
|
||||
*/
|
||||
private static byte[] getCRLExtensions(int crlNum) {
|
||||
if (crlNum < 0 || crlNum > 255)
|
||||
throw new IllegalArgumentException();
|
||||
byte[] oid = getEncodedOID(OID_CRLNUM);
|
||||
int extlen = oid.length + 5;
|
||||
int extslen = spaceFor(extlen);
|
||||
int seqlen = spaceFor(extslen);
|
||||
int totlen = spaceFor(seqlen);
|
||||
byte[] rv = new byte[totlen];
|
||||
int idx = 0;
|
||||
rv[idx++] = (byte) 0xa0;
|
||||
idx = intToASN1(rv, idx, seqlen);
|
||||
rv[idx++] = (byte) 0x30;
|
||||
idx = intToASN1(rv, idx, extslen);
|
||||
rv[idx++] = (byte) 0x30;
|
||||
idx = intToASN1(rv, idx, extlen);
|
||||
System.arraycopy(oid, 0, rv, idx, oid.length);
|
||||
idx += oid.length;
|
||||
// don't know why we wrap the int in an octet string
|
||||
rv[idx++] = (byte) 0x04;
|
||||
rv[idx++] = (byte) 3;
|
||||
rv[idx++] = (byte) 0x02;
|
||||
rv[idx++] = (byte) 1;
|
||||
rv[idx++] = (byte) crlNum;
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* 0x30 len 0x06 len encodedbytes... 0x05 0
|
||||
* @return ASN.1 encoded object
|
||||
* @throws IllegalArgumentException
|
||||
*/
|
||||
private static byte[] getEncodedOIDSeq(String oid) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(16);
|
||||
baos.write(0x30);
|
||||
// len to be filled in later
|
||||
baos.write(0);
|
||||
byte[] b = getEncodedOID(oid);
|
||||
baos.write(b, 0, b.length);
|
||||
// NULL
|
||||
baos.write(0x05);
|
||||
baos.write(0);
|
||||
byte[] rv = baos.toByteArray();
|
||||
rv[1] = (byte) (rv.length - 2);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* 0x06 len encodedbytes...
|
||||
* @return ASN.1 encoded object
|
||||
* @throws IllegalArgumentException
|
||||
*/
|
||||
private static byte[] getEncodedOID(String oid) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(16);
|
||||
baos.write(0x06);
|
||||
// len to be filled in later
|
||||
baos.write(0);
|
||||
String[] f = DataHelper.split(oid, "[.]");
|
||||
if (f.length < 2)
|
||||
throw new IllegalArgumentException("length: " + f.length);
|
||||
baos.write((40 * Integer.parseInt(f[0])) + Integer.parseInt(f[1]));
|
||||
for (int i = 2; i < f.length; i++) {
|
||||
int v = Integer.parseInt(f[i]);
|
||||
if (v >= 128 * 128 * 128 || v < 0)
|
||||
throw new IllegalArgumentException();
|
||||
if (v >= 128 * 128)
|
||||
baos.write((v >> 14) | 0x80);
|
||||
if (v >= 128)
|
||||
baos.write((v >> 7) | 0x80);
|
||||
baos.write(v & 0x7f);
|
||||
}
|
||||
byte[] rv = baos.toByteArray();
|
||||
if (rv.length > 129)
|
||||
throw new IllegalArgumentException();
|
||||
rv[1] = (byte) (rv.length - 2);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/****
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
test("test0", SigType.DSA_SHA1);
|
||||
test("test1", SigType.ECDSA_SHA256_P256);
|
||||
test("test2", SigType.ECDSA_SHA384_P384);
|
||||
test("test3", SigType.ECDSA_SHA512_P521);
|
||||
test("test4", SigType.RSA_SHA256_2048);
|
||||
test("test5", SigType.RSA_SHA384_3072);
|
||||
test("test6", SigType.RSA_SHA512_4096);
|
||||
test("test7", SigType.EdDSA_SHA512_Ed25519);
|
||||
test("test8", SigType.EdDSA_SHA512_Ed25519ph);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static final void test(String name, SigType type) throws Exception {
|
||||
Object[] rv = generate("cname", "ou", "l", "o", "st", "c", 3652, type);
|
||||
PublicKey jpub = (PublicKey) rv[0];
|
||||
PrivateKey jpriv = (PrivateKey) rv[1];
|
||||
X509Certificate cert = (X509Certificate) rv[2];
|
||||
X509CRL crl = (X509CRL) rv[3];
|
||||
File ks = new File(name + ".ks");
|
||||
List<X509Certificate> certs = new ArrayList<X509Certificate>(1);
|
||||
certs.add(cert);
|
||||
KeyStoreUtil.storePrivateKey(ks, "changeit", "foo", "foobar", jpriv, certs);
|
||||
System.out.println("Private key saved to " + ks + " with alias foo, password foobar, keystore password changeit");
|
||||
File cf = new File(name + ".crt");
|
||||
CertUtil.saveCert(cert, cf);
|
||||
System.out.println("Certificate saved to " + cf);
|
||||
File pf = new File(name + ".priv");
|
||||
FileOutputStream pfs = new SecureFileOutputStream(pf);
|
||||
KeyStoreUtil.exportPrivateKey(ks, "changeit", "foo", "foobar", pfs);
|
||||
pfs.close();
|
||||
System.out.println("Private key saved to " + pf);
|
||||
File cr = new File(name + ".crl");
|
||||
CertUtil.saveCRL(crl, cr);
|
||||
System.out.println("CRL saved to " + cr);
|
||||
}
|
||||
****/
|
||||
}
|
@ -10,7 +10,15 @@ public enum SigAlgo {
|
||||
DSA("DSA"),
|
||||
EC("EC"),
|
||||
EdDSA("EdDSA"),
|
||||
RSA("RSA")
|
||||
/**
|
||||
* For local use only, not for use in the network.
|
||||
*/
|
||||
RSA("RSA"),
|
||||
/**
|
||||
* For local use only, not for use in the network.
|
||||
* @since 0.9.25
|
||||
*/
|
||||
ElGamal("ElGamal")
|
||||
;
|
||||
|
||||
private final String name;
|
||||
|
@ -32,20 +32,20 @@ public enum SigType {
|
||||
* Pubkey 128 bytes; privkey 20 bytes; hash 20 bytes; sig 40 bytes
|
||||
* @since 0.9.8
|
||||
*/
|
||||
DSA_SHA1(0, 128, 20, 20, 40, SigAlgo.DSA, "SHA-1", "SHA1withDSA", CryptoConstants.DSA_SHA1_SPEC, "0"),
|
||||
DSA_SHA1(0, 128, 20, 20, 40, SigAlgo.DSA, "SHA-1", "SHA1withDSA", CryptoConstants.DSA_SHA1_SPEC, "1.2.840.10040.4.3", "0"),
|
||||
/** Pubkey 64 bytes; privkey 32 bytes; hash 32 bytes; sig 64 bytes */
|
||||
ECDSA_SHA256_P256(1, 64, 32, 32, 64, SigAlgo.EC, "SHA-256", "SHA256withECDSA", ECConstants.P256_SPEC, "0.9.12"),
|
||||
ECDSA_SHA256_P256(1, 64, 32, 32, 64, SigAlgo.EC, "SHA-256", "SHA256withECDSA", ECConstants.P256_SPEC, "1.2.840.10045.4.3.2", "0.9.12"),
|
||||
/** Pubkey 96 bytes; privkey 48 bytes; hash 48 bytes; sig 96 bytes */
|
||||
ECDSA_SHA384_P384(2, 96, 48, 48, 96, SigAlgo.EC, "SHA-384", "SHA384withECDSA", ECConstants.P384_SPEC, "0.9.12"),
|
||||
ECDSA_SHA384_P384(2, 96, 48, 48, 96, SigAlgo.EC, "SHA-384", "SHA384withECDSA", ECConstants.P384_SPEC, "1.2.840.10045.4.3.3", "0.9.12"),
|
||||
/** Pubkey 132 bytes; privkey 66 bytes; hash 64 bytes; sig 132 bytes */
|
||||
ECDSA_SHA512_P521(3, 132, 66, 64, 132, SigAlgo.EC, "SHA-512", "SHA512withECDSA", ECConstants.P521_SPEC, "0.9.12"),
|
||||
ECDSA_SHA512_P521(3, 132, 66, 64, 132, SigAlgo.EC, "SHA-512", "SHA512withECDSA", ECConstants.P521_SPEC, "1.2.840.10045.4.3.4", "0.9.12"),
|
||||
|
||||
/** Pubkey 256 bytes; privkey 512 bytes; hash 32 bytes; sig 256 bytes */
|
||||
RSA_SHA256_2048(4, 256, 512, 32, 256, SigAlgo.RSA, "SHA-256", "SHA256withRSA", RSAConstants.F4_2048_SPEC, "0.9.12"),
|
||||
RSA_SHA256_2048(4, 256, 512, 32, 256, SigAlgo.RSA, "SHA-256", "SHA256withRSA", RSAConstants.F4_2048_SPEC, "1.2.840.113549.1.1.11", "0.9.12"),
|
||||
/** Pubkey 384 bytes; privkey 768 bytes; hash 48 bytes; sig 384 bytes */
|
||||
RSA_SHA384_3072(5, 384, 768, 48, 384, SigAlgo.RSA, "SHA-384", "SHA384withRSA", RSAConstants.F4_3072_SPEC, "0.9.12"),
|
||||
RSA_SHA384_3072(5, 384, 768, 48, 384, SigAlgo.RSA, "SHA-384", "SHA384withRSA", RSAConstants.F4_3072_SPEC, "1.2.840.113549.1.1.12", "0.9.12"),
|
||||
/** Pubkey 512 bytes; privkey 1024 bytes; hash 64 bytes; sig 512 bytes */
|
||||
RSA_SHA512_4096(6, 512, 1024, 64, 512, SigAlgo.RSA, "SHA-512", "SHA512withRSA", RSAConstants.F4_4096_SPEC, "0.9.12"),
|
||||
RSA_SHA512_4096(6, 512, 1024, 64, 512, SigAlgo.RSA, "SHA-512", "SHA512withRSA", RSAConstants.F4_4096_SPEC, "1.2.840.113549.1.1.13", "0.9.12"),
|
||||
|
||||
/**
|
||||
* Pubkey 32 bytes; privkey 32 bytes; hash 64 bytes; sig 64 bytes
|
||||
@ -55,8 +55,17 @@ public enum SigType {
|
||||
* @since 0.9.15
|
||||
*/
|
||||
EdDSA_SHA512_Ed25519(7, 32, 32, 64, 64, SigAlgo.EdDSA, "SHA-512", "SHA512withEdDSA",
|
||||
EdDSANamedCurveTable.getByName("ed25519-sha-512"), "0.9.17");
|
||||
EdDSANamedCurveTable.getByName("ed25519-sha-512"), "1.3.101.101", "0.9.17"),
|
||||
|
||||
/**
|
||||
* Prehash version (double hashing, for offline use such as su3, not for use on the network)
|
||||
* Pubkey 32 bytes; privkey 32 bytes; hash 64 bytes; sig 64 bytes
|
||||
* @since 0.9.25
|
||||
*/
|
||||
EdDSA_SHA512_Ed25519ph(8, 32, 32, 64, 64, SigAlgo.EdDSA, "SHA-512", "NonewithEdDSA",
|
||||
EdDSANamedCurveTable.getByName("ed25519-sha-512"), "1.3.101.101", "0.9.25"),
|
||||
|
||||
;
|
||||
|
||||
// TESTING....................
|
||||
|
||||
@ -99,12 +108,12 @@ public enum SigType {
|
||||
|
||||
private final int code, pubkeyLen, privkeyLen, hashLen, sigLen;
|
||||
private final SigAlgo base;
|
||||
private final String digestName, algoName, since;
|
||||
private final String digestName, algoName, oid, since;
|
||||
private final AlgorithmParameterSpec params;
|
||||
private final boolean isAvail;
|
||||
|
||||
SigType(int cod, int pubLen, int privLen, int hLen, int sLen, SigAlgo baseAlgo,
|
||||
String mdName, String aName, AlgorithmParameterSpec pSpec, String supportedSince) {
|
||||
String mdName, String aName, AlgorithmParameterSpec pSpec, String oid, String supportedSince) {
|
||||
code = cod;
|
||||
pubkeyLen = pubLen;
|
||||
privkeyLen = privLen;
|
||||
@ -114,6 +123,7 @@ public enum SigType {
|
||||
digestName = mdName;
|
||||
algoName = aName;
|
||||
params = pSpec;
|
||||
this.oid = oid;
|
||||
since = supportedSince;
|
||||
isAvail = x_isAvailable();
|
||||
}
|
||||
@ -183,6 +193,15 @@ public enum SigType {
|
||||
return since;
|
||||
}
|
||||
|
||||
/**
|
||||
* The OID for the signature.
|
||||
*
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public String getOID() {
|
||||
return oid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.12
|
||||
* @return true if supported in this JVM
|
||||
@ -274,6 +293,8 @@ public enum SigType {
|
||||
// handle mixed-case enum
|
||||
if (uc.equals("EDDSA_SHA512_ED25519"))
|
||||
return EdDSA_SHA512_Ed25519;
|
||||
if (uc.equals("EDDSA_SHA512_ED25519PH"))
|
||||
return EdDSA_SHA512_Ed25519ph;
|
||||
return valueOf(uc);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
try {
|
||||
|
@ -50,7 +50,7 @@ import net.i2p.util.NativeBigInteger;
|
||||
*
|
||||
* @since 0.9.9, public since 0.9.12
|
||||
*/
|
||||
public class SigUtil {
|
||||
public final class SigUtil {
|
||||
|
||||
private static final Map<SigningPublicKey, ECPublicKey> _ECPubkeyCache = new LHMCache<SigningPublicKey, ECPublicKey>(64);
|
||||
private static final Map<SigningPrivateKey, ECPrivateKey> _ECPrivkeyCache = new LHMCache<SigningPrivateKey, ECPrivateKey>(16);
|
||||
@ -141,7 +141,7 @@ public class SigUtil {
|
||||
throw new IllegalArgumentException("Unknown RSA type");
|
||||
return fromJavaKey(k, type);
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown type");
|
||||
throw new IllegalArgumentException("Unknown type: " + pk.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -161,7 +161,7 @@ public class SigUtil {
|
||||
case RSA:
|
||||
return fromJavaKey((RSAPublicKey) pk, type);
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
throw new IllegalArgumentException("Unknown type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,7 +209,7 @@ public class SigUtil {
|
||||
throw new IllegalArgumentException("Unknown RSA type");
|
||||
return fromJavaKey(k, type);
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown type");
|
||||
throw new IllegalArgumentException("Unknown type: " + pk.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -229,7 +229,7 @@ public class SigUtil {
|
||||
case RSA:
|
||||
return fromJavaKey((RSAPrivateKey) pk, type);
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
throw new IllegalArgumentException("Unknown type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
@ -549,9 +549,13 @@ public class SigUtil {
|
||||
|
||||
/**
|
||||
* Split a byte array into two BigIntegers
|
||||
* @param b length must be even
|
||||
* @return array of two BigIntegers
|
||||
* @since 0.9.9
|
||||
*/
|
||||
private static BigInteger[] split(byte[] b) {
|
||||
private static NativeBigInteger[] split(byte[] b) {
|
||||
if ((b.length & 0x01) != 0)
|
||||
throw new IllegalArgumentException("length must be even");
|
||||
int sublen = b.length / 2;
|
||||
byte[] bx = new byte[sublen];
|
||||
byte[] by = new byte[sublen];
|
||||
@ -565,9 +569,12 @@ public class SigUtil {
|
||||
/**
|
||||
* Combine two BigIntegers of nominal length = len / 2
|
||||
* @return array of exactly len bytes
|
||||
* @since 0.9.9
|
||||
*/
|
||||
private static byte[] combine(BigInteger x, BigInteger y, int len)
|
||||
throws InvalidKeyException {
|
||||
if ((len & 0x01) != 0)
|
||||
throw new InvalidKeyException("length must be even");
|
||||
int sublen = len / 2;
|
||||
byte[] b = new byte[len];
|
||||
byte[] bx = rectify(x, sublen);
|
||||
@ -609,7 +616,8 @@ public class SigUtil {
|
||||
|
||||
/**
|
||||
* http://download.oracle.com/javase/1.5.0/docs/guide/security/CryptoSpec.html
|
||||
* Signature Format ASN.1 sequence of two INTEGER values: r and s, in that order:
|
||||
*<pre>
|
||||
* Signature Format: ASN.1 sequence of two INTEGER values: r and s, in that order:
|
||||
* SEQUENCE ::= { r INTEGER, s INTEGER }
|
||||
*
|
||||
* http://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One
|
||||
@ -619,6 +627,7 @@ public class SigUtil {
|
||||
* 02 -- tag indicating INTEGER
|
||||
* xx - length in octets
|
||||
* xxxxxx - value
|
||||
*</pre>
|
||||
*
|
||||
* Convert to BigInteger and back so we have the minimum length representation, as required.
|
||||
* r and s are always non-negative.
|
||||
@ -626,46 +635,72 @@ public class SigUtil {
|
||||
* Only supports sigs up to about 252 bytes. See code to fix BER encoding for this before you
|
||||
* add a SigType with bigger signatures.
|
||||
*
|
||||
* @param sig length must be even
|
||||
* @throws IllegalArgumentException if too big
|
||||
* @since 0.8.7, moved to SigUtil in 0.9.9
|
||||
*/
|
||||
private static byte[] sigBytesToASN1(byte[] sig) {
|
||||
//System.out.println("pre TO asn1\n" + net.i2p.util.HexDump.dump(sig));
|
||||
int len = sig.length;
|
||||
int sublen = len / 2;
|
||||
byte[] tmp = new byte[sublen];
|
||||
BigInteger[] rs = split(sig);
|
||||
return sigBytesToASN1(rs[0], rs[1]);
|
||||
}
|
||||
|
||||
System.arraycopy(sig, 0, tmp, 0, sublen);
|
||||
BigInteger r = new BigInteger(1, tmp);
|
||||
/**
|
||||
* http://download.oracle.com/javase/1.5.0/docs/guide/security/CryptoSpec.html
|
||||
*<pre>
|
||||
* Signature Format: ASN.1 sequence of two INTEGER values: r and s, in that order:
|
||||
* SEQUENCE ::= { r INTEGER, s INTEGER }
|
||||
*
|
||||
* http://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One
|
||||
* 30 -- tag indicating SEQUENCE
|
||||
* xx - length in octets
|
||||
*
|
||||
* 02 -- tag indicating INTEGER
|
||||
* xx - length in octets
|
||||
* xxxxxx - value
|
||||
*</pre>
|
||||
*
|
||||
* r and s are always non-negative.
|
||||
*
|
||||
* Only supports sigs up to about 65530 bytes. See code to fix BER encoding for this before you
|
||||
* add a SigType with bigger signatures.
|
||||
*
|
||||
* @throws IllegalArgumentException if too big
|
||||
* @since 0.9.25, split out from sigBytesToASN1(byte[])
|
||||
*/
|
||||
public static byte[] sigBytesToASN1(BigInteger r, BigInteger s) {
|
||||
int extra = 4;
|
||||
byte[] rb = r.toByteArray();
|
||||
if (rb.length > 127)
|
||||
throw new IllegalArgumentException("FIXME R length > 127");
|
||||
System.arraycopy(sig, sublen, tmp, 0, sublen);
|
||||
BigInteger s = new BigInteger(1, tmp);
|
||||
if (rb.length > 127) {
|
||||
extra++;
|
||||
if (rb.length > 255)
|
||||
extra++;
|
||||
}
|
||||
byte[] sb = s.toByteArray();
|
||||
if (sb.length > 127)
|
||||
throw new IllegalArgumentException("FIXME S length > 127");
|
||||
int seqlen = rb.length + sb.length + 4;
|
||||
if (seqlen > 255)
|
||||
throw new IllegalArgumentException("FIXME seq length > 255");
|
||||
if (sb.length > 127) {
|
||||
extra++;
|
||||
if (sb.length > 255)
|
||||
extra++;
|
||||
}
|
||||
int seqlen = rb.length + sb.length + extra;
|
||||
int totlen = seqlen + 2;
|
||||
if (seqlen > 127)
|
||||
if (seqlen > 127) {
|
||||
totlen++;
|
||||
if (seqlen > 255)
|
||||
totlen++;
|
||||
}
|
||||
byte[] rv = new byte[totlen];
|
||||
int idx = 0;
|
||||
|
||||
rv[idx++] = 0x30;
|
||||
if (seqlen > 127)
|
||||
rv[idx++] =(byte) 0x81;
|
||||
rv[idx++] = (byte) seqlen;
|
||||
idx = intToASN1(rv, idx, seqlen);
|
||||
|
||||
rv[idx++] = 0x02;
|
||||
rv[idx++] = (byte) rb.length;
|
||||
idx = intToASN1(rv, idx, rb.length);
|
||||
System.arraycopy(rb, 0, rv, idx, rb.length);
|
||||
idx += rb.length;
|
||||
|
||||
rv[idx++] = 0x02;
|
||||
rv[idx++] = (byte) sb.length;
|
||||
idx = intToASN1(rv, idx, sb.length);
|
||||
System.arraycopy(sb, 0, rv, idx, sb.length);
|
||||
|
||||
//System.out.println("post TO asn1\n" + net.i2p.util.HexDump.dump(rv));
|
||||
@ -673,10 +708,34 @@ public class SigUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* See above.
|
||||
* Only supports sigs up to about 252 bytes. See code to fix BER encoding for bigger than that.
|
||||
* Output an length or integer value in ASN.1
|
||||
* Does NOT output the tag e.g. 0x02 / 0x30
|
||||
*
|
||||
* @return len bytes
|
||||
* @param val 0-65535
|
||||
* @return the new index
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public static int intToASN1(byte[] d, int idx, int val) {
|
||||
if (val < 0 || val > 65535)
|
||||
throw new IllegalArgumentException("fixme length " + val);
|
||||
if (val > 127) {
|
||||
if (val > 255) {
|
||||
d[idx++] = (byte) 0x82;
|
||||
d[idx++] = (byte) (val >> 8);
|
||||
} else {
|
||||
d[idx++] = (byte) 0x81;
|
||||
}
|
||||
}
|
||||
d[idx++] = (byte) val;
|
||||
return idx;
|
||||
}
|
||||
|
||||
/**
|
||||
* See above.
|
||||
* Only supports sigs up to about 65530 bytes. See code to fix BER encoding for bigger than that.
|
||||
*
|
||||
* @param len must be even, twice the nominal length of each BigInteger
|
||||
* @return len bytes, call split() on the result to get two BigIntegers
|
||||
* @since 0.8.7, moved to SigUtil in 0.9.9
|
||||
*/
|
||||
private static byte[] aSN1ToSigBytes(byte[] asn, int len)
|
||||
@ -693,8 +752,17 @@ public class SigUtil {
|
||||
byte[] rv = new byte[len];
|
||||
int sublen = len / 2;
|
||||
int rlen = asn[++idx];
|
||||
if ((rlen & 0x80) != 0)
|
||||
throw new SignatureException("FIXME R length > 127");
|
||||
if ((rlen & 0x80) != 0) {
|
||||
if ((rlen & 0xff) == 0x81) {
|
||||
rlen = asn[++idx] & 0xff;
|
||||
} else if ((rlen & 0xff) == 0x82) {
|
||||
rlen = asn[++idx] & 0xff;
|
||||
rlen <<= 8;
|
||||
rlen |= asn[++idx] & 0xff;
|
||||
} else {
|
||||
throw new SignatureException("FIXME R length > 65535");
|
||||
}
|
||||
}
|
||||
if ((asn[++idx] & 0x80) != 0)
|
||||
throw new SignatureException("R is negative");
|
||||
if (rlen > sublen + 1)
|
||||
@ -704,24 +772,47 @@ public class SigUtil {
|
||||
else
|
||||
System.arraycopy(asn, idx, rv, sublen - rlen, rlen);
|
||||
idx += rlen;
|
||||
int slenloc = idx + 1;
|
||||
|
||||
if (asn[idx] != 0x02)
|
||||
throw new SignatureException("asn[s] = " + (asn[idx] & 0xff));
|
||||
int slen = asn[slenloc];
|
||||
if ((slen & 0x80) != 0)
|
||||
throw new SignatureException("FIXME S length > 127");
|
||||
if ((asn[slenloc + 1] & 0x80) != 0)
|
||||
int slen = asn[++idx];
|
||||
if ((slen & 0x80) != 0) {
|
||||
if ((slen & 0xff) == 0x81) {
|
||||
slen = asn[++idx] & 0xff;
|
||||
} else if ((slen & 0xff) == 0x82) {
|
||||
slen = asn[++idx] & 0xff;
|
||||
slen <<= 8;
|
||||
slen |= asn[++idx] & 0xff;
|
||||
} else {
|
||||
throw new SignatureException("FIXME S length > 65535");
|
||||
}
|
||||
}
|
||||
if ((asn[++idx] & 0x80) != 0)
|
||||
throw new SignatureException("S is negative");
|
||||
if (slen > sublen + 1)
|
||||
throw new SignatureException("S too big " + slen);
|
||||
if (slen == sublen + 1)
|
||||
System.arraycopy(asn, slenloc + 2, rv, sublen, sublen);
|
||||
System.arraycopy(asn, idx + 1, rv, sublen, sublen);
|
||||
else
|
||||
System.arraycopy(asn, slenloc + 1, rv, len - slen, slen);
|
||||
System.arraycopy(asn, idx, rv, len - slen, slen);
|
||||
//System.out.println("post from asn1\n" + net.i2p.util.HexDump.dump(rv));
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* See above.
|
||||
* Only supports sigs up to about 65530 bytes. See code to fix BER encoding for bigger than that.
|
||||
*
|
||||
* @param len nominal length of each BigInteger
|
||||
* @return two BigIntegers
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public static NativeBigInteger[] aSN1ToBigInteger(byte[] asn, int len)
|
||||
throws SignatureException {
|
||||
byte[] sig = aSN1ToSigBytes(asn, len * 2);
|
||||
return split(sig);
|
||||
}
|
||||
|
||||
public static void clearCaches() {
|
||||
synchronized(_ECPubkeyCache) {
|
||||
_ECPubkeyCache.clear();
|
||||
|
@ -35,7 +35,7 @@ import net.i2p.util.SystemVersion;
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
class YKGenerator {
|
||||
final class YKGenerator {
|
||||
//private final static Log _log = new Log(YKGenerator.class);
|
||||
private final int MIN_NUM_BUILDERS;
|
||||
private final int MAX_NUM_BUILDERS;
|
||||
|
@ -2,6 +2,7 @@ package net.i2p.crypto.eddsa;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
@ -9,6 +10,7 @@ import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
import java.util.Arrays;
|
||||
|
||||
import net.i2p.crypto.eddsa.math.Curve;
|
||||
@ -16,21 +18,68 @@ import net.i2p.crypto.eddsa.math.GroupElement;
|
||||
import net.i2p.crypto.eddsa.math.ScalarOps;
|
||||
|
||||
/**
|
||||
* Signing and verification for EdDSA.
|
||||
*<p>
|
||||
* The EdDSA sign and verify algorithms do not interact well with
|
||||
* the Java Signature API, as one or more update() methods must be
|
||||
* called before sign() or verify(). Using the standard API,
|
||||
* this implementation must copy and buffer all data passed in
|
||||
* via update().
|
||||
*</p><p>
|
||||
* This implementation offers two ways to avoid this copying,
|
||||
* but only if all data to be signed or verified is available
|
||||
* in a single byte array.
|
||||
*</p><p>
|
||||
*Option 1:
|
||||
*</p><ol>
|
||||
*<li>Call initSign() or initVerify() as usual.
|
||||
*</li><li>Call setParameter(ONE_SHOT_MOE)
|
||||
*</li><li>Call update(byte[]) or update(byte[], int, int) exactly once
|
||||
*</li><li>Call sign() or verify() as usual.
|
||||
*</li><li>If doing additional one-shot signs or verifies with this object, you must
|
||||
* call setParameter(ONE_SHOT_MODE) each time
|
||||
*</li></ol>
|
||||
*
|
||||
*<p>
|
||||
*Option 2:
|
||||
*</p><ol>
|
||||
*<li>Call initSign() or initVerify() as usual.
|
||||
*</li><li>Call one of the signOneShot() or verifyOneShot() methods.
|
||||
*</li><li>If doing additional one-shot signs or verifies with this object,
|
||||
* just call signOneShot() or verifyOneShot() again.
|
||||
*</li></ol>
|
||||
*
|
||||
* @since 0.9.15
|
||||
* @author str4d
|
||||
*
|
||||
*/
|
||||
public class EdDSAEngine extends Signature {
|
||||
public final class EdDSAEngine extends Signature {
|
||||
private MessageDigest digest;
|
||||
private final ByteArrayOutputStream baos;
|
||||
private ByteArrayOutputStream baos;
|
||||
private EdDSAKey key;
|
||||
private boolean oneShotMode;
|
||||
private byte[] oneShotBytes;
|
||||
private int oneShotOffset;
|
||||
private int oneShotLength;
|
||||
|
||||
/**
|
||||
* To efficiently sign or verify data in one shot, pass this to setParameters()
|
||||
* after initSign() or initVerify() but BEFORE THE FIRST AND ONLY
|
||||
* update(data) or update(data, off, len). The data reference will be saved
|
||||
* and then used in sign() or verify() without copying the data.
|
||||
* Violate these rules and you will get a SignatureException.
|
||||
*
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public static final AlgorithmParameterSpec ONE_SHOT_MODE = new OneShotSpec();
|
||||
|
||||
private static class OneShotSpec implements AlgorithmParameterSpec {}
|
||||
|
||||
/**
|
||||
* No specific hash requested, allows any EdDSA key.
|
||||
*/
|
||||
public EdDSAEngine() {
|
||||
super("EdDSA");
|
||||
baos = new ByteArrayOutputStream(256);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -42,12 +91,21 @@ public class EdDSAEngine extends Signature {
|
||||
this.digest = digest;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException {
|
||||
/**
|
||||
* @since 0.9.25
|
||||
*/
|
||||
private void reset() {
|
||||
if (digest != null)
|
||||
digest.reset();
|
||||
baos.reset();
|
||||
if (baos != null)
|
||||
baos.reset();
|
||||
oneShotMode = false;
|
||||
oneShotBytes = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException {
|
||||
reset();
|
||||
if (privateKey instanceof EdDSAPrivateKey) {
|
||||
EdDSAPrivateKey privKey = (EdDSAPrivateKey) privateKey;
|
||||
key = privKey;
|
||||
@ -61,21 +119,22 @@ public class EdDSAEngine extends Signature {
|
||||
}
|
||||
} else if (!key.getParams().getHashAlgorithm().equals(digest.getAlgorithm()))
|
||||
throw new InvalidKeyException("Key hash algorithm does not match chosen digest");
|
||||
digestInitSign(privKey);
|
||||
} else {
|
||||
throw new InvalidKeyException("cannot identify EdDSA private key: " + privateKey.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
// Preparing for hash
|
||||
// r = H(h_b,...,h_2b-1,M)
|
||||
int b = privKey.getParams().getCurve().getField().getb();
|
||||
digest.update(privKey.getH(), b/8, b/4 - b/8);
|
||||
} else
|
||||
throw new InvalidKeyException("cannot identify EdDSA private key.");
|
||||
private void digestInitSign(EdDSAPrivateKey privKey) {
|
||||
// Preparing for hash
|
||||
// r = H(h_b,...,h_2b-1,M)
|
||||
int b = privKey.getParams().getCurve().getField().getb();
|
||||
digest.update(privKey.getH(), b/8, b/4 - b/8);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {
|
||||
if (digest != null)
|
||||
digest.reset();
|
||||
baos.reset();
|
||||
|
||||
reset();
|
||||
if (publicKey instanceof EdDSAPublicKey) {
|
||||
key = (EdDSAPublicKey) publicKey;
|
||||
|
||||
@ -88,34 +147,79 @@ public class EdDSAEngine extends Signature {
|
||||
}
|
||||
} else if (!key.getParams().getHashAlgorithm().equals(digest.getAlgorithm()))
|
||||
throw new InvalidKeyException("Key hash algorithm does not match chosen digest");
|
||||
} else
|
||||
throw new InvalidKeyException("cannot identify EdDSA public key.");
|
||||
} else {
|
||||
throw new InvalidKeyException("cannot identify EdDSA public key: " + publicKey.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SignatureException if in one-shot mode
|
||||
*/
|
||||
@Override
|
||||
protected void engineUpdate(byte b) throws SignatureException {
|
||||
// We need to store the message because it is used in several hashes
|
||||
// XXX Can this be done more efficiently?
|
||||
if (oneShotMode)
|
||||
throw new SignatureException("unsupported in one-shot mode");
|
||||
if (baos == null)
|
||||
baos = new ByteArrayOutputStream(256);
|
||||
baos.write(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SignatureException if one-shot rules are violated
|
||||
*/
|
||||
@Override
|
||||
protected void engineUpdate(byte[] b, int off, int len)
|
||||
throws SignatureException {
|
||||
// We need to store the message because it is used in several hashes
|
||||
// XXX Can this be done more efficiently?
|
||||
baos.write(b, off, len);
|
||||
if (oneShotMode) {
|
||||
if (oneShotBytes != null)
|
||||
throw new SignatureException("update() already called");
|
||||
oneShotBytes = b;
|
||||
oneShotOffset = off;
|
||||
oneShotLength = len;
|
||||
} else {
|
||||
if (baos == null)
|
||||
baos = new ByteArrayOutputStream(256);
|
||||
baos.write(b, off, len);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] engineSign() throws SignatureException {
|
||||
try {
|
||||
return x_engineSign();
|
||||
} finally {
|
||||
reset();
|
||||
// must leave the object ready to sign again with
|
||||
// the same key, as required by the API
|
||||
EdDSAPrivateKey privKey = (EdDSAPrivateKey) key;
|
||||
digestInitSign(privKey);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] x_engineSign() throws SignatureException {
|
||||
Curve curve = key.getParams().getCurve();
|
||||
ScalarOps sc = key.getParams().getScalarOps();
|
||||
byte[] a = ((EdDSAPrivateKey) key).geta();
|
||||
|
||||
byte[] message = baos.toByteArray();
|
||||
byte[] message;
|
||||
int offset, length;
|
||||
if (oneShotMode) {
|
||||
if (oneShotBytes == null)
|
||||
throw new SignatureException("update() not called first");
|
||||
message = oneShotBytes;
|
||||
offset = oneShotOffset;
|
||||
length = oneShotLength;
|
||||
} else {
|
||||
if (baos == null)
|
||||
message = new byte[0];
|
||||
else
|
||||
message = baos.toByteArray();
|
||||
offset = 0;
|
||||
length = message.length;
|
||||
}
|
||||
// r = H(h_b,...,h_2b-1,M)
|
||||
byte[] r = digest.digest(message);
|
||||
digest.update(message, offset, length);
|
||||
byte[] r = digest.digest();
|
||||
|
||||
// r mod l
|
||||
// Reduces r from 64 bytes to 32 bytes
|
||||
@ -128,7 +232,8 @@ public class EdDSAEngine extends Signature {
|
||||
// S = (r + H(Rbar,Abar,M)*a) mod l
|
||||
digest.update(Rbyte);
|
||||
digest.update(((EdDSAPrivateKey) key).getAbyte());
|
||||
byte[] h = digest.digest(message);
|
||||
digest.update(message, offset, length);
|
||||
byte[] h = digest.digest();
|
||||
h = sc.reduce(h);
|
||||
byte[] S = sc.multiplyAndAdd(h, a, r);
|
||||
|
||||
@ -141,6 +246,14 @@ public class EdDSAEngine extends Signature {
|
||||
|
||||
@Override
|
||||
protected boolean engineVerify(byte[] sigBytes) throws SignatureException {
|
||||
try {
|
||||
return x_engineVerify(sigBytes);
|
||||
} finally {
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean x_engineVerify(byte[] sigBytes) throws SignatureException {
|
||||
Curve curve = key.getParams().getCurve();
|
||||
int b = curve.getField().getb();
|
||||
if (sigBytes.length != b/4)
|
||||
@ -150,8 +263,24 @@ public class EdDSAEngine extends Signature {
|
||||
digest.update(sigBytes, 0, b/8);
|
||||
digest.update(((EdDSAPublicKey) key).getAbyte());
|
||||
// h = H(Rbar,Abar,M)
|
||||
byte[] message = baos.toByteArray();
|
||||
byte[] h = digest.digest(message);
|
||||
byte[] message;
|
||||
int offset, length;
|
||||
if (oneShotMode) {
|
||||
if (oneShotBytes == null)
|
||||
throw new SignatureException("update() not called first");
|
||||
message = oneShotBytes;
|
||||
offset = oneShotOffset;
|
||||
length = oneShotLength;
|
||||
} else {
|
||||
if (baos == null)
|
||||
message = new byte[0];
|
||||
else
|
||||
message = baos.toByteArray();
|
||||
offset = 0;
|
||||
length = message.length;
|
||||
}
|
||||
digest.update(message, offset, length);
|
||||
byte[] h = digest.digest();
|
||||
|
||||
// h mod l
|
||||
h = key.getParams().getScalarOps().reduce(h);
|
||||
@ -171,6 +300,140 @@ public class EdDSAEngine extends Signature {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* To efficiently sign all the data in one shot, if it is available,
|
||||
* use this method, which will avoid copying the data.
|
||||
*
|
||||
* Same as:
|
||||
*<pre>
|
||||
* setParameter(ONE_SHOT_MODE)
|
||||
* update(data)
|
||||
* sig = sign()
|
||||
*</pre>
|
||||
*
|
||||
* @throws SignatureException if update() already called
|
||||
* @see #ONE_SHOT_MODE
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public byte[] signOneShot(byte[] data) throws SignatureException {
|
||||
return signOneShot(data, 0, data.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* To efficiently sign all the data in one shot, if it is available,
|
||||
* use this method, which will avoid copying the data.
|
||||
*
|
||||
* Same as:
|
||||
*<pre>
|
||||
* setParameter(ONE_SHOT_MODE)
|
||||
* update(data, off, len)
|
||||
* sig = sign()
|
||||
*</pre>
|
||||
*
|
||||
* @throws SignatureException if update() already called
|
||||
* @see #ONE_SHOT_MODE
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public byte[] signOneShot(byte[] data, int off, int len) throws SignatureException {
|
||||
oneShotMode = true;
|
||||
update(data, off, len);
|
||||
return sign();
|
||||
}
|
||||
|
||||
/**
|
||||
* To efficiently verify all the data in one shot, if it is available,
|
||||
* use this method, which will avoid copying the data.
|
||||
*
|
||||
* Same as:
|
||||
*<pre>
|
||||
* setParameter(ONE_SHOT_MODE)
|
||||
* update(data)
|
||||
* ok = verify(signature)
|
||||
*</pre>
|
||||
*
|
||||
* @throws SignatureException if update() already called
|
||||
* @see #ONE_SHOT_MODE
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public boolean verifyOneShot(byte[] data, byte[] signature) throws SignatureException {
|
||||
return verifyOneShot(data, 0, data.length, signature, 0, signature.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* To efficiently verify all the data in one shot, if it is available,
|
||||
* use this method, which will avoid copying the data.
|
||||
*
|
||||
* Same as:
|
||||
*<pre>
|
||||
* setParameter(ONE_SHOT_MODE)
|
||||
* update(data, off, len)
|
||||
* ok = verify(signature)
|
||||
*</pre>
|
||||
*
|
||||
* @throws SignatureException if update() already called
|
||||
* @see #ONE_SHOT_MODE
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public boolean verifyOneShot(byte[] data, int off, int len, byte[] signature) throws SignatureException {
|
||||
return verifyOneShot(data, off, len, signature, 0, signature.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* To efficiently verify all the data in one shot, if it is available,
|
||||
* use this method, which will avoid copying the data.
|
||||
*
|
||||
* Same as:
|
||||
*<pre>
|
||||
* setParameter(ONE_SHOT_MODE)
|
||||
* update(data)
|
||||
* ok = verify(signature, sigoff, siglen)
|
||||
*</pre>
|
||||
*
|
||||
* @throws SignatureException if update() already called
|
||||
* @see #ONE_SHOT_MODE
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public boolean verifyOneShot(byte[] data, byte[] signature, int sigoff, int siglen) throws SignatureException {
|
||||
return verifyOneShot(data, 0, data.length, signature, sigoff, siglen);
|
||||
}
|
||||
|
||||
/**
|
||||
* To efficiently verify all the data in one shot, if it is available,
|
||||
* use this method, which will avoid copying the data.
|
||||
*
|
||||
* Same as:
|
||||
*<pre>
|
||||
* setParameter(ONE_SHOT_MODE)
|
||||
* update(data, off, len)
|
||||
* ok = verify(signature, sigoff, siglen)
|
||||
*</pre>
|
||||
*
|
||||
* @throws SignatureException if update() already called
|
||||
* @see #ONE_SHOT_MODE
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public boolean verifyOneShot(byte[] data, int off, int len, byte[] signature, int sigoff, int siglen) throws SignatureException {
|
||||
oneShotMode = true;
|
||||
update(data, off, len);
|
||||
return verify(signature, sigoff, siglen);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidAlgorithmParameterException if spec is ONE_SHOT_MODE and update() already called
|
||||
* @see #ONE_SHOT_MODE
|
||||
* @since 0.9.25
|
||||
*/
|
||||
@Override
|
||||
protected void engineSetParameter(AlgorithmParameterSpec spec) throws InvalidAlgorithmParameterException {
|
||||
if (spec.equals(ONE_SHOT_MODE)) {
|
||||
if (oneShotBytes != null || baos != null && baos.size() > 0)
|
||||
throw new InvalidAlgorithmParameterException("update() already called");
|
||||
oneShotMode = true;
|
||||
} else {
|
||||
super.engineSetParameter(spec);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated replaced with <a href="#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
|
||||
*/
|
||||
|
@ -1,13 +1,24 @@
|
||||
package net.i2p.crypto.eddsa;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.util.Arrays;
|
||||
|
||||
import net.i2p.crypto.eddsa.math.GroupElement;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
|
||||
|
||||
/**
|
||||
* An EdDSA private key.
|
||||
*<p>
|
||||
* Warning: Private key encoding is not fully specified in the
|
||||
* current IETF draft. This implementation uses PKCS#8 encoding,
|
||||
* and is subject to change. See getEncoded().
|
||||
*</p><p>
|
||||
* Ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04
|
||||
*</p>
|
||||
*
|
||||
* @since 0.9.15
|
||||
* @author str4d
|
||||
@ -31,6 +42,14 @@ public class EdDSAPrivateKey implements EdDSAKey, PrivateKey {
|
||||
this.edDsaSpec = spec.getParams();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public EdDSAPrivateKey(PKCS8EncodedKeySpec spec) throws InvalidKeySpecException {
|
||||
this(new EdDSAPrivateKeySpec(decode(spec.getEncoded()),
|
||||
EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512)));
|
||||
}
|
||||
|
||||
public String getAlgorithm() {
|
||||
return "EdDSA";
|
||||
}
|
||||
@ -39,9 +58,116 @@ public class EdDSAPrivateKey implements EdDSAKey, PrivateKey {
|
||||
return "PKCS#8";
|
||||
}
|
||||
|
||||
/**
|
||||
* This follows the docs from
|
||||
* java.security.spec.PKCS8EncodedKeySpec
|
||||
* quote:
|
||||
*<pre>
|
||||
* The PrivateKeyInfo syntax is defined in the PKCS#8 standard as follows:
|
||||
* PrivateKeyInfo ::= SEQUENCE {
|
||||
* version Version,
|
||||
* privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
|
||||
* privateKey PrivateKey,
|
||||
* attributes [0] IMPLICIT Attributes OPTIONAL }
|
||||
* Version ::= INTEGER
|
||||
* PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
|
||||
* PrivateKey ::= OCTET STRING
|
||||
* Attributes ::= SET OF Attribute
|
||||
*</pre>
|
||||
*
|
||||
*<pre>
|
||||
* AlgorithmIdentifier ::= SEQUENCE
|
||||
* {
|
||||
* algorithm OBJECT IDENTIFIER,
|
||||
* parameters ANY OPTIONAL
|
||||
* }
|
||||
*</pre>
|
||||
*
|
||||
* Ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04
|
||||
*
|
||||
* Note that the private key encoding is not fully specified in the Josefsson draft version 04,
|
||||
* and the example could be wrong, as it's lacking Version and AlgorithmIdentifier.
|
||||
* This will hopefully be clarified in the next draft.
|
||||
* But sun.security.pkcs.PKCS8Key expects them so we must include them for keytool to work.
|
||||
*
|
||||
* @return 49 bytes for Ed25519, null for other curves
|
||||
* @since implemented in 0.9.25
|
||||
*/
|
||||
public byte[] getEncoded() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
if (!edDsaSpec.equals(EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512)))
|
||||
return null;
|
||||
int totlen = 17 + seed.length;
|
||||
byte[] rv = new byte[totlen];
|
||||
int idx = 0;
|
||||
// sequence
|
||||
rv[idx++] = 0x30;
|
||||
rv[idx++] = (byte) (15 + seed.length);
|
||||
|
||||
// version
|
||||
// not in the Josefsson example
|
||||
rv[idx++] = 0x02;
|
||||
rv[idx++] = 1;
|
||||
rv[idx++] = 0;
|
||||
|
||||
// Algorithm Identifier
|
||||
// sequence
|
||||
// not in the Josefsson example
|
||||
rv[idx++] = 0x30;
|
||||
rv[idx++] = 8;
|
||||
// OID 1.3.101.100
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/bb540809%28v=vs.85%29.aspx
|
||||
// not in the Josefsson example
|
||||
rv[idx++] = 0x06;
|
||||
rv[idx++] = 3;
|
||||
rv[idx++] = (1 * 40) + 3;
|
||||
rv[idx++] = 101;
|
||||
rv[idx++] = 100;
|
||||
// params
|
||||
rv[idx++] = 0x0a;
|
||||
rv[idx++] = 1;
|
||||
rv[idx++] = 1; // Ed25519
|
||||
// the key
|
||||
rv[idx++] = 0x04; // octet string
|
||||
rv[idx++] = (byte) seed.length;
|
||||
System.arraycopy(seed, 0, rv, idx, seed.length);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is really dumb for now.
|
||||
* See getEncoded().
|
||||
*
|
||||
* @return 32 bytes for Ed25519, throws for other curves
|
||||
* @since 0.9.25
|
||||
*/
|
||||
private static byte[] decode(byte[] d) throws InvalidKeySpecException {
|
||||
try {
|
||||
int idx = 0;
|
||||
if (d[idx++] != 0x30 ||
|
||||
d[idx++] != 47 ||
|
||||
d[idx++] != 0x02 ||
|
||||
d[idx++] != 1 ||
|
||||
d[idx++] != 0 ||
|
||||
d[idx++] != 0x30 ||
|
||||
d[idx++] != 8 ||
|
||||
d[idx++] != 0x06 ||
|
||||
d[idx++] != 3 ||
|
||||
d[idx++] != (1 * 40) + 3 ||
|
||||
d[idx++] != 101 ||
|
||||
d[idx++] != 100 ||
|
||||
d[idx++] != 0x0a ||
|
||||
d[idx++] != 1 ||
|
||||
d[idx++] != 1 ||
|
||||
d[idx++] != 0x04 ||
|
||||
d[idx++] != 32) {
|
||||
throw new InvalidKeySpecException("unsupported key spec");
|
||||
}
|
||||
byte[] rv = new byte[32];
|
||||
System.arraycopy(d, idx, rv, 0, 32);
|
||||
return rv;
|
||||
} catch (IndexOutOfBoundsException ioobe) {
|
||||
throw new InvalidKeySpecException(ioobe);
|
||||
}
|
||||
}
|
||||
|
||||
public EdDSAParameterSpec getParams() {
|
||||
@ -67,4 +193,26 @@ public class EdDSAPrivateKey implements EdDSAKey, PrivateKey {
|
||||
public byte[] getAbyte() {
|
||||
return Abyte;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.25
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(seed);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.25
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this)
|
||||
return true;
|
||||
if (!(o instanceof EdDSAPrivateKey))
|
||||
return false;
|
||||
EdDSAPrivateKey pk = (EdDSAPrivateKey) o;
|
||||
return Arrays.equals(seed, pk.getSeed()) &&
|
||||
edDsaSpec.equals(pk.getParams());
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,23 @@
|
||||
package net.i2p.crypto.eddsa;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Arrays;
|
||||
|
||||
import net.i2p.crypto.eddsa.math.GroupElement;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
|
||||
|
||||
/**
|
||||
* An EdDSA public key.
|
||||
*<p>
|
||||
* Warning: Public key encoding is is based on the
|
||||
* current IETF draft, and is subject to change. See getEncoded().
|
||||
*</p><p>
|
||||
* Ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04
|
||||
*</p>
|
||||
*
|
||||
* @since 0.9.15
|
||||
* @author str4d
|
||||
@ -27,6 +37,14 @@ public class EdDSAPublicKey implements EdDSAKey, PublicKey {
|
||||
this.edDsaSpec = spec.getParams();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public EdDSAPublicKey(X509EncodedKeySpec spec) throws InvalidKeySpecException {
|
||||
this(new EdDSAPublicKeySpec(decode(spec.getEncoded()),
|
||||
EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512)));
|
||||
}
|
||||
|
||||
public String getAlgorithm() {
|
||||
return "EdDSA";
|
||||
}
|
||||
@ -35,9 +53,95 @@ public class EdDSAPublicKey implements EdDSAKey, PublicKey {
|
||||
return "X.509";
|
||||
}
|
||||
|
||||
/**
|
||||
* This follows the spec at
|
||||
* ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04
|
||||
* which matches the docs from
|
||||
* java.security.spec.X509EncodedKeySpec
|
||||
* quote:
|
||||
*<pre>
|
||||
* The SubjectPublicKeyInfo syntax is defined in the X.509 standard as follows:
|
||||
* SubjectPublicKeyInfo ::= SEQUENCE {
|
||||
* algorithm AlgorithmIdentifier,
|
||||
* subjectPublicKey BIT STRING }
|
||||
*</pre>
|
||||
*
|
||||
*<pre>
|
||||
* AlgorithmIdentifier ::= SEQUENCE
|
||||
* {
|
||||
* algorithm OBJECT IDENTIFIER,
|
||||
* parameters ANY OPTIONAL
|
||||
* }
|
||||
*</pre>
|
||||
*
|
||||
* @return 47 bytes for Ed25519, null for other curves
|
||||
* @since implemented in 0.9.25
|
||||
*/
|
||||
public byte[] getEncoded() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
if (!edDsaSpec.equals(EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512)))
|
||||
return null;
|
||||
int totlen = 15 + Abyte.length;
|
||||
byte[] rv = new byte[totlen];
|
||||
int idx = 0;
|
||||
// sequence
|
||||
rv[idx++] = 0x30;
|
||||
rv[idx++] = (byte) (13 + Abyte.length);
|
||||
// Algorithm Identifier
|
||||
// sequence
|
||||
rv[idx++] = 0x30;
|
||||
rv[idx++] = 8;
|
||||
// OID 1.3.101.100
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/bb540809%28v=vs.85%29.aspx
|
||||
rv[idx++] = 0x06;
|
||||
rv[idx++] = 3;
|
||||
rv[idx++] = (1 * 40) + 3;
|
||||
rv[idx++] = 101;
|
||||
rv[idx++] = 100;
|
||||
// params
|
||||
rv[idx++] = 0x0a;
|
||||
rv[idx++] = 1;
|
||||
rv[idx++] = 1; // Ed25519
|
||||
// the key
|
||||
rv[idx++] = 0x03; // bit string
|
||||
rv[idx++] = (byte) (1 + Abyte.length);
|
||||
rv[idx++] = 0; // number of trailing unused bits
|
||||
System.arraycopy(Abyte, 0, rv, idx, Abyte.length);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is really dumb for now.
|
||||
* See getEncoded().
|
||||
*
|
||||
* @return 32 bytes for Ed25519, throws for other curves
|
||||
* @since 0.9.25
|
||||
*/
|
||||
private static byte[] decode(byte[] d) throws InvalidKeySpecException {
|
||||
try {
|
||||
int idx = 0;
|
||||
if (d[idx++] != 0x30 ||
|
||||
d[idx++] != 45 ||
|
||||
d[idx++] != 0x30 ||
|
||||
d[idx++] != 8 ||
|
||||
d[idx++] != 0x06 ||
|
||||
d[idx++] != 3 ||
|
||||
d[idx++] != (1 * 40) + 3 ||
|
||||
d[idx++] != 101 ||
|
||||
d[idx++] != 100 ||
|
||||
d[idx++] != 0x0a ||
|
||||
d[idx++] != 1 ||
|
||||
d[idx++] != 1 ||
|
||||
d[idx++] != 0x03 ||
|
||||
d[idx++] != 33 ||
|
||||
d[idx++] != 0) {
|
||||
throw new InvalidKeySpecException("unsupported key spec");
|
||||
}
|
||||
byte[] rv = new byte[32];
|
||||
System.arraycopy(d, idx, rv, 0, 32);
|
||||
return rv;
|
||||
} catch (IndexOutOfBoundsException ioobe) {
|
||||
throw new InvalidKeySpecException(ioobe);
|
||||
}
|
||||
}
|
||||
|
||||
public EdDSAParameterSpec getParams() {
|
||||
@ -55,4 +159,26 @@ public class EdDSAPublicKey implements EdDSAKey, PublicKey {
|
||||
public byte[] getAbyte() {
|
||||
return Abyte;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.25
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(Abyte);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.25
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this)
|
||||
return true;
|
||||
if (!(o instanceof EdDSAPublicKey))
|
||||
return false;
|
||||
EdDSAPublicKey pk = (EdDSAPublicKey) o;
|
||||
return Arrays.equals(Abyte, pk.getAbyte()) &&
|
||||
edDsaSpec.equals(pk.getParams());
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
|
||||
@ -16,22 +18,34 @@ import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
|
||||
* @author str4d
|
||||
*
|
||||
*/
|
||||
public class KeyFactory extends KeyFactorySpi {
|
||||
public final class KeyFactory extends KeyFactorySpi {
|
||||
|
||||
/**
|
||||
* As of 0.9.25, supports PKCS8EncodedKeySpec
|
||||
*/
|
||||
protected PrivateKey engineGeneratePrivate(KeySpec keySpec)
|
||||
throws InvalidKeySpecException {
|
||||
if (keySpec instanceof EdDSAPrivateKeySpec) {
|
||||
return new EdDSAPrivateKey((EdDSAPrivateKeySpec) keySpec);
|
||||
}
|
||||
throw new InvalidKeySpecException("key spec not recognised");
|
||||
if (keySpec instanceof PKCS8EncodedKeySpec) {
|
||||
return new EdDSAPrivateKey((PKCS8EncodedKeySpec) keySpec);
|
||||
}
|
||||
throw new InvalidKeySpecException("key spec not recognised: " + keySpec.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* As of 0.9.25, supports X509EncodedKeySpec
|
||||
*/
|
||||
protected PublicKey engineGeneratePublic(KeySpec keySpec)
|
||||
throws InvalidKeySpecException {
|
||||
if (keySpec instanceof EdDSAPublicKeySpec) {
|
||||
return new EdDSAPublicKey((EdDSAPublicKeySpec) keySpec);
|
||||
}
|
||||
throw new InvalidKeySpecException("key spec not recognised");
|
||||
if (keySpec instanceof X509EncodedKeySpec) {
|
||||
return new EdDSAPublicKey((X509EncodedKeySpec) keySpec);
|
||||
}
|
||||
throw new InvalidKeySpecException("key spec not recognised: " + keySpec.getClass());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -21,7 +21,7 @@ import net.i2p.util.RandomSource;
|
||||
*
|
||||
* @since 0.9.15
|
||||
*/
|
||||
public class KeyPairGenerator extends KeyPairGeneratorSpi {
|
||||
public final class KeyPairGenerator extends KeyPairGeneratorSpi {
|
||||
private static final int DEFAULT_STRENGTH = 256;
|
||||
private EdDSAParameterSpec edParams;
|
||||
private SecureRandom random;
|
||||
|
@ -69,4 +69,29 @@ public class Curve implements Serializable {
|
||||
ge.precompute(true);
|
||||
return ge;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.25
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return f.hashCode() ^
|
||||
d.hashCode() ^
|
||||
I.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.25
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this)
|
||||
return true;
|
||||
if (!(o instanceof Curve))
|
||||
return false;
|
||||
Curve c = (Curve) o;
|
||||
return f.equals(c.getField()) &&
|
||||
d.equals(c.getD()) &&
|
||||
I.equals(c.getI());
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ package net.i2p.crypto.eddsa.math;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
*
|
||||
* Note: concrete subclasses must implement hashCode() and equals()
|
||||
*
|
||||
* @since 0.9.15
|
||||
*
|
||||
@ -60,4 +62,6 @@ public abstract class FieldElement implements Serializable {
|
||||
public abstract FieldElement invert();
|
||||
|
||||
public abstract FieldElement pow22523();
|
||||
|
||||
// Note: concrete subclasses must implement hashCode() and equals()
|
||||
}
|
||||
|
@ -716,6 +716,8 @@ public class GroupElement implements Serializable {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this)
|
||||
return true;
|
||||
if (!(obj instanceof GroupElement))
|
||||
return false;
|
||||
GroupElement ge = (GroupElement) obj;
|
||||
|
@ -15,7 +15,7 @@ public class BigIntegerLittleEndianEncoding extends Encoding implements Serializ
|
||||
private BigInteger mask;
|
||||
|
||||
@Override
|
||||
public void setField(Field f) {
|
||||
public synchronized void setField(Field f) {
|
||||
super.setField(f);
|
||||
mask = BigInteger.ONE.shiftLeft(f.getb()-1).subtract(BigInteger.ONE);
|
||||
}
|
||||
|
@ -59,4 +59,29 @@ public class EdDSAParameterSpec implements AlgorithmParameterSpec, Serializable
|
||||
public GroupElement getB() {
|
||||
return B;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.25
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashAlgo.hashCode() ^
|
||||
curve.hashCode() ^
|
||||
B.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.25
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this)
|
||||
return true;
|
||||
if (!(o instanceof EdDSAParameterSpec))
|
||||
return false;
|
||||
EdDSAParameterSpec s = (EdDSAParameterSpec) o;
|
||||
return hashAlgo.equals(s.getHashAlgorithm()) &&
|
||||
curve.equals(s.getCurve()) &&
|
||||
B.equals(s.getB());
|
||||
}
|
||||
}
|
||||
|
11
core/java/src/net/i2p/crypto/elgamal/ElGamalKey.java
Normal file
11
core/java/src/net/i2p/crypto/elgamal/ElGamalKey.java
Normal file
@ -0,0 +1,11 @@
|
||||
package net.i2p.crypto.elgamal;
|
||||
|
||||
import javax.crypto.interfaces.DHKey;
|
||||
|
||||
import net.i2p.crypto.elgamal.spec.ElGamalParameterSpec;
|
||||
|
||||
public interface ElGamalKey
|
||||
extends DHKey
|
||||
{
|
||||
public ElGamalParameterSpec getParameters();
|
||||
}
|
11
core/java/src/net/i2p/crypto/elgamal/ElGamalPrivateKey.java
Normal file
11
core/java/src/net/i2p/crypto/elgamal/ElGamalPrivateKey.java
Normal file
@ -0,0 +1,11 @@
|
||||
package net.i2p.crypto.elgamal;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import javax.crypto.interfaces.DHPrivateKey;
|
||||
|
||||
public interface ElGamalPrivateKey
|
||||
extends ElGamalKey, DHPrivateKey
|
||||
{
|
||||
public BigInteger getX();
|
||||
}
|
11
core/java/src/net/i2p/crypto/elgamal/ElGamalPublicKey.java
Normal file
11
core/java/src/net/i2p/crypto/elgamal/ElGamalPublicKey.java
Normal file
@ -0,0 +1,11 @@
|
||||
package net.i2p.crypto.elgamal;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import javax.crypto.interfaces.DHPublicKey;
|
||||
|
||||
public interface ElGamalPublicKey
|
||||
extends ElGamalKey, DHPublicKey
|
||||
{
|
||||
public BigInteger getY();
|
||||
}
|
164
core/java/src/net/i2p/crypto/elgamal/ElGamalSigEngine.java
Normal file
164
core/java/src/net/i2p/crypto/elgamal/ElGamalSigEngine.java
Normal file
@ -0,0 +1,164 @@
|
||||
package net.i2p.crypto.elgamal;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import net.i2p.crypto.SHA256Generator;
|
||||
import net.i2p.crypto.SigUtil;
|
||||
import net.i2p.util.NativeBigInteger;
|
||||
import net.i2p.util.RandomSource;
|
||||
|
||||
/**
|
||||
* ElG signatures with SHA-256
|
||||
*
|
||||
* ref: https://en.wikipedia.org/wiki/ElGamal_signature_scheme
|
||||
*
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public final class ElGamalSigEngine extends Signature {
|
||||
private final MessageDigest digest;
|
||||
private ElGamalKey key;
|
||||
|
||||
/**
|
||||
* No specific hash requested, allows any ElGamal key.
|
||||
*/
|
||||
public ElGamalSigEngine() {
|
||||
this(SHA256Generator.getDigestInstance());
|
||||
}
|
||||
|
||||
/**
|
||||
* Specific hash requested, only matching keys will be allowed.
|
||||
* @param digest the hash algorithm that keys must have to sign or verify.
|
||||
*/
|
||||
public ElGamalSigEngine(MessageDigest digest) {
|
||||
super("ElGamal");
|
||||
this.digest = digest;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException {
|
||||
digest.reset();
|
||||
if (privateKey instanceof ElGamalPrivateKey) {
|
||||
ElGamalPrivateKey privKey = (ElGamalPrivateKey) privateKey;
|
||||
key = privKey;
|
||||
} else {
|
||||
throw new InvalidKeyException("cannot identify ElGamal private key: " + privateKey.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {
|
||||
digest.reset();
|
||||
if (publicKey instanceof ElGamalPublicKey) {
|
||||
key = (ElGamalPublicKey) publicKey;
|
||||
} else {
|
||||
throw new InvalidKeyException("cannot identify ElGamal public key: " + publicKey.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineUpdate(byte b) throws SignatureException {
|
||||
digest.update(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineUpdate(byte[] b, int off, int len)
|
||||
throws SignatureException {
|
||||
digest.update(b, off, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ASN.1 R,S
|
||||
*/
|
||||
@Override
|
||||
protected byte[] engineSign() throws SignatureException {
|
||||
BigInteger elgp = key.getParams().getP();
|
||||
BigInteger pm1 = elgp.subtract(BigInteger.ONE);
|
||||
BigInteger elgg = key.getParams().getG();
|
||||
BigInteger x = ((ElGamalPrivateKey) key).getX();
|
||||
if (!(x instanceof NativeBigInteger))
|
||||
x = new NativeBigInteger(x);
|
||||
byte[] data = digest.digest();
|
||||
|
||||
BigInteger k;
|
||||
boolean ok;
|
||||
do {
|
||||
k = new BigInteger(2048, RandomSource.getInstance());
|
||||
ok = k.compareTo(pm1) == -1;
|
||||
ok = ok && k.compareTo(BigInteger.ONE) == 1;
|
||||
ok = ok && k.gcd(pm1).equals(BigInteger.ONE);
|
||||
} while (!ok);
|
||||
|
||||
BigInteger r = elgg.modPow(k, elgp);
|
||||
BigInteger kinv = k.modInverse(pm1);
|
||||
BigInteger h = new NativeBigInteger(1, data);
|
||||
BigInteger s = (kinv.multiply(h.subtract(x.multiply(r)))).mod(pm1);
|
||||
// todo if s == 0 go around again
|
||||
|
||||
byte[] rv;
|
||||
try {
|
||||
rv = SigUtil.sigBytesToASN1(r, s);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
throw new SignatureException("ASN1", iae);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sigBytes ASN.1 R,S
|
||||
*/
|
||||
@Override
|
||||
protected boolean engineVerify(byte[] sigBytes) throws SignatureException {
|
||||
BigInteger elgp = key.getParams().getP();
|
||||
BigInteger pm1 = elgp.subtract(BigInteger.ONE);
|
||||
BigInteger elgg = key.getParams().getG();
|
||||
BigInteger y = ((ElGamalPublicKey) key).getY();
|
||||
if (!(y instanceof NativeBigInteger))
|
||||
y = new NativeBigInteger(y);
|
||||
byte[] data = digest.digest();
|
||||
|
||||
try {
|
||||
BigInteger[] rs = SigUtil.aSN1ToBigInteger(sigBytes, 256);
|
||||
BigInteger r = rs[0];
|
||||
BigInteger s = rs[1];
|
||||
if (r.signum() != 1 || s.signum() != 1 ||
|
||||
r.compareTo(elgp) != -1 || s.compareTo(pm1) != -1)
|
||||
return false;
|
||||
NativeBigInteger h = new NativeBigInteger(1, data);
|
||||
BigInteger modvalr = r.modPow(s, elgp);
|
||||
BigInteger modvaly = y.modPow(r, elgp);
|
||||
BigInteger modmulval = modvalr.multiply(modvaly).mod(elgp);
|
||||
BigInteger v = elgg.modPow(h, elgp);
|
||||
|
||||
boolean ok = v.compareTo(modmulval) == 0;
|
||||
return ok;
|
||||
} catch (RuntimeException e) {
|
||||
throw new SignatureException("verify", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated replaced with <a href="#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">
|
||||
*/
|
||||
@Override
|
||||
protected void engineSetParameter(String param, Object value) {
|
||||
throw new UnsupportedOperationException("engineSetParameter unsupported");
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Override
|
||||
protected Object engineGetParameter(String param) {
|
||||
throw new UnsupportedOperationException("engineSetParameter unsupported");
|
||||
}
|
||||
}
|
80
core/java/src/net/i2p/crypto/elgamal/KeyFactory.java
Normal file
80
core/java/src/net/i2p/crypto/elgamal/KeyFactory.java
Normal file
@ -0,0 +1,80 @@
|
||||
package net.i2p.crypto.elgamal;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyFactorySpi;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
|
||||
import javax.crypto.spec.DHParameterSpec;
|
||||
|
||||
import static net.i2p.crypto.CryptoConstants.I2P_ELGAMAL_2048_SPEC;
|
||||
import net.i2p.crypto.elgamal.impl.ElGamalPrivateKeyImpl;
|
||||
import net.i2p.crypto.elgamal.impl.ElGamalPrivateKeyImpl;
|
||||
import net.i2p.crypto.elgamal.impl.ElGamalPublicKeyImpl;
|
||||
import net.i2p.crypto.elgamal.spec.ElGamalParameterSpec;
|
||||
import net.i2p.crypto.elgamal.spec.ElGamalPrivateKeySpec;
|
||||
import net.i2p.crypto.elgamal.spec.ElGamalPublicKeySpec;
|
||||
|
||||
/**
|
||||
* Modified from eddsa
|
||||
*
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public final class KeyFactory extends KeyFactorySpi {
|
||||
|
||||
/**
|
||||
* Supports PKCS8EncodedKeySpec
|
||||
*/
|
||||
protected PrivateKey engineGeneratePrivate(KeySpec keySpec)
|
||||
throws InvalidKeySpecException {
|
||||
if (keySpec instanceof ElGamalPrivateKeySpec) {
|
||||
return new ElGamalPrivateKeyImpl((ElGamalPrivateKeySpec) keySpec);
|
||||
}
|
||||
if (keySpec instanceof PKCS8EncodedKeySpec) {
|
||||
return new ElGamalPrivateKeyImpl((PKCS8EncodedKeySpec) keySpec);
|
||||
}
|
||||
throw new InvalidKeySpecException("key spec not recognised");
|
||||
}
|
||||
|
||||
/**
|
||||
* Supports X509EncodedKeySpec
|
||||
*/
|
||||
protected PublicKey engineGeneratePublic(KeySpec keySpec)
|
||||
throws InvalidKeySpecException {
|
||||
if (keySpec instanceof ElGamalPublicKeySpec) {
|
||||
return new ElGamalPublicKeyImpl((ElGamalPublicKeySpec) keySpec);
|
||||
}
|
||||
if (keySpec instanceof X509EncodedKeySpec) {
|
||||
return new ElGamalPublicKeyImpl((X509EncodedKeySpec) keySpec);
|
||||
}
|
||||
throw new InvalidKeySpecException("key spec not recognised");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpec)
|
||||
throws InvalidKeySpecException {
|
||||
if (keySpec.isAssignableFrom(ElGamalPublicKeySpec.class) && key instanceof ElGamalPublicKey) {
|
||||
ElGamalPublicKey k = (ElGamalPublicKey) key;
|
||||
ElGamalParameterSpec egp = k.getParameters();
|
||||
if (egp != null) {
|
||||
return (T) new ElGamalPrivateKeySpec(k.getY(), egp);
|
||||
}
|
||||
} else if (keySpec.isAssignableFrom(ElGamalPrivateKeySpec.class) && key instanceof ElGamalPrivateKey) {
|
||||
ElGamalPrivateKey k = (ElGamalPrivateKey) key;
|
||||
ElGamalParameterSpec egp = k.getParameters();
|
||||
if (egp != null) {
|
||||
return (T) new ElGamalPrivateKeySpec(k.getX(), egp);
|
||||
}
|
||||
}
|
||||
throw new InvalidKeySpecException("not implemented yet " + key + " " + keySpec);
|
||||
}
|
||||
|
||||
protected Key engineTranslateKey(Key key) throws InvalidKeyException {
|
||||
throw new InvalidKeyException("No other ElGamal key providers known");
|
||||
}
|
||||
}
|
84
core/java/src/net/i2p/crypto/elgamal/KeyPairGenerator.java
Normal file
84
core/java/src/net/i2p/crypto/elgamal/KeyPairGenerator.java
Normal file
@ -0,0 +1,84 @@
|
||||
package net.i2p.crypto.elgamal;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidParameterException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGeneratorSpi;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
|
||||
import static net.i2p.crypto.CryptoConstants.I2P_ELGAMAL_2048_SPEC;
|
||||
import net.i2p.crypto.KeyGenerator;
|
||||
import net.i2p.crypto.elgamal.impl.ElGamalPrivateKeyImpl;
|
||||
import net.i2p.crypto.elgamal.impl.ElGamalPublicKeyImpl;
|
||||
import net.i2p.crypto.elgamal.spec.ElGamalGenParameterSpec;
|
||||
import net.i2p.crypto.elgamal.spec.ElGamalParameterSpec;
|
||||
import net.i2p.crypto.elgamal.spec.ElGamalPrivateKeySpec;
|
||||
import net.i2p.crypto.elgamal.spec.ElGamalPublicKeySpec;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SimpleDataStructure;
|
||||
import net.i2p.util.NativeBigInteger;
|
||||
import net.i2p.util.RandomSource;
|
||||
|
||||
/**
|
||||
* Modified from eddsa
|
||||
* Only supported strength is 2048
|
||||
*
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public final class KeyPairGenerator extends KeyPairGeneratorSpi {
|
||||
// always long, don't use short key
|
||||
private static final int DEFAULT_STRENGTH = 2048;
|
||||
private ElGamalParameterSpec elgParams;
|
||||
//private SecureRandom random;
|
||||
private boolean initialized;
|
||||
|
||||
/**
|
||||
* @param strength must be 2048
|
||||
* @param random ignored
|
||||
*/
|
||||
public void initialize(int strength, SecureRandom random) {
|
||||
if (strength != DEFAULT_STRENGTH)
|
||||
throw new InvalidParameterException("unknown key type.");
|
||||
elgParams = I2P_ELGAMAL_2048_SPEC;
|
||||
try {
|
||||
initialize(elgParams, random);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
throw new InvalidParameterException("key type not configurable.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param random ignored
|
||||
*/
|
||||
@Override
|
||||
public void initialize(AlgorithmParameterSpec params, SecureRandom random) throws InvalidAlgorithmParameterException {
|
||||
if (params instanceof ElGamalParameterSpec) {
|
||||
elgParams = (ElGamalParameterSpec) params;
|
||||
if (!elgParams.equals(I2P_ELGAMAL_2048_SPEC))
|
||||
throw new InvalidAlgorithmParameterException("unsupported ElGamalParameterSpec");
|
||||
} else if (params instanceof ElGamalGenParameterSpec) {
|
||||
ElGamalGenParameterSpec elgGPS = (ElGamalGenParameterSpec) params;
|
||||
if (elgGPS.getPrimeSize() != DEFAULT_STRENGTH)
|
||||
throw new InvalidAlgorithmParameterException("unsupported prime size");
|
||||
elgParams = I2P_ELGAMAL_2048_SPEC;
|
||||
} else {
|
||||
throw new InvalidAlgorithmParameterException("parameter object not a ElGamalParameterSpec");
|
||||
}
|
||||
//this.random = random;
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
public KeyPair generateKeyPair() {
|
||||
if (!initialized)
|
||||
initialize(DEFAULT_STRENGTH, RandomSource.getInstance());
|
||||
KeyGenerator kg = KeyGenerator.getInstance();
|
||||
SimpleDataStructure[] keys = kg.generatePKIKeys();
|
||||
PublicKey pubKey = (PublicKey) keys[0];
|
||||
PrivateKey privKey = (PrivateKey) keys[1];
|
||||
ElGamalPublicKey epubKey = new ElGamalPublicKeyImpl(new NativeBigInteger(1, pubKey.getData()), elgParams);
|
||||
ElGamalPrivateKey eprivKey = new ElGamalPrivateKeyImpl(new NativeBigInteger(1, privKey.getData()), elgParams);
|
||||
return new KeyPair(epubKey, eprivKey);
|
||||
}
|
||||
}
|
@ -0,0 +1,188 @@
|
||||
package net.i2p.crypto.elgamal.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
|
||||
import javax.crypto.interfaces.DHPrivateKey;
|
||||
import javax.crypto.spec.DHParameterSpec;
|
||||
import javax.crypto.spec.DHPrivateKeySpec;
|
||||
|
||||
import static net.i2p.crypto.SigUtil.intToASN1;
|
||||
import net.i2p.crypto.elgamal.ElGamalPrivateKey;
|
||||
import static net.i2p.crypto.elgamal.impl.ElGamalPublicKeyImpl.spaceFor;
|
||||
import net.i2p.crypto.elgamal.spec.ElGamalParameterSpec;
|
||||
import net.i2p.crypto.elgamal.spec.ElGamalPrivateKeySpec;
|
||||
|
||||
public class ElGamalPrivateKeyImpl
|
||||
implements ElGamalPrivateKey, DHPrivateKey
|
||||
{
|
||||
private static final long serialVersionUID = 4819350091141529678L;
|
||||
|
||||
private BigInteger x;
|
||||
private ElGamalParameterSpec elSpec;
|
||||
|
||||
protected ElGamalPrivateKeyImpl()
|
||||
{
|
||||
}
|
||||
|
||||
public ElGamalPrivateKeyImpl(
|
||||
ElGamalPrivateKey key)
|
||||
{
|
||||
this.x = key.getX();
|
||||
this.elSpec = key.getParameters();
|
||||
}
|
||||
|
||||
public ElGamalPrivateKeyImpl(
|
||||
DHPrivateKey key)
|
||||
{
|
||||
this.x = key.getX();
|
||||
this.elSpec = new ElGamalParameterSpec(key.getParams().getP(), key.getParams().getG());
|
||||
}
|
||||
|
||||
public ElGamalPrivateKeyImpl(
|
||||
ElGamalPrivateKeySpec spec)
|
||||
{
|
||||
this.x = spec.getX();
|
||||
this.elSpec = new ElGamalParameterSpec(spec.getParams().getP(), spec.getParams().getG());
|
||||
}
|
||||
|
||||
public ElGamalPrivateKeyImpl(
|
||||
DHPrivateKeySpec spec)
|
||||
{
|
||||
this.x = spec.getX();
|
||||
this.elSpec = new ElGamalParameterSpec(spec.getP(), spec.getG());
|
||||
}
|
||||
|
||||
public ElGamalPrivateKeyImpl(
|
||||
BigInteger x,
|
||||
ElGamalParameterSpec elSpec)
|
||||
{
|
||||
this.x = x;
|
||||
this.elSpec = elSpec;
|
||||
}
|
||||
|
||||
public ElGamalPrivateKeyImpl(
|
||||
PKCS8EncodedKeySpec spec)
|
||||
{
|
||||
throw new UnsupportedOperationException("todo");
|
||||
//this.x = spec.getX();
|
||||
//this.elSpec = new ElGamalParameterSpec(spec.getP(), spec.getG());
|
||||
}
|
||||
|
||||
public String getAlgorithm()
|
||||
{
|
||||
return "ElGamal";
|
||||
}
|
||||
|
||||
/**
|
||||
* return the encoding format we produce in getEncoded().
|
||||
*
|
||||
* @return the string "PKCS#8"
|
||||
*/
|
||||
public String getFormat()
|
||||
{
|
||||
return "PKCS#8";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a PKCS8 representation of the key. The sequence returned
|
||||
* represents a full PrivateKeyInfo object.
|
||||
*
|
||||
* @return a PKCS8 representation of the key.
|
||||
*/
|
||||
public byte[] getEncoded()
|
||||
{
|
||||
byte[] pb = elSpec.getP().toByteArray();
|
||||
byte[] gb = elSpec.getG().toByteArray();
|
||||
byte[] xb = x.toByteArray();
|
||||
int seq3len = spaceFor(pb.length) + spaceFor(gb.length);
|
||||
int seq2len = 8 + spaceFor(seq3len);
|
||||
int seq1len = 3 + spaceFor(seq2len) + spaceFor(xb.length);
|
||||
int totlen = spaceFor(seq1len);
|
||||
byte[] rv = new byte[totlen];
|
||||
int idx = 0;
|
||||
// sequence 1
|
||||
rv[idx++] = 0x30;
|
||||
idx = intToASN1(rv, idx, seq1len);
|
||||
|
||||
// version
|
||||
rv[idx++] = 0x02;
|
||||
rv[idx++] = 1;
|
||||
rv[idx++] = 0;
|
||||
|
||||
// Algorithm Identifier
|
||||
// sequence 2
|
||||
rv[idx++] = 0x30;
|
||||
idx = intToASN1(rv, idx, seq2len);
|
||||
// OID: 1.3.14.7.2.1.1
|
||||
rv[idx++] = 0x06;
|
||||
rv[idx++] = 6;
|
||||
rv[idx++] = (1 * 40) + 3;
|
||||
rv[idx++] = 14;
|
||||
rv[idx++] = 7;
|
||||
rv[idx++] = 2;
|
||||
rv[idx++] = 1;
|
||||
rv[idx++] = 1;
|
||||
|
||||
// params
|
||||
// sequence 3
|
||||
rv[idx++] = 0x30;
|
||||
idx = intToASN1(rv, idx, seq3len);
|
||||
// P
|
||||
// integer
|
||||
rv[idx++] = 0x02;
|
||||
idx = intToASN1(rv, idx, pb.length);
|
||||
System.arraycopy(pb, 0, rv, idx, pb.length);
|
||||
idx += pb.length;
|
||||
// G
|
||||
// integer
|
||||
rv[idx++] = 0x02;
|
||||
idx = intToASN1(rv, idx, gb.length);
|
||||
System.arraycopy(gb, 0, rv, idx, gb.length);
|
||||
idx += gb.length;
|
||||
|
||||
// the key
|
||||
// octet string
|
||||
rv[idx++] = 0x04;
|
||||
idx = intToASN1(rv, idx, xb.length);
|
||||
// BC puts an integer in the bit string, we're not going to do that
|
||||
System.arraycopy(xb, 0, rv, idx, xb.length);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public ElGamalParameterSpec getParameters()
|
||||
{
|
||||
return elSpec;
|
||||
}
|
||||
|
||||
public DHParameterSpec getParams()
|
||||
{
|
||||
return new DHParameterSpec(elSpec.getP(), elSpec.getG());
|
||||
}
|
||||
|
||||
public BigInteger getX()
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
private void readObject(
|
||||
ObjectInputStream in)
|
||||
throws IOException, ClassNotFoundException
|
||||
{
|
||||
x = (BigInteger)in.readObject();
|
||||
|
||||
this.elSpec = new ElGamalParameterSpec((BigInteger)in.readObject(), (BigInteger)in.readObject());
|
||||
}
|
||||
|
||||
private void writeObject(
|
||||
ObjectOutputStream out)
|
||||
throws IOException
|
||||
{
|
||||
out.writeObject(this.getX());
|
||||
out.writeObject(elSpec.getP());
|
||||
out.writeObject(elSpec.getG());
|
||||
}
|
||||
}
|
@ -0,0 +1,182 @@
|
||||
package net.i2p.crypto.elgamal.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
|
||||
import javax.crypto.interfaces.DHPublicKey;
|
||||
import javax.crypto.spec.DHParameterSpec;
|
||||
import javax.crypto.spec.DHPublicKeySpec;
|
||||
|
||||
import static net.i2p.crypto.SigUtil.intToASN1;
|
||||
import net.i2p.crypto.elgamal.ElGamalPublicKey;
|
||||
import net.i2p.crypto.elgamal.spec.ElGamalParameterSpec;
|
||||
import net.i2p.crypto.elgamal.spec.ElGamalPublicKeySpec;
|
||||
|
||||
public class ElGamalPublicKeyImpl
|
||||
implements ElGamalPublicKey, DHPublicKey
|
||||
{
|
||||
private static final long serialVersionUID = 8712728417091216948L;
|
||||
|
||||
private BigInteger y;
|
||||
private ElGamalParameterSpec elSpec;
|
||||
|
||||
public ElGamalPublicKeyImpl(
|
||||
ElGamalPublicKeySpec spec)
|
||||
{
|
||||
this.y = spec.getY();
|
||||
this.elSpec = new ElGamalParameterSpec(spec.getParams().getP(), spec.getParams().getG());
|
||||
}
|
||||
|
||||
public ElGamalPublicKeyImpl(
|
||||
DHPublicKeySpec spec)
|
||||
{
|
||||
this.y = spec.getY();
|
||||
this.elSpec = new ElGamalParameterSpec(spec.getP(), spec.getG());
|
||||
}
|
||||
|
||||
public ElGamalPublicKeyImpl(
|
||||
ElGamalPublicKey key)
|
||||
{
|
||||
this.y = key.getY();
|
||||
this.elSpec = key.getParameters();
|
||||
}
|
||||
|
||||
public ElGamalPublicKeyImpl(
|
||||
DHPublicKey key)
|
||||
{
|
||||
this.y = key.getY();
|
||||
this.elSpec = new ElGamalParameterSpec(key.getParams().getP(), key.getParams().getG());
|
||||
}
|
||||
|
||||
public ElGamalPublicKeyImpl(
|
||||
BigInteger y,
|
||||
ElGamalParameterSpec elSpec)
|
||||
{
|
||||
this.y = y;
|
||||
this.elSpec = elSpec;
|
||||
}
|
||||
|
||||
public ElGamalPublicKeyImpl(
|
||||
X509EncodedKeySpec spec)
|
||||
{
|
||||
throw new UnsupportedOperationException("todo");
|
||||
//this.y = y;
|
||||
//this.elSpec = elSpec;
|
||||
}
|
||||
|
||||
public String getAlgorithm()
|
||||
{
|
||||
return "ElGamal";
|
||||
}
|
||||
|
||||
public String getFormat()
|
||||
{
|
||||
return "X.509";
|
||||
}
|
||||
|
||||
public byte[] getEncoded()
|
||||
{
|
||||
byte[] pb = elSpec.getP().toByteArray();
|
||||
byte[] gb = elSpec.getG().toByteArray();
|
||||
byte[] yb = y.toByteArray();
|
||||
int seq3len = spaceFor(pb.length) + spaceFor(gb.length);
|
||||
int seq2len = 8 + spaceFor(seq3len);
|
||||
int seq1len = spaceFor(seq2len) + spaceFor(yb.length + 1);
|
||||
int totlen = spaceFor(seq1len);
|
||||
byte[] rv = new byte[totlen];
|
||||
int idx = 0;
|
||||
// sequence 1
|
||||
rv[idx++] = 0x30;
|
||||
idx = intToASN1(rv, idx, seq1len);
|
||||
|
||||
// Algorithm Identifier
|
||||
// sequence 2
|
||||
rv[idx++] = 0x30;
|
||||
idx = intToASN1(rv, idx, seq2len);
|
||||
// OID: 1.3.14.7.2.1.1
|
||||
rv[idx++] = 0x06;
|
||||
rv[idx++] = 6;
|
||||
rv[idx++] = (1 * 40) + 3;
|
||||
rv[idx++] = 14;
|
||||
rv[idx++] = 7;
|
||||
rv[idx++] = 2;
|
||||
rv[idx++] = 1;
|
||||
rv[idx++] = 1;
|
||||
|
||||
// params
|
||||
// sequence 3
|
||||
rv[idx++] = 0x30;
|
||||
idx = intToASN1(rv, idx, seq3len);
|
||||
// P
|
||||
// integer
|
||||
rv[idx++] = 0x02;
|
||||
idx = intToASN1(rv, idx, pb.length);
|
||||
System.arraycopy(pb, 0, rv, idx, pb.length);
|
||||
idx += pb.length;
|
||||
// G
|
||||
// integer
|
||||
rv[idx++] = 0x02;
|
||||
idx = intToASN1(rv, idx, gb.length);
|
||||
System.arraycopy(gb, 0, rv, idx, gb.length);
|
||||
idx += gb.length;
|
||||
|
||||
// the key
|
||||
// bit string
|
||||
rv[idx++] = 0x03;
|
||||
idx = intToASN1(rv, idx, yb.length + 1);
|
||||
rv[idx++] = 0; // number of trailing unused bits
|
||||
// BC puts an integer in the bit string, we're not going to do that
|
||||
System.arraycopy(yb, 0, rv, idx, yb.length);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param val the length of the value, 65535 max
|
||||
* @return the length of the TLV
|
||||
*/
|
||||
static int spaceFor(int val) {
|
||||
int rv;
|
||||
if (val > 255)
|
||||
rv = 3;
|
||||
else if (val > 127)
|
||||
rv = 2;
|
||||
else
|
||||
rv = 1;
|
||||
return 1 + rv + val;
|
||||
}
|
||||
|
||||
public ElGamalParameterSpec getParameters()
|
||||
{
|
||||
return elSpec;
|
||||
}
|
||||
|
||||
public DHParameterSpec getParams()
|
||||
{
|
||||
return new DHParameterSpec(elSpec.getP(), elSpec.getG());
|
||||
}
|
||||
|
||||
public BigInteger getY()
|
||||
{
|
||||
return y;
|
||||
}
|
||||
|
||||
private void readObject(
|
||||
ObjectInputStream in)
|
||||
throws IOException, ClassNotFoundException
|
||||
{
|
||||
this.y = (BigInteger)in.readObject();
|
||||
this.elSpec = new ElGamalParameterSpec((BigInteger)in.readObject(), (BigInteger)in.readObject());
|
||||
}
|
||||
|
||||
private void writeObject(
|
||||
ObjectOutputStream out)
|
||||
throws IOException
|
||||
{
|
||||
out.writeObject(this.getY());
|
||||
out.writeObject(elSpec.getP());
|
||||
out.writeObject(elSpec.getG());
|
||||
}
|
||||
}
|
9
core/java/src/net/i2p/crypto/elgamal/impl/package.html
Normal file
9
core/java/src/net/i2p/crypto/elgamal/impl/package.html
Normal file
@ -0,0 +1,9 @@
|
||||
<html><body>
|
||||
<p>
|
||||
Implementation of ElGamal keys, used for I2PProvider.
|
||||
Modified from Bouncy Castle 1.53.
|
||||
See net.i2p.crypto.elgamal for license info.
|
||||
</p><p>
|
||||
Since 0.9.25.
|
||||
</p>
|
||||
</body></html>
|
29
core/java/src/net/i2p/crypto/elgamal/package.html
Normal file
29
core/java/src/net/i2p/crypto/elgamal/package.html
Normal file
@ -0,0 +1,29 @@
|
||||
<html><body>
|
||||
<p>
|
||||
Interfaces for ElGamal keys, used for I2PProvider.
|
||||
Copied from Bouncy Castle 1.53.
|
||||
</p><p>
|
||||
Since 0.9.25.
|
||||
</p><p><pre>
|
||||
|
||||
|
||||
Copyright (c) 2000 - 2013 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
|
||||
AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
</pre></p>
|
||||
</body></html>
|
@ -0,0 +1,28 @@
|
||||
package net.i2p.crypto.elgamal.spec;
|
||||
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
|
||||
public class ElGamalGenParameterSpec
|
||||
implements AlgorithmParameterSpec
|
||||
{
|
||||
private final int primeSize;
|
||||
|
||||
/*
|
||||
* @param primeSize the size (in bits) of the prime modulus.
|
||||
*/
|
||||
public ElGamalGenParameterSpec(
|
||||
int primeSize)
|
||||
{
|
||||
this.primeSize = primeSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size in bits of the prime modulus.
|
||||
*
|
||||
* @return the size in bits of the prime modulus
|
||||
*/
|
||||
public int getPrimeSize()
|
||||
{
|
||||
return primeSize;
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package net.i2p.crypto.elgamal.spec;
|
||||
|
||||
import java.security.spec.KeySpec;
|
||||
|
||||
public class ElGamalKeySpec
|
||||
implements KeySpec
|
||||
{
|
||||
private final ElGamalParameterSpec spec;
|
||||
|
||||
public ElGamalKeySpec(
|
||||
ElGamalParameterSpec spec)
|
||||
{
|
||||
this.spec = spec;
|
||||
}
|
||||
|
||||
public ElGamalParameterSpec getParams()
|
||||
{
|
||||
return spec;
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package net.i2p.crypto.elgamal.spec;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
|
||||
/**
|
||||
* Copied from org.bouncycastle.jce.spec
|
||||
* This can't actually be passed to the BC provider, we would have to
|
||||
* use reflection to create a "real" org.bouncycasle.jce.spec.ElGamalParameterSpec.
|
||||
*
|
||||
* @since 0.9.18, moved from net.i2p.crypto in 0.9.25
|
||||
*/
|
||||
public class ElGamalParameterSpec implements AlgorithmParameterSpec {
|
||||
private final BigInteger p;
|
||||
private final BigInteger g;
|
||||
|
||||
/**
|
||||
* Constructs a parameter set for Diffie-Hellman, using a prime modulus
|
||||
* <code>p</code> and a base generator <code>g</code>.
|
||||
*
|
||||
* @param p the prime modulus
|
||||
* @param g the base generator
|
||||
*/
|
||||
public ElGamalParameterSpec(BigInteger p, BigInteger g) {
|
||||
this.p = p;
|
||||
this.g = g;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the prime modulus <code>p</code>.
|
||||
*
|
||||
* @return the prime modulus <code>p</code>
|
||||
*/
|
||||
public BigInteger getP() {
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base generator <code>g</code>.
|
||||
*
|
||||
* @return the base generator <code>g</code>
|
||||
*/
|
||||
public BigInteger getG() {
|
||||
return g;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.25
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return p.hashCode() ^ g.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.25
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null)
|
||||
return false;
|
||||
BigInteger op, og;
|
||||
if (obj instanceof ElGamalParameterSpec) {
|
||||
ElGamalParameterSpec egps = (ElGamalParameterSpec) obj;
|
||||
op = egps.getP();
|
||||
og = egps.getG();
|
||||
//} else if (obj.getClass().getName().equals("org.bouncycastle.jce.spec.ElGamalParameterSpec")) {
|
||||
//reflection... no...
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return p.equals(op) && g.equals(og);
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package net.i2p.crypto.elgamal.spec;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* This class specifies an ElGamal private key with its associated parameters.
|
||||
*
|
||||
* @see ElGamalPublicKeySpec
|
||||
*/
|
||||
public class ElGamalPrivateKeySpec
|
||||
extends ElGamalKeySpec
|
||||
{
|
||||
private final BigInteger x;
|
||||
|
||||
public ElGamalPrivateKeySpec(
|
||||
BigInteger x,
|
||||
ElGamalParameterSpec spec)
|
||||
{
|
||||
super(spec);
|
||||
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the private value <code>x</code>.
|
||||
*
|
||||
* @return the private value <code>x</code>
|
||||
*/
|
||||
public BigInteger getX()
|
||||
{
|
||||
return x;
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package net.i2p.crypto.elgamal.spec;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* This class specifies an ElGamal public key with its associated parameters.
|
||||
*
|
||||
* @see ElGamalPrivateKeySpec
|
||||
*/
|
||||
public class ElGamalPublicKeySpec
|
||||
extends ElGamalKeySpec
|
||||
{
|
||||
private final BigInteger y;
|
||||
|
||||
public ElGamalPublicKeySpec(
|
||||
BigInteger y,
|
||||
ElGamalParameterSpec spec)
|
||||
{
|
||||
super(spec);
|
||||
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the public value <code>y</code>.
|
||||
*
|
||||
* @return the public value <code>y</code>
|
||||
*/
|
||||
public BigInteger getY()
|
||||
{
|
||||
return y;
|
||||
}
|
||||
}
|
9
core/java/src/net/i2p/crypto/elgamal/spec/package.html
Normal file
9
core/java/src/net/i2p/crypto/elgamal/spec/package.html
Normal file
@ -0,0 +1,9 @@
|
||||
<html><body>
|
||||
<p>
|
||||
Specs ElGamal keys, used for I2PProvider.
|
||||
Copied from Bouncy Castle 1.53.
|
||||
See net.i2p.crypto.elgamal for license info.
|
||||
</p><p>
|
||||
Since 0.9.25.
|
||||
</p>
|
||||
</body></html>
|
@ -3,6 +3,7 @@ package net.i2p.crypto.provider;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.security.Provider;
|
||||
import java.security.Security;
|
||||
|
||||
/**
|
||||
* @since 0.9.15
|
||||
@ -11,6 +12,7 @@ public final class I2PProvider extends Provider {
|
||||
public static final String PROVIDER_NAME = "I2P";
|
||||
private static final String INFO = "I2P Security Provider v0.1, implementing" +
|
||||
"several algorithms used by I2P.";
|
||||
private static boolean _installed;
|
||||
|
||||
/**
|
||||
* Construct a new provider. This should only be required when
|
||||
@ -31,15 +33,89 @@ public final class I2PProvider extends Provider {
|
||||
|
||||
private void setup() {
|
||||
// TODO: Implement SPIs for existing code
|
||||
// However -
|
||||
// quote
|
||||
// http://docs.oracle.com/javase/7/docs/technotes/guides/security/crypto/HowToImplAProvider.html
|
||||
//
|
||||
// If your provider is supplying encryption algorithms through the
|
||||
// Cipher, KeyAgreement, KeyGenerator, Mac, or SecretKeyFactory classes,
|
||||
// you will need to sign your JAR file so that the JCA can authenticate the code at runtime.
|
||||
// If you are NOT providing an implementation of this type you can skip this step.
|
||||
//
|
||||
//put("Cipher.AES", "net.i2p.crypto.provider.CipherSpi$aesCBC");
|
||||
//put("Cipher.ElGamal", "net.i2p.crypto.provider.CipherSpi$elGamal");
|
||||
//put("Mac.HmacMD5-I2P", "net.i2p.crypto.provider.MacSpi");
|
||||
|
||||
put("MessageDigest.SHA-1", "net.i2p.crypto.SHA1");
|
||||
//put("Signature.SHA1withDSA", "net.i2p.crypto.provider.SignatureSpi");
|
||||
|
||||
// EdDSA
|
||||
// Key OID: 1.3.101.100; Sig OID: 1.3.101.101
|
||||
put("KeyFactory.EdDSA", "net.i2p.crypto.eddsa.KeyFactory");
|
||||
put("KeyPairGenerator.EdDSA", "net.i2p.crypto.eddsa.KeyPairGenerator");
|
||||
put("Signature.SHA512withEdDSA", "net.i2p.crypto.eddsa.EdDSAEngine");
|
||||
// Didn't find much documentation on these at all,
|
||||
// see http://docs.oracle.com/javase/7/docs/technotes/guides/security/crypto/HowToImplAProvider.html
|
||||
// section "Mapping from OID to name"
|
||||
// without these, Certificate.verify() fails
|
||||
put("Alg.Alias.KeyFactory.1.3.101.100", "EdDSA");
|
||||
put("Alg.Alias.KeyFactory.OID.1.3.101.100", "EdDSA");
|
||||
// Without these, keytool fails with:
|
||||
// keytool error: java.security.NoSuchAlgorithmException: unrecognized algorithm name: SHA512withEdDSA
|
||||
put("Alg.Alias.KeyPairGenerator.1.3.101.100", "EdDSA");
|
||||
put("Alg.Alias.KeyPairGenerator.OID.1.3.101.100", "EdDSA");
|
||||
// with this setting, keytool keygen doesn't work
|
||||
// java.security.cert.CertificateException: Signature algorithm mismatch
|
||||
// it must match the key setting (1.3.101.100) to work
|
||||
// but this works fine with programmatic cert generation
|
||||
put("Alg.Alias.Signature.1.3.101.101", "SHA512withEdDSA");
|
||||
put("Alg.Alias.Signature.OID.1.3.101.101", "SHA512withEdDSA");
|
||||
// TODO Ed25519ph
|
||||
// OID: 1.3.101.101
|
||||
|
||||
// ElGamal
|
||||
// OID: 1.3.14.7.2.1.1
|
||||
put("KeyFactory.DH", "net.i2p.crypto.elgamal.KeyFactory");
|
||||
put("KeyFactory.DiffieHellman", "net.i2p.crypto.elgamal.KeyFactory");
|
||||
put("KeyFactory.ElGamal", "net.i2p.crypto.elgamal.KeyFactory");
|
||||
put("KeyPairGenerator.DH", "net.i2p.crypto.elgamal.KeyPairGenerator");
|
||||
put("KeyPairGenerator.DiffieHellman", "net.i2p.crypto.elgamal.KeyPairGenerator");
|
||||
put("KeyPairGenerator.ElGamal", "net.i2p.crypto.elgamal.KeyPairGenerator");
|
||||
put("Signature.SHA256withElGamal", "net.i2p.crypto.elgamal.ElGamalSigEngine");
|
||||
put("Alg.Alias.KeyFactory.1.3.14.7.2.1.1", "ElGamal");
|
||||
put("Alg.Alias.KeyFactory.OID.1.3.14.7.2.1.1", "ElGamal");
|
||||
put("Alg.Alias.KeyPairGenerator.1.3.14.7.2.1.1", "ElGamal");
|
||||
put("Alg.Alias.KeyPairGenerator.OID.1.3.14.7.2.1.1", "ElGamal");
|
||||
put("Alg.Alias.Signature.1.3.14.7.2.1.1", "SHA256withElGamal");
|
||||
put("Alg.Alias.Signature.OID.1.3.14.7.2.1.1", "SHA256withElGamal");
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the I2PProvider.
|
||||
* Harmless to call multiple times.
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public static void addProvider() {
|
||||
synchronized(I2PProvider.class) {
|
||||
if (!_installed) {
|
||||
try {
|
||||
Provider us = new I2PProvider();
|
||||
// put ours ahead of BC, if installed, because our ElGamal
|
||||
// implementation may not be fully compatible with BC
|
||||
Provider[] provs = Security.getProviders();
|
||||
for (int i = 0; i < provs.length; i++) {
|
||||
if (provs[i].getName().equals("BC")) {
|
||||
Security.insertProviderAt(us, i);
|
||||
_installed = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
Security.addProvider(us);
|
||||
_installed = true;
|
||||
} catch (SecurityException se) {
|
||||
System.out.println("WARN: Could not install I2P provider: " + se);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -104,6 +104,17 @@ public class Base64 {
|
||||
return safeDecode(s, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes data from Base64 notation using the I2P alphabet.
|
||||
*
|
||||
* @param useStandardAlphabet Warning, must be false for I2P compatibility
|
||||
* @return the decoded data, null on error
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public static byte[] decode(String s, boolean useStandardAlphabet) {
|
||||
return safeDecode(s, useStandardAlphabet);
|
||||
}
|
||||
|
||||
/** Maximum line length (76) of Base64 output. */
|
||||
private final static int MAX_LINE_LENGTH = 76;
|
||||
|
||||
|
@ -1324,8 +1324,9 @@ public class DataHelper {
|
||||
*
|
||||
* @param hash null OK
|
||||
* @return null on EOF
|
||||
* @deprecated use MessageDigest version
|
||||
* @deprecated use MessageDigest version to be removed in 0.9.27
|
||||
*/
|
||||
@Deprecated
|
||||
public static String readLine(InputStream in, Sha256Standalone hash) throws IOException {
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
boolean ok = readLine(in, buf, hash);
|
||||
@ -1380,7 +1381,7 @@ public class DataHelper {
|
||||
*
|
||||
* @return true if the line was read, false if eof was reached on an empty line
|
||||
* (returns true for non-empty last line without a newline)
|
||||
* @deprecated use StringBuilder / MessageDigest version
|
||||
* @deprecated use StringBuilder / MessageDigest version, to be removed in 0.9.27
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean readLine(InputStream in, StringBuffer buf, Sha256Standalone hash) throws IOException {
|
||||
@ -1420,8 +1421,9 @@ public class DataHelper {
|
||||
* @param hash null OK
|
||||
* @return true if the line was read, false if eof was reached on an empty line
|
||||
* (returns true for non-empty last line without a newline)
|
||||
* @deprecated use MessageDigest version
|
||||
* @deprecated use MessageDigest version, to be removed in 0.9.27
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean readLine(InputStream in, StringBuilder buf, Sha256Standalone hash) throws IOException {
|
||||
int c = -1;
|
||||
int i = 0;
|
||||
@ -1463,8 +1465,9 @@ public class DataHelper {
|
||||
|
||||
/**
|
||||
* update the hash along the way
|
||||
* @deprecated use MessageDigest version
|
||||
* @deprecated use MessageDigest version, to be removed in 0.9.27
|
||||
*/
|
||||
@Deprecated
|
||||
public static void write(OutputStream out, byte data[], Sha256Standalone hash) throws IOException {
|
||||
hash.update(data);
|
||||
out.write(data);
|
||||
|
@ -201,7 +201,8 @@ public class RandomSource extends SecureRandom implements EntropyHarvester {
|
||||
} catch (InterruptedException ie) {}
|
||||
|
||||
// why urandom? because /dev/random blocks
|
||||
ok = seedFromFile(new File("/dev/urandom"), buf) || ok;
|
||||
if (!SystemVersion.isWindows())
|
||||
ok = seedFromFile(new File("/dev/urandom"), buf) || ok;
|
||||
// we merge (XOR) in the data from /dev/urandom with our own seedfile
|
||||
File localFile = new File(_context.getConfigDir(), SEEDFILE);
|
||||
ok = seedFromFile(localFile, buf) || ok;
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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=
|
||||
|
BIN
installer/resources/themes/console/images/thetinhat.png
Normal file
BIN
installer/resources/themes/console/images/thetinhat.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 622 B |
@ -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 ********* //
|
||||
}
|
||||
@ -537,6 +547,14 @@ public class Router implements RouterClock.ClockShiftListener {
|
||||
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
|
||||
* as the context will not be initialized.
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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)) {
|
||||
|
@ -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);
|
||||
|
@ -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 "
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
||||
/*
|
||||
|
281
router/java/src/net/i2p/router/util/ArraySet.java
Normal file
281
router/java/src/net/i2p/router/util/ArraySet.java
Normal file
@ -0,0 +1,281 @@
|
||||
package net.i2p.router.util;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.AbstractSet;
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A small, fast Set with a maximum size, backed by a fixed-size array.
|
||||
* Unsynchronized, not thread-safe.
|
||||
* Null elements are not permitted.
|
||||
* Not appropriate for large Sets.
|
||||
*
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public class ArraySet<E> extends AbstractSet<E> implements Set<E> {
|
||||
public static final int MAX_CAPACITY = 32;
|
||||
private final Object[] _entries;
|
||||
private final boolean _throwOnFull;
|
||||
private int _size;
|
||||
private int _overflowIndex;
|
||||
private transient int modCount;
|
||||
|
||||
/**
|
||||
* A fixed capacity of MAX_CAPACITY.
|
||||
* Adds over capacity will throw a SetFullException.
|
||||
*/
|
||||
public ArraySet() {
|
||||
this(MAX_CAPACITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* A fixed capacity of MAX_CAPACITY.
|
||||
* Adds over capacity will throw a SetFullException.
|
||||
* @throws SetFullException if more than MAX_CAPACITY unique elements in c.
|
||||
*/
|
||||
public ArraySet(Collection<? extends E> c) {
|
||||
this();
|
||||
addAll(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds over capacity will throw a SetFullException.
|
||||
*
|
||||
* @param capacity the maximum size
|
||||
* @throws IllegalArgumentException if capacity less than 1 or more than MAX_CAPACITY.
|
||||
*/
|
||||
public ArraySet(int capacity) {
|
||||
this(capacity, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* If throwOnFull is false,
|
||||
* adds over capacity will overwrite starting at slot zero.
|
||||
* This breaks the AbstractCollection invariant that
|
||||
* "a Collection will always contain the specified element after add() returns",
|
||||
* but it prevents unexpected exceptions.
|
||||
* If throwOnFull is true, adds over capacity will throw a SetFullException.
|
||||
*
|
||||
* @param capacity the maximum size
|
||||
* @throws IllegalArgumentException if capacity less than 1 or more than MAX_CAPACITY.
|
||||
*/
|
||||
public ArraySet(int capacity, boolean throwOnFull) {
|
||||
if (capacity <= 0 || capacity > MAX_CAPACITY)
|
||||
throw new IllegalArgumentException("bad capacity");
|
||||
_entries = new Object[capacity];
|
||||
_throwOnFull = throwOnFull;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return -1 if not found or if o is null
|
||||
*/
|
||||
private int indexOf(Object o) {
|
||||
if (o != null) {
|
||||
for (int i = 0; i < _size; i++) {
|
||||
if (o.equals(_entries[i]))
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SetFullException if throwOnFull was true in constructor
|
||||
* @throws NullPointerException if o is null
|
||||
*/
|
||||
@Override
|
||||
public boolean add(E o) {
|
||||
if (o == null)
|
||||
throw new NullPointerException();
|
||||
int i = indexOf(o);
|
||||
if (i >= 0) {
|
||||
_entries[i] = o;
|
||||
return false;
|
||||
}
|
||||
if (_size >= _entries.length) {
|
||||
if (_throwOnFull)
|
||||
throw new SetFullException();
|
||||
i = _overflowIndex++;
|
||||
if (i >= _entries.length) {
|
||||
i = 0;
|
||||
_overflowIndex = 0;
|
||||
}
|
||||
} else {
|
||||
modCount++;
|
||||
i = _size++;
|
||||
}
|
||||
_entries[i] = o;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
if (_size != 0) {
|
||||
modCount++;
|
||||
for (int i = 0; i < _size; i++) {
|
||||
_entries[i] = null;
|
||||
}
|
||||
_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return indexOf(o) >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return _size <= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
int i = indexOf(o);
|
||||
if (i < 0)
|
||||
return false;
|
||||
modCount++;
|
||||
_size--;
|
||||
for (int j = i; j < _size; j++) {
|
||||
_entries[j] = _entries[j + 1];
|
||||
}
|
||||
_entries[_size] = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return _size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Supports remove.
|
||||
* Supports comodification checks.
|
||||
*/
|
||||
public Iterator<E> iterator() {
|
||||
return new ASIterator();
|
||||
}
|
||||
|
||||
public static class SetFullException extends IllegalStateException {
|
||||
private static final long serialVersionUID = 9087390587254111L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modified from CachedIteratorArrayList
|
||||
*/
|
||||
private class ASIterator implements Iterator<E>, Serializable {
|
||||
/**
|
||||
* Index of element to be returned by subsequent call to next.
|
||||
*/
|
||||
int cursor = 0;
|
||||
|
||||
/**
|
||||
* Index of element returned by most recent call to next or
|
||||
* previous. Reset to -1 if this element is deleted by a call
|
||||
* to remove.
|
||||
*/
|
||||
int lastRet = -1;
|
||||
|
||||
/**
|
||||
* The modCount value that the iterator believes that the backing
|
||||
* List should have. If this expectation is violated, the iterator
|
||||
* has detected concurrent modification.
|
||||
*/
|
||||
int expectedModCount = modCount;
|
||||
|
||||
public boolean hasNext() {
|
||||
return cursor != _size;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public E next() {
|
||||
checkForComodification();
|
||||
try {
|
||||
int i = cursor;
|
||||
E next = (E) _entries[i];
|
||||
lastRet = i;
|
||||
cursor = i + 1;
|
||||
return next;
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
checkForComodification();
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
if (lastRet < 0)
|
||||
throw new IllegalStateException();
|
||||
checkForComodification();
|
||||
|
||||
try {
|
||||
ArraySet.this.remove(lastRet);
|
||||
if (lastRet < cursor)
|
||||
cursor--;
|
||||
lastRet = -1;
|
||||
expectedModCount = modCount;
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
throw new ConcurrentModificationException();
|
||||
}
|
||||
}
|
||||
|
||||
final void checkForComodification() {
|
||||
if (modCount != expectedModCount)
|
||||
throw new ConcurrentModificationException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* About 3x faster than HashSet.
|
||||
*/
|
||||
/****
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public static void main(String[] args) {
|
||||
if (args.length > 0) {
|
||||
System.out.println("Test with overwrite");
|
||||
Set s = new ArraySet(4, false);
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
System.out.println("Added " + args[i] + "? " + s.add(args[i]));
|
||||
System.out.println("Size is now " + s.size());
|
||||
}
|
||||
// toString tests the iterator
|
||||
System.out.println("Set now contains" + s);
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
System.out.println("Removed " + args[i] + "? " + s.remove(args[i]));
|
||||
System.out.println("Size is now " + s.size());
|
||||
}
|
||||
System.out.println("\nTest with throw on full");
|
||||
s = new ArraySet(4);
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
System.out.println("Added " + args[i] + "? " + s.add(args[i]));
|
||||
System.out.println("Size is now " + s.size());
|
||||
}
|
||||
// toString tests the iterator
|
||||
System.out.println("Set now contains" + s);
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
System.out.println("Removed " + args[i] + "? " + s.remove(args[i]));
|
||||
System.out.println("Size is now " + s.size());
|
||||
}
|
||||
}
|
||||
|
||||
//java.util.List c = java.util.Arrays.asList(new String[] {"foo", "bar", "baz", "splat", "barf", "baz", "moose", "bear", "cat", "dog"} );
|
||||
java.util.List c = java.util.Arrays.asList(new String[] {"foo", "bar"} );
|
||||
long start = System.currentTimeMillis();
|
||||
Set s = new java.util.HashSet(c);
|
||||
int runs = 10000000;
|
||||
for (int i = 0; i < runs; i++) {
|
||||
s = new java.util.HashSet(s);
|
||||
}
|
||||
System.out.println("HashSet took " + (System.currentTimeMillis() - start));
|
||||
|
||||
start = System.currentTimeMillis();
|
||||
s = new ArraySet(c);
|
||||
for (int i = 0; i < runs; i++) {
|
||||
s = new ArraySet(s);
|
||||
}
|
||||
System.out.println("ArraySet took " + (System.currentTimeMillis() - start));
|
||||
}
|
||||
****/
|
||||
}
|
Reference in New Issue
Block a user