From 5842e2520544077988a5b698b365e665f4548b18 Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Tue, 24 Dec 2013 16:41:05 +0000
Subject: [PATCH] Initial support for key certificates and arbitrary types and
 lengths of signing keys and signatures in RouterIdentities and Destinations.
 Untested, not even for regressions, except with command line using
 PrivateKeyFile. Based on preliminary spec at
 http://zzz.i2p/topics/1442?page=1#p7524 Not done:  - Transport handshake
 signing  - Configuration of default type  - Specification of type in options
 to I2PSocketManagerFactory  - Specification of type in i2ptunnel  - Fix up
 caching of SigningPublicKey and P256 key cert  - Any non-default crypto type
 in the key cert  - Documentation

---
 .../src/net/i2p/client/streaming/Packet.java  |  61 +++--
 core/java/src/net/i2p/client/I2PClient.java   |  13 +
 .../src/net/i2p/client/I2PClientImpl.java     |  63 ++++-
 .../src/net/i2p/client/I2PSessionImpl.java    |   3 +-
 .../src/net/i2p/client/I2PSimpleClient.java   |  26 +-
 .../client/RequestLeaseSetMessageHandler.java |  10 +
 .../client/datagram/I2PDatagramDissector.java |   6 +-
 core/java/src/net/i2p/crypto/SU3File.java     |  21 +-
 core/java/src/net/i2p/crypto/SigType.java     |  21 ++
 core/java/src/net/i2p/data/Certificate.java   |  53 ++++
 core/java/src/net/i2p/data/Destination.java   |  14 +-
 .../java/src/net/i2p/data/KeyCertificate.java | 252 ++++++++++++++++++
 core/java/src/net/i2p/data/KeysAndCert.java   |  33 ++-
 core/java/src/net/i2p/data/LeaseSet.java      |  19 +-
 .../java/src/net/i2p/data/PrivateKeyFile.java |  60 ++++-
 core/java/src/net/i2p/data/RouterInfo.java    |   2 +-
 core/java/src/net/i2p/data/Signature.java     |  16 +-
 .../src/net/i2p/data/SigningPrivateKey.java   |   5 +-
 .../src/net/i2p/data/SigningPublicKey.java    |  89 ++++++-
 .../i2p/data/i2cp/CreateLeaseSetMessage.java  |   7 +-
 .../src/net/i2p/data/i2cp/SessionConfig.java  |   2 +-
 21 files changed, 698 insertions(+), 78 deletions(-)
 create mode 100644 core/java/src/net/i2p/data/KeyCertificate.java

diff --git a/apps/streaming/java/src/net/i2p/client/streaming/Packet.java b/apps/streaming/java/src/net/i2p/client/streaming/Packet.java
index 6fbe1dcce4..1ecdc0771b 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 521ed35b5b..84bcf50d2a 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 932daa61a5..7fac17e10d 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 8cf7ad095e..cfacb4cec1 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 e91ae2f8b7..8e198daf9f 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 620f571f4d..09f224efc0 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 00c897a6f5..281ea20359 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 dd23ad05ab..55e21fea40 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 105ff09b1a..a37d65dfb0 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 ef6484f31e..a4a1b3148d 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 ad21985e39..4d8affc870 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 0000000000..5726c80b14
--- /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 429d89be7c..33a1de2944 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 75b24220d4..cf9b39deb2 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 9c71cc96b3..b97d762574 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 214937815e..4900a4e9ee 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 83f0410e24..1338cb6641 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 4b60ee7e88..a8fcbb2081 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 e01d6ec392..c7ec32bb8c 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 b57af1f8da..6477768c53 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 cd6db38e4e..8c5521993e 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);
     }
 
-- 
GitLab