I2P Address: [http://git.idk.i2p]

Skip to content
Snippets Groups Projects
Commit 5842e252 authored by zzz's avatar zzz
Browse files

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
parent bf485d8b
No related branches found
No related tags found
No related merge requests found
Showing
with 697 additions and 77 deletions
...@@ -5,6 +5,7 @@ import java.io.IOException; ...@@ -5,6 +5,7 @@ import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.crypto.SigType;
import net.i2p.data.Base64; import net.i2p.data.Base64;
import net.i2p.data.ByteArray; import net.i2p.data.ByteArray;
import net.i2p.data.DataFormatException; import net.i2p.data.DataFormatException;
...@@ -436,8 +437,9 @@ class Packet { ...@@ -436,8 +437,9 @@ class Packet {
optionSize += _optionFrom.size(); optionSize += _optionFrom.size();
if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED)) if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED))
optionSize += 2; optionSize += 2;
if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) {
optionSize += Signature.SIGNATURE_BYTES; optionSize += _optionSignature.length();
}
DataHelper.toLong(buffer, cur, 2, optionSize); DataHelper.toLong(buffer, cur, 2, optionSize);
cur += 2; cur += 2;
...@@ -455,10 +457,10 @@ class Packet { ...@@ -455,10 +457,10 @@ class Packet {
} }
if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) { if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) {
if (includeSig) 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) else // we're signing (or validating)
Arrays.fill(buffer, cur, cur+Signature.SIGNATURE_BYTES, (byte)0x0); Arrays.fill(buffer, cur, cur + _optionSignature.length(), (byte)0x0);
cur += Signature.SIGNATURE_BYTES; cur += _optionSignature.length();
} }
if (_payload != null) { if (_payload != null) {
...@@ -511,7 +513,7 @@ class Packet { ...@@ -511,7 +513,7 @@ class Packet {
if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED)) if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED))
size += 2; size += 2;
if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) if (isFlagSet(FLAG_SIGNATURE_INCLUDED))
size += Signature.SIGNATURE_BYTES; size += _optionSignature.length();
size += 2; // option size size += 2; // option size
...@@ -606,12 +608,37 @@ class Packet { ...@@ -606,12 +608,37 @@ class Packet {
cur += 2; cur += 2;
} }
if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) { if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) {
Signature optionSignature = new Signature(); Signature optionSignature;
byte buf[] = new byte[Signature.SIGNATURE_BYTES]; Destination from = getOptionalFrom();
System.arraycopy(buffer, cur, buf, 0, Signature.SIGNATURE_BYTES); 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); optionSignature.setData(buf);
setOptionalSignature(optionSignature); setOptionalSignature(optionSignature);
cur += Signature.SIGNATURE_BYTES; cur += buf.length;
} }
} }
...@@ -667,12 +694,12 @@ class Packet { ...@@ -667,12 +694,12 @@ class Packet {
setFlag(FLAG_SIGNATURE_INCLUDED); setFlag(FLAG_SIGNATURE_INCLUDED);
int size = writePacket(buffer, offset, false); int size = writePacket(buffer, offset, false);
_optionSignature = ctx.dsa().sign(buffer, offset, size, key); _optionSignature = ctx.dsa().sign(buffer, offset, size, key);
if (false) { //if (false) {
Log l = ctx.logManager().getLog(Packet.class); // Log l = ctx.logManager().getLog(Packet.class);
l.error("Signing: " + toString()); // l.error("Signing: " + toString());
l.error(Base64.encode(buffer, 0, size)); // l.error(Base64.encode(buffer, 0, size));
l.error("Signature: " + Base64.encode(_optionSignature.getData())); // l.error("Signature: " + Base64.encode(_optionSignature.getData()));
} //}
// jump into the signed data and inject the signature where we // jump into the signed data and inject the signature where we
// previously placed a bunch of zeroes // previously placed a bunch of zeroes
int signatureOffset = offset int signatureOffset = offset
...@@ -687,7 +714,7 @@ class Packet { ...@@ -687,7 +714,7 @@ class Packet {
+ (isFlagSet(FLAG_DELAY_REQUESTED) ? 2 : 0) + (isFlagSet(FLAG_DELAY_REQUESTED) ? 2 : 0)
+ (isFlagSet(FLAG_FROM_INCLUDED) ? _optionFrom.size() : 0) + (isFlagSet(FLAG_FROM_INCLUDED) ? _optionFrom.size() : 0)
+ (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED) ? 2 : 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; return size;
} }
......
...@@ -15,6 +15,7 @@ import java.io.OutputStream; ...@@ -15,6 +15,7 @@ import java.io.OutputStream;
import java.util.Properties; import java.util.Properties;
import net.i2p.I2PException; import net.i2p.I2PException;
import net.i2p.crypto.SigType;
import net.i2p.data.Certificate; import net.i2p.data.Certificate;
import net.i2p.data.Destination; import net.i2p.data.Destination;
...@@ -83,6 +84,18 @@ public interface I2PClient { ...@@ -83,6 +84,18 @@ public interface I2PClient {
*/ */
public Destination createDestination(OutputStream destKeyStream) throws I2PException, IOException; 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 /** Create a new destination with the given certificate and store it, along with the private
* encryption and signing keys at the specified location * encryption and signing keys at the specified location
* *
......
...@@ -12,17 +12,22 @@ package net.i2p.client; ...@@ -12,17 +12,22 @@ package net.i2p.client;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.util.Properties; import java.util.Properties;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.I2PException; import net.i2p.I2PException;
import net.i2p.crypto.KeyGenerator; import net.i2p.crypto.KeyGenerator;
import net.i2p.crypto.SigType;
import net.i2p.data.Certificate; import net.i2p.data.Certificate;
import net.i2p.data.Destination; import net.i2p.data.Destination;
import net.i2p.data.KeyCertificate;
import net.i2p.data.PrivateKey; import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey; import net.i2p.data.PublicKey;
import net.i2p.data.SigningPrivateKey; import net.i2p.data.SigningPrivateKey;
import net.i2p.data.SigningPublicKey; import net.i2p.data.SigningPublicKey;
import net.i2p.data.SimpleDataStructure;
import net.i2p.util.RandomSource;
/** /**
* Base client implementation. * Base client implementation.
...@@ -34,7 +39,7 @@ import net.i2p.data.SigningPublicKey; ...@@ -34,7 +39,7 @@ import net.i2p.data.SigningPublicKey;
class I2PClientImpl implements I2PClient { 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 * This is not bound to the I2PClient, you must supply the data back again
* in createSession(). * in createSession().
* *
...@@ -42,9 +47,26 @@ class I2PClientImpl implements I2PClient { ...@@ -42,9 +47,26 @@ class I2PClientImpl implements I2PClient {
* format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile} * format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile}
*/ */
public Destination createDestination(OutputStream destKeyStream) throws I2PException, IOException { public Destination createDestination(OutputStream destKeyStream) throws I2PException, IOException {
Certificate cert = new Certificate(); return createDestination(destKeyStream, SigType.DSA_SHA1);
cert.setCertificateType(Certificate.CERTIFICATE_TYPE_NULL); }
cert.setPayload(null);
/**
* 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); return createDestination(destKeyStream, cert);
} }
...@@ -52,20 +74,49 @@ class I2PClientImpl implements I2PClient { ...@@ -52,20 +74,49 @@ class I2PClientImpl implements I2PClient {
* Create the destination with the given payload and write it out along with * Create the destination with the given payload and write it out along with
* the PrivateKey and SigningPrivateKey to the destKeyStream * 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, * @param destKeyStream location to write out the destination, PrivateKey, and SigningPrivateKey,
* format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile} * format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile}
*/ */
public Destination createDestination(OutputStream destKeyStream, Certificate cert) throws I2PException, IOException { public Destination createDestination(OutputStream destKeyStream, Certificate cert) throws I2PException, IOException {
Destination d = new Destination(); Destination d = new Destination();
d.setCertificate(cert);
Object keypair[] = KeyGenerator.getInstance().generatePKIKeypair(); Object keypair[] = KeyGenerator.getInstance().generatePKIKeypair();
PublicKey publicKey = (PublicKey) keypair[0]; PublicKey publicKey = (PublicKey) keypair[0];
PrivateKey privateKey = (PrivateKey) keypair[1]; 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]; SigningPublicKey signingPubKey = (SigningPublicKey) signingKeys[0];
SigningPrivateKey signingPrivKey = (SigningPrivateKey) signingKeys[1]; SigningPrivateKey signingPrivKey = (SigningPrivateKey) signingKeys[1];
d.setPublicKey(publicKey); d.setPublicKey(publicKey);
d.setSigningPublicKey(signingPubKey); 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); d.writeBytes(destKeyStream);
privateKey.writeBytes(destKeyStream); privateKey.writeBytes(destKeyStream);
......
...@@ -62,7 +62,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa ...@@ -62,7 +62,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
/** private key for decryption */ /** private key for decryption */
private final PrivateKey _privateKey; private final PrivateKey _privateKey;
/** private key for signing */ /** private key for signing */
private final SigningPrivateKey _signingPrivateKey; private /* final */ SigningPrivateKey _signingPrivateKey;
/** configuration options */ /** configuration options */
private final Properties _options; private final Properties _options;
/** this session's Id */ /** this session's Id */
...@@ -373,6 +373,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa ...@@ -373,6 +373,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
private void readDestination(InputStream destKeyStream) throws DataFormatException, IOException { private void readDestination(InputStream destKeyStream) throws DataFormatException, IOException {
_myDestination.readBytes(destKeyStream); _myDestination.readBytes(destKeyStream);
_privateKey.readBytes(destKeyStream); _privateKey.readBytes(destKeyStream);
_signingPrivateKey = new SigningPrivateKey(_myDestination.getSigningPublicKey().getType());
_signingPrivateKey.readBytes(destKeyStream); _signingPrivateKey.readBytes(destKeyStream);
} }
......
...@@ -12,6 +12,7 @@ import java.util.Properties; ...@@ -12,6 +12,7 @@ import java.util.Properties;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.I2PException; import net.i2p.I2PException;
import net.i2p.crypto.SigType;
import net.i2p.data.Certificate; import net.i2p.data.Certificate;
import net.i2p.data.Destination; import net.i2p.data.Destination;
...@@ -20,14 +21,30 @@ import net.i2p.data.Destination; ...@@ -20,14 +21,30 @@ import net.i2p.data.Destination;
* just used to talk to the router. * just used to talk to the router.
*/ */
public class I2PSimpleClient implements I2PClient { 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 { 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 { public Destination createDestination(OutputStream destKeyStream, Certificate cert) throws I2PException, IOException {
return null; throw new UnsupportedOperationException();
} }
/** /**
...@@ -37,6 +54,7 @@ public class I2PSimpleClient implements I2PClient { ...@@ -37,6 +54,7 @@ public class I2PSimpleClient implements I2PClient {
public I2PSession createSession(InputStream destKeyStream, Properties options) throws I2PSessionException { public I2PSession createSession(InputStream destKeyStream, Properties options) throws I2PSessionException {
return createSession(I2PAppContext.getGlobalContext(), options); return createSession(I2PAppContext.getGlobalContext(), options);
} }
/** /**
* Create a new session (though do not connect it yet) * Create a new session (though do not connect it yet)
* *
......
...@@ -14,6 +14,7 @@ import java.util.concurrent.ConcurrentHashMap; ...@@ -14,6 +14,7 @@ import java.util.concurrent.ConcurrentHashMap;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.crypto.KeyGenerator; import net.i2p.crypto.KeyGenerator;
import net.i2p.crypto.SigType;
import net.i2p.data.DataFormatException; import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
import net.i2p.data.Destination; import net.i2p.data.Destination;
...@@ -105,6 +106,15 @@ class RequestLeaseSetMessageHandler extends HandlerImpl { ...@@ -105,6 +106,15 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
} }
try { try {
leaseSet.sign(session.getPrivateKey()); 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.getProducer().createLeaseSet(session, leaseSet, li.getSigningPrivateKey(), li.getPrivateKey());
session.setLeaseSet(leaseSet); session.setLeaseSet(leaseSet);
} catch (DataFormatException dfe) { } catch (DataFormatException dfe) {
......
...@@ -14,6 +14,7 @@ import java.io.IOException; ...@@ -14,6 +14,7 @@ import java.io.IOException;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.crypto.DSAEngine; import net.i2p.crypto.DSAEngine;
import net.i2p.crypto.SHA256Generator; import net.i2p.crypto.SHA256Generator;
import net.i2p.crypto.SigType;
import net.i2p.data.DataFormatException; import net.i2p.data.DataFormatException;
import net.i2p.data.Destination; import net.i2p.data.Destination;
import net.i2p.data.Hash; import net.i2p.data.Hash;
...@@ -67,7 +68,10 @@ public final class I2PDatagramDissector { ...@@ -67,7 +68,10 @@ public final class I2PDatagramDissector {
try { try {
// read destination // read destination
rxDest = Destination.create(dgStream); 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 // read signature
rxSign.readBytes(dgStream); rxSign.readBytes(dgStream);
......
...@@ -518,23 +518,6 @@ public class SU3File { ...@@ -518,23 +518,6 @@ public class SU3File {
return buf.toString(); 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 * @param stype number or name
* @return null if not found * @return null if not found
...@@ -627,7 +610,7 @@ public class SU3File { ...@@ -627,7 +610,7 @@ public class SU3File {
*/ */
private static final boolean signCLI(String stype, String ctype, String inputFile, String signedFile, private static final boolean signCLI(String stype, String ctype, String inputFile, String signedFile,
String privateKeyFile, String version, String signerName, String keypw) { 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) { if (type == null) {
System.out.println("Signature type " + stype + " is not supported"); System.out.println("Signature type " + stype + " is not supported");
return false; return false;
...@@ -719,7 +702,7 @@ public class SU3File { ...@@ -719,7 +702,7 @@ public class SU3File {
* @since 0.9.9 * @since 0.9.9
*/ */
private static final boolean genKeysCLI(String stype, String publicKeyFile, String privateKeyFile, String alias) { 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) { if (type == null) {
System.out.println("Signature type " + stype + " is not supported"); System.out.println("Signature type " + stype + " is not supported");
return false; return false;
......
...@@ -5,6 +5,7 @@ import java.security.NoSuchAlgorithmException; ...@@ -5,6 +5,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec; import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidParameterSpecException; import java.security.spec.InvalidParameterSpecException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import net.i2p.data.Hash; import net.i2p.data.Hash;
...@@ -170,4 +171,24 @@ public enum SigType { ...@@ -170,4 +171,24 @@ public enum SigType {
public static SigType getByCode(int code) { public static SigType getByCode(int code) {
return BY_CODE.get(Integer.valueOf(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;
}
}
}
} }
...@@ -42,6 +42,8 @@ public class Certificate extends DataStructureImpl { ...@@ -42,6 +42,8 @@ public class Certificate extends DataStructureImpl {
public final static int CERTIFICATE_LENGTH_SIGNED_WITH_HASH = Signature.SIGNATURE_BYTES + Hash.HASH_LENGTH; public final static int CERTIFICATE_LENGTH_SIGNED_WITH_HASH = Signature.SIGNATURE_BYTES + Hash.HASH_LENGTH;
/** Contains multiple certs */ /** Contains multiple certs */
public final static int CERTIFICATE_TYPE_MULTIPLE = 4; 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 * If null cert, return immutable static instance, else create new
...@@ -58,6 +60,13 @@ public class Certificate extends DataStructureImpl { ...@@ -58,6 +60,13 @@ public class Certificate extends DataStructureImpl {
return new Certificate(type, null); return new Certificate(type, null);
byte[] payload = new byte[length]; byte[] payload = new byte[length];
System.arraycopy(data, off + 3, payload, 0, 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); return new Certificate(type, payload);
} }
...@@ -77,13 +86,20 @@ public class Certificate extends DataStructureImpl { ...@@ -77,13 +86,20 @@ public class Certificate extends DataStructureImpl {
int read = DataHelper.read(in, payload); int read = DataHelper.read(in, payload);
if (read != length) if (read != length)
throw new DataFormatException("Not enough bytes for the payload (read: " + read + " length: " + 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); return new Certificate(type, payload);
} }
public Certificate() { public Certificate() {
} }
/**
* @throws IllegalArgumentException if type < 0
*/
public Certificate(int type, byte[] payload) { public Certificate(int type, byte[] payload) {
if (type < 0)
throw new IllegalArgumentException();
_type = type; _type = type;
_payload = payload; _payload = payload;
} }
...@@ -93,7 +109,15 @@ public class Certificate extends DataStructureImpl { ...@@ -93,7 +109,15 @@ public class Certificate extends DataStructureImpl {
return _type; return _type;
} }
/**
* @throws IllegalArgumentException if type < 0
* @throws IllegalStateException if already set
*/
public void setCertificateType(int type) { public void setCertificateType(int type) {
if (type < 0)
throw new IllegalArgumentException();
if (_type != 0 && _type != type)
throw new IllegalStateException("already set");
_type = type; _type = type;
} }
...@@ -101,11 +125,21 @@ public class Certificate extends DataStructureImpl { ...@@ -101,11 +125,21 @@ public class Certificate extends DataStructureImpl {
return _payload; return _payload;
} }
/**
* @throws IllegalStateException if already set
*/
public void setPayload(byte[] payload) { public void setPayload(byte[] payload) {
if (_payload != null)
throw new IllegalStateException("already set");
_payload = payload; _payload = payload;
} }
/**
* @throws IllegalStateException if already set
*/
public void readBytes(InputStream in) throws DataFormatException, IOException { public void readBytes(InputStream in) throws DataFormatException, IOException {
if (_type != 0 || _payload != null)
throw new IllegalStateException("already set");
_type = (int) DataHelper.readLong(in, 1); _type = (int) DataHelper.readLong(in, 1);
int length = (int) DataHelper.readLong(in, 2); int length = (int) DataHelper.readLong(in, 2);
if (length > 0) { if (length > 0) {
...@@ -149,7 +183,12 @@ public class Certificate extends DataStructureImpl { ...@@ -149,7 +183,12 @@ public class Certificate extends DataStructureImpl {
return cur - offset; return cur - offset;
} }
/**
* @throws IllegalStateException if already set
*/
public int readBytes(byte source[], int offset) throws DataFormatException { 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 == null) throw new DataFormatException("Cert is null");
if (source.length < offset + 3) if (source.length < offset + 3)
throw new DataFormatException("Cert is too small [" + source.length + " off=" + offset + "]"); throw new DataFormatException("Cert is too small [" + source.length + " off=" + offset + "]");
...@@ -175,6 +214,18 @@ public class Certificate extends DataStructureImpl { ...@@ -175,6 +214,18 @@ public class Certificate extends DataStructureImpl {
return 1 + 2 + (_payload != null ? _payload.length : 0); 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 @Override
public boolean equals(Object object) { public boolean equals(Object object) {
if (object == this) return true; if (object == this) return true;
...@@ -194,6 +245,8 @@ public class Certificate extends DataStructureImpl { ...@@ -194,6 +245,8 @@ public class Certificate extends DataStructureImpl {
buf.append("[Certificate: type: "); buf.append("[Certificate: type: ");
if (getCertificateType() == CERTIFICATE_TYPE_NULL) if (getCertificateType() == CERTIFICATE_TYPE_NULL)
buf.append("Null certificate"); buf.append("Null certificate");
else if (getCertificateType() == CERTIFICATE_TYPE_KEY)
buf.append("Key certificate");
else if (getCertificateType() == CERTIFICATE_TYPE_HASHCASH) else if (getCertificateType() == CERTIFICATE_TYPE_HASHCASH)
buf.append("Hashcash certificate"); buf.append("Hashcash certificate");
else if (getCertificateType() == CERTIFICATE_TYPE_HIDDEN) else if (getCertificateType() == CERTIFICATE_TYPE_HIDDEN)
......
...@@ -100,17 +100,22 @@ public class Destination extends KeysAndCert { ...@@ -100,17 +100,22 @@ public class Destination extends KeysAndCert {
int cur = offset; int cur = offset;
System.arraycopy(_publicKey.getData(), 0, target, cur, PublicKey.KEYSIZE_BYTES); System.arraycopy(_publicKey.getData(), 0, target, cur, PublicKey.KEYSIZE_BYTES);
cur += PublicKey.KEYSIZE_BYTES; cur += PublicKey.KEYSIZE_BYTES;
System.arraycopy(_signingKey.getData(), 0, target, cur, SigningPublicKey.KEYSIZE_BYTES); if (_padding != null) {
cur += SigningPublicKey.KEYSIZE_BYTES; 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); cur += _certificate.writeBytes(target, cur);
return cur - offset; 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 * @throws IllegalStateException if data already set
*/ */
/****
public int readBytes(byte source[], int offset) throws DataFormatException { public int readBytes(byte source[], int offset) throws DataFormatException {
if (source == null) throw new DataFormatException("Null source"); if (source == null) throw new DataFormatException("Null source");
if (source.length <= offset + PublicKey.KEYSIZE_BYTES + SigningPublicKey.KEYSIZE_BYTES) if (source.length <= offset + PublicKey.KEYSIZE_BYTES + SigningPublicKey.KEYSIZE_BYTES)
...@@ -130,9 +135,10 @@ public class Destination extends KeysAndCert { ...@@ -130,9 +135,10 @@ public class Destination extends KeysAndCert {
return cur - offset; return cur - offset;
} }
****/
public int size() { public int size() {
return PublicKey.KEYSIZE_BYTES + SigningPublicKey.KEYSIZE_BYTES + _certificate.size(); return PublicKey.KEYSIZE_BYTES + _signingKey.length() + _certificate.size();
} }
/** /**
......
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;
}
}
}
...@@ -35,6 +35,7 @@ public class KeysAndCert extends DataStructureImpl { ...@@ -35,6 +35,7 @@ public class KeysAndCert extends DataStructureImpl {
protected SigningPublicKey _signingKey; protected SigningPublicKey _signingKey;
protected Certificate _certificate; protected Certificate _certificate;
protected Hash __calculatedHash; protected Hash __calculatedHash;
protected byte[] _padding;
public Certificate getCertificate() { public Certificate getCertificate() {
return _certificate; return _certificate;
...@@ -78,6 +79,17 @@ public class KeysAndCert extends DataStructureImpl { ...@@ -78,6 +79,17 @@ public class KeysAndCert extends DataStructureImpl {
__calculatedHash = null; __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 * @throws IllegalStateException if data already set
*/ */
...@@ -85,8 +97,18 @@ public class KeysAndCert extends DataStructureImpl { ...@@ -85,8 +97,18 @@ public class KeysAndCert extends DataStructureImpl {
if (_publicKey != null || _signingKey != null || _certificate != null) if (_publicKey != null || _signingKey != null || _certificate != null)
throw new IllegalStateException(); throw new IllegalStateException();
_publicKey = PublicKey.create(in); _publicKey = PublicKey.create(in);
_signingKey = SigningPublicKey.create(in); SigningPublicKey spk = SigningPublicKey.create(in);
_certificate = Certificate.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; __calculatedHash = null;
} }
...@@ -94,7 +116,9 @@ public class KeysAndCert extends DataStructureImpl { ...@@ -94,7 +116,9 @@ public class KeysAndCert extends DataStructureImpl {
if ((_certificate == null) || (_publicKey == null) || (_signingKey == null)) if ((_certificate == null) || (_publicKey == null) || (_signingKey == null))
throw new DataFormatException("Not enough data to format the router identity"); throw new DataFormatException("Not enough data to format the router identity");
_publicKey.writeBytes(out); _publicKey.writeBytes(out);
_signingKey.writeBytes(out); if (_padding != null)
out.write(_padding);
_signingKey.writeTruncatedBytes(out);
_certificate.writeBytes(out); _certificate.writeBytes(out);
} }
...@@ -106,6 +130,7 @@ public class KeysAndCert extends DataStructureImpl { ...@@ -106,6 +130,7 @@ public class KeysAndCert extends DataStructureImpl {
return return
DataHelper.eq(_signingKey, ident._signingKey) DataHelper.eq(_signingKey, ident._signingKey)
&& DataHelper.eq(_publicKey, ident._publicKey) && DataHelper.eq(_publicKey, ident._publicKey)
&& DataHelper.eq(_padding, ident._padding)
&& DataHelper.eq(_certificate, ident._certificate); && DataHelper.eq(_certificate, ident._certificate);
} }
...@@ -125,6 +150,8 @@ public class KeysAndCert extends DataStructureImpl { ...@@ -125,6 +150,8 @@ public class KeysAndCert extends DataStructureImpl {
buf.append("\n\tCertificate: ").append(_certificate); buf.append("\n\tCertificate: ").append(_certificate);
buf.append("\n\tPublicKey: ").append(_publicKey); buf.append("\n\tPublicKey: ").append(_publicKey);
buf.append("\n\tSigningPublicKey: ").append(_signingKey); buf.append("\n\tSigningPublicKey: ").append(_signingKey);
if (_padding != null)
buf.append("\n\tPadding: ").append(_padding.length).append(" bytes");
buf.append(']'); buf.append(']');
return buf.toString(); return buf.toString();
} }
......
...@@ -275,11 +275,9 @@ public class LeaseSet extends DatabaseEntry { ...@@ -275,11 +275,9 @@ public class LeaseSet extends DatabaseEntry {
protected byte[] getBytes() { protected byte[] getBytes() {
if ((_destination == null) || (_encryptionKey == null) || (_signingKey == null)) if ((_destination == null) || (_encryptionKey == null) || (_signingKey == null))
return null; return null;
int len = PublicKey.KEYSIZE_BYTES // dest int len = _destination.size()
+ SigningPublicKey.KEYSIZE_BYTES // dest
+ 3 // cert minimum, could be more, only used to size the BAOS
+ PublicKey.KEYSIZE_BYTES // encryptionKey + PublicKey.KEYSIZE_BYTES // encryptionKey
+ SigningPublicKey.KEYSIZE_BYTES // signingKey + _signingKey.length() // signingKey
+ 1 + 1
+ _leases.size() * 44; // leases + _leases.size() * 44; // leases
ByteArrayOutputStream out = new ByteArrayOutputStream(len); ByteArrayOutputStream out = new ByteArrayOutputStream(len);
...@@ -310,7 +308,9 @@ public class LeaseSet extends DatabaseEntry { ...@@ -310,7 +308,9 @@ public class LeaseSet extends DatabaseEntry {
throw new IllegalStateException(); throw new IllegalStateException();
_destination = Destination.create(in); _destination = Destination.create(in);
_encryptionKey = PublicKey.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); int numLeases = (int) DataHelper.readLong(in, 1);
if (numLeases > MAX_LEASES) if (numLeases > MAX_LEASES)
throw new DataFormatException("Too many leases - max is " + MAX_LEASES); throw new DataFormatException("Too many leases - max is " + MAX_LEASES);
...@@ -320,7 +320,8 @@ public class LeaseSet extends DatabaseEntry { ...@@ -320,7 +320,8 @@ public class LeaseSet extends DatabaseEntry {
lease.readBytes(in); lease.readBytes(in);
addLease(lease); addLease(lease);
} }
_signature = new Signature(); // signature must be same type as the destination signing key
_signature = new Signature(_destination.getSigningPublicKey().getType());
_signature.readBytes(in); _signature.readBytes(in);
} }
...@@ -345,11 +346,9 @@ public class LeaseSet extends DatabaseEntry { ...@@ -345,11 +346,9 @@ public class LeaseSet extends DatabaseEntry {
* Number of bytes, NOT including signature * Number of bytes, NOT including signature
*/ */
public int size() { public int size() {
return PublicKey.KEYSIZE_BYTES //destination.pubKey return _destination.size()
+ SigningPublicKey.KEYSIZE_BYTES // destination.signPubKey
+ _destination.getCertificate().size() // destination.certificate, usually 3
+ PublicKey.KEYSIZE_BYTES // encryptionKey + PublicKey.KEYSIZE_BYTES // encryptionKey
+ SigningPublicKey.KEYSIZE_BYTES // signingKey + _signingKey.length() // signingKey
+ 1 // number of leases + 1 // number of leases
+ _leases.size() * (Hash.HASH_LENGTH + 4 + 8); + _leases.size() * (Hash.HASH_LENGTH + 4 + 8);
} }
......
...@@ -6,6 +6,8 @@ import java.io.File; ...@@ -6,6 +6,8 @@ import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
...@@ -17,6 +19,9 @@ import net.i2p.client.I2PClientFactory; ...@@ -17,6 +19,9 @@ import net.i2p.client.I2PClientFactory;
import net.i2p.client.I2PSession; import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException; import net.i2p.client.I2PSessionException;
import net.i2p.crypto.DSAEngine; 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 * This helper class reads and writes files in the
...@@ -50,7 +55,9 @@ public class PrivateKeyFile { ...@@ -50,7 +55,9 @@ public class PrivateKeyFile {
* Copied and expanded from that in Destination.java * Copied and expanded from that in Destination.java
*/ */
public static void main(String args[]) { 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("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 filename (generates if nonexistent, adds hashcash cert)");
System.err.println(" PrivateKeyFile -h effort filename (specify HashCash effort instead of default " + HASH_EFFORT + ")"); System.err.println(" PrivateKeyFile -h effort filename (specify HashCash effort instead of default " + HASH_EFFORT + ")");
...@@ -58,13 +65,14 @@ public class PrivateKeyFile { ...@@ -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 -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 -u filename (changes to unknown cert)");
System.err.println(" PrivateKeyFile -x filename (changes to hidden 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; return;
} }
I2PClient client = I2PClientFactory.createClient(); I2PClient client = I2PClientFactory.createClient();
int filearg = 0; int filearg = 0;
if (args.length > 1) { 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; filearg = args.length - 1;
else else
filearg = 1; filearg = 1;
...@@ -101,10 +109,17 @@ public class PrivateKeyFile { ...@@ -101,10 +109,17 @@ public class PrivateKeyFile {
PrivateKeyFile pkf2 = new PrivateKeyFile(args[2]); PrivateKeyFile pkf2 = new PrivateKeyFile(args[2]);
pkf.setSignedCert(pkf2); pkf.setSignedCert(pkf2);
System.out.println("New destination with signed cert is:"); 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); System.out.println(pkf);
pkf.write(); pkf.write();
verifySignature(d); verifySignature(pkf.getDestination());
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
...@@ -209,6 +224,43 @@ public class PrivateKeyFile { ...@@ -209,6 +224,43 @@ public class PrivateKeyFile {
return c; 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() */ /** change to hashcash cert - caller must also call write() */
public Certificate setHashCashCert(int effort) { public Certificate setHashCashCert(int effort) {
Certificate c = setCertType(Certificate.CERTIFICATE_TYPE_HASHCASH); Certificate c = setCertType(Certificate.CERTIFICATE_TYPE_HASHCASH);
...@@ -444,8 +496,6 @@ public class PrivateKeyFile { ...@@ -444,8 +496,6 @@ public class PrivateKeyFile {
private static final int HASH_EFFORT = VerifiedDestination.MIN_HASHCASH_EFFORT; private static final int HASH_EFFORT = VerifiedDestination.MIN_HASHCASH_EFFORT;
private final File file; private final File file;
private final I2PClient client; private final I2PClient client;
private Destination dest; private Destination dest;
......
...@@ -554,7 +554,7 @@ public class RouterInfo extends DatabaseEntry { ...@@ -554,7 +554,7 @@ public class RouterInfo extends DatabaseEntry {
} }
} }
DataHelper.readProperties(din, _options); DataHelper.readProperties(din, _options);
_signature = new Signature(); _signature = new Signature(_identity.getSigningPublicKey().getType());
_signature.readBytes(in); _signature.readBytes(in);
if (verifySig) { if (verifySig) {
......
...@@ -13,12 +13,15 @@ import net.i2p.crypto.SigType; ...@@ -13,12 +13,15 @@ import net.i2p.crypto.SigType;
/** /**
* Defines the signature as defined by the I2P data structure spec. * 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. * using the DSA-SHA1 algorithm.
* *
* The signature is the 20-byte R followed by the 20-byte S, * The signature is the 20-byte R followed by the 20-byte S,
* both are unsigned integers. * both are unsigned integers.
* *
* As of release 0.9.8, signatures of arbitrary length and type are supported.
* See SigType.
*
* @author jrandom * @author jrandom
*/ */
public class Signature extends SimpleDataStructure { public class Signature extends SimpleDataStructure {
...@@ -39,10 +42,15 @@ 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 * @since 0.9.8
*/ */
public Signature(SigType type) { public Signature(SigType type) {
super(); super();
if (type == null)
throw new IllegalArgumentException("unknown type");
_type = type; _type = type;
} }
...@@ -51,10 +59,15 @@ public class Signature extends SimpleDataStructure { ...@@ -51,10 +59,15 @@ public class Signature extends SimpleDataStructure {
} }
/** /**
* Should we allow an unknown type here?
*
* @param type non-null
* @since 0.9.8 * @since 0.9.8
*/ */
public Signature(SigType type, byte data[]) { public Signature(SigType type, byte data[]) {
super(); super();
if (type == null)
throw new IllegalArgumentException("unknown type");
_type = type; _type = type;
setData(data); setData(data);
} }
...@@ -64,6 +77,7 @@ public class Signature extends SimpleDataStructure { ...@@ -64,6 +77,7 @@ public class Signature extends SimpleDataStructure {
} }
/** /**
* @return non-null
* @since 0.9.8 * @since 0.9.8
*/ */
public SigType getType() { public SigType getType() {
......
...@@ -14,10 +14,13 @@ import net.i2p.crypto.SigType; ...@@ -14,10 +14,13 @@ import net.i2p.crypto.SigType;
/** /**
* Defines the SigningPrivateKey as defined by the I2P data structure spec. * 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. * 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) * 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 * @author jrandom
*/ */
public class SigningPrivateKey extends SimpleDataStructure { public class SigningPrivateKey extends SimpleDataStructure {
......
...@@ -11,15 +11,19 @@ package net.i2p.data; ...@@ -11,15 +11,19 @@ package net.i2p.data;
import java.io.InputStream; import java.io.InputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import net.i2p.crypto.SigType; import net.i2p.crypto.SigType;
/** /**
* Defines the SigningPublicKey as defined by the I2P data structure spec. * 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. * 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) * 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 * @author jrandom
*/ */
public class SigningPublicKey extends SimpleDataStructure { public class SigningPublicKey extends SimpleDataStructure {
...@@ -55,6 +59,7 @@ 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 * @since 0.9.8
*/ */
public SigningPublicKey(SigType type) { public SigningPublicKey(SigType type) {
...@@ -67,12 +72,16 @@ public class SigningPublicKey extends SimpleDataStructure { ...@@ -67,12 +72,16 @@ public class SigningPublicKey extends SimpleDataStructure {
} }
/** /**
* @param type if null, type is unknown
* @since 0.9.8 * @since 0.9.8
*/ */
public SigningPublicKey(SigType type, byte data[]) { public SigningPublicKey(SigType type, byte data[]) {
super(); super();
_type = type; _type = type;
setData(data); if (type != null || data == null)
setData(data);
else
_data = data; // bypass length check
} }
/** constructs from base64 /** constructs from base64
...@@ -84,17 +93,91 @@ public class SigningPublicKey extends SimpleDataStructure { ...@@ -84,17 +93,91 @@ public class SigningPublicKey extends SimpleDataStructure {
fromBase64(base64Data); fromBase64(base64Data);
} }
/**
* @return if type unknown, the length of the data, or 128 if no data
*/
public int length() { 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 * @since 0.9.8
*/ */
public SigType getType() { public SigType getType() {
return _type; 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 * @since 0.9.8
*/ */
......
...@@ -71,6 +71,11 @@ public class CreateLeaseSetMessage extends I2CPMessageImpl { ...@@ -71,6 +71,11 @@ public class CreateLeaseSetMessage extends I2CPMessageImpl {
try { try {
_sessionId = new SessionId(); _sessionId = new SessionId();
_sessionId.readBytes(in); _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 = new SigningPrivateKey();
_signingPrivateKey.readBytes(in); _signingPrivateKey.readBytes(in);
_privateKey = new PrivateKey(); _privateKey = new PrivateKey();
...@@ -87,7 +92,7 @@ public class CreateLeaseSetMessage extends I2CPMessageImpl { ...@@ -87,7 +92,7 @@ public class CreateLeaseSetMessage extends I2CPMessageImpl {
if ((_sessionId == null) || (_signingPrivateKey == null) || (_privateKey == null) || (_leaseSet == null)) if ((_sessionId == null) || (_signingPrivateKey == null) || (_privateKey == null) || (_leaseSet == null))
throw new I2CPMessageException("Unable to write out the message as there is not enough data"); throw new I2CPMessageException("Unable to write out the message as there is not enough data");
int size = 4 // sessionId int size = 4 // sessionId
+ SigningPrivateKey.KEYSIZE_BYTES + _signingPrivateKey.length()
+ PrivateKey.KEYSIZE_BYTES + PrivateKey.KEYSIZE_BYTES
+ _leaseSet.size(); + _leaseSet.size();
ByteArrayOutputStream os = new ByteArrayOutputStream(size); ByteArrayOutputStream os = new ByteArrayOutputStream(size);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment