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 6fbe1dcce4acf82b94535f7b171a56ddb4ef45d7..1ecdc0771b6cb01f8e3cac49f1e35cfd440fd267 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 521ed35b5bc8a6e143a96394d788770b0155e625..84bcf50d2af68c8215124d89f52f7d6f1935f76f 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 932daa61a5a4f0c9fc9476bce23d82253ecee215..7fac17e10d7532508ddaf2a91c7f1ca145022c3d 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 8cf7ad095e827365bdbd093f3a123d51443fcaf3..cfacb4cec13efb2973e3023c93dea59edc51f30e 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 e91ae2f8b7e9416359eb20f2524f3bb4e3e8e8d4..8e198daf9fe38afb1f15ed033a26096cb05102f1 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 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 or this */ + /** + * @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 620f571f4d328604b50127df06224858754d8c30..09f224efc06f98ab65fc6b5f0fa3e026870be490 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 00c897a6f5d4a637afe0c8eb1f6795d593294835..281ea20359d68900d1b7b6d068d98929c9c61e8b 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 dd23ad05abf9418f8888bf7711e851b61ca2e856..55e21fea40b6ba982863f04ca80de9702cce67ff 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 105ff09b1a5e7686cdc1d259b5176dfe9d966e07..a37d65dfb040e511f0546664f8b4731589a1bc0d 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 ef6484f31ed4d147e47fb06562ad6e1aa7b75c12..a4a1b3148d22c14e2d548e319aa55a33ec30b8cd 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 ad21985e39f61a1c9fb11ceef4074290a10014a1..4d8affc870570310a5a0e0302902b7cb296aa3a1 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 0000000000000000000000000000000000000000..5726c80b14f6d908e66ca69b08a716b788be78f2 --- /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 429d89be7c56bbeef7d195e9cbf1d12351ead5b8..33a1de2944dc3f6c64b4e48f392c645398b97108 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 75b24220d4d89a399f5b258c38ba3c2171081ca4..cf9b39deb2c467882ffe8fd9091c4cdd0322943e 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 9c71cc96b301a84275b8d7c03a530d45f3c255e0..b97d76257461d08baa56fdb8be98d02b75273f92 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 214937815e164c26b0bbba686eb874b0514c88b7..4900a4e9ee470d1b47b9461c0200750ed7765281 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 83f0410e2458ab1e4c920260aac7cb747da6a2ea..1338cb664140607a2bfc2ee1b55cc0a4e9a77831 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 4b60ee7e88fb1f878931f0fd1d4f9d367eced176..a8fcbb208196c88c8310f5b3e33b382a91057e39 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 e01d6ec39294a510371cf16a9bc8862a3600ee38..c7ec32bb8cccd80191d00905469e99a6d72bad62 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 b57af1f8da89573253470f706dd65c3817238f0a..6477768c53211e7cbcb6d002613b8dface7c3171 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 cd6db38e4edc9cb06d19a17287cfe9c073db1624..8c5521993edb3556775f3088f5f95a0d11dbf833 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); }