i2ptunnel: New form to enter private key file for alternate destination

- Use alt destination for registration if set
   - Another dup check for alt destination
SusiDNS: New button for adding alternate destination
   - Fix nonces on details page with multiple destinations
   - Fix single dest deletion on details page with multiple destinations
   - Set book in all forms to ensure correct book
Blockfile: Fix specified-destination deletion from the correct book
This commit is contained in:
zzz
2017-03-14 14:15:54 +00:00
parent 1150b4cd73
commit 4b722c9b7f
14 changed files with 267 additions and 15 deletions

View File

@ -254,6 +254,8 @@ public class TunnelController implements Logging {
File altFile = getAlternatePrivateKeyFile();
if (altFile == null)
return false;
if (altFile.equals(keyFile))
return false;
if (altFile.exists())
return true;
PrivateKeyFile pkf = new PrivateKeyFile(keyFile);

View File

@ -264,14 +264,23 @@ public class GeneralHelper {
return (tun != null && tun.getSpoofedHost() != null) ? tun.getSpoofedHost() : "";
}
/**
* @return path, non-null, non-empty
*/
public String getPrivateKeyFile(int tunnel) {
return getPrivateKeyFile(_group, tunnel);
}
/**
* @return path, non-null, non-empty
*/
public String getPrivateKeyFile(TunnelControllerGroup tcg, int tunnel) {
TunnelController tun = getController(tcg, tunnel);
if (tun != null && tun.getPrivKeyFile() != null)
return tun.getPrivKeyFile();
if (tun != null) {
String rv = tun.getPrivKeyFile();
if (rv != null)
return rv;
}
if (tunnel < 0)
tunnel = tcg == null ? 999 : tcg.getControllers().size();
String rv = "i2ptunnel" + tunnel + "-privKeys.dat";
@ -284,6 +293,28 @@ public class GeneralHelper {
return rv;
}
/**
* @return path or ""
* @since 0.9.30
*/
public String getAltPrivateKeyFile(int tunnel) {
return getAltPrivateKeyFile(_group, tunnel);
}
/**
* @return path or ""
* @since 0.9.30
*/
public String getAltPrivateKeyFile(TunnelControllerGroup tcg, int tunnel) {
TunnelController tun = getController(tcg, tunnel);
if (tun != null) {
File f = tun.getAlternatePrivateKeyFile();
if (f != null)
return f.getAbsolutePath();
}
return "";
}
public String getClientInterface(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null) {
@ -357,6 +388,29 @@ public class GeneralHelper {
return null;
}
/**
* Works even if tunnel is not running.
* @return Destination or null
* @since 0.9.30
*/
public Destination getAltDestination(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null) {
// do this the hard way
File keyFile = tun.getAlternatePrivateKeyFile();
if (keyFile != null) {
PrivateKeyFile pkf = new PrivateKeyFile(keyFile);
try {
Destination rv = pkf.getDestination();
if (rv != null)
return rv;
} catch (I2PException e) {
} catch (IOException e) {}
}
}
return null;
}
public boolean shouldStartAutomatically(int tunnel) {
TunnelController tun = getController(tunnel);
return tun != null ? tun.getStartOnLoad() : false;

View File

@ -161,6 +161,16 @@ public class TunnelConfig {
public String getPrivKeyFile() {
return _privKeyFile;
}
/**
* What filename is this server tunnel's alternate private keys stored in
* @since 0.9.30
*/
public void setAltPrivKeyFile(String file) {
if (file != null)
_otherOptions.put(I2PTunnelServer.PROP_ALT_PKF, file.trim());
}
/**
* If called with any value, we want this tunnel to start whenever it is
* loaded (aka right now and whenever the router is started up)
@ -725,7 +735,8 @@ public class TunnelConfig {
PROP_MAX_CONNS_MIN, PROP_MAX_CONNS_HOUR, PROP_MAX_CONNS_DAY,
PROP_MAX_TOTAL_CONNS_MIN, PROP_MAX_TOTAL_CONNS_HOUR, PROP_MAX_TOTAL_CONNS_DAY,
PROP_MAX_STREAMS, I2PClient.PROP_SIGTYPE,
"inbound.randomKey", "outbound.randomKey", "i2cp.leaseSetSigningPrivateKey", "i2cp.leaseSetPrivateKey"
"inbound.randomKey", "outbound.randomKey", "i2cp.leaseSetSigningPrivateKey", "i2cp.leaseSetPrivateKey",
I2PTunnelServer.PROP_ALT_PKF
};
private static final String _httpServerOpts[] = {
I2PTunnelHTTPServer.OPT_POST_WINDOW,

View File

@ -70,6 +70,14 @@ public class EditBean extends IndexBean {
public String getPrivateKeyFile(int tunnel) {
return _helper.getPrivateKeyFile(tunnel);
}
/**
* @return path or ""
* @since 0.9.30
*/
public String getAltPrivateKeyFile(int tunnel) {
return _helper.getAltPrivateKeyFile(tunnel);
}
/****
public String getNameSignature(int tunnel) {

View File

@ -488,6 +488,39 @@ public class IndexBean {
return d.toBase32();
return "";
}
/**
* Works even if tunnel is not running.
* @return Destination or null
* @since 0.9.30
*/
protected Destination getAltDestination(int tunnel) {
return _helper.getAltDestination(tunnel);
}
/**
* Works even if tunnel is not running.
* @return Base64 or ""
* @since 0.9.30
*/
public String getAltDestinationBase64(int tunnel) {
Destination d = getAltDestination(tunnel);
if (d != null)
return d.toBase64();
return "";
}
/**
* Works even if tunnel is not running.
* @return "{52 chars}.b32.i2p" or ""
* @since 0.9.30
*/
public String getAltDestHashBase32(int tunnel) {
Destination d = getAltDestination(tunnel);
if (d != null)
return d.toBase32();
return "";
}
/**
* For index.jsp
@ -613,10 +646,20 @@ public class IndexBean {
public void setSpoofedHost(String host) {
_config.setSpoofedHost(host);
}
/** What filename is this server tunnel's private keys stored in */
public void setPrivKeyFile(String file) {
_config.setPrivKeyFile(file);
}
/**
* What filename is this server tunnel's alternate private keys stored in
* @since 0.9.30
*/
public void setAltPrivKeyFile(String file) {
_config.setAltPrivKeyFile(file);
}
/**
* If called with any value (and the form submitted with action=Remove),
* we really do want to stop and remove the tunnel.

View File

@ -669,8 +669,9 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
</div>
<% **********************/ %>
<% if (true /* editBean.isAdvanced() */ ) {
int currentSigType = editBean.getSigType(curTunnel, tunnelType);
<%
int currentSigType = editBean.getSigType(curTunnel, tunnelType);
if (true /* editBean.isAdvanced() */ ) {
%>
<div id="tunnelOptionsField" class="rowItem">
<label>
@ -713,6 +714,50 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
<hr />
</div>
<% } // isAdvanced %>
<%
/* alternate dest, only if current dest is set and is DSA_SHA1 */
if (currentSigType == 0 && !"".equals(b64) && !"streamrserver".equals(tunnelType)) {
%><div id="privKeyField" class="rowItem">
<label for="privKeyFile"><%=intl._t("Alternate private key file")%> (Ed25519-SHA-512):</label>
<input type="text" size="30" id="privKeyFile" name="altPrivKeyFile" title="Path to Private Key File" value="<%=editBean.getAltPrivateKeyFile(curTunnel)%>" class="freetext" />
</div>
<%
String ab64 = editBean.getAltDestinationBase64(curTunnel);
if (!"".equals(ab64)) {
%><div id="destinationField" class="rowItem">
<label for="localDestination"><%=intl._t("Alternate local destination")%>:</label>
<textarea rows="1" style="height: 3em;" cols="60" readonly="readonly" id="localDestination" title="Read Only: Alternate Local Destination" wrap="off" spellcheck="false"><%=ab64%></textarea>
</div>
<div id="destinationField" class="rowItem">
<label> </label>
<span class="comment"><%=editBean.getAltDestHashBase32(curTunnel)%></span>
</div>
<div id="destinationField" class="rowItem">
<%
ab64 = ab64.replace("=", "%3d");
String name = editBean.getSpoofedHost(curTunnel);
if (name == null || name.equals(""))
name = editBean.getTunnelName(curTunnel);
// mysite.i2p is set in the installed i2ptunnel.config
if (name != null && !name.equals("") && !name.equals("mysite.i2p") && !name.contains(" ") && name.endsWith(".i2p")) {
%><label>
<a class="control" title="<%=intl._t("Generate QR Code")%>" href="/imagegen/qr?s=320&amp;t=<%=name%>&amp;c=http%3a%2f%2f<%=name%>%2f%3fi2paddresshelper%3d<%=ab64%>" target="_top"><%=intl._t("Generate QR Code")%></a>
</label>
<a class="control" href="/susidns/addressbook.jsp?book=private&amp;hostname=<%=name%>&amp;destination=<%=ab64%>#add"><%=intl._t("Add to local addressbook")%></a>
<%
} else {
%><label> </label>
<span class="comment"><%=intl._t("Set name with .i2p suffix to enable QR code generation")%></span>
<%
} // name
%></div>
<%
} // ab64
%><div class="subdivider"><hr /></div>
<% } // currentSigType %>
<div id="customOptionsField" class="rowItem">
<label for="customOptions" accesskey="u">

View File

@ -259,10 +259,38 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
<span class="comment"><%=intl._t("This will add an alternate destination for {0}", name)%></span>
<%
} else {
// If set, use the configured alternate destination as the new alias destination,
// and the configured primary destination as the inner signer.
// This is backwards from all the other ones, so we have to make a second HostTxtEntry just for this.
SigningPrivateKey spk3 = null;
String altdest = null;
String altdestfile = editBean.getAltPrivateKeyFile(curTunnel);
if (altdestfile.length() > 0) {
try {
PrivateKeyFile pkf3 = new PrivateKeyFile(altdestfile);
altdest = pkf3.getDestination().toBase64();
if (!b64.equals(altdest)) {
// disallow dup
spk3 = pkf3.getSigningPrivKey();
}
} catch (Exception e) {}
}
if (spk3 != null) {
OrderedProperties props2 = new OrderedProperties();
HostTxtEntry he2 = new HostTxtEntry(name, altdest, props2);
props2.setProperty(HostTxtEntry.PROP_ACTION, HostTxtEntry.ACTION_ADDDEST);
props2.setProperty(HostTxtEntry.PROP_OLDDEST, b64);
he2.signInner(spk);
he2.sign(spk3);
%><textarea rows="1" style="height: 3em;" cols="60" readonly="readonly" id="localDestination" title="Copy and paste this to the registration site" wrap="off" spellcheck="false"><% he2.write(out); %></textarea>
<span class="comment"><%=intl._t("This will add an alternate destination for {0}", name)%></span>
<%
} else {
%><span class="comment"><%=intl._t("This tunnel must be configured with the new destination.")%></span>
<span class="comment"><%=intl._t("Enter old destination below.")%></span>
<%
}
} // spk3
} // spk2
%></div>
<div class="separator">
<hr />

View File

@ -314,6 +314,9 @@ public class AddressbookBean extends BaseBean
}
if (action.equals(_t("Delete Entry")))
search = null;
} else if (action.equals(_t("Add Alternate"))) {
// button won't be in UI
message = "Unsupported";
}
if( changed ) {
try {

View File

@ -231,7 +231,7 @@ public class NamingServiceBean extends AddressbookBean
if (_context.getBooleanProperty(PROP_PW_ENABLE) ||
(serial != null && serial.equals(lastSerial))) {
boolean changed = false;
if (action.equals(_t("Add")) || action.equals(_t("Replace"))) {
if (action.equals(_t("Add")) || action.equals(_t("Replace")) || action.equals(_t("Add Alternate"))) {
if(hostname != null && destination != null) {
try {
// throws IAE with translated message
@ -243,20 +243,38 @@ public class NamingServiceBean extends AddressbookBean
Destination oldDest = getNamingService().lookup(host, nsOptions, outProperties);
if (oldDest != null && destination.equals(oldDest.toBase64())) {
message = _t("Host name {0} is already in address book, unchanged.", displayHost);
} else if (oldDest != null && !action.equals(_t("Replace"))) {
} else if (oldDest == null && action.equals(_t("Add Alternate"))) {
message = _t("Host name {0} is not in the address book.", displayHost);
} else if (oldDest != null && action.equals(_t("Add"))) {
message = _t("Host name {0} is already in address book with a different destination. Click \"Replace\" to overwrite.", displayHost);
} else {
try {
Destination dest = new Destination(destination);
if (oldDest != null) {
nsOptions.putAll(outProperties);
nsOptions.setProperty("m", Long.toString(_context.clock().now()));
String now = Long.toString(_context.clock().now());
if (action.equals(_t("Add Alternate")))
nsOptions.setProperty("a", now);
else
nsOptions.setProperty("m", now);
}
nsOptions.setProperty("s", _t("Manually added via SusiDNS"));
boolean success = getNamingService().put(host, dest, nsOptions);
boolean success;
if (action.equals(_t("Add Alternate"))) {
// check all for dups
List<Destination> all = getNamingService().lookupAll(host);
if (all == null || !all.contains(dest)) {
success = getNamingService().addDestination(host, dest, nsOptions);
} else {
// will get generic message below
success = false;
}
} else {
success = getNamingService().put(host, dest, nsOptions);
}
if (success) {
changed = true;
if (oldDest == null)
if (oldDest == null || action.equals(_t("Add Alternate")))
message = _t("Destination added for {0}.", displayHost);
else
message = _t("Destination changed for {0}.", displayHost);
@ -285,8 +303,21 @@ public class NamingServiceBean extends AddressbookBean
} else if (action.equals(_t("Delete Selected")) || action.equals(_t("Delete Entry"))) {
String name = null;
int deleted = 0;
Destination matchDest = null;
if (action.equals(_t("Delete Entry"))) {
// remove specified dest only in case there is more than one
if (destination != null) {
try {
matchDest = new Destination(destination);
} catch (DataFormatException dfe) {}
}
}
for (String n : deletionMarks) {
boolean success = getNamingService().remove(n, nsOptions);
boolean success;
if (matchDest != null)
success = getNamingService().remove(n, matchDest, nsOptions);
else
success = getNamingService().remove(n, nsOptions);
String uni = AddressBean.toUnicode(n);
String displayHost = uni.equals(n) ? n : uni + " (" + n + ')';
if (!success) {

View File

@ -120,6 +120,7 @@ ${book.loadBookMessages}
<div id="search">
<form method="POST" action="addressbook">
<input type="hidden" name="book" value="${book.book}">
<input type="hidden" name="begin" value="0">
<input type="hidden" name="end" value="49">
<table><tr>
@ -136,6 +137,7 @@ ${book.loadBookMessages}
%>
<c:if test="${book.notEmpty}">
<form method="POST" action="addressbook">
<input type="hidden" name="book" value="${book.book}">
<input type="hidden" name="serial" value="<%=susiNonce%>">
<input type="hidden" name="begin" value="0">
<input type="hidden" name="end" value="49">
@ -163,7 +165,7 @@ ${book.loadBookMessages}
</td><td class="names">
<span class="addrhlpr"><a href="http://${addr.b32}/" target="_top" title="<%=intl._t("Base 32 address")%>">b32</a></span>
</td><td class="names">
<span class="addrhlpr"><a href="details?h=${addr.name}" title="<%=intl._t("More information on this entry")%>"><%=intl._t("details")%></a></span>
<span class="addrhlpr"><a href="details?h=${addr.name}&amp;book=${book.book}" title="<%=intl._t("More information on this entry")%>"><%=intl._t("details")%></a></span>
</td>
<td class="destinations"><textarea rows="1" style="height:3em;" wrap="off" cols="40" readonly="readonly" name="dest_${addr.name}" >${addr.destination}</textarea></td>
</tr>
@ -208,6 +210,7 @@ ${book.loadBookMessages}
</c:if>
<form method="POST" action="addressbook">
<input type="hidden" name="book" value="${book.book}">
<input type="hidden" name="serial" value="<%=susiNonce%>">
<input type="hidden" name="begin" value="0">
<input type="hidden" name="end" value="49">
@ -221,6 +224,9 @@ ${book.loadBookMessages}
<p class="buttons">
<input class="cancel" type="reset" value="<%=intl._t("Cancel")%>" >
<input class="accept" type="submit" name="action" value="<%=intl._t("Replace")%>" >
<% if (!book.getBook().equals("published")) { %>
<input class="add" type="submit" name="action" value="<%=intl._t("Add Alternate")%>" >
<% } %>
<input class="add" type="submit" name="action" value="<%=intl._t("Add")%>" >
</p>
</div></form>

View File

@ -81,6 +81,8 @@
if (addrs == null) {
%><p>Not found: <%=detail%></p><%
} else {
// use one nonce for all
String nonce = book.getSerial();
for (i2p.susi.dns.AddressBean addr : addrs) {
String b32 = addr.getB32();
%>
@ -139,10 +141,12 @@
<div id="buttons">
<form method="POST" action="addressbook">
<p class="buttons">
<input type="hidden" name="serial" value="${book.serial}">
<input type="hidden" name="book" value="${book.book}">
<input type="hidden" name="serial" value="<%=nonce%>">
<input type="hidden" name="begin" value="0">
<input type="hidden" name="end" value="49">
<input type="hidden" name="checked" value="<%=detail%>">
<input type="hidden" name="destination" value="<%=addr.getDestination()%>">
<input class="delete" type="submit" name="action" value="<%=intl._t("Delete Entry")%>" >
</p>
</form>

View File

@ -1578,6 +1578,11 @@ public class BlockfileNamingService extends DummyNamingService {
}
storedOptions.remove(i);
removeReverseEntry(hostname, d);
if (options != null) {
String list = options.getProperty("list");
if (list != null)
storedOptions.get(0).setProperty("list", list);
}
return put(hostname, newDests, storedOptions, false);
}
}

View File

@ -1,3 +1,15 @@
2017-03-14 zzz
* Blockfile: Fix specified-destination deletion from the correct book
* i2ptunnel:
- New form to enter private key file for alternate destination
- Use alt destination for registration if set
* NBI: Adjust info logging at startup
* SusiDNS:
- New button for adding alternate destination
- Fix nonces on details page with multiple destinations
- Fix single dest deletion on details page with multiple destinations
- Set book in all forms to ensure correct book
2017-03-13 zzz
* i2ptunnel:
- Add subsession support to servers

View File

@ -18,7 +18,7 @@ public class RouterVersion {
/** deprecated */
public final static String ID = "Monotone";
public final static String VERSION = CoreVersion.VERSION;
public final static long BUILD = 5;
public final static long BUILD = 6;
/** for example "-test" */
public final static String EXTRA = "";