From f8fffe5e29f55856b93b5e21f1a672bb1a0fec40 Mon Sep 17 00:00:00 2001 From: hsm 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 @@ + + + + + + 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