From f8fffe5e29f55856b93b5e21f1a672bb1a0fec40 Mon Sep 17 00:00:00 2001
From: hsm <hsm@lamia.panaceas.james.local>
Date: Sun, 29 May 2016 04:17:53 +0100
Subject: Add support for auth with open-keychain

---
 sshlib/src/main/AndroidManifest.xml                |   7 +
 .../trilead/ssh2/auth/AuthenticationManager.java   |  33 ++++
 .../java/com/trilead/ssh2/crypto/PEMDecoder.java   |  59 +++++++
 .../java/com/trilead/ssh2/crypto/PEMStructure.java |   3 +-
 .../trilead/ssh2/signature/TokenRSAPrivateKey.java |  72 +++++++++
 .../trilead/ssh2/signature/TokenRSASHA1Verify.java | 175 +++++++++++++++++++++
 6 files changed, 348 insertions(+), 1 deletion(-)
 create mode 100644 sshlib/src/main/AndroidManifest.xml
 create mode 100644 sshlib/src/main/java/com/trilead/ssh2/signature/TokenRSAPrivateKey.java
 create mode 100644 sshlib/src/main/java/com/trilead/ssh2/signature/TokenRSASHA1Verify.java

diff --git a/sshlib/src/main/AndroidManifest.xml b/sshlib/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..d15f4a6
--- /dev/null
+++ b/sshlib/src/main/AndroidManifest.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="org.connectbot.sshlib">
+
+    <application/>
+
+</manifest>
diff --git a/sshlib/src/main/java/com/trilead/ssh2/auth/AuthenticationManager.java b/sshlib/src/main/java/com/trilead/ssh2/auth/AuthenticationManager.java
index dfafcbd..117ed57 100644
--- a/sshlib/src/main/java/com/trilead/ssh2/auth/AuthenticationManager.java
+++ b/sshlib/src/main/java/com/trilead/ssh2/auth/AuthenticationManager.java
@@ -33,6 +33,8 @@ import com.trilead.ssh2.signature.DSASHA1Verify;
 import com.trilead.ssh2.signature.ECDSASHA2Verify;
 import com.trilead.ssh2.signature.Ed25519Verify;
 import com.trilead.ssh2.signature.RSASHA1Verify;
+import com.trilead.ssh2.signature.TokenRSAPrivateKey;
+import com.trilead.ssh2.signature.TokenRSASHA1Verify;
 import com.trilead.ssh2.transport.MessageHandler;
 import com.trilead.ssh2.transport.TransportManager;
 
@@ -246,6 +248,37 @@ public class AuthenticationManager implements MessageHandler
 
 				tm.sendMessage(ua.getPayload());
 			}
+			else if (key instanceof TokenRSAPrivateKey)
+			{
+				TokenRSAPrivateKey pk = (TokenRSAPrivateKey) key;
+
+				byte[] pk_enc = RSASHA1Verify.encodeSSHRSAPublicKey((RSAPublicKey) pair.getPublic());
+
+				TypesWriter tw = new TypesWriter();
+				{
+					byte[] H = tm.getSessionIdentifier();
+
+					tw.writeString(H, 0, H.length);
+					tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST);
+					tw.writeString(user);
+					tw.writeString("ssh-connection");
+					tw.writeString("publickey");
+					tw.writeBoolean(true);
+					tw.writeString("ssh-rsa");
+					tw.writeString(pk_enc, 0, pk_enc.length);
+				}
+
+				byte[] msg = tw.getBytes();
+
+				byte[] ds = TokenRSASHA1Verify.generateSignature(msg, pk);
+
+				byte[] rsa_sig_enc = RSASHA1Verify.encodeSSHRSASignature(ds);
+
+				PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user,
+						"ssh-rsa", pk_enc, rsa_sig_enc);
+
+				tm.sendMessage(ua.getPayload());
+			}
 			else if (key instanceof ECPrivateKey)
 			{
 				ECPrivateKey pk = (ECPrivateKey) key;
diff --git a/sshlib/src/main/java/com/trilead/ssh2/crypto/PEMDecoder.java b/sshlib/src/main/java/com/trilead/ssh2/crypto/PEMDecoder.java
index 5c0c2fd..09b875e 100644
--- a/sshlib/src/main/java/com/trilead/ssh2/crypto/PEMDecoder.java
+++ b/sshlib/src/main/java/com/trilead/ssh2/crypto/PEMDecoder.java
@@ -30,6 +30,7 @@ import com.trilead.ssh2.crypto.cipher.CBCMode;
 import com.trilead.ssh2.crypto.cipher.DES;
 import com.trilead.ssh2.crypto.cipher.DESede;
 import com.trilead.ssh2.signature.ECDSASHA2Verify;
+import com.trilead.ssh2.signature.TokenRSAPrivateKey;
 
 /**
  * PEM Support.
@@ -42,6 +43,7 @@ public class PEMDecoder
 	public static final int PEM_RSA_PRIVATE_KEY = 1;
 	public static final int PEM_DSA_PRIVATE_KEY = 2;
 	public static final int PEM_EC_PRIVATE_KEY = 3;
+	public static final int PEM_RSA_TOKEN_PRIVATE_KEY = 4;
 
 	private static final int hexToInt(char c)
 	{
@@ -186,6 +188,12 @@ public class PEMDecoder
 				ps.pemType = PEM_EC_PRIVATE_KEY;
 				break;
 			}
+
+			if (line.startsWith("-----BEGIN RSA PUBLIC KEY-----")) {
+				endLine = "-----END RSA PUBLIC KEY-----";
+				ps.pemType = PEM_RSA_TOKEN_PRIVATE_KEY;
+				break;
+			}
 		}
 
 		while (true)
@@ -224,6 +232,12 @@ public class PEMDecoder
 				ps.dekInfo = values;
 				continue;
 			}
+
+			if ("Private-Key-ID:".equals(name))
+			{
+				ps.private_key_id = values;
+				continue;
+			}
 			/* Ignore line */
 		}
 
