# HG changeset patch # User Carl Byington # Date 1405741486 25200 # Node ID c41a399da303b87ce69387a5840762bc5406ac11 # Parent e730b8a5321e9663c2147f275dd1fb00e4b132f9 start conversion from trilead to ganymed diff -r e730b8a5321e -r c41a399da303 src/ch/ethz/ssh2/channel/AuthAgentForwardThread.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/channel/AuthAgentForwardThread.java Fri Jul 18 20:44:46 2014 -0700 @@ -0,0 +1,542 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.ethz.ssh2.channel; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.spec.DSAPrivateKeySpec; +import java.security.spec.DSAPublicKeySpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.RSAPrivateCrtKeySpec; +import java.security.spec.RSAPublicKeySpec; +import java.util.Map; +import java.util.Map.Entry; + +import ch.ethz.ssh2.AuthAgentCallback; +import ch.ethz.ssh2.log.Logger; +import ch.ethz.ssh2.packets.TypesReader; +import ch.ethz.ssh2.packets.TypesWriter; +import ch.ethz.ssh2.signature.DSASHA1Verify; +import ch.ethz.ssh2.signature.ECDSASHA2Verify; +import ch.ethz.ssh2.signature.RSASHA1Verify; + +/** + * AuthAgentForwardThread. + * + * @author Kenny Root + * @version $Id$ + */ +public class AuthAgentForwardThread extends Thread implements IChannelWorkerThread { + private static final byte[] SSH_AGENT_FAILURE = {0, 0, 0, 1, 5}; // 5 + private static final byte[] SSH_AGENT_SUCCESS = {0, 0, 0, 1, 6}; // 6 + + private static final int SSH2_AGENTC_REQUEST_IDENTITIES = 11; + private static final int SSH2_AGENT_IDENTITIES_ANSWER = 12; + + private static final int SSH2_AGENTC_SIGN_REQUEST = 13; + private static final int SSH2_AGENT_SIGN_RESPONSE = 14; + + private static final int SSH2_AGENTC_ADD_IDENTITY = 17; + private static final int SSH2_AGENTC_REMOVE_IDENTITY = 18; + private static final int SSH2_AGENTC_REMOVE_ALL_IDENTITIES = 19; + +// private static final int SSH_AGENTC_ADD_SMARTCARD_KEY = 20; +// private static final int SSH_AGENTC_REMOVE_SMARTCARD_KEY = 21; + + private static final int SSH_AGENTC_LOCK = 22; + private static final int SSH_AGENTC_UNLOCK = 23; + + private static final int SSH2_AGENTC_ADD_ID_CONSTRAINED = 25; +// private static final int SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED = 26; + + // Constraints for adding keys + private static final int SSH_AGENT_CONSTRAIN_LIFETIME = 1; + private static final int SSH_AGENT_CONSTRAIN_CONFIRM = 2; + + // Flags for signature requests +// private static final int SSH_AGENT_OLD_SIGNATURE = 1; + + private static final Logger log = Logger.getLogger(RemoteAcceptThread.class); + + AuthAgentCallback authAgent; + OutputStream os; + InputStream is; + Channel c; + + byte[] buffer = new byte[Channel.CHANNEL_BUFFER_SIZE]; + + public AuthAgentForwardThread(Channel c, AuthAgentCallback authAgent) { + this.c = c; + this.authAgent = authAgent; + + if (log.isEnabled()) + log.log(20, "AuthAgentForwardThread started"); + } + + @Override + public void run() { + try { + c.cm.registerThread(this); + } + catch (IOException e) { + stopWorking(); + return; + } + + try { + c.cm.sendOpenConfirmation(c); + is = c.getStdoutStream(); + os = c.getStdinStream(); + int totalSize = 4; + int readSoFar = 0; + + while (true) { + int len; + + try { + len = is.read(buffer, readSoFar, buffer.length - readSoFar); + } + catch (IOException e) { + stopWorking(); + return; + } + + if (len <= 0) + break; + + readSoFar += len; + + if (readSoFar >= 4) { + TypesReader tr = new TypesReader(buffer, 0, 4); + totalSize = tr.readUINT32() + 4; + } + + if (totalSize == readSoFar) { + TypesReader tr = new TypesReader(buffer, 4, readSoFar - 4); + int messageType = tr.readByte(); + + switch (messageType) { + case SSH2_AGENTC_REQUEST_IDENTITIES: + sendIdentities(); + break; + + case SSH2_AGENTC_ADD_IDENTITY: + addIdentity(tr, false); + break; + + case SSH2_AGENTC_ADD_ID_CONSTRAINED: + addIdentity(tr, true); + break; + + case SSH2_AGENTC_REMOVE_IDENTITY: + removeIdentity(tr); + break; + + case SSH2_AGENTC_REMOVE_ALL_IDENTITIES: + removeAllIdentities(tr); + break; + + case SSH2_AGENTC_SIGN_REQUEST: + processSignRequest(tr); + break; + + case SSH_AGENTC_LOCK: + processLockRequest(tr); + break; + + case SSH_AGENTC_UNLOCK: + processUnlockRequest(tr); + break; + + default: + os.write(SSH_AGENT_FAILURE); + break; + } + + readSoFar = 0; + } + } + + c.cm.closeChannel(c, "EOF on both streams reached.", true); + } + catch (IOException e) { + log.log(50, "IOException in agent forwarder: " + e.getMessage()); + + try { + is.close(); + } + catch (IOException e1) { + } + + try { + os.close(); + } + catch (IOException e2) { + } + + try { + c.cm.closeChannel(c, "IOException in agent forwarder (" + e.getMessage() + ")", true); + } + catch (IOException e3) { + } + } + } + + public void stopWorking() { + try { + /* This will lead to an IOException in the is.read() call */ + is.close(); + } + catch (IOException e) { + } + } + + /** + * @return whether the agent is locked + */ + private boolean failWhenLocked() throws IOException { + if (authAgent.isAgentLocked()) { + os.write(SSH_AGENT_FAILURE); + return true; + } + else + return false; + } + + private void sendIdentities() throws IOException { + Map keys = null; + TypesWriter tw = new TypesWriter(); + tw.writeByte(SSH2_AGENT_IDENTITIES_ANSWER); + int numKeys = 0; + + if (!authAgent.isAgentLocked()) + keys = authAgent.retrieveIdentities(); + + if (keys != null) + numKeys = keys.size(); + + tw.writeUINT32(numKeys); + + if (keys != null) { + for (Entry entry : keys.entrySet()) { + byte[] keyBytes = entry.getValue(); + tw.writeString(keyBytes, 0, keyBytes.length); + tw.writeString(entry.getKey()); + } + } + + sendPacket(tw.getBytes()); + } + + /** + * @param tr + */ + private void addIdentity(TypesReader tr, boolean checkConstraints) { + try { + if (failWhenLocked()) + return; + + String type = tr.readString(); + String comment; + String keyType; + KeySpec pubSpec; + KeySpec privSpec; + + if (type.equals("ssh-rsa")) { + keyType = "RSA"; + BigInteger n = tr.readMPINT(); + BigInteger e = tr.readMPINT(); + BigInteger d = tr.readMPINT(); + BigInteger iqmp = tr.readMPINT(); + BigInteger p = tr.readMPINT(); + BigInteger q = tr.readMPINT(); + comment = tr.readString(); + // Derive the extra values Java needs. + BigInteger dmp1 = d.mod(p.subtract(BigInteger.ONE)); + BigInteger dmq1 = d.mod(q.subtract(BigInteger.ONE)); + pubSpec = new RSAPublicKeySpec(n, e); + privSpec = new RSAPrivateCrtKeySpec(n, e, d, p, q, dmp1, dmq1, iqmp); + } + else if (type.equals("ssh-dss")) { + keyType = "DSA"; + BigInteger p = tr.readMPINT(); + BigInteger q = tr.readMPINT(); + BigInteger g = tr.readMPINT(); + BigInteger y = tr.readMPINT(); + BigInteger x = tr.readMPINT(); + comment = tr.readString(); + pubSpec = new DSAPublicKeySpec(y, p, q, g); + privSpec = new DSAPrivateKeySpec(x, p, q, g); + } + else if (type.equals("ecdsa-sha2-nistp256")) { + keyType = "EC"; + String curveName = tr.readString(); + byte[] groupBytes = tr.readByteString(); + BigInteger exponent = tr.readMPINT(); + comment = tr.readString(); + + if (!"nistp256".equals(curveName)) { + log.log(2, "Invalid curve name for ecdsa-sha2-nistp256: " + curveName); + os.write(SSH_AGENT_FAILURE); + return; + } + + ECParameterSpec nistp256 = ECDSASHA2Verify.EllipticCurves.nistp256; + ECPoint group = ECDSASHA2Verify.decodeECPoint(groupBytes, nistp256.getCurve()); + + if (group == null) { + // TODO log error + os.write(SSH_AGENT_FAILURE); + return; + } + + pubSpec = new ECPublicKeySpec(group, nistp256); + privSpec = new ECPrivateKeySpec(exponent, nistp256); + } + else { + log.log(2, "Unknown key type: " + type); + os.write(SSH_AGENT_FAILURE); + return; + } + + PublicKey pubKey; + PrivateKey privKey; + + try { + KeyFactory kf = KeyFactory.getInstance(keyType); + pubKey = kf.generatePublic(pubSpec); + privKey = kf.generatePrivate(privSpec); + } + catch (NoSuchAlgorithmException ex) { + // TODO: log error + os.write(SSH_AGENT_FAILURE); + return; + } + catch (InvalidKeySpecException ex) { + // TODO: log error + os.write(SSH_AGENT_FAILURE); + return; + } + + KeyPair pair = new KeyPair(pubKey, privKey); + boolean confirmUse = false; + int lifetime = 0; + + if (checkConstraints) { + while (tr.remain() > 0) { + int constraint = tr.readByte(); + + if (constraint == SSH_AGENT_CONSTRAIN_CONFIRM) + confirmUse = true; + else if (constraint == SSH_AGENT_CONSTRAIN_LIFETIME) + lifetime = tr.readUINT32(); + else { + // Unknown constraint. Bail. + os.write(SSH_AGENT_FAILURE); + return; + } + } + } + + if (authAgent.addIdentity(pair, comment, confirmUse, lifetime)) + os.write(SSH_AGENT_SUCCESS); + else + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e) { + try { + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e1) { + } + } + } + + /** + * @param tr + */ + private void removeIdentity(TypesReader tr) { + try { + if (failWhenLocked()) + return; + + byte[] publicKey = tr.readByteString(); + + if (authAgent.removeIdentity(publicKey)) + os.write(SSH_AGENT_SUCCESS); + else + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e) { + try { + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e1) { + } + } + } + + /** + * @param tr + */ + private void removeAllIdentities(TypesReader tr) { + try { + if (failWhenLocked()) + return; + + if (authAgent.removeAllIdentities()) + os.write(SSH_AGENT_SUCCESS); + else + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e) { + try { + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e1) { + } + } + } + + private void processSignRequest(TypesReader tr) { + try { + if (failWhenLocked()) + return; + + byte[] publicKeyBytes = tr.readByteString(); + byte[] challenge = tr.readByteString(); + int flags = tr.readUINT32(); + + if (flags != 0) { + // We don't understand any flags; abort! + os.write(SSH_AGENT_FAILURE); + return; + } + + KeyPair pair = authAgent.getKeyPair(publicKeyBytes); + + if (pair == null) { + os.write(SSH_AGENT_FAILURE); + return; + } + + byte[] response; + PrivateKey privKey = pair.getPrivate(); + + if (privKey instanceof RSAPrivateKey) { + byte[] signature = RSASHA1Verify.generateSignature(challenge, + (RSAPrivateKey) privKey); + response = RSASHA1Verify.encodeSSHRSASignature(signature); + } + else if (privKey instanceof DSAPrivateKey) { + byte[] signature = DSASHA1Verify.generateSignature(challenge, + (DSAPrivateKey) privKey, new SecureRandom()); + response = DSASHA1Verify.encodeSSHDSASignature(signature); + } + else { + os.write(SSH_AGENT_FAILURE); + return; + } + + TypesWriter tw = new TypesWriter(); + tw.writeByte(SSH2_AGENT_SIGN_RESPONSE); + tw.writeString(response, 0, response.length); + sendPacket(tw.getBytes()); + } + catch (IOException e) { + try { + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e1) { + } + } + } + + /** + * @param tr + */ + private void processLockRequest(TypesReader tr) { + try { + if (failWhenLocked()) + return; + + String lockPassphrase = tr.readString(); + + if (!authAgent.setAgentLock(lockPassphrase)) { + os.write(SSH_AGENT_FAILURE); + return; + } + else + os.write(SSH_AGENT_SUCCESS); + } + catch (IOException e) { + try { + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e1) { + } + } + } + + /** + * @param tr + */ + private void processUnlockRequest(TypesReader tr) { + try { + String unlockPassphrase = tr.readString(); + + if (authAgent.requestAgentUnlock(unlockPassphrase)) + os.write(SSH_AGENT_SUCCESS); + else + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e) { + try { + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e1) { + } + } + } + + /** + * @param tw + * @throws IOException + */ + private void sendPacket(byte[] message) throws IOException { + TypesWriter packet = new TypesWriter(); + packet.writeUINT32(message.length); + packet.writeBytes(message); + os.write(packet.getBytes()); + } +} diff -r e730b8a5321e -r c41a399da303 src/ch/ethz/ssh2/channel/DynamicAcceptThread.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/channel/DynamicAcceptThread.java Fri Jul 18 20:44:46 2014 -0700 @@ -0,0 +1,295 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.ethz.ssh2.channel; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InterruptedIOException; +import java.io.OutputStream; +import java.io.PushbackInputStream; +import java.net.ConnectException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.NoRouteToHostException; +import java.net.ServerSocket; +import java.net.Socket; + +import net.sourceforge.jsocks.Proxy; +import net.sourceforge.jsocks.ProxyMessage; +import net.sourceforge.jsocks.Socks4Message; +import net.sourceforge.jsocks.Socks5Message; +import net.sourceforge.jsocks.SocksException; +import net.sourceforge.jsocks.server.ServerAuthenticator; +import net.sourceforge.jsocks.server.ServerAuthenticatorNone; + +/** + * DynamicAcceptThread. + * + * @author Kenny Root + * @version $Id$ + */ +public class DynamicAcceptThread extends Thread implements IChannelWorkerThread { + private ChannelManager cm; + private ServerSocket ss; + + class DynamicAcceptRunnable implements Runnable { + private static final int idleTimeout = 180000; //3 minutes + + private ServerAuthenticator auth; + private Socket sock; + private InputStream in; + private OutputStream out; + private ProxyMessage msg; + + public DynamicAcceptRunnable(ServerAuthenticator auth, Socket sock) { + this.auth = auth; + this.sock = sock; + setName("DynamicAcceptRunnable"); + } + + public void run() { + try { + startSession(); + } + catch (IOException ioe) { + int error_code = Proxy.SOCKS_FAILURE; + + if (ioe instanceof SocksException) + error_code = ((SocksException) ioe).errCode; + else if (ioe instanceof NoRouteToHostException) + error_code = Proxy.SOCKS_HOST_UNREACHABLE; + else if (ioe instanceof ConnectException) + error_code = Proxy.SOCKS_CONNECTION_REFUSED; + else if (ioe instanceof InterruptedIOException) + error_code = Proxy.SOCKS_TTL_EXPIRE; + + if (error_code > Proxy.SOCKS_ADDR_NOT_SUPPORTED + || error_code < 0) { + error_code = Proxy.SOCKS_FAILURE; + } + + sendErrorMessage(error_code); + } + finally { + if (auth != null) + auth.endSession(); + } + } + + private ProxyMessage readMsg(InputStream in) throws IOException { + PushbackInputStream push_in; + + if (in instanceof PushbackInputStream) + push_in = (PushbackInputStream) in; + else + push_in = new PushbackInputStream(in); + + int version = push_in.read(); + push_in.unread(version); + ProxyMessage msg; + + if (version == 5) { + msg = new Socks5Message(push_in, false); + } + else if (version == 4) { + msg = new Socks4Message(push_in, false); + } + else { + throw new SocksException(Proxy.SOCKS_FAILURE); + } + + return msg; + } + + private void sendErrorMessage(int error_code) { + ProxyMessage err_msg; + + if (msg instanceof Socks4Message) + err_msg = new Socks4Message(Socks4Message.REPLY_REJECTED); + else + err_msg = new Socks5Message(error_code); + + try { + err_msg.write(out); + } + catch (IOException ioe) { + } + } + + private void handleRequest(ProxyMessage msg) throws IOException { + if (!auth.checkRequest(msg)) + throw new SocksException(Proxy.SOCKS_FAILURE); + + switch (msg.command) { + case Proxy.SOCKS_CMD_CONNECT: + onConnect(msg); + break; + + default: + throw new SocksException(Proxy.SOCKS_CMD_NOT_SUPPORTED); + } + } + + private void startSession() throws IOException { + sock.setSoTimeout(idleTimeout); + + try { + auth = auth.startSession(sock); + } + catch (IOException ioe) { + System.out.println("Could not start SOCKS session"); + ioe.printStackTrace(); + auth = null; + return; + } + + if (auth == null) { // Authentication failed + System.out.println("SOCKS auth failed"); + return; + } + + in = auth.getInputStream(); + out = auth.getOutputStream(); + msg = readMsg(in); + handleRequest(msg); + } + + private void onConnect(ProxyMessage msg) throws IOException { + ProxyMessage response = null; + Channel cn = null; + StreamForwarder r2l = null; + StreamForwarder l2r = null; + + if (msg instanceof Socks5Message) { + response = new Socks5Message(Proxy.SOCKS_SUCCESS, (InetAddress)null, 0); + } + else { + response = new Socks4Message(Socks4Message.REPLY_OK, (InetAddress)null, 0); + } + + response.write(out); + String destHost = msg.host; + + if (msg.ip != null) + destHost = msg.ip.getHostAddress(); + + try { + /* + * This may fail, e.g., if the remote port is closed (in + * optimistic terms: not open yet) + */ + cn = cm.openDirectTCPIPChannel(destHost, msg.port, + "127.0.0.1", 0); + } + catch (IOException e) { + /* + * Simply close the local socket and wait for the next incoming + * connection + */ + try { + sock.close(); + } + catch (IOException ignore) { + } + + return; + } + + try { + r2l = new StreamForwarder(cn, null, sock, cn.stdoutStream, out, "RemoteToLocal"); + l2r = new StreamForwarder(cn, r2l, sock, in, cn.stdinStream, "LocalToRemote"); + } + catch (IOException e) { + try { + /* + * This message is only visible during debugging, since we + * discard the channel immediatelly + */ + cn.cm.closeChannel(cn, + "Weird error during creation of StreamForwarder (" + + e.getMessage() + ")", true); + } + catch (IOException ignore) { + } + + return; + } + + r2l.setDaemon(true); + l2r.setDaemon(true); + r2l.start(); + l2r.start(); + } + } + + public DynamicAcceptThread(ChannelManager cm, int local_port) + throws IOException { + this.cm = cm; + setName("DynamicAcceptThread"); + ss = new ServerSocket(local_port); + } + + public DynamicAcceptThread(ChannelManager cm, InetSocketAddress localAddress) + throws IOException { + this.cm = cm; + ss = new ServerSocket(); + ss.bind(localAddress); + } + + @Override + public void run() { + try { + cm.registerThread(this); + } + catch (IOException e) { + stopWorking(); + return; + } + + while (true) { + Socket sock = null; + + try { + sock = ss.accept(); + } + catch (IOException e) { + stopWorking(); + return; + } + + DynamicAcceptRunnable dar = new DynamicAcceptRunnable(new ServerAuthenticatorNone(), sock); + Thread t = new Thread(dar); + t.setDaemon(true); + t.start(); + } + } + + /* + * (non-Javadoc) + * + * @see com.trilead.ssh2.channel.IChannelWorkerThread#stopWorking() + */ + public void stopWorking() { + try { + /* This will lead to an IOException in the ss.accept() call */ + ss.close(); + } + catch (IOException e) { + } + } +}