Blinding:

- Blinding only throws IAE
- Remove context arg from encode()
- Hook in new form args on /configkeyring
- Show b33 on /configkeyring
- Remove support for appended secret in b33
- Persist cache immediately when storing secret or privkey
- Fix i2ptunnel for std. ls2
- Add auth/secret required flags to BlindData
more to do
This commit is contained in:
zzz
2019-05-28 15:12:20 +00:00
parent 0c7c19451c
commit 973aab8f53
9 changed files with 274 additions and 116 deletions

View File

@ -564,13 +564,13 @@ public class IndexBean {
Destination d = getDestination(tunnel); Destination d = getDestination(tunnel);
if (d != null) { if (d != null) {
int mode = _helper.getEncryptMode(tunnel); int mode = _helper.getEncryptMode(tunnel);
if (mode > 1) { if (mode > 1 && mode < 10) {
try { try {
String secret = _helper.getBlindedPassword(tunnel); String secret = _helper.getBlindedPassword(tunnel);
boolean requireSecret = secret != null && secret.length() > 0 && boolean requireSecret = secret != null && secret.length() > 0 &&
(mode == 3 || mode == 5 || mode == 7 || mode == 9); (mode == 3 || mode == 5 || mode == 7 || mode == 9);
boolean requireAuth = mode >= 4 && mode <= 9; boolean requireAuth = mode >= 4 && mode <= 9;
return Blinding.encode(_context, d.getSigningPublicKey(), requireSecret, requireAuth); return Blinding.encode(d.getSigningPublicKey(), requireSecret, requireAuth);
} catch (RuntimeException re) {} } catch (RuntimeException re) {}
} }
} }

View File

@ -462,13 +462,10 @@
%> %>
<option title="<%=intl._t("Only clients with the password will be able to connect")%>" value="3" <%=(curEncryptMode.equals("3") ? " selected=\"selected\"" : "")%> > <option title="<%=intl._t("Only clients with the password will be able to connect")%>" value="3" <%=(curEncryptMode.equals("3") ? " selected=\"selected\"" : "")%> >
<%=intl._t("Blinded with lookup password")%></option> <%=intl._t("Blinded with lookup password")%></option>
<%
// TODO, unimplemented
%>
<option title="<%=intl._t("Only clients with the encryption key will be able to connect")%>" value="4" <%=(curEncryptMode.equals("4") ? " selected=\"selected\"" : "")%> > <option title="<%=intl._t("Only clients with the encryption key will be able to connect")%>" value="4" <%=(curEncryptMode.equals("4") ? " selected=\"selected\"" : "")%> >
<%=intl._t("Blinded with shared key")%></option> <%=intl._t("Blinded with shared key")%> (PSK)</option>
<option title="<%=intl._t("Only clients with the password and key will be able to connect")%>" value="5" <%=(curEncryptMode.equals("5") ? " selected=\"selected\"" : "")%> > <option title="<%=intl._t("Only clients with the password and key will be able to connect")%>" value="5" <%=(curEncryptMode.equals("5") ? " selected=\"selected\"" : "")%> >
<%=intl._t("Blinded with lookup password and shared key")%></option> <%=intl._t("Blinded with lookup password and shared key")%> (PSK)</option>
<option title="<%=intl._t("Only clients with the encryption key will be able to connect")%>" value="6" <%=(curEncryptMode.equals("6") ? " selected=\"selected\"" : "")%> > <option title="<%=intl._t("Only clients with the encryption key will be able to connect")%>" value="6" <%=(curEncryptMode.equals("6") ? " selected=\"selected\"" : "")%> >
<%=intl._t("Blinded with per-user key")%> (PSK)</option> <%=intl._t("Blinded with per-user key")%> (PSK)</option>
<option title="<%=intl._t("Only clients with the password and key will be able to connect")%>" value="7" <%=(curEncryptMode.equals("7") ? " selected=\"selected\"" : "")%> > <option title="<%=intl._t("Only clients with the password and key will be able to connect")%>" value="7" <%=(curEncryptMode.equals("7") ? " selected=\"selected\"" : "")%> >

View File

@ -1,8 +1,16 @@
package net.i2p.router.web.helpers; package net.i2p.router.web.helpers;
import net.i2p.crypto.Blinding;
import net.i2p.crypto.EncType;
import net.i2p.crypto.SigType;
import net.i2p.data.Base64;
import net.i2p.data.BlindData;
import net.i2p.data.DataFormatException; import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.data.Hash; import net.i2p.data.Hash;
import net.i2p.data.PrivateKey;
import net.i2p.data.SessionKey; import net.i2p.data.SessionKey;
import net.i2p.data.SigningPublicKey;
import net.i2p.router.web.FormHandler; import net.i2p.router.web.FormHandler;
import net.i2p.util.ConvertToHash; import net.i2p.util.ConvertToHash;
@ -12,33 +20,151 @@ import net.i2p.util.ConvertToHash;
public class ConfigKeyringHandler extends FormHandler { public class ConfigKeyringHandler extends FormHandler {
private String _peer; private String _peer;
private String _key; private String _key;
private String _secret;
private int _mode;
@Override @Override
protected void processForm() { protected void processForm() {
boolean adding = _action.equals(_t("Add key")); boolean adding = _action.equals(_t("Add key"));
if (adding || _action.equals(_t("Delete key"))) { if (adding || _action.equals(_t("Delete key"))) {
if (_peer == null) if (_peer == null) {
addFormError(_t("You must enter a destination")); addFormError(_t("You must enter a destination"));
if (_key == null && adding)
addFormError(_t("You must enter a key"));
if (_peer == null || (_key == null && adding))
return; return;
Hash h = ConvertToHash.getHash(_peer); }
Hash h = null;
if (!_peer.endsWith(".b32.i2p") || _peer.length() <= 60) {
// don't wait for several seconds for b33 lookup
h = ConvertToHash.getHash(_peer);
}
if (adding) { if (adding) {
SessionKey sk = new SessionKey(); byte[] b = null;
try { if (_mode == 1 || _mode == 4 || _mode == 5) {
sk.fromBase64(_key); if (_key == null) {
} catch (DataFormatException dfe) {} addFormError(_t("You must enter a key"));
if (h == null || h.getData() == null) { return;
addFormError(_t("Invalid destination")); }
} else if (_context.clientManager().isLocal(h)) { b = Base64.decode(_key);
// don't bother translating if (b == null || b.length != 32) {
addFormError("Cannot add key for local destination. Enable encryption in the Hidden Services Manager."); addFormError(_t("Invalid key"));
} else if (sk.getData() == null) { return;
addFormError(_t("Invalid key")); }
}
if (_mode == 1) {
// LS1
if (h == null || h.getData() == null) {
addFormError(_t("Invalid destination"));
} else if (_context.clientManager().isLocal(h)) {
// don't bother translating
addFormError("Cannot add key for local destination. Enable encryption in the Hidden Services Manager.");
} else {
SessionKey sk = new SessionKey(b);
_context.keyRing().put(h, sk);
addFormNotice(_t("Key for {0} added to keyring", h.toBase32()));
}
} else { } else {
_context.keyRing().put(h, sk); if ((_mode == 3 || _mode == 5 || _mode == 7) && _secret == null) {
addFormNotice(_t("Key for {0} added to keyring", h.toBase32())); addFormError(_t("Lookup password required"));
return;
}
// b33 if supplied as hostname
BlindData bdin = null;
try {
bdin = Blinding.decode(_context, _peer);
} catch (IllegalArgumentException iae) {}
// we need the dest or the spk, not just the desthash
SigningPublicKey spk = null;
Destination d = null;
// don't cause LS fetch
if (!_peer.endsWith(".b32.i2p"))
d = _context.namingService().lookup(_peer);
if (d != null) {
spk = d.getSigningPublicKey();
} else if (bdin != null) {
spk = bdin.getUnblindedPubKey();
}
if (spk == null) {
addFormError(_t("Requires hostname, destination, or blinded base32"));
return;
}
// from BlindCache
BlindData bdold = _context.netDb().getBlindData(spk);
if (bdold != null && d == null)
d = bdold.getDestination();
if (d != null && _context.clientManager().isLocal(d)) {
// don't bother translating
addFormError("Cannot add key for local destination. Enable encryption in the Hidden Services Manager.");
return;
}
SigType blindType;
if (bdin != null) {
blindType = bdin.getBlindedSigType();
} else if (bdold != null) {
blindType = bdold.getBlindedSigType();
} else {
blindType = Blinding.getDefaultBlindedType(spk.getType());
}
int atype;
PrivateKey pk;
if (_mode == 4 || _mode == 5) {
atype = BlindData.AUTH_PSK;
// use supplied pk
pk = new PrivateKey(EncType.ECIES_X25519, b);
} else if (_mode == 6 || _mode == 7) {
atype = BlindData.AUTH_DH;
// create new pk
b = new byte[32];
_context.random().nextBytes(b);
pk = new PrivateKey(EncType.ECIES_X25519, b);
} else {
// modes 2 and 3
atype = BlindData.AUTH_NONE;
pk = null;
}
if (_mode == 2 || _mode == 4 || _mode == 6)
_secret = null;
if (bdin != null) {
// more checks based on supplied b33
if (bdin.getSecretRequired() && _secret == null) {
addFormError(_t("Destination requires lookup password"));
return;
}
if (!bdin.getSecretRequired() && _secret != null) {
addFormError(_t("Destination does not require lookup password"));
return;
}
if (bdin.getAuthRequired() && pk == null) {
addFormError(_t("Destination requires encryption key"));
return;
}
if (!bdin.getAuthRequired() && pk != null) {
addFormError(_t("Destination does not require encryption key"));
return;
}
}
// to BlindCache
BlindData bdout;
if (d != null) {
bdout = new BlindData(_context, d, blindType, _secret, atype, pk);
} else {
bdout = new BlindData(_context, spk, blindType, _secret, atype, pk);
}
if (bdold != null) {
// debug
addFormNotice("already cached: " + bdold);
}
try {
_context.netDb().setBlindData(bdout);
addFormNotice(_t("Key for {0} added to keyring", bdout.toBase32()));
if (_mode == 6 || _mode == 7) {
addFormNotice(_t("Send your new key to the server opererator") + ": " + pk.toPublic().toBase64());
}
} catch (IllegalArgumentException iae) {
addFormError(_t("Invalid destination") + ": " + iae.getMessage());
}
} }
} else { // Delete } else { // Delete
if (h != null && h.getData() != null) { if (h != null && h.getData() != null) {
@ -61,4 +187,20 @@ public class ConfigKeyringHandler extends FormHandler {
public void setPeer(String peer) { if (peer != null) _peer = peer.trim(); } public void setPeer(String peer) { if (peer != null) _peer = peer.trim(); }
public void setKey(String key) { if (key != null) _key = key.trim(); } public void setKey(String key) { if (key != null) _key = key.trim(); }
/** @since 0.9.41 */
public void setNofilter_blindedPassword(String pw) {
if (pw != null) {
pw = pw.trim();
if (pw.length() > 0)
_secret = pw;
}
}
/** @since 0.9.41 */
public void setEncryptMode(String m) {
try {
_mode = Integer.parseInt(m);
} catch (NumberFormatException nfe) {}
}
} }

View File

@ -85,15 +85,15 @@ public class ConfigKeyringHelper extends HelperBase {
List<BlindData> bdata = _context.netDb().getBlindData(); List<BlindData> bdata = _context.netDb().getBlindData();
// TODO sort by hostname // TODO sort by hostname
for (BlindData bd : bdata) { for (BlindData bd : bdata) {
Hash h = bd.getDestHash();
if (h == null)
continue;
buf.append("\n<tr><td>"); buf.append("\n<tr><td>");
buf.append(h.toBase32()); buf.append(bd.toBase32());
buf.append("</td><td>"); buf.append("</td><td>");
String host = _context.namingService().reverseLookup(h); Hash h = bd.getDestHash();
if (host != null) if (h != null) {
buf.append(host); String host = _context.namingService().reverseLookup(h);
if (host != null)
buf.append(host);
}
buf.append("</td><td>"); buf.append("</td><td>");
int type = bd.getAuthType(); int type = bd.getAuthType();
PrivateKey pk = bd.getAuthPrivKey(); PrivateKey pk = bd.getAuthPrivKey();

View File

@ -52,9 +52,9 @@
<%=intl._t("Blinded with shared key")%></option> <%=intl._t("Blinded with shared key")%></option>
<option title="<%=intl._t("Only clients with the password and key will be able to connect")%>" value="5"> <option title="<%=intl._t("Only clients with the password and key will be able to connect")%>" value="5">
<%=intl._t("Blinded with lookup password and shared key")%></option> <%=intl._t("Blinded with lookup password and shared key")%></option>
<option title="<%=intl._t("Only clients with the encryption key will be able to connect")%>" value="8"> <option title="<%=intl._t("Only clients with the encryption key will be able to connect")%>" value="6">
<%=intl._t("Blinded with per-user key")%> (DH)</option> <%=intl._t("Blinded with per-user key")%> (DH)</option>
<option title="<%=intl._t("Only clients with the password and key will be able to connect")%>" value="9"> <option title="<%=intl._t("Only clients with the password and key will be able to connect")%>" value="7">
<%=intl._t("Blinded with lookup password and per-user key")%> (DH)</option> <%=intl._t("Blinded with lookup password and per-user key")%> (DH)</option>
</select></td> </select></td>
</tr><tr> </tr><tr>

View File

@ -53,14 +53,13 @@ public final class Blinding {
* @param key must be SigType EdDSA_SHA512_Ed25519 or RedDSA_SHA512_Ed25519 * @param key must be SigType EdDSA_SHA512_Ed25519 or RedDSA_SHA512_Ed25519
* @param alpha must be SigType RedDSA_SHA512_Ed25519 * @param alpha must be SigType RedDSA_SHA512_Ed25519
* @return SigType RedDSA_SHA512_Ed25519 * @return SigType RedDSA_SHA512_Ed25519
* @throws UnsupportedOperationException unless supported SigTypes * @throws IllegalArgumentException on bad inputs or unsupported SigTypes
* @throws IllegalArgumentException on bad inputs
*/ */
public static SigningPublicKey blind(SigningPublicKey key, SigningPrivateKey alpha) { public static SigningPublicKey blind(SigningPublicKey key, SigningPrivateKey alpha) {
SigType type = key.getType(); SigType type = key.getType();
if ((type != TYPE && type != TYPER) || if ((type != TYPE && type != TYPER) ||
alpha.getType() != TYPER) alpha.getType() != TYPER)
throw new UnsupportedOperationException(); throw new IllegalArgumentException("Unsupported blinding from " + type + " to " + alpha.getType());
try { try {
EdDSAPublicKey jk = SigUtil.toJavaEdDSAKey(key); EdDSAPublicKey jk = SigUtil.toJavaEdDSAKey(key);
EdDSAPrivateKey ajk = SigUtil.toJavaEdDSAKey(alpha); EdDSAPrivateKey ajk = SigUtil.toJavaEdDSAKey(alpha);
@ -77,14 +76,13 @@ public final class Blinding {
* @param key must be SigType EdDSA_SHA512_Ed25519 or RedDSA_SHA512_Ed25519 * @param key must be SigType EdDSA_SHA512_Ed25519 or RedDSA_SHA512_Ed25519
* @param alpha must be SigType RedDSA_SHA512_Ed25519 * @param alpha must be SigType RedDSA_SHA512_Ed25519
* @return SigType RedDSA_SHA512_Ed25519 * @return SigType RedDSA_SHA512_Ed25519
* @throws UnsupportedOperationException unless supported SigTypes * @throws IllegalArgumentException on bad inputs or unsupported SigTypes
* @throws IllegalArgumentException on bad inputs
*/ */
public static SigningPrivateKey blind(SigningPrivateKey key, SigningPrivateKey alpha) { public static SigningPrivateKey blind(SigningPrivateKey key, SigningPrivateKey alpha) {
SigType type = key.getType(); SigType type = key.getType();
if ((type != TYPE && type != TYPER) || if ((type != TYPE && type != TYPER) ||
alpha.getType() != TYPER) alpha.getType() != TYPER)
throw new UnsupportedOperationException(); throw new IllegalArgumentException("Unsupported blinding from " + type + " to " + alpha.getType());
try { try {
EdDSAPrivateKey jk = SigUtil.toJavaEdDSAKey(key); EdDSAPrivateKey jk = SigUtil.toJavaEdDSAKey(key);
EdDSAPrivateKey ajk = SigUtil.toJavaEdDSAKey(alpha); EdDSAPrivateKey ajk = SigUtil.toJavaEdDSAKey(alpha);
@ -101,12 +99,11 @@ public final class Blinding {
* @param key must be SigType RedDSA_SHA512_Ed25519 * @param key must be SigType RedDSA_SHA512_Ed25519
* @param alpha must be SigType RedDSA_SHA512_Ed25519 * @param alpha must be SigType RedDSA_SHA512_Ed25519
* @return SigType EdDSA_SHA512_Ed25519 * @return SigType EdDSA_SHA512_Ed25519
* @throws UnsupportedOperationException unless supported SigTypes * @throws IllegalArgumentException on bad inputs or unsupported SigTypes
* @throws IllegalArgumentException on bad inputs
*/ */
public static SigningPrivateKey unblind(SigningPrivateKey key, SigningPrivateKey alpha) { public static SigningPrivateKey unblind(SigningPrivateKey key, SigningPrivateKey alpha) {
if (key.getType() != TYPER || alpha.getType() != TYPER) if (key.getType() != TYPER || alpha.getType() != TYPER)
throw new UnsupportedOperationException(); throw new IllegalArgumentException("Unsupported blinding from " + key.getType() + " / " + alpha.getType());
try { try {
EdDSAPrivateKey bjk = SigUtil.toJavaEdDSAKey(key); EdDSAPrivateKey bjk = SigUtil.toJavaEdDSAKey(key);
EdDSAPrivateKey ajk = SigUtil.toJavaEdDSAKey(alpha); EdDSAPrivateKey ajk = SigUtil.toJavaEdDSAKey(alpha);
@ -124,8 +121,7 @@ public final class Blinding {
* @param destspk must be SigType EdDSA_SHA512_Ed25519 * @param destspk must be SigType EdDSA_SHA512_Ed25519
* @param secret may be null or zero-length * @param secret may be null or zero-length
* @return SigType RedDSA_SHA512_Ed25519 * @return SigType RedDSA_SHA512_Ed25519
* @throws UnsupportedOperationException unless supported SigTypes * @throws IllegalArgumentException on bad inputs or unsupported SigTypes
* @throws IllegalArgumentException on bad inputs
* @since 0.9.39 * @since 0.9.39
*/ */
public static SigningPrivateKey generateAlpha(I2PAppContext ctx, SigningPublicKey destspk, String secret) { public static SigningPrivateKey generateAlpha(I2PAppContext ctx, SigningPublicKey destspk, String secret) {
@ -141,12 +137,14 @@ public final class Blinding {
* @param secret may be null or zero-length * @param secret may be null or zero-length
* @param now for what time? * @param now for what time?
* @return SigType RedDSA_SHA512_Ed25519 * @return SigType RedDSA_SHA512_Ed25519
* @throws UnsupportedOperationException unless supported SigTypes * @throws IllegalArgumentException on bad inputs or unsupported SigTypes
* @throws IllegalArgumentException on bad inputs
* @since 0.9.39 * @since 0.9.39
*/ */
public static SigningPrivateKey generateAlpha(I2PAppContext ctx, SigningPublicKey destspk, public static SigningPrivateKey generateAlpha(I2PAppContext ctx, SigningPublicKey destspk,
String secret, long now) { String secret, long now) {
SigType type = destspk.getType();
if (type != TYPE && type != TYPER)
throw new IllegalArgumentException("Unsupported blinding from " + type);
String modVal; String modVal;
synchronized(_fmt) { synchronized(_fmt) {
modVal = _fmt.format(now); modVal = _fmt.format(now);
@ -170,7 +168,7 @@ public final class Blinding {
// SHA256("I2PGenerateAlpha" || spk || sigtypein || sigtypeout) // SHA256("I2PGenerateAlpha" || spk || sigtypein || sigtypeout)
System.arraycopy(INFO_ALPHA, 0, in, 0, INFO_ALPHA.length); System.arraycopy(INFO_ALPHA, 0, in, 0, INFO_ALPHA.length);
System.arraycopy(destspk.getData(), 0, in, INFO_ALPHA.length, destspk.length()); System.arraycopy(destspk.getData(), 0, in, INFO_ALPHA.length, destspk.length());
DataHelper.toLong(in, stoff, 2, destspk.getType().getCode()); DataHelper.toLong(in, stoff, 2, type.getCode());
DataHelper.toLong(in, stoff + 2, 2, TYPER.getCode()); DataHelper.toLong(in, stoff + 2, 2, TYPER.getCode());
Hash salt = ctx.sha().calculateHash(in); Hash salt = ctx.sha().calculateHash(in);
hkdf.calculate(salt.getData(), data, INFO, out, out, 32); hkdf.calculate(salt.getData(), data, INFO, out, out, 32);
@ -198,14 +196,13 @@ public final class Blinding {
/** /**
* Decode a new-format b32 address. * Decode a new-format b32 address.
* PRELIMINARY - Subject to change - see proposal 149 * See proposal 149.
* *
* @param address ending with ".b32.i2p" * @param address ending with ".b32.i2p"
* @throws IllegalArgumentException on bad inputs * @throws IllegalArgumentException on bad inputs or unsupported SigTypes
* @throws UnsupportedOperationException unless supported SigTypes
* @since 0.9.40 * @since 0.9.40
*/ */
public static BlindData decode(I2PAppContext ctx, String address) throws RuntimeException { public static BlindData decode(I2PAppContext ctx, String address) throws IllegalArgumentException {
address = address.toLowerCase(Locale.US); address = address.toLowerCase(Locale.US);
if (!address.endsWith(".b32.i2p")) if (!address.endsWith(".b32.i2p"))
throw new IllegalArgumentException("Not a .b32.i2p address"); throw new IllegalArgumentException("Not a .b32.i2p address");
@ -219,15 +216,15 @@ public final class Blinding {
/** /**
* Decode a new-format b32 address. * Decode a new-format b32 address.
* PRELIMINARY - Subject to change - see proposal 149 * See proposal 149.
* NOTE: Not for external use, use decode(String)
* *
* @param b 35+ bytes * @param b 35+ bytes
* @return BlindData structure, use getUnblindedPubKey() for the result * @return BlindData structure, use getUnblindedPubKey() for the result
* @throws IllegalArgumentException on bad inputs * @throws IllegalArgumentException on bad inputs or unsupported SigTypes
* @throws UnsupportedOperationException unless supported SigTypes
* @since 0.9.40 * @since 0.9.40
*/ */
public static BlindData decode(I2PAppContext ctx, byte[] b) throws RuntimeException { public static BlindData decode(I2PAppContext ctx, byte[] b) throws IllegalArgumentException {
Checksum crc = new CRC32(); Checksum crc = new CRC32();
crc.update(b, 3, b.length - 3); crc.update(b, 3, b.length - 3);
long check = crc.getValue(); long check = crc.getValue();
@ -239,8 +236,6 @@ public final class Blinding {
throw new IllegalArgumentException("Corrupt b32 or unsupported options"); throw new IllegalArgumentException("Corrupt b32 or unsupported options");
if ((flag & FLAG_TWOBYTE) != 0) if ((flag & FLAG_TWOBYTE) != 0)
throw new IllegalArgumentException("Two byte sig types unsupported"); throw new IllegalArgumentException("Two byte sig types unsupported");
if ((flag & FLAG_AUTH) != 0)
throw new IllegalArgumentException("Per-client auth unsupported");
// TODO two-byte sigtypes // TODO two-byte sigtypes
int st1 = b[1] & 0xff; int st1 = b[1] & 0xff;
int st2 = b[2] & 0xff; int st2 = b[2] & 0xff;
@ -261,85 +256,49 @@ public final class Blinding {
byte[] spkData = new byte[spkLen]; byte[] spkData = new byte[spkLen];
System.arraycopy(b, 3, spkData, 0, spkLen); System.arraycopy(b, 3, spkData, 0, spkLen);
SigningPublicKey spk = new SigningPublicKey(sigt1, spkData); SigningPublicKey spk = new SigningPublicKey(sigt1, spkData);
String secret; if (3 + spkLen != b.length)
if ((flag & FLAG_SECRET) != 0) {
if (4 + spkLen > b.length) {
//throw new IllegalArgumentException("No secret data");
secret = null;
} else {
int secLen = b[3 + spkLen] & 0xff;
if (4 + spkLen + secLen != b.length)
throw new IllegalArgumentException("Bad b32 length");
secret = DataHelper.getUTF8(b, 4 + spkLen, secLen);
}
} else if (3 + spkLen != b.length) {
throw new IllegalArgumentException("b32 too long"); throw new IllegalArgumentException("b32 too long");
} else { BlindData rv = new BlindData(ctx, spk, sigt2, null);
secret = null; if ((flag & FLAG_SECRET) != 0)
} rv.setSecretRequired();
BlindData rv = new BlindData(ctx, spk, sigt2, secret); if ((flag & FLAG_AUTH) != 0)
rv.setAuthRequired();
return rv; return rv;
} }
/** /**
* Encode a public key as a new-format b32 address. * Encode a public key as a new-format b32 address.
* PRELIMINARY - Subject to change - see proposal 149 * See proposal 149.
* *
* @return (56 chars).b32.i2p * @return (56 chars).b32.i2p
* @throws IllegalArgumentException on bad inputs * @throws IllegalArgumentException on bad inputs or unsupported SigTypes
* @throws UnsupportedOperationException unless supported SigTypes
* @since 0.9.40 * @since 0.9.40
*/ */
public static String encode(I2PAppContext ctx, SigningPublicKey key) throws RuntimeException { public static String encode(SigningPublicKey key) throws IllegalArgumentException {
return encode(ctx, key, false, false, null); return encode(key, false, false);
} }
/** /**
* Encode a public key as a new-format b32 address. * Encode a public key as a new-format b32 address.
* PRELIMINARY - Subject to change - see proposal 149 * See proposal 149.
* *
* @return (56 chars).b32.i2p * @return (56 chars).b32.i2p
* @throws IllegalArgumentException on bad inputs * @throws IllegalArgumentException on bad inputs or unsupported SigTypes
* @throws UnsupportedOperationException unless supported SigTypes
* @since 0.9.40 * @since 0.9.40
*/ */
public static String encode(I2PAppContext ctx, SigningPublicKey key, public static String encode(SigningPublicKey key,
boolean requireSecret, boolean requireAuth) throws RuntimeException { boolean requireSecret, boolean requireAuth) throws IllegalArgumentException {
return encode(ctx, key, requireSecret, requireAuth, null);
}
/**
* Encode a public key as a new-format b32 address.
* PRELIMINARY - Subject to change - see proposal 149
*
* @param secret may be empty or null
* @return (56+ chars).b32.i2p
* @throws IllegalArgumentException on bad inputs
* @throws UnsupportedOperationException unless supported SigTypes
* @since 0.9.40
*/
public static String encode(I2PAppContext ctx, SigningPublicKey key,
boolean requireSecret, boolean requireAuth,
String secret) throws RuntimeException {
SigType type = key.getType(); SigType type = key.getType();
if (type != TYPE && type != TYPER) if (type != TYPE && type != TYPER)
throw new UnsupportedOperationException(); throw new IllegalArgumentException("Unsupported blinding from " + type);
byte sdata[] = (secret != null) ? DataHelper.getUTF8(secret) : null;
int slen = (secret != null) ? 1 + sdata.length : 0;
if (slen > 256)
throw new IllegalArgumentException("secret too long");
byte[] d = key.getData(); byte[] d = key.getData();
byte[] b = new byte[d.length + slen + 3]; byte[] b = new byte[d.length + 3];
System.arraycopy(d, 0, b, 3, d.length); System.arraycopy(d, 0, b, 3, d.length);
if (slen > 0) {
b[3 + d.length] = (byte) sdata.length;
System.arraycopy(sdata, 0, b, 4 + d.length, sdata.length);
}
Checksum crc = new CRC32(); Checksum crc = new CRC32();
crc.update(b, 3, b.length - 3); crc.update(b, 3, b.length - 3);
long check = crc.getValue(); long check = crc.getValue();
// TODO two-byte sigtypes // TODO two-byte sigtypes
if (slen > 0 || requireSecret) if (requireSecret)
b[0] = FLAG_SECRET; b[0] = FLAG_SECRET;
if (requireAuth) if (requireAuth)
b[0] |= FLAG_AUTH; b[0] |= FLAG_AUTH;
@ -367,8 +326,8 @@ public final class Blinding {
SigningPublicKey pub = (SigningPublicKey) keys[0]; SigningPublicKey pub = (SigningPublicKey) keys[0];
SigningPrivateKey priv = (SigningPrivateKey) keys[1]; SigningPrivateKey priv = (SigningPrivateKey) keys[1];
I2PAppContext ctx = I2PAppContext.getGlobalContext(); I2PAppContext ctx = I2PAppContext.getGlobalContext();
//String b32 = encode(ctx, pub, null); //String b32 = encode(pub, null);
String b32 = encode(ctx, pub, "foobarbaz"); String b32 = encode(pub, "foobarbaz");
System.out.println("pub b32 is " + b32); System.out.println("pub b32 is " + b32);
BlindData bd = decode(ctx, b32); BlindData bd = decode(ctx, b32);
if (bd.getBlindedPubKey().equals(pub)) if (bd.getBlindedPubKey().equals(pub))

View File

@ -23,6 +23,8 @@ public class BlindData {
private SigningPrivateKey _alpha; private SigningPrivateKey _alpha;
private Destination _dest; private Destination _dest;
private long _routingKeyGenMod; private long _routingKeyGenMod;
private boolean _secretRequired;
private boolean _authRequired;
/** /**
* bits 3-0 including per-client bit * bits 3-0 including per-client bit
@ -39,6 +41,11 @@ public class BlindData {
* @since 0.9.41 * @since 0.9.41
*/ */
public static final int AUTH_PSK = 3; public static final int AUTH_PSK = 3;
/**
* Enabled, unspecified type
* @since 0.9.41
*/
public static final int AUTH_ON = 999;
/** /**
* @param secret may be null or zero-length * @param secret may be null or zero-length
@ -83,6 +90,10 @@ public class BlindData {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
_authType = authType; _authType = authType;
_authKey = authKey; _authKey = authKey;
if (secret != null)
_secretRequired = true;
if (authKey != null)
_authRequired = true;
// defer until needed // defer until needed
//calculate(); //calculate();
} }
@ -197,6 +208,42 @@ public class BlindData {
_blindHash = _context.sha().calculateHash(hashData); _blindHash = _context.sha().calculateHash(hashData);
} }
/**
* b33 format
* @since 0.9.41
*/
public synchronized String toBase32() {
return Blinding.encode(_clearSPK, _secret != null, _authKey != null);
}
/**
* @since 0.9.41
*/
public void setSecretRequired() {
_secretRequired = true;
}
/**
* @since 0.9.41
*/
public boolean getSecretRequired() {
return _secretRequired;
}
/**
* @since 0.9.41
*/
public void setAuthRequired() {
_authRequired = true;
}
/**
* @since 0.9.41
*/
public boolean getAuthRequired() {
return _authRequired;
}
@Override @Override
public synchronized String toString() { public synchronized String toString() {
calculate(); calculate();
@ -219,6 +266,7 @@ public class BlindData {
buf.append("\n\tDestination: ").append(_dest); buf.append("\n\tDestination: ").append(_dest);
else else
buf.append("\n\tDestination: unknown"); buf.append("\n\tDestination: unknown");
buf.append("\n\tB32 : ").append(toBase32());
buf.append(']'); buf.append(']');
return buf.toString(); return buf.toString();
} }

View File

@ -769,7 +769,7 @@ public class PrivateKeyFile {
if (type == SigType.EdDSA_SHA512_Ed25519 || if (type == SigType.EdDSA_SHA512_Ed25519 ||
type == SigType.RedDSA_SHA512_Ed25519) { type == SigType.RedDSA_SHA512_Ed25519) {
I2PAppContext ctx = I2PAppContext.getGlobalContext(); I2PAppContext ctx = I2PAppContext.getGlobalContext();
s.append("\nBlinded B32: ").append(Blinding.encode(ctx, spk)); s.append("\nBlinded B32: ").append(Blinding.encode(spk));
} }
} }
s.append("\nContains: "); s.append("\nContains: ");

View File

@ -168,7 +168,19 @@ class BlindCache {
} }
} }
/**
* Persists immediately if secret or privkey is non-null
*/
public void addToCache(BlindData bd) { public void addToCache(BlindData bd) {
storeInCache(bd);
if (bd.getSecret() != null || bd.getAuthPrivKey() != null)
store();
}
/**
* @since 0.9.41 from addToCache()
*/
private void storeInCache(BlindData bd) {
_cache.put(bd.getUnblindedPubKey(), bd); _cache.put(bd.getUnblindedPubKey(), bd);
_reverseCache.put(bd.getBlindedPubKey(), bd); _reverseCache.put(bd.getBlindedPubKey(), bd);
Destination dest = bd.getDestination(); Destination dest = bd.getDestination();
@ -257,7 +269,7 @@ class BlindCache {
if (line.startsWith("#")) if (line.startsWith("#"))
continue; continue;
try { try {
addToCache(fromPersistentString(line)); storeInCache(fromPersistentString(line));
count++; count++;
} catch (IllegalArgumentException iae) { } catch (IllegalArgumentException iae) {
if (log.shouldLog(Log.WARN)) if (log.shouldLog(Log.WARN))