@@ -468,9 +482,54 @@ public class PEMDecoder
 			return generateKeyPair("EC", privSpec, pubSpec);
 		}
 
+		if (ps.pemType == PEM_RSA_TOKEN_PRIVATE_KEY)
+		{
+
+			if (ps.private_key_id == null)  {
+				throw new IOException("No Private-Key-ID: line in stream.");
+			}
+			if (ps.private_key_id.length != 1)  {
+				throw new IOException("No Private-Key-ID: line in stream.");
+			}
+
+			SimpleDERReader dr = new SimpleDERReader(ps.data);
+
+			byte[] seq = dr.readSequenceAsByteArray();
+
+			if (dr.available() != 0)
+				throw new IOException("Padding in RSA PUBLIC KEY DER stream.");
+
+			dr.resetInput(seq);
+
+			BigInteger n = dr.readInt();
+			BigInteger e = dr.readInt();
+
+			RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(n, e);
+
+			return generateTokenKeyPair("RSA", new TokenRSAPrivateKey(ps.private_key_id[0]), pubSpec);
+		}
+
 		throw new IOException("PEM problem: it is of unknown type");
 	}
 
+
+	private static KeyPair generateTokenKeyPair(String algorithm, PrivateKey priv_key, KeySpec pubSpec)
+			throws IOException {
+		try {
+			final KeyFactory kf = KeyFactory.getInstance(algorithm);
+			final PublicKey pubKey = kf.generatePublic(pubSpec);
+			final PrivateKey privKey = priv_key;
+			return new KeyPair(pubKey, privKey);
+		} catch (NoSuchAlgorithmException ex) {
+			IOException ioex = new IOException();
+			ioex.initCause(ex);
+			throw ioex;
+		} catch (InvalidKeySpecException ex) {
+			IOException ioex = new IOException("invalid keyspec");
+			ioex.initCause(ex);
+			throw ioex;
+		}
+	}
 	/**
 	 * Generate a {@code KeyPair} given an {@code algorithm} and {@code KeySpec}.
 	 */
