forked from I2P_Developers/i2p.i2p
Data: Initial work on b32 format for blinded leasesets (proposal 149, WIP)
This commit is contained in:
@ -69,10 +69,16 @@ public class DummyNamingService extends NamingService {
|
||||
}
|
||||
|
||||
// Try Base32 decoding
|
||||
if (hostname.length() == BASE32_HASH_LENGTH + 8 && hostname.toLowerCase(Locale.US).endsWith(".b32.i2p") &&
|
||||
if (hostname.length() >= BASE32_HASH_LENGTH + 8 && hostname.toLowerCase(Locale.US).endsWith(".b32.i2p") &&
|
||||
_context.getBooleanPropertyDefaultTrue(PROP_B32)) {
|
||||
try {
|
||||
d = LookupDest.lookupBase32Hash(_context, hostname.substring(0, BASE32_HASH_LENGTH));
|
||||
if (hostname.length() == BASE32_HASH_LENGTH + 8) {
|
||||
// b32
|
||||
d = LookupDest.lookupBase32Hash(_context, hostname.substring(0, BASE32_HASH_LENGTH));
|
||||
} else {
|
||||
// b33
|
||||
d = LookupDest.lookupHostname(_context, hostname);
|
||||
}
|
||||
if (d != null) {
|
||||
putCache(hostname, d);
|
||||
return d;
|
||||
|
@ -59,10 +59,49 @@ class LookupDest {
|
||||
****/
|
||||
|
||||
/** @param h 32 byte hash */
|
||||
static Destination lookupHash(I2PAppContext ctx, byte[] h) throws I2PSessionException {
|
||||
private static Destination lookupHash(I2PAppContext ctx, byte[] h) throws I2PSessionException {
|
||||
Hash key = Hash.create(h);
|
||||
Destination rv = null;
|
||||
I2PClient client = new I2PSimpleClient();
|
||||
Properties opts = getOpts(ctx);
|
||||
I2PSession session = null;
|
||||
try {
|
||||
session = client.createSession(null, opts);
|
||||
session.connect();
|
||||
rv = session.lookupDest(key, DEFAULT_TIMEOUT);
|
||||
} finally {
|
||||
if (session != null)
|
||||
session.destroySession();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Any hostname, but this is for long-format b32
|
||||
*
|
||||
* @param hostname a "b33" hostname, 64+ chars ending with ".b32.i2p"
|
||||
* @since 0.9.40
|
||||
*/
|
||||
static Destination lookupHostname(I2PAppContext ctx, String hostname) throws I2PSessionException {
|
||||
Destination rv = null;
|
||||
I2PClient client = new I2PSimpleClient();
|
||||
Properties opts = getOpts(ctx);
|
||||
I2PSession session = null;
|
||||
try {
|
||||
session = client.createSession(null, opts);
|
||||
session.connect();
|
||||
rv = session.lookupDest(hostname, DEFAULT_TIMEOUT);
|
||||
} finally {
|
||||
if (session != null)
|
||||
session.destroySession();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.40 split out from above
|
||||
*/
|
||||
private static Properties getOpts(I2PAppContext ctx) {
|
||||
Properties opts = new Properties();
|
||||
if (!ctx.isRouterContext()) {
|
||||
String s = ctx.getProperty(I2PClient.PROP_TCP_HOST);
|
||||
@ -81,16 +120,7 @@ class LookupDest {
|
||||
if (s != null)
|
||||
opts.put(PROP_PW, s);
|
||||
}
|
||||
I2PSession session = null;
|
||||
try {
|
||||
session = client.createSession(null, opts);
|
||||
session.connect();
|
||||
rv = session.lookupDest(key, DEFAULT_TIMEOUT);
|
||||
} finally {
|
||||
if (session != null)
|
||||
session.destroySession();
|
||||
}
|
||||
return rv;
|
||||
return opts;
|
||||
}
|
||||
|
||||
public static void main(String args[]) throws I2PSessionException {
|
||||
|
@ -103,7 +103,7 @@ public class MetaNamingService extends DummyNamingService {
|
||||
if (d != null)
|
||||
return d;
|
||||
// Base32 failed?
|
||||
if (hostname.length() == BASE32_HASH_LENGTH + 8 && hostname.toLowerCase(Locale.US).endsWith(".b32.i2p"))
|
||||
if (hostname.length() >= BASE32_HASH_LENGTH + 8 && hostname.toLowerCase(Locale.US).endsWith(".b32.i2p"))
|
||||
return null;
|
||||
|
||||
for (NamingService ns : _services) {
|
||||
|
@ -4,11 +4,15 @@ import java.security.GeneralSecurityException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import java.util.zip.Checksum;
|
||||
import java.util.zip.CRC32;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.eddsa.EdDSABlinding;
|
||||
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey;
|
||||
import net.i2p.data.Base32;
|
||||
import net.i2p.data.BlindData;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
@ -176,13 +180,155 @@ public final class Blinding {
|
||||
return new SigningPrivateKey(TYPER, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* What's the default blinded type for a given unblinded type?
|
||||
*
|
||||
* @return non-null
|
||||
* @since 0.9.40
|
||||
*/
|
||||
public static SigType getDefaultBlindedType(SigType unblindedType) {
|
||||
if (unblindedType == TYPE)
|
||||
return TYPER;
|
||||
return unblindedType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a new-format b32 address.
|
||||
* PRELIMINARY - Subject to change - see proposal 149
|
||||
*
|
||||
* @param address ending with ".b32.i2p"
|
||||
* @throws IllegalArgumentException on bad inputs
|
||||
* @throws UnsupportedOperationException unless supported SigTypes
|
||||
* @since 0.9.40
|
||||
*/
|
||||
public static BlindData decode(I2PAppContext ctx, String address) throws RuntimeException {
|
||||
address = address.toLowerCase(Locale.US);
|
||||
if (!address.endsWith(".b32.i2p"))
|
||||
throw new IllegalArgumentException("Not a .b32.i2p address");
|
||||
byte[] b = Base32.decode(address.substring(0, address.length() - 8));
|
||||
if (b == null)
|
||||
throw new IllegalArgumentException("Bad base32 encoding");
|
||||
if (b.length < 35)
|
||||
throw new IllegalArgumentException("Not a new-format address");
|
||||
return decode(ctx, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a new-format b32 address.
|
||||
* PRELIMINARY - Subject to change - see proposal 149
|
||||
*
|
||||
* @param b 35+ bytes
|
||||
* @throws IllegalArgumentException on bad inputs
|
||||
* @throws UnsupportedOperationException unless supported SigTypes
|
||||
* @since 0.9.40
|
||||
*/
|
||||
public static BlindData decode(I2PAppContext ctx, byte[] b) throws RuntimeException {
|
||||
Checksum crc = new CRC32();
|
||||
crc.update(b, 3, b.length - 3);
|
||||
long check = crc.getValue();
|
||||
b[0] ^= (byte) check;
|
||||
b[1] ^= (byte) (check >> 8);
|
||||
b[2] ^= (byte) (check >> 16);
|
||||
int flag = b[0] & 0xff;
|
||||
if ((flag & 0xf8) != 0)
|
||||
throw new IllegalArgumentException("Corrupt b32 or unsupported options");
|
||||
if ((flag & 0x01) != 0)
|
||||
throw new IllegalArgumentException("Two byte sig types unsupported");
|
||||
if ((flag & 0x04) != 0)
|
||||
throw new IllegalArgumentException("Per-client auth unsupported");
|
||||
// TODO two-byte sigtypes
|
||||
int st1 = b[1] & 0xff;
|
||||
int st2 = b[2] & 0xff;
|
||||
SigType sigt1 = SigType.getByCode(st1);
|
||||
SigType sigt2 = SigType.getByCode(st2);
|
||||
if (sigt1 == null)
|
||||
throw new IllegalArgumentException("Unknown sig type " + st1);
|
||||
if (!sigt1.isAvailable())
|
||||
throw new IllegalArgumentException("Unavailable sig type " + sigt1);
|
||||
if (sigt2 == null)
|
||||
throw new IllegalArgumentException("Unknown blinded sig type " + st2);
|
||||
if (!sigt2.isAvailable())
|
||||
throw new IllegalArgumentException("Unavailable blinded sig type " + sigt2);
|
||||
// todo secret/privkey
|
||||
int spkLen = sigt1.getPubkeyLen();
|
||||
if (3 + spkLen > b.length)
|
||||
throw new IllegalArgumentException("b32 too short");
|
||||
byte[] spkData = new byte[spkLen];
|
||||
System.arraycopy(b, 3, spkData, 0, spkLen);
|
||||
SigningPublicKey spk = new SigningPublicKey(sigt1, spkData);
|
||||
String secret;
|
||||
if ((flag & 0x02) != 0) {
|
||||
if (4 + spkLen > b.length)
|
||||
throw new IllegalArgumentException("No secret data");
|
||||
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");
|
||||
} else {
|
||||
secret = null;
|
||||
}
|
||||
BlindData rv = new BlindData(ctx, spk, sigt2, secret);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, String secret) throws RuntimeException {
|
||||
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");
|
||||
byte[] d = key.getData();
|
||||
byte[] b = new byte[d.length + slen + 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)
|
||||
b[0] = 0x02;
|
||||
b[1] = (byte) (type.getCode() & 0xff);
|
||||
b[2] = (byte) (TYPER.getCode() & 0xff);
|
||||
b[0] ^= (byte) check;
|
||||
b[1] ^= (byte) (check >> 8);
|
||||
b[2] ^= (byte) (check >> 16);
|
||||
// todo privkey
|
||||
return Base32.encode(b) + ".b32.i2p";
|
||||
}
|
||||
|
||||
/******
|
||||
public static void main(String args[]) throws Exception {
|
||||
net.i2p.data.SimpleDataStructure[] keys = KeyGenerator.getInstance().generateSigningKeys(TYPE);
|
||||
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");
|
||||
System.out.println("pub b32 is " + b32);
|
||||
BlindData bd = decode(ctx, b32);
|
||||
if (bd.getBlindedPubKey().equals(pub))
|
||||
System.out.println("B32 test failed");
|
||||
else
|
||||
System.out.println("B32 test passed");
|
||||
byte[] b = new byte[64];
|
||||
net.i2p.I2PAppContext.getGlobalContext().random().nextBytes(b);
|
||||
ctx.random().nextBytes(b);
|
||||
b = EdDSABlinding.reduce(b);
|
||||
SigningPrivateKey alpha = new SigningPrivateKey(TYPER, b);
|
||||
SigningPublicKey bpub = null;
|
||||
|
130
core/java/src/net/i2p/data/BlindData.java
Normal file
130
core/java/src/net/i2p/data/BlindData.java
Normal file
@ -0,0 +1,130 @@
|
||||
package net.i2p.data;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.Blinding;
|
||||
import net.i2p.crypto.SigType;
|
||||
|
||||
/**
|
||||
* Cache data for Blinding EdDSA keys.
|
||||
* PRELIMINARY - Subject to change - see proposal 123
|
||||
*
|
||||
* @since 0.9.40
|
||||
*/
|
||||
public class BlindData {
|
||||
|
||||
private final I2PAppContext _context;
|
||||
private final SigningPublicKey _clearSPK;
|
||||
private final String _secret;
|
||||
private SigningPublicKey _blindSPK;
|
||||
private final SigType _blindType;
|
||||
private final int _authType;
|
||||
private final PrivateKey _authKey;
|
||||
private Hash _blindHash;
|
||||
private SigningPrivateKey _alpha;
|
||||
private Destination _dest;
|
||||
private long _routingKeyGenMod;
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException on various errors
|
||||
*/
|
||||
public BlindData(I2PAppContext ctx, Destination dest, SigType blindType, String secret) {
|
||||
this(ctx, dest.getSigningPublicKey(), blindType, secret);
|
||||
_dest = dest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException on various errors
|
||||
*/
|
||||
public BlindData(I2PAppContext ctx, SigningPublicKey spk, SigType blindType, String secret) {
|
||||
_context = ctx;
|
||||
_clearSPK = spk;
|
||||
_blindType = blindType;
|
||||
_secret = secret;
|
||||
_authType = 0;
|
||||
_authKey = null;
|
||||
// defer until needed
|
||||
//calculate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The blinded key for the current day
|
||||
*/
|
||||
public synchronized SigningPublicKey getBlindedPubKey() {
|
||||
calculate();
|
||||
return _blindSPK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The hash of the blinded key for the current day
|
||||
*/
|
||||
public synchronized Hash getBlindedHash() {
|
||||
calculate();
|
||||
return _blindHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Alpha for the current day
|
||||
*/
|
||||
public synchronized SigningPrivateKey getAlpha() {
|
||||
calculate();
|
||||
return _alpha;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null if unknown
|
||||
*/
|
||||
public synchronized Destination getDestination() {
|
||||
return _dest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException on SigningPublicKey mismatch
|
||||
*/
|
||||
public synchronized void setDestination(Destination d) {
|
||||
if (_dest != null) {
|
||||
if (!_dest.equals(d))
|
||||
throw new IllegalArgumentException("Dest mismatch");
|
||||
return;
|
||||
}
|
||||
if (!d.getSigningPublicKey().equals(_clearSPK))
|
||||
throw new IllegalArgumentException("Dest mismatch");
|
||||
_dest = d;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null if none
|
||||
*/
|
||||
public String getSecret() {
|
||||
return _secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 0 for no client auth
|
||||
*/
|
||||
public int getAuthType() {
|
||||
return _authType;
|
||||
}
|
||||
|
||||
private synchronized void calculate() {
|
||||
if (_context.isRouterContext()) {
|
||||
RoutingKeyGenerator gen = _context.routingKeyGenerator();
|
||||
long mod = gen.getLastChanged();
|
||||
if (mod == _routingKeyGenMod)
|
||||
return;
|
||||
_routingKeyGenMod = mod;
|
||||
}
|
||||
// For now, always calculate in app context,
|
||||
// where we don't have a routingKeyGenerator
|
||||
// TODO we could cache based on current day
|
||||
_alpha = Blinding.generateAlpha(_context, _clearSPK, _secret);
|
||||
_blindSPK = Blinding.blind(_clearSPK, _alpha);
|
||||
SigType bsigt2 = _blindSPK.getType();
|
||||
if (_blindType != bsigt2) {
|
||||
throw new IllegalArgumentException("Requested blinded sig type " + _blindType + " supported type " + bsigt2);
|
||||
}
|
||||
byte[] hashData = new byte[2 + Hash.HASH_LENGTH];
|
||||
DataHelper.toLong(hashData, 0, 2, _blindType.getCode());
|
||||
System.arraycopy(_blindSPK.getData(), 0, hashData, 2, _blindSPK.length());
|
||||
_blindHash = _context.sha().calculateHash(hashData);
|
||||
}
|
||||
}
|
@ -20,12 +20,14 @@ import com.nettgryppa.security.HashCash;
|
||||
|
||||
import gnu.getopt.Getopt;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.client.naming.HostTxtEntry;
|
||||
import net.i2p.crypto.Blinding;
|
||||
import net.i2p.crypto.DSAEngine;
|
||||
import net.i2p.crypto.KeyGenerator;
|
||||
import net.i2p.crypto.SigType;
|
||||
@ -755,6 +757,15 @@ public class PrivateKeyFile {
|
||||
s.append(this.dest != null ? this.dest.toBase64() : "null");
|
||||
s.append("\nB32: ");
|
||||
s.append(this.dest != null ? this.dest.toBase32() : "null");
|
||||
if (dest != null) {
|
||||
SigningPublicKey spk = dest.getSigningPublicKey();
|
||||
SigType type = spk.getType();
|
||||
if (type == SigType.EdDSA_SHA512_Ed25519 ||
|
||||
type == SigType.RedDSA_SHA512_Ed25519) {
|
||||
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||
s.append("\nBlinded B32: ").append(Blinding.encode(ctx, spk, null));
|
||||
}
|
||||
}
|
||||
s.append("\nContains: ");
|
||||
s.append(this.dest);
|
||||
s.append("\nPrivate Key: ");
|
||||
|
Reference in New Issue
Block a user