From 5842e2520544077988a5b698b365e665f4548b18 Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 24 Dec 2013 16:41:05 +0000 Subject: [PATCH 1/9] Initial support for key certificates and arbitrary types and lengths of signing keys and signatures in RouterIdentities and Destinations. Untested, not even for regressions, except with command line using PrivateKeyFile. Based on preliminary spec at http://zzz.i2p/topics/1442?page=1#p7524 Not done: - Transport handshake signing - Configuration of default type - Specification of type in options to I2PSocketManagerFactory - Specification of type in i2ptunnel - Fix up caching of SigningPublicKey and P256 key cert - Any non-default crypto type in the key cert - Documentation --- .../src/net/i2p/client/streaming/Packet.java | 61 +++-- core/java/src/net/i2p/client/I2PClient.java | 13 + .../src/net/i2p/client/I2PClientImpl.java | 63 ++++- .../src/net/i2p/client/I2PSessionImpl.java | 3 +- .../src/net/i2p/client/I2PSimpleClient.java | 26 +- .../client/RequestLeaseSetMessageHandler.java | 10 + .../client/datagram/I2PDatagramDissector.java | 6 +- core/java/src/net/i2p/crypto/SU3File.java | 21 +- core/java/src/net/i2p/crypto/SigType.java | 21 ++ core/java/src/net/i2p/data/Certificate.java | 53 ++++ core/java/src/net/i2p/data/Destination.java | 14 +- .../java/src/net/i2p/data/KeyCertificate.java | 252 ++++++++++++++++++ core/java/src/net/i2p/data/KeysAndCert.java | 33 ++- core/java/src/net/i2p/data/LeaseSet.java | 19 +- .../java/src/net/i2p/data/PrivateKeyFile.java | 60 ++++- core/java/src/net/i2p/data/RouterInfo.java | 2 +- core/java/src/net/i2p/data/Signature.java | 16 +- .../src/net/i2p/data/SigningPrivateKey.java | 5 +- .../src/net/i2p/data/SigningPublicKey.java | 89 ++++++- .../i2p/data/i2cp/CreateLeaseSetMessage.java | 7 +- .../src/net/i2p/data/i2cp/SessionConfig.java | 2 +- 21 files changed, 698 insertions(+), 78 deletions(-) create mode 100644 core/java/src/net/i2p/data/KeyCertificate.java diff --git a/apps/streaming/java/src/net/i2p/client/streaming/Packet.java b/apps/streaming/java/src/net/i2p/client/streaming/Packet.java index 6fbe1dcce..1ecdc0771 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/Packet.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/Packet.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.util.Arrays; import net.i2p.I2PAppContext; +import net.i2p.crypto.SigType; import net.i2p.data.Base64; import net.i2p.data.ByteArray; import net.i2p.data.DataFormatException; @@ -436,8 +437,9 @@ class Packet { optionSize += _optionFrom.size(); if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED)) optionSize += 2; - if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) - optionSize += Signature.SIGNATURE_BYTES; + if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) { + optionSize += _optionSignature.length(); + } DataHelper.toLong(buffer, cur, 2, optionSize); cur += 2; @@ -455,10 +457,10 @@ class Packet { } if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) { if (includeSig) - System.arraycopy(_optionSignature.getData(), 0, buffer, cur, Signature.SIGNATURE_BYTES); + System.arraycopy(_optionSignature.getData(), 0, buffer, cur, _optionSignature.length()); else // we're signing (or validating) - Arrays.fill(buffer, cur, cur+Signature.SIGNATURE_BYTES, (byte)0x0); - cur += Signature.SIGNATURE_BYTES; + Arrays.fill(buffer, cur, cur + _optionSignature.length(), (byte)0x0); + cur += _optionSignature.length(); } if (_payload != null) { @@ -511,7 +513,7 @@ class Packet { if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED)) size += 2; if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) - size += Signature.SIGNATURE_BYTES; + size += _optionSignature.length(); size += 2; // option size @@ -606,12 +608,37 @@ class Packet { cur += 2; } if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) { - Signature optionSignature = new Signature(); - byte buf[] = new byte[Signature.SIGNATURE_BYTES]; - System.arraycopy(buffer, cur, buf, 0, Signature.SIGNATURE_BYTES); + Signature optionSignature; + Destination from = getOptionalFrom(); + if (from != null) { + optionSignature = new Signature(from.getSigningPublicKey().getType()); + } else { + // super cheat for now, look for correct type, + // assume no more options. If we add to the options + // we will have to ask the manager. + int siglen = payloadBegin - cur; + SigType type = null; + for (SigType t : SigType.values()) { + if (t.getSigLen() == siglen) { + type = t; + break; + } + } + if (type == null) { + if (siglen < Signature.SIGNATURE_BYTES) + throw new IllegalArgumentException("unknown sig type len=" + siglen); + // Hope it's the default type with some unknown options following; + // if not the sig will fail later + type = SigType.DSA_SHA1; + siglen = Signature.SIGNATURE_BYTES; + } + optionSignature = new Signature(type); + } + byte buf[] = new byte[optionSignature.length()]; + System.arraycopy(buffer, cur, buf, 0, buf.length); optionSignature.setData(buf); setOptionalSignature(optionSignature); - cur += Signature.SIGNATURE_BYTES; + cur += buf.length; } } @@ -667,12 +694,12 @@ class Packet { setFlag(FLAG_SIGNATURE_INCLUDED); int size = writePacket(buffer, offset, false); _optionSignature = ctx.dsa().sign(buffer, offset, size, key); - if (false) { - Log l = ctx.logManager().getLog(Packet.class); - l.error("Signing: " + toString()); - l.error(Base64.encode(buffer, 0, size)); - l.error("Signature: " + Base64.encode(_optionSignature.getData())); - } + //if (false) { + // Log l = ctx.logManager().getLog(Packet.class); + // l.error("Signing: " + toString()); + // l.error(Base64.encode(buffer, 0, size)); + // l.error("Signature: " + Base64.encode(_optionSignature.getData())); + //} // jump into the signed data and inject the signature where we // previously placed a bunch of zeroes int signatureOffset = offset @@ -687,7 +714,7 @@ class Packet { + (isFlagSet(FLAG_DELAY_REQUESTED) ? 2 : 0) + (isFlagSet(FLAG_FROM_INCLUDED) ? _optionFrom.size() : 0) + (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED) ? 2 : 0); - System.arraycopy(_optionSignature.getData(), 0, buffer, signatureOffset, Signature.SIGNATURE_BYTES); + System.arraycopy(_optionSignature.getData(), 0, buffer, signatureOffset, _optionSignature.length()); return size; } diff --git a/core/java/src/net/i2p/client/I2PClient.java b/core/java/src/net/i2p/client/I2PClient.java index 521ed35b5..84bcf50d2 100644 --- a/core/java/src/net/i2p/client/I2PClient.java +++ b/core/java/src/net/i2p/client/I2PClient.java @@ -15,6 +15,7 @@ import java.io.OutputStream; import java.util.Properties; import net.i2p.I2PException; +import net.i2p.crypto.SigType; import net.i2p.data.Certificate; import net.i2p.data.Destination; @@ -83,6 +84,18 @@ public interface I2PClient { */ public Destination createDestination(OutputStream destKeyStream) throws I2PException, IOException; + /** + * Create a destination with the given signature type. + * It will have a null certificate for DSA 1024/160 and KeyCertificate otherwise. + * This is not bound to the I2PClient, you must supply the data back again + * in createSession(). + * + * @param destKeyStream location to write out the destination, PrivateKey, and SigningPrivateKey, + * format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile} + * @since 0.9.11 + */ + public Destination createDestination(OutputStream destKeyStream, SigType type) throws I2PException, IOException; + /** Create a new destination with the given certificate and store it, along with the private * encryption and signing keys at the specified location * diff --git a/core/java/src/net/i2p/client/I2PClientImpl.java b/core/java/src/net/i2p/client/I2PClientImpl.java index 932daa61a..7fac17e10 100644 --- a/core/java/src/net/i2p/client/I2PClientImpl.java +++ b/core/java/src/net/i2p/client/I2PClientImpl.java @@ -12,17 +12,22 @@ package net.i2p.client; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.security.GeneralSecurityException; import java.util.Properties; import net.i2p.I2PAppContext; import net.i2p.I2PException; import net.i2p.crypto.KeyGenerator; +import net.i2p.crypto.SigType; import net.i2p.data.Certificate; import net.i2p.data.Destination; +import net.i2p.data.KeyCertificate; import net.i2p.data.PrivateKey; import net.i2p.data.PublicKey; import net.i2p.data.SigningPrivateKey; import net.i2p.data.SigningPublicKey; +import net.i2p.data.SimpleDataStructure; +import net.i2p.util.RandomSource; /** * Base client implementation. @@ -34,7 +39,7 @@ import net.i2p.data.SigningPublicKey; class I2PClientImpl implements I2PClient { /** - * Create the destination with a null payload. + * Create a destination with a DSA 1024/160 signature type and a null certificate. * This is not bound to the I2PClient, you must supply the data back again * in createSession(). * @@ -42,9 +47,26 @@ class I2PClientImpl implements I2PClient { * format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile} */ public Destination createDestination(OutputStream destKeyStream) throws I2PException, IOException { - Certificate cert = new Certificate(); - cert.setCertificateType(Certificate.CERTIFICATE_TYPE_NULL); - cert.setPayload(null); + return createDestination(destKeyStream, SigType.DSA_SHA1); + } + + /** + * Create a destination with the given signature type. + * It will have a null certificate for DSA 1024/160 and KeyCertificate otherwise. + * This is not bound to the I2PClient, you must supply the data back again + * in createSession(). + * + * @param destKeyStream location to write out the destination, PrivateKey, and SigningPrivateKey, + * format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile} + * @since 0.9.11 + */ + public Destination createDestination(OutputStream destKeyStream, SigType type) throws I2PException, IOException { + Certificate cert; + if (type == SigType.DSA_SHA1) { + cert = Certificate.NULL_CERT; + } else { + cert = new KeyCertificate(type); + } return createDestination(destKeyStream, cert); } @@ -52,20 +74,49 @@ class I2PClientImpl implements I2PClient { * Create the destination with the given payload and write it out along with * the PrivateKey and SigningPrivateKey to the destKeyStream * + * If cert is a KeyCertificate, the signing keypair will be of the specified type. + * The KeyCertificate data must be ............................. + * The padding if any will be randomized. The extra key data if any will be set in the + * key cert. + * * @param destKeyStream location to write out the destination, PrivateKey, and SigningPrivateKey, * format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile} */ public Destination createDestination(OutputStream destKeyStream, Certificate cert) throws I2PException, IOException { Destination d = new Destination(); - d.setCertificate(cert); Object keypair[] = KeyGenerator.getInstance().generatePKIKeypair(); PublicKey publicKey = (PublicKey) keypair[0]; PrivateKey privateKey = (PrivateKey) keypair[1]; - Object signingKeys[] = KeyGenerator.getInstance().generateSigningKeypair(); + SimpleDataStructure signingKeys[]; + if (cert.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY) { + KeyCertificate kcert = cert.toKeyCertificate(); + SigType type = kcert.getSigType(); + try { + signingKeys = KeyGenerator.getInstance().generateSigningKeys(type); + } catch (GeneralSecurityException gse) { + throw new I2PException("keygen fail", gse); + } + } else { + signingKeys = KeyGenerator.getInstance().generateSigningKeys(); + } SigningPublicKey signingPubKey = (SigningPublicKey) signingKeys[0]; SigningPrivateKey signingPrivKey = (SigningPrivateKey) signingKeys[1]; d.setPublicKey(publicKey); d.setSigningPublicKey(signingPubKey); + if (cert.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY) { + // fix up key certificate or padding + KeyCertificate kcert = cert.toKeyCertificate(); + SigType type = kcert.getSigType(); + int len = type.getPubkeyLen(); + if (len < 128) { + byte[] pad = new byte[128 - len]; + RandomSource.getInstance().nextBytes(pad); + d.setPadding(pad); + } else if (len > 128) { + System.arraycopy(signingPubKey.getData(), 128, kcert.getPayload(), KeyCertificate.HEADER_LENGTH, len - 128); + } + } + d.setCertificate(cert); d.writeBytes(destKeyStream); privateKey.writeBytes(destKeyStream); diff --git a/core/java/src/net/i2p/client/I2PSessionImpl.java b/core/java/src/net/i2p/client/I2PSessionImpl.java index 8cf7ad095..cfacb4cec 100644 --- a/core/java/src/net/i2p/client/I2PSessionImpl.java +++ b/core/java/src/net/i2p/client/I2PSessionImpl.java @@ -62,7 +62,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa /** private key for decryption */ private final PrivateKey _privateKey; /** private key for signing */ - private final SigningPrivateKey _signingPrivateKey; + private /* final */ SigningPrivateKey _signingPrivateKey; /** configuration options */ private final Properties _options; /** this session's Id */ @@ -373,6 +373,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa private void readDestination(InputStream destKeyStream) throws DataFormatException, IOException { _myDestination.readBytes(destKeyStream); _privateKey.readBytes(destKeyStream); + _signingPrivateKey = new SigningPrivateKey(_myDestination.getSigningPublicKey().getType()); _signingPrivateKey.readBytes(destKeyStream); } diff --git a/core/java/src/net/i2p/client/I2PSimpleClient.java b/core/java/src/net/i2p/client/I2PSimpleClient.java index e91ae2f8b..8e198daf9 100644 --- a/core/java/src/net/i2p/client/I2PSimpleClient.java +++ b/core/java/src/net/i2p/client/I2PSimpleClient.java @@ -12,6 +12,7 @@ import java.util.Properties; import net.i2p.I2PAppContext; import net.i2p.I2PException; +import net.i2p.crypto.SigType; import net.i2p.data.Certificate; import net.i2p.data.Destination; @@ -20,14 +21,30 @@ import net.i2p.data.Destination; * just used to talk to the router. */ public class I2PSimpleClient implements I2PClient { - /** @deprecated Don't do this */ + + /** + * @deprecated Don't do this + * @throws UnsupportedOperationException always + */ public Destination createDestination(OutputStream destKeyStream) throws I2PException, IOException { - return null; + throw new UnsupportedOperationException(); } - /** @deprecated or this */ + /** + * @deprecated Don't do this + * @throws UnsupportedOperationException always + * @since 0.9.11 + */ + public Destination createDestination(OutputStream destKeyStream, SigType type) throws I2PException, IOException { + throw new UnsupportedOperationException(); + } + + /** + * @deprecated Don't do this + * @throws UnsupportedOperationException always + */ public Destination createDestination(OutputStream destKeyStream, Certificate cert) throws I2PException, IOException { - return null; + throw new UnsupportedOperationException(); } /** @@ -37,6 +54,7 @@ public class I2PSimpleClient implements I2PClient { public I2PSession createSession(InputStream destKeyStream, Properties options) throws I2PSessionException { return createSession(I2PAppContext.getGlobalContext(), options); } + /** * Create a new session (though do not connect it yet) * diff --git a/core/java/src/net/i2p/client/RequestLeaseSetMessageHandler.java b/core/java/src/net/i2p/client/RequestLeaseSetMessageHandler.java index 620f571f4..09f224efc 100644 --- a/core/java/src/net/i2p/client/RequestLeaseSetMessageHandler.java +++ b/core/java/src/net/i2p/client/RequestLeaseSetMessageHandler.java @@ -14,6 +14,7 @@ import java.util.concurrent.ConcurrentHashMap; import net.i2p.I2PAppContext; import net.i2p.crypto.KeyGenerator; +import net.i2p.crypto.SigType; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.Destination; @@ -105,6 +106,15 @@ class RequestLeaseSetMessageHandler extends HandlerImpl { } try { leaseSet.sign(session.getPrivateKey()); + // Workaround for unparsable serialized signing private key for revocation + // Send him a dummy DSA_SHA1 private key since it's unused anyway + // See CreateLeaseSetMessage.doReadMessage() + SigningPrivateKey spk = li.getSigningPrivateKey(); + if (!_context.isRouterContext() && spk.getType() != SigType.DSA_SHA1) { + byte[] dummy = new byte[SigningPrivateKey.KEYSIZE_BYTES]; + _context.random().nextBytes(dummy); + spk = new SigningPrivateKey(dummy); + } session.getProducer().createLeaseSet(session, leaseSet, li.getSigningPrivateKey(), li.getPrivateKey()); session.setLeaseSet(leaseSet); } catch (DataFormatException dfe) { diff --git a/core/java/src/net/i2p/client/datagram/I2PDatagramDissector.java b/core/java/src/net/i2p/client/datagram/I2PDatagramDissector.java index 00c897a6f..281ea2035 100644 --- a/core/java/src/net/i2p/client/datagram/I2PDatagramDissector.java +++ b/core/java/src/net/i2p/client/datagram/I2PDatagramDissector.java @@ -14,6 +14,7 @@ import java.io.IOException; import net.i2p.I2PAppContext; import net.i2p.crypto.DSAEngine; import net.i2p.crypto.SHA256Generator; +import net.i2p.crypto.SigType; import net.i2p.data.DataFormatException; import net.i2p.data.Destination; import net.i2p.data.Hash; @@ -67,7 +68,10 @@ public final class I2PDatagramDissector { try { // read destination rxDest = Destination.create(dgStream); - rxSign = new Signature(); + SigType type = rxDest.getSigningPublicKey().getType(); + if (type != null) + throw new DataFormatException("unsupported sig type"); + rxSign = new Signature(type); // read signature rxSign.readBytes(dgStream); diff --git a/core/java/src/net/i2p/crypto/SU3File.java b/core/java/src/net/i2p/crypto/SU3File.java index dd23ad05a..55e21fea4 100644 --- a/core/java/src/net/i2p/crypto/SU3File.java +++ b/core/java/src/net/i2p/crypto/SU3File.java @@ -518,23 +518,6 @@ public class SU3File { return buf.toString(); } - /** - * @param stype number or name - * @return null if not found - * @since 0.9.9 - */ - private static SigType parseSigType(String stype) { - try { - return SigType.valueOf(stype.toUpperCase(Locale.US)); - } catch (IllegalArgumentException iae) { - try { - int code = Integer.parseInt(stype); - return SigType.getByCode(code); - } catch (NumberFormatException nfe) { - return null; - } - } - } /** * @param stype number or name * @return null if not found @@ -627,7 +610,7 @@ public class SU3File { */ private static final boolean signCLI(String stype, String ctype, String inputFile, String signedFile, String privateKeyFile, String version, String signerName, String keypw) { - SigType type = stype == null ? SigType.getByCode(Integer.valueOf(DEFAULT_SIG_CODE)) : parseSigType(stype); + SigType type = stype == null ? SigType.getByCode(Integer.valueOf(DEFAULT_SIG_CODE)) : SigType.parseSigType(stype); if (type == null) { System.out.println("Signature type " + stype + " is not supported"); return false; @@ -719,7 +702,7 @@ public class SU3File { * @since 0.9.9 */ private static final boolean genKeysCLI(String stype, String publicKeyFile, String privateKeyFile, String alias) { - SigType type = stype == null ? SigType.getByCode(Integer.valueOf(DEFAULT_SIG_CODE)) : parseSigType(stype); + SigType type = stype == null ? SigType.getByCode(Integer.valueOf(DEFAULT_SIG_CODE)) : SigType.parseSigType(stype); if (type == null) { System.out.println("Signature type " + stype + " is not supported"); return false; diff --git a/core/java/src/net/i2p/crypto/SigType.java b/core/java/src/net/i2p/crypto/SigType.java index 105ff09b1..a37d65dfb 100644 --- a/core/java/src/net/i2p/crypto/SigType.java +++ b/core/java/src/net/i2p/crypto/SigType.java @@ -5,6 +5,7 @@ import java.security.NoSuchAlgorithmException; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.InvalidParameterSpecException; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import net.i2p.data.Hash; @@ -170,4 +171,24 @@ public enum SigType { public static SigType getByCode(int code) { return BY_CODE.get(Integer.valueOf(code)); } + + /** + * Convenience for user apps + * + * @param stype number or name + * @return null if not found + * @since 0.9.9 moved from SU3File in 0.9.11 + */ + public static SigType parseSigType(String stype) { + try { + return valueOf(stype.toUpperCase(Locale.US)); + } catch (IllegalArgumentException iae) { + try { + int code = Integer.parseInt(stype); + return getByCode(code); + } catch (NumberFormatException nfe) { + return null; + } + } + } } diff --git a/core/java/src/net/i2p/data/Certificate.java b/core/java/src/net/i2p/data/Certificate.java index ef6484f31..a4a1b3148 100644 --- a/core/java/src/net/i2p/data/Certificate.java +++ b/core/java/src/net/i2p/data/Certificate.java @@ -42,6 +42,8 @@ public class Certificate extends DataStructureImpl { public final static int CERTIFICATE_LENGTH_SIGNED_WITH_HASH = Signature.SIGNATURE_BYTES + Hash.HASH_LENGTH; /** Contains multiple certs */ public final static int CERTIFICATE_TYPE_MULTIPLE = 4; + /** @since 0.9.11 */ + public final static int CERTIFICATE_TYPE_KEY = 5; /** * If null cert, return immutable static instance, else create new @@ -58,6 +60,13 @@ public class Certificate extends DataStructureImpl { return new Certificate(type, null); byte[] payload = new byte[length]; System.arraycopy(data, off + 3, payload, 0, length); + if (type == CERTIFICATE_TYPE_KEY) { + try { + return new KeyCertificate(payload); + } catch (DataFormatException dfe) { + throw new IllegalArgumentException(dfe); + } + } return new Certificate(type, payload); } @@ -77,13 +86,20 @@ public class Certificate extends DataStructureImpl { int read = DataHelper.read(in, payload); if (read != length) throw new DataFormatException("Not enough bytes for the payload (read: " + read + " length: " + length + ')'); + if (type == CERTIFICATE_TYPE_KEY) + return new KeyCertificate(payload); return new Certificate(type, payload); } public Certificate() { } + /** + * @throws IllegalArgumentException if type < 0 + */ public Certificate(int type, byte[] payload) { + if (type < 0) + throw new IllegalArgumentException(); _type = type; _payload = payload; } @@ -93,7 +109,15 @@ public class Certificate extends DataStructureImpl { return _type; } + /** + * @throws IllegalArgumentException if type < 0 + * @throws IllegalStateException if already set + */ public void setCertificateType(int type) { + if (type < 0) + throw new IllegalArgumentException(); + if (_type != 0 && _type != type) + throw new IllegalStateException("already set"); _type = type; } @@ -101,11 +125,21 @@ public class Certificate extends DataStructureImpl { return _payload; } + /** + * @throws IllegalStateException if already set + */ public void setPayload(byte[] payload) { + if (_payload != null) + throw new IllegalStateException("already set"); _payload = payload; } + /** + * @throws IllegalStateException if already set + */ public void readBytes(InputStream in) throws DataFormatException, IOException { + if (_type != 0 || _payload != null) + throw new IllegalStateException("already set"); _type = (int) DataHelper.readLong(in, 1); int length = (int) DataHelper.readLong(in, 2); if (length > 0) { @@ -149,7 +183,12 @@ public class Certificate extends DataStructureImpl { return cur - offset; } + /** + * @throws IllegalStateException if already set + */ public int readBytes(byte source[], int offset) throws DataFormatException { + if (_type != 0 || _payload != null) + throw new IllegalStateException("already set"); if (source == null) throw new DataFormatException("Cert is null"); if (source.length < offset + 3) throw new DataFormatException("Cert is too small [" + source.length + " off=" + offset + "]"); @@ -175,6 +214,18 @@ public class Certificate extends DataStructureImpl { return 1 + 2 + (_payload != null ? _payload.length : 0); } + /** + * Up-convert this to a KeyCertificate + * + * @throws DataFormatException if cert type != CERTIFICATE_TYPE_KEY + * @since 0.9.11 + */ + public KeyCertificate toKeyCertificate() throws DataFormatException { + if (_type != CERTIFICATE_TYPE_KEY) + throw new DataFormatException("type"); + return new KeyCertificate(this); + } + @Override public boolean equals(Object object) { if (object == this) return true; @@ -194,6 +245,8 @@ public class Certificate extends DataStructureImpl { buf.append("[Certificate: type: "); if (getCertificateType() == CERTIFICATE_TYPE_NULL) buf.append("Null certificate"); + else if (getCertificateType() == CERTIFICATE_TYPE_KEY) + buf.append("Key certificate"); else if (getCertificateType() == CERTIFICATE_TYPE_HASHCASH) buf.append("Hashcash certificate"); else if (getCertificateType() == CERTIFICATE_TYPE_HIDDEN) diff --git a/core/java/src/net/i2p/data/Destination.java b/core/java/src/net/i2p/data/Destination.java index ad21985e3..4d8affc87 100644 --- a/core/java/src/net/i2p/data/Destination.java +++ b/core/java/src/net/i2p/data/Destination.java @@ -100,17 +100,22 @@ public class Destination extends KeysAndCert { int cur = offset; System.arraycopy(_publicKey.getData(), 0, target, cur, PublicKey.KEYSIZE_BYTES); cur += PublicKey.KEYSIZE_BYTES; - System.arraycopy(_signingKey.getData(), 0, target, cur, SigningPublicKey.KEYSIZE_BYTES); - cur += SigningPublicKey.KEYSIZE_BYTES; + if (_padding != null) { + System.arraycopy(_padding, 0, target, cur, _padding.length); + cur += _padding.length; + } + System.arraycopy(_signingKey.getData(), 0, target, cur, _signingKey.length()); + cur += _signingKey.length(); cur += _certificate.writeBytes(target, cur); return cur - offset; } /** - * @deprecated was used only by Packet.java in streaming, now unused + * deprecated was used only by Packet.java in streaming, now unused * * @throws IllegalStateException if data already set */ +/**** public int readBytes(byte source[], int offset) throws DataFormatException { if (source == null) throw new DataFormatException("Null source"); if (source.length <= offset + PublicKey.KEYSIZE_BYTES + SigningPublicKey.KEYSIZE_BYTES) @@ -130,9 +135,10 @@ public class Destination extends KeysAndCert { return cur - offset; } +****/ public int size() { - return PublicKey.KEYSIZE_BYTES + SigningPublicKey.KEYSIZE_BYTES + _certificate.size(); + return PublicKey.KEYSIZE_BYTES + _signingKey.length() + _certificate.size(); } /** diff --git a/core/java/src/net/i2p/data/KeyCertificate.java b/core/java/src/net/i2p/data/KeyCertificate.java new file mode 100644 index 000000000..5726c80b1 --- /dev/null +++ b/core/java/src/net/i2p/data/KeyCertificate.java @@ -0,0 +1,252 @@ +package net.i2p.data; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import net.i2p.crypto.SigType; + +/** + * This certificate type gets its own class because it's going to be used a lot. + * + * The crypto type is assumed to be always 0x0000 (ElG) for now. + * + * @since 0.9.11 + */ +public class KeyCertificate extends Certificate { + + public static final int HEADER_LENGTH = 4; + + public static final KeyCertificate ELG_ECDSA256_CERT; + static { + KeyCertificate kc; + try { + kc = new ECDSA256Cert(); + } catch (DataFormatException dfe) { + kc = null; // won't happen + } + ELG_ECDSA256_CERT = kc; + } + + /** + * @param payload 4 bytes minimum if non-null + * @throws DataFormatException + */ + public KeyCertificate(byte[] payload) throws DataFormatException { + super(CERTIFICATE_TYPE_KEY, payload); + if (payload != null && payload.length < HEADER_LENGTH) + throw new DataFormatException("data"); + } + + /** + * A KeyCertificate with crypto type 0 (ElGamal) + * and the signature type and extra data from the given public key. + * + * @param sig non-null data non-null + * @throws IllegalArgumentException + */ + public KeyCertificate(SigningPublicKey spk) { + super(CERTIFICATE_TYPE_KEY, null); + if (spk == null || spk.getData() == null) + throw new IllegalArgumentException(); + SigType type = spk.getType(); + int len = type.getPubkeyLen(); + int extra = Math.max(0, len - 128); + _payload = new byte[HEADER_LENGTH + extra]; + int code = type.getCode(); + _payload[0] = (byte) (code >> 8); + _payload[1] = (byte) (code & 0xff); + // 2 and 3 always 0, it is the only crypto code for now + if (extra > 0) + System.arraycopy(spk.getData(), 128, _payload, HEADER_LENGTH, extra); + } + + /** + * A KeyCertificate with crypto type 0 (ElGamal) + * and the signature type as specified. + * Payload is created. + * If type.getPubkeyLen() is greater than 128, caller MUST + * fill in the extra key data in the payload. + * + * @param sig non-null data non-null + * @throws IllegalArgumentException + */ + public KeyCertificate(SigType type) { + super(CERTIFICATE_TYPE_KEY, null); + int len = type.getPubkeyLen(); + int extra = Math.max(0, len - 128); + _payload = new byte[HEADER_LENGTH + extra]; + int code = type.getCode(); + _payload[0] = (byte) (code >> 8); + _payload[1] = (byte) (code & 0xff); + // 2 and 3 always 0, it is the only crypto code for now + } + + /** + * Up-convert a cert to this class + * + * @param cert payload 4 bytes minimum if non-null + * @throws DataFormatException if cert type != CERTIFICATE_TYPE_KEY + */ + public KeyCertificate(Certificate cert) throws DataFormatException { + this(cert.getPayload()); + if (cert.getCertificateType() != CERTIFICATE_TYPE_KEY) + throw new DataFormatException("type"); + } + + /** + * @return -1 if unset + */ + public int getSigTypeCode() { + if (_payload == null) + return -1; + return ((_payload[0] & 0xff) << 8) | (_payload[1] & 0xff); + } + + /** + * @return -1 if unset + */ + public int getCryptoTypeCode() { + if (_payload == null) + return -1; + return ((_payload[2] & 0xff) << 8) | (_payload[3] & 0xff); + } + + /** + * @return null if unset or unknown + */ + public SigType getSigType() { + return SigType.getByCode(getSigTypeCode()); + } + + /** + * Signing Key extra data, if any, is first in the array. + * Crypto Key extra data, if any, is second in the array, + * at offset max(0, 128 - getSigType().getPubkeyLen() + * + * @return null if unset or none + */ + public byte[] getExtraKeyData() { + if (_payload == null || _payload.length <= HEADER_LENGTH) + return null; + byte[] rv = new byte[_payload.length - HEADER_LENGTH]; + System.arraycopy(_payload, HEADER_LENGTH, rv, 0, rv.length); + return rv; + } + + + /** + * Signing Key extra data, if any. + * + * @return null if unset or none + * @throws UnsupportedOperationException if the sig type is unsupported + */ + public byte[] getExtraSigningKeyData() { + // we assume no crypto key data + if (_payload == null || _payload.length <= HEADER_LENGTH) + return null; + SigType type = getSigType(); + if (type == null) + throw new UnsupportedOperationException("unknown sig type"); + int extra = 128 - type.getPubkeyLen(); + if (_payload.length == HEADER_LENGTH + extra) + return getExtraKeyData(); + byte[] rv = new byte[extra]; + System.arraycopy(_payload, HEADER_LENGTH, rv, 0, extra); + return rv; + } + + // todo + // constructor w/ crypto type + // getCryptoType() + // getCryptoDataOffset() + + @Override + public KeyCertificate toKeyCertificate() { + return this; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(64); + buf.append("[Certificate: type: Key certificate"); + if (_payload == null) { + buf.append(" null payload"); + } else { + buf.append("\n\tCrypto type: ").append(getCryptoTypeCode()); + buf.append("\n\tSig type: ").append(getSigTypeCode()) + .append(" (").append(getSigType()).append(')'); + if (_payload.length > HEADER_LENGTH) + buf.append("\n\tKey data: ").append(_payload.length - HEADER_LENGTH).append(" bytes"); + } + buf.append("]"); + return buf.toString(); + } + + /** + * An immutable ElG/ECDSA-256 certificate. + * @since 0.8.3 + */ + private static final class ECDSA256Cert extends KeyCertificate { + private static final byte[] ECDSA256_DATA = new byte[] { + CERTIFICATE_TYPE_KEY, 0, HEADER_LENGTH, 0, (byte) (SigType.ECDSA_SHA256_P256.getCode()), 0, 0 + }; + private static final int ECDSA256_LENGTH = ECDSA256_DATA.length; + private static final byte[] ECDSA256_PAYLOAD = new byte[] { + 0, (byte) (SigType.ECDSA_SHA256_P256.getCode()), 0, 0 + }; + + public ECDSA256Cert() throws DataFormatException { + super(ECDSA256_PAYLOAD); + } + + /** @throws RuntimeException always */ + @Override + public void setCertificateType(int type) { + throw new RuntimeException("Data already set"); + } + + /** @throws RuntimeException always */ + @Override + public void setPayload(byte[] payload) { + throw new RuntimeException("Data already set"); + } + + /** @throws RuntimeException always */ + @Override + public void readBytes(InputStream in) throws DataFormatException, IOException { + throw new RuntimeException("Data already set"); + } + + /** Overridden for efficiency */ + @Override + public void writeBytes(OutputStream out) throws IOException { + out.write(ECDSA256_DATA); + } + + /** Overridden for efficiency */ + @Override + public int writeBytes(byte target[], int offset) { + System.arraycopy(ECDSA256_DATA, 0, target, offset, ECDSA256_LENGTH); + return ECDSA256_LENGTH; + } + + /** @throws RuntimeException always */ + @Override + public int readBytes(byte source[], int offset) throws DataFormatException { + throw new RuntimeException("Data already set"); + } + + /** Overridden for efficiency */ + @Override + public int size() { + return ECDSA256_LENGTH; + } + + /** Overridden for efficiency */ + @Override + public int hashCode() { + return 1234567; + } + } +} diff --git a/core/java/src/net/i2p/data/KeysAndCert.java b/core/java/src/net/i2p/data/KeysAndCert.java index 429d89be7..33a1de294 100644 --- a/core/java/src/net/i2p/data/KeysAndCert.java +++ b/core/java/src/net/i2p/data/KeysAndCert.java @@ -35,6 +35,7 @@ public class KeysAndCert extends DataStructureImpl { protected SigningPublicKey _signingKey; protected Certificate _certificate; protected Hash __calculatedHash; + protected byte[] _padding; public Certificate getCertificate() { return _certificate; @@ -78,6 +79,17 @@ public class KeysAndCert extends DataStructureImpl { __calculatedHash = null; } + /** + * @throws IllegalStateException if was already set + * @since 0.9.11 + */ + public void setPadding(byte[] padding) { + if (_padding != null) + throw new IllegalStateException(); + _padding = padding; + __calculatedHash = null; + } + /** * @throws IllegalStateException if data already set */ @@ -85,8 +97,18 @@ public class KeysAndCert extends DataStructureImpl { if (_publicKey != null || _signingKey != null || _certificate != null) throw new IllegalStateException(); _publicKey = PublicKey.create(in); - _signingKey = SigningPublicKey.create(in); - _certificate = Certificate.create(in); + SigningPublicKey spk = SigningPublicKey.create(in); + Certificate cert = Certificate.create(in); + if (cert.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY) { + // convert SPK to new SPK and padding + KeyCertificate kcert = cert.toKeyCertificate(); + _signingKey = spk.toTypedKey(kcert); + _padding = spk.getPadding(kcert); + _certificate = kcert; + } else { + _signingKey = spk; + _certificate = cert; + } __calculatedHash = null; } @@ -94,7 +116,9 @@ public class KeysAndCert extends DataStructureImpl { if ((_certificate == null) || (_publicKey == null) || (_signingKey == null)) throw new DataFormatException("Not enough data to format the router identity"); _publicKey.writeBytes(out); - _signingKey.writeBytes(out); + if (_padding != null) + out.write(_padding); + _signingKey.writeTruncatedBytes(out); _certificate.writeBytes(out); } @@ -106,6 +130,7 @@ public class KeysAndCert extends DataStructureImpl { return DataHelper.eq(_signingKey, ident._signingKey) && DataHelper.eq(_publicKey, ident._publicKey) + && DataHelper.eq(_padding, ident._padding) && DataHelper.eq(_certificate, ident._certificate); } @@ -125,6 +150,8 @@ public class KeysAndCert extends DataStructureImpl { buf.append("\n\tCertificate: ").append(_certificate); buf.append("\n\tPublicKey: ").append(_publicKey); buf.append("\n\tSigningPublicKey: ").append(_signingKey); + if (_padding != null) + buf.append("\n\tPadding: ").append(_padding.length).append(" bytes"); buf.append(']'); return buf.toString(); } diff --git a/core/java/src/net/i2p/data/LeaseSet.java b/core/java/src/net/i2p/data/LeaseSet.java index 75b24220d..cf9b39deb 100644 --- a/core/java/src/net/i2p/data/LeaseSet.java +++ b/core/java/src/net/i2p/data/LeaseSet.java @@ -275,11 +275,9 @@ public class LeaseSet extends DatabaseEntry { protected byte[] getBytes() { if ((_destination == null) || (_encryptionKey == null) || (_signingKey == null)) return null; - int len = PublicKey.KEYSIZE_BYTES // dest - + SigningPublicKey.KEYSIZE_BYTES // dest - + 3 // cert minimum, could be more, only used to size the BAOS + int len = _destination.size() + PublicKey.KEYSIZE_BYTES // encryptionKey - + SigningPublicKey.KEYSIZE_BYTES // signingKey + + _signingKey.length() // signingKey + 1 + _leases.size() * 44; // leases ByteArrayOutputStream out = new ByteArrayOutputStream(len); @@ -310,7 +308,9 @@ public class LeaseSet extends DatabaseEntry { throw new IllegalStateException(); _destination = Destination.create(in); _encryptionKey = PublicKey.create(in); - _signingKey = SigningPublicKey.create(in); + // revocation signing key must be same type as the destination signing key + _signingKey = new SigningPublicKey(_destination.getSigningPublicKey().getType()); + _signingKey.readBytes(in); int numLeases = (int) DataHelper.readLong(in, 1); if (numLeases > MAX_LEASES) throw new DataFormatException("Too many leases - max is " + MAX_LEASES); @@ -320,7 +320,8 @@ public class LeaseSet extends DatabaseEntry { lease.readBytes(in); addLease(lease); } - _signature = new Signature(); + // signature must be same type as the destination signing key + _signature = new Signature(_destination.getSigningPublicKey().getType()); _signature.readBytes(in); } @@ -345,11 +346,9 @@ public class LeaseSet extends DatabaseEntry { * Number of bytes, NOT including signature */ public int size() { - return PublicKey.KEYSIZE_BYTES //destination.pubKey - + SigningPublicKey.KEYSIZE_BYTES // destination.signPubKey - + _destination.getCertificate().size() // destination.certificate, usually 3 + return _destination.size() + PublicKey.KEYSIZE_BYTES // encryptionKey - + SigningPublicKey.KEYSIZE_BYTES // signingKey + + _signingKey.length() // signingKey + 1 // number of leases + _leases.size() * (Hash.HASH_LENGTH + 4 + 8); } diff --git a/core/java/src/net/i2p/data/PrivateKeyFile.java b/core/java/src/net/i2p/data/PrivateKeyFile.java index 9c71cc96b..b97d76257 100644 --- a/core/java/src/net/i2p/data/PrivateKeyFile.java +++ b/core/java/src/net/i2p/data/PrivateKeyFile.java @@ -6,6 +6,8 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.Locale; import java.util.Map; import java.util.Properties; @@ -17,6 +19,9 @@ import net.i2p.client.I2PClientFactory; import net.i2p.client.I2PSession; import net.i2p.client.I2PSessionException; import net.i2p.crypto.DSAEngine; +import net.i2p.crypto.KeyGenerator; +import net.i2p.crypto.SigType; +import net.i2p.util.RandomSource; /** * This helper class reads and writes files in the @@ -50,7 +55,9 @@ public class PrivateKeyFile { * Copied and expanded from that in Destination.java */ public static void main(String args[]) { - if (args.length == 0) { + if (args.length == 0 || + (args[0].startsWith("-") && args.length == 1) || + (args[0].equals("-t") && args.length != 3)) { System.err.println("Usage: PrivateKeyFile filename (generates if nonexistent, then prints)"); System.err.println(" PrivateKeyFile -h filename (generates if nonexistent, adds hashcash cert)"); System.err.println(" PrivateKeyFile -h effort filename (specify HashCash effort instead of default " + HASH_EFFORT + ")"); @@ -58,13 +65,14 @@ public class PrivateKeyFile { System.err.println(" PrivateKeyFile -s filename signwithdestfile (generates if nonexistent, adds cert signed by 2nd dest)"); System.err.println(" PrivateKeyFile -u filename (changes to unknown cert)"); System.err.println(" PrivateKeyFile -x filename (changes to hidden cert)"); + System.err.println(" PrivateKeyFile -t sigtype filename (changes to KeyCertificate of the given sig type"); return; } I2PClient client = I2PClientFactory.createClient(); int filearg = 0; if (args.length > 1) { - if (args.length >= 2 && args[0].equals("-h")) + if (args.length >= 2 && (args[0].equals("-h") || args[0].equals("-t"))) filearg = args.length - 1; else filearg = 1; @@ -101,10 +109,17 @@ public class PrivateKeyFile { PrivateKeyFile pkf2 = new PrivateKeyFile(args[2]); pkf.setSignedCert(pkf2); System.out.println("New destination with signed cert is:"); + } else if (args.length == 3 && args[0].equals("-t")) { + // KeyCert + SigType type = SigType.parseSigType(args[1]); + if (type == null) + throw new IllegalArgumentException("Signature type " + args[1] + " is not supported"); + pkf.setKeyCert(type); + System.out.println("New destination with key cert is:"); } System.out.println(pkf); pkf.write(); - verifySignature(d); + verifySignature(pkf.getDestination()); } catch (Exception e) { e.printStackTrace(); } @@ -209,6 +224,43 @@ public class PrivateKeyFile { return c; } + /** + * Change cert type - caller must also call write(). + * Side effect - creates new Destination object. + * @since 0.9.11 + */ + public Certificate setKeyCert(SigType type) { + if (type == SigType.DSA_SHA1) + return setCertType(Certificate.CERTIFICATE_TYPE_NULL); + if (dest == null) + throw new IllegalArgumentException("Dest is null"); + KeyCertificate c = new KeyCertificate(type); + SimpleDataStructure signingKeys[]; + try { + signingKeys = KeyGenerator.getInstance().generateSigningKeys(type); + } catch (GeneralSecurityException gse) { + throw new RuntimeException("keygen fail", gse); + } + SigningPublicKey signingPubKey = (SigningPublicKey) signingKeys[0]; + signingPrivKey = (SigningPrivateKey) signingKeys[1]; + // dests now immutable, must create new + Destination newdest = new Destination(); + newdest.setPublicKey(dest.getPublicKey()); + newdest.setSigningPublicKey(signingPubKey); + // fix up key certificate or padding + int len = type.getPubkeyLen(); + if (len < 128) { + byte[] pad = new byte[128 - len]; + RandomSource.getInstance().nextBytes(pad); + newdest.setPadding(pad); + } else if (len > 128) { + System.arraycopy(signingPubKey.getData(), 128, c.getPayload(), KeyCertificate.HEADER_LENGTH, len - 128); + } + newdest.setCertificate(c); + dest = newdest; + return c; + } + /** change to hashcash cert - caller must also call write() */ public Certificate setHashCashCert(int effort) { Certificate c = setCertType(Certificate.CERTIFICATE_TYPE_HASHCASH); @@ -444,8 +496,6 @@ public class PrivateKeyFile { private static final int HASH_EFFORT = VerifiedDestination.MIN_HASHCASH_EFFORT; - - private final File file; private final I2PClient client; private Destination dest; diff --git a/core/java/src/net/i2p/data/RouterInfo.java b/core/java/src/net/i2p/data/RouterInfo.java index 214937815..4900a4e9e 100644 --- a/core/java/src/net/i2p/data/RouterInfo.java +++ b/core/java/src/net/i2p/data/RouterInfo.java @@ -554,7 +554,7 @@ public class RouterInfo extends DatabaseEntry { } } DataHelper.readProperties(din, _options); - _signature = new Signature(); + _signature = new Signature(_identity.getSigningPublicKey().getType()); _signature.readBytes(in); if (verifySig) { diff --git a/core/java/src/net/i2p/data/Signature.java b/core/java/src/net/i2p/data/Signature.java index 83f0410e2..1338cb664 100644 --- a/core/java/src/net/i2p/data/Signature.java +++ b/core/java/src/net/i2p/data/Signature.java @@ -13,12 +13,15 @@ import net.i2p.crypto.SigType; /** * Defines the signature as defined by the I2P data structure spec. - * A signature is a 40-byte array verifying the authenticity of some data + * By default, a signature is a 40-byte array verifying the authenticity of some data * using the DSA-SHA1 algorithm. * * The signature is the 20-byte R followed by the 20-byte S, * both are unsigned integers. * + * As of release 0.9.8, signatures of arbitrary length and type are supported. + * See SigType. + * * @author jrandom */ public class Signature extends SimpleDataStructure { @@ -39,10 +42,15 @@ public class Signature extends SimpleDataStructure { } /** + * Unknown type not allowed as we won't know the length to read in the data. + * + * @param type non-null * @since 0.9.8 */ public Signature(SigType type) { super(); + if (type == null) + throw new IllegalArgumentException("unknown type"); _type = type; } @@ -51,10 +59,15 @@ public class Signature extends SimpleDataStructure { } /** + * Should we allow an unknown type here? + * + * @param type non-null * @since 0.9.8 */ public Signature(SigType type, byte data[]) { super(); + if (type == null) + throw new IllegalArgumentException("unknown type"); _type = type; setData(data); } @@ -64,6 +77,7 @@ public class Signature extends SimpleDataStructure { } /** + * @return non-null * @since 0.9.8 */ public SigType getType() { diff --git a/core/java/src/net/i2p/data/SigningPrivateKey.java b/core/java/src/net/i2p/data/SigningPrivateKey.java index 4b60ee7e8..a8fcbb208 100644 --- a/core/java/src/net/i2p/data/SigningPrivateKey.java +++ b/core/java/src/net/i2p/data/SigningPrivateKey.java @@ -14,10 +14,13 @@ import net.i2p.crypto.SigType; /** * Defines the SigningPrivateKey as defined by the I2P data structure spec. - * A signing private key is 20 byte Integer. The private key represents only the + * A signing private key is by default a 20 byte Integer. The private key represents only the * exponent, not the primes, which are constant and defined in the crypto spec. * This key varies from the PrivateKey in its usage (signing, not decrypting) * + * As of release 0.9.8, keys of arbitrary length and type are supported. + * See SigType. + * * @author jrandom */ public class SigningPrivateKey extends SimpleDataStructure { diff --git a/core/java/src/net/i2p/data/SigningPublicKey.java b/core/java/src/net/i2p/data/SigningPublicKey.java index e01d6ec39..c7ec32bb8 100644 --- a/core/java/src/net/i2p/data/SigningPublicKey.java +++ b/core/java/src/net/i2p/data/SigningPublicKey.java @@ -11,15 +11,19 @@ package net.i2p.data; import java.io.InputStream; import java.io.IOException; +import java.io.OutputStream; import net.i2p.crypto.SigType; /** * Defines the SigningPublicKey as defined by the I2P data structure spec. - * A signing public key is 128 byte Integer. The public key represents only the + * A signing public key is by default 128 byte Integer. The public key represents only the * exponent, not the primes, which are constant and defined in the crypto spec. * This key varies from the PrivateKey in its usage (verifying signatures, not encrypting) * + * As of release 0.9.8, keys of arbitrary length and type are supported. + * See SigType. + * * @author jrandom */ public class SigningPublicKey extends SimpleDataStructure { @@ -55,6 +59,7 @@ public class SigningPublicKey extends SimpleDataStructure { } /** + * @param type if null, type is unknown * @since 0.9.8 */ public SigningPublicKey(SigType type) { @@ -67,12 +72,16 @@ public class SigningPublicKey extends SimpleDataStructure { } /** + * @param type if null, type is unknown * @since 0.9.8 */ public SigningPublicKey(SigType type, byte data[]) { super(); _type = type; - setData(data); + if (type != null || data == null) + setData(data); + else + _data = data; // bypass length check } /** constructs from base64 @@ -84,17 +93,91 @@ public class SigningPublicKey extends SimpleDataStructure { fromBase64(base64Data); } + /** + * @return if type unknown, the length of the data, or 128 if no data + */ public int length() { - return _type.getPubkeyLen(); + if (_type != null) + return _type.getPubkeyLen(); + if (_data != null) + return _data.length; + return KEYSIZE_BYTES; } /** + * @return null if unknown * @since 0.9.8 */ public SigType getType() { return _type; } + /** + * Up-convert this from an untyped (type 0) SPK to a typed SPK based on the Key Cert given + * + * @throws IllegalArgumentException if this is already typed to a different type + * @since 0.9.11 + */ + public SigningPublicKey toTypedKey(KeyCertificate kcert) { + if (_data == null) + throw new IllegalStateException(); + SigType newType = kcert.getSigType(); + if (_type == newType) + return this; + if (_type != SigType.DSA_SHA1) + throw new IllegalArgumentException("Cannot convert " + _type + " to " + newType); + int newLen = newType.getPubkeyLen(); + if (newLen == SigType.DSA_SHA1.getPubkeyLen()) + return new SigningPublicKey(newType, _data); + byte[] newData = new byte[newLen]; + if (newLen < SigType.DSA_SHA1.getPubkeyLen()) { + // right-justified + System.arraycopy(_data, _data.length - newLen, newData, 0, newLen); + } else { + // full 128 bytes + fragment in kcert + System.arraycopy(_data, 0, newData, 0, _data.length); + System.arraycopy(kcert.getPayload(), KeyCertificate.HEADER_LENGTH, newData, _data.length, newLen - _data.length); + } + return new SigningPublicKey(newType, newData); + } + + /** + * Get the portion of this (type 0) SPK that is really padding based on the Key Cert type given, + * if any + * + * @return leading padding length > 0 or null + * @throws IllegalArgumentException if this is already typed to a different type + * @since 0.9.11 + */ + public byte[] getPadding(KeyCertificate kcert) { + if (_data == null) + throw new IllegalStateException(); + SigType newType = kcert.getSigType(); + if (_type == newType) + return null; + if (_type != SigType.DSA_SHA1) + throw new IllegalStateException("Cannot convert " + _type + " to " + newType); + int newLen = newType.getPubkeyLen(); + if (newLen >= SigType.DSA_SHA1.getPubkeyLen()) + return null; + int padLen = SigType.DSA_SHA1.getPubkeyLen() - newLen; + byte[] pad = new byte[padLen]; + System.arraycopy(_data, 0, pad, 0, padLen); + return pad; + } + + /** + * Write the data up to a max of 128 bytes. + * If longer, the rest will be written in the KeyCertificate. + * @since 0.9.11 + */ + public void writeTruncatedBytes(OutputStream out) throws DataFormatException, IOException { + if (_type.getPubkeyLen() <= KEYSIZE_BYTES) + out.write(_data); + else + out.write(_data, 0, KEYSIZE_BYTES); + } + /** * @since 0.9.8 */ diff --git a/core/java/src/net/i2p/data/i2cp/CreateLeaseSetMessage.java b/core/java/src/net/i2p/data/i2cp/CreateLeaseSetMessage.java index b57af1f8d..6477768c5 100644 --- a/core/java/src/net/i2p/data/i2cp/CreateLeaseSetMessage.java +++ b/core/java/src/net/i2p/data/i2cp/CreateLeaseSetMessage.java @@ -71,6 +71,11 @@ public class CreateLeaseSetMessage extends I2CPMessageImpl { try { _sessionId = new SessionId(); _sessionId.readBytes(in); + // Revocation is unimplemented. + // As the SPK comes before the LeaseSet, we don't know the key type. + // We could have some sort of callback or state setting so we get the + // expected type from the session. But for now, we just assume it's 20 bytes. + // Clients outside router context should throw in a dummy 20 bytes. _signingPrivateKey = new SigningPrivateKey(); _signingPrivateKey.readBytes(in); _privateKey = new PrivateKey(); @@ -87,7 +92,7 @@ public class CreateLeaseSetMessage extends I2CPMessageImpl { if ((_sessionId == null) || (_signingPrivateKey == null) || (_privateKey == null) || (_leaseSet == null)) throw new I2CPMessageException("Unable to write out the message as there is not enough data"); int size = 4 // sessionId - + SigningPrivateKey.KEYSIZE_BYTES + + _signingPrivateKey.length() + PrivateKey.KEYSIZE_BYTES + _leaseSet.size(); ByteArrayOutputStream os = new ByteArrayOutputStream(size); diff --git a/core/java/src/net/i2p/data/i2cp/SessionConfig.java b/core/java/src/net/i2p/data/i2cp/SessionConfig.java index cd6db38e4..8c5521993 100644 --- a/core/java/src/net/i2p/data/i2cp/SessionConfig.java +++ b/core/java/src/net/i2p/data/i2cp/SessionConfig.java @@ -189,7 +189,7 @@ public class SessionConfig extends DataStructureImpl { _destination = Destination.create(rawConfig); _options = DataHelper.readProperties(rawConfig); _creationDate = DataHelper.readDate(rawConfig); - _signature = new Signature(); + _signature = new Signature(_destination.getSigningPublicKey().getType()); _signature.readBytes(rawConfig); } From e8e239616fcae8d3d20adb1d250ba2728a28adb7 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 3 Jan 2014 00:22:44 +0000 Subject: [PATCH 2/9] * Crypto: More implementation for key certs - Support i2cp.destination.sigType option in TunnelController and I2PSocketManagerFactory - Fixup of Destination.create() and Destination.size() - Add generic off/len methods in DSAEngine, needed for streaming - Fixup of sign/verify in streaming Packet - Javadocs --- .../java/src/net/i2p/i2ptunnel/I2PTunnel.java | 13 ++-- .../net/i2p/i2ptunnel/TunnelController.java | 19 ++++- .../udpTunnel/I2PTunnelUDPClientBase.java | 14 +++- .../streaming/I2PSocketManagerFactory.java | 28 +++++-- .../src/net/i2p/client/streaming/Packet.java | 33 +++++--- core/java/src/net/i2p/client/I2PClient.java | 5 ++ .../src/net/i2p/client/I2PClientImpl.java | 2 +- core/java/src/net/i2p/crypto/DSAEngine.java | 77 ++++++++++--------- core/java/src/net/i2p/data/Destination.java | 26 ++++++- 9 files changed, 149 insertions(+), 68 deletions(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java index 2b269e666..0b06c1ef9 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java @@ -1223,7 +1223,8 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging { } /** - * Generate a new keypair + * Generate a new keypair. + * Does NOT support non-default sig types. * Deprecated - only used by CLI * * Sets the event "genkeysResult" = "ok" or "error" after the generation is complete @@ -1266,7 +1267,8 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging { } /** - * Generate a new keypair + * Generate a new keypair. + * Does NOT support non-default sig types. * Deprecated - only used by CLI * * Sets the event "privateKey" = base64 of the privateKey stream and @@ -1275,7 +1277,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging { * @param l logger to receive events and output */ private static void runGenTextKeys(Logging l) { - ByteArrayOutputStream privkey = new ByteArrayOutputStream(512); + ByteArrayOutputStream privkey = new ByteArrayOutputStream(1024); ByteArrayOutputStream pubkey = new ByteArrayOutputStream(512); makeKey(privkey, pubkey, l); l.log("Private key: " + Base64.encode(privkey.toByteArray())); @@ -1527,10 +1529,11 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging { /** * Create a new destination, storing the destination and its private keys where - * instructed + * instructed. + * Does NOT support non-default sig types. * Deprecated - only used by CLI * - * @param writeTo location to store the private keys + * @param writeTo location to store the destination and private keys * @param pubDest location to store the destination * @param l logger to send messages to */ diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java index 1b1fdefcf..5936b6936 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java @@ -7,11 +7,13 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Properties; + 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.crypto.SigType; import net.i2p.data.Base32; import net.i2p.data.Destination; import net.i2p.i2ptunnel.socks.I2PSOCKSTunnel; @@ -49,8 +51,8 @@ public class TunnelController implements Logging { * the prefix should be used (and, in turn, that prefix should be stripped off * before being interpreted by this controller) * - * @param config original key=value mapping - * @param prefix beginning of key values that are relevent to this tunnel + * @param config original key=value mapping non-null + * @param prefix beginning of key values that are relevant to this tunnel */ public TunnelController(Properties config, String prefix) { this(config, prefix, true); @@ -58,6 +60,8 @@ public class TunnelController implements Logging { /** * + * @param config original key=value mapping non-null + * @param prefix beginning of key values that are relevant to this tunnel * @param createKey for servers, whether we want to create a brand new destination * with private keys at the location specified or not (does not * overwrite existing ones) @@ -99,7 +103,16 @@ public class TunnelController implements Logging { FileOutputStream fos = null; try { fos = new SecureFileOutputStream(keyFile); - Destination dest = client.createDestination(fos); + SigType stype = I2PClient.DEFAULT_SIGTYPE; + String st = _config.getProperty("option." + I2PClient.PROP_SIGTYPE); + if (st != null) { + SigType type = SigType.parseSigType(st); + if (type != null) + stype = type; + else + log("Unsupported sig type " + st); + } + Destination dest = client.createDestination(fos, stype); String destStr = dest.toBase64(); log("Private key created and saved in " + keyFile.getAbsolutePath()); log("You should backup this file in a secure place."); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java index 425cfb9ca..f1e8abaab 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java @@ -12,6 +12,7 @@ import net.i2p.client.I2PClient; import net.i2p.client.I2PClientFactory; import net.i2p.client.I2PSession; import net.i2p.client.I2PSessionException; +import net.i2p.crypto.SigType; import net.i2p.data.Destination; import net.i2p.i2ptunnel.I2PTunnel; import net.i2p.i2ptunnel.I2PTunnelTask; @@ -78,8 +79,17 @@ import net.i2p.util.EventDispatcher; I2PClient client = I2PClientFactory.createClient(); byte[] key; try { - ByteArrayOutputStream out = new ByteArrayOutputStream(512); - client.createDestination(out); + ByteArrayOutputStream out = new ByteArrayOutputStream(1024); + SigType stype = I2PClient.DEFAULT_SIGTYPE; + String st = tunnel.getClientOptions().getProperty(I2PClient.PROP_SIGTYPE); + if (st != null) { + SigType type = SigType.parseSigType(st); + if (type != null) + stype = type; + else + l.log("Unsupported sig type " + st); + } + client.createDestination(out, stype); key = out.toByteArray(); } catch(Exception exc) { throw new RuntimeException("failed to create i2p-destination", exc); diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java index ebfb0c707..493a0d272 100644 --- a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java +++ b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java @@ -14,6 +14,7 @@ import net.i2p.client.I2PClient; import net.i2p.client.I2PClientFactory; import net.i2p.client.I2PSession; import net.i2p.client.I2PSessionException; +import net.i2p.crypto.SigType; import net.i2p.util.Log; /** @@ -26,7 +27,7 @@ public class I2PSocketManagerFactory { public static final String PROP_MANAGER = "i2p.streaming.manager"; public static final String DEFAULT_MANAGER = "net.i2p.client.streaming.I2PSocketManagerFull"; - + /** * Create a socket manager using a brand new destination connected to the * I2CP router on the local machine on the default port (7654). @@ -79,9 +80,9 @@ public class I2PSocketManagerFactory { */ public static I2PSocketManager createManager(String i2cpHost, int i2cpPort, Properties opts) { I2PClient client = I2PClientFactory.createClient(); - ByteArrayOutputStream keyStream = new ByteArrayOutputStream(512); + ByteArrayOutputStream keyStream = new ByteArrayOutputStream(1024); try { - client.createDestination(keyStream); + client.createDestination(keyStream, getSigType(opts)); ByteArrayInputStream in = new ByteArrayInputStream(keyStream.toByteArray()); return createManager(in, i2cpHost, i2cpPort, opts); } catch (IOException ioe) { @@ -168,9 +169,9 @@ public class I2PSocketManagerFactory { int i2cpPort, Properties opts) throws I2PSessionException { if (myPrivateKeyStream == null) { I2PClient client = I2PClientFactory.createClient(); - ByteArrayOutputStream keyStream = new ByteArrayOutputStream(512); + ByteArrayOutputStream keyStream = new ByteArrayOutputStream(1024); try { - client.createDestination(keyStream); + client.createDestination(keyStream, getSigType(opts)); } catch (Exception e) { throw new I2PSessionException("Error creating keys", e); } @@ -257,6 +258,23 @@ public class I2PSocketManagerFactory { return i2cpPort; } + /** + * @param opts may be null + * @since 0.9.11 + */ + private static SigType getSigType(Properties opts) { + if (opts != null) { + String st = opts.getProperty(I2PClient.PROP_SIGTYPE); + if (st != null) { + SigType rv = SigType.parseSigType(st); + if (rv != null) + return rv; + getLog().error("Unsupported sig type " + st); + } + } + return I2PClient.DEFAULT_SIGTYPE; + } + /** @since 0.9.7 */ private static Log getLog() { return I2PAppContext.getGlobalContext().logManager().getLog(I2PSocketManagerFactory.class); diff --git a/apps/streaming/java/src/net/i2p/client/streaming/Packet.java b/apps/streaming/java/src/net/i2p/client/streaming/Packet.java index 1ecdc0771..411e66105 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/Packet.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/Packet.java @@ -397,13 +397,14 @@ class Packet { * @throws IllegalStateException if there is data missing or otherwise b0rked */ public int writePacket(byte buffer[], int offset) throws IllegalStateException { - return writePacket(buffer, offset, true); + return writePacket(buffer, offset, 0); } + /** - * @param includeSig if true, include the real signature, otherwise put zeroes - * in its place. + * @param fakeSigLen if 0, include the real signature in _optionSignature; + * if nonzero, leave space for that many bytes */ - private int writePacket(byte buffer[], int offset, boolean includeSig) throws IllegalStateException { + private int writePacket(byte buffer[], int offset, int fakeSigLen) throws IllegalStateException { int cur = offset; DataHelper.toLong(buffer, cur, 4, (_sendStreamId >= 0 ? _sendStreamId : STREAM_ID_UNKNOWN)); cur += 4; @@ -438,7 +439,12 @@ class Packet { if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED)) optionSize += 2; if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) { - optionSize += _optionSignature.length(); + if (fakeSigLen > 0) + optionSize += fakeSigLen; + else if (_optionSignature != null) + optionSize += _optionSignature.length(); + else + throw new IllegalStateException(); } DataHelper.toLong(buffer, cur, 2, optionSize); @@ -456,11 +462,14 @@ class Packet { cur += 2; } if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) { - if (includeSig) + if (fakeSigLen == 0) { + // we're signing (or validating) System.arraycopy(_optionSignature.getData(), 0, buffer, cur, _optionSignature.length()); - else // we're signing (or validating) - Arrays.fill(buffer, cur, cur + _optionSignature.length(), (byte)0x0); - cur += _optionSignature.length(); + cur += _optionSignature.length(); + } else { + Arrays.fill(buffer, cur, cur + fakeSigLen, (byte)0x0); + cur += fakeSigLen; + } } if (_payload != null) { @@ -661,7 +670,7 @@ class Packet { if (buffer == null) buffer = new byte[size]; - int written = writePacket(buffer, 0, false); + int written = writePacket(buffer, 0, from.getSigningPublicKey().getType().getSigLen()); if (written != size) { ctx.logManager().getLog(Packet.class).error("Written " + written + " size " + size + " for " + toString(), new Exception("moo")); return false; @@ -692,7 +701,7 @@ class Packet { */ public int writeSignedPacket(byte buffer[], int offset, I2PAppContext ctx, SigningPrivateKey key) throws IllegalStateException { setFlag(FLAG_SIGNATURE_INCLUDED); - int size = writePacket(buffer, offset, false); + int size = writePacket(buffer, offset, key.getType().getSigLen()); _optionSignature = ctx.dsa().sign(buffer, offset, size, key); //if (false) { // Log l = ctx.logManager().getLog(Packet.class); @@ -760,7 +769,7 @@ class Packet { if (isFlagSet(FLAG_CLOSE)) buf.append(" CLOSE"); if (isFlagSet(FLAG_DELAY_REQUESTED)) buf.append(" DELAY ").append(_optionDelay); if (isFlagSet(FLAG_ECHO)) buf.append(" ECHO"); - if (isFlagSet(FLAG_FROM_INCLUDED)) buf.append(" FROM"); + if (isFlagSet(FLAG_FROM_INCLUDED)) buf.append(" FROM ").append(_optionFrom.size()); if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED)) buf.append(" MS ").append(_optionMaxSize); if (isFlagSet(FLAG_PROFILE_INTERACTIVE)) buf.append(" INTERACTIVE"); if (isFlagSet(FLAG_RESET)) buf.append(" RESET"); diff --git a/core/java/src/net/i2p/client/I2PClient.java b/core/java/src/net/i2p/client/I2PClient.java index 84bcf50d2..bbfd6811a 100644 --- a/core/java/src/net/i2p/client/I2PClient.java +++ b/core/java/src/net/i2p/client/I2PClient.java @@ -41,6 +41,11 @@ public interface I2PClient { /** @since 0.8.1 */ public final static String PROP_RELIABILITY_NONE = "none"; + /** @since 0.9.11 */ + public static final String PROP_SIGTYPE = "i2cp.destination.sigType"; + /** @since 0.9.11 */ + public static final SigType DEFAULT_SIGTYPE = SigType.DSA_SHA1; + /** * For router->client payloads. * diff --git a/core/java/src/net/i2p/client/I2PClientImpl.java b/core/java/src/net/i2p/client/I2PClientImpl.java index 7fac17e10..e29cdc342 100644 --- a/core/java/src/net/i2p/client/I2PClientImpl.java +++ b/core/java/src/net/i2p/client/I2PClientImpl.java @@ -47,7 +47,7 @@ class I2PClientImpl implements I2PClient { * format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile} */ public Destination createDestination(OutputStream destKeyStream) throws I2PException, IOException { - return createDestination(destKeyStream, SigType.DSA_SHA1); + return createDestination(destKeyStream, DEFAULT_SIGTYPE); } /** diff --git a/core/java/src/net/i2p/crypto/DSAEngine.java b/core/java/src/net/i2p/crypto/DSAEngine.java index dce8843e6..16c63e8ea 100644 --- a/core/java/src/net/i2p/crypto/DSAEngine.java +++ b/core/java/src/net/i2p/crypto/DSAEngine.java @@ -89,13 +89,20 @@ public class DSAEngine { * Uses TheCrypto code for DSA-SHA1 unless configured to use the java.security libraries. */ public boolean verifySignature(Signature signature, byte signedData[], SigningPublicKey verifyingKey) { + return verifySignature(signature, signedData, 0, signedData.length, verifyingKey); + } + + /** + * Verify using any sig type as of 0.9.11 (DSA only prior to that) + */ + public boolean verifySignature(Signature signature, byte signedData[], int offset, int size, SigningPublicKey verifyingKey) { boolean rv; SigType type = signature.getType(); if (type != verifyingKey.getType()) throw new IllegalArgumentException("type mismatch sig=" + signature.getType() + " key=" + verifyingKey.getType()); if (type != SigType.DSA_SHA1) { try { - rv = altVerifySig(signature, signedData, verifyingKey); + rv = altVerifySig(signature, signedData, offset, size, verifyingKey); if ((!rv) && _log.shouldLog(Log.WARN)) _log.warn(type + " Sig Verify Fail"); return rv; @@ -107,7 +114,7 @@ public class DSAEngine { } if (_useJavaLibs) { try { - rv = altVerifySigSHA1(signature, signedData, verifyingKey); + rv = altVerifySigSHA1(signature, signedData, offset, size, verifyingKey); if ((!rv) && _log.shouldLog(Log.WARN)) _log.warn("Lib DSA Sig Verify Fail"); return rv; @@ -117,19 +124,12 @@ public class DSAEngine { // now try TheCrypto } } - rv = verifySignature(signature, signedData, 0, signedData.length, verifyingKey); + rv = verifySignature(signature, calculateHash(signedData, offset, size), verifyingKey); if ((!rv) && _log.shouldLog(Log.WARN)) _log.warn("TheCrypto DSA Sig Verify Fail"); return rv; } - /** - * Verify using DSA-SHA1 ONLY - */ - public boolean verifySignature(Signature signature, byte signedData[], int offset, int size, SigningPublicKey verifyingKey) { - return verifySignature(signature, calculateHash(signedData, offset, size), verifyingKey); - } - /** * Verify using DSA-SHA1 ONLY */ @@ -256,16 +256,26 @@ public class DSAEngine { } /** - * Sign using DSA-SHA1 or ECDSA. + * Sign using any key type. * Uses TheCrypto code unless configured to use the java.security libraries. * * @return null on error */ public Signature sign(byte data[], SigningPrivateKey signingKey) { + return sign(data, 0, data.length, signingKey); + } + + /** + * Sign using any key type as of 0.9.11 (DSA-SHA1 only prior to that) + * + * @return null on error + */ + public Signature sign(byte data[], int offset, int length, SigningPrivateKey signingKey) { + if ((signingKey == null) || (data == null) || (data.length <= 0)) return null; SigType type = signingKey.getType(); if (type != SigType.DSA_SHA1) { try { - return altSign(data, signingKey); + return altSign(data, offset, length, signingKey); } catch (GeneralSecurityException gse) { if (_log.shouldLog(Log.WARN)) _log.warn(type + " Sign Fail", gse); @@ -274,23 +284,13 @@ public class DSAEngine { } if (_useJavaLibs) { try { - return altSignSHA1(data, signingKey); + return altSignSHA1(data, offset, length, signingKey); } catch (GeneralSecurityException gse) { if (_log.shouldLog(Log.WARN)) _log.warn("Lib Sign Fail, privkey = " + signingKey, gse); // now try TheCrypto } } - return sign(data, 0, data.length, signingKey); - } - - /** - * Sign using DSA-SHA1 ONLY - * - * @return null on error - */ - public Signature sign(byte data[], int offset, int length, SigningPrivateKey signingKey) { - if ((signingKey == null) || (data == null) || (data.length <= 0)) return null; SHA1Hash h = calculateHash(data, offset, length); return sign(h, signingKey); } @@ -495,20 +495,20 @@ public class DSAEngine { /** * Generic verify DSA_SHA1, ECDSA, or RSA * @throws GeneralSecurityException if algorithm unvailable or on other errors - * @since 0.9.9 + * @since 0.9.9 added off/len 0.9.11 */ - private boolean altVerifySig(Signature signature, byte[] data, SigningPublicKey verifyingKey) + private boolean altVerifySig(Signature signature, byte[] data, int offset, int len, SigningPublicKey verifyingKey) throws GeneralSecurityException { SigType type = signature.getType(); if (type != verifyingKey.getType()) throw new IllegalArgumentException("type mismatch sig=" + type + " key=" + verifyingKey.getType()); if (type == SigType.DSA_SHA1) - return altVerifySigSHA1(signature, data, verifyingKey); + return altVerifySigSHA1(signature, data, offset, len, verifyingKey); java.security.Signature jsig = java.security.Signature.getInstance(type.getAlgorithmName()); PublicKey pubKey = SigUtil.toJavaKey(verifyingKey); jsig.initVerify(pubKey); - jsig.update(data); + jsig.update(data, offset, len); boolean rv = jsig.verify(SigUtil.toJavaSig(signature)); return rv; } @@ -555,13 +555,14 @@ public class DSAEngine { /** * Alternate to verifySignature() using java.security libraries. * @throws GeneralSecurityException if algorithm unvailable or on other errors - * @since 0.8.7 + * @since 0.8.7 added off/len 0.9.11 */ - private boolean altVerifySigSHA1(Signature signature, byte[] data, SigningPublicKey verifyingKey) throws GeneralSecurityException { + private boolean altVerifySigSHA1(Signature signature, byte[] data, int offset, + int len, SigningPublicKey verifyingKey) throws GeneralSecurityException { java.security.Signature jsig = java.security.Signature.getInstance("SHA1withDSA"); PublicKey pubKey = SigUtil.toJavaDSAKey(verifyingKey); jsig.initVerify(pubKey); - jsig.update(data); + jsig.update(data, offset, len); boolean rv = jsig.verify(SigUtil.toJavaSig(signature)); //if (!rv) { // System.out.println("BAD SIG\n" + net.i2p.util.HexDump.dump(signature.getData())); @@ -573,17 +574,18 @@ public class DSAEngine { /** * Generic sign DSA_SHA1, ECDSA, or RSA * @throws GeneralSecurityException if algorithm unvailable or on other errors - * @since 0.9.9 + * @since 0.9.9 added off/len 0.9.11 */ - private Signature altSign(byte[] data, SigningPrivateKey privateKey) throws GeneralSecurityException { + private Signature altSign(byte[] data, int offset, int len, + SigningPrivateKey privateKey) throws GeneralSecurityException { SigType type = privateKey.getType(); if (type == SigType.DSA_SHA1) - return altSignSHA1(data, privateKey); + return altSignSHA1(data, offset, len, privateKey); java.security.Signature jsig = java.security.Signature.getInstance(type.getAlgorithmName()); PrivateKey privKey = SigUtil.toJavaKey(privateKey); jsig.initSign(privKey, _context.random()); - jsig.update(data); + jsig.update(data, offset, len); return SigUtil.fromJavaSig(jsig.sign(), type); } @@ -622,13 +624,14 @@ public class DSAEngine { /** * Alternate to sign() using java.security libraries. * @throws GeneralSecurityException if algorithm unvailable or on other errors - * @since 0.8.7 + * @since 0.8.7 added off/len args 0.9.11 */ - private Signature altSignSHA1(byte[] data, SigningPrivateKey privateKey) throws GeneralSecurityException { + private Signature altSignSHA1(byte[] data, int offset, int len, + SigningPrivateKey privateKey) throws GeneralSecurityException { java.security.Signature jsig = java.security.Signature.getInstance("SHA1withDSA"); PrivateKey privKey = SigUtil.toJavaDSAKey(privateKey); jsig.initSign(privKey, _context.random()); - jsig.update(data); + jsig.update(data, offset, len); return SigUtil.fromJavaSig(jsig.sign(), SigType.DSA_SHA1); } diff --git a/core/java/src/net/i2p/data/Destination.java b/core/java/src/net/i2p/data/Destination.java index 4d8affc87..7268be640 100644 --- a/core/java/src/net/i2p/data/Destination.java +++ b/core/java/src/net/i2p/data/Destination.java @@ -57,6 +57,16 @@ public class Destination extends KeysAndCert { PublicKey pk = PublicKey.create(in); SigningPublicKey sk = SigningPublicKey.create(in); Certificate c = Certificate.create(in); + byte[] padding; + if (c.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY) { + // convert SPK to new SPK and padding + KeyCertificate kcert = c.toKeyCertificate(); + padding = sk.getPadding(kcert); + sk = sk.toTypedKey(kcert); + c = kcert; + } else { + padding = null; + } Destination rv; synchronized(_cache) { rv = _cache.get(sk); @@ -67,7 +77,7 @@ public class Destination extends KeysAndCert { } //if (STATS) // I2PAppContext.getGlobalContext().statManager().addRateData("DestCache", 0); - rv = new Destination(pk, sk, c); + rv = new Destination(pk, sk, c, padding); _cache.put(sk, rv); } return rv; @@ -86,10 +96,11 @@ public class Destination extends KeysAndCert { /** * @since 0.9.9 */ - private Destination(PublicKey pk, SigningPublicKey sk, Certificate c) { + private Destination(PublicKey pk, SigningPublicKey sk, Certificate c, byte[] padding) { _publicKey = pk; _signingKey = sk; _certificate = c; + _padding = padding; } /** @@ -138,7 +149,16 @@ public class Destination extends KeysAndCert { ****/ public int size() { - return PublicKey.KEYSIZE_BYTES + _signingKey.length() + _certificate.size(); + int rv = PublicKey.KEYSIZE_BYTES + _signingKey.length(); + if (_certificate.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY) { + // cert data included in keys + rv += 7; + if (_padding != null) + rv += _padding.length; + } else { + rv += _certificate.size(); + } + return rv; } /** From 41e071efe514a0c9ddce42430d1d8c802238c63b Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 3 Jan 2014 15:31:08 +0000 Subject: [PATCH 3/9] * Key cert GUI support: - Add setting in i2ptunnel server edit page - Comment out cert setting on i2ptunnel server edit page - Show key type on susidns details page - Show key type on LS debug page --- .../src/net/i2p/i2ptunnel/web/EditBean.java | 6 ++++ .../src/net/i2p/i2ptunnel/web/IndexBean.java | 18 +++++++++++- apps/i2ptunnel/jsp/editServer.jsp | 27 ++++++++++++++++++ .../src/net/i2p/router/web/NetDbRenderer.java | 3 ++ .../java/src/i2p/susi/dns/AddressBean.java | 28 +++++++++++++++++++ apps/susidns/src/jsp/details.jsp | 2 +- 6 files changed, 82 insertions(+), 2 deletions(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java index 6e917cf0c..0e1a61d2b 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java @@ -14,6 +14,7 @@ import java.util.Properties; import java.util.Set; import java.util.TreeMap; +import net.i2p.client.I2PClient; import net.i2p.data.Base64; import net.i2p.data.Destination; import net.i2p.data.PrivateKeyFile; @@ -177,6 +178,11 @@ public class EditBean extends IndexBean { return getBooleanProperty(tunnel, "i2cp.encryptLeaseSet"); } + /** @since 0.9.11 */ + public int getSigType(int tunnel) { + return getProperty(tunnel, I2PClient.PROP_SIGTYPE, 0); + } + /** @since 0.8.9 */ public boolean getDCC(int tunnel) { return getBooleanProperty(tunnel, I2PTunnelIRCClient.PROP_DCC); 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 56be5d3a8..620c8fff3 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java @@ -22,6 +22,7 @@ import java.util.StringTokenizer; import java.util.concurrent.ConcurrentHashMap; import net.i2p.I2PAppContext; +import net.i2p.client.I2PClient; import net.i2p.data.Base32; import net.i2p.data.Certificate; import net.i2p.data.Destination; @@ -950,6 +951,7 @@ public class IndexBean { } catch (NumberFormatException nfe) {} } } + public void setCert(String val) { if (val != null) { try { @@ -957,10 +959,24 @@ public class IndexBean { } catch (NumberFormatException nfe) {} } } + public void setSigner(String val) { _certSigner = val; } + /** @since 0.9.11 */ + public void setSigType(String val) { + if (val != null) { + _otherOptions.put(I2PClient.PROP_SIGTYPE, val); + if (val.equals("0")) + _certType = 0; + else + _certType = 5; + } + // TODO: Call modifyDestination?? + // Otherwise this only works on a new tunnel... + } + /** Modify or create a destination */ private String modifyDestination() { if (_privKeyFile == null || _privKeyFile.trim().length() <= 0) @@ -1205,7 +1221,7 @@ public class IndexBean { "i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.leaseSetKey", "i2cp.accessList", 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 + PROP_MAX_STREAMS, I2PClient.PROP_SIGTYPE }; private static final String _httpServerOpts[] = { I2PTunnelHTTPServer.OPT_POST_WINDOW, diff --git a/apps/i2ptunnel/jsp/editServer.jsp b/apps/i2ptunnel/jsp/editServer.jsp index a6c8e7413..c9c72f6c0 100644 --- a/apps/i2ptunnel/jsp/editServer.jsp +++ b/apps/i2ptunnel/jsp/editServer.jsp @@ -491,6 +491,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
+<% /***************** %>
+<% **********************/ %> + +
+ +
+
+
+ + class="tickbox" /> +
+
+ + class="tickbox" /> +
+
+ + class="tickbox" /> +
+
+ + class="tickbox" /> +
+

diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java index d892469a3..db047c39b 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java @@ -23,6 +23,7 @@ import java.util.Map; import java.util.Set; import java.util.TreeSet; +import net.i2p.data.Base32; import net.i2p.data.DataHelper; import net.i2p.data.Destination; import net.i2p.data.Hash; @@ -167,6 +168,8 @@ public class NetDbRenderer { median = dist; } buf.append(" Dist: ").append(fmt.format(biLog2(dist))).append("
"); + buf.append(Base32.encode(key.getData())).append(".b32.i2p
"); + buf.append("Sig type: ").append(dest.getSigningPublicKey().getType()).append("
"); buf.append("Routing Key: ").append(ls.getRoutingKey().toBase64()); buf.append("
"); buf.append("Encryption Key: ").append(ls.getEncryptionKey().toBase64().substring(0, 20)).append("...
"); diff --git a/apps/susidns/src/java/src/i2p/susi/dns/AddressBean.java b/apps/susidns/src/java/src/i2p/susi/dns/AddressBean.java index 24289f903..eef101c1e 100644 --- a/apps/susidns/src/java/src/i2p/susi/dns/AddressBean.java +++ b/apps/susidns/src/java/src/i2p/susi/dns/AddressBean.java @@ -29,6 +29,7 @@ import java.util.Locale; import java.util.Properties; import net.i2p.I2PAppContext; +import net.i2p.crypto.SigType; import net.i2p.data.Base32; import net.i2p.data.Base64; import net.i2p.data.Certificate; @@ -228,11 +229,38 @@ public class AddressBean return _("Hidden"); case Certificate.CERTIFICATE_TYPE_SIGNED: return _("Signed"); + case Certificate.CERTIFICATE_TYPE_KEY: + return _("Key"); default: return _("Type {0}", type); } } + /** + * Do this the easy way + * @since 0.9.11 + */ + public String getSigType() { + // (4 / 3) * (pubkey length + signing key length) + String cert = destination.substring(512); + if (cert.equals("AAAA")) + return _("DSA 1024 bit"); + byte[] enc = Base64.decode(cert); + if (enc == null) + // shouldn't happen + return "invalid"; + int type = enc[0] & 0xff; + if (type != Certificate.CERTIFICATE_TYPE_KEY) + return _("DSA 1024 bit"); + int st = ((enc[3] & 0xff) << 8) | (enc[4] & 0xff); + if (st == 0) + return _("DSA 1024 bit"); + SigType stype = SigType.getByCode(st); + if (stype == null) + return _("Type {0}", st); + return stype.toString(); + } + /** @since 0.8.7 */ private String getProp(String p) { if (props == null) diff --git a/apps/susidns/src/jsp/details.jsp b/apps/susidns/src/jsp/details.jsp index 2c657f7c2..d5d625641 100644 --- a/apps/susidns/src/jsp/details.jsp +++ b/apps/susidns/src/jsp/details.jsp @@ -107,7 +107,7 @@ <%=intl._("ElGamal 2048 bit")%> <%=intl._("Signing Key")%> -<%=intl._("DSA 1024 bit")%> +<%=addr.getSigType()%> <%=intl._("Certificate")%> <%=addr.getCert()%> From fba209ca7df1685d4d35f7ac04c503e9bac2bb5a Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 4 Jan 2014 17:32:38 +0000 Subject: [PATCH 4/9] restore method used by bote --- core/java/src/net/i2p/data/Destination.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/java/src/net/i2p/data/Destination.java b/core/java/src/net/i2p/data/Destination.java index 7268be640..9533c86f4 100644 --- a/core/java/src/net/i2p/data/Destination.java +++ b/core/java/src/net/i2p/data/Destination.java @@ -123,10 +123,10 @@ public class Destination extends KeysAndCert { /** * deprecated was used only by Packet.java in streaming, now unused + * Warning - used by i2p-bote. Does NOT support alternate key types. DSA-SHA1 only. * * @throws IllegalStateException if data already set */ -/**** public int readBytes(byte source[], int offset) throws DataFormatException { if (source == null) throw new DataFormatException("Null source"); if (source.length <= offset + PublicKey.KEYSIZE_BYTES + SigningPublicKey.KEYSIZE_BYTES) @@ -146,7 +146,6 @@ public class Destination extends KeysAndCert { return cur - offset; } -****/ public int size() { int rv = PublicKey.KEYSIZE_BYTES + _signingKey.length(); From 17e63b054cf24c2bddb78536c2eced2116d93b7a Mon Sep 17 00:00:00 2001 From: zzz Date: Sun, 5 Jan 2014 16:38:39 +0000 Subject: [PATCH 5/9] add sigtype to i2ptunnel client gui too --- .../src/net/i2p/i2ptunnel/web/IndexBean.java | 2 +- apps/i2ptunnel/jsp/editClient.jsp | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) 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 620c8fff3..6dfa4bba8 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java @@ -1215,7 +1215,7 @@ public class IndexBean { "i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.closeIdleTime", "outproxyUsername", "outproxyPassword", I2PTunnelHTTPClient.PROP_JUMP_SERVERS, - I2PTunnelHTTPClientBase.PROP_AUTH + I2PTunnelHTTPClientBase.PROP_AUTH, I2PClient.PROP_SIGTYPE }; private static final String _otherServerOpts[] = { "i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.leaseSetKey", "i2cp.accessList", diff --git a/apps/i2ptunnel/jsp/editClient.jsp b/apps/i2ptunnel/jsp/editClient.jsp index 41727d79f..f9437bd5b 100644 --- a/apps/i2ptunnel/jsp/editClient.jsp +++ b/apps/i2ptunnel/jsp/editClient.jsp @@ -434,6 +434,35 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
<% } %> +
+ +
+
+
+ + class="tickbox" /> +
+
+ + class="tickbox" /> +
+
+ + class="tickbox" /> +
+
+ + class="tickbox" /> +
+
+ +
+
+
+ <% if ("httpclient".equals(tunnelType) || "connectclient".equals(tunnelType) || "sockstunnel".equals(tunnelType) || "socksirctunnel".equals(tunnelType)) { %>
From 28575dbdaeda470805a0578cfaea30021d40b9d1 Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 28 Jan 2014 14:21:54 +0000 Subject: [PATCH 6/9] * Key certs: - Hide setting in i2ptunnel edit pages unless advanced user - Only store LS with key certs to routers that support it --- .../src/net/i2p/i2ptunnel/web/EditBean.java | 5 +++++ apps/i2ptunnel/jsp/editClient.jsp | 2 ++ apps/i2ptunnel/jsp/editServer.jsp | 2 ++ .../router/networkdb/kademlia/StoreJob.java | 21 +++++++++++++++++++ 4 files changed, 30 insertions(+) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java index 0e1a61d2b..85365e28d 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java @@ -351,6 +351,11 @@ public class EditBean extends IndexBean { return Addresses.getAllAddresses(); } + /** @since 0.9.11 */ + public boolean isAdvanced() { + return _context.getBooleanProperty("routerconsole.advanced"); + } + public String getI2CPHost(int tunnel) { if (_context.isRouterContext()) return _("internal"); diff --git a/apps/i2ptunnel/jsp/editClient.jsp b/apps/i2ptunnel/jsp/editClient.jsp index f9437bd5b..f158f61ee 100644 --- a/apps/i2ptunnel/jsp/editClient.jsp +++ b/apps/i2ptunnel/jsp/editClient.jsp @@ -434,6 +434,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
<% } %> + <% if (editBean.isAdvanced()) { %>