diff --git a/sshlib/src/main/java/com/trilead/ssh2/crypto/PEMStructure.java b/sshlib/src/main/java/com/trilead/ssh2/crypto/PEMStructure.java
index 83fb799..0aeb2eb 100644
--- a/sshlib/src/main/java/com/trilead/ssh2/crypto/PEMStructure.java
+++ b/sshlib/src/main/java/com/trilead/ssh2/crypto/PEMStructure.java
@@ -12,6 +12,7 @@ public class PEMStructure
 {
 	public int pemType;
 	String dekInfo[];
+	String private_key_id[];
 	String procType[];
 	public byte[] data;
-}
\ No newline at end of file
+}
diff --git a/sshlib/src/main/java/com/trilead/ssh2/signature/TokenRSAPrivateKey.java b/sshlib/src/main/java/com/trilead/ssh2/signature/TokenRSAPrivateKey.java
new file mode 100644
index 0000000..4438cab
--- /dev/null
+++ b/sshlib/src/main/java/com/trilead/ssh2/signature/TokenRSAPrivateKey.java
@@ -0,0 +1,72 @@
+
+package com.trilead.ssh2.signature;
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamException;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.PrivateKey;
+import java.security.spec.RSAPublicKeySpec;
+
+import com.trilead.ssh2.log.Logger;
+import com.trilead.ssh2.packets.TypesReader;
+import com.trilead.ssh2.packets.TypesWriter;
+
+
+public class TokenRSAPrivateKey implements PrivateKey
+{
+  private long key_id;
+
+  public TokenRSAPrivateKey (String s)
+  {
+    key_id = new BigInteger (s, 16).longValue();
+  }
+
+  public TokenRSAPrivateKey (long l)
+  {
+    key_id = l;
+  }
+
+  public long getKeyId()
+  {
+    return key_id;
+  }
+
+  private void writeObject (ObjectOutputStream stream) throws IOException
+  {
+    throw new IOException();
+  }
+
+  public void readObject (ObjectInputStream stream) throws IOException
+  {
+    throw new IOException();
+  }
+
+  public void readObjectNoData()     throws ObjectStreamException
+  {
+    throw new ObjectStreamException() {};
+  }
+
+  public String getAlgorithm()
+  {
+    return "TokenRSA";
+  }
+
+  public String getFormat()
+  {
+    return "None";
+  }
+
+  public byte[] getEncoded()
+  {
+    return new byte[0];
+  }
+}
+
diff --git a/sshlib/src/main/java/com/trilead/ssh2/signature/TokenRSASHA1Verify.java b/sshlib/src/main/java/com/trilead/ssh2/signature/TokenRSASHA1Verify.java
new file mode 100644
index 0000000..d8b95fa
--- /dev/null
+++ b/sshlib/src/main/java/com/trilead/ssh2/signature/TokenRSASHA1Verify.java
@@ -0,0 +1,175 @@
+
+package com.trilead.ssh2.signature;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+
+
+import com.trilead.ssh2.log.Logger;
+import com.trilead.ssh2.packets.TypesReader;
+import com.trilead.ssh2.packets.TypesWriter;
+
+import android.app.Activity;
+import android.content.IntentSender;
+import android.content.Intent;
+import android.app.PendingIntent;
+
+
+
+import org.openintents.openpgp.IOpenPgpService2;
+import org.openintents.openpgp.OpenPgpDecryptionResult;
+import org.openintents.openpgp.OpenPgpError;
+import org.openintents.openpgp.OpenPgpSignatureResult;
+import org.openintents.openpgp.util.OpenPgpApi;
+import org.openintents.openpgp.util.OpenPgpServiceConnection;
+import org.openintents.openpgp.util.OpenPgpUtils;
+
+
+/**
+ * TokenRSASHA1Verify.
+ *
+ * @author James McKenzie
+ */
+public class TokenRSASHA1Verify
+{
+  private static final Object lock = new Object();
+
+  private static final Logger log = Logger.getLogger (TokenRSASHA1Verify.class);
+  private static final int pending_intent_code = 28674;
+
+  static private Activity activity;
+  static private OpenPgpServiceConnection mServiceConnection;
+
+  static private boolean async_semaphore = false;
+  static private boolean async_abort = false;
+  static private Intent  async_intent;
+
+  public static void open (Activity _activity)
+  {
+    activity = _activity;
+
+    if (activity == null)
+      return;
+
+    mServiceConnection = new OpenPgpServiceConnection (activity, "org.sufficientlysecure.keychain");
+    mServiceConnection.bindToService();
+  }
+
+
+  public static void callback (int requestCode, int resultCode, Intent intent)
+  {
+    if (requestCode !=  pending_intent_code) return;
+
+    synchronized (lock) {
+      if (resultCode == Activity.RESULT_OK) {
+        async_intent = intent;
+        async_abort = false;
+      } else
+        async_abort = true;
+
+      async_semaphore = true;
+
+      lock.notify();
+    }
+  }
+
+  public static byte[] generateSignature (byte[] message, TokenRSAPrivateKey pk) throws IOException
+  {
+    byte [] fail = new byte[0];
+    long key_id = pk.getKeyId();
+
+    if ((activity == null) || (mServiceConnection == null)) return fail;
+
+    Intent data = new Intent();
+    data.setAction (OpenPgpApi.ACTION_SSH_AUTH);
+    data.putExtra (OpenPgpApi.EXTRA_SIGN_KEY_ID, key_id);
+
+    InputStream is = new ByteArrayInputStream (message);
+
+    OpenPgpApi api = new OpenPgpApi (activity, mServiceConnection.getService());
+    Intent result = api.executeApi (data, is, null);
+
+
+    int result_code;
+
+    do {
+      result_code = result.getIntExtra (OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
+
+      if (result_code == OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED) {
+
+        synchronized (lock) {
+          async_semaphore = false;
+          async_abort = true;
+
+          PendingIntent pi = result.getParcelableExtra (OpenPgpApi.RESULT_INTENT);
+
+          try {
+            activity.startIntentSenderForResult (pi.getIntentSender(), pending_intent_code, null, 0, 0, 0);
+          } catch (IntentSender.SendIntentException e) {
+            return fail;
+          }
+
+          try {
+            while (async_semaphore == false)
+              lock.wait();
+          } catch (InterruptedException e) { }
+
+          if (async_abort)
+            return fail;
+
+          data = async_intent;
+        }
+
+        is = new ByteArrayInputStream (message);
+        result = api.executeApi (data, is, null);
+
+      } else
+        break;
+
+    } while (true);
+
+    switch (result_code) {
+    case OpenPgpApi.RESULT_CODE_SUCCESS: {
+
+      byte [] output = result.getByteArrayExtra (OpenPgpApi.RESULT_DETACHED_SIGNATURE);
+
+      if (output == null)
+        return fail;
+
+      return output;
+    }
+
+    case OpenPgpApi.RESULT_CODE_ERROR: {
+      //OpenPgpError error = result.getParcelableExtra (OpenPgpApi.RESULT_ERROR);
+      return fail;
+    }
+    }
+
+    return fail;
+
+  }
+
+  public static void close()
+  {
+    if (mServiceConnection != null)
+      mServiceConnection.unbindFromService();
+
+    activity = null;
+  }
+
+}
-- 
cgit v1.2.3