diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java index 7e45ca9dc4..b09beceaa8 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java @@ -564,13 +564,13 @@ public class IndexBean { Destination d = getDestination(tunnel); if (d != null) { int mode = _helper.getEncryptMode(tunnel); - if (mode > 1) { + if (mode > 1 && mode < 10) { try { String secret = _helper.getBlindedPassword(tunnel); boolean requireSecret = secret != null && secret.length() > 0 && (mode == 3 || mode == 5 || mode == 7 || 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) {} } } diff --git a/apps/i2ptunnel/jsp/editServer.jsi b/apps/i2ptunnel/jsp/editServer.jsi index b949e27a8f..a8370f18e7 100644 --- a/apps/i2ptunnel/jsp/editServer.jsi +++ b/apps/i2ptunnel/jsp/editServer.jsi @@ -462,13 +462,10 @@ %> -<% - // TODO, unimplemented -%> + <%=intl._t("Blinded with shared key")%> (PSK) + <%=intl._t("Blinded with lookup password and shared key")%> (PSK) - - diff --git a/core/java/src/net/i2p/crypto/Blinding.java b/core/java/src/net/i2p/crypto/Blinding.java index 1752f9897f..4fe04b2809 100644 --- a/core/java/src/net/i2p/crypto/Blinding.java +++ b/core/java/src/net/i2p/crypto/Blinding.java @@ -53,14 +53,13 @@ public final class Blinding { * @param key must be SigType EdDSA_SHA512_Ed25519 or RedDSA_SHA512_Ed25519 * @param alpha must be SigType RedDSA_SHA512_Ed25519 * @return SigType RedDSA_SHA512_Ed25519 - * @throws UnsupportedOperationException unless supported SigTypes - * @throws IllegalArgumentException on bad inputs + * @throws IllegalArgumentException on bad inputs or unsupported SigTypes */ public static SigningPublicKey blind(SigningPublicKey key, SigningPrivateKey alpha) { SigType type = key.getType(); if ((type != TYPE && type != TYPER) || alpha.getType() != TYPER) - throw new UnsupportedOperationException(); + throw new IllegalArgumentException("Unsupported blinding from " + type + " to " + alpha.getType()); try { EdDSAPublicKey jk = SigUtil.toJavaEdDSAKey(key); 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 alpha must be SigType RedDSA_SHA512_Ed25519 * @return SigType RedDSA_SHA512_Ed25519 - * @throws UnsupportedOperationException unless supported SigTypes - * @throws IllegalArgumentException on bad inputs + * @throws IllegalArgumentException on bad inputs or unsupported SigTypes */ public static SigningPrivateKey blind(SigningPrivateKey key, SigningPrivateKey alpha) { SigType type = key.getType(); if ((type != TYPE && type != TYPER) || alpha.getType() != TYPER) - throw new UnsupportedOperationException(); + throw new IllegalArgumentException("Unsupported blinding from " + type + " to " + alpha.getType()); try { EdDSAPrivateKey jk = SigUtil.toJavaEdDSAKey(key); EdDSAPrivateKey ajk = SigUtil.toJavaEdDSAKey(alpha); @@ -101,12 +99,11 @@ public final class Blinding { * @param key must be SigType RedDSA_SHA512_Ed25519 * @param alpha must be SigType RedDSA_SHA512_Ed25519 * @return SigType EdDSA_SHA512_Ed25519 - * @throws UnsupportedOperationException unless supported SigTypes - * @throws IllegalArgumentException on bad inputs + * @throws IllegalArgumentException on bad inputs or unsupported SigTypes */ public static SigningPrivateKey unblind(SigningPrivateKey key, SigningPrivateKey alpha) { if (key.getType() != TYPER || alpha.getType() != TYPER) - throw new UnsupportedOperationException(); + throw new IllegalArgumentException("Unsupported blinding from " + key.getType() + " / " + alpha.getType()); try { EdDSAPrivateKey bjk = SigUtil.toJavaEdDSAKey(key); EdDSAPrivateKey ajk = SigUtil.toJavaEdDSAKey(alpha); @@ -124,8 +121,7 @@ public final class Blinding { * @param destspk must be SigType EdDSA_SHA512_Ed25519 * @param secret may be null or zero-length * @return SigType RedDSA_SHA512_Ed25519 - * @throws UnsupportedOperationException unless supported SigTypes - * @throws IllegalArgumentException on bad inputs + * @throws IllegalArgumentException on bad inputs or unsupported SigTypes * @since 0.9.39 */ 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 now for what time? * @return SigType RedDSA_SHA512_Ed25519 - * @throws UnsupportedOperationException unless supported SigTypes - * @throws IllegalArgumentException on bad inputs + * @throws IllegalArgumentException on bad inputs or unsupported SigTypes * @since 0.9.39 */ public static SigningPrivateKey generateAlpha(I2PAppContext ctx, SigningPublicKey destspk, String secret, long now) { + SigType type = destspk.getType(); + if (type != TYPE && type != TYPER) + throw new IllegalArgumentException("Unsupported blinding from " + type); String modVal; synchronized(_fmt) { modVal = _fmt.format(now); @@ -170,7 +168,7 @@ public final class Blinding { // SHA256("I2PGenerateAlpha" || spk || sigtypein || sigtypeout) System.arraycopy(INFO_ALPHA, 0, in, 0, INFO_ALPHA.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()); Hash salt = ctx.sha().calculateHash(in); hkdf.calculate(salt.getData(), data, INFO, out, out, 32); @@ -198,14 +196,13 @@ public final class Blinding { /** * Decode a new-format b32 address. - * PRELIMINARY - Subject to change - see proposal 149 + * See proposal 149. * * @param address ending with ".b32.i2p" - * @throws IllegalArgumentException on bad inputs - * @throws UnsupportedOperationException unless supported SigTypes + * @throws IllegalArgumentException on bad inputs or unsupported SigTypes * @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); if (!address.endsWith(".b32.i2p")) throw new IllegalArgumentException("Not a .b32.i2p address"); @@ -219,15 +216,15 @@ public final class Blinding { /** * 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 * @return BlindData structure, use getUnblindedPubKey() for the result - * @throws IllegalArgumentException on bad inputs - * @throws UnsupportedOperationException unless supported SigTypes + * @throws IllegalArgumentException on bad inputs or unsupported SigTypes * @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(); crc.update(b, 3, b.length - 3); long check = crc.getValue(); @@ -239,8 +236,6 @@ public final class Blinding { throw new IllegalArgumentException("Corrupt b32 or unsupported options"); if ((flag & FLAG_TWOBYTE) != 0) throw new IllegalArgumentException("Two byte sig types unsupported"); - if ((flag & FLAG_AUTH) != 0) - throw new IllegalArgumentException("Per-client auth unsupported"); // TODO two-byte sigtypes int st1 = b[1] & 0xff; int st2 = b[2] & 0xff; @@ -261,85 +256,49 @@ public final class Blinding { byte[] spkData = new byte[spkLen]; System.arraycopy(b, 3, spkData, 0, spkLen); SigningPublicKey spk = new SigningPublicKey(sigt1, spkData); - String secret; - 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) { + if (3 + spkLen != b.length) throw new IllegalArgumentException("b32 too long"); - } else { - secret = null; - } - BlindData rv = new BlindData(ctx, spk, sigt2, secret); + BlindData rv = new BlindData(ctx, spk, sigt2, null); + if ((flag & FLAG_SECRET) != 0) + rv.setSecretRequired(); + if ((flag & FLAG_AUTH) != 0) + rv.setAuthRequired(); return rv; } /** * 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 - * @throws IllegalArgumentException on bad inputs - * @throws UnsupportedOperationException unless supported SigTypes + * @throws IllegalArgumentException on bad inputs or unsupported SigTypes * @since 0.9.40 */ - public static String encode(I2PAppContext ctx, SigningPublicKey key) throws RuntimeException { - return encode(ctx, key, false, false, null); + public static String encode(SigningPublicKey key) throws IllegalArgumentException { + return encode(key, false, false); } /** * 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 - * @throws IllegalArgumentException on bad inputs - * @throws UnsupportedOperationException unless supported SigTypes + * @throws IllegalArgumentException on bad inputs or unsupported SigTypes * @since 0.9.40 */ - public static String encode(I2PAppContext ctx, SigningPublicKey key, - boolean requireSecret, boolean requireAuth) throws RuntimeException { - 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 { + public static String encode(SigningPublicKey key, + boolean requireSecret, boolean requireAuth) throws IllegalArgumentException { SigType type = key.getType(); if (type != TYPE && type != TYPER) - throw new UnsupportedOperationException(); - 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"); + throw new IllegalArgumentException("Unsupported blinding from " + type); 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); - if (slen > 0) { - b[3 + d.length] = (byte) sdata.length; - System.arraycopy(sdata, 0, b, 4 + d.length, sdata.length); - } Checksum crc = new CRC32(); crc.update(b, 3, b.length - 3); long check = crc.getValue(); // TODO two-byte sigtypes - if (slen > 0 || requireSecret) + if (requireSecret) b[0] = FLAG_SECRET; if (requireAuth) b[0] |= FLAG_AUTH; @@ -367,8 +326,8 @@ public final class Blinding { SigningPublicKey pub = (SigningPublicKey) keys[0]; SigningPrivateKey priv = (SigningPrivateKey) keys[1]; I2PAppContext ctx = I2PAppContext.getGlobalContext(); - //String b32 = encode(ctx, pub, null); - String b32 = encode(ctx, pub, "foobarbaz"); + //String b32 = encode(pub, null); + String b32 = encode(pub, "foobarbaz"); System.out.println("pub b32 is " + b32); BlindData bd = decode(ctx, b32); if (bd.getBlindedPubKey().equals(pub)) diff --git a/core/java/src/net/i2p/data/BlindData.java b/core/java/src/net/i2p/data/BlindData.java index 7c97201f71..933bef8217 100644 --- a/core/java/src/net/i2p/data/BlindData.java +++ b/core/java/src/net/i2p/data/BlindData.java @@ -23,6 +23,8 @@ public class BlindData { private SigningPrivateKey _alpha; private Destination _dest; private long _routingKeyGenMod; + private boolean _secretRequired; + private boolean _authRequired; /** * bits 3-0 including per-client bit @@ -39,6 +41,11 @@ public class BlindData { * @since 0.9.41 */ 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 @@ -83,6 +90,10 @@ public class BlindData { throw new IllegalArgumentException(); _authType = authType; _authKey = authKey; + if (secret != null) + _secretRequired = true; + if (authKey != null) + _authRequired = true; // defer until needed //calculate(); } @@ -196,7 +207,43 @@ public class BlindData { System.arraycopy(_blindSPK.getData(), 0, hashData, 2, _blindSPK.length()); _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 public synchronized String toString() { calculate(); @@ -219,6 +266,7 @@ public class BlindData { buf.append("\n\tDestination: ").append(_dest); else buf.append("\n\tDestination: unknown"); + buf.append("\n\tB32 : ").append(toBase32()); buf.append(']'); return buf.toString(); } diff --git a/core/java/src/net/i2p/data/PrivateKeyFile.java b/core/java/src/net/i2p/data/PrivateKeyFile.java index ca14e64bb3..866b7125a0 100644 --- a/core/java/src/net/i2p/data/PrivateKeyFile.java +++ b/core/java/src/net/i2p/data/PrivateKeyFile.java @@ -769,7 +769,7 @@ public class PrivateKeyFile { if (type == SigType.EdDSA_SHA512_Ed25519 || type == SigType.RedDSA_SHA512_Ed25519) { I2PAppContext ctx = I2PAppContext.getGlobalContext(); - s.append("\nBlinded B32: ").append(Blinding.encode(ctx, spk)); + s.append("\nBlinded B32: ").append(Blinding.encode(spk)); } } s.append("\nContains: "); diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/BlindCache.java b/router/java/src/net/i2p/router/networkdb/kademlia/BlindCache.java index 11a6470072..8be87f86d3 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/BlindCache.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/BlindCache.java @@ -168,7 +168,19 @@ class BlindCache { } } + /** + * Persists immediately if secret or privkey is non-null + */ 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); _reverseCache.put(bd.getBlindedPubKey(), bd); Destination dest = bd.getDestination(); @@ -257,7 +269,7 @@ class BlindCache { if (line.startsWith("#")) continue; try { - addToCache(fromPersistentString(line)); + storeInCache(fromPersistentString(line)); count++; } catch (IllegalArgumentException iae) { if (log.shouldLog(Log.WARN))