# HG changeset patch # User Carl Byington # Date 1406849618 25200 # Node ID 175c7d68f3c428733551d7b76da99bd3b8b8d822 # Parent ce2f4e39770384cc35f2f2d363110a41036c4543# Parent 529883a3a11450bf3171e754fe0ca0c845518d57 merge ganymed into mainline diff -r ce2f4e397703 -r 175c7d68f3c4 Makefile --- a/Makefile Thu Jul 17 22:09:05 2014 -0700 +++ b/Makefile Thu Jul 31 16:33:38 2014 -0700 @@ -76,9 +76,9 @@ --indent-cases \ --indent-namespaces \ --break-blocks \ + --unpad-paren \ --pad-oper \ --pad-header \ - --unpad-paren \ --delete-empty-lines \ --align-pointer=type \ --break-closing-brackets \ diff -r ce2f4e397703 -r 175c7d68f3c4 TODO --- a/TODO Thu Jul 17 22:09:05 2014 -0700 +++ b/TODO Thu Jul 31 16:33:38 2014 -0700 @@ -37,6 +37,12 @@ svn checkout svn://svn.code.sf.net/p/tn5250j/code/branches/new-tabs-jse1.6 tn5250j ================================== + +update com.trilead.ssh2 to ganymed +svn checkout http://ganymed-ssh-2.googlecode.com/svn/trunk/ ganymed-ssh-2-read-only +rev 161 + +================================== TODO: possible merge of irssi? diff -r ce2f4e397703 -r 175c7d68f3c4 assets/help/About.html --- a/assets/help/About.html Thu Jul 17 22:09:05 2014 -0700 +++ b/assets/help/About.html Thu Jul 31 16:33:38 2014 -0700 @@ -38,9 +38,32 @@

-Based on the Trilead SSH2 client provided under a BSD-style -license. Copyright © 2007 Trilead -AG http://www.trilead.com +Based on the Ganymed SSH2 client provided under a BSD-style +license. Copyright © 2005 - 2006 Swiss Federal Institute of +Technology (ETH Zurich), Department of Computer +Science http://www.inf.ethz.ch, +Christian Plattner. Copyright © 2006 - 2013 Christian Plattner. +http://code.google.com/p/ganymed-ssh-2/ +The Java implementations of the AES, Blowfish and 3DES ciphers have been +taken (and slightly modified) from the cryptography package released by +"The Legion Of The Bouncy Castle". +Copyright © 2000 - 2004 The Legion Of The Bouncy Castle +http://www.bouncycastle.org +The following disclaimer applies: +

+ +

+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE.

diff -r ce2f4e397703 -r 175c7d68f3c4 ch.ethz.ssh2.LICENSE.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ch.ethz.ssh2.LICENSE.txt Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,87 @@ +Copyright (c) 2006 - 2013 Christian Plattner. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +a.) Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +b.) Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +c.) Neither the name of Christian Plattner nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + +This software includes work that was released under the following license: + +Copyright (c) 2005 - 2006 Swiss Federal Institute of Technology (ETH Zurich), + Department of Computer Science (http://www.inf.ethz.ch), + Christian Plattner. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +a.) Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +b.) Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +c.) Neither the name of ETH Zurich nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + +The Java implementations of the AES, Blowfish and 3DES ciphers have been +taken (and slightly modified) from the cryptography package released by +"The Legion Of The Bouncy Castle". + +Their license states the following: + +Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle +(http://www.bouncycastle.org) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff -r ce2f4e397703 -r 175c7d68f3c4 res/layout/act_generatepubkey.xml --- a/res/layout/act_generatepubkey.xml Thu Jul 17 22:09:05 2014 -0700 +++ b/res/layout/act_generatepubkey.xml Thu Jul 31 16:33:38 2014 -0700 @@ -66,8 +66,7 @@ android:id="@+id/rsa" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="RSA" - android:paddingRight="30dip" /> + android:text="RSA" /> pendingReadQueue + = new HashMap(); + + /** + * Mapping request ID to request. + */ + private Map pendingStatusQueue + = new HashMap(); + + private PacketListener listener; + + protected AbstractSFTPClient(final Connection conn, final int version, final PacketListener listener) throws IOException { + this.listener = listener; + log.debug("Opening session and starting SFTP subsystem."); + sess = conn.openSession(); + sess.startSubSystem("sftp"); + is = sess.getStdout(); + os = new BufferedOutputStream(sess.getStdin(), 2048); + init(version); + } + + private void init(final int client_version) throws IOException { + // Send SSH_FXP_INIT with client version + TypesWriter tw = new TypesWriter(); + tw.writeUINT32(client_version); + sendMessage(Packet.SSH_FXP_INIT, 0, tw.getBytes()); + /* Receive SSH_FXP_VERSION */ + log.debug("Waiting for SSH_FXP_VERSION..."); + TypesReader tr = new TypesReader(receiveMessage(34000)); /* Should be enough for any reasonable server */ + int t = tr.readByte(); + listener.read(Packet.forName(t)); + + if (t != Packet.SSH_FXP_VERSION) { + log.warning(String.format("The server did not send a SSH_FXP_VERSION but %d", t)); + throw new PacketTypeException(t); + } + + final int protocol_version = tr.readUINT32(); + log.debug("SSH_FXP_VERSION: protocol_version = " + protocol_version); + + if (protocol_version != client_version) { + throw new IOException(String.format("Server protocol version %d does not match %d", + protocol_version, client_version)); + } + + // Both parties should from then on adhere to particular version of the protocol + + // Read and save extensions (if any) for later use + while (tr.remain() != 0) { + String name = tr.readString(); + listener.read(name); + byte[] value = tr.readByteString(); + log.debug(String.format("SSH_FXP_VERSION: extension: %s = '%s'", name, StringEncoder.GetString(value))); + } + } + + /** + * Queries the channel state + * + * @return True if the underlying session is in open state + */ + public boolean isConnected() { + return sess.getState() == Channel.STATE_OPEN; + } + + /** + * Close this SFTP session. NEVER forget to call this method to free up + * resources - even if you got an exception from one of the other methods. + * Sometimes these other methods may throw an exception, saying that the + * underlying channel is closed (this can happen, e.g., if the other server + * sent a close message.) However, as long as you have not called the + * close() method, you are likely wasting resources. + */ + public void close() { + sess.close(); + } + + /** + * Set the charset used to convert between Java Unicode Strings and byte encodings + * used by the server for paths and file names. + * + * @param charset the name of the charset to be used or null to use UTF-8. + * @throws java.io.IOException + * @see #getCharset() + */ + public void setCharset(String charset) throws IOException { + if (charset == null) { + this.charset = null; + return; + } + + try { + Charset.forName(charset); + } + catch (UnsupportedCharsetException e) { + throw new IOException("This charset is not supported", e); + } + + this.charset = charset; + } + + /** + * The currently used charset for filename encoding/decoding. + * + * @return The name of the charset (null if UTF-8 is used). + * @see #setCharset(String) + */ + public String getCharset() { + return charset; + } + + public abstract SFTPFileHandle openFile(String fileName, int flags, SFTPFileAttributes attr) throws IOException; + + public void mkdir(String dirName, int posixPermissions) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(dirName, this.getCharset()); + tw.writeUINT32(AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS); + tw.writeUINT32(posixPermissions); + sendMessage(Packet.SSH_FXP_MKDIR, req_id, tw.getBytes()); + expectStatusOKMessage(req_id); + } + + public void rm(String fileName) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(fileName, this.getCharset()); + sendMessage(Packet.SSH_FXP_REMOVE, req_id, tw.getBytes()); + expectStatusOKMessage(req_id); + } + + public void rmdir(String dirName) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(dirName, this.getCharset()); + sendMessage(Packet.SSH_FXP_RMDIR, req_id, tw.getBytes()); + expectStatusOKMessage(req_id); + } + + public void mv(String oldPath, String newPath) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(oldPath, this.getCharset()); + tw.writeString(newPath, this.getCharset()); + sendMessage(Packet.SSH_FXP_RENAME, req_id, tw.getBytes()); + expectStatusOKMessage(req_id); + } + + public String readLink(String path) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(path, charset); + sendMessage(Packet.SSH_FXP_READLINK, req_id, tw.getBytes()); + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + int rep_id = tr.readUINT32(); + + if (rep_id != req_id) { + throw new RequestMismatchException(); + } + + if (t == Packet.SSH_FXP_NAME) { + int count = tr.readUINT32(); + + if (count != 1) { + throw new PacketTypeException(t); + } + + return tr.readString(charset); + } + + if (t != Packet.SSH_FXP_STATUS) { + throw new PacketTypeException(t); + } + + int errorCode = tr.readUINT32(); + String errorMessage = tr.readString(); + listener.read(errorMessage); + throw new SFTPException(errorMessage, errorCode); + } + + public void setstat(String path, SFTPFileAttributes attr) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(path, charset); + tw.writeBytes(attr.toBytes()); + sendMessage(Packet.SSH_FXP_SETSTAT, req_id, tw.getBytes()); + expectStatusOKMessage(req_id); + } + + public void fsetstat(SFTPFileHandle handle, SFTPFileAttributes attr) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(handle.getHandle(), 0, handle.getHandle().length); + tw.writeBytes(attr.toBytes()); + sendMessage(Packet.SSH_FXP_FSETSTAT, req_id, tw.getBytes()); + expectStatusOKMessage(req_id); + } + + public void createSymlink(String src, String target) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(src, charset); + tw.writeString(target, charset); + sendMessage(Packet.SSH_FXP_SYMLINK, req_id, tw.getBytes()); + expectStatusOKMessage(req_id); + } + + public void createHardlink(String src, String target) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString("hardlink@openssh.com", charset); + tw.writeString(target, charset); + tw.writeString(src, charset); + sendMessage(Packet.SSH_FXP_EXTENDED, req_id, tw.getBytes()); + expectStatusOKMessage(req_id); + } + + public String canonicalPath(String path) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(path, charset); + sendMessage(Packet.SSH_FXP_REALPATH, req_id, tw.getBytes()); + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + int rep_id = tr.readUINT32(); + + if (rep_id != req_id) { + throw new RequestMismatchException(); + } + + if (t == Packet.SSH_FXP_NAME) { + int count = tr.readUINT32(); + + if (count != 1) { + throw new PacketFormatException("The server sent an invalid SSH_FXP_NAME packet."); + } + + final String name = tr.readString(charset); + listener.read(name); + return name; + } + + if (t != Packet.SSH_FXP_STATUS) { + throw new PacketTypeException(t); + } + + int errorCode = tr.readUINT32(); + String errorMessage = tr.readString(); + listener.read(errorMessage); + throw new SFTPException(errorMessage, errorCode); + } + + private void sendMessage(int type, int requestId, byte[] msg, int off, int len) throws IOException { + if (log.isDebugEnabled()) { + log.debug(String.format("Send message of type %d with request id %d", type, requestId)); + } + + listener.write(Packet.forName(type)); + int msglen = len + 1; + + if (type != Packet.SSH_FXP_INIT) { + msglen += 4; + } + + os.write(msglen >> 24); + os.write(msglen >> 16); + os.write(msglen >> 8); + os.write(msglen); + os.write(type); + + if (type != Packet.SSH_FXP_INIT) { + os.write(requestId >> 24); + os.write(requestId >> 16); + os.write(requestId >> 8); + os.write(requestId); + } + + os.write(msg, off, len); + os.flush(); + } + + protected void sendMessage(int type, int requestId, byte[] msg) throws IOException { + sendMessage(type, requestId, msg, 0, msg.length); + } + + private void readBytes(byte[] buff, int pos, int len) throws IOException { + while (len > 0) { + int count = is.read(buff, pos, len); + + if (count < 0) { + throw new SocketException("Unexpected end of stream."); + } + + len -= count; + pos += count; + } + } + + /** + * Read a message and guarantee that the contents is not larger than + * maxlen bytes. + *

+ * Note: receiveMessage(34000) actually means that the message may be up to 34004 + * bytes (the length attribute preceding the contents is 4 bytes). + * + * @param maxlen + * @return the message contents + * @throws IOException + */ + protected byte[] receiveMessage(int maxlen) throws IOException { + byte[] msglen = new byte[4]; + readBytes(msglen, 0, 4); + int len = (((msglen[0] & 0xff) << 24) | ((msglen[1] & 0xff) << 16) | ((msglen[2] & 0xff) << 8) | (msglen[3] & 0xff)); + + if ((len > maxlen) || (len <= 0)) { + throw new PacketFormatException(String.format("Illegal SFTP packet length %d", len)); + } + + byte[] msg = new byte[len]; + readBytes(msg, 0, len); + return msg; + } + + protected int generateNextRequestID() { + synchronized (this) { + return next_request_id++; + } + } + + protected void closeHandle(byte[] handle) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(handle, 0, handle.length); + sendMessage(Packet.SSH_FXP_CLOSE, req_id, tw.getBytes()); + expectStatusOKMessage(req_id); + } + + private void readStatus() throws IOException { + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + // Search the pending queue + OutstandingStatusRequest status = pendingStatusQueue.remove(tr.readUINT32()); + + if (null == status) { + throw new RequestMismatchException(); + } + + // Evaluate the answer + if (t == Packet.SSH_FXP_STATUS) { + // In any case, stop sending more packets + int code = tr.readUINT32(); + + if (log.isDebugEnabled()) { + String[] desc = ErrorCodes.getDescription(code); + log.debug("Got SSH_FXP_STATUS (" + status.req_id + ") (" + ((desc != null) ? desc[0] : "UNKNOWN") + ")"); + } + + if (code == ErrorCodes.SSH_FX_OK) { + return; + } + + String msg = tr.readString(); + listener.read(msg); + throw new SFTPException(msg, code); + } + + throw new PacketTypeException(t); + } + + private void readPendingReadStatus() throws IOException { + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + // Search the pending queue + OutstandingReadRequest status = pendingReadQueue.remove(tr.readUINT32()); + + if (null == status) { + throw new RequestMismatchException(); + } + + // Evaluate the answer + if (t == Packet.SSH_FXP_STATUS) { + // In any case, stop sending more packets + int code = tr.readUINT32(); + + if (log.isDebugEnabled()) { + String[] desc = ErrorCodes.getDescription(code); + log.debug("Got SSH_FXP_STATUS (" + status.req_id + ") (" + ((desc != null) ? desc[0] : "UNKNOWN") + ")"); + } + + if (code == ErrorCodes.SSH_FX_OK) { + return; + } + + if (code == ErrorCodes.SSH_FX_EOF) { + return; + } + + String msg = tr.readString(); + listener.read(msg); + throw new SFTPException(msg, code); + } + + throw new PacketTypeException(t); + } + + protected void expectStatusOKMessage(int id) throws IOException { + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + int rep_id = tr.readUINT32(); + + if (rep_id != id) { + throw new RequestMismatchException(); + } + + if (t != Packet.SSH_FXP_STATUS) { + throw new PacketTypeException(t); + } + + int errorCode = tr.readUINT32(); + + if (errorCode == ErrorCodes.SSH_FX_OK) { + return; + } + + String errorMessage = tr.readString(); + listener.read(errorMessage); + throw new SFTPException(errorMessage, errorCode); + } + + public void closeFile(SFTPFileHandle handle) throws IOException { + while (!pendingReadQueue.isEmpty()) { + this.readPendingReadStatus(); + } + + while (!pendingStatusQueue.isEmpty()) { + this.readStatus(); + } + + closeHandle(handle.getHandle()); + } + + public int read(SFTPFileHandle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException { + boolean errorOccured = false; + int remaining = len * parallelism; + //int clientOffset = dstoff; + long serverOffset = fileOffset; + + for (OutstandingReadRequest r : pendingReadQueue.values()) { + // Server offset should take pending requests into account. + serverOffset += r.len; + } + + while (true) { + // Stop if there was an error and no outstanding request + if ((pendingReadQueue.size() == 0) && errorOccured) { + break; + } + + // Send as many requests as we are allowed to + while (pendingReadQueue.size() < parallelism) { + if (errorOccured) { + break; + } + + // Send the next read request + OutstandingReadRequest req = new OutstandingReadRequest(); + req.req_id = generateNextRequestID(); + req.serverOffset = serverOffset; + req.len = (remaining > len) ? len : remaining; + req.buffer = dst; + req.dstOffset = dstoff; + serverOffset += req.len; + //clientOffset += req.len; + remaining -= req.len; + sendReadRequest(req.req_id, handle, req.serverOffset, req.len); + pendingReadQueue.put(req.req_id, req); + } + + if (pendingReadQueue.size() == 0) { + break; + } + + // Receive a single answer + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + // Search the pending queue + OutstandingReadRequest req = pendingReadQueue.remove(tr.readUINT32()); + + if (null == req) { + throw new RequestMismatchException(); + } + + // Evaluate the answer + if (t == Packet.SSH_FXP_STATUS) { + /* In any case, stop sending more packets */ + int code = tr.readUINT32(); + String msg = tr.readString(); + listener.read(msg); + + if (log.isDebugEnabled()) { + String[] desc = ErrorCodes.getDescription(code); + log.debug("Got SSH_FXP_STATUS (" + req.req_id + ") (" + ((desc != null) ? desc[0] : "UNKNOWN") + ")"); + } + + // Flag to read all pending requests but don't send any more. + errorOccured = true; + + if (pendingReadQueue.isEmpty()) { + if (ErrorCodes.SSH_FX_EOF == code) { + return -1; + } + + throw new SFTPException(msg, code); + } + } + else if (t == Packet.SSH_FXP_DATA) { + // OK, collect data + int readLen = tr.readUINT32(); + + if ((readLen < 0) || (readLen > req.len)) { + throw new PacketFormatException("The server sent an invalid length field in a SSH_FXP_DATA packet."); + } + + if (log.isDebugEnabled()) { + log.debug("Got SSH_FXP_DATA (" + req.req_id + ") " + req.serverOffset + "/" + readLen + + " (requested: " + req.len + ")"); + } + + // Read bytes into buffer + tr.readBytes(req.buffer, req.dstOffset, readLen); + + if (readLen < req.len) { + /* Send this request packet again to request the remaing data in this slot. */ + req.req_id = generateNextRequestID(); + req.serverOffset += readLen; + req.len -= readLen; + log.debug("Requesting again: " + req.serverOffset + "/" + req.len); + sendReadRequest(req.req_id, handle, req.serverOffset, req.len); + pendingReadQueue.put(req.req_id, req); + } + + return readLen; + } + else { + throw new PacketTypeException(t); + } + } + + // Should never reach here. + throw new SFTPException("No EOF reached", -1); + } + + private void sendReadRequest(int id, SFTPFileHandle handle, long offset, int len) throws IOException { + TypesWriter tw = new TypesWriter(); + tw.writeString(handle.getHandle(), 0, handle.getHandle().length); + tw.writeUINT64(offset); + tw.writeUINT32(len); + sendMessage(Packet.SSH_FXP_READ, id, tw.getBytes()); + } + + public void write(SFTPFileHandle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException { + while (len > 0) { + int writeRequestLen = len; + + if (writeRequestLen > 32768) { + writeRequestLen = 32768; + } + + // Send the next write request + OutstandingStatusRequest req = new OutstandingStatusRequest(); + req.req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(handle.getHandle(), 0, handle.getHandle().length); + tw.writeUINT64(fileOffset); + tw.writeString(src, srcoff, writeRequestLen); + sendMessage(Packet.SSH_FXP_WRITE, req.req_id, tw.getBytes()); + pendingStatusQueue.put(req.req_id, req); + + // Only read next status if parallelism reached + while (pendingStatusQueue.size() >= parallelism) { + this.readStatus(); + } + + fileOffset += writeRequestLen; + srcoff += writeRequestLen; + len -= writeRequestLen; + } + } + + + /** + * A read is divided into multiple requests sent sequentially before + * reading any status from the server + */ + private static class OutstandingReadRequest { + int req_id; + /** + * Read offset to request on server starting at the file offset for the first request. + */ + long serverOffset; + /** + * Length of requested data + */ + int len; + /** + * Offset in destination buffer + */ + int dstOffset; + /** + * Temporary buffer + */ + byte[] buffer; + } + + /** + * A read is divided into multiple requests sent sequentially before + * reading any status from the server + */ + private static final class OutstandingStatusRequest { + int req_id; + } + +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/AuthAgentCallback.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/AuthAgentCallback.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,64 @@ +package ch.ethz.ssh2; + +import java.security.KeyPair; +import java.util.Map; + +/** + * AuthAgentCallback. + * + * @author Kenny Root + * @version $Id$ + */ +public interface AuthAgentCallback { + + /** + * @return array of blobs containing the OpenSSH-format encoded public keys + */ + Map retrieveIdentities(); + + /** + * @param pair A RSAPrivateKey or DSAPrivateKey or ECPrivateKey + * containing a DSA or RSA or EC private key of + * the user in standard java key format. + * @param comment comment associated with this key + * @param confirmUse whether to prompt before using this key + * @param lifetime lifetime in seconds for key to be remembered + * @return success or failure + */ + boolean addIdentity(KeyPair pair, String comment, boolean confirmUse, int lifetime); + + /** + * @param publicKey byte blob containing the OpenSSH-format encoded public key + * @return success or failure + */ + boolean removeIdentity(byte[] publicKey); + + /** + * @return success or failure + */ + boolean removeAllIdentities(); + + /** + * @param publicKey byte blob containing the OpenSSH-format encoded public key + * @return A RSAPrivateKey or DSAPrivateKey + * containing a DSA or RSA or EC private key of + * the user in standard java key format. + */ + KeyPair getKeyPair(byte[] publicKey); + + /** + * @return + */ + boolean isAgentLocked(); + + /** + * @param lockPassphrase + */ + boolean setAgentLock(String lockPassphrase); + + /** + * @param unlockPassphrase + * @return + */ + boolean requestAgentUnlock(String unlockPassphrase); +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/AuthenticationResult.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/AuthenticationResult.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,21 @@ + +package ch.ethz.ssh2; + +public enum AuthenticationResult { + + /** + * + */ + SUCCESS, + /** + * The authentication request to which this is a response was successful, however, more + * authentication requests are needed (multi-method authentication sequence). + * + * @see ServerAuthenticationCallback#getRemainingAuthMethods(ServerConnection) + */ + PARTIAL_SUCCESS, + /** + * The server rejected the authentication request. + */ + FAILURE +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/ChannelCondition.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/ChannelCondition.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +/** + * Contains constants that can be used to specify what conditions to wait for on + * a SSH-2 channel (e.g., represented by a {@link Session}). + * + * @see Session#waitForCondition(int, long) + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ + +public abstract interface ChannelCondition { + /** + * A timeout has occurred, none of your requested conditions is fulfilled. + * However, other conditions may be true - therefore, NEVER use the "==" + * operator to test for this (or any other) condition. Always use + * something like ((cond & ChannelCondition.CLOSED) != 0). + */ + public static final int TIMEOUT = 1; + + /** + * The underlying SSH-2 channel, however not necessarily the whole connection, + * has been closed. This implies EOF. Note that there may still + * be unread stdout or stderr data in the local window, i.e, STDOUT_DATA + * or/and STDERR_DATA may be set at the same time. + */ + public static final int CLOSED = 2; + + /** + * There is stdout data available that is ready to be consumed. + */ + public static final int STDOUT_DATA = 4; + + /** + * There is stderr data available that is ready to be consumed. + */ + public static final int STDERR_DATA = 8; + + /** + * EOF on has been reached, no more _new_ stdout or stderr data will arrive + * from the remote server. However, there may be unread stdout or stderr + * data, i.e, STDOUT_DATA or/and STDERR_DATA + * may be set at the same time. + */ + public static final int EOF = 16; + + /** + * The exit status of the remote process is available. + * Some servers never send the exist status, or occasionally "forget" to do so. + */ + public static final int EXIT_STATUS = 32; + + /** + * The exit signal of the remote process is available. + */ + public static final int EXIT_SIGNAL = 64; + +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/Connection.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/Connection.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,1444 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2; + +import java.io.CharArrayWriter; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.security.KeyPair; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import ch.ethz.ssh2.auth.AgentProxy; +import ch.ethz.ssh2.auth.AuthenticationManager; +import ch.ethz.ssh2.channel.ChannelManager; +import ch.ethz.ssh2.compression.CompressionFactory; +import ch.ethz.ssh2.crypto.CryptoWishList; +import ch.ethz.ssh2.crypto.cipher.BlockCipherFactory; +import ch.ethz.ssh2.crypto.digest.MAC; +import ch.ethz.ssh2.packets.PacketIgnore; +import ch.ethz.ssh2.transport.ClientTransportManager; +import ch.ethz.ssh2.transport.HTTPProxyClientTransportManager; +import ch.ethz.ssh2.transport.KexManager; +import ch.ethz.ssh2.util.TimeoutService; +import ch.ethz.ssh2.util.TimeoutService.TimeoutToken; + +/** + * A Connection is used to establish an encrypted TCP/IP + * connection to a SSH-2 server. + *

+ * Typically, one + *

    + *
  1. creates a {@link #Connection(String) Connection} object.
  2. + *
  3. calls the {@link #connect() connect()} method.
  4. + *
  5. calls some of the authentication methods (e.g., {@link #authenticateWithPublicKey(String, File, String) authenticateWithPublicKey()}).
  6. + *
  7. calls one or several times the {@link #openSession() openSession()} method.
  8. + *
  9. finally, one must close the connection and release resources with the {@link #close() close()} method.
  10. + *
+ * + * @author Christian Plattner + * @version $Id: Connection.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ + +public class Connection { + /** + * The identifier presented to the SSH-2 server. This is the same + * as the "softwareversion" defined in RFC 4253. + *

+ * NOTE: As per the RFC, the "softwareversion" string MUST consist of printable + * US-ASCII characters, with the exception of whitespace characters and the minus sign (-). + */ + private String softwareversion + = String.format("Ganymed_%s", Version.getSpecification()); + + /* Will be used to generate all random data needed for the current connection. + * Note: SecureRandom.nextBytes() is thread safe. + */ + + private SecureRandom generator; + + /** + * Unless you know what you are doing, you will never need this. + * + * @return The list of supported cipher algorithms by this implementation. + */ + + public static synchronized String[] getAvailableCiphers() { + return BlockCipherFactory.getDefaultCipherList(); + } + + /** + * Unless you know what you are doing, you will never need this. + * + * @return The list of supported MAC algorthims by this implementation. + */ + + public static synchronized String[] getAvailableMACs() { + return MAC.getMacList(); + } + + /** + * Unless you know what you are doing, you will never need this. + * + * @return The list of supported server host key algorthims by this implementation. + */ + + public static synchronized String[] getAvailableServerHostKeyAlgorithms() { + return KexManager.getDefaultServerHostkeyAlgorithmList(); + } + + private AuthenticationManager am; + + private boolean authenticated; + private ChannelManager cm; + + private CryptoWishList cryptoWishList + = new CryptoWishList(); + + private DHGexParameters dhgexpara + = new DHGexParameters(); + + private final String hostname; + + private final int port; + + private ClientTransportManager tm; + + private boolean tcpNoDelay = false; + + private HTTPProxyData proxy; + + private List connectionMonitors + = new ArrayList(); + + /** + * Prepares a fresh Connection object which can then be used + * to establish a connection to the specified SSH-2 server. + *

+ * Same as {@link #Connection(String, int) Connection(hostname, 22)}. + * + * @param hostname the hostname of the SSH-2 server. + */ + public Connection(String hostname) { + this(hostname, 22); + } + + /** + * Prepares a fresh Connection object which can then be used + * to establish a connection to the specified SSH-2 server. + * + * @param hostname the host where we later want to connect to. + * @param port port on the server, normally 22. + */ + public Connection(String hostname, int port) { + this.hostname = hostname; + this.port = port; + } + + /** + * Prepares a fresh Connection object which can then be used + * to establish a connection to the specified SSH-2 server. + * + * @param hostname the host where we later want to connect to. + * @param port port on the server, normally 22. + * @param softwareversion Allows you to set a custom "softwareversion" string as defined in RFC 4253. + * NOTE: As per the RFC, the "softwareversion" string MUST consist of printable + * US-ASCII characters, with the exception of whitespace characters and the minus sign (-). + */ + public Connection(String hostname, int port, String softwareversion) { + this.hostname = hostname; + this.port = port; + this.softwareversion = softwareversion; + } + + public Connection(String hostname, int port, final HTTPProxyData proxy) { + this.hostname = hostname; + this.port = port; + this.proxy = proxy; + } + + public Connection(String hostname, int port, String softwareversion, final HTTPProxyData proxy) { + this.hostname = hostname; + this.port = port; + this.softwareversion = softwareversion; + this.proxy = proxy; + } + + /** + * After a successful connect, one has to authenticate oneself. This method + * is based on DSA (it uses DSA to sign a challenge sent by the server). + *

+ * If the authentication phase is complete, true will be + * returned. If the server does not accept the request (or if further + * authentication steps are needed), false is returned and + * one can retry either by using this or any other authentication method + * (use the getRemainingAuthMethods method to get a list of + * the remaining possible methods). + * + * @param user A String holding the username. + * @param pem A String containing the DSA private key of the + * user in OpenSSH key format (PEM, you can't miss the + * "-----BEGIN DSA PRIVATE KEY-----" tag). The string may contain + * linefeeds. + * @param password If the PEM string is 3DES encrypted ("DES-EDE3-CBC"), then you + * must specify the password. Otherwise, this argument will be + * ignored and can be set to null. + * @return whether the connection is now authenticated. + * @throws IOException + * @deprecated You should use one of the {@link #authenticateWithPublicKey(String, File, String) authenticateWithPublicKey()} + * methods, this method is just a wrapper for it and will + * disappear in future builds. + */ + @Deprecated + + public synchronized boolean authenticateWithDSA(String user, String pem, String password) throws IOException { + if (tm == null) { + throw new IllegalStateException("Connection is not established!"); + } + + if (authenticated) { + throw new IllegalStateException("Connection is already authenticated!"); + } + + if (am == null) { + am = new AuthenticationManager(tm); + } + + if (cm == null) { + cm = new ChannelManager(tm); + } + + if (user == null) { + throw new IllegalArgumentException("user argument is null"); + } + + if (pem == null) { + throw new IllegalArgumentException("pem argument is null"); + } + + authenticated = am.authenticatePublicKey(user, pem.toCharArray(), password, getOrCreateSecureRND()); + return authenticated; + } + + /** + * A wrapper that calls {@link #authenticateWithKeyboardInteractive(String, String[], InteractiveCallback) + * authenticateWithKeyboardInteractivewith} a null submethod list. + * + * @param user A String holding the username. + * @param cb An InteractiveCallback which will be used to + * determine the responses to the questions asked by the server. + * @return whether the connection is now authenticated. + * @throws IOException + */ + + public synchronized boolean authenticateWithKeyboardInteractive(String user, InteractiveCallback cb) + throws IOException { + return authenticateWithKeyboardInteractive(user, null, cb); + } + + /** + * After a successful connect, one has to authenticate oneself. This method + * is based on "keyboard-interactive", specified in + * draft-ietf-secsh-auth-kbdinteract-XX. Basically, you have to define a + * callback object which will be feeded with challenges generated by the + * server. Answers are then sent back to the server. It is possible that the + * callback will be called several times during the invocation of this + * method (e.g., if the server replies to the callback's answer(s) with + * another challenge...) + *

+ * If the authentication phase is complete, true will be + * returned. If the server does not accept the request (or if further + * authentication steps are needed), false is returned and + * one can retry either by using this or any other authentication method + * (use the getRemainingAuthMethods method to get a list of + * the remaining possible methods). + *

+ * Note: some SSH servers advertise "keyboard-interactive", however, any + * interactive request will be denied (without having sent any challenge to + * the client). + * + * @param user A String holding the username. + * @param submethods An array of submethod names, see + * draft-ietf-secsh-auth-kbdinteract-XX. May be null + * to indicate an empty list. + * @param cb An InteractiveCallback which will be used to + * determine the responses to the questions asked by the server. + * @return whether the connection is now authenticated. + * @throws IOException + */ + + public synchronized boolean authenticateWithKeyboardInteractive(String user, String[] submethods, + InteractiveCallback cb) throws IOException { + if (cb == null) { + throw new IllegalArgumentException("Callback may not ne NULL!"); + } + + if (tm == null) { + throw new IllegalStateException("Connection is not established!"); + } + + if (authenticated) { + throw new IllegalStateException("Connection is already authenticated!"); + } + + if (am == null) { + am = new AuthenticationManager(tm); + } + + if (cm == null) { + cm = new ChannelManager(tm); + } + + if (user == null) { + throw new IllegalArgumentException("user argument is null"); + } + + authenticated = am.authenticateInteractive(user, submethods, cb); + return authenticated; + } + + public synchronized boolean authenticateWithAgent(String user, AgentProxy proxy) throws IOException { + if (tm == null) { + throw new IllegalStateException("Connection is not established!"); + } + + if (authenticated) { + throw new IllegalStateException("Connection is already authenticated!"); + } + + if (am == null) { + am = new AuthenticationManager(tm); + } + + if (cm == null) { + cm = new ChannelManager(tm); + } + + if (user == null) { + throw new IllegalArgumentException("user argument is null"); + } + + authenticated = am.authenticatePublicKey(user, proxy); + return authenticated; + } + + /** + * After a successful connect, one has to authenticate oneself. This method + * sends username and password to the server. + *

+ * If the authentication phase is complete, true will be + * returned. If the server does not accept the request (or if further + * authentication steps are needed), false is returned and + * one can retry either by using this or any other authentication method + * (use the getRemainingAuthMethods method to get a list of + * the remaining possible methods). + *

+ * Note: if this method fails, then please double-check that it is actually + * offered by the server (use {@link #getRemainingAuthMethods(String) getRemainingAuthMethods()}. + *

+ * Often, password authentication is disabled, but users are not aware of it. + * Many servers only offer "publickey" and "keyboard-interactive". However, + * even though "keyboard-interactive" *feels* like password authentication + * (e.g., when using the putty or openssh clients) it is *not* the same mechanism. + * + * @param user + * @param password + * @return if the connection is now authenticated. + * @throws IOException + */ + + public synchronized boolean authenticateWithPassword(String user, String password) throws IOException { + if (tm == null) { + throw new IllegalStateException("Connection is not established!"); + } + + if (authenticated) { + throw new IllegalStateException("Connection is already authenticated!"); + } + + if (am == null) { + am = new AuthenticationManager(tm); + } + + if (cm == null) { + cm = new ChannelManager(tm); + } + + if (user == null) { + throw new IllegalArgumentException("user argument is null"); + } + + if (password == null) { + throw new IllegalArgumentException("password argument is null"); + } + + authenticated = am.authenticatePassword(user, password); + return authenticated; + } + + /** + * After a successful connect, one has to authenticate oneself. + * This method can be used to explicitly use the special "none" + * authentication method (where only a username has to be specified). + *

+ * Note 1: The "none" method may always be tried by clients, however as by + * the specs, the server will not explicitly announce it. In other words, + * the "none" token will never show up in the list returned by + * {@link #getRemainingAuthMethods(String)}. + *

+ * Note 2: no matter which one of the authenticateWithXXX() methods + * you call, the library will always issue exactly one initial "none" + * authentication request to retrieve the initially allowed list of + * authentication methods by the server. Please read RFC 4252 for the + * details. + *

+ * If the authentication phase is complete, true will be + * returned. If further authentication steps are needed, false + * is returned and one can retry by any other authentication method + * (use the getRemainingAuthMethods method to get a list of + * the remaining possible methods). + * + * @param user + * @return if the connection is now authenticated. + * @throws IOException + */ + + public synchronized boolean authenticateWithNone(String user) throws IOException { + if (tm == null) { + throw new IllegalStateException("Connection is not established!"); + } + + if (authenticated) { + throw new IllegalStateException("Connection is already authenticated!"); + } + + if (am == null) { + am = new AuthenticationManager(tm); + } + + if (cm == null) { + cm = new ChannelManager(tm); + } + + if (user == null) { + throw new IllegalArgumentException("user argument is null"); + } + + /* Trigger the sending of the PacketUserauthRequestNone packet */ + /* (if not already done) */ + authenticated = am.authenticateNone(user); + return authenticated; + } + + /** + * After a successful connect, one has to authenticate oneself. + * The authentication method "publickey" works by signing a challenge + * sent by the server. The signature is either DSA or RSA based - it + * just depends on the type of private key you specify, either a DSA + * or RSA private key in PEM format. And yes, this is may seem to be a + * little confusing, the method is called "publickey" in the SSH-2 protocol + * specification, however since we need to generate a signature, you + * actually have to supply a private key =). + *

+ * The private key contained in the PEM file may also be encrypted ("Proc-Type: 4,ENCRYPTED"). + * The library supports DES-CBC and DES-EDE3-CBC encryption, as well + * as the more exotic PEM encrpytions AES-128-CBC, AES-192-CBC and AES-256-CBC. + *

+ * If the authentication phase is complete, true will be + * returned. If the server does not accept the request (or if further + * authentication steps are needed), false is returned and + * one can retry either by using this or any other authentication method + * (use the getRemainingAuthMethods method to get a list of + * the remaining possible methods). + *

+ * NOTE PUTTY USERS: Event though your key file may start with "-----BEGIN..." + * it is not in the expected format. You have to convert it to the OpenSSH + * key format by using the "puttygen" tool (can be downloaded from the Putty + * website). Simply load your key and then use the "Conversions/Export OpenSSH key" + * functionality to get a proper PEM file. + * + * @param user A String holding the username. + * @param pemPrivateKey A char[] containing a DSA or RSA private key of the + * user in OpenSSH key format (PEM, you can't miss the + * "-----BEGIN DSA PRIVATE KEY-----" or "-----BEGIN RSA PRIVATE KEY-----" + * tag). The char array may contain linebreaks/linefeeds. + * @param password If the PEM structure is encrypted ("Proc-Type: 4,ENCRYPTED") then + * you must specify a password. Otherwise, this argument will be ignored + * and can be set to null. + * @return whether the connection is now authenticated. + * @throws IOException + */ + + public synchronized boolean authenticateWithPublicKey(String user, char[] pemPrivateKey, String password) + throws IOException { + if (tm == null) { + throw new IllegalStateException("Connection is not established!"); + } + + if (authenticated) { + throw new IllegalStateException("Connection is already authenticated!"); + } + + if (am == null) { + am = new AuthenticationManager(tm); + } + + if (cm == null) { + cm = new ChannelManager(tm); + } + + if (user == null) { + throw new IllegalArgumentException("user argument is null"); + } + + if (pemPrivateKey == null) { + throw new IllegalArgumentException("pemPrivateKey argument is null"); + } + + authenticated = am.authenticatePublicKey(user, pemPrivateKey, password, getOrCreateSecureRND()); + return authenticated; + } + + /** + * After a successful connect, one has to authenticate oneself. The + * authentication method "publickey" works by signing a challenge sent by + * the server. The signature is either DSA or RSA based - it just depends on + * the type of private key you specify, either a DSA or RSA private key in + * PEM format. And yes, this is may seem to be a little confusing, the + * method is called "publickey" in the SSH-2 protocol specification, however + * since we need to generate a signature, you actually have to supply a + * private key =). + *

+ * If the authentication phase is complete, true will be + * returned. If the server does not accept the request (or if further + * authentication steps are needed), false is returned and + * one can retry either by using this or any other authentication method + * (use the getRemainingAuthMethods method to get a list of + * the remaining possible methods). + * + * @param user + * A String holding the username. + * @param pair + * A RSAPrivateKey or DSAPrivateKey + * containing a DSA or RSA private key of + * the user in Trilead object format. + * + * @return whether the connection is now authenticated. + * @throws IOException + */ + + public synchronized boolean authenticateWithPublicKey(String user, KeyPair pair) + throws IOException { + if (tm == null) + throw new IllegalStateException("Connection is not established!"); + + if (authenticated) + throw new IllegalStateException("Connection is already authenticated!"); + + if (am == null) + am = new AuthenticationManager(tm); + + if (cm == null) + cm = new ChannelManager(tm); + + if (user == null) + throw new IllegalArgumentException("user argument is null"); + + if (pair == null) + throw new IllegalArgumentException("Key pair argument is null"); + + authenticated = am.authenticatePublicKey(user, pair, getOrCreateSecureRND()); + return authenticated; + } + + /** + * A convenience wrapper function which reads in a private key (PEM format, either DSA or RSA) + * and then calls authenticateWithPublicKey(String, char[], String). + *

+ * NOTE PUTTY USERS: Event though your key file may start with "-----BEGIN..." + * it is not in the expected format. You have to convert it to the OpenSSH + * key format by using the "puttygen" tool (can be downloaded from the Putty + * website). Simply load your key and then use the "Conversions/Export OpenSSH key" + * functionality to get a proper PEM file. + * + * @param user A String holding the username. + * @param pemFile A File object pointing to a file containing a DSA or RSA + * private key of the user in OpenSSH key format (PEM, you can't miss the + * "-----BEGIN DSA PRIVATE KEY-----" or "-----BEGIN RSA PRIVATE KEY-----" + * tag). + * @param password If the PEM file is encrypted then you must specify the password. + * Otherwise, this argument will be ignored and can be set to null. + * @return whether the connection is now authenticated. + * @throws IOException + */ + + public synchronized boolean authenticateWithPublicKey(String user, File pemFile, String password) + throws IOException { + if (pemFile == null) { + throw new IllegalArgumentException("pemFile argument is null"); + } + + char[] buff = new char[256]; + CharArrayWriter cw = new CharArrayWriter(); + FileReader fr = new FileReader(pemFile); + + while (true) { + int len = fr.read(buff); + + if (len < 0) { + break; + } + + cw.write(buff, 0, len); + } + + fr.close(); + return authenticateWithPublicKey(user, cw.toCharArray(), password); + } + + /** + * Add a {@link ConnectionMonitor} to this connection. Can be invoked at any time, + * but it is best to add connection monitors before invoking + * connect() to avoid glitches (e.g., you add a connection monitor after + * a successful connect(), but the connection has died in the mean time. Then, + * your connection monitor won't be notified.) + *

+ * You can add as many monitors as you like. If a monitor has already been added, then + * this method does nothing. + * + * @param cmon An object implementing the {@link ConnectionMonitor} interface. + * @see ConnectionMonitor + */ + + public synchronized void addConnectionMonitor(ConnectionMonitor cmon) { + if (!connectionMonitors.contains(cmon)) { + connectionMonitors.add(cmon); + + if (tm != null) { + tm.setConnectionMonitors(connectionMonitors); + } + } + } + + /** + * Remove a {@link ConnectionMonitor} from this connection. + * + * @param cmon + * @return whether the monitor could be removed + */ + + public synchronized boolean removeConnectionMonitor(ConnectionMonitor cmon) { + boolean existed = connectionMonitors.remove(cmon); + + if (tm != null) { + tm.setConnectionMonitors(connectionMonitors); + } + + return existed; + } + + /** + * Controls whether compression is used on the link or not. + *

+ * Note: This can only be called before connect() + * @param enabled whether to enable compression + * @throws IOException + */ + + public synchronized void setCompression(boolean enabled) throws IOException { + if (tm != null) + throw new IOException("Connection to " + hostname + " is already in connected state!"); + + if (enabled) enableCompression(); + else disableCompression(); + } + + /** + * Close the connection to the SSH-2 server. All assigned sessions will be + * closed, too. Can be called at any time. Don't forget to call this once + * you don't need a connection anymore - otherwise the receiver thread may + * run forever. + */ + + public synchronized void close() { + Throwable t = new Throwable("Closed due to user request."); + close(t, false); + } + + public synchronized void close(Throwable t, boolean hard) { + if (cm != null) { + cm.closeAllChannels(); + } + + if (tm != null) { + tm.close(t, hard == false); + tm = null; + } + + am = null; + cm = null; + authenticated = false; + } + + /** + * Same as {@link #connect(ServerHostKeyVerifier, int, int) connect(null, 0, 0)}. + * + * @return see comments for the {@link #connect(ServerHostKeyVerifier, int, int) connect(ServerHostKeyVerifier, int, int)} method. + * @throws IOException + */ + + public synchronized ConnectionInfo connect() throws IOException { + return connect(null, 0, 0); + } + + /** + * Same as {@link #connect(ServerHostKeyVerifier, int, int) connect(verifier, 0, 0)}. + * + * @return see comments for the {@link #connect(ServerHostKeyVerifier, int, int) connect(ServerHostKeyVerifier, int, int)} method. + * @throws IOException + */ + + public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier) throws IOException { + return connect(verifier, 0, 0); + } + + /** + * Connect to the SSH-2 server and, as soon as the server has presented its + * host key, use the {@link ServerHostKeyVerifier#verifyServerHostKey(String, + * int, String, byte[]) ServerHostKeyVerifier.verifyServerHostKey()} + * method of the verifier to ask for permission to proceed. + * If verifier is null, then any host key will be + * accepted - this is NOT recommended, since it makes man-in-the-middle attackes + * VERY easy (somebody could put a proxy SSH server between you and the real server). + *

+ * Note: The verifier will be called before doing any crypto calculations + * (i.e., diffie-hellman). Therefore, if you don't like the presented host key then + * no CPU cycles are wasted (and the evil server has less information about us). + *

+ * However, it is still possible that the server presented a fake host key: the server + * cheated (typically a sign for a man-in-the-middle attack) and is not able to generate + * a signature that matches its host key. Don't worry, the library will detect such + * a scenario later when checking the signature (the signature cannot be checked before + * having completed the diffie-hellman exchange). + *

+ * Note 2: The {@link ServerHostKeyVerifier#verifyServerHostKey(String, + * int, String, byte[]) ServerHostKeyVerifier.verifyServerHostKey()} method + * will *NOT* be called from the current thread, the call is being made from a + * background thread (there is a background dispatcher thread for every + * established connection). + *

+ * Note 3: This method will block as long as the key exchange of the underlying connection + * has not been completed (and you have not specified any timeouts). + *

+ * Note 4: If you want to re-use a connection object that was successfully connected, + * then you must call the {@link #close()} method before invoking connect() again. + * + * @param verifier An object that implements the + * {@link ServerHostKeyVerifier} interface. Pass null + * to accept any server host key - NOT recommended. + * @param connectTimeout Connect the underlying TCP socket to the server with the given timeout + * value (non-negative, in milliseconds). Zero means no timeout. + * @param kexTimeout Timeout for complete connection establishment (non-negative, + * in milliseconds). Zero means no timeout. The timeout counts from the + * moment you invoke the connect() method and is cancelled as soon as the + * first key-exchange round has finished. It is possible that + * the timeout event will be fired during the invocation of the + * verifier callback, but it will only have an effect after + * the verifier returns. + * @return A {@link ConnectionInfo} object containing the details of + * the established connection. + * @throws IOException If any problem occurs, e.g., the server's host key is not + * accepted by the verifier or there is problem during + * the initial crypto setup (e.g., the signature sent by the server is wrong). + *

+ * In case of a timeout (either connectTimeout or kexTimeout) + * a SocketTimeoutException is thrown. + *

+ * An exception may also be thrown if the connection was already successfully + * connected (no matter if the connection broke in the mean time) and you invoke + * connect() again without having called {@link #close()} first. + *

+ * If a HTTP proxy is being used and the proxy refuses the connection, + * then a {@link HTTPProxyException} may be thrown, which + * contains the details returned by the proxy. If the proxy is buggy and does + * not return a proper HTTP response, then a normal IOException is thrown instead. + */ + + public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier, int connectTimeout, int kexTimeout) + throws IOException { + final class TimeoutState { + boolean isCancelled = false; + boolean timeoutSocketClosed = false; + } + + if (tm != null) { + throw new IllegalStateException(String.format("Connection to %s is already in connected state", hostname)); + } + + if (connectTimeout < 0) { + throw new IllegalArgumentException("connectTimeout must be non-negative!"); + } + + if (kexTimeout < 0) { + throw new IllegalArgumentException("kexTimeout must be non-negative!"); + } + + final TimeoutState state = new TimeoutState(); + + if (null == proxy) { + tm = new ClientTransportManager(new Socket()); + } + else { + tm = new HTTPProxyClientTransportManager(new Socket(), proxy); + } + + tm.setSoTimeout(connectTimeout); + tm.setTcpNoDelay(tcpNoDelay); + tm.setConnectionMonitors(connectionMonitors); + + try { + TimeoutToken token = null; + + if (kexTimeout > 0) { + final Runnable timeoutHandler = new Runnable() { + public void run() { + synchronized (state) { + if (state.isCancelled) { + return; + } + + state.timeoutSocketClosed = true; + tm.close(new SocketTimeoutException("The connect timeout expired"), false); + } + } + }; + long timeoutHorizont = System.currentTimeMillis() + kexTimeout; + token = TimeoutService.addTimeoutHandler(timeoutHorizont, timeoutHandler); + } + + tm.connect(hostname, port, softwareversion, cryptoWishList, verifier, dhgexpara, connectTimeout, + getOrCreateSecureRND()); + /* Wait until first KEX has finished */ + ConnectionInfo ci = tm.getConnectionInfo(1); + + /* Now try to cancel the timeout, if needed */ + + if (token != null) { + TimeoutService.cancelTimeoutHandler(token); + + /* Were we too late? */ + + synchronized (state) { + if (state.timeoutSocketClosed) { + throw new IOException("This exception will be replaced by the one below =)"); + } + + /* Just in case the "cancelTimeoutHandler" invocation came just a little bit + * too late but the handler did not enter the semaphore yet - we can + * still stop it. + */ + state.isCancelled = true; + } + } + + return ci; + } + catch (SocketTimeoutException e) { + throw e; + } + catch (HTTPProxyException e) { + throw e; + } + catch (IOException e) { + // This will also invoke any registered connection monitors + close(e, false); + + synchronized (state) { + /* Show a clean exception, not something like "the socket is closed!?!" */ + if (state.timeoutSocketClosed) { + throw new SocketTimeoutException(String.format("The kexTimeout (%d ms) expired.", kexTimeout)); + } + } + + throw e; + } + } + + /** + * Creates a new {@link LocalPortForwarder}. + * A LocalPortForwarder forwards TCP/IP connections that arrive at a local + * port via the secure tunnel to another host (which may or may not be + * identical to the remote SSH-2 server). + *

+ * This method must only be called after one has passed successfully the authentication step. + * There is no limit on the number of concurrent forwardings. + * + * @param local_port the local port the LocalPortForwarder shall bind to. + * @param host_to_connect target address (IP or hostname) + * @param port_to_connect target port + * @return A {@link LocalPortForwarder} object. + * @throws IOException + */ + + public synchronized LocalPortForwarder createLocalPortForwarder(int local_port, String host_to_connect, + int port_to_connect) throws IOException { + this.checkConnection(); + return new LocalPortForwarder(cm, local_port, host_to_connect, port_to_connect); + } + + /** + * Creates a new {@link LocalPortForwarder}. + * A LocalPortForwarder forwards TCP/IP connections that arrive at a local + * port via the secure tunnel to another host (which may or may not be + * identical to the remote SSH-2 server). + *

+ * This method must only be called after one has passed successfully the authentication step. + * There is no limit on the number of concurrent forwardings. + * + * @param addr specifies the InetSocketAddress where the local socket shall be bound to. + * @param host_to_connect target address (IP or hostname) + * @param port_to_connect target port + * @return A {@link LocalPortForwarder} object. + * @throws IOException + */ + + public synchronized LocalPortForwarder createLocalPortForwarder(InetSocketAddress addr, String host_to_connect, + int port_to_connect) throws IOException { + this.checkConnection(); + return new LocalPortForwarder(cm, addr, host_to_connect, port_to_connect); + } + + /** + * Creates a new {@link LocalStreamForwarder}. + * A LocalStreamForwarder manages an Input/Outputstream pair + * that is being forwarded via the secure tunnel into a TCP/IP connection to another host + * (which may or may not be identical to the remote SSH-2 server). + * + * @param host_to_connect + * @param port_to_connect + * @return A {@link LocalStreamForwarder} object. + * @throws IOException + */ + + public synchronized LocalStreamForwarder createLocalStreamForwarder(String host_to_connect, int port_to_connect) + throws IOException { + this.checkConnection(); + return new LocalStreamForwarder(cm, host_to_connect, port_to_connect); + } + + /** + * Creates a new {@link DynamicPortForwarder}. A + * DynamicPortForwarder forwards TCP/IP connections that arrive + * at a local port via the secure tunnel to another host that is chosen via + * the SOCKS protocol. + *

+ * This method must only be called after one has passed successfully the + * authentication step. There is no limit on the number of concurrent + * forwardings. + * + * @param local_port + * @return A {@link DynamicPortForwarder} object. + * @throws IOException + */ + + public synchronized DynamicPortForwarder createDynamicPortForwarder(int local_port) throws IOException { + if (tm == null) + throw new IllegalStateException("Cannot forward ports, you need to establish a connection first."); + + if (!authenticated) + throw new IllegalStateException("Cannot forward ports, connection is not authenticated."); + + return new DynamicPortForwarder(cm, local_port); + } + + /** + * Creates a new {@link DynamicPortForwarder}. A + * DynamicPortForwarder forwards TCP/IP connections that arrive + * at a local port via the secure tunnel to another host that is chosen via + * the SOCKS protocol. + *

+ * This method must only be called after one has passed successfully the + * authentication step. There is no limit on the number of concurrent + * forwardings. + * + * @param addr + * specifies the InetSocketAddress where the local socket shall + * be bound to. + * @return A {@link DynamicPortForwarder} object. + * @throws IOException + */ + + public synchronized DynamicPortForwarder createDynamicPortForwarder(InetSocketAddress addr) throws IOException { + if (tm == null) + throw new IllegalStateException("Cannot forward ports, you need to establish a connection first."); + + if (!authenticated) + throw new IllegalStateException("Cannot forward ports, connection is not authenticated."); + + return new DynamicPortForwarder(cm, addr); + } + + /** + * Create a very basic {@link SCPClient} that can be used to copy + * files from/to the SSH-2 server. + *

+ * Works only after one has passed successfully the authentication step. + * There is no limit on the number of concurrent SCP clients. + *

+ * Note: This factory method will probably disappear in the future. + * + * @return A {@link SCPClient} object. + * @throws IOException + */ + + public synchronized SCPClient createSCPClient() throws IOException { + this.checkConnection(); + return new SCPClient(this); + } + + /** + * Force an asynchronous key re-exchange (the call does not block). The + * latest values set for MAC, Cipher and DH group exchange parameters will + * be used. If a key exchange is currently in progress, then this method has + * the only effect that the so far specified parameters will be used for the + * next (server driven) key exchange. + *

+ * Note: This implementation will never start a key exchange (other than the initial one) + * unless you or the SSH-2 server ask for it. + * + * @throws IOException In case of any failure behind the scenes. + */ + + public synchronized void forceKeyExchange() throws IOException { + this.checkConnection(); + tm.forceKeyExchange(cryptoWishList, dhgexpara, null, null, null); + } + + /** + * Returns the hostname that was passed to the constructor. + * + * @return the hostname + */ + + public synchronized String getHostname() { + return hostname; + } + + /** + * Returns the port that was passed to the constructor. + * + * @return the TCP port + */ + + public synchronized int getPort() { + return port; + } + + /** + * Returns a {@link ConnectionInfo} object containing the details of + * the connection. Can be called as soon as the connection has been + * established (successfully connected). + * + * @return A {@link ConnectionInfo} object. + * @throws IOException In case of any failure behind the scenes. + */ + + public synchronized ConnectionInfo getConnectionInfo() throws IOException { + this.checkConnection(); + return tm.getConnectionInfo(1); + } + + /** + * After a successful connect, one has to authenticate oneself. This method + * can be used to tell which authentication methods are supported by the + * server at a certain stage of the authentication process (for the given + * username). + *

+ * Note 1: the username will only be used if no authentication step was done + * so far (it will be used to ask the server for a list of possible + * authentication methods by sending the initial "none" request). Otherwise, + * this method ignores the user name and returns a cached method list + * (which is based on the information contained in the last negative server response). + *

+ * Note 2: the server may return method names that are not supported by this + * implementation. + *

+ * After a successful authentication, this method must not be called + * anymore. + * + * @param user A String holding the username. + * @return a (possibly emtpy) array holding authentication method names. + * @throws IOException + */ + + public synchronized String[] getRemainingAuthMethods(String user) throws IOException { + if (user == null) { + throw new IllegalArgumentException("user argument may not be NULL!"); + } + + if (tm == null) { + throw new IllegalStateException("Connection is not established!"); + } + + if (authenticated) { + throw new IllegalStateException("Connection is already authenticated!"); + } + + if (am == null) { + am = new AuthenticationManager(tm); + } + + if (cm == null) { + cm = new ChannelManager(tm); + } + + final Set remainingMethods = am.getRemainingMethods(user); + return remainingMethods.toArray(new String[remainingMethods.size()]); + } + + /** + * Determines if the authentication phase is complete. Can be called at any + * time. + * + * @return true if no further authentication steps are + * needed. + */ + + public synchronized boolean isAuthenticationComplete() { + return authenticated; + } + + /** + * Returns true if there was at least one failed authentication request and + * the last failed authentication request was marked with "partial success" + * by the server. This is only needed in the rare case of SSH-2 server setups + * that cannot be satisfied with a single successful authentication request + * (i.e., multiple authentication steps are needed.) + *

+ * If you are interested in the details, then have a look at RFC4252. + * + * @return if the there was a failed authentication step and the last one + * was marked as a "partial success". + */ + + public synchronized boolean isAuthenticationPartialSuccess() { + if (am == null) { + return false; + } + + return am.getPartialSuccess(); + } + + /** + * Checks if a specified authentication method is available. This method is + * actually just a wrapper for {@link #getRemainingAuthMethods(String) + * getRemainingAuthMethods()}. + * + * @param user A String holding the username. + * @param method An authentication method name (e.g., "publickey", "password", + * "keyboard-interactive") as specified by the SSH-2 standard. + * @return if the specified authentication method is currently available. + * @throws IOException + */ + + public synchronized boolean isAuthMethodAvailable(String user, String method) throws IOException { + String methods[] = getRemainingAuthMethods(user); + + for (final String m : methods) { + if (m.compareTo(method) == 0) { + return true; + } + } + + return false; + } + + private SecureRandom getOrCreateSecureRND() { + if (generator == null) { + generator = new SecureRandom(); + } + + return generator; + } + + /** + * Open a new {@link Session} on this connection. Works only after one has passed + * successfully the authentication step. There is no limit on the number of + * concurrent sessions. + * + * @return A {@link Session} object. + * @throws IOException + */ + + public synchronized Session openSession() throws IOException { + this.checkConnection(); + return new Session(cm, getOrCreateSecureRND()); + } + + /** + * Send an SSH_MSG_IGNORE packet. This method will generate a random data attribute + * (length between 0 (invlusive) and 16 (exclusive) bytes, contents are random bytes). + *

+ * This method must only be called once the connection is established. + * + * @throws IOException + */ + + public synchronized void sendIgnorePacket() throws IOException { + SecureRandom rnd = getOrCreateSecureRND(); + byte[] data = new byte[rnd.nextInt(16)]; + rnd.nextBytes(data); + sendIgnorePacket(data); + } + + /** + * Send an SSH_MSG_IGNORE packet with the given data attribute. + *

+ * This method must only be called once the connection is established. + * + * @throws IOException + */ + + public synchronized void sendIgnorePacket(byte[] data) throws IOException { + this.checkConnection(); + PacketIgnore pi = new PacketIgnore(data); + tm.sendMessage(pi.getPayload()); + } + + /** + * Controls whether compression is used on the link or not. + */ + + public synchronized void setCompression(String[] algorithms) { + CompressionFactory.checkCompressorList(algorithms); + cryptoWishList.c2s_comp_algos = algorithms; + } + + public synchronized void enableCompression() { + cryptoWishList.c2s_comp_algos = CompressionFactory.getDefaultCompressorList(); + cryptoWishList.s2c_comp_algos = CompressionFactory.getDefaultCompressorList(); + } + + public synchronized void disableCompression() { + cryptoWishList.c2s_comp_algos = new String[] {"none"}; + cryptoWishList.s2c_comp_algos = new String[] {"none"}; + } + + /** + * Unless you know what you are doing, you will never need this. + */ + + public synchronized void setClient2ServerCiphers(final String[] ciphers) { + if ((ciphers == null) || (ciphers.length == 0)) { + throw new IllegalArgumentException(); + } + + BlockCipherFactory.checkCipherList(ciphers); + cryptoWishList.c2s_enc_algos = ciphers; + } + + /** + * Unless you know what you are doing, you will never need this. + */ + + public synchronized void setClient2ServerMACs(final String[] macs) { + MAC.checkMacList(macs); + cryptoWishList.c2s_mac_algos = macs; + } + + /** + * Sets the parameters for the diffie-hellman group exchange. Unless you + * know what you are doing, you will never need this. Default values are + * defined in the {@link DHGexParameters} class. + * + * @param dgp {@link DHGexParameters}, non null. + */ + + public synchronized void setDHGexParameters(DHGexParameters dgp) { + if (dgp == null) { + throw new IllegalArgumentException(); + } + + dhgexpara = dgp; + } + + /** + * Unless you know what you are doing, you will never need this. + */ + + public synchronized void setServer2ClientCiphers(final String[] ciphers) { + BlockCipherFactory.checkCipherList(ciphers); + cryptoWishList.s2c_enc_algos = ciphers; + } + + /** + * Unless you know what you are doing, you will never need this. + */ + + public synchronized void setServer2ClientMACs(final String[] macs) { + MAC.checkMacList(macs); + cryptoWishList.s2c_mac_algos = macs; + } + + /** + * Define the set of allowed server host key algorithms to be used for + * the following key exchange operations. + *

+ * Unless you know what you are doing, you will never need this. + * + * @param algos An array of allowed server host key algorithms. + * SSH-2 defines ssh-dss and ssh-rsa. + * The entries of the array must be ordered after preference, i.e., + * the entry at index 0 is the most preferred one. You must specify + * at least one entry. + */ + + public synchronized void setServerHostKeyAlgorithms(final String[] algos) { + KexManager.checkServerHostkeyAlgorithmsList(algos); + cryptoWishList.serverHostKeyAlgorithms = algos; + } + + /** + * Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm) on the underlying socket. + *

+ * Can be called at any time. If the connection has not yet been established + * then the passed value will be stored and set after the socket has been set up. + * The default value that will be used is false. + * + * @param enable the argument passed to the Socket.setTCPNoDelay() method. + * @throws IOException + */ + + public synchronized void setTCPNoDelay(boolean enable) throws IOException { + tcpNoDelay = enable; + + if (tm != null) { + tm.setTcpNoDelay(enable); + } + } + + /** + * Used to tell the library that the connection shall be established through + * a proxy server. It only makes sense to call this method before calling + * the {@link #connect() connect()} method. + *

+ * At the moment, only HTTP proxies are supported. + *

+ * Note: This method can be called any number of times. The + * {@link #connect() connect()} method will use the value set in the last + * preceding invocation of this method. + * + * @see HTTPProxyData + * + * @param proxy + * Connection information about the proxy. If null, + * then no proxy will be used (non surprisingly, this is also the + * default). + */ + + public synchronized void setProxyData(HTTPProxyData proxy) { + this.proxy = proxy; + } + + /** + * Request a remote port forwarding. + * If successful, then forwarded connections will be redirected to the given target address. + * You can cancle a requested remote port forwarding by calling + * {@link #cancelRemotePortForwarding(int) cancelRemotePortForwarding()}. + *

+ * A call of this method will block until the peer either agreed or disagreed to your request- + *

+ * Note 1: this method typically fails if you + *

+ *

+ * Note 2: (from the openssh man page): By default, the listening socket on the server will be + * bound to the loopback interface only. This may be overriden by specifying a bind address. + * Specifying a remote bind address will only succeed if the server's GatewayPorts option + * is enabled (see sshd_config(5)). + * + * @param bindAddress address to bind to on the server: + *

+ * @param bindPort port number to bind on the server (must be > 0) + * @param targetAddress the target address (IP or hostname) + * @param targetPort the target port + * @throws IOException + */ + + public synchronized void requestRemotePortForwarding(String bindAddress, int bindPort, String targetAddress, + int targetPort) throws IOException { + this.checkConnection(); + + if ((bindAddress == null) || (targetAddress == null) || (bindPort <= 0) || (targetPort <= 0)) { + throw new IllegalArgumentException(); + } + + cm.requestGlobalForward(bindAddress, bindPort, targetAddress, targetPort); + } + + /** + * Cancel an earlier requested remote port forwarding. + * Currently active forwardings will not be affected (e.g., disrupted). + * Note that further connection forwarding requests may be received until + * this method has returned. + * + * @param bindPort the allocated port number on the server + * @throws IOException if the remote side refuses the cancel request or another low + * level error occurs (e.g., the underlying connection is closed) + */ + + public synchronized void cancelRemotePortForwarding(int bindPort) throws IOException { + this.checkConnection(); + cm.requestCancelGlobalForward(bindPort); + } + + /** + * Provide your own instance of SecureRandom. Can be used, e.g., if you + * want to seed the used SecureRandom generator manually. + *

+ * The SecureRandom instance is used during key exchanges, public key authentication, + * x11 cookie generation and the like. + * + * @param rnd a SecureRandom instance + */ + + public synchronized void setSecureRandom(SecureRandom rnd) { + if (rnd == null) { + throw new IllegalArgumentException(); + } + + this.generator = rnd; + } + + private void checkConnection() throws IllegalStateException { + if (tm == null) { + throw new IllegalStateException("You need to establish a connection first."); + } + + if (!authenticated) { + throw new IllegalStateException("The connection is not authenticated."); + } + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/ConnectionInfo.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/ConnectionInfo.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +/** + * In most cases you probably do not need the information contained in here. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class ConnectionInfo { + /** + * The used key exchange (KEX) algorithm in the latest key exchange. + */ + public String keyExchangeAlgorithm; + + /** + * The currently used crypto algorithm for packets from to the client to the + * server. + */ + public String clientToServerCryptoAlgorithm; + /** + * The currently used crypto algorithm for packets from to the server to the + * client. + */ + public String serverToClientCryptoAlgorithm; + + /** + * The currently used MAC algorithm for packets from to the client to the + * server. + */ + public String clientToServerMACAlgorithm; + /** + * The currently used MAC algorithm for packets from to the server to the + * client. + */ + public String serverToClientMACAlgorithm; + + /** + * The type of the server host key (currently either "ssh-dss" or + * "ssh-rsa"). + */ + public String serverHostKeyAlgorithm; + + /** + * The server host key that was sent during the latest key exchange. + */ + public byte[] serverHostKey; + + /** + * Number of kex exchanges performed on this connection so far. + */ + public int keyExchangeCounter = 0; +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/ConnectionMonitor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/ConnectionMonitor.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +/** + * A ConnectionMonitor is used to get notified when the + * underlying socket of a connection is closed. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ + +public interface ConnectionMonitor { + /** + * This method is called after the connection's underlying + * socket has been closed. E.g., due to the {@link Connection#close()} request of the + * user, if the peer closed the connection, due to a fatal error during connect() + * (also if the socket cannot be established) or if a fatal error occured on + * an established connection. + *

+ * This is an experimental feature. + *

+ * You MUST NOT make any assumption about the thread that invokes this method. + *

+ * Please note: if the connection is not connected (e.g., there was no successful + * connect() call), then the invocation of {@link Connection#close()} will NOT trigger + * this method. + * + * @see Connection#addConnectionMonitor(ConnectionMonitor) + * + * @param reason Includes an indication why the socket was closed. + */ + public void connectionLost(Throwable reason); +} \ No newline at end of file diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/DHGexParameters.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/DHGexParameters.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +/** + * A DHGexParameters object can be used to specify parameters for + * the diffie-hellman group exchange. + *

+ * Depending on which constructor is used, either the use of a + * SSH_MSG_KEX_DH_GEX_REQUEST or SSH_MSG_KEX_DH_GEX_REQUEST_OLD + * can be forced. + * + * @see Connection#setDHGexParameters(DHGexParameters) + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ + +public class DHGexParameters { + private final int min_group_len; + private final int pref_group_len; + private final int max_group_len; + + private static final int MIN_ALLOWED = 1024; + private static final int MAX_ALLOWED = 8192; + + /** + * Same as calling {@link #DHGexParameters(int, int, int) DHGexParameters(1024, 1024, 4096)}. + * This is also the default used by the Connection class. + * + */ + public DHGexParameters() { + this(1024, 1024, 4096); + } + + /** + * This constructor can be used to force the sending of a + * SSH_MSG_KEX_DH_GEX_REQUEST_OLD request. + * Internally, the minimum and maximum group lengths will + * be set to zero. + * + * @param pref_group_len has to be >= 1024 and <= 8192 + */ + public DHGexParameters(int pref_group_len) { + if ((pref_group_len < MIN_ALLOWED) || (pref_group_len > MAX_ALLOWED)) + throw new IllegalArgumentException("pref_group_len out of range!"); + + this.pref_group_len = pref_group_len; + this.min_group_len = 0; + this.max_group_len = 0; + } + + /** + * This constructor can be used to force the sending of a + * SSH_MSG_KEX_DH_GEX_REQUEST request. + *

+ * Note: older OpenSSH servers don't understand this request, in which + * case you should use the {@link #DHGexParameters(int)} constructor. + *

+ * All values have to be >= 1024 and <= 8192. Furthermore, + * min_group_len <= pref_group_len <= max_group_len. + * + * @param min_group_len + * @param pref_group_len + * @param max_group_len + */ + public DHGexParameters(int min_group_len, int pref_group_len, int max_group_len) { + if ((min_group_len < MIN_ALLOWED) || (min_group_len > MAX_ALLOWED)) + throw new IllegalArgumentException("min_group_len out of range!"); + + if ((pref_group_len < MIN_ALLOWED) || (pref_group_len > MAX_ALLOWED)) + throw new IllegalArgumentException("pref_group_len out of range!"); + + if ((max_group_len < MIN_ALLOWED) || (max_group_len > MAX_ALLOWED)) + throw new IllegalArgumentException("max_group_len out of range!"); + + if ((pref_group_len < min_group_len) || (pref_group_len > max_group_len)) + throw new IllegalArgumentException("pref_group_len is incompatible with min and max!"); + + if (max_group_len < min_group_len) + throw new IllegalArgumentException("max_group_len must not be smaller than min_group_len!"); + + this.min_group_len = min_group_len; + this.pref_group_len = pref_group_len; + this.max_group_len = max_group_len; + } + + /** + * Get the maximum group length. + * + * @return the maximum group length, may be zero if + * SSH_MSG_KEX_DH_GEX_REQUEST_OLD should be requested + */ + public int getMax_group_len() { + return max_group_len; + } + + /** + * Get the minimum group length. + * + * @return minimum group length, may be zero if + * SSH_MSG_KEX_DH_GEX_REQUEST_OLD should be requested + */ + public int getMin_group_len() { + return min_group_len; + } + + /** + * Get the preferred group length. + * + * @return the preferred group length + */ + public int getPref_group_len() { + return pref_group_len; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/DynamicPortForwarder.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/DynamicPortForwarder.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,63 @@ +/* + * 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; + +import java.io.IOException; +import java.net.InetSocketAddress; + +import ch.ethz.ssh2.channel.ChannelManager; +import ch.ethz.ssh2.channel.DynamicAcceptThread; + +/** + * A DynamicPortForwarder forwards TCP/IP connections to a local + * port via the secure tunnel to another host which is selected via the + * SOCKS protocol. Checkout {@link Connection#createDynamicPortForwarder(int)} + * on how to create one. + * + * @author Kenny Root + * @version $Id: $ + */ +public class DynamicPortForwarder { + ChannelManager cm; + + DynamicAcceptThread dat; + + DynamicPortForwarder(ChannelManager cm, int local_port) + throws IOException { + this.cm = cm; + dat = new DynamicAcceptThread(cm, local_port); + dat.setDaemon(true); + dat.start(); + } + + DynamicPortForwarder(ChannelManager cm, InetSocketAddress addr) throws IOException { + this.cm = cm; + dat = new DynamicAcceptThread(cm, addr); + dat.setDaemon(true); + dat.start(); + } + + /** + * Stop TCP/IP forwarding of newly arriving connections. + * + * @throws IOException + */ + public void close() throws IOException { + dat.stopWorking(); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/HTTPProxyData.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/HTTPProxyData.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +/** + * A HTTPProxyData object is used to specify the needed connection data + * to connect through a HTTP proxy. + * + * @see Connection#setProxyData(ProxyData) + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ + +public class HTTPProxyData implements ProxyData { + public final String proxyHost; + public final int proxyPort; + public final String proxyUser; + public final String proxyPass; + public final String[] requestHeaderLines; + + /** + * Same as calling {@link #HTTPProxyData(String, int, String, String) HTTPProxyData(proxyHost, proxyPort, null, null)} + * + * @param proxyHost Proxy hostname. + * @param proxyPort Proxy port. + */ + public HTTPProxyData(String proxyHost, int proxyPort) { + this(proxyHost, proxyPort, null, null); + } + + /** + * Same as calling {@link #HTTPProxyData(String, int, String, String, String[]) HTTPProxyData(proxyHost, proxyPort, null, null, null)} + * + * @param proxyHost Proxy hostname. + * @param proxyPort Proxy port. + * @param proxyUser Username for basic authentication (null if no authentication is needed). + * @param proxyPass Password for basic authentication (null if no authentication is needed). + */ + public HTTPProxyData(String proxyHost, int proxyPort, String proxyUser, String proxyPass) { + this(proxyHost, proxyPort, proxyUser, proxyPass, null); + } + + /** + * Connection data for a HTTP proxy. It is possible to specify a username and password + * if the proxy requires basic authentication. Also, additional request header lines can + * be specified (e.g., "User-Agent: CERN-LineMode/2.15 libwww/2.17b3"). + *

+ * Please note: if you want to use basic authentication, then both proxyUser + * and proxyPass must be non-null. + *

+ * Here is an example: + *

+ * + * new HTTPProxyData("192.168.1.1", "3128", "proxyuser", "secret", new String[] {"User-Agent: GanymedBasedClient/1.0", "X-My-Proxy-Option: something"}); + * + * + * @param proxyHost Proxy hostname. + * @param proxyPort Proxy port. + * @param proxyUser Username for basic authentication (null if no authentication is needed). + * @param proxyPass Password for basic authentication (null if no authentication is needed). + * @param requestHeaderLines An array with additional request header lines (without end-of-line markers) + * that have to be sent to the server. May be null. + */ + + public HTTPProxyData(String proxyHost, int proxyPort, String proxyUser, String proxyPass, + String[] requestHeaderLines) { + if (proxyHost == null) + throw new IllegalArgumentException("proxyHost must be non-null"); + + if (proxyPort < 0) + throw new IllegalArgumentException("proxyPort must be non-negative"); + + this.proxyHost = proxyHost; + this.proxyPort = proxyPort; + this.proxyUser = proxyUser; + this.proxyPass = proxyPass; + this.requestHeaderLines = requestHeaderLines; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/HTTPProxyException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/HTTPProxyException.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +import java.io.IOException; + +/** + * May be thrown upon connect() if a HTTP proxy is being used. + * + * @see Connection#connect() + * @see Connection#setProxyData(ProxyData) + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ + +public class HTTPProxyException extends IOException { + private static final long serialVersionUID = 2241537397104426186L; + + public final String httpResponse; + public final int httpErrorCode; + + public HTTPProxyException(String httpResponse, int httpErrorCode) { + super("HTTP Proxy Error (" + httpErrorCode + " " + httpResponse + ")"); + this.httpResponse = httpResponse; + this.httpErrorCode = httpErrorCode; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/InteractiveCallback.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/InteractiveCallback.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +/** + * An InteractiveCallback is used to respond to challenges sent + * by the server if authentication mode "keyboard-interactive" is selected. + * + * @see Connection#authenticateWithKeyboardInteractive(String, + * String[], InteractiveCallback) + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ + +public interface InteractiveCallback { + /** + * This callback interface is used during a "keyboard-interactive" + * authentication. Every time the server sends a set of challenges (however, + * most often just one challenge at a time), this callback function will be + * called to give your application a chance to talk to the user and to + * determine the response(s). + *

+ * Some copy-paste information from the standard: a command line interface + * (CLI) client SHOULD print the name and instruction (if non-empty), adding + * newlines. Then for each prompt in turn, the client SHOULD display the + * prompt and read the user input. The name and instruction fields MAY be + * empty strings, the client MUST be prepared to handle this correctly. The + * prompt field(s) MUST NOT be empty strings. + *

+ * Please refer to draft-ietf-secsh-auth-kbdinteract-XX.txt for the details. + *

+ * Note: clients SHOULD use control character filtering as discussed in + * RFC4251 to avoid attacks by including + * terminal control characters in the fields to be displayed. + * + * @param name + * the name String sent by the server. + * @param instruction + * the instruction String sent by the server. + * @param numPrompts + * number of prompts - may be zero (in this case, you should just + * return a String array of length zero). + * @param prompt + * an array (length numPrompts) of Strings + * @param echo + * an array (length numPrompts) of booleans. For + * each prompt, the corresponding echo field indicates whether or + * not the user input should be echoed as characters are typed. + * @return an array of reponses - the array size must match the parameter + * numPrompts. + */ + public String[] replyToChallenge(String name, String instruction, int numPrompts, String[] prompt, boolean[] echo) + throws Exception; +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/KnownHosts.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/KnownHosts.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,813 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2; + +import java.io.BufferedReader; +import java.io.CharArrayReader; +import java.io.CharArrayWriter; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.security.DigestException; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import ch.ethz.ssh2.crypto.Base64; +import ch.ethz.ssh2.crypto.digest.Digest; +import ch.ethz.ssh2.crypto.digest.HMAC; +import ch.ethz.ssh2.crypto.digest.MD5; +import ch.ethz.ssh2.crypto.digest.SHA1; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPublicKey; +import ch.ethz.ssh2.signature.DSASHA1Verify; +import ch.ethz.ssh2.signature.ECDSASHA2Verify; +import ch.ethz.ssh2.signature.RSASHA1Verify; +import ch.ethz.ssh2.util.StringEncoder; + +/** + * The KnownHosts class is a handy tool to verify received server hostkeys + * based on the information in known_hosts files (the ones used by OpenSSH). + *

+ * It offers basically an in-memory database for known_hosts entries, as well as some + * helper functions. Entries from a known_hosts file can be loaded at construction time. + * It is also possible to add more keys later (e.g., one can parse different + * known_hosts files). + *

+ * It is a thread safe implementation, therefore, you need only to instantiate one + * KnownHosts for your whole application. + * + * @author Christian Plattner + * @version $Id: KnownHosts.java 152 2014-04-28 11:02:23Z dkocher@sudo.ch $ + */ + +public class KnownHosts { + public static final int HOSTKEY_IS_OK = 0; + public static final int HOSTKEY_IS_NEW = 1; + public static final int HOSTKEY_HAS_CHANGED = 2; + + private class KnownHostsEntry { + String[] patterns; + PublicKey key; + + KnownHostsEntry(String[] patterns, PublicKey key) { + this.patterns = patterns; + this.key = key; + } + } + + private final LinkedList publicKeys = new LinkedList(); + + public KnownHosts() { + } + + public KnownHosts(char[] knownHostsData) throws IOException { + initialize(knownHostsData); + } + + public KnownHosts(String knownHosts) throws IOException { + initialize(new File(knownHosts)); + } + + public KnownHosts(File knownHosts) throws IOException { + initialize(knownHosts); + } + + /** + * Adds a single public key entry to the database. Note: this will NOT add the public key + * to any physical file (e.g., "~/.ssh/known_hosts") - use addHostkeyToFile() for that purpose. + * This method is designed to be used in a {@link ServerHostKeyVerifier}. + * + * @param hostnames a list of hostname patterns - at least one most be specified. Check out the + * OpenSSH sshd man page for a description of the pattern matching algorithm. + * @param serverHostKeyAlgorithm as passed to the {@link ServerHostKeyVerifier}. + * @param serverHostKey as passed to the {@link ServerHostKeyVerifier}. + * @throws IOException + */ + public void addHostkey(String hostnames[], String serverHostKeyAlgorithm, byte[] serverHostKey) throws IOException { + if (hostnames == null) { + throw new IllegalArgumentException("hostnames may not be null"); + } + + if ("ssh-rsa".equals(serverHostKeyAlgorithm)) { + RSAPublicKey rpk = RSASHA1Verify.decodeSSHRSAPublicKey(serverHostKey); + + synchronized (publicKeys) { + publicKeys.add(new KnownHostsEntry(hostnames, rpk)); + } + } + else if ("ssh-dss".equals(serverHostKeyAlgorithm)) { + DSAPublicKey dpk = DSASHA1Verify.decodeSSHDSAPublicKey(serverHostKey); + + synchronized (publicKeys) { + publicKeys.add(new KnownHostsEntry(hostnames, dpk)); + } + } + else if (serverHostKeyAlgorithm.startsWith("ecdsa-sha2-")) { + ECPublicKey epk = ECDSASHA2Verify.decodeSSHECDSAPublicKey(serverHostKey); + + synchronized (publicKeys) { + publicKeys.add(new KnownHostsEntry(hostnames, epk)); + } + } + else { + throw new IOException(String.format("Unknown host key type %s", serverHostKeyAlgorithm)); + } + } + + /** + * Parses the given known_hosts data and adds entries to the database. + * + * @param knownHostsData + * @throws IOException + */ + public void addHostkeys(char[] knownHostsData) throws IOException { + initialize(knownHostsData); + } + + /** + * Parses the given known_hosts file and adds entries to the database. + * + * @param knownHosts + * @throws IOException + */ + public void addHostkeys(File knownHosts) throws IOException { + initialize(knownHosts); + } + + /** + * Generate the hashed representation of the given hostname. Useful for adding entries + * with hashed hostnames to a known_hosts file. (see -H option of OpenSSH key-gen). + * + * @param hostname + * @return the hashed representation, e.g., "|1|cDhrv7zwEUV3k71CEPHnhHZezhA=|Xo+2y6rUXo2OIWRAYhBOIijbJMA=" + */ + public static String createHashedHostname(String hostname) throws IOException { + SHA1 sha1 = new SHA1(); + byte[] salt = new byte[sha1.getDigestLength()]; + new SecureRandom().nextBytes(salt); + byte[] hash; + + try { + hash = hmacSha1Hash(salt, hostname); + } + catch (IOException e) { + throw new IOException(e); + } + + String base64_salt = new String(Base64.encode(salt)); + String base64_hash = new String(Base64.encode(hash)); + return String.format("|1|%s|%s", base64_salt, base64_hash); + } + + private static byte[] hmacSha1Hash(byte[] salt, String hostname) throws IOException { + SHA1 sha1 = new SHA1(); + + if (salt.length != sha1.getDigestLength()) { + throw new IllegalArgumentException("Salt has wrong length (" + salt.length + ")"); + } + + try { + HMAC hmac = new HMAC(sha1, salt, salt.length); + hmac.update(StringEncoder.GetBytes(hostname)); + byte[] dig = new byte[hmac.getDigestLength()]; + hmac.digest(dig); + return dig; + } + catch (DigestException e) { + throw new IOException(e); + } + } + + private boolean checkHashed(String entry, String hostname) { + if (entry.startsWith("|1|") == false) { + return false; + } + + int delim_idx = entry.indexOf('|', 3); + + if (delim_idx == -1) { + return false; + } + + String salt_base64 = entry.substring(3, delim_idx); + String hash_base64 = entry.substring(delim_idx + 1); + byte[] salt; + byte[] hash; + + try { + salt = Base64.decode(salt_base64.toCharArray()); + hash = Base64.decode(hash_base64.toCharArray()); + } + catch (IOException e) { + return false; + } + + SHA1 sha1 = new SHA1(); + + if (salt.length != sha1.getDigestLength()) { + return false; + } + + byte[] dig = new byte[0]; + + try { + dig = hmacSha1Hash(salt, hostname); + } + catch (IOException e) { + return false; + } + + for (int i = 0; i < dig.length; i++) { + if (dig[i] != hash[i]) { + return false; + } + } + + return true; + } + + private int checkKey(String remoteHostname, PublicKey remoteKey) { + int result = HOSTKEY_IS_NEW; + + synchronized (publicKeys) { + for (KnownHostsEntry ke : publicKeys) { + if (hostnameMatches(ke.patterns, remoteHostname) == false) { + continue; + } + + boolean res = matchKeys(ke.key, remoteKey); + + if (res == true) { + return HOSTKEY_IS_OK; + } + + result = HOSTKEY_HAS_CHANGED; + } + } + + return result; + } + + private List getAllKeys(String hostname) { + List keys = new ArrayList(); + + synchronized (publicKeys) { + for (KnownHostsEntry ke : publicKeys) { + if (hostnameMatches(ke.patterns, hostname) == false) { + continue; + } + + keys.add(ke.key); + } + } + + return keys; + } + + /** + * Try to find the preferred order of hostkey algorithms for the given hostname. + * Based on the type of hostkey that is present in the internal database + * (i.e., either ssh-rsa or ssh-dss) + * an ordered list of hostkey algorithms is returned which can be passed + * to Connection.setServerHostKeyAlgorithms. + * + * @param hostname + * @return null if no key for the given hostname is present or + * there are keys of multiple types present for the given hostname. Otherwise, + * an array with hostkey algorithms is returned (i.e., an array of length 2). + */ + public String[] getPreferredServerHostkeyAlgorithmOrder(String hostname) { + String[] algos = recommendHostkeyAlgorithms(hostname); + + if (algos != null) { + return algos; + } + + InetAddress[] ipAdresses; + + try { + ipAdresses = InetAddress.getAllByName(hostname); + } + catch (UnknownHostException e) { + return null; + } + + for (int i = 0; i < ipAdresses.length; i++) { + algos = recommendHostkeyAlgorithms(ipAdresses[i].getHostAddress()); + + if (algos != null) { + return algos; + } + } + + return null; + } + + private boolean hostnameMatches(String[] hostpatterns, String hostname) { + boolean isMatch = false; + boolean negate; + hostname = hostname.toLowerCase(); + + for (int k = 0; k < hostpatterns.length; k++) { + if (hostpatterns[k] == null) { + continue; + } + + String pattern; + + /* In contrast to OpenSSH we also allow negated hash entries (as well as hashed + * entries in lines with multiple entries). + */ + + if ((hostpatterns[k].length() > 0) && (hostpatterns[k].charAt(0) == '!')) { + pattern = hostpatterns[k].substring(1); + negate = true; + } + else { + pattern = hostpatterns[k]; + negate = false; + } + + /* Optimize, no need to check this entry */ + + if ((isMatch) && (negate == false)) { + continue; + } + + /* Now compare */ + + if (pattern.charAt(0) == '|') { + if (checkHashed(pattern, hostname)) { + if (negate) { + return false; + } + + isMatch = true; + } + } + else { + pattern = pattern.toLowerCase(); + + if ((pattern.indexOf('?') != -1) || (pattern.indexOf('*') != -1)) { + if (pseudoRegex(pattern.toCharArray(), 0, hostname.toCharArray(), 0)) { + if (negate) { + return false; + } + + isMatch = true; + } + } + else if (pattern.compareTo(hostname) == 0) { + if (negate) { + return false; + } + + isMatch = true; + } + } + } + + return isMatch; + } + + private void initialize(char[] knownHostsData) throws IOException { + BufferedReader br = new BufferedReader(new CharArrayReader(knownHostsData)); + + while (true) { + String line = br.readLine(); + + if (line == null) { + break; + } + + line = line.trim(); + + if (line.startsWith("#")) { + continue; + } + + String[] arr = line.split(" "); + + if (arr.length >= 3) { + if ((arr[1].compareTo("ssh-rsa") == 0) || + (arr[1].compareTo("ssh-dss") == 0) || + (arr[1].startsWith("ecdsa-sha2-"))) { + String[] hostnames = arr[0].split(","); + byte[] msg = Base64.decode(arr[2].toCharArray()); + + try { + addHostkey(hostnames, arr[1], msg); + } + catch (IOException e) { + continue; + } + } + } + } + } + + private void initialize(File knownHosts) throws IOException { + char[] buff = new char[512]; + CharArrayWriter cw = new CharArrayWriter(); + knownHosts.createNewFile(); + FileReader fr = new FileReader(knownHosts); + + while (true) { + int len = fr.read(buff); + + if (len < 0) { + break; + } + + cw.write(buff, 0, len); + } + + fr.close(); + initialize(cw.toCharArray()); + } + + private final boolean matchKeys(PublicKey key1, PublicKey key2) { + return key1.equals(key2); + } + + private boolean pseudoRegex(char[] pattern, int i, char[] match, int j) { + /* This matching logic is equivalent to the one present in OpenSSH 4.1 */ + while (true) { + /* Are we at the end of the pattern? */ + if (pattern.length == i) { + return (match.length == j); + } + + if (pattern[i] == '*') { + i++; + + if (pattern.length == i) { + return true; + } + + if ((pattern[i] != '*') && (pattern[i] != '?')) { + while (true) { + if ((pattern[i] == match[j]) && pseudoRegex(pattern, i + 1, match, j + 1)) { + return true; + } + + j++; + + if (match.length == j) { + return false; + } + } + } + + while (true) { + if (pseudoRegex(pattern, i, match, j)) { + return true; + } + + j++; + + if (match.length == j) { + return false; + } + } + } + + if (match.length == j) { + return false; + } + + if ((pattern[i] != '?') && (pattern[i] != match[j])) { + return false; + } + + i++; + j++; + } + } + + private String[] recommendHostkeyAlgorithms(String hostname) { + String preferredAlgo = null; + List keys = getAllKeys(hostname); + + for (Object key : keys) { + String thisAlgo; + + if (key instanceof RSAPublicKey) { + thisAlgo = "ssh-rsa"; + } + else if (key instanceof DSAPublicKey) { + thisAlgo = "ssh-dss"; + } + else if (key instanceof ECPublicKey) { + ECPublicKey ecPub = (ECPublicKey) key; + String keyType = ECDSASHA2Verify.getCurveName(ecPub.getParams().getCurve().getField().getFieldSize()); + thisAlgo = ECDSASHA2Verify.ECDSA_SHA2_PREFIX + keyType; + } + else { + continue; + } + + if (preferredAlgo != null) { + /* If we find different key types, then return null */ + if (preferredAlgo.compareTo(thisAlgo) != 0) { + return null; + } + } + else { + preferredAlgo = thisAlgo; + } + } + + /* If we did not find anything that we know of, return null */ + + if (preferredAlgo == null) { + return null; + } + + /* Now put the preferred algo to the start of the array. + * You may ask yourself why we do it that way - basically, we could just + * return only the preferred algorithm: since we have a saved key of that + * type (sent earlier from the remote host), then that should work out. + * However, imagine that the server is (for whatever reasons) not offering + * that type of hostkey anymore (e.g., "ssh-rsa" was disabled and + * now "ssh-dss" is being used). If we then do not let the server send us + * a fresh key of the new type, then we shoot ourself into the foot: + * the connection cannot be established and hence the user cannot decide + * if he/she wants to accept the new key. + */ + + if (preferredAlgo.equals("ssh-rsa")) { + return new String[] {"ssh-rsa", "ssh-dss", "ecdsa-sha2-nistp256"}; + } + + return new String[] {"ssh-dss", "ssh-rsa", "ecdsa-sha2-nistp256"}; + } + + /** + * Checks the internal hostkey database for the given hostkey. + * If no matching key can be found, then the hostname is resolved to an IP address + * and the search is repeated using that IP address. + * + * @param hostname the server's hostname, will be matched with all hostname patterns + * @param serverHostKeyAlgorithm type of hostkey, either ssh-rsa or ssh-dss + * @param serverHostKey the key blob + * @return
    + *
  • HOSTKEY_IS_OK: the given hostkey matches an entry for the given hostname
  • + *
  • HOSTKEY_IS_NEW: no entries found for this hostname and this type of hostkey
  • + *
  • HOSTKEY_HAS_CHANGED: hostname is known, but with another key of the same type + * (man-in-the-middle attack?)
  • + *
+ * @throws IOException if the supplied key blob cannot be parsed or does not match the given hostkey type. + */ + public int verifyHostkey(String hostname, String serverHostKeyAlgorithm, byte[] serverHostKey) throws IOException { + PublicKey remoteKey; + + if ("ssh-rsa".equals(serverHostKeyAlgorithm)) { + remoteKey = RSASHA1Verify.decodeSSHRSAPublicKey(serverHostKey); + } + else if ("ssh-dss".equals(serverHostKeyAlgorithm)) { + remoteKey = DSASHA1Verify.decodeSSHDSAPublicKey(serverHostKey); + } + else if (serverHostKeyAlgorithm.startsWith("ecdsa-sha2-")) { + remoteKey = ECDSASHA2Verify.decodeSSHECDSAPublicKey(serverHostKey); + } + else { + throw new IllegalArgumentException("Unknown hostkey type " + serverHostKeyAlgorithm); + } + + int result = checkKey(hostname, remoteKey); + + if (result == HOSTKEY_IS_OK) { + return result; + } + + InetAddress[] ipAdresses; + + try { + ipAdresses = InetAddress.getAllByName(hostname); + } + catch (UnknownHostException e) { + return result; + } + + for (int i = 0; i < ipAdresses.length; i++) { + int newresult = checkKey(ipAdresses[i].getHostAddress(), remoteKey); + + if (newresult == HOSTKEY_IS_OK) { + return newresult; + } + + if (newresult == HOSTKEY_HAS_CHANGED) { + result = HOSTKEY_HAS_CHANGED; + } + } + + return result; + } + + /** + * Adds a single public key entry to the a known_hosts file. + * This method is designed to be used in a {@link ServerHostKeyVerifier}. + * + * @param knownHosts the file where the publickey entry will be appended. + * @param hostnames a list of hostname patterns - at least one most be specified. Check out the + * OpenSSH sshd man page for a description of the pattern matching algorithm. + * @param serverHostKeyAlgorithm as passed to the {@link ServerHostKeyVerifier}. + * @param serverHostKey as passed to the {@link ServerHostKeyVerifier}. + * @throws IOException + */ + public static void addHostkeyToFile(File knownHosts, String[] hostnames, String serverHostKeyAlgorithm, + byte[] serverHostKey) throws IOException { + if ((hostnames == null) || (hostnames.length == 0)) { + throw new IllegalArgumentException("Need at least one hostname specification"); + } + + if ((serverHostKeyAlgorithm == null) || (serverHostKey == null)) { + throw new IllegalArgumentException(); + } + + CharArrayWriter writer = new CharArrayWriter(); + + for (int i = 0; i < hostnames.length; i++) { + if (i != 0) { + writer.write(','); + } + + writer.write(hostnames[i]); + } + + writer.write(' '); + writer.write(serverHostKeyAlgorithm); + writer.write(' '); + writer.write(Base64.encode(serverHostKey)); + writer.write("\n"); + char[] entry = writer.toCharArray(); + RandomAccessFile raf = new RandomAccessFile(knownHosts, "rw"); + long len = raf.length(); + + if (len > 0) { + raf.seek(len - 1); + int last = raf.read(); + + if (last != '\n') { + raf.write('\n'); + } + } + + raf.write(StringEncoder.GetBytes(new String(entry))); + raf.close(); + } + + /** + * Generates a "raw" fingerprint of a hostkey. + * + * @param type either "md5" or "sha1" + * @param keyType either "ssh-rsa" or "ssh-dss" or "ecdsa-sha2..." + * @param hostkey the hostkey + * @return the raw fingerprint + */ + static private byte[] rawFingerPrint(String type, String keyType, byte[] hostkey) throws IOException { + Digest dig; + + if ("md5".equals(type)) { + dig = new MD5(); + } + else if ("sha1".equals(type)) { + dig = new SHA1(); + } + else { + throw new IllegalArgumentException("Unknown hash type " + type); + } + + if ("ssh-rsa".equals(keyType)) { + } + else if ("ssh-dss".equals(keyType)) { + } + else if (keyType.startsWith("ecdsa-sha2-")) { + } + else { + throw new IllegalArgumentException("Unknown key type " + keyType); + } + + if (hostkey == null) { + throw new IllegalArgumentException("hostkey is null"); + } + + dig.update(hostkey); + byte[] res = new byte[dig.getDigestLength()]; + + try { + dig.digest(res); + } + catch (DigestException e) { + throw new IOException(e); + } + + return res; + } + + /** + * Convert a raw fingerprint to hex representation (XX:YY:ZZ...). + * + * @param fingerprint raw fingerprint + * @return the hex representation + */ + static private String rawToHexFingerprint(byte[] fingerprint) { + final char[] alpha = "0123456789abcdef".toCharArray(); + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < fingerprint.length; i++) { + if (i != 0) { + sb.append(':'); + } + + int b = fingerprint[i] & 0xff; + sb.append(alpha[b >> 4]); + sb.append(alpha[b & 15]); + } + + return sb.toString(); + } + + /** + * Convert a raw fingerprint to bubblebabble representation. + * + * @param raw raw fingerprint + * @return the bubblebabble representation + */ + static private String rawToBubblebabbleFingerprint(byte[] raw) { + final char[] v = "aeiouy".toCharArray(); + final char[] c = "bcdfghklmnprstvzx".toCharArray(); + StringBuilder sb = new StringBuilder(); + int seed = 1; + int rounds = (raw.length / 2) + 1; + sb.append('x'); + + for (int i = 0; i < rounds; i++) { + if (((i + 1) < rounds) || ((raw.length) % 2 != 0)) { + sb.append(v[(((raw[2 * i] >> 6) & 3) + seed) % 6]); + sb.append(c[(raw[2 * i] >> 2) & 15]); + sb.append(v[((raw[2 * i] & 3) + (seed / 6)) % 6]); + + if ((i + 1) < rounds) { + sb.append(c[(((raw[(2 * i) + 1])) >> 4) & 15]); + sb.append('-'); + sb.append(c[(((raw[(2 * i) + 1]))) & 15]); + // As long as seed >= 0, seed will be >= 0 afterwards + seed = ((seed * 5) + (((raw[2 * i] & 0xff) * 7) + (raw[(2 * i) + 1] & 0xff))) % 36; + } + } + else { + sb.append(v[seed % 6]); // seed >= 0, therefore index positive + sb.append('x'); + sb.append(v[seed / 6]); + } + } + + sb.append('x'); + return sb.toString(); + } + + /** + * Convert a ssh2 key-blob into a human readable hex fingerprint. + * Generated fingerprints are identical to those generated by OpenSSH. + *

+ * Example fingerprint: d0:cb:76:19:99:5a:03:fc:73:10:70:93:f2:44:63:47. + * + * @param keytype either "ssh-rsa" or "ssh-dss" or "ecdsa-sha2..." + * @param publickey key blob + * @return Hex fingerprint + */ + public static String createHexFingerprint(String keytype, byte[] publickey) throws IOException { + byte[] raw = rawFingerPrint("md5", keytype, publickey); + return rawToHexFingerprint(raw); + } + + /** + * Convert a ssh2 key-blob into a human readable bubblebabble fingerprint. + * The used bubblebabble algorithm (taken from OpenSSH) generates fingerprints + * that are easier to remember for humans. + *

+ * Example fingerprint: xofoc-bubuz-cazin-zufyl-pivuk-biduk-tacib-pybur-gonar-hotat-lyxux. + * + * @param keytype either "ssh-rsa" or "ssh-dss" + * @param publickey key data + * @return Bubblebabble fingerprint + */ + public static String createBubblebabbleFingerprint(String keytype, byte[] publickey) throws IOException { + byte[] raw = rawFingerPrint("sha1", keytype, publickey); + return rawToBubblebabbleFingerprint(raw); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/LocalPortForwarder.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/LocalPortForwarder.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2006-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; + +import ch.ethz.ssh2.channel.ChannelManager; +import ch.ethz.ssh2.channel.LocalAcceptThread; + +/** + * A LocalPortForwarder forwards TCP/IP connections to a local + * port via the secure tunnel to another host (which may or may not be identical + * to the remote SSH-2 server). Checkout {@link Connection#createLocalPortForwarder(int, String, int)} + * on how to create one. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class LocalPortForwarder { + final ChannelManager cm; + + final String host_to_connect; + + final int port_to_connect; + + final LocalAcceptThread lat; + + LocalPortForwarder(ChannelManager cm, int local_port, String host_to_connect, int port_to_connect) + throws IOException { + this.cm = cm; + this.host_to_connect = host_to_connect; + this.port_to_connect = port_to_connect; + lat = new LocalAcceptThread(cm, local_port, host_to_connect, port_to_connect); + lat.setDaemon(true); + lat.start(); + } + + LocalPortForwarder(ChannelManager cm, InetSocketAddress addr, String host_to_connect, int port_to_connect) + throws IOException { + this.cm = cm; + this.host_to_connect = host_to_connect; + this.port_to_connect = port_to_connect; + lat = new LocalAcceptThread(cm, addr, host_to_connect, port_to_connect); + lat.setDaemon(true); + lat.start(); + } + + /** + * Return the local socket address of the {@link ServerSocket} used to accept connections. + * @return + */ + public InetSocketAddress getLocalSocketAddress() { + return (InetSocketAddress) lat.getServerSocket().getLocalSocketAddress(); + } + + /** + * Stop TCP/IP forwarding of newly arriving connections. + * + * @throws IOException + */ + public void close() throws IOException { + lat.stopWorking(); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/LocalStreamForwarder.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/LocalStreamForwarder.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; + +import ch.ethz.ssh2.channel.Channel; +import ch.ethz.ssh2.channel.ChannelManager; + +/** + * A LocalStreamForwarder forwards an Input- and Outputstream + * pair via the secure tunnel to another host (which may or may not be identical + * to the remote SSH-2 server). + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class LocalStreamForwarder { + private ChannelManager cm; + + private Channel cn; + + LocalStreamForwarder(ChannelManager cm, String host_to_connect, int port_to_connect) throws IOException { + this.cm = cm; + cn = cm.openDirectTCPIPChannel(host_to_connect, port_to_connect, + InetAddress.getLocalHost().getHostAddress(), 0); + } + + /** + * @return An InputStream object. + * @throws IOException + */ + public InputStream getInputStream() throws IOException { + return cn.getStdoutStream(); + } + + /** + * Get the OutputStream. Please be aware that the implementation MAY use an + * internal buffer. To make sure that the buffered data is sent over the + * tunnel, you have to call the flush method of the + * OutputStream. To signal EOF, please use the + * close method of the OutputStream. + * + * @return An OutputStream object. + * @throws IOException + */ + public OutputStream getOutputStream() throws IOException { + return cn.getStdinStream(); + } + + /** + * Close the underlying SSH forwarding channel and free up resources. + * You can also use this method to force the shutdown of the underlying + * forwarding channel. Pending output (OutputStream not flushed) will NOT + * be sent. Pending input (InputStream) can still be read. If the shutdown + * operation is already in progress (initiated from either side), then this + * call is a no-op. + * + * @throws IOException + */ + public void close() throws IOException { + cm.closeChannel(cn, "Closed due to user request.", true); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/PacketFormatException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/PacketFormatException.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,13 @@ +package ch.ethz.ssh2; + +import java.io.IOException; + +/** + * @version $Id: PacketFormatException.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public class PacketFormatException extends IOException { + + public PacketFormatException(String message) { + super(message); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/PacketListener.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/PacketListener.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2011 David Kocher. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +/** + * @version $Id: PacketListener.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public interface PacketListener { + void read(String packet); + + void write(String packet); +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/PacketTypeException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/PacketTypeException.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,24 @@ +package ch.ethz.ssh2; + +import java.io.IOException; + +/** + * @version $Id: PacketTypeException.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public class PacketTypeException extends IOException { + + public PacketTypeException() { + } + + public PacketTypeException(final String message) { + super(message); + } + + public PacketTypeException(final int packet) { + super(String.format("The SFTP server sent an unexpected packet type (%d)", packet)); + } + + public PacketTypeException(final int packet, final String message) { + super(String.format("The SFTP server sent an invalid packet type (%d). %s", packet, message)); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/ProxyData.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/ProxyData.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +/** + * An abstract marker interface implemented by all proxy data implementations. + * + * @see HTTPProxyData + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ + +public abstract interface ProxyData { +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/PtySettings.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/PtySettings.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2012-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +/** + * PTY settings for a SSH session. Zero dimension parameters are ignored. The character/row dimensions + * override the pixel dimensions (when nonzero). Pixel dimensions refer to + * the drawable area of the window. The dimension parameters are only + * informational. The encoding of terminal modes (parameter + * terminal_modes) is described in RFC4254. + * + * @author Christian + */ +public class PtySettings { + /** + * TERM environment variable value (e.g., vt100) + */ + public String term; + + /** + * Terminal width, characters (e.g., 80) + */ + public int term_width_characters; + + /** + * Terminal height, rows (e.g., 24) + */ + public int term_height_characters; + + /** + * Terminal width, pixels (e.g., 640) + */ + public int term_width_pixels; + + /** + * Terminal height, pixels (e.g., 480) + */ + public int term_height_pixels; + + /** + * Encoded terminal modes + */ + public byte[] terminal_modes; +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/RequestMismatchException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/RequestMismatchException.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,17 @@ +package ch.ethz.ssh2; + +import java.io.IOException; + +/** + * @version $Id: RequestMismatchException.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public class RequestMismatchException extends IOException { + + public RequestMismatchException() { + super("The server sent an invalid id field."); + } + + public RequestMismatchException(final String message) { + super(message); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/SCPClient.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/SCPClient.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,742 @@ +package ch.ethz.ssh2; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.nio.charset.UnsupportedCharsetException; + +/** + * A very basic SCPClient that can be used to copy files from/to + * the SSH-2 server. On the server side, the "scp" program must be in the PATH. + *

+ * This scp client is thread safe - you can download (and upload) different sets + * of files concurrently without any troubles. The SCPClient is + * actually mapping every request to a distinct {@link ch.ethz.ssh2.Session}. + * + * @author Christian Plattner, plattner@inf.ethz.ch + * @version $Id: SCPClient.java 85 2014-04-07 14:05:09Z dkocher@sudo.ch $ + */ + +public class SCPClient { + Connection conn; + + String charsetName = null; + + /** + * Set the charset used to convert between Java Unicode Strings and byte encodings + * used by the server for paths and file names. + * + * @param charset the name of the charset to be used or null to use the platform's + * default encoding. + * @throws IOException + * @see #getCharset() + */ + public void setCharset(String charset) throws IOException { + if (charset == null) { + charsetName = charset; + return; + } + + try { + Charset.forName(charset); + } + catch (UnsupportedCharsetException e) { + throw new IOException("This charset is not supported", e); + } + + charsetName = charset; + } + + /** + * The currently used charset for filename encoding/decoding. + * + * @return The name of the charset (null if the platform's default charset is being used) + * @see #setCharset(String) + */ + public String getCharset() { + return charsetName; + } + + public class LenNamePair { + public long length; + String filename; + } + + public SCPClient(Connection conn) { + if (conn == null) + throw new IllegalArgumentException("Cannot accept null argument!"); + + this.conn = conn; + } + + protected void readResponse(InputStream is) throws IOException { + int c = is.read(); + + if (c == 0) + return; + + if (c == -1) + throw new IOException("Remote scp terminated unexpectedly."); + + if ((c != 1) && (c != 2)) + throw new IOException("Remote scp sent illegal error code."); + + if (c == 2) + throw new IOException("Remote scp terminated with error."); + + String err = receiveLine(is); + throw new IOException("Remote scp terminated with error (" + err + ")."); + } + + protected String receiveLine(InputStream is) throws IOException { + StringBuilder sb = new StringBuilder(30); + + while (true) { + /* This is a random limit - if your path names are longer, then adjust it */ + if (sb.length() > 8192) + throw new IOException("Remote scp sent a too long line"); + + int c = is.read(); + + if (c < 0) + throw new IOException("Remote scp terminated unexpectedly."); + + if (c == '\n') + break; + + sb.append((char) c); + } + + return sb.toString(); + } + + protected LenNamePair parseCLine(String line) throws IOException { + /* Minimum line: "xxxx y z" ---> 8 chars */ + if (line.length() < 8) + throw new IOException("Malformed C line sent by remote SCP binary, line too short."); + + if ((line.charAt(4) != ' ') || (line.charAt(5) == ' ')) + throw new IOException("Malformed C line sent by remote SCP binary."); + + int length_name_sep = line.indexOf(' ', 5); + + if (length_name_sep == -1) + throw new IOException("Malformed C line sent by remote SCP binary."); + + String length_substring = line.substring(5, length_name_sep); + String name_substring = line.substring(length_name_sep + 1); + + if ((length_substring.length() <= 0) || (name_substring.length() <= 0)) + throw new IOException("Malformed C line sent by remote SCP binary."); + + if ((6 + length_substring.length() + name_substring.length()) != line.length()) + throw new IOException("Malformed C line sent by remote SCP binary."); + + final long len; + + try { + len = Long.parseLong(length_substring); + } + catch (NumberFormatException e) { + throw new IOException("Malformed C line sent by remote SCP binary, cannot parse file length."); + } + + if (len < 0) + throw new IOException("Malformed C line sent by remote SCP binary, illegal file length."); + + LenNamePair lnp = new LenNamePair(); + lnp.length = len; + lnp.filename = name_substring; + return lnp; + } + + /** + * The session for opened for this SCP transfer must be closed using + * SCPOutputStream#close + * + * @param remoteFile + * @param length The size of the file to send + * @param remoteTargetDirectory + * @param mode + * @return + * @throws IOException + */ + public SCPOutputStream put(final String remoteFile, long length, String remoteTargetDirectory, String mode) + throws IOException { + Session sess = null; + + if (null == remoteFile) + throw new IllegalArgumentException("Null argument."); + + if (null == remoteTargetDirectory) + remoteTargetDirectory = ""; + + if (null == mode) + mode = "0600"; + + if (mode.length() != 4) + throw new IllegalArgumentException("Invalid mode."); + + for (int i = 0; i < mode.length(); i++) + if (Character.isDigit(mode.charAt(i)) == false) + throw new IllegalArgumentException("Invalid mode."); + + remoteTargetDirectory = (remoteTargetDirectory.length() > 0) ? remoteTargetDirectory : "."; + String cmd = "scp -t -d \"" + remoteTargetDirectory + "\""; + sess = conn.openSession(); + sess.execCommand(cmd, charsetName); + return new SCPOutputStream(this, sess, remoteFile, length, mode); + } + + /** + * The session for opened for this SCP transfer must be closed using + * SCPInputStream#close + * + * @param remoteFile + * @return + * @throws IOException + */ + public SCPInputStream get(final String remoteFile) throws IOException { + Session sess = null; + + if (null == remoteFile) + throw new IllegalArgumentException("Null argument."); + + if (remoteFile.length() == 0) + throw new IllegalArgumentException("Cannot accept empty filename."); + + String cmd = "scp -f"; + cmd += (" \"" + remoteFile + "\""); + sess = conn.openSession(); + sess.execCommand(cmd, charsetName); + return new SCPInputStream(this, sess); + } + private void sendBytes(Session sess, byte[] data, String fileName, String mode) throws IOException { + OutputStream os = sess.getStdin(); + InputStream is = new BufferedInputStream(sess.getStdout(), 512); + readResponse(is); + String cline = "C" + mode + " " + data.length + " " + fileName + "\n"; + os.write(cline.getBytes("ISO-8859-1")); + os.flush(); + readResponse(is); + os.write(data, 0, data.length); + os.write(0); + os.flush(); + readResponse(is); + os.write("E\n".getBytes("ISO-8859-1")); + os.flush(); + } + + private void sendFiles(Session sess, String[] files, String[] remoteFiles, String mode) throws IOException { + byte[] buffer = new byte[8192]; + OutputStream os = new BufferedOutputStream(sess.getStdin(), 40000); + InputStream is = new BufferedInputStream(sess.getStdout(), 512); + readResponse(is); + + for (int i = 0; i < files.length; i++) { + File f = new File(files[i]); + long remain = f.length(); + String remoteName; + + if ((remoteFiles != null) && (remoteFiles.length > i) && (remoteFiles[i] != null)) + remoteName = remoteFiles[i]; + else + remoteName = f.getName(); + + String cline = "C" + mode + " " + remain + " " + remoteName + "\n"; + os.write(cline.getBytes("ISO-8859-1")); + os.flush(); + readResponse(is); + FileInputStream fis = null; + + try { + fis = new FileInputStream(f); + + while (remain > 0) { + int trans; + + if (remain > buffer.length) + trans = buffer.length; + else + trans = (int) remain; + + if (fis.read(buffer, 0, trans) != trans) + throw new IOException("Cannot read enough from local file " + files[i]); + + os.write(buffer, 0, trans); + remain -= trans; + } + } + finally { + if (fis != null) + fis.close(); + } + + os.write(0); + os.flush(); + readResponse(is); + } + + os.write("E\n".getBytes("ISO-8859-1")); + os.flush(); + } + + private void receiveFiles(Session sess, OutputStream[] targets) throws IOException { + byte[] buffer = new byte[8192]; + OutputStream os = new BufferedOutputStream(sess.getStdin(), 512); + InputStream is = new BufferedInputStream(sess.getStdout(), 40000); + os.write(0x0); + os.flush(); + + for (int i = 0; i < targets.length; i++) { + LenNamePair lnp = null; + + while (true) { + int c = is.read(); + + if (c < 0) + throw new IOException("Remote scp terminated unexpectedly."); + + String line = receiveLine(is); + + if (c == 'T') { + /* Ignore modification times */ + continue; + } + + if ((c == 1) || (c == 2)) + throw new IOException("Remote SCP error: " + line); + + if (c == 'C') { + lnp = parseCLine(line); + break; + } + + throw new IOException("Remote SCP error: " + ((char) c) + line); + } + + os.write(0x0); + os.flush(); + long remain = lnp.length; + + while (remain > 0) { + int trans; + + if (remain > buffer.length) + trans = buffer.length; + else + trans = (int) remain; + + int this_time_received = is.read(buffer, 0, trans); + + if (this_time_received < 0) { + throw new IOException("Remote scp terminated connection unexpectedly"); + } + + targets[i].write(buffer, 0, this_time_received); + remain -= this_time_received; + } + + readResponse(is); + os.write(0x0); + os.flush(); + } + } + + private void receiveFiles(Session sess, String[] files, String target) throws IOException { + byte[] buffer = new byte[8192]; + OutputStream os = new BufferedOutputStream(sess.getStdin(), 512); + InputStream is = new BufferedInputStream(sess.getStdout(), 40000); + os.write(0x0); + os.flush(); + + for (int i = 0; i < files.length; i++) { + LenNamePair lnp = null; + + while (true) { + int c = is.read(); + + if (c < 0) + throw new IOException("Remote scp terminated unexpectedly."); + + String line = receiveLine(is); + + if (c == 'T') { + /* Ignore modification times */ + continue; + } + + if ((c == 1) || (c == 2)) + throw new IOException("Remote SCP error: " + line); + + if (c == 'C') { + lnp = parseCLine(line); + break; + } + + throw new IOException("Remote SCP error: " + ((char) c) + line); + } + + os.write(0x0); + os.flush(); + File f = new File(target + File.separatorChar + lnp.filename); + FileOutputStream fop = null; + + try { + fop = new FileOutputStream(f); + long remain = lnp.length; + + while (remain > 0) { + int trans; + + if (remain > buffer.length) + trans = buffer.length; + else + trans = (int) remain; + + int this_time_received = is.read(buffer, 0, trans); + + if (this_time_received < 0) { + throw new IOException("Remote scp terminated connection unexpectedly"); + } + + fop.write(buffer, 0, this_time_received); + remain -= this_time_received; + } + } + finally { + if (fop != null) + fop.close(); + } + + readResponse(is); + os.write(0x0); + os.flush(); + } + } + + /** + * Copy a local file to a remote directory, uses mode 0600 when creating the + * file on the remote side. + * + * @param localFile + * Path and name of local file. + * @param remoteTargetDirectory + * Remote target directory. Use an empty string to specify the + * default directory. + * + * @throws IOException + */ + public void put(String localFile, String remoteTargetDirectory) throws IOException { + put(new String[] { localFile }, remoteTargetDirectory, "0600"); + } + + /** + * Copy a set of local files to a remote directory, uses mode 0600 when + * creating files on the remote side. + * + * @param localFiles + * Paths and names of local file names. + * @param remoteTargetDirectory + * Remote target directory. Use an empty string to specify the + * default directory. + * + * @throws IOException + */ + + public void put(String[] localFiles, String remoteTargetDirectory) throws IOException { + put(localFiles, remoteTargetDirectory, "0600"); + } + + /** + * Copy a local file to a remote directory, uses the specified mode when + * creating the file on the remote side. + * + * @param localFile + * Path and name of local file. + * @param remoteTargetDirectory + * Remote target directory. Use an empty string to specify the + * default directory. + * @param mode + * a four digit string (e.g., 0644, see "man chmod", "man open") + * @throws IOException + */ + public void put(String localFile, String remoteTargetDirectory, String mode) throws IOException { + put(new String[] { localFile }, remoteTargetDirectory, mode); + } + + /** + * Copy a local file to a remote directory, uses the specified mode and + * remote filename when creating the file on the remote side. + * + * @param localFile + * Path and name of local file. + * @param remoteFileName + * The name of the file which will be created in the remote + * target directory. + * @param remoteTargetDirectory + * Remote target directory. Use an empty string to specify the + * default directory. + * @param mode + * a four digit string (e.g., 0644, see "man chmod", "man open") + * @throws IOException + */ + public void put(String localFile, String remoteFileName, String remoteTargetDirectory, String mode) + throws IOException { + put(new String[] { localFile }, new String[] { remoteFileName }, remoteTargetDirectory, mode); + } + + /** + * Create a remote file and copy the contents of the passed byte array into + * it. Uses mode 0600 for creating the remote file. + * + * @param data + * the data to be copied into the remote file. + * @param remoteFileName + * The name of the file which will be created in the remote + * target directory. + * @param remoteTargetDirectory + * Remote target directory. Use an empty string to specify the + * default directory. + * @throws IOException + */ + + public void put(byte[] data, String remoteFileName, String remoteTargetDirectory) throws IOException { + put(data, remoteFileName, remoteTargetDirectory, "0600"); + } + + /** + * Create a remote file and copy the contents of the passed byte array into + * it. The method use the specified mode when creating the file on the + * remote side. + * + * @param data + * the data to be copied into the remote file. + * @param remoteFileName + * The name of the file which will be created in the remote + * target directory. + * @param remoteTargetDirectory + * Remote target directory. Use an empty string to specify the + * default directory. + * @param mode + * a four digit string (e.g., 0644, see "man chmod", "man open") + * @throws IOException + */ + public void put(byte[] data, String remoteFileName, String remoteTargetDirectory, String mode) throws IOException { + Session sess = null; + + if ((remoteFileName == null) || (remoteTargetDirectory == null) || (mode == null)) + throw new IllegalArgumentException("Null argument."); + + if (mode.length() != 4) + throw new IllegalArgumentException("Invalid mode."); + + for (int i = 0; i < mode.length(); i++) + if (Character.isDigit(mode.charAt(i)) == false) + throw new IllegalArgumentException("Invalid mode."); + + remoteTargetDirectory = remoteTargetDirectory.trim(); + remoteTargetDirectory = (remoteTargetDirectory.length() > 0) ? remoteTargetDirectory : "."; + String cmd = "scp -t -d " + remoteTargetDirectory; + + try { + sess = conn.openSession(); + sess.execCommand(cmd); + sendBytes(sess, data, remoteFileName, mode); + } + catch (IOException e) { + throw(IOException) new IOException("Error during SCP transfer.").initCause(e); + } + finally { + if (sess != null) + sess.close(); + } + } + + /** + * Copy a set of local files to a remote directory, uses the specified mode + * when creating the files on the remote side. + * + * @param localFiles + * Paths and names of the local files. + * @param remoteTargetDirectory + * Remote target directory. Use an empty string to specify the + * default directory. + * @param mode + * a four digit string (e.g., 0644, see "man chmod", "man open") + * @throws IOException + */ + public void put(String[] localFiles, String remoteTargetDirectory, String mode) throws IOException { + put(localFiles, null, remoteTargetDirectory, mode); + } + + public void put(String[] localFiles, String[] remoteFiles, String remoteTargetDirectory, String mode) + throws IOException { + Session sess = null; + + /* + * remoteFiles may be null, indicating that the local filenames shall be + * used + */ + + if ((localFiles == null) || (remoteTargetDirectory == null) || (mode == null)) + throw new IllegalArgumentException("Null argument."); + + if (mode.length() != 4) + throw new IllegalArgumentException("Invalid mode."); + + for (int i = 0; i < mode.length(); i++) + if (Character.isDigit(mode.charAt(i)) == false) + throw new IllegalArgumentException("Invalid mode."); + + if (localFiles.length == 0) + return; + + remoteTargetDirectory = remoteTargetDirectory.trim(); + remoteTargetDirectory = (remoteTargetDirectory.length() > 0) ? remoteTargetDirectory : "."; + String cmd = "scp -t -d " + remoteTargetDirectory; + + for (int i = 0; i < localFiles.length; i++) { + if (localFiles[i] == null) + throw new IllegalArgumentException("Cannot accept null filename."); + } + + try { + sess = conn.openSession(); + sess.execCommand(cmd); + sendFiles(sess, localFiles, remoteFiles, mode); + } + catch (IOException e) { + throw(IOException) new IOException("Error during SCP transfer.").initCause(e); + } + finally { + if (sess != null) + sess.close(); + } + } + + /** + * Download a file from the remote server to a local directory. + * + * @param remoteFile + * Path and name of the remote file. + * @param localTargetDirectory + * Local directory to put the downloaded file. + * + * @throws IOException + */ + public void get(String remoteFile, String localTargetDirectory) throws IOException { + get(new String[] { remoteFile }, localTargetDirectory); + } + + /** + * Download a file from the remote server and pipe its contents into an + * OutputStream. Please note that, to enable flexible usage + * of this method, the OutputStream will not be closed nor + * flushed. + * + * @param remoteFile + * Path and name of the remote file. + * @param target + * OutputStream where the contents of the file will be sent to. + * @throws IOException + */ + public void get(String remoteFile, OutputStream target) throws IOException { + get(new String[] { remoteFile }, new OutputStream[] { target }); + } + + private void get(String remoteFiles[], OutputStream[] targets) throws IOException { + Session sess = null; + + if ((remoteFiles == null) || (targets == null)) + throw new IllegalArgumentException("Null argument."); + + if (remoteFiles.length != targets.length) + throw new IllegalArgumentException("Length of arguments does not match."); + + if (remoteFiles.length == 0) + return; + + String cmd = "scp -f"; + + for (int i = 0; i < remoteFiles.length; i++) { + if (remoteFiles[i] == null) + throw new IllegalArgumentException("Cannot accept null filename."); + + String tmp = remoteFiles[i].trim(); + + if (tmp.length() == 0) + throw new IllegalArgumentException("Cannot accept empty filename."); + + cmd += (" " + tmp); + } + + try { + sess = conn.openSession(); + sess.execCommand(cmd); + receiveFiles(sess, targets); + } + catch (IOException e) { + throw(IOException) new IOException("Error during SCP transfer.").initCause(e); + } + finally { + if (sess != null) + sess.close(); + } + } + + /** + * Download a set of files from the remote server to a local directory. + * + * @param remoteFiles + * Paths and names of the remote files. + * @param localTargetDirectory + * Local directory to put the downloaded files. + * + * @throws IOException + */ + public void get(String remoteFiles[], String localTargetDirectory) throws IOException { + Session sess = null; + + if ((remoteFiles == null) || (localTargetDirectory == null)) + throw new IllegalArgumentException("Null argument."); + + if (remoteFiles.length == 0) + return; + + String cmd = "scp -f"; + + for (int i = 0; i < remoteFiles.length; i++) { + if (remoteFiles[i] == null) + throw new IllegalArgumentException("Cannot accept null filename."); + + String tmp = remoteFiles[i].trim(); + + if (tmp.length() == 0) + throw new IllegalArgumentException("Cannot accept empty filename."); + + cmd += (" " + tmp); + } + + try { + sess = conn.openSession(); + sess.execCommand(cmd); + receiveFiles(sess, remoteFiles, localTargetDirectory); + } + catch (IOException e) { + throw(IOException) new IOException("Error during SCP transfer.").initCause(e); + } + finally { + if (sess != null) + sess.close(); + } + } +} + + diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/SCPInputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/SCPInputStream.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2011 David Kocher. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * @version $Id: SCPInputStream.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public class SCPInputStream extends BufferedInputStream { + private Session session; + + /** + * Bytes remaining to be read from the stream + */ + private long remaining; + + public SCPInputStream(SCPClient client, Session session) throws IOException { + super(session.getStdout()); + this.session = session; + OutputStream os = new BufferedOutputStream(session.getStdin(), 512); + os.write(0x0); + os.flush(); + final SCPClient.LenNamePair lnp; + + while (true) { + int c = session.getStdout().read(); + + if (c < 0) { + throw new IOException("Remote scp terminated unexpectedly."); + } + + String line = client.receiveLine(session.getStdout()); + + if (c == 'T') { + /* Ignore modification times */ + continue; + } + + if ((c == 1) || (c == 2)) { + throw new IOException("Remote SCP error: " + line); + } + + if (c == 'C') { + lnp = client.parseCLine(line); + break; + } + + throw new IOException("Remote SCP error: " + ((char) c) + line); + } + + os.write(0x0); + os.flush(); + this.remaining = lnp.length; + } + + @Override + public int read() throws IOException { + if (!(remaining > 0)) { + return -1; + } + + int b = super.read(); + + if (b < 0) { + throw new IOException("Remote scp terminated connection unexpectedly"); + } + + remaining -= 1; + return b; + } + + @Override + public int read(byte b[], int off, int len) throws IOException { + if (!(remaining > 0)) { + return -1; + } + + int trans = (int) remaining; + + if (remaining > len) { + trans = len; + } + + int read = super.read(b, off, trans); + + if (read < 0) { + throw new IOException("Remote scp terminated connection unexpectedly"); + } + + remaining -= read; + return read; + } + + @Override + public void close() throws IOException { + try { + session.getStdin().write(0x0); + session.getStdin().flush(); + } + finally { + if (session != null) { + session.close(); + } + } + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/SCPOutputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/SCPOutputStream.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2011 David Kocher. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import ch.ethz.ssh2.util.StringEncoder; + +/** + * @version $Id: SCPOutputStream.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public class SCPOutputStream extends BufferedOutputStream { + + private Session session; + + private SCPClient scp; + + public SCPOutputStream(SCPClient client, Session session, final String remoteFile, long length, String mode) throws IOException { + super(session.getStdin(), 40000); + this.session = session; + this.scp = client; + InputStream is = new BufferedInputStream(session.getStdout(), 512); + scp.readResponse(is); + String cline = "C" + mode + " " + length + " " + remoteFile + "\n"; + super.write(StringEncoder.GetBytes(cline)); + this.flush(); + scp.readResponse(is); + } + + @Override + public void close() throws IOException { + try { + this.write(0); + this.flush(); + scp.readResponse(session.getStdout()); + this.write(StringEncoder.GetBytes("E\n")); + this.flush(); + } + finally { + if (session != null) + session.close(); + } + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/SFTPClient.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/SFTPClient.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,225 @@ +package ch.ethz.ssh2; + +import java.io.IOException; +import java.util.List; + +/** + * @version $Id: SFTPClient.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public interface SFTPClient { + + /** + * Retrieve the file attributes of an open file. + * + * @param handle a SFTPv3FileHandle handle. + * @return a SFTPv3FileAttributes object. + * @throws IOException + */ + SFTPFileAttributes fstat(SFTPFileHandle handle) throws IOException; + + /** + * Retrieve the file attributes of a file. This method + * follows symbolic links on the server. + * + * @param path See the {@link SFTPClient comment} for the class for more details. + * @return a SFTPv3FileAttributes object. + * @throws IOException + * @see #lstat(String) + */ + SFTPFileAttributes stat(String path) throws IOException; + + /** + * Retrieve the file attributes of a file. This method + * does NOT follow symbolic links on the server. + * + * @param path See the {@link SFTPClient comment} for the class for more details. + * @return a SFTPv3FileAttributes object. + * @throws IOException + * @see #stat(String) + */ + SFTPFileAttributes lstat(String path) throws IOException; + + /** + * Read the target of a symbolic link. Note: OpenSSH (as of version 4.4) gets very upset + * (SSH_FX_BAD_MESSAGE error) if you want to read the target of a file that is not a + * symbolic link. Better check first with {@link #lstat(String)}. + * + * @param path See the {@link SFTPClient comment} for the class for more details. + * @return The target of the link. + * @throws IOException + */ + String readLink(String path) throws IOException; + + /** + * Modify the attributes of a file. Used for operations such as changing + * the ownership, permissions or access times, as well as for truncating a file. + * + * @param path See the {@link SFTPClient comment} for the class for more details. + * @param attr A SFTPv3FileAttributes object. Specifies the modifications to be + * made to the attributes of the file. Empty fields will be ignored. + * @throws IOException + */ + void setstat(String path, SFTPFileAttributes attr) throws IOException; + + /** + * Modify the attributes of a file. Used for operations such as changing + * the ownership, permissions or access times, as well as for truncating a file. + * + * @param handle a SFTPv3FileHandle handle + * @param attr A SFTPv3FileAttributes object. Specifies the modifications to be + * made to the attributes of the file. Empty fields will be ignored. + * @throws IOException + */ + void fsetstat(SFTPFileHandle handle, SFTPFileAttributes attr) throws IOException; + + /** + * Create a symbolic link on the server. Creates a link "src" that points + * to "target". + * + * @param src See the {@link SFTPClient comment} for the class for more details. + * @param target See the {@link SFTPClient comment} for the class for more details. + * @throws IOException + */ + void createSymlink(String src, String target) throws IOException; + + /** + * Create a symbolic link on the server. Creates a link "src" that points + * to "target". + * + * @param src See the {@link SFTPClient comment} for the class for more details. + * @param target See the {@link SFTPClient comment} for the class for more details. + * @throws IOException + */ + void createHardlink(String src, String target) throws IOException; + + /** + * Have the server canonicalize any given path name to an absolute path. + * This is useful for converting path names containing ".." components or + * relative pathnames without a leading slash into absolute paths. + * + * @param path See the {@link SFTPClient comment} for the class for more details. + * @return An absolute path. + * @throws IOException + */ + String canonicalPath(String path) throws IOException; + + void setCharset(String charset) throws IOException; + + String getCharset(); + + SFTPFileHandle openDirectory(String path) throws IOException; + + boolean isConnected(); + + void close(); + + List ls(String dirName) throws IOException; + + /** + * Create a new directory. + * + * @param name See the {@link SFTPClient comment} for the class for more details. + * @param posixPermissions the permissions for this directory, e.g., "0700" (remember that + * this is octal noation). The server will likely apply a umask. + * @throws IOException + */ + void mkdir(String name, int posixPermissions) throws IOException; + + /** + * Remove a file. + * + * @param filename See the {@link SFTPClient comment} for the class for more details. + * @throws IOException + */ + void rm(String filename) throws IOException; + + /** + * Remove an empty directory. + * + * @param dirName See the {@link SFTPClient comment} for the class for more details. + * @throws IOException + */ + void rmdir(String dirName) throws IOException; + + /** + * Move a file or directory. + * + * @param oldPath See the {@link SFTPClient comment} for the class for more details. + * @param newPath See the {@link SFTPClient comment} for the class for more details. + * @throws IOException + */ + void mv(String oldPath, String newPath) throws IOException; + + /** + * Create a file and open it for reading and writing. + * Same as {@link #createFile(String, SFTPFileAttributes) createFile(filename, null)}. + * + * @param filename See the {@link SFTPClient comment} for the class for more details. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + SFTPFileHandle createFile(String filename) throws IOException; + + /** + * Create a file and open it for reading and writing. + * You can specify the default attributes of the file (the server may or may + * not respect your wishes). + * + * @param filename See the {@link SFTPv3Client comment} for the class for more details. + * @param attr may be null to use server defaults. Probably only + * the uid, gid and permissions + * (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle} + * structure make sense. You need only to set those fields where you want + * to override the server's defaults. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + SFTPFileHandle createFile(String filename, SFTPFileAttributes attr) throws IOException; + + SFTPFileHandle openFile(String filename, int flags) throws IOException; + + /** + * Read bytes from a file in a parallel fashion. As many bytes as you want will be read. + *

+ *

    + *
  • The server will read as many bytes as it can from the file (up to len), + * and return them.
  • + *
  • If EOF is encountered before reading any data, -1 is returned. + *
  • If an error occurs, an exception is thrown
  • . + *
  • For normal disk files, it is guaranteed that the server will return the specified + * number of bytes, or up to end of file. For, e.g., device files this may return + * fewer bytes than requested.
  • + *
+ * + * @param handle a SFTPv3FileHandle handle + * @param fileOffset offset (in bytes) in the file + * @param dst the destination byte array + * @param dstoff offset in the destination byte array + * @param len how many bytes to read, 0 < len + * @return the number of bytes that could be read, may be less than requested if + * the end of the file is reached, -1 is returned in case of EOF + * @throws IOException + */ + int read(SFTPFileHandle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException; + + /** + * Write bytes to a file. If len > 32768, then the write operation will + * be split into multiple writes. + * + * @param handle a SFTPv3FileHandle handle. + * @param fileOffset offset (in bytes) in the file. + * @param src the source byte array. + * @param srcoff offset in the source byte array. + * @param len how many bytes to write. + * @throws IOException + */ + void write(SFTPFileHandle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException; + + /** + * Close a file. + * + * @param handle a file handle + * @throws IOException + */ + void closeFile(SFTPFileHandle handle) throws IOException; +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/SFTPDirectoryEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/SFTPDirectoryEntry.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,12 @@ +package ch.ethz.ssh2; + +/** + * @version $Id: SFTPDirectoryEntry.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public interface SFTPDirectoryEntry { + + public String getFilename(); + + public SFTPFileAttributes getAttributes(); + +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/SFTPException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/SFTPException.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please see the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +import java.io.IOException; + +import ch.ethz.ssh2.sftp.ErrorCodes; + +/** + * Used in combination with the SFTPv3Client. This exception wraps + * error messages sent by the SFTP server. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ + +public class SFTPException extends IOException { + private static final long serialVersionUID = 578654644222421811L; + + private final String sftpErrorMessage; + private final int sftpErrorCode; + + private static String constructMessage(String s, int errorCode) { + String[] detail = ErrorCodes.getDescription(errorCode); + + if (detail == null) + return s + " (UNKNOWN SFTP ERROR CODE)"; + + return s + " (" + detail[0] + ": " + detail[1] + ")"; + } + + SFTPException(String msg, int errorCode) { + super(constructMessage(msg, errorCode)); + sftpErrorMessage = msg; + sftpErrorCode = errorCode; + } + + /** + * Get the error message sent by the server. Often, this + * message does not help a lot (e.g., "failure"). + * + * @return the plain string as sent by the server. + */ + public String getServerErrorMessage() { + return sftpErrorMessage; + } + + /** + * Get the error code sent by the server. + * + * @return an error code as defined in the SFTP specs. + */ + public int getServerErrorCode() { + return sftpErrorCode; + } + + /** + * Get the symbolic name of the error code as given in the SFTP specs. + * + * @return e.g., "SSH_FX_INVALID_FILENAME". + */ + public String getServerErrorCodeSymbol() { + String[] detail = ErrorCodes.getDescription(sftpErrorCode); + + if (detail == null) + return "UNKNOWN SFTP ERROR CODE " + sftpErrorCode; + + return detail[0]; + } + + /** + * Get the description of the error code as given in the SFTP specs. + * + * @return e.g., "The filename is not valid." + */ + public String getServerErrorCodeVerbose() { + String[] detail = ErrorCodes.getDescription(sftpErrorCode); + + if (detail == null) + return "The error code " + sftpErrorCode + " is unknown."; + + return detail[1]; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/SFTPFileAttributes.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/SFTPFileAttributes.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,15 @@ +package ch.ethz.ssh2; + +/** + * @version $Id: SFTPFileAttributes.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public interface SFTPFileAttributes { + + boolean isDirectory(); + + boolean isRegularFile(); + + boolean isSymlink(); + + byte[] toBytes(); +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/SFTPFileHandle.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/SFTPFileHandle.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,35 @@ +package ch.ethz.ssh2; + +import java.io.IOException; + +/** + * @version $Id: SFTPFileHandle.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public class SFTPFileHandle { + + private final SFTPClient client; + + private final byte[] handle; + + protected SFTPFileHandle(final SFTPClient client, final byte[] handle) { + this.client = client; + this.handle = handle; + } + + /** + * Get the SFTPv3Client instance which created this handle. + * + * @return A SFTPv3Client instance. + */ + public SFTPClient getClient() { + return client; + } + + public byte[] getHandle() { + return handle; + } + + public void close() throws IOException { + client.closeFile(this); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/SFTPInputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/SFTPInputStream.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2011 David Kocher. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +import java.io.IOException; +import java.io.InputStream; + +/** + * @version $Id: SFTPInputStream.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public class SFTPInputStream extends InputStream { + + private SFTPFileHandle handle; + + /** + * Offset (in bytes) in the file to read + */ + private long readOffset = 0; + + public SFTPInputStream(SFTPFileHandle handle) { + this.handle = handle; + } + + /** + * Reads up to len bytes of data from the input stream into + * an array of bytes. An attempt is made to read as many as + * len bytes, but a smaller number may be read, possibly + * zero. The number of bytes actually read is returned as an integer. + * + * @see SFTPClient#read(SFTPFileHandle, long, byte[], int, int) + */ + @Override + public int read(byte[] buffer, int offset, int len) throws IOException { + int read = handle.getClient().read(handle, readOffset, buffer, offset, len); + + if (read > 0) { + readOffset += read; + } + + return read; + } + + /** + * Reads the next byte of data from the input stream. The value byte is + * returned as an int in the range 0 to + * 255. If no byte is available because the end of the stream + * has been reached, the value -1 is returned. This method + * blocks until input data is available, the end of the stream is detected, + * or an exception is thrown. + *

+ *

A subclass must provide an implementation of this method. + * + * @return the next byte of data, or -1 if the end of the + * stream is reached. + * @throws IOException if an I/O error occurs. + */ + @Override + public int read() throws IOException { + byte[] buffer = new byte[1]; + int b = handle.getClient().read(handle, readOffset, buffer, 0, 1); + + if (b > 0) { + readOffset += 1; + } + + return b; + } + + /** + * Skips over and discards n bytes of data from this input + * stream. + * + * @param n the number of bytes to be skipped. + * @return the actual number of bytes skipped. + */ + @Override + public long skip(long n) { + readOffset += n; + return n; + } + + @Override + public void close() throws IOException { + handle.getClient().closeFile(handle); + } +} \ No newline at end of file diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/SFTPOutputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/SFTPOutputStream.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2011 David Kocher. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * @version $Id: SFTPOutputStream.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public class SFTPOutputStream extends OutputStream { + + private SFTPFileHandle handle; + + /** + * Offset (in bytes) in the file to write + */ + private long writeOffset = 0; + + public SFTPOutputStream(SFTPFileHandle handle) { + this.handle = handle; + } + + /** + * Writes len bytes from the specified byte array + * starting at offset off to this output stream. + * The general contract for write(b, off, len) is that + * some of the bytes in the array b are written to the + * output stream in order; element b[off] is the first + * byte written and b[off+len-1] is the last byte written + * by this operation. + * + * @see SFTPClient#write(SFTPFileHandle, long, byte[], int, int) + */ + @Override + public void write(byte[] buffer, int offset, int len) throws IOException { + // We can just blindly write the whole buffer at once. + // if len > 32768, then the write operation will + // be split into multiple writes in SFTPv3Client#write. + handle.getClient().write(handle, writeOffset, buffer, offset, len); + writeOffset += len; + } + + @Override + public void write(int b) throws IOException { + byte[] buffer = new byte[1]; + buffer[0] = (byte) b; + handle.getClient().write(handle, writeOffset, buffer, 0, 1); + writeOffset += 1; + } + + public long skip(long n) { + writeOffset += n; + return n; + } + + @Override + public void close() throws IOException { + handle.getClient().closeFile(handle); + } +} \ No newline at end of file diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/SFTPv3Client.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/SFTPv3Client.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,433 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import ch.ethz.ssh2.log.Logger; +import ch.ethz.ssh2.packets.TypesReader; +import ch.ethz.ssh2.packets.TypesWriter; +import ch.ethz.ssh2.sftp.ErrorCodes; +import ch.ethz.ssh2.sftp.Packet; + +/** + * A SFTPv3Client represents a SFTP (protocol version 3) + * client connection tunnelled over a SSH-2 connection. This is a very simple + * (synchronous) implementation. + *

+ * Basically, most methods in this class map directly to one of + * the packet types described in draft-ietf-secsh-filexfer-02.txt. + *

+ * Note: this is experimental code. + *

+ * Error handling: the methods of this class throw IOExceptions. However, unless + * there is catastrophic failure, exceptions of the type {@link SFTPv3Client} will + * be thrown (a subclass of IOException). Therefore, you can implement more verbose + * behavior by checking if a thrown exception if of this type. If yes, then you + * can cast the exception and access detailed information about the failure. + *

+ * Notes about file names, directory names and paths, copy-pasted + * from the specs: + *

    + *
  • SFTP v3 represents file names as strings. File names are + * assumed to use the slash ('/') character as a directory separator.
  • + *
  • File names starting with a slash are "absolute", and are relative to + * the root of the file system. Names starting with any other character + * are relative to the user's default directory (home directory).
  • + *
  • Servers SHOULD interpret a path name component ".." as referring to + * the parent directory, and "." as referring to the current directory. + * If the server implementation limits access to certain parts of the + * file system, it must be extra careful in parsing file names when + * enforcing such restrictions. There have been numerous reported + * security bugs where a ".." in a path name has allowed access outside + * the intended area.
  • + *
  • An empty path name is valid, and it refers to the user's default + * directory (usually the user's home directory).
  • + *
+ *

+ * If you are still not tired then please go on and read the comment for + * {@link #setCharset(String)}. + * + * @author Christian Plattner, plattner@inf.ethz.ch + * @version $Id: SFTPv3Client.java 133 2014-04-14 12:26:29Z dkocher@sudo.ch $ + */ +public class SFTPv3Client extends AbstractSFTPClient { + private static final Logger log = Logger.getLogger(SFTPv3Client.class); + + /** + * Open the file for reading. + */ + public static final int SSH_FXF_READ = 0x00000001; + /** + * Open the file for writing. If both this and SSH_FXF_READ are + * specified, the file is opened for both reading and writing. + */ + public static final int SSH_FXF_WRITE = 0x00000002; + /** + * Force all writes to append data at the end of the file. + */ + public static final int SSH_FXF_APPEND = 0x00000004; + /** + * If this flag is specified, then a new file will be created if one + * does not alread exist (if O_TRUNC is specified, the new file will + * be truncated to zero length if it previously exists). + */ + public static final int SSH_FXF_CREAT = 0x00000008; + /** + * Forces an existing file with the same name to be truncated to zero + * length when creating a file by specifying SSH_FXF_CREAT. + * SSH_FXF_CREAT MUST also be specified if this flag is used. + */ + public static final int SSH_FXF_TRUNC = 0x00000010; + /** + * Causes the request to fail if the named file already exists. + */ + public static final int SSH_FXF_EXCL = 0x00000020; + + private PacketListener listener; + + /** + * Create a SFTP v3 client. + * + * @param conn The underlying SSH-2 connection to be used. + * @throws IOException + */ + public SFTPv3Client(Connection conn) throws IOException { + this(conn, new PacketListener() { + public void read(String packet) { + log.debug("Read packet " + packet); + } + public void write(String packet) { + log.debug("Write packet " + packet); + } + }); + } + + /** + * Create a SFTP v3 client. + * + * @param conn The underlying SSH-2 connection to be used. + * @throws IOException + */ + public SFTPv3Client(Connection conn, PacketListener listener) throws IOException { + super(conn, 3, listener); + this.listener = listener; + } + + public SFTPv3FileAttributes fstat(SFTPFileHandle handle) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(handle.getHandle(), 0, handle.getHandle().length); + sendMessage(Packet.SSH_FXP_FSTAT, req_id, tw.getBytes()); + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + int rep_id = tr.readUINT32(); + + if (rep_id != req_id) { + throw new RequestMismatchException(); + } + + if (t == Packet.SSH_FXP_ATTRS) { + return new SFTPv3FileAttributes(tr); + } + + if (t != Packet.SSH_FXP_STATUS) { + throw new PacketTypeException(t); + } + + int errorCode = tr.readUINT32(); + String errorMessage = tr.readString(); + listener.read(errorMessage); + throw new SFTPException(errorMessage, errorCode); + } + + private SFTPv3FileAttributes statBoth(String path, int statMethod) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(path, this.getCharset()); + sendMessage(statMethod, req_id, tw.getBytes()); + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + int rep_id = tr.readUINT32(); + + if (rep_id != req_id) { + throw new RequestMismatchException(); + } + + if (t == Packet.SSH_FXP_ATTRS) { + return new SFTPv3FileAttributes(tr); + } + + if (t != Packet.SSH_FXP_STATUS) { + throw new PacketTypeException(t); + } + + int errorCode = tr.readUINT32(); + String errorMessage = tr.readString(); + listener.read(errorMessage); + throw new SFTPException(errorMessage, errorCode); + } + + public SFTPv3FileAttributes stat(String path) throws IOException { + return statBoth(path, Packet.SSH_FXP_STAT); + } + + public SFTPv3FileAttributes lstat(String path) throws IOException { + return statBoth(path, Packet.SSH_FXP_LSTAT); + } + + + private List scanDirectory(byte[] handle) throws IOException { + List files = new ArrayList(); + + while (true) { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(handle, 0, handle.length); + sendMessage(Packet.SSH_FXP_READDIR, req_id, tw.getBytes()); + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + int rep_id = tr.readUINT32(); + + if (rep_id != req_id) { + throw new RequestMismatchException(); + } + + if (t == Packet.SSH_FXP_NAME) { + int count = tr.readUINT32(); + + if (log.isDebugEnabled()) { + log.debug(String.format("Parsing %d name entries", count)); + } + + while (count > 0) { + SFTPv3DirectoryEntry file = new SFTPv3DirectoryEntry(); + file.filename = tr.readString(this.getCharset()); + file.longEntry = tr.readString(this.getCharset()); + listener.read(file.longEntry); + file.attributes = new SFTPv3FileAttributes(tr); + + if (log.isDebugEnabled()) { + log.debug(String.format("Adding file %s", file)); + } + + files.add(file); + count--; + } + + continue; + } + + if (t != Packet.SSH_FXP_STATUS) { + throw new PacketTypeException(t); + } + + int errorCode = tr.readUINT32(); + + if (errorCode == ErrorCodes.SSH_FX_EOF) { + return files; + } + + String errorMessage = tr.readString(); + listener.read(errorMessage); + throw new SFTPException(errorMessage, errorCode); + } + } + + public final SFTPv3FileHandle openDirectory(String path) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(path, this.getCharset()); + sendMessage(Packet.SSH_FXP_OPENDIR, req_id, tw.getBytes()); + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + int rep_id = tr.readUINT32(); + + if (rep_id != req_id) { + throw new RequestMismatchException(); + } + + if (t == Packet.SSH_FXP_HANDLE) { + return new SFTPv3FileHandle(this, tr.readByteString()); + } + + if (t != Packet.SSH_FXP_STATUS) { + throw new PacketTypeException(t); + } + + int errorCode = tr.readUINT32(); + String errorMessage = tr.readString(); + listener.read(errorMessage); + throw new SFTPException(errorMessage, errorCode); + } + + /** + * List the contents of a directory. + * + * @param dirName See the {@link SFTPv3Client comment} for the class for more details. + * @return A Vector containing {@link SFTPv3DirectoryEntry} objects. + * @throws IOException + */ + public List ls(String dirName) throws IOException { + SFTPv3FileHandle handle = openDirectory(dirName); + List result = scanDirectory(handle.getHandle()); + closeFile(handle); + return result; + } + + /** + * Open a file for reading. + * + * @param filename See the {@link SFTPv3Client comment} for the class for more details. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + public SFTPv3FileHandle openFileRO(String filename) throws IOException { + return openFile(filename, SSH_FXF_READ, new SFTPv3FileAttributes()); + } + + /** + * Open a file for reading and writing. + * + * @param filename See the {@link SFTPv3Client comment} for the class for more details. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + public SFTPv3FileHandle openFileRW(String filename) throws IOException { + return openFile(filename, SSH_FXF_READ | SSH_FXF_WRITE, new SFTPv3FileAttributes()); + } + + /** + * Open a file in append mode. The SFTP v3 draft says nothing but assuming normal POSIX + * behavior, all writes will be appendend to the end of the file, no matter which offset + * one specifies. + *

+ * A side note for the curious: OpenSSH does an lseek() to the specified writing offset before each write(), + * even for writes to files opened in O_APPEND mode. However, bear in mind that when working + * in the O_APPEND mode, each write() includes an implicit lseek() to the end of the file + * (well, this is what the newsgroups say). + * + * @param filename See the {@link SFTPv3Client comment} for the class for more details. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + public SFTPv3FileHandle openFileRWAppend(String filename) throws IOException { + return openFile(filename, SSH_FXF_READ | SSH_FXF_WRITE | SSH_FXF_APPEND, new SFTPv3FileAttributes()); + } + + /** + * Open a file in append mode. The SFTP v3 draft says nothing but assuming normal POSIX + * behavior, all writes will be appendend to the end of the file, no matter which offset + * one specifies. + *

+ * A side note for the curious: OpenSSH does an lseek() to the specified writing offset before each write(), + * even for writes to files opened in O_APPEND mode. However, bear in mind that when working + * in the O_APPEND mode, each write() includes an implicit lseek() to the end of the file + * (well, this is what the newsgroups say). + * + * @param filename See the {@link SFTPv3Client comment} for the class for more details. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + public SFTPv3FileHandle openFileWAppend(String filename) throws IOException { + return openFile(filename, SSH_FXF_WRITE | SSH_FXF_APPEND, new SFTPv3FileAttributes()); + } + + public SFTPv3FileHandle createFile(String filename) throws IOException { + return createFile(filename, new SFTPv3FileAttributes()); + } + + public SFTPv3FileHandle createFile(String filename, SFTPFileAttributes attr) throws IOException { + return openFile(filename, SSH_FXF_CREAT | SSH_FXF_READ | SSH_FXF_WRITE, attr); + } + + /** + * Create a file (truncate it if it already exists) and open it for writing. + * Same as {@link #createFileTruncate(String, SFTPFileAttributes) createFileTruncate(filename, null)}. + * + * @param filename See the {@link SFTPv3Client comment} for the class for more details. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + public SFTPv3FileHandle createFileTruncate(String filename) throws IOException { + return createFileTruncate(filename, new SFTPv3FileAttributes()); + } + + /** + * reate a file (truncate it if it already exists) and open it for writing. + * You can specify the default attributes of the file (the server may or may + * not respect your wishes). + * + * @param filename See the {@link SFTPv3Client comment} for the class for more details. + * @param attr may be null to use server defaults. Probably only + * the uid, gid and permissions + * (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle} + * structure make sense. You need only to set those fields where you want + * to override the server's defaults. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + public SFTPv3FileHandle createFileTruncate(String filename, SFTPFileAttributes attr) throws IOException { + return openFile(filename, SSH_FXF_CREAT | SSH_FXF_TRUNC | SSH_FXF_WRITE, attr); + } + + public SFTPv3FileHandle openFile(String filename, int flags) throws IOException { + return openFile(filename, flags, new SFTPv3FileAttributes()); + } + + @Override + public SFTPv3FileHandle openFile(String filename, int flags, SFTPFileAttributes attr) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(filename, this.getCharset()); + tw.writeUINT32(flags); + tw.writeBytes(attr.toBytes()); + sendMessage(Packet.SSH_FXP_OPEN, req_id, tw.getBytes()); + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + int rep_id = tr.readUINT32(); + + if (rep_id != req_id) { + throw new RequestMismatchException(); + } + + if (t == Packet.SSH_FXP_HANDLE) { + return new SFTPv3FileHandle(this, tr.readByteString()); + } + + if (t != Packet.SSH_FXP_STATUS) { + throw new PacketTypeException(t); + } + + int errorCode = tr.readUINT32(); + String errorMessage = tr.readString(); + listener.read(errorMessage); + throw new SFTPException(errorMessage, errorCode); + } + + @Override + public void createSymlink(String src, String target) throws IOException { + int req_id = generateNextRequestID(); + // Changed semantics of src and target. The bug is known on SFTP servers shipped with all + // versions of OpenSSH (Bug #861). + TypesWriter tw = new TypesWriter(); + tw.writeString(target, this.getCharset()); + tw.writeString(src, this.getCharset()); + sendMessage(Packet.SSH_FXP_SYMLINK, req_id, tw.getBytes()); + expectStatusOKMessage(req_id); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/SFTPv3DirectoryEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/SFTPv3DirectoryEntry.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +/** + * A SFTPv3DirectoryEntry as returned by {@link SFTPv3Client#ls(String)}. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class SFTPv3DirectoryEntry implements SFTPDirectoryEntry { + /** + * A relative name within the directory, without any path components. + */ + public String filename; + + /** + * An expanded format for the file name, similar to what is returned by + * "ls -l" on Un*x systems. + *

+ * The format of this field is unspecified by the SFTP v3 protocol. + * It MUST be suitable for use in the output of a directory listing + * command (in fact, the recommended operation for a directory listing + * command is to simply display this data). However, clients SHOULD NOT + * attempt to parse the longname field for file attributes; they SHOULD + * use the attrs field instead. + *

+ * The recommended format for the longname field is as follows:
+ * -rwxr-xr-x 1 mjos staff 348911 Mar 25 14:29 t-filexfer + */ + public String longEntry; + + /** + * The attributes of this entry. + */ + public SFTPv3FileAttributes attributes; + + public String getFilename() { + return filename; + } + + public SFTPv3FileAttributes getAttributes() { + return attributes; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("SFTPv3DirectoryEntry{"); + sb.append("filename='").append(filename).append('\''); + sb.append(", attributes=").append(attributes); + sb.append('}'); + return sb.toString(); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/SFTPv3FileAttributes.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/SFTPv3FileAttributes.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +import java.io.IOException; + +import ch.ethz.ssh2.packets.TypesReader; +import ch.ethz.ssh2.packets.TypesWriter; +import ch.ethz.ssh2.sftp.AttribFlags; + +/** + * A SFTPv3FileAttributes object represents detail information + * about a file on the server. Not all fields may/must be present. + * + * @author Christian Plattner, plattner@inf.ethz.ch + * @version $Id: SFTPv3FileAttributes.java 133 2014-04-14 12:26:29Z dkocher@sudo.ch $ + */ +public class SFTPv3FileAttributes implements SFTPFileAttributes { + /** + * The SIZE attribute. NULL if not present. + */ + public Long size = null; + + /** + * The UID attribute. NULL if not present. + */ + public Integer uid = null; + + /** + * The GID attribute. NULL if not present. + */ + public Integer gid = null; + + /** + * The POSIX permissions. NULL if not present. + *

+ * Here is a list: + *

+ *

Note: these numbers are all OCTAL.
+     * 

+ * S_IFMT 0170000 bitmask for the file type bitfields + * S_IFSOCK 0140000 socket + * S_IFLNK 0120000 symbolic link + * S_IFREG 0100000 regular file + * S_IFBLK 0060000 block device + * S_IFDIR 0040000 directory + * S_IFCHR 0020000 character device + * S_IFIFO 0010000 fifo + * S_ISUID 0004000 set UID bit + * S_ISGID 0002000 set GID bit + * S_ISVTX 0001000 sticky bit + *

+ * S_IRWXU 00700 mask for file owner permissions + * S_IRUSR 00400 owner has read permission + * S_IWUSR 00200 owner has write permission + * S_IXUSR 00100 owner has execute permission + * S_IRWXG 00070 mask for group permissions + * S_IRGRP 00040 group has read permission + * S_IWGRP 00020 group has write permission + * S_IXGRP 00010 group has execute permission + * S_IRWXO 00007 mask for permissions for others (not in group) + * S_IROTH 00004 others have read permission + * S_IWOTH 00002 others have write permisson + * S_IXOTH 00001 others have execute permission + *

+ */ + public Integer permissions = null; + + /** + * Last access time of the file. + *

+ * The atime attribute. Represented as seconds from Jan 1, 1970 in UTC. + * NULL if not present. + */ + public Integer atime = null; + + /** + * The mtime attribute. Represented as seconds from Jan 1, 1970 in UTC. + * NULL if not present. + */ + public Integer mtime = null; + + /** + * Checks if this entry is a directory. + * + * @return Returns true if permissions are available and they indicate + * that this entry represents a directory. + */ + public boolean isDirectory() { + if (permissions == null) { + return false; + } + + return ((permissions & 0040000) == 0040000); + } + + /** + * Checks if this entry is a regular file. + * + * @return Returns true if permissions are available and they indicate + * that this entry represents a regular file. + */ + public boolean isRegularFile() { + if (permissions == null) { + return false; + } + + return ((permissions & 0100000) == 0100000); + } + + /** + * Checks if this entry is a a symlink. + * + * @return Returns true if permissions are available and they indicate + * that this entry represents a symlink. + */ + public boolean isSymlink() { + if (permissions == null) { + return false; + } + + return ((permissions & 0120000) == 0120000); + } + + /** + * Turn the POSIX permissions into a 7 digit octal representation. + * Note: the returned value is first masked with 0177777. + * + * @return NULL if permissions are not available. + */ + public String getOctalPermissions() { + if (permissions == null) { + return null; + } + + String res = Integer.toString(permissions.intValue() & 0177777, 8); + StringBuilder sb = new StringBuilder(); + int leadingZeros = 7 - res.length(); + + while (leadingZeros > 0) { + sb.append('0'); + leadingZeros--; + } + + sb.append(res); + return sb.toString(); + } + + public SFTPv3FileAttributes() { + // + } + + /** + * uint32 valid-attribute-flags + * byte type always present + * uint64 size if flag SIZE + * uint64 allocation-size if flag ALLOCATION_SIZE + * string owner if flag OWNERGROUP + * string group if flag OWNERGROUP + * uint32 permissions if flag PERMISSIONS + * int64 atime if flag ACCESSTIME + * uint32 atime-nseconds if flag SUBSECOND_TIMES + * int64 createtime if flag CREATETIME + * uint32 createtime-nseconds if flag SUBSECOND_TIMES + * int64 mtime if flag MODIFYTIME + * uint32 mtime-nseconds if flag SUBSECOND_TIMES + * int64 ctime if flag CTIME + * uint32 ctime-nseconds if flag SUBSECOND_TIMES + * string acl if flag ACL + * uint32 attrib-bits if flag BITS + * uint32 attrib-bits-valid if flag BITS + * byte text-hint if flag TEXT_HINT + * string mime-type if flag MIME_TYPE + * uint32 link-count if flag LINK_COUNT + * string untranslated-name if flag UNTRANSLATED_NAME + * uint32 extended-count if flag EXTENDED + * extension-pair extensions + */ + public SFTPv3FileAttributes(final TypesReader tr) throws IOException { + int flags = tr.readUINT32(); + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_SIZE) != 0) { + this.size = tr.readUINT64(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID) != 0) { + this.uid = tr.readUINT32(); + this.gid = tr.readUINT32(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS) != 0) { + this.permissions = tr.readUINT32(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME) != 0) { + this.atime = tr.readUINT32(); + this.mtime = tr.readUINT32(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_EXTENDED) != 0) { + int count = tr.readUINT32(); + + // Read it anyway to detect corrupt packets + while (count > 0) { + tr.readByteString(); + tr.readByteString(); + count--; + } + } + } + + /** + * The same encoding is used both when returning file + * attributes from the server and when sending file attributes to the + * server. + * + * @return Encoded attributes + */ + public byte[] toBytes() { + TypesWriter tw = new TypesWriter(); + int attrFlags = 0; + + if (this.size != null) { + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_SIZE; + } + + if ((this.uid != null) && (this.gid != null)) { + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID; + } + + if (this.permissions != null) { + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS; + } + + if ((this.atime != null) && (this.mtime != null)) { + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME; + } + + tw.writeUINT32(attrFlags); + + if (this.size != null) { + tw.writeUINT64(this.size); + } + + if ((this.uid != null) && (this.gid != null)) { + tw.writeUINT32(this.uid); + tw.writeUINT32(this.gid); + } + + if (this.permissions != null) { + tw.writeUINT32(this.permissions); + } + + if ((this.atime != null) && (this.mtime != null)) { + tw.writeUINT32(this.atime); + tw.writeUINT32(this.mtime); + } + + return tw.getBytes(); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("SFTPv3FileAttributes{"); + sb.append("size=").append(size); + sb.append(", uid=").append(uid); + sb.append(", gid=").append(gid); + sb.append(", permissions=").append(permissions); + sb.append(", atime=").append(atime); + sb.append(", mtime=").append(mtime); + sb.append('}'); + return sb.toString(); + } +} \ No newline at end of file diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/SFTPv3FileHandle.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/SFTPv3FileHandle.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +/** + * A SFTPv3FileHandle. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class SFTPv3FileHandle extends SFTPFileHandle { + protected SFTPv3FileHandle(final SFTPv3Client client, final byte[] handle) { + super(client, handle); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/SFTPv6Client.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/SFTPv6Client.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,300 @@ +package ch.ethz.ssh2; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import ch.ethz.ssh2.log.Logger; +import ch.ethz.ssh2.packets.TypesReader; +import ch.ethz.ssh2.packets.TypesWriter; +import ch.ethz.ssh2.sftp.AceMask; +import ch.ethz.ssh2.sftp.ErrorCodes; +import ch.ethz.ssh2.sftp.OpenFlags; +import ch.ethz.ssh2.sftp.Packet; + +/** + * @version $Id: SFTPv6Client.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public class SFTPv6Client extends AbstractSFTPClient { + private static final Logger log = Logger.getLogger(SFTPv6Client.class); + + private PacketListener listener; + + public SFTPv6Client(Connection conn) throws IOException { + this(conn, new PacketListener() { + public void read(String packet) { + log.debug("Read packet " + packet); + } + public void write(String packet) { + log.debug("Write packet " + packet); + } + }); + } + + public SFTPv6Client(Connection conn, PacketListener listener) throws IOException { + super(conn, 6, listener); + this.listener = listener; + } + + public SFTPv6FileAttributes fstat(SFTPFileHandle handle) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(handle.getHandle(), 0, handle.getHandle().length); + sendMessage(Packet.SSH_FXP_FSTAT, req_id, tw.getBytes()); + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + int rep_id = tr.readUINT32(); + + if (rep_id != req_id) { + throw new RequestMismatchException(); + } + + if (t == Packet.SSH_FXP_ATTRS) { + return new SFTPv6FileAttributes(tr); + } + + if (t != Packet.SSH_FXP_STATUS) { + throw new PacketTypeException(t); + } + + int errorCode = tr.readUINT32(); + String errorMessage = tr.readString(); + listener.read(errorMessage); + throw new SFTPException(errorMessage, errorCode); + } + + private SFTPv6FileAttributes statBoth(String path, int statMethod) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(path, this.getCharset()); + sendMessage(statMethod, req_id, tw.getBytes()); + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + int rep_id = tr.readUINT32(); + + if (rep_id != req_id) { + throw new RequestMismatchException(); + } + + if (t == Packet.SSH_FXP_ATTRS) { + return new SFTPv6FileAttributes(tr); + } + + if (t != Packet.SSH_FXP_STATUS) { + throw new PacketTypeException(t); + } + + int errorCode = tr.readUINT32(); + String errorMessage = tr.readString(); + listener.read(errorMessage); + throw new SFTPException(errorMessage, errorCode); + } + + public SFTPv6FileAttributes stat(String path) throws IOException { + return statBoth(path, Packet.SSH_FXP_STAT); + } + + public SFTPv6FileAttributes lstat(String path) throws IOException { + return statBoth(path, Packet.SSH_FXP_LSTAT); + } + + + private List scanDirectory(byte[] handle) throws IOException { + List files = new ArrayList(); + + while (true) { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(handle, 0, handle.length); + sendMessage(Packet.SSH_FXP_READDIR, req_id, tw.getBytes()); + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + int rep_id = tr.readUINT32(); + + if (rep_id != req_id) { + throw new RequestMismatchException(); + } + + if (t == Packet.SSH_FXP_NAME) { + int count = tr.readUINT32(); + + if (log.isDebugEnabled()) { + log.debug(String.format("Parsing %d name entries", count)); + } + + while (count > 0) { + SFTPv6DirectoryEntry file = new SFTPv6DirectoryEntry(); + file.filename = tr.readString(this.getCharset()); + listener.read(file.filename); + file.attributes = new SFTPv6FileAttributes(tr); + + if (log.isDebugEnabled()) { + log.debug(String.format("Adding file %s", file)); + } + + files.add(file); + count--; + } + + continue; + } + + if (t != Packet.SSH_FXP_STATUS) { + throw new PacketTypeException(t); + } + + int errorCode = tr.readUINT32(); + + if (errorCode == ErrorCodes.SSH_FX_EOF) { + return files; + } + + String errorMessage = tr.readString(); + listener.read(errorMessage); + throw new SFTPException(errorMessage, errorCode); + } + } + + public final SFTPFileHandle openDirectory(String path) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(path, this.getCharset()); + sendMessage(Packet.SSH_FXP_OPENDIR, req_id, tw.getBytes()); + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + int rep_id = tr.readUINT32(); + + if (rep_id != req_id) { + throw new RequestMismatchException(); + } + + if (t == Packet.SSH_FXP_HANDLE) { + return new SFTPFileHandle(this, tr.readByteString()); + } + + if (t != Packet.SSH_FXP_STATUS) { + throw new PacketTypeException(t); + } + + int errorCode = tr.readUINT32(); + String errorMessage = tr.readString(); + listener.read(errorMessage); + throw new SFTPException(errorMessage, errorCode); + } + + /** + * List the contents of a directory. + * + * @param dirName See the {@link SFTPv6Client comment} for the class for more details. + * @return A Vector containing {@link SFTPv6DirectoryEntry} objects. + * @throws IOException + */ + public List ls(String dirName) throws IOException { + SFTPFileHandle handle = openDirectory(dirName); + List result = scanDirectory(handle.getHandle()); + closeFile(handle); + return result; + } + + /** + * Create a file and open it for reading and writing. + * Same as {@link #createFile(String, SFTPFileAttributes) createFile(fileName, null)}. + * + * @param filename See the {@link SFTPv6Client comment} for the class for more details. + * @return a SFTPFileHandle handle + * @throws IOException + */ + public SFTPFileHandle createFile(String filename) throws IOException { + return createFile(filename, new SFTPv6FileAttributes()); + } + + /** + * Create a file and open it for reading and writing. + * You can specify the default attributes of the file (the server may or may + * not respect your wishes). + * + * @param filename See the {@link SFTPv6Client comment} for the class for more details. + * @param attr may be null to use server defaults. Probably only + * the uid, gid and permissions + * (remember the server may apply a umask) entries of the {@link SFTPFileHandle} + * structure make sense. You need only to set those fields where you want + * to override the server's defaults. + * @return a SFTPFileHandle handle + * @throws IOException + */ + public SFTPFileHandle createFile(String filename, SFTPFileAttributes attr) throws IOException { + return openFile(filename, OpenFlags.SSH_FXF_CREATE_NEW, attr); + } + + public SFTPFileHandle openFile(String filename, int flags) throws IOException { + return this.openFile(filename, flags, new SFTPv6FileAttributes()); + } + + @Override + public SFTPFileHandle openFile(String filename, int flags, SFTPFileAttributes attr) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(filename, this.getCharset()); + tw.writeUINT32(AceMask.ACE4_READ_DATA | AceMask.ACE4_READ_ATTRIBUTES | AceMask.ACE4_READ_ACL | AceMask.ACE4_READ_NAMED_ATTRS + | AceMask.ACE4_WRITE_DATA | AceMask.ACE4_APPEND_DATA | AceMask.ACE4_WRITE_ATTRIBUTES | AceMask.ACE4_WRITE_ACL | AceMask.ACE4_WRITE_NAMED_ATTRS); + tw.writeUINT32(flags); + tw.writeBytes(attr.toBytes()); + sendMessage(Packet.SSH_FXP_OPEN, req_id, tw.getBytes()); + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + int rep_id = tr.readUINT32(); + + if (rep_id != req_id) { + throw new RequestMismatchException(); + } + + if (t == Packet.SSH_FXP_HANDLE) { + return new SFTPFileHandle(this, tr.readByteString()); + } + + if (t != Packet.SSH_FXP_STATUS) { + throw new PacketTypeException(t); + } + + int errorCode = tr.readUINT32(); + String errorMessage = tr.readString(); + listener.read(errorMessage); + throw new SFTPException(errorMessage, errorCode); + } + + @Override + public void createSymlink(String src, String target) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + // new-link-path + tw.writeString(src, this.getCharset()); + // existing-path + tw.writeString(target, this.getCharset()); + tw.writeBoolean(true); + sendMessage(Packet.SSH_FXP_LINK, req_id, tw.getBytes()); + expectStatusOKMessage(req_id); + } + + @Override + public void createHardlink(String src, String target) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + // new-link-path + tw.writeString(src, this.getCharset()); + // existing-path + tw.writeString(target, this.getCharset()); + tw.writeBoolean(false); + sendMessage(Packet.SSH_FXP_LINK, req_id, tw.getBytes()); + expectStatusOKMessage(req_id); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/SFTPv6DirectoryEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/SFTPv6DirectoryEntry.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,33 @@ +package ch.ethz.ssh2; + +/** + * @version $Id: SFTPv6DirectoryEntry.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public class SFTPv6DirectoryEntry implements SFTPDirectoryEntry { + /** + * A relative name within the directory, without any path components. + */ + public String filename; + + /** + * The attributes of this entry. + */ + public SFTPv6FileAttributes attributes; + + public String getFilename() { + return filename; + } + + public SFTPv6FileAttributes getAttributes() { + return attributes; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("SFTPv6DirectoryEntry{"); + sb.append("filename='").append(filename).append('\''); + sb.append(", attributes=").append(attributes); + sb.append('}'); + return sb.toString(); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/SFTPv6FileAttributes.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/SFTPv6FileAttributes.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,371 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +import java.io.IOException; + +import ch.ethz.ssh2.packets.TypesReader; +import ch.ethz.ssh2.packets.TypesWriter; +import ch.ethz.ssh2.sftp.AttribFlags; +import ch.ethz.ssh2.sftp.AttribTypes; + +/** + * A SFTPv3FileAttributes object represents detail information + * about a file on the server. Not all fields may/must be present. + * + * @author Christian Plattner, plattner@inf.ethz.ch + * @version $Id: SFTPv6FileAttributes.java 133 2014-04-14 12:26:29Z dkocher@sudo.ch $ + */ + +public class SFTPv6FileAttributes implements SFTPFileAttributes { + + /** + * The type field is always present + * + * @see ch.ethz.ssh2.sftp.AttribTypes + */ + private Integer type = null; + + /** + * The SIZE attribute. NULL if not present. + */ + public Long size = null; + + /** + * The POSIX permissions. NULL if not present. + *

+ * Here is a list: + *

+ *

Note: these numbers are all OCTAL.
+     * 

+ * S_IFMT 0170000 bitmask for the file type bitfields + * S_IFSOCK 0140000 socket + * S_IFLNK 0120000 symbolic link + * S_IFREG 0100000 regular file + * S_IFBLK 0060000 block device + * S_IFDIR 0040000 directory + * S_IFCHR 0020000 character device + * S_IFIFO 0010000 fifo + * S_ISUID 0004000 set UID bit + * S_ISGID 0002000 set GID bit + * S_ISVTX 0001000 sticky bit + *

+ * S_IRWXU 00700 mask for file owner permissions + * S_IRUSR 00400 owner has read permission + * S_IWUSR 00200 owner has write permission + * S_IXUSR 00100 owner has execute permission + * S_IRWXG 00070 mask for group permissions + * S_IRGRP 00040 group has read permission + * S_IWGRP 00020 group has write permission + * S_IXGRP 00010 group has execute permission + * S_IRWXO 00007 mask for permissions for others (not in group) + * S_IROTH 00004 others have read permission + * S_IWOTH 00002 others have write permisson + * S_IXOTH 00001 others have execute permission + *

+ */ + public Integer permissions = null; + + /** + * Creation time of the file. + *

+ * The createtime attribute. Represented as seconds from Jan 1, 1970 in UTC. + * NULL if not present. + */ + public Long createtime = null; + + /** + * Last access time of the file. + *

+ * The atime attribute. Represented as seconds from Jan 1, 1970 in UTC. + * NULL if not present. + */ + public Long atime = null; + + /** + * The mtime attribute. Represented as seconds from Jan 1, 1970 in UTC. + * NULL if not present. + */ + public Long mtime = null; + + /** + * Last time the file attributes were changed. The exact meaning of this field depends on the server. + *

+ * The ctime attribute. Represented as seconds from Jan 1, 1970 in UTC. + * NULL if not present. + */ + public Long ctime = null; + + /** + * The 'owner' and 'group' fields are represented as UTF-8 strings. user@localhost represents + * a user in the context of the server. + */ + public String owner = null; + + /** + * The 'owner' and 'group' fields are represented as UTF-8 strings + */ + public String group = null; + + /** + * Checks if this entry is a directory. + * + * @return Returns true if permissions are available and they indicate + * that this entry represents a directory. + */ + public boolean isDirectory() { + return (type & AttribTypes.SSH_FILEXFER_TYPE_DIRECTORY) == AttribTypes.SSH_FILEXFER_TYPE_DIRECTORY; + } + + /** + * Checks if this entry is a regular file. + * + * @return Returns true if permissions are available and they indicate + * that this entry represents a regular file. + */ + public boolean isRegularFile() { + return (type & AttribTypes.SSH_FILEXFER_TYPE_REGULAR) == AttribTypes.SSH_FILEXFER_TYPE_REGULAR; + } + + /** + * Checks if this entry is a a symlink. + * + * @return Returns true if permissions are available and they indicate + * that this entry represents a symlink. + */ + public boolean isSymlink() { + return (type & AttribTypes.SSH_FILEXFER_TYPE_SYMLINK) == AttribTypes.SSH_FILEXFER_TYPE_SYMLINK; + } + + public SFTPv6FileAttributes() { + // + } + + /** + * uint32 valid-attribute-flags + * byte type always present + * uint64 size if flag SIZE + * uint64 allocation-size if flag ALLOCATION_SIZE + * string owner if flag OWNERGROUP + * string group if flag OWNERGROUP + * uint32 permissions if flag PERMISSIONS + * int64 atime if flag ACCESSTIME + * uint32 atime-nseconds if flag SUBSECOND_TIMES + * int64 createtime if flag CREATETIME + * uint32 createtime-nseconds if flag SUBSECOND_TIMES + * int64 mtime if flag MODIFYTIME + * uint32 mtime-nseconds if flag SUBSECOND_TIMES + * int64 ctime if flag CTIME + * uint32 ctime-nseconds if flag SUBSECOND_TIMES + * string acl if flag ACL + * uint32 attrib-bits if flag BITS + * uint32 attrib-bits-valid if flag BITS + * byte text-hint if flag TEXT_HINT + * string mime-type if flag MIME_TYPE + * uint32 link-count if flag LINK_COUNT + * string untranslated-name if flag UNTRANSLATED_NAME + * uint32 extended-count if flag EXTENDED + * extension-pair extensions + */ + public SFTPv6FileAttributes(final TypesReader tr) throws IOException { + int flags = tr.readUINT32(); + // The type field is always present + this.type = tr.readByte(); + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_SIZE) != 0) { + this.size = tr.readUINT64(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_ALLOCATION_SIZE) != 0) { + // Ignore + tr.readUINT64(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_OWNERGROUP) != 0) { + this.owner = tr.readString(); + this.group = tr.readString(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS) != 0) { + this.permissions = tr.readUINT32(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_ACCESSTIME) != 0) { + this.atime = tr.readUINT64(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) { + // Ignore + tr.readUINT32(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_CREATETIME) != 0) { + this.createtime = tr.readUINT64(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) { + // Ignore + tr.readUINT32(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_MODIFYTIME) != 0) { + this.mtime = tr.readUINT64(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) { + // Ignore + tr.readUINT32(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_CTIME) != 0) { + this.ctime = tr.readUINT64(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) { + // Ignore + tr.readUINT32(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_ACL) != 0) { + // Ignore + tr.readString(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_BITS) != 0) { + // Ignore attrib-bits + tr.readUINT32(); + // Ignore attrib-bits-valid + tr.readUINT32(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_TEXT_HINT) != 0) { + // Ignore + tr.readByte(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_MIME_TYPE) != 0) { + // Ignore + tr.readString(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_LINK_COUNT) != 0) { + // Ignore + tr.readUINT32(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_UNTRANSLATED_NAME) != 0) { + // Ignore + tr.readString(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_EXTENDED) != 0) { + int count = tr.readUINT32(); + + // Read it anyway to detect corrupt packets + while (count > 0) { + // extension-name + tr.readByteString(); + // extension-data + tr.readByteString(); + count--; + } + } + } + + /** + * The same encoding is used both when returning file + * attributes from the server and when sending file attributes to the + * server. + * + * @return Encoded attributes + */ + public byte[] toBytes() { + TypesWriter tw = new TypesWriter(); + // The 'valid-attribute-flags' specifies which of the fields are present. Those fields + // for which the corresponding flag is not set are not present + int attrFlags = 0; + + if (this.size != null) { + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_SIZE; + } + + if ((this.owner != null) && (this.group != null)) { + // If either the owner or group field is zero length, the field should + // be considered absent, and no change should be made to that specific + // field during a modification operation. + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_OWNERGROUP; + } + + if (this.permissions != null) { + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS; + } + + if (this.atime != null) { + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_ACCESSTIME; + } + + if (this.createtime != null) { + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_CREATETIME; + } + + if (this.mtime != null) { + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_MODIFYTIME; + } + + if (this.ctime != null) { + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_CTIME; + } + + tw.writeUINT32(attrFlags); + + // The type field is always present. + if (this.size != null) { + tw.writeUINT64(this.size); + } + + if ((this.owner != null) && (this.group != null)) { + tw.writeString(owner); + tw.writeString(group); + } + + if (this.permissions != null) { + tw.writeUINT32(this.permissions); + } + + if (this.atime != null) { + tw.writeUINT64(this.atime); + } + + if (this.createtime != null) { + tw.writeUINT64(this.createtime); + } + + if (this.mtime != null) { + tw.writeUINT64(this.mtime); + } + + if (this.ctime != null) { + tw.writeUINT64(this.ctime); + } + + return tw.getBytes(); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("SFTPv6FileAttributes{"); + sb.append("type=").append(type); + sb.append(", size=").append(size); + sb.append(", permissions=").append(permissions); + sb.append(", createtime=").append(createtime); + sb.append(", atime=").append(atime); + sb.append(", mtime=").append(mtime); + sb.append(", ctime=").append(ctime); + sb.append(", owner='").append(owner).append('\''); + sb.append(", group='").append(group).append('\''); + sb.append('}'); + return sb.toString(); + } +} \ No newline at end of file diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/ServerAuthenticationCallback.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/ServerAuthenticationCallback.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2012-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2; + +/** + * A callback used during the authentication phase (see RFC 4252) when + * implementing a SSH server. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public interface ServerAuthenticationCallback { + /** + * The method name for host-based authentication. + */ + public final String METHOD_HOSTBASED = "hostbased"; + + /** + * The method name for public-key authentication. + */ + public final String METHOD_PUBLICKEY = "publickey"; + + /** + * The method name for password authentication. + */ + public final String METHOD_PASSWORD = "password"; + + /** + * Called when the client enters authentication. + * This gives you the chance to set a custom authentication banner + * for this SSH-2 session. This is the first method called in this interface. + * It will only called at most once per ServerConnection. + * + * @param sc The corresponding ServerConnection + * @return The authentication banner or NULL in case no banner should be send. + */ + public String initAuthentication(ServerConnection sc); + + /** + * Return the authentication methods that are currently available to the client. + * Be prepared to return this information at any time during the authentication procedure. + *

+ * The returned name-list of 'method names' (see RFC4252) indicate the authentication methods + * that may productively continue the authentication dialog. + *

+ * It is RECOMMENDED that servers only include those 'method name' + * values in the name-list that are actually useful. However, it is not + * illegal to include 'method name' values that cannot be used to + * authenticate the user. + *

+ * Already successfully completed authentications SHOULD NOT be included + * in the name-list, unless they should be performed again for some reason. + * + * @see #METHOD_HOSTBASED + * @see #METHOD_PASSWORD + * @see #METHOD_PUBLICKEY + * + * @param sc + * @return A list of method names. + */ + public String[] getRemainingAuthMethods(ServerConnection sc); + + /** + * Typically, this will be called be the client to get the list of + * authentication methods that can continue. You should simply return + * {@link AuthenticationResult#FAILURE}. + * + * @param sc + * @param username Name of the user that wants to log in with the "none" method. + * @return + */ + public AuthenticationResult authenticateWithNone(ServerConnection sc, String username); + + public AuthenticationResult authenticateWithPassword(ServerConnection sc, String username, String password); + + /** + * NOTE: Not implemented yet. + * + * @param sc + * @param username + * @param algorithm + * @param publickey + * @param signature + * @return + */ + public AuthenticationResult authenticateWithPublicKey(ServerConnection sc, String username, String algorithm, + byte[] publickey, byte[] signature); +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/ServerConnection.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/ServerConnection.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,380 @@ +/* + * Copyright (c) 2012-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2; + +import java.io.CharArrayWriter; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.net.Socket; +import java.util.List; +import java.util.ArrayList; + +import ch.ethz.ssh2.crypto.CryptoWishList; +import ch.ethz.ssh2.crypto.PEMDecoder; +import ch.ethz.ssh2.server.ServerConnectionState; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.RSAPrivateKey; +import ch.ethz.ssh2.transport.ServerTransportManager; + +/** + * A server-side SSH-2 connection. + * + * @author Christian + * + */ +public class ServerConnection { + /** + * The softwareversion presented to the SSH-2 client. + */ + private String softwareversion = String.format("Ganymed_SSHD_%s", Version.getSpecification()); + + private final ServerConnectionState state = new ServerConnectionState(this); + + /** + * Creates a new ServerConnection that will communicate + * with the client over the given Socket. + *

+ * Note: you need to call {@link #connect()} or {@link #connect(int)} to + * perform the initial handshake and establish the encrypted communication. + * + * @see #connect(int) + * + * @param s The socket + */ + public ServerConnection(Socket s) { + this(s, null, null, null); + } + + public ServerConnection(Socket s, String softwareversion) { + this(s, null, null, null); + this.softwareversion = softwareversion; + } + + /** + * Creates a new ServerConnection that will communicate + * with the client over the given Socket. + *

+ * Note: you need to call {@link #connect()} or {@link #connect(int)} to + * perform the initial handshake and establish the encrypted communication. + *

+ * Please read the javadoc for the {@link #connect(int)} method. + * + * @see #connect(int) + * + * @param s The socket + * @param dsa_key The DSA hostkey, may be NULL + * @param rsa_key The RSA hostkey, may be NULL + * @param ec_key The EC hostkey, may be NULL + */ + public ServerConnection(Socket s, KeyPair dsa_key, KeyPair rsa_key, KeyPair ec_key) { + state.s = s; + state.softwareversion = softwareversion; + state.next_dsa_key = dsa_key; + state.next_rsa_key = rsa_key; + state.next_ec_key = ec_key; + fixCryptoWishList(state.next_cryptoWishList, state.next_dsa_key, state.next_rsa_key, state.next_ec_key); + } + + /** + * Establish the connection and block until the first handshake has completed. + *

+ * Note: this is a wrapper that calls connect(0) (i.e., connect with no timeout). + *

+ * Please read the javadoc for the {@link #connect(int)} method. + * + * @see #connect(int) + * + * @throws IOException + */ + + public synchronized void connect() throws IOException { + connect(0); + } + + /** + * Establish the connection and block until the first handshake has completed. + *

+ * Note 1: at least one DSA, RSA or EC hostkey must be set before calling this method. + *

+ * Note 2: You must set the callbacks for authentication ({@link #setAuthenticationCallback(ServerAuthenticationCallback)}) + * and connection events ({@link #setServerConnectionCallback(ServerConnectionCallback)}). + * + * @see #setPEMHostKey(char[], String) + * @see #setPEMHostKey(File, String) + * @see #setRsaHostKey(RSAPrivateKey) + * @see #setDsaHostKey(DSAPrivateKey) + * + * @param timeout_milliseconds Timeout in milliseconds, 0 means no timeout. + * @throws IOException + */ + + public synchronized void connect(int timeout_milliseconds) throws IOException { + synchronized (state) { + if (state.cb_conn == null) + throw new IllegalStateException("The callback for connection events has not been set."); + + if (state.cb_auth == null) + throw new IllegalStateException("The callback for authentication events has not been set."); + + if (state.tm != null) + throw new IllegalStateException("The initial handshake has already been started."); + + if ((state.next_dsa_key == null) && (state.next_rsa_key == null) && (state.next_ec_key == null)) + throw new IllegalStateException("Neither an RSA nor a DSA nor an EC host key has been specified!"); + + state.tm = new ServerTransportManager(state.s); + } + + state.tm.connect(state); + /* Wait until first KEX has finished */ + state.tm.getConnectionInfo(1); + } + + /** + * Retrieve the underlying socket. + * + * @return the socket that has been passed to the constructor. + */ + public Socket getSocket() { + return state.s; + } + + /** + * Force an asynchronous key re-exchange (the call does not block). The + * latest values set for MAC, Cipher and DH group exchange parameters will + * be used. If a key exchange is currently in progress, then this method has + * the only effect that the so far specified parameters will be used for the + * next (client driven) key exchange. You may call this method only after + * the initial key exchange has been established. + *

+ * Note: This implementation will never start automatically a key exchange (other than the initial one) + * unless you or the connected SSH-2 client ask for it. + * + * @throws IOException + * In case of any failure behind the scenes. + */ + + public synchronized void forceKeyExchange() throws IOException { + synchronized (state) { + if (state.tm == null) + throw new IllegalStateException( + "Cannot force another key exchange, you need to start the key exchange first."); + + state.tm.forceKeyExchange(state.next_cryptoWishList, null, state.next_dsa_key, state.next_rsa_key, state.next_ec_key); + } + } + + /** + * Returns a {@link ConnectionInfo} object containing the details of + * the connection. May be called as soon as the first key exchange has been + * started. The method blocks in case the first key exchange has not been completed. + *

+ * Note: upon return of this method, authentication may still be pending. + * + * @return A {@link ConnectionInfo} object. + * @throws IOException + * In case of any failure behind the scenes; e.g., first key exchange was aborted. + */ + + public synchronized ConnectionInfo getConnectionInfo() throws IOException { + synchronized (state) { + if (state.tm == null) + throw new IllegalStateException( + "Cannot get details of connection, you need to start the key exchange first."); + } + + return state.tm.getConnectionInfo(1); + } + + /** + * Change the current DSA hostkey. Either a DSA or RSA or EC private key must be set for a successful handshake with + * the client. + *

+ * Note: You can change an existing DSA hostkey after the initial kex exchange (the new value will + * be used during the next server initiated key exchange), but you cannot remove (i.e., set to null) the + * current DSA key, otherwise the next key exchange may fail in case the client supports only DSA hostkeys. + * + * @param dsa_hostkey + */ + + public synchronized void setDsaHostKey(KeyPair dsa_hostkey) { + synchronized (state) { + if ((dsa_hostkey == null) && (state.next_dsa_key != null) && (state.tm != null)) + throw new IllegalStateException("Cannot remove DSA hostkey after first key exchange."); + + state.next_dsa_key = dsa_hostkey; + fixCryptoWishList(state.next_cryptoWishList, state.next_dsa_key, state.next_rsa_key, state.next_ec_key); + } + } + + /** + * Change the current RSA hostkey. Either a DSA or RSA or EC private key must be set for a successful handshake with + * the client. + *

+ * Note: You can change an existing RSA hostkey after the initial kex exchange (the new value will + * be used during the next server initiated key exchange), but you cannot remove (i.e., set to null) the + * current RSA key, otherwise the next key exchange may fail in case the client supports only RSA hostkeys. + * + * @param rsa_hostkey + */ + + public synchronized void setRsaHostKey(KeyPair rsa_hostkey) { + synchronized (state) { + if ((rsa_hostkey == null) && (state.next_rsa_key != null) && (state.tm != null)) + throw new IllegalStateException("Cannot remove RSA hostkey after first key exchange."); + + state.next_rsa_key = rsa_hostkey; + fixCryptoWishList(state.next_cryptoWishList, state.next_dsa_key, state.next_rsa_key, state.next_ec_key); + } + } + + /** + * Change the current EC hostkey. Either a DSA or RSA or EC private key must be set for a successful handshake with + * the client. + *

+ * Note: You can change an existing EC hostkey after the initial kex exchange (the new value will + * be used during the next server initiated key exchange), but you cannot remove (i.e., set to null) the + * current EC key, otherwise the next key exchange may fail in case the client supports only EC hostkeys. + * + * @param rsa_hostkey + */ + + public synchronized void setEcHostKey(KeyPair ec_hostkey) { + synchronized (state) { + if ((ec_hostkey == null) && (state.next_ec_key != null) && (state.tm != null)) + throw new IllegalStateException("Cannot remove EC hostkey after first key exchange."); + + state.next_ec_key = ec_hostkey; + fixCryptoWishList(state.next_cryptoWishList, state.next_dsa_key, state.next_rsa_key, state.next_ec_key); + } + } + + /** + * Utility method that loads a PEM based hostkey (either RSA or DSA based) and + * calls either setRsaHostKey() or setDsaHostKey(). + * + * @param pemdata The PEM data + * @param password Password, may be null in case the PEM data is not password protected + * @throws IOException In case of any error. + */ + public void setPEMHostKey(char[] pemdata, String password) throws IOException { + KeyPair pair = PEMDecoder.decode(pemdata, password); + PrivateKey key = pair.getPrivate(); + + if (key instanceof DSAPrivateKey) setDsaHostKey(pair); + + if (key instanceof RSAPrivateKey) setRsaHostKey(pair); + + if (key instanceof ECPrivateKey) setEcHostKey(pair); + } + + /** + * Utility method that loads a hostkey from a PEM file (either RSA or DSA based) and + * calls either setRsaHostKey() or setDsaHostKey(). + * + * @param pemFile The PEM file + * @param password Password, may be null in case the PEM file is not password protected + * @throws IOException + */ + public void setPEMHostKey(File pemFile, String password) throws IOException { + if (pemFile == null) + throw new IllegalArgumentException("pemfile argument is null"); + + char[] buff = new char[256]; + CharArrayWriter cw = new CharArrayWriter(); + FileReader fr = new FileReader(pemFile); + + while (true) { + int len = fr.read(buff); + + if (len < 0) + break; + + cw.write(buff, 0, len); + } + + fr.close(); + setPEMHostKey(cw.toCharArray(), password); + } + + private void fixCryptoWishList(CryptoWishList next_cryptoWishList, KeyPair next_dsa_key, KeyPair next_rsa_key, KeyPair next_ec_key) { + List algos = new ArrayList(); + + if (next_ec_key != null) algos.add("ecdsa-sha2-nistp521"); + + if (next_ec_key != null) algos.add("ecdsa-sha2-nistp384"); + + if (next_ec_key != null) algos.add("ecdsa-sha2-nistp256"); + + if (next_dsa_key != null) algos.add("ssh-dss"); + + if (next_rsa_key != null) algos.add("ssh-rsa"); + + next_cryptoWishList.serverHostKeyAlgorithms = new String[algos.size()]; + algos.toArray(next_cryptoWishList.serverHostKeyAlgorithms); + } + + /** + * Callback interface with methods that will be called upon events + * generated by the client (e.g., client opens a new Session which results in a ServerSession). + *

+ * Note: This must be set before the first handshake. + * + * @param cb The callback implementation + */ + + public synchronized void setServerConnectionCallback(ServerConnectionCallback cb) { + synchronized (state) { + state.cb_conn = cb; + } + } + + /** + * Callback interface with methods that will be called upon authentication events. + *

+ * Note: This must be set before the first handshake. + * + * @param cb The callback implementation + */ + + public synchronized void setAuthenticationCallback(ServerAuthenticationCallback cb) { + synchronized (state) { + state.cb_auth = cb; + } + } + + /** + * Close the connection to the SSH-2 server. All assigned sessions will be + * closed, too. Can be called at any time. Don't forget to call this once + * you don't need a connection anymore - otherwise the receiver thread may + * run forever. + */ + public void close() { + synchronized (state) { + if (state.cm != null) + state.cm.closeAllChannels(); + + if (state.tm != null) { + state.tm.close(new Throwable("Closed due to user request."), false); + } + } + } + + public void close(IOException t) { + synchronized (state) { + if (state.cm != null) + state.cm.closeAllChannels(); + + if (state.tm != null) { + state.tm.close(t, false); + } + } + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/ServerConnectionCallback.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/ServerConnectionCallback.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2012-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +public interface ServerConnectionCallback { + public ServerSessionCallback acceptSession(ServerSession session); +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/ServerHostKeyVerifier.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/ServerHostKeyVerifier.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +/** + * A callback interface used to implement a client specific method of checking + * server host keys. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ + +public interface ServerHostKeyVerifier { + /** + * The actual verifier method, it will be called by the key exchange code + * on EVERY key exchange - this can happen several times during the lifetime + * of a connection. + *

+ * Note: SSH-2 servers are allowed to change their hostkey at ANY time. + * + * @param hostname the hostname used to create the {@link Connection} object + * @param port the remote TCP port + * @param serverHostKeyAlgorithm the public key algorithm (ssh-rsa or ssh-dss) + * @param serverHostKey the server's public key blob + * @return if the client wants to accept the server's host key - if not, the + * connection will be closed. + * @throws Exception Will be wrapped with an IOException, extended version of returning false =) + */ + public boolean verifyServerHostKey(String hostname, int port, String serverHostKeyAlgorithm, byte[] serverHostKey) + throws Exception; +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/ServerSession.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/ServerSession.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2012-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +import java.io.InputStream; +import java.io.OutputStream; + +/** + * A ServerSession represents the server-side of a session channel. + * + * @see Session + * + * @author Christian + * + */ +public interface ServerSession { + public InputStream getStdout(); + + public InputStream getStderr(); + + public OutputStream getStdin(); + + public void close(); +} \ No newline at end of file diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/ServerSessionCallback.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/ServerSessionCallback.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2012-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2; + +import java.io.IOException; + +/** + * The interface for an object that receives events based on requests that + * a client sends on a session channel. Once the session channel has been set up, + * a program is started at the server end. The program can be a shell, an application + * program, or a subsystem with a host-independent name. Only one of these requests + * can succeed per channel. + *

+ * CAUTION: These event methods are being called from the receiver thread. The receiving of + * messages will be blocked until your event handler returns. To signal to the client that you + * are willing to accept its request, return a {@link Runnable} object which will be executed + * in a new thread after the acknowledgment has been sent back to the client. + *

+ * If a method does not allow you to return a {@link Runnable}, then the + * SSH protocol does not allow to send a status back to the client (more exactly, the client cannot + * request an acknowledgment). In these cases, if you need to invoke + * methods on the {@link ServerSession} or plan to execute long-running activity, then please do this from within a new {@link Thread}. + *

+ * If you want to signal a fatal error, then please throw an IOException. Currently, this will + * tear down the whole SSH connection. + * + * @see ServerSession + * + * @author Christian + * + */ +public interface ServerSessionCallback { + public Runnable requestPtyReq(ServerSession ss, PtySettings pty) throws IOException; + + public Runnable requestEnv(ServerSession ss, String name, String value) throws IOException; + + public Runnable requestShell(ServerSession ss) throws IOException; + + public Runnable requestExec(ServerSession ss, String command) throws IOException; + + public Runnable requestSubsystem(ServerSession ss, String subsystem) throws IOException; + + /** + * When the window (terminal) size changes on the client side, it MAY send a message to the other side to inform it of the new dimensions. + * + * @param ss the corresponding session + * @param term_width_columns + * @param term_height_rows + * @param term_width_pixels + */ + public void requestWindowChange(ServerSession ss, int term_width_columns, int term_height_rows, + int term_width_pixels, int term_height_pixels) throws IOException; + + /** + * A signal can be delivered to the remote process/service. Some systems may not implement signals, in which case they SHOULD ignore this message. + * + * @param ss the corresponding session + * @param signal (a string without the "SIG" prefix) + * @return + * @throws IOException + */ + +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/Session.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/Session.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,476 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.SecureRandom; + +import ch.ethz.ssh2.channel.Channel; +import ch.ethz.ssh2.channel.ChannelManager; +import ch.ethz.ssh2.channel.X11ServerData; + +/** + * A Session is a remote execution of a program. "Program" means + * in this context either a shell, an application or a system command. The + * program may or may not have a tty. Only one single program can be started on + * a session. However, multiple sessions can be active simultaneously. + * + * @author Christian Plattner + * @version $Id: Session.java 96 2014-04-08 15:14:37Z dkocher@sudo.ch $ + */ +public class Session { + private ChannelManager cm; + private Channel cn; + + private boolean flag_pty_requested = false; + private boolean flag_x11_requested = false; + private boolean flag_execution_started = false; + private boolean flag_closed = false; + + private String x11FakeCookie = null; + + private final SecureRandom rnd; + + protected Session(ChannelManager cm, SecureRandom rnd) throws IOException { + this.cm = cm; + this.cn = cm.openSessionChannel(); + this.rnd = rnd; + } + + /** + * Basically just a wrapper for lazy people - identical to calling + * requestPTY("dumb", 0, 0, 0, 0, null). + * + * @throws IOException + */ + public void requestDumbPTY() throws IOException { + requestPTY("dumb", 0, 0, 0, 0, null); + } + + /** + * Basically just another wrapper for lazy people - identical to calling + * requestPTY(term, 0, 0, 0, 0, null). + * + * @throws IOException + */ + public void requestPTY(String term) throws IOException { + requestPTY(term, 0, 0, 0, 0, null); + } + + /** + * Allocate a pseudo-terminal for this session. + *

+ * This method may only be called before a program or shell is started in + * this session. + *

+ * Different aspects can be specified: + *

+ *

    + *
  • The TERM environment variable value (e.g., vt100)
  • + *
  • The terminal's dimensions.
  • + *
  • The encoded terminal modes.
  • + *
+ * Zero dimension parameters are ignored. The character/row dimensions + * override the pixel dimensions (when nonzero). Pixel dimensions refer to + * the drawable area of the window. The dimension parameters are only + * informational. The encoding of terminal modes (parameter + * terminal_modes) is described in RFC4254. + * + * @param term The TERM environment variable value (e.g., vt100) + * @param term_width_characters terminal width, characters (e.g., 80) + * @param term_height_characters terminal height, rows (e.g., 24) + * @param term_width_pixels terminal width, pixels (e.g., 640) + * @param term_height_pixels terminal height, pixels (e.g., 480) + * @param terminal_modes encoded terminal modes (may be null) + * @throws IOException + */ + public void requestPTY(String term, int term_width_characters, int term_height_characters, int term_width_pixels, + int term_height_pixels, byte[] terminal_modes) throws IOException { + if (term == null) + throw new IllegalArgumentException("TERM cannot be null."); + + if ((terminal_modes != null) && (terminal_modes.length > 0)) { + if (terminal_modes[terminal_modes.length - 1] != 0) + throw new IOException("Illegal terminal modes description, does not end in zero byte"); + } + else + terminal_modes = new byte[] {0}; + + synchronized (this) { + /* The following is just a nicer error, we would catch it anyway later in the channel code */ + if (flag_closed) + throw new IOException("This session is closed."); + + if (flag_pty_requested) + throw new IOException("A PTY was already requested."); + + if (flag_execution_started) + throw new IOException( + "Cannot request PTY at this stage anymore, a remote execution has already started."); + + flag_pty_requested = true; + } + + cm.requestPTY(cn, term, term_width_characters, term_height_characters, term_width_pixels, term_height_pixels, + terminal_modes); + } + + /** + * Tells the server that the size of the terminal has changed. + * + * See {@link #requestPTY(String, int, int, int, int, byte[])} for more details about how parameters are interpreted. + * + * @param term_width_characters + * terminal width, characters (e.g., 80) + * @param term_height_characters + * terminal height, rows (e.g., 24) + * @param term_width_pixels + * terminal width, pixels (e.g., 640) + * @param term_height_pixels + * terminal height, pixels (e.g., 480) + * @throws IOException + */ + public void resizePTY(int term_width_characters, int term_height_characters, int term_width_pixels, int term_height_pixels) throws IOException { + requestWindowChange(term_width_characters, term_height_characters, term_width_pixels, term_height_pixels); + } + + public void requestWindowChange(int term_width_characters, int term_height_characters, int term_width_pixels, + int term_height_pixels) throws IOException { + synchronized (this) { + /* The following is just a nicer error, we would catch it anyway later in the channel code */ + if (flag_closed) + throw new IOException("This session is closed."); + + if (!flag_pty_requested) + throw new IOException("A PTY was not requested."); + } + + cm.requestWindowChange(cn, term_width_characters, term_height_characters, term_width_pixels, term_height_pixels); + } + + /** + * Request X11 forwarding for the current session. + *

+ * You have to supply the name and port of your X-server. + *

+ * This method may only be called before a program or shell is started in + * this session. + * + * @param hostname the hostname of the real (target) X11 server (e.g., 127.0.0.1) + * @param port the port of the real (target) X11 server (e.g., 6010) + * @param cookie if non-null, then present this cookie to the real X11 server + * @param singleConnection if true, then the server is instructed to only forward one single + * connection, no more connections shall be forwarded after first, or after the session + * channel has been closed + * @throws IOException + */ + public void requestX11Forwarding(String hostname, int port, byte[] cookie, boolean singleConnection) + throws IOException { + if (hostname == null) + throw new IllegalArgumentException("hostname argument may not be null"); + + synchronized (this) { + /* The following is just a nicer error, we would catch it anyway later in the channel code */ + if (flag_closed) + throw new IOException("This session is closed."); + + if (flag_x11_requested) + throw new IOException("X11 forwarding was already requested."); + + if (flag_execution_started) + throw new IOException( + "Cannot request X11 forwarding at this stage anymore, a remote execution has already started."); + + flag_x11_requested = true; + } + + /* X11ServerData - used to store data about the target X11 server */ + X11ServerData x11data = new X11ServerData(); + x11data.hostname = hostname; + x11data.port = port; + x11data.x11_magic_cookie = cookie; /* if non-null, then present this cookie to the real X11 server */ + /* Generate fake cookie - this one is used between remote clients and the ganymed proxy */ + byte[] fakeCookie = new byte[16]; + String hexEncodedFakeCookie; + + /* Make sure that this fake cookie is unique for this connection */ + + while (true) { + rnd.nextBytes(fakeCookie); + /* Generate also hex representation of fake cookie */ + StringBuilder tmp = new StringBuilder(32); + + for (int i = 0; i < fakeCookie.length; i++) { + String digit2 = Integer.toHexString(fakeCookie[i] & 0xff); + tmp.append((digit2.length() == 2) ? digit2 : "0" + digit2); + } + + hexEncodedFakeCookie = tmp.toString(); + + /* Well, yes, chances are low, but we want to be on the safe side */ + + if (cm.checkX11Cookie(hexEncodedFakeCookie) == null) + break; + } + + /* Ask for X11 forwarding */ + cm.requestX11(cn, singleConnection, "MIT-MAGIC-COOKIE-1", hexEncodedFakeCookie, 0); + + /* OK, that went fine, get ready to accept X11 connections... */ + /* ... but only if the user has not called close() in the meantime =) */ + + synchronized (this) { + if (flag_closed == false) { + this.x11FakeCookie = hexEncodedFakeCookie; + cm.registerX11Cookie(hexEncodedFakeCookie, x11data); + } + } + + /* Now it is safe to start remote X11 programs */ + } + + /** + * Execute a command on the remote machine. + * + * @param cmd The command to execute on the remote host. + * @throws IOException + */ + public void execCommand(String cmd) throws IOException { + this.execCommand(cmd, null); + } + + /** + * Execute a command on the remote machine. + * + * @param cmd The command to execute on the remote host. + * @param charsetName The charset used to convert between Java Unicode Strings and byte encodings + * @throws IOException + */ + public void execCommand(String cmd, String charsetName) throws IOException { + if (cmd == null) + throw new IllegalArgumentException("cmd argument may not be null"); + + synchronized (this) { + /* The following is just a nicer error, we would catch it anyway later in the channel code */ + if (flag_closed) + throw new IOException("This session is closed."); + + if (flag_execution_started) + throw new IOException("A remote execution has already started."); + + flag_execution_started = true; + } + + cm.requestExecCommand(cn, cmd, charsetName); + } + + /** + * Start a shell on the remote machine. + * + * @throws IOException + */ + public void startShell() throws IOException { + synchronized (this) { + /* The following is just a nicer error, we would catch it anyway later in the channel code */ + if (flag_closed) + throw new IOException("This session is closed."); + + if (flag_execution_started) + throw new IOException("A remote execution has already started."); + + flag_execution_started = true; + } + + cm.requestShell(cn); + } + + /** + * Start a subsystem on the remote machine. + * Unless you know what you are doing, you will never need this. + * + * @param name the name of the subsystem. + * @throws IOException + */ + public void startSubSystem(String name) throws IOException { + if (name == null) + throw new IllegalArgumentException("name argument may not be null"); + + synchronized (this) { + /* The following is just a nicer error, we would catch it anyway later in the channel code */ + if (flag_closed) + throw new IOException("This session is closed."); + + if (flag_execution_started) + throw new IOException("A remote execution has already started."); + + flag_execution_started = true; + } + + cm.requestSubSystem(cn, name); + } + + /** + * Request authentication agent forwarding. + * @param agent object that implements the callbacks + * + * @throws IOException in case of any problem or when the session is closed + */ + + public synchronized void requestAuthAgentForwarding(AuthAgentCallback agent) throws IOException { + synchronized (this) { + /* + * The following is just a nicer error, we would catch it anyway + * later in the channel code + */ + if (flag_closed) + throw new IOException("This session is closed."); + } + + cm.requestChannelAgentForwarding(cn, agent); + } + + public int getState() { + return cn.getState(); + } + + public InputStream getStdout() { + return cn.getStdoutStream(); + } + + public InputStream getStderr() { + return cn.getStderrStream(); + } + + public OutputStream getStdin() { + return cn.getStdinStream(); + } + + /** + * This method blocks until there is more data available on either the + * stdout or stderr InputStream of this Session. Very useful + * if you do not want to use two parallel threads for reading from the two + * InputStreams. One can also specify a timeout. NOTE: do NOT call this + * method if you use concurrent threads that operate on either of the two + * InputStreams of this Session (otherwise this method may + * block, even though more data is available). + * + * @param timeout The (non-negative) timeout in ms. 0 means no + * timeout, the call may block forever. + * @return

    + *
  • 0 if no more data will arrive.
  • + *
  • 1 if more data is available.
  • + *
  • -1 if a timeout occurred.
  • + *
+ * @throws IOException + * @deprecated This method has been replaced with a much more powerful wait-for-condition + * interface and therefore acts only as a wrapper. + */ + public int waitUntilDataAvailable(long timeout) throws IOException { + if (timeout < 0) + throw new IllegalArgumentException("timeout must not be negative!"); + + int conditions = cm.waitForCondition(cn, timeout, ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA + | ChannelCondition.EOF); + + if ((conditions & ChannelCondition.TIMEOUT) != 0) + return -1; + + if ((conditions & (ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA)) != 0) + return 1; + + /* Here we do not need to check separately for CLOSED, since CLOSED implies EOF */ + + if ((conditions & ChannelCondition.EOF) != 0) + return 0; + + throw new IllegalStateException("Unexpected condition result (" + conditions + ")"); + } + + /** + * This method blocks until certain conditions hold true on the underlying SSH-2 channel. + *

+ * This method returns as soon as one of the following happens: + *

    + *
  • at least of the specified conditions (see {@link ChannelCondition}) holds true
  • + *
  • timeout > 0 and a timeout occured (TIMEOUT will be set in result conditions) + *
  • the underlying channel was closed (CLOSED will be set in result conditions) + *
+ *

+ * In any case, the result value contains ALL current conditions, which may be more + * than the specified condition set (i.e., never use the "==" operator to test for conditions + * in the bitmask, see also comments in {@link ChannelCondition}). + *

+ * Note: do NOT call this method if you want to wait for STDOUT_DATA or STDERR_DATA and + * there are concurrent threads (e.g., StreamGobblers) that operate on either of the two + * InputStreams of this Session (otherwise this method may + * block, even though more data is available in the StreamGobblers). + * + * @param condition_set a bitmask based on {@link ChannelCondition} values + * @param timeout non-negative timeout in ms, 0 means no timeout + * @return all bitmask specifying all current conditions that are true + */ + + public int waitForCondition(int condition_set, long timeout) throws IOException { + if (timeout < 0) + throw new IllegalArgumentException("timeout must be non-negative!"); + + return cm.waitForCondition(cn, timeout, condition_set); + } + + /** + * Get the exit code/status from the remote command - if available. Be + * careful - not all server implementations return this value. It is + * generally a good idea to call this method only when all data from the + * remote side has been consumed (see also the method). + * + * @return An Integer holding the exit code, or + * null if no exit code is (yet) available. + */ + public Integer getExitStatus() { + return cn.getExitStatus(); + } + + /** + * Get the name of the signal by which the process on the remote side was + * stopped - if available and applicable. Be careful - not all server + * implementations return this value. + * + * @return An String holding the name of the signal, or + * null if the process exited normally or is still + * running (or if the server forgot to send this information). + */ + public String getExitSignal() { + return cn.getExitSignal(); + } + + /** + * Close this session. NEVER forget to call this method to free up resources - + * even if you got an exception from one of the other methods (or when + * getting an Exception on the Input- or OutputStreams). Sometimes these other + * methods may throw an exception, saying that the underlying channel is + * closed (this can happen, e.g., if the other server sent a close message.) + * However, as long as you have not called the close() + * method, you may be wasting (local) resources. + */ + public void close() { + synchronized (this) { + if (flag_closed) + return; + + flag_closed = true; + + if (x11FakeCookie != null) + cm.unRegisterX11Cookie(x11FakeCookie, true); + + try { + cm.closeChannel(cn, "Closed due to user request", true); + } + catch (IOException ignored) { + } + } + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/SimpleServerSessionCallback.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/SimpleServerSessionCallback.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2006-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2; + +import java.io.IOException; + +/** + * A basic ServerSessionCallback implementation. + *

+ * Note: you should derive from this class instead of implementing + * the {@link ServerSessionCallback} interface directly. This way + * your code works also in case the interface gets extended in future + * versions. + * + * @author Christian + * + */ +public class SimpleServerSessionCallback implements ServerSessionCallback { + public Runnable requestShell(ServerSession ss) throws IOException { + return null; + } + + public Runnable requestExec(ServerSession ss, String command) throws IOException { + return null; + } + + public Runnable requestSubsystem(ServerSession ss, String subsystem) throws IOException { + return null; + } + + public Runnable requestPtyReq(ServerSession ss, PtySettings pty) throws IOException { + return null; + } + + /** + * By default, silently ignore passwd environment variables. + */ + public Runnable requestEnv(ServerSession ss, String name, String value) throws IOException { + return new Runnable() { + public void run() { + /* Do nothing */ + } + }; + } + + public void requestWindowChange(ServerSession ss, int term_width_columns, int term_height_rows, + int term_width_pixels, int term_height_pixels) throws IOException { + } + +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/StreamGobbler.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/StreamGobbler.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InterruptedIOException; + +/** + * A StreamGobbler is an InputStream that uses an internal worker + * thread to constantly consume input from another InputStream. It uses a buffer + * to store the consumed data. The buffer size is automatically adjusted, if needed. + *

+ * This class is sometimes very convenient - if you wrap a session's STDOUT and STDERR + * InputStreams with instances of this class, then you don't have to bother about + * the shared window of STDOUT and STDERR in the low level SSH-2 protocol, + * since all arriving data will be immediatelly consumed by the worker threads. + * Also, as a side effect, the streams will be buffered (e.g., single byte + * read() operations are faster). + *

+ * Other SSH for Java libraries include this functionality by default in + * their STDOUT and STDERR InputStream implementations, however, please be aware + * that this approach has also a downside: + *

+ * If you do not call the StreamGobbler's read() method often enough + * and the peer is constantly sending huge amounts of data, then you will sooner or later + * encounter a low memory situation due to the aggregated data (well, it also depends on the Java heap size). + * Joe Average will like this class anyway - a paranoid programmer would never use such an approach. + *

+ * The term "StreamGobbler" was taken from an article called "When Runtime.exec() won't", + * see http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ + +public class StreamGobbler extends InputStream { + class GobblerThread extends Thread { + @Override + public void run() { + byte[] buff = new byte[8192]; + + while (true) { + try { + int avail = is.read(buff); + + synchronized (synchronizer) { + if (avail <= 0) { + isEOF = true; + synchronizer.notifyAll(); + break; + } + + int space_available = buffer.length - write_pos; + + if (space_available < avail) { + /* compact/resize buffer */ + int unread_size = write_pos - read_pos; + int need_space = unread_size + avail; + byte[] new_buffer = buffer; + + if (need_space > buffer.length) { + int inc = need_space / 3; + inc = (inc < 256) ? 256 : inc; + inc = (inc > 8192) ? 8192 : inc; + new_buffer = new byte[need_space + inc]; + } + + if (unread_size > 0) + System.arraycopy(buffer, read_pos, new_buffer, 0, unread_size); + + buffer = new_buffer; + read_pos = 0; + write_pos = unread_size; + } + + System.arraycopy(buff, 0, buffer, write_pos, avail); + write_pos += avail; + synchronizer.notifyAll(); + } + } + catch (IOException e) { + synchronized (synchronizer) { + exception = e; + synchronizer.notifyAll(); + break; + } + } + } + } + } + + private InputStream is; + + private final Object synchronizer = new Object(); + + private boolean isEOF = false; + private boolean isClosed = false; + private IOException exception = null; + + private byte[] buffer = new byte[2048]; + private int read_pos = 0; + private int write_pos = 0; + + public StreamGobbler(InputStream is) { + this.is = is; + GobblerThread t = new GobblerThread(); + t.setDaemon(true); + t.start(); + } + + @Override + public int read() throws IOException { + synchronized (synchronizer) { + if (isClosed) + throw new IOException("This StreamGobbler is closed."); + + while (read_pos == write_pos) { + if (exception != null) + throw exception; + + if (isEOF) + return -1; + + try { + synchronizer.wait(); + } + catch (InterruptedException e) { + throw new InterruptedIOException(); + } + } + + return buffer[read_pos++] & 0xff; + } + } + + @Override + public int available() throws IOException { + synchronized (synchronizer) { + if (isClosed) + throw new IOException("This StreamGobbler is closed."); + + return write_pos - read_pos; + } + } + + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public void close() throws IOException { + synchronized (synchronizer) { + if (isClosed) + return; + + isClosed = true; + isEOF = true; + synchronizer.notifyAll(); + is.close(); + } + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (b == null) + throw new NullPointerException(); + + if ((off < 0) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) || (off > b.length)) + throw new IndexOutOfBoundsException(); + + if (len == 0) + return 0; + + synchronized (synchronizer) { + if (isClosed) + throw new IOException("This StreamGobbler is closed."); + + while (read_pos == write_pos) { + if (exception != null) + throw exception; + + if (isEOF) + return -1; + + try { + synchronizer.wait(); + } + catch (InterruptedException e) { + throw new InterruptedIOException(); + } + } + + int avail = write_pos - read_pos; + avail = (avail > len) ? len : avail; + System.arraycopy(buffer, read_pos, b, off, avail); + read_pos += avail; + return avail; + } + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/Version.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/Version.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,26 @@ +package ch.ethz.ssh2; + +/** + * Provides version information from the manifest. + * + * @version $Id: Version.java 140 2014-04-15 13:16:25Z dkocher@sudo.ch $ + */ +public class Version { + public static String getSpecification() { + Package pkg = Version.class.getPackage(); + return (pkg == null) ? "SNAPSHOT" : pkg.getSpecificationVersion() == null ? "SNAPSHOT" : pkg.getSpecificationVersion(); + } + + public static String getImplementation() { + Package pkg = Version.class.getPackage(); + return (pkg == null) ? "SNAPSHOT" : pkg.getImplementationVersion() == null ? "SNAPSHOT" : pkg.getImplementationVersion(); + } + + /** + * A simple main method that prints the version and exits + */ + public static void main(String[] args) { + System.out.println("Version: " + getSpecification()); + System.out.println("Implementation: " + getImplementation()); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/auth/AgentIdentity.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/auth/AgentIdentity.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.auth; + +public interface AgentIdentity { + String getAlgName(); + byte[] getPublicKeyBlob(); + byte[] sign(byte[] data); +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/auth/AgentProxy.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/auth/AgentProxy.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.auth; + +import java.util.Collection; + +public interface AgentProxy { + public Collection getIdentities(); +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/auth/AuthenticationManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/auth/AuthenticationManager.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,424 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.auth; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.SecureRandom; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +import ch.ethz.ssh2.InteractiveCallback; +import ch.ethz.ssh2.PacketTypeException; +import ch.ethz.ssh2.crypto.PEMDecoder; +import ch.ethz.ssh2.packets.PacketServiceAccept; +import ch.ethz.ssh2.packets.PacketServiceRequest; +import ch.ethz.ssh2.packets.PacketUserauthBanner; +import ch.ethz.ssh2.packets.PacketUserauthFailure; +import ch.ethz.ssh2.packets.PacketUserauthInfoRequest; +import ch.ethz.ssh2.packets.PacketUserauthInfoResponse; +import ch.ethz.ssh2.packets.PacketUserauthRequestInteractive; +import ch.ethz.ssh2.packets.PacketUserauthRequestNone; +import ch.ethz.ssh2.packets.PacketUserauthRequestPassword; +import ch.ethz.ssh2.packets.PacketUserauthRequestPublicKey; +import ch.ethz.ssh2.packets.Packets; +import ch.ethz.ssh2.packets.TypesWriter; +import ch.ethz.ssh2.signature.DSASHA1Verify; +import ch.ethz.ssh2.signature.ECDSASHA2Verify; +import ch.ethz.ssh2.signature.RSASHA1Verify; +import ch.ethz.ssh2.transport.ClientTransportManager; +import ch.ethz.ssh2.transport.MessageHandler; + +/** + * @author Christian Plattner + * @version $Id: AuthenticationManager.java 161 2014-05-01 18:01:55Z dkocher@sudo.ch $ + */ +public class AuthenticationManager implements MessageHandler { + private ClientTransportManager tm; + + private final BlockingQueue packets + = new ArrayBlockingQueue(5); + + private boolean connectionClosed = false; + + private String banner; + + private Set remainingMethods + = new HashSet(); + + private boolean isPartialSuccess = false; + + private boolean authenticated = false; + private boolean initDone = false; + + public AuthenticationManager(ClientTransportManager tm) { + this.tm = tm; + } + + private byte[] deQueue() throws IOException { + if (connectionClosed) { + throw(IOException) new IOException("The connection is closed.").initCause(tm.getReasonClosedCause()); + } + + // Wait for packet + try { + return packets.take(); + } + catch (InterruptedException e) { + throw new InterruptedIOException(e.getMessage()); + } + } + + byte[] getNextMessage() throws IOException { + while (true) { + byte[] message = deQueue(); + + switch (message[0]) { + case Packets.SSH_MSG_USERAUTH_BANNER: + // The server may send an SSH_MSG_USERAUTH_BANNER message at any + // time after this authentication protocol starts and before + // authentication is successful. + PacketUserauthBanner sb = new PacketUserauthBanner(message); + banner = sb.getBanner(); + break; + + default: + return message; + } + } + } + + public Set getRemainingMethods(String user) throws IOException { + initialize(user); + return remainingMethods; + } + + public String getBanner() { + return banner; + } + + public boolean getPartialSuccess() { + return isPartialSuccess; + } + + private boolean initialize(String user) throws IOException { + if (initDone == false) { + tm.registerMessageHandler(this, 0, 255); + PacketServiceRequest sr = new PacketServiceRequest("ssh-userauth"); + tm.sendMessage(sr.getPayload()); + final PacketServiceAccept accept = new PacketServiceAccept(this.getNextMessage()); + PacketUserauthRequestNone auth = new PacketUserauthRequestNone("ssh-connection", user); + tm.sendMessage(auth.getPayload()); + byte[] message = this.getNextMessage(); + initDone = true; + + switch (message[0]) { + case Packets.SSH_MSG_USERAUTH_SUCCESS: + authenticated = true; + tm.removeMessageHandler(this); + return true; + + case Packets.SSH_MSG_USERAUTH_FAILURE: + PacketUserauthFailure puf = new PacketUserauthFailure(message); + remainingMethods = puf.getAuthThatCanContinue(); + isPartialSuccess = puf.isPartialSuccess(); + return false; + } + + throw new PacketTypeException(message[0]); + } + + return authenticated; + } + + public boolean authenticatePublicKey(String user, AgentProxy proxy) throws IOException { + initialize(user); + boolean success; + + for (AgentIdentity identity : proxy.getIdentities()) { + success = authenticatePublicKey(user, identity); + + if (success) { + return true; + } + } + + return false; + } + + private boolean authenticatePublicKey(String user, AgentIdentity identity) throws IOException { + if (!remainingMethods.contains("publickey")) { + throw new IOException("Authentication method not supported"); + } + + byte[] pubKeyBlob = identity.getPublicKeyBlob(); + + if (pubKeyBlob == null) { + return false; + } + + 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(identity.getAlgName()); + tw.writeString(pubKeyBlob, 0, pubKeyBlob.length); + byte[] msg = tw.getBytes(); + byte[] response = identity.sign(msg); + PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey( + "ssh-connection", user, identity.getAlgName(), pubKeyBlob, response); + tm.sendMessage(ua.getPayload()); + byte[] message = getNextMessage(); + final int type = message[0]; + + switch (type) { + case Packets.SSH_MSG_USERAUTH_SUCCESS: + authenticated = true; + tm.removeMessageHandler(this); + return true; + + case Packets.SSH_MSG_USERAUTH_FAILURE: + PacketUserauthFailure puf = new PacketUserauthFailure(message); + remainingMethods = puf.getAuthThatCanContinue(); + isPartialSuccess = puf.isPartialSuccess(); + return false; + } + + throw new PacketTypeException(type); + } + + public boolean authenticatePublicKey(String user, char[] PEMPrivateKey, String password, SecureRandom rnd) + throws IOException { + KeyPair pair = PEMDecoder.decode(PEMPrivateKey, password); + return authenticatePublicKey(user, pair, rnd); + } + + public boolean authenticatePublicKey(String user, KeyPair pair, SecureRandom rnd) + throws IOException { + PrivateKey key = pair.getPrivate(); + + try { + initialize(user); + + if (!remainingMethods.contains("publickey")) { + throw new IOException("Authentication method publickey not supported by the server at this stage."); + } + + if (key instanceof DSAPrivateKey) { + DSAPrivateKey pk = (DSAPrivateKey) key; + byte[] pk_enc = DSASHA1Verify.encodeSSHDSAPublicKey((DSAPublicKey) 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-dss"); + tw.writeString(pk_enc, 0, pk_enc.length); + byte[] msg = tw.getBytes(); + byte[] ds = DSASHA1Verify.generateSignature(msg, pk, rnd); + byte[] ds_enc = DSASHA1Verify.encodeSSHDSASignature(ds); + PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user, + "ssh-dss", pk_enc, ds_enc); + tm.sendMessage(ua.getPayload()); + } + else if (key instanceof RSAPrivateKey) { + RSAPrivateKey pk = (RSAPrivateKey) 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 = RSASHA1Verify.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; + final String algo = ECDSASHA2Verify.ECDSA_SHA2_PREFIX + + ECDSASHA2Verify.getCurveName(pk.getParams()); + byte[] pk_enc = ECDSASHA2Verify.encodeSSHECDSAPublicKey((ECPublicKey) 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(algo); + tw.writeString(pk_enc, 0, pk_enc.length); + } + byte[] msg = tw.getBytes(); + byte[] ds = ECDSASHA2Verify.generateSignature(msg, pk); + byte[] ec_sig_enc = ECDSASHA2Verify.encodeSSHECDSASignature(ds, pk.getParams()); + PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user, + algo, pk_enc, ec_sig_enc); + tm.sendMessage(ua.getPayload()); + } + else { + throw new IOException("Unknown private key type returned by the PEM decoder."); + } + + byte[] message = getNextMessage(); + final int type = message[0]; + + switch (type) { + case Packets.SSH_MSG_USERAUTH_SUCCESS: + authenticated = true; + tm.removeMessageHandler(this); + return true; + + case Packets.SSH_MSG_USERAUTH_FAILURE: + PacketUserauthFailure puf = new PacketUserauthFailure(message); + remainingMethods = puf.getAuthThatCanContinue(); + isPartialSuccess = puf.isPartialSuccess(); + return false; + } + + throw new PacketTypeException(type); + } + catch (IOException e) { + tm.close(e, false); + throw e; + } + } + + public boolean authenticateNone(String user) throws IOException { + try { + initialize(user); + return authenticated; + } + catch (IOException e) { + tm.close(e, false); + throw e; + } + } + + public boolean authenticatePassword(String user, String pass) throws IOException { + try { + initialize(user); + + if (!remainingMethods.contains("password")) { + throw new IOException("Authentication method not supported"); + } + + PacketUserauthRequestPassword ua = new PacketUserauthRequestPassword("ssh-connection", user, pass); + tm.sendMessage(ua.getPayload()); + byte[] message = getNextMessage(); + final int type = message[0]; + + switch (type) { + case Packets.SSH_MSG_USERAUTH_SUCCESS: + authenticated = true; + tm.removeMessageHandler(this); + return true; + + case Packets.SSH_MSG_USERAUTH_FAILURE: + PacketUserauthFailure puf = new PacketUserauthFailure(message); + remainingMethods = puf.getAuthThatCanContinue(); + isPartialSuccess = puf.isPartialSuccess(); + return false; + } + + throw new PacketTypeException(type); + } + catch (IOException e) { + tm.close(e, false); + throw e; + } + } + + public boolean authenticateInteractive(String user, String[] submethods, InteractiveCallback cb) throws IOException { + try { + initialize(user); + + if (!remainingMethods.contains("keyboard-interactive")) { + throw new IOException( + "Authentication method keyboard-interactive not supported by the server at this stage."); + } + + PacketUserauthRequestInteractive ua = new PacketUserauthRequestInteractive("ssh-connection", user, + submethods); + tm.sendMessage(ua.getPayload()); + + while (true) { + byte[] message = getNextMessage(); + final int type = message[0]; + + switch (type) { + case Packets.SSH_MSG_USERAUTH_SUCCESS: + authenticated = true; + tm.removeMessageHandler(this); + return true; + + case Packets.SSH_MSG_USERAUTH_FAILURE: + PacketUserauthFailure puf = new PacketUserauthFailure(message); + remainingMethods = puf.getAuthThatCanContinue(); + isPartialSuccess = puf.isPartialSuccess(); + return false; + + case Packets.SSH_MSG_USERAUTH_INFO_REQUEST: + PacketUserauthInfoRequest info = new PacketUserauthInfoRequest(message); + String[] responses; + + try { + responses = cb.replyToChallenge(info.getName(), info.getInstruction(), info.getNumPrompts(), + info.getPrompt(), info.getEcho()); + } + catch (Exception e) { + throw new IOException("Exception in callback.", e); + } + + PacketUserauthInfoResponse puir = new PacketUserauthInfoResponse(responses); + tm.sendMessage(puir.getPayload()); + continue; + } + + throw new PacketTypeException(type); + } + } + catch (IOException e) { + tm.close(e, false); + throw e; + } + } + + public void handleFailure(final IOException failure) { + connectionClosed = true; + } + + public void handleMessage(byte[] message) throws IOException { + packets.add(message); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/auth/ServerAuthenticationManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/auth/ServerAuthenticationManager.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,128 @@ + +package ch.ethz.ssh2.auth; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import ch.ethz.ssh2.AuthenticationResult; +import ch.ethz.ssh2.PacketTypeException; +import ch.ethz.ssh2.ServerAuthenticationCallback; +import ch.ethz.ssh2.channel.ChannelManager; +import ch.ethz.ssh2.packets.PacketServiceAccept; +import ch.ethz.ssh2.packets.PacketServiceRequest; +import ch.ethz.ssh2.packets.PacketUserauthBanner; +import ch.ethz.ssh2.packets.PacketUserauthFailure; +import ch.ethz.ssh2.packets.PacketUserauthSuccess; +import ch.ethz.ssh2.packets.Packets; +import ch.ethz.ssh2.packets.TypesReader; +import ch.ethz.ssh2.server.ServerConnectionState; +import ch.ethz.ssh2.transport.MessageHandler; + +public class ServerAuthenticationManager implements MessageHandler { + private final ServerConnectionState state; + + public ServerAuthenticationManager(ServerConnectionState state) { + this.state = state; + state.tm.registerMessageHandler(this, 0, 255); + } + + private void sendresult(AuthenticationResult result) throws IOException { + if (AuthenticationResult.SUCCESS == result) { + PacketUserauthSuccess pus = new PacketUserauthSuccess(); + state.tm.sendAsynchronousMessage(pus.getPayload()); + state.tm.removeMessageHandler(this); + state.tm.registerMessageHandler(this, 50, 79); + state.cm = new ChannelManager(state); + state.flag_auth_completed = true; + } + else { + Set remaining_methods = new HashSet(); + + if (state.cb_auth != null) { + remaining_methods.addAll(Arrays.asList( + state.cb_auth.getRemainingAuthMethods(state.conn))); + } + + PacketUserauthFailure puf = new PacketUserauthFailure(remaining_methods, + AuthenticationResult.PARTIAL_SUCCESS == result); + state.tm.sendAsynchronousMessage(puf.getPayload()); + } + } + + public void handleFailure(final IOException failure) { + // + } + + public void handleMessage(byte[] msg) throws IOException { + /* Ignore all authentication messages after successful auth */ + if (state.flag_auth_completed) { + return; + } + + if (!state.flag_auth_serviceRequested) { + /* Must be PacketServiceRequest */ + PacketServiceRequest psr = new PacketServiceRequest(msg); + + if (!"ssh-userauth".equals(psr.getServiceName())) { + throw new IOException("SSH protocol error, expected ssh-userauth service request"); + } + + PacketServiceAccept psa = new PacketServiceAccept("ssh-userauth"); + state.tm.sendAsynchronousMessage(psa.getPayload()); + String banner = state.cb_auth.initAuthentication(state.conn); + + if (banner != null) { + PacketUserauthBanner pub = new PacketUserauthBanner(banner); + state.tm.sendAsynchronousMessage(pub.getPayload()); + } + + state.flag_auth_serviceRequested = true; + return; + } + + ServerAuthenticationCallback cb = state.cb_auth; + TypesReader tr = new TypesReader(msg); + int packet_type = tr.readByte(); + + if (packet_type == Packets.SSH_MSG_USERAUTH_REQUEST) { + String username = tr.readString("UTF-8"); + String service = tr.readString(); + String method = tr.readString(); + + if (!"ssh-connection".equals(service)) { + sendresult(AuthenticationResult.FAILURE); + return; + } + + if ("none".equals(method)) { + if (cb != null) { + sendresult(cb.authenticateWithNone(state.conn, username)); + return; + } + } + + if ("password".equals(method)) { + boolean flag_change_pass = tr.readBoolean(); + + if (flag_change_pass) { + sendresult(AuthenticationResult.FAILURE); + return; + } + + String password = tr.readString("UTF-8"); + + if (cb != null) { + sendresult(cb.authenticateWithPassword(state.conn, username, password)); + return; + } + } + + sendresult(AuthenticationResult.FAILURE); + return; + } + + throw new PacketTypeException(packet_type); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 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 Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,546 @@ +/* + * 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.ECPrivateKey; +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; + log.debug("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.debug("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.debug("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) { + log.debug("No groupfor ecdsa-sha2-nistp256: "); + os.write(SSH_AGENT_FAILURE); + return; + } + + pubSpec = new ECPublicKeySpec(group, nistp256); + privSpec = new ECPrivateKeySpec(exponent, nistp256); + } + else { + log.debug("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 if (privKey instanceof ECPrivateKey) { + ECPrivateKey pk = (ECPrivateKey) privKey; + byte[] signature = ECDSASHA2Verify.generateSignature(challenge, pk); + response = ECDSASHA2Verify.encodeSSHECDSASignature(signature, pk.getParams()); + } + 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 ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/channel/Channel.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/channel/Channel.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.channel; + +import java.io.IOException; + +import ch.ethz.ssh2.transport.TransportManager; + +/** + * Channel. + * + * @author Christian Plattner + * @version $Id: Channel.java 123 2014-04-12 21:11:47Z dkocher@sudo.ch $ + */ +public class Channel { + /* + * OK. Here is an important part of the JVM Specification: + * (http://java.sun.com/docs/books/vmspec/2nd-edition/html/Threads.doc.html#22214) + * + * Any association between locks and variables is purely conventional. + * Locking any lock conceptually flushes all variables from a thread's + * working memory, and unlocking any lock forces the writing out to main + * memory of all variables that the thread has assigned. That a lock may be + * associated with a particular object or a class is purely a convention. + * (...) + * + * If a thread uses a particular shared variable only after locking a + * particular lock and before the corresponding unlocking of that same lock, + * then the thread will read the shared value of that variable from main + * memory after the lock operation, if necessary, and will copy back to main + * memory the value most recently assigned to that variable before the + * unlock operation. + * + * This, in conjunction with the mutual exclusion rules for locks, suffices + * to guarantee that values are correctly transmitted from one thread to + * another through shared variables. + * + * ====> Always keep that in mind when modifying the Channel/ChannelManger + * code. + * + */ + + public static final int STATE_OPENING = 1; + public static final int STATE_OPEN = 2; + public static final int STATE_CLOSED = 4; + + static final int CHANNEL_BUFFER_SIZE = 32 * 1024 * 3 * 2; + + /* + * To achieve correctness, the following rules have to be respected when + * accessing this object: + */ + + // These fields can always be read + final ChannelManager cm; + final ChannelOutputStream stdinStream; + final ChannelInputStream stdoutStream; + final ChannelInputStream stderrStream; + + // In case this channel belongs to a server-side session. + ServerSessionImpl ss; + + // These two fields will only be written while the Channel is in state + // STATE_OPENING. + // The code makes sure that the two fields are written out when the state is + // changing to STATE_OPEN. + // Therefore, if you know that the Channel is in state STATE_OPEN, then you + // can read these two fields without synchronizing on the Channel. However, make + // sure that you get the latest values (e.g., flush caches by synchronizing on any + // object). However, to be on the safe side, you can lock the channel. + + int localID = -1; + int remoteID = -1; + + /* + * Make sure that we never send a data/EOF/WindowChange msg after a CLOSE + * msg. + * + * This is a little bit complicated, but we have to do it in that way, since + * we cannot keep a lock on the Channel during the send operation (this + * would block sometimes the receiver thread, and, in extreme cases, can + * lead to a deadlock on both sides of the connection (senders are blocked + * since the receive buffers on the other side are full, and receiver + * threads wait for the senders to finish). It all depends on the + * implementation on the other side. But we cannot make any assumptions, we + * have to assume the worst case. Confused? Just believe me. + */ + + /* + * If you send a message on a channel, then you have to aquire the + * "channelSendLock" and check the "closeMessageSent" flag (this variable + * may only be accessed while holding the "channelSendLock" !!! + * + * BTW: NEVER EVER SEND MESSAGES FROM THE RECEIVE THREAD - see explanation + * above. + */ + + final Object channelSendLock = new Object(); + boolean closeMessageSent = false; + + /* + * Stop memory fragmentation by allocating this often used buffer. + * May only be used while holding the channelSendLock + */ + + final byte[] msgWindowAdjust = new byte[9]; + + // If you access (read or write) any of the following fields, then you have + // to synchronize on the channel. + + int state = STATE_OPENING; + + boolean closeMessageRecv = false; + + /* This is a stupid implementation. At the moment we can only wait + * for one pending request per channel. + */ + int successCounter = 0; + int failedCounter = 0; + + int localWindow = 0; /* locally, we use a small window, < 2^31 */ + long remoteWindow = 0; /* long for readable 2^32 - 1 window support */ + + int localMaxPacketSize = -1; + int remoteMaxPacketSize = -1; + + final byte[] stdoutBuffer = new byte[CHANNEL_BUFFER_SIZE]; + final byte[] stderrBuffer = new byte[CHANNEL_BUFFER_SIZE]; + + int stdoutReadpos = 0; + int stdoutWritepos = 0; + int stderrReadpos = 0; + int stderrWritepos = 0; + + boolean EOF = false; + + Integer exit_status; + + String exit_signal; + + // we keep the x11 cookie so that this channel can be closed when this + // specific x11 forwarding gets stopped + + String hexX11FakeCookie; + + // reasonClosed is special, since we sometimes need to access it + // while holding the channelSendLock. + // We protect it with a private short term lock. + + private final Object reasonClosedLock = new Object(); + private IOException reasonClosed = null; + + public Channel(ChannelManager cm) { + this.cm = cm; + this.localWindow = CHANNEL_BUFFER_SIZE; + this.localMaxPacketSize = TransportManager.MAX_PACKET_SIZE; + this.stdinStream = new ChannelOutputStream(this); + this.stdoutStream = new ChannelInputStream(this, false); + this.stderrStream = new ChannelInputStream(this, true); + } + + /* Methods to allow access from classes outside of this package */ + + public ChannelInputStream getStderrStream() { + return stderrStream; + } + + public ChannelOutputStream getStdinStream() { + return stdinStream; + } + + public ChannelInputStream getStdoutStream() { + return stdoutStream; + } + + public String getExitSignal() { + synchronized (this) { + return exit_signal; + } + } + + public Integer getExitStatus() { + synchronized (this) { + return exit_status; + } + } + + public IOException getReasonClosed() { + synchronized (reasonClosedLock) { + return reasonClosed; + } + } + + public void setReasonClosed(IOException e) { + synchronized (reasonClosedLock) { + this.reasonClosed = e; + } + } + + public int getState() { + return this.state; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/channel/ChannelClosedException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/channel/ChannelClosedException.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2011 David Kocher. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2.channel; + +import java.io.IOException; + +/** + * @version $Id: ChannelClosedException.java 3183 2007-07-30 19:22:34Z dkocher $ + */ +public class ChannelClosedException extends IOException { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public ChannelClosedException(String s) { + super(s); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/channel/ChannelInputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/channel/ChannelInputStream.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.channel; + +import java.io.IOException; +import java.io.InputStream; + +/** + * ChannelInputStream. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public final class ChannelInputStream extends InputStream { + Channel c; + + boolean isClosed = false; + boolean isEOF = false; + boolean extendedFlag = false; + + ChannelInputStream(Channel c, boolean isExtended) { + this.c = c; + this.extendedFlag = isExtended; + } + + @Override + public int available() throws IOException { + if (isEOF) + return 0; + + int avail = c.cm.getAvailable(c, extendedFlag); + /* We must not return -1 on EOF */ + return (avail > 0) ? avail : 0; + } + + @Override + public void close() throws IOException { + isClosed = true; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (b == null) + throw new NullPointerException(); + + if ((off < 0) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) || (off > b.length)) + throw new IndexOutOfBoundsException(); + + if (len == 0) + return 0; + + if (isEOF) + return -1; + + int ret = c.cm.getChannelData(c, extendedFlag, b, off, len); + + if (ret == -1) { + isEOF = true; + } + + return ret; + } + + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public int read() throws IOException { + /* Yes, this stream is pure and unbuffered, a single byte read() is slow */ + final byte b[] = new byte[1]; + int ret = read(b, 0, 1); + + if (ret != 1) + return -1; + + return b[0] & 0xff; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/channel/ChannelManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/channel/ChannelManager.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,1660 @@ +/* + + * Copyright (c) 2006-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2.channel; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import ch.ethz.ssh2.AuthAgentCallback; +import ch.ethz.ssh2.ChannelCondition; +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; +import ch.ethz.ssh2.PtySettings; +import ch.ethz.ssh2.ServerConnectionCallback; +import ch.ethz.ssh2.ServerSessionCallback; +import ch.ethz.ssh2.log.Logger; +import ch.ethz.ssh2.packets.PacketChannelAuthAgentReq; +import ch.ethz.ssh2.packets.PacketChannelFailure; +import ch.ethz.ssh2.packets.PacketChannelOpenConfirmation; +import ch.ethz.ssh2.packets.PacketChannelOpenFailure; +import ch.ethz.ssh2.packets.PacketChannelSuccess; +import ch.ethz.ssh2.packets.PacketGlobalCancelForwardRequest; +import ch.ethz.ssh2.packets.PacketGlobalForwardRequest; +import ch.ethz.ssh2.packets.PacketOpenDirectTCPIPChannel; +import ch.ethz.ssh2.packets.PacketOpenSessionChannel; +import ch.ethz.ssh2.packets.PacketSessionExecCommand; +import ch.ethz.ssh2.packets.PacketSessionPtyRequest; +import ch.ethz.ssh2.packets.PacketSessionStartShell; +import ch.ethz.ssh2.packets.PacketSessionSubsystemRequest; +import ch.ethz.ssh2.packets.PacketSessionX11Request; +import ch.ethz.ssh2.packets.PacketWindowChange; +import ch.ethz.ssh2.packets.Packets; +import ch.ethz.ssh2.packets.TypesReader; +import ch.ethz.ssh2.server.ServerConnectionState; +import ch.ethz.ssh2.transport.MessageHandler; +import ch.ethz.ssh2.transport.TransportManager; + +/** + * ChannelManager. Please read the comments in Channel.java. + *

+ * Besides the crypto part, this is the core of the library. + * + * @author Christian Plattner + * @version $Id: ChannelManager.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public class ChannelManager implements MessageHandler { + private static final Logger log = Logger.getLogger(ChannelManager.class); + + private final ServerConnectionState server_state; + private final TransportManager tm; + + private final Map x11_magic_cookies = new HashMap(); + + private final List channels = new ArrayList(); + private int nextLocalChannel = 100; + private boolean shutdown = false; + private int globalSuccessCounter = 0; + private int globalFailedCounter = 0; + + private final HashMap remoteForwardings = new HashMap(); + + private AuthAgentCallback authAgent; + + private final List listenerThreads = new ArrayList(); + + private boolean listenerThreadsAllowed = true; + + /** + * Constructor for client-mode. + * + * @param tm + */ + public ChannelManager(TransportManager tm) { + this.server_state = null; + this.tm = tm; + tm.registerMessageHandler(this, 80, 100); + } + + /** + * Constructor for server-mode. + * + * @param state + */ + public ChannelManager(ServerConnectionState state) { + this.server_state = state; + this.tm = state.tm; + tm.registerMessageHandler(this, 80, 100); + } + + private Channel getChannel(int id) { + synchronized (channels) { + for (Channel c : channels) { + if (c.localID == id) { + return c; + } + } + } + + return null; + } + + private void removeChannel(int id) { + synchronized (channels) { + for (Channel c : channels) { + if (c.localID == id) { + channels.remove(c); + break; + } + } + } + } + + private int addChannel(Channel c) { + synchronized (channels) { + channels.add(c); + return nextLocalChannel++; + } + } + + private void waitUntilChannelOpen(Channel c) throws IOException { + synchronized (c) { + while (c.state == Channel.STATE_OPENING) { + try { + c.wait(); + } + catch (InterruptedException e) { + throw new InterruptedIOException(e.getMessage()); + } + } + + if (c.state != Channel.STATE_OPEN) { + removeChannel(c.localID); + throw c.getReasonClosed(); + } + } + } + + private void waitForGlobalSuccessOrFailure() throws IOException { + synchronized (channels) { + while ((globalSuccessCounter == 0) && (globalFailedCounter == 0)) { + if (shutdown) { + throw new IOException("The connection is being shutdown"); + } + + try { + channels.wait(); + } + catch (InterruptedException e) { + throw new InterruptedIOException(e.getMessage()); + } + } + + if ((globalFailedCounter == 0) && (globalSuccessCounter == 1)) { + return; + } + + if ((globalFailedCounter == 1) && (globalSuccessCounter == 0)) { + throw new IOException("The server denied the request (did you enable port forwarding?)"); + } + + throw new IOException("Illegal state. The server sent " + globalSuccessCounter + + " SSH_MSG_REQUEST_SUCCESS and " + globalFailedCounter + " SSH_MSG_REQUEST_FAILURE messages."); + } + } + + private void waitForChannelSuccessOrFailure(Channel c) throws IOException { + synchronized (c) { + while ((c.successCounter == 0) && (c.failedCounter == 0)) { + if (c.state != Channel.STATE_OPEN) { + throw c.getReasonClosed(); + } + + try { + c.wait(); + } + catch (InterruptedException ignore) { + throw new InterruptedIOException(); + } + } + + if ((c.failedCounter == 0) && (c.successCounter == 1)) { + return; + } + + if ((c.failedCounter == 1) && (c.successCounter == 0)) { + throw new IOException("The server denied the request."); + } + + throw new IOException("Illegal state. The server sent " + c.successCounter + + " SSH_MSG_CHANNEL_SUCCESS and " + c.failedCounter + " SSH_MSG_CHANNEL_FAILURE messages."); + } + } + + public void registerX11Cookie(String hexFakeCookie, X11ServerData data) { + synchronized (x11_magic_cookies) { + x11_magic_cookies.put(hexFakeCookie, data); + } + } + + public void unRegisterX11Cookie(String hexFakeCookie, boolean killChannels) { + if (hexFakeCookie == null) { + throw new IllegalStateException("hexFakeCookie may not be null"); + } + + synchronized (x11_magic_cookies) { + x11_magic_cookies.remove(hexFakeCookie); + } + + if (killChannels == false) { + return; + } + + log.debug("Closing all X11 channels for the given fake cookie"); + List channel_copy = new ArrayList(); + + synchronized (channels) { + channel_copy.addAll(channels); + } + + for (Channel c : channel_copy) { + synchronized (c) { + if (hexFakeCookie.equals(c.hexX11FakeCookie) == false) { + continue; + } + } + + try { + closeChannel(c, "Closing X11 channel since the corresponding session is closing", true); + } + catch (IOException ignored) { + } + } + } + + public X11ServerData checkX11Cookie(String hexFakeCookie) { + synchronized (x11_magic_cookies) { + if (hexFakeCookie != null) { + return x11_magic_cookies.get(hexFakeCookie); + } + } + + return null; + } + + public void closeAllChannels() { + log.debug("Closing all channels"); + List channel_copy = new ArrayList(); + + synchronized (channels) { + channel_copy.addAll(channels); + } + + for (Channel c : channel_copy) { + try { + closeChannel(c, "Closing all channels", true); + } + catch (IOException ignored) { + } + } + } + + public void closeChannel(Channel c, String reason, boolean force) throws IOException { + this.closeChannel(c, new ChannelClosedException(reason), force); + } + + public void closeChannel(Channel c, IOException reason, boolean force) throws IOException { + byte msg[] = new byte[5]; + + synchronized (c) { + if (force) { + c.state = Channel.STATE_CLOSED; + c.EOF = true; + } + + c.setReasonClosed(reason); + msg[0] = Packets.SSH_MSG_CHANNEL_CLOSE; + msg[1] = (byte)(c.remoteID >> 24); + msg[2] = (byte)(c.remoteID >> 16); + msg[3] = (byte)(c.remoteID >> 8); + msg[4] = (byte)(c.remoteID); + c.notifyAll(); + } + + synchronized (c.channelSendLock) { + if (c.closeMessageSent) { + return; + } + + tm.sendMessage(msg); + c.closeMessageSent = true; + } + + log.debug("Sent SSH_MSG_CHANNEL_CLOSE (channel " + c.localID + ")"); + } + + public void sendEOF(Channel c) throws IOException { + byte[] msg = new byte[5]; + + synchronized (c) { + if (c.state != Channel.STATE_OPEN) { + return; + } + + msg[0] = Packets.SSH_MSG_CHANNEL_EOF; + msg[1] = (byte)(c.remoteID >> 24); + msg[2] = (byte)(c.remoteID >> 16); + msg[3] = (byte)(c.remoteID >> 8); + msg[4] = (byte)(c.remoteID); + } + + synchronized (c.channelSendLock) { + if (c.closeMessageSent == true) { + return; + } + + tm.sendMessage(msg); + } + + log.debug("Sent EOF (Channel " + c.localID + "/" + c.remoteID + ")"); + } + + public void sendOpenConfirmation(Channel c) throws IOException { + PacketChannelOpenConfirmation pcoc = null; + + synchronized (c) { + if (c.state != Channel.STATE_OPENING) { + return; + } + + c.state = Channel.STATE_OPEN; + pcoc = new PacketChannelOpenConfirmation(c.remoteID, c.localID, c.localWindow, c.localMaxPacketSize); + } + + synchronized (c.channelSendLock) { + if (c.closeMessageSent == true) { + return; + } + + tm.sendMessage(pcoc.getPayload()); + } + } + + public void sendData(Channel c, byte[] buffer, int pos, int len) throws IOException { + while (len > 0) { + int thislen = 0; + byte[] msg; + + synchronized (c) { + while (true) { + if (c.state == Channel.STATE_CLOSED) { + throw c.getReasonClosed(); + } + + if (c.state != Channel.STATE_OPEN) { + throw new ChannelClosedException("SSH channel in strange state. (" + c.state + ")"); + } + + if (c.remoteWindow != 0) { + break; + } + + try { + c.wait(); + } + catch (InterruptedException e) { + throw new InterruptedIOException(e.getMessage()); + } + } + + /* len > 0, no sign extension can happen when comparing */ + thislen = (c.remoteWindow >= len) ? len : (int) c.remoteWindow; + int estimatedMaxDataLen = c.remoteMaxPacketSize - (tm.getPacketOverheadEstimate() + 9); + + /* The worst case scenario =) a true bottleneck */ + + if (estimatedMaxDataLen <= 0) { + estimatedMaxDataLen = 1; + } + + if (thislen > estimatedMaxDataLen) { + thislen = estimatedMaxDataLen; + } + + c.remoteWindow -= thislen; + msg = new byte[1 + 8 + thislen]; + msg[0] = Packets.SSH_MSG_CHANNEL_DATA; + msg[1] = (byte)(c.remoteID >> 24); + msg[2] = (byte)(c.remoteID >> 16); + msg[3] = (byte)(c.remoteID >> 8); + msg[4] = (byte)(c.remoteID); + msg[5] = (byte)(thislen >> 24); + msg[6] = (byte)(thislen >> 16); + msg[7] = (byte)(thislen >> 8); + msg[8] = (byte)(thislen); + System.arraycopy(buffer, pos, msg, 9, thislen); + } + + synchronized (c.channelSendLock) { + if (c.closeMessageSent) { + throw c.getReasonClosed(); + } + + tm.sendMessage(msg); + } + + pos += thislen; + len -= thislen; + } + } + + public int requestGlobalForward(String bindAddress, int bindPort, String targetAddress, int targetPort) + throws IOException { + RemoteForwardingData rfd = new RemoteForwardingData(); + rfd.bindAddress = bindAddress; + rfd.bindPort = bindPort; + rfd.targetAddress = targetAddress; + rfd.targetPort = targetPort; + + synchronized (remoteForwardings) { + if (remoteForwardings.get(bindPort) != null) { + throw new IOException("There is already a forwarding for remote port " + bindPort); + } + + remoteForwardings.put(bindPort, rfd); + } + + synchronized (channels) { + globalSuccessCounter = globalFailedCounter = 0; + } + + PacketGlobalForwardRequest pgf = new PacketGlobalForwardRequest(true, bindAddress, bindPort); + tm.sendMessage(pgf.getPayload()); + log.debug("Requesting a remote forwarding ('" + bindAddress + "', " + bindPort + ")"); + + try { + waitForGlobalSuccessOrFailure(); + } + catch (IOException e) { + synchronized (remoteForwardings) { + remoteForwardings.remove(bindPort); + } + + throw e; + } + + return bindPort; + } + + public void requestCancelGlobalForward(int bindPort) throws IOException { + RemoteForwardingData rfd; + + synchronized (remoteForwardings) { + rfd = remoteForwardings.get(bindPort); + + if (rfd == null) { + throw new IOException("Sorry, there is no known remote forwarding for remote port " + bindPort); + } + } + + synchronized (channels) { + globalSuccessCounter = globalFailedCounter = 0; + } + + PacketGlobalCancelForwardRequest pgcf = new PacketGlobalCancelForwardRequest(true, rfd.bindAddress, + rfd.bindPort); + tm.sendMessage(pgcf.getPayload()); + log.debug("Requesting cancelation of remote forward ('" + rfd.bindAddress + "', " + rfd.bindPort + ")"); + waitForGlobalSuccessOrFailure(); + + /* Only now we are sure that no more forwarded connections will arrive */ + + synchronized (remoteForwardings) { + remoteForwardings.remove(bindPort); + } + } + + /** + * @param agent + * @throws IOException + */ + public void requestChannelAgentForwarding(Channel c, AuthAgentCallback authAgent) throws IOException { + synchronized (this) { + if (this.authAgent != null) + throw new IllegalStateException("Auth agent already exists"); + + this.authAgent = authAgent; + } + + synchronized (channels) { + globalSuccessCounter = globalFailedCounter = 0; + } + + log.debug("Requesting agent forwarding"); + PacketChannelAuthAgentReq aar = new PacketChannelAuthAgentReq(c.remoteID); + + synchronized (c.channelSendLock) { + if (c.closeMessageSent) { + throw c.getReasonClosed(); + } + + tm.sendMessage(aar.getPayload()); + } + + try { + waitForChannelSuccessOrFailure(c); + } + catch (IOException e) { + authAgent = null; + throw e; + } + } + + public void registerThread(IChannelWorkerThread thr) throws IOException { + synchronized (listenerThreads) { + if (listenerThreadsAllowed == false) { + throw new IOException("Too late, this connection is closed."); + } + + listenerThreads.add(thr); + } + } + + public Channel openDirectTCPIPChannel(String host_to_connect, int port_to_connect, String originator_IP_address, + int originator_port) throws IOException { + Channel c = new Channel(this); + + synchronized (c) { + c.localID = addChannel(c); + // end of synchronized block forces writing out to main memory + } + + PacketOpenDirectTCPIPChannel dtc = new PacketOpenDirectTCPIPChannel(c.localID, c.localWindow, + c.localMaxPacketSize, host_to_connect, port_to_connect, originator_IP_address, originator_port); + tm.sendMessage(dtc.getPayload()); + waitUntilChannelOpen(c); + return c; + } + + public Channel openSessionChannel() throws IOException { + Channel c = new Channel(this); + + synchronized (c) { + c.localID = addChannel(c); + // end of synchronized block forces the writing out to main memory + } + + log.debug("Sending SSH_MSG_CHANNEL_OPEN (Channel " + c.localID + ")"); + PacketOpenSessionChannel smo = new PacketOpenSessionChannel(c.localID, c.localWindow, c.localMaxPacketSize); + tm.sendMessage(smo.getPayload()); + waitUntilChannelOpen(c); + return c; + } + + public void requestPTY(Channel c, String term, int term_width_characters, int term_height_characters, + int term_width_pixels, int term_height_pixels, byte[] terminal_modes) throws IOException { + PacketSessionPtyRequest spr; + + synchronized (c) { + if (c.state != Channel.STATE_OPEN) { + throw c.getReasonClosed(); + } + + spr = new PacketSessionPtyRequest(c.remoteID, true, term, term_width_characters, term_height_characters, + term_width_pixels, term_height_pixels, terminal_modes); + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) { + if (c.closeMessageSent) { + throw c.getReasonClosed(); + } + + tm.sendMessage(spr.getPayload()); + } + + try { + waitForChannelSuccessOrFailure(c); + } + catch (IOException e) { + throw new IOException("PTY request failed", e); + } + } + + public void requestWindowChange(Channel c, int term_width_characters, int term_height_characters, + int term_width_pixels, int term_height_pixels) throws IOException { + PacketWindowChange pwc; + + synchronized (c) { + if (c.state != Channel.STATE_OPEN) { + throw c.getReasonClosed(); + } + + pwc = new PacketWindowChange(c.remoteID, term_width_characters, term_height_characters, + term_width_pixels, term_height_pixels); + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) { + if (c.closeMessageSent) { + throw c.getReasonClosed(); + } + + tm.sendMessage(pwc.getPayload()); + } + + try { + waitForChannelSuccessOrFailure(c); + } + catch (IOException e) { + throw new IOException("The window-change request failed.", e); + } + } + + public void requestX11(Channel c, boolean singleConnection, String x11AuthenticationProtocol, + String x11AuthenticationCookie, int x11ScreenNumber) throws IOException { + PacketSessionX11Request psr; + + synchronized (c) { + if (c.state != Channel.STATE_OPEN) { + throw c.getReasonClosed(); + } + + psr = new PacketSessionX11Request(c.remoteID, true, singleConnection, x11AuthenticationProtocol, + x11AuthenticationCookie, x11ScreenNumber); + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) { + if (c.closeMessageSent) { + throw c.getReasonClosed(); + } + + tm.sendMessage(psr.getPayload()); + } + + log.debug("Requesting X11 forwarding (Channel " + c.localID + "/" + c.remoteID + ")"); + + try { + waitForChannelSuccessOrFailure(c); + } + catch (IOException e) { + throw new IOException("The X11 request failed.", e); + } + } + + public void requestSubSystem(Channel c, String subSystemName) throws IOException { + PacketSessionSubsystemRequest ssr; + + synchronized (c) { + if (c.state != Channel.STATE_OPEN) { + throw c.getReasonClosed(); + } + + ssr = new PacketSessionSubsystemRequest(c.remoteID, true, subSystemName); + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) { + if (c.closeMessageSent) { + throw c.getReasonClosed(); + } + + tm.sendMessage(ssr.getPayload()); + } + + try { + waitForChannelSuccessOrFailure(c); + } + catch (IOException e) { + throw new IOException("The subsystem request failed.", e); + } + } + + public void requestExecCommand(Channel c, String cmd) throws IOException { + this.requestExecCommand(c, cmd, null); + } + + /** + * @param charsetName The charset used to convert between Java Unicode Strings and byte encodings + */ + public void requestExecCommand(Channel c, String cmd, String charsetName) throws IOException { + PacketSessionExecCommand sm; + + synchronized (c) { + if (c.state != Channel.STATE_OPEN) { + throw c.getReasonClosed(); + } + + sm = new PacketSessionExecCommand(c.remoteID, true, cmd, charsetName); + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) { + if (c.closeMessageSent) { + throw c.getReasonClosed(); + } + + tm.sendMessage(sm.getPayload()); + } + + log.debug("Executing command (channel " + c.localID + ", '" + cmd + "')"); + + try { + waitForChannelSuccessOrFailure(c); + } + catch (IOException e) { + throw new IOException("The execute request failed.", e); + } + } + + public void requestShell(Channel c) throws IOException { + PacketSessionStartShell sm; + + synchronized (c) { + if (c.state != Channel.STATE_OPEN) { + throw c.getReasonClosed(); + } + + sm = new PacketSessionStartShell(c.remoteID, true); + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) { + if (c.closeMessageSent) { + throw c.getReasonClosed(); + } + + tm.sendMessage(sm.getPayload()); + } + + try { + waitForChannelSuccessOrFailure(c); + } + catch (IOException e) { + throw new IOException("The shell request failed.", e); + } + } + + public void msgChannelExtendedData(byte[] msg) throws IOException { + if (msg.length <= 13) { + throw new PacketFormatException(String.format("SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong size (%d)", msg.length)); + } + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + int dataType = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); + int len = ((msg[9] & 0xff) << 24) | ((msg[10] & 0xff) << 16) | ((msg[11] & 0xff) << 8) | (msg[12] & 0xff); + Channel c = getChannel(id); + + if (c == null) { + throw new PacketTypeException("Unexpected SSH_MSG_CHANNEL_EXTENDED_DATA message for non-existent channel " + id); + } + + if (dataType != Packets.SSH_EXTENDED_DATA_STDERR) { + throw new PacketFormatException("SSH_MSG_CHANNEL_EXTENDED_DATA message has unknown type (" + dataType + ")"); + } + + if (len != (msg.length - 13)) { + throw new PacketFormatException("SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong len (calculated " + (msg.length - 13) + + ", got " + len + ")"); + } + + log.debug("Got SSH_MSG_CHANNEL_EXTENDED_DATA (channel " + id + ", " + len + ")"); + + synchronized (c) { + if (c.state == Channel.STATE_CLOSED) { + return; // ignore + } + + if (c.state != Channel.STATE_OPEN) { + throw new PacketTypeException("Got SSH_MSG_CHANNEL_EXTENDED_DATA, but channel is not in correct state (" + + c.state + ")"); + } + + if (c.localWindow < len) { + throw new PacketFormatException("Remote sent too much data, does not fit into window."); + } + + c.localWindow -= len; + System.arraycopy(msg, 13, c.stderrBuffer, c.stderrWritepos, len); + c.stderrWritepos += len; + c.notifyAll(); + } + } + + /** + * Wait until for a condition. + * + * @param c Channel + * @param timeout in ms, 0 means no timeout. + * @param condition_mask minimum event mask (at least one of the conditions must be fulfilled) + * @return all current events + */ + public int waitForCondition(Channel c, long timeout, int condition_mask) throws IOException { + long end_time = 0; + boolean end_time_set = false; + + synchronized (c) { + while (true) { + int current_cond = 0; + int stdoutAvail = c.stdoutWritepos - c.stdoutReadpos; + int stderrAvail = c.stderrWritepos - c.stderrReadpos; + + if (stdoutAvail > 0) { + current_cond = current_cond | ChannelCondition.STDOUT_DATA; + } + + if (stderrAvail > 0) { + current_cond = current_cond | ChannelCondition.STDERR_DATA; + } + + if (c.EOF) { + current_cond = current_cond | ChannelCondition.EOF; + } + + if (c.getExitStatus() != null) { + current_cond = current_cond | ChannelCondition.EXIT_STATUS; + } + + if (c.getExitSignal() != null) { + current_cond = current_cond | ChannelCondition.EXIT_SIGNAL; + } + + if (c.state == Channel.STATE_CLOSED) { + return current_cond | ChannelCondition.CLOSED | ChannelCondition.EOF; + } + + if ((current_cond & condition_mask) != 0) { + return current_cond; + } + + if (timeout > 0) { + if (!end_time_set) { + end_time = System.currentTimeMillis() + timeout; + end_time_set = true; + } + else { + timeout = end_time - System.currentTimeMillis(); + + if (timeout <= 0) { + return current_cond | ChannelCondition.TIMEOUT; + } + } + } + + try { + if (timeout > 0) { + c.wait(timeout); + } + else { + c.wait(); + } + } + catch (InterruptedException e) { + throw new InterruptedIOException(e.getMessage()); + } + } + } + } + + public int getAvailable(Channel c, boolean extended) throws IOException { + synchronized (c) { + int avail; + + if (extended) { + avail = c.stderrWritepos - c.stderrReadpos; + } + else { + avail = c.stdoutWritepos - c.stdoutReadpos; + } + + return ((avail > 0) ? avail : (c.EOF ? -1 : 0)); + } + } + + public int getChannelData(Channel c, boolean extended, byte[] target, int off, int len) throws IOException { + int copylen = 0; + int increment = 0; + int remoteID = 0; + int localID = 0; + + synchronized (c) { + int stdoutAvail = 0; + int stderrAvail = 0; + + while (true) { + /* + * Data available? We have to return remaining data even if the + * channel is already closed. + */ + stdoutAvail = c.stdoutWritepos - c.stdoutReadpos; + stderrAvail = c.stderrWritepos - c.stderrReadpos; + + if ((!extended) && (stdoutAvail != 0)) { + break; + } + + if ((extended) && (stderrAvail != 0)) { + break; + } + + /* Do not wait if more data will never arrive (EOF or CLOSED) */ + + if ((c.EOF) || (c.state != Channel.STATE_OPEN)) { + return -1; + } + + try { + c.wait(); + } + catch (InterruptedException e) { + throw new InterruptedIOException(e.getMessage()); + } + } + + /* OK, there is some data. Return it. */ + + if (!extended) { + copylen = (stdoutAvail > len) ? len : stdoutAvail; + System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, target, off, copylen); + c.stdoutReadpos += copylen; + + if (c.stdoutReadpos != c.stdoutWritepos) { + System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, c.stdoutBuffer, 0, c.stdoutWritepos + - c.stdoutReadpos); + } + + c.stdoutWritepos -= c.stdoutReadpos; + c.stdoutReadpos = 0; + } + else { + copylen = (stderrAvail > len) ? len : stderrAvail; + System.arraycopy(c.stderrBuffer, c.stderrReadpos, target, off, copylen); + c.stderrReadpos += copylen; + + if (c.stderrReadpos != c.stderrWritepos) { + System.arraycopy(c.stderrBuffer, c.stderrReadpos, c.stderrBuffer, 0, c.stderrWritepos + - c.stderrReadpos); + } + + c.stderrWritepos -= c.stderrReadpos; + c.stderrReadpos = 0; + } + + if (c.state != Channel.STATE_OPEN) { + return copylen; + } + + if (c.localWindow < ((Channel.CHANNEL_BUFFER_SIZE + 1) / 2)) { + int minFreeSpace = Math.min(Channel.CHANNEL_BUFFER_SIZE - c.stdoutWritepos, + Channel.CHANNEL_BUFFER_SIZE - c.stderrWritepos); + increment = minFreeSpace - c.localWindow; + c.localWindow = minFreeSpace; + } + + remoteID = c.remoteID; /* read while holding the lock */ + localID = c.localID; /* read while holding the lock */ + } + + /* + * If a consumer reads stdout and stdin in parallel, we may end up with + * sending two msgWindowAdjust messages. Luckily, it + * does not matter in which order they arrive at the server. + */ + + if (increment > 0) { + log.debug("Sending SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + localID + ", " + increment + ")"); + + synchronized (c.channelSendLock) { + byte[] msg = c.msgWindowAdjust; + msg[0] = Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST; + msg[1] = (byte)(remoteID >> 24); + msg[2] = (byte)(remoteID >> 16); + msg[3] = (byte)(remoteID >> 8); + msg[4] = (byte)(remoteID); + msg[5] = (byte)(increment >> 24); + msg[6] = (byte)(increment >> 16); + msg[7] = (byte)(increment >> 8); + msg[8] = (byte)(increment); + + if (!c.closeMessageSent) { + tm.sendMessage(msg); + } + } + } + + return copylen; + } + + public void msgChannelData(byte[] msg) throws IOException { + if (msg.length <= 9) { + throw new PacketFormatException(String.format("SSH_MSG_CHANNEL_DATA message has wrong size (%d)", msg.length)); + } + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + int len = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); + Channel c = getChannel(id); + + if (c == null) { + throw new PacketTypeException("Unexpected SSH_MSG_CHANNEL_DATA message for non-existent channel " + id); + } + + if (len != (msg.length - 9)) { + throw new PacketFormatException("SSH_MSG_CHANNEL_DATA message has wrong len (calculated " + (msg.length - 9) + ", got " + + len + ")"); + } + + log.debug("Got SSH_MSG_CHANNEL_DATA (channel " + id + ", " + len + ")"); + + synchronized (c) { + if (c.state == Channel.STATE_CLOSED) { + return; // ignore + } + + if (c.state != Channel.STATE_OPEN) { + throw new PacketTypeException("Got SSH_MSG_CHANNEL_DATA, but channel is not in correct state (" + c.state + ")"); + } + + if (c.localWindow < len) { + throw new IOException("Remote sent too much data, does not fit into window."); + } + + c.localWindow -= len; + System.arraycopy(msg, 9, c.stdoutBuffer, c.stdoutWritepos, len); + c.stdoutWritepos += len; + c.notifyAll(); + } + } + + public void msgChannelWindowAdjust(byte[] msg) throws IOException { + if (msg.length != 9) { + throw new PacketFormatException(String.format("SSH_MSG_CHANNEL_WINDOW_ADJUST message has wrong size (%d)", msg.length)); + } + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + int windowChange = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); + Channel c = getChannel(id); + + if (c == null) { + throw new PacketTypeException("Unexpected SSH_MSG_CHANNEL_WINDOW_ADJUST message for non-existent channel " + id); + } + + synchronized (c) { + final long huge = 0xFFFFffffL; /* 2^32 - 1 */ + c.remoteWindow += (windowChange & huge); /* avoid sign extension */ + + /* TODO - is this a good heuristic? */ + + if ((c.remoteWindow > huge)) { + c.remoteWindow = huge; + } + + c.notifyAll(); + } + + log.debug("Got SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + id + ", " + windowChange + ")"); + } + + public void msgChannelOpen(byte[] msg) throws IOException { + TypesReader tr = new TypesReader(msg); + tr.readByte(); // skip packet type + String channelType = tr.readString(); + int remoteID = tr.readUINT32(); /* sender channel */ + int remoteWindow = tr.readUINT32(); /* initial window size */ + int remoteMaxPacketSize = tr.readUINT32(); /* maximum packet size */ + + if ("x11".equals(channelType)) { + synchronized (x11_magic_cookies) { + /* If we did not request X11 forwarding, then simply ignore this bogus request. */ + if (x11_magic_cookies.size() == 0) { + PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, + Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, "X11 forwarding not activated", ""); + tm.sendAsynchronousMessage(pcof.getPayload()); + log.warning("Unexpected X11 request, denying it!"); + return; + } + } + + String remoteOriginatorAddress = tr.readString(); + int remoteOriginatorPort = tr.readUINT32(); + Channel c = new Channel(this); + + synchronized (c) { + c.remoteID = remoteID; + c.remoteWindow = remoteWindow & 0xFFFFffffL; /* properly convert UINT32 to long */ + c.remoteMaxPacketSize = remoteMaxPacketSize; + c.localID = addChannel(c); + } + + /* + * The open confirmation message will be sent from another thread + */ + RemoteX11AcceptThread rxat = new RemoteX11AcceptThread(c, remoteOriginatorAddress, remoteOriginatorPort); + rxat.setDaemon(true); + rxat.start(); + return; + } + + if ("forwarded-tcpip".equals(channelType)) { + String remoteConnectedAddress = tr.readString(); /* address that was connected */ + int remoteConnectedPort = tr.readUINT32(); /* port that was connected */ + String remoteOriginatorAddress = tr.readString(); /* originator IP address */ + int remoteOriginatorPort = tr.readUINT32(); /* originator port */ + RemoteForwardingData rfd; + + synchronized (remoteForwardings) { + rfd = remoteForwardings.get(remoteConnectedPort); + } + + if (rfd == null) { + PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, + Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, + "No thanks, unknown port in forwarded-tcpip request", ""); + /* Always try to be polite. */ + tm.sendAsynchronousMessage(pcof.getPayload()); + log.debug("Unexpected forwarded-tcpip request, denying it!"); + return; + } + + Channel c = new Channel(this); + + synchronized (c) { + c.remoteID = remoteID; + c.remoteWindow = remoteWindow & 0xFFFFffffL; /* convert UINT32 to long */ + c.remoteMaxPacketSize = remoteMaxPacketSize; + c.localID = addChannel(c); + } + + /* + * The open confirmation message will be sent from another thread. + */ + RemoteAcceptThread rat = new RemoteAcceptThread(c, remoteConnectedAddress, remoteConnectedPort, + remoteOriginatorAddress, remoteOriginatorPort, rfd.targetAddress, rfd.targetPort); + rat.setDaemon(true); + rat.start(); + return; + } + + if ((server_state != null) && ("session".equals(channelType))) { + ServerConnectionCallback cb; + + synchronized (server_state) { + cb = server_state.cb_conn; + } + + if (cb == null) { + tm.sendAsynchronousMessage(new PacketChannelOpenFailure(remoteID, Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, + "Sessions are currently not enabled", "en").getPayload()); + return; + } + + final Channel c = new Channel(this); + + synchronized (c) { + c.remoteID = remoteID; + c.remoteWindow = remoteWindow & 0xFFFFffffL; /* convert UINT32 to long */ + c.remoteMaxPacketSize = remoteMaxPacketSize; + c.localID = addChannel(c); + c.state = Channel.STATE_OPEN; + c.ss = new ServerSessionImpl(c); + } + + PacketChannelOpenConfirmation pcoc = new PacketChannelOpenConfirmation(c.remoteID, c.localID, + c.localWindow, c.localMaxPacketSize); + tm.sendAsynchronousMessage(pcoc.getPayload()); + c.ss.sscb = cb.acceptSession(c.ss); + return; + } + + /* Tell the server that we have no idea what it is talking about */ + PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, Packets.SSH_OPEN_UNKNOWN_CHANNEL_TYPE, + "Unknown channel type", ""); + tm.sendAsynchronousMessage(pcof.getPayload()); + log.warning("The peer tried to open an unsupported channel type (" + channelType + ")"); + } + + /* Starts the given runnable in a foreground (non-daemon) thread */ + private void runAsync(Runnable r) { + Thread t = new Thread(r); + t.start(); + } + + public void msgChannelRequest(byte[] msg) throws IOException { + TypesReader tr = new TypesReader(msg); + tr.readByte(); // skip packet type + int id = tr.readUINT32(); + Channel c = getChannel(id); + + if (c == null) { + throw new IOException("Unexpected SSH_MSG_CHANNEL_REQUEST message for non-existent channel " + id); + } + + ServerSessionImpl server_session = null; + + if (server_state != null) { + synchronized (c) { + server_session = c.ss; + } + } + + String type = tr.readString("US-ASCII"); + boolean wantReply = tr.readBoolean(); + log.debug("Got SSH_MSG_CHANNEL_REQUEST (channel " + id + ", '" + type + "')"); + + if (type.equals("exit-status")) { + if (wantReply) { + throw new IOException( + "Badly formatted SSH_MSG_CHANNEL_REQUEST exit-status message, 'want reply' is true"); + } + + int exit_status = tr.readUINT32(); + + if (tr.remain() != 0) { + throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); + } + + synchronized (c) { + c.exit_status = exit_status; + c.notifyAll(); + } + + log.debug("Got EXIT STATUS (channel " + id + ", status " + exit_status + ")"); + return; + } + + if ((server_state == null) && (type.equals("exit-signal"))) { + if (wantReply) { + throw new IOException( + "Badly formatted SSH_MSG_CHANNEL_REQUEST exit-signal message, 'want reply' is true"); + } + + String signame = tr.readString("US-ASCII"); + tr.readBoolean(); + tr.readString(); + tr.readString(); + + if (tr.remain() != 0) { + throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); + } + + synchronized (c) { + c.exit_signal = signame; + c.notifyAll(); + } + + log.debug("Got EXIT SIGNAL (channel " + id + ", signal " + signame + ")"); + return; + } + + if ((server_session != null) && (type.equals("pty-req"))) { + PtySettings pty = new PtySettings(); + pty.term = tr.readString(); + pty.term_width_characters = tr.readUINT32(); + pty.term_height_characters = tr.readUINT32(); + pty.term_width_pixels = tr.readUINT32(); + pty.term_height_pixels = tr.readUINT32(); + pty.terminal_modes = tr.readByteString(); + + if (tr.remain() != 0) { + throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); + } + + Runnable run_after_sending_success = null; + ServerSessionCallback sscb = server_session.getServerSessionCallback(); + + if (sscb != null) { + run_after_sending_success = sscb.requestPtyReq(server_session, pty); + } + + if (wantReply) { + if (run_after_sending_success != null) { + tm.sendAsynchronousMessage(new PacketChannelSuccess(c.remoteID).getPayload()); + } + else { + tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); + } + } + + if (run_after_sending_success != null) { + runAsync(run_after_sending_success); + } + + return; + } + + if ((server_session != null) && (type.equals("shell"))) { + if (tr.remain() != 0) { + throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); + } + + Runnable run_after_sending_success = null; + ServerSessionCallback sscb = server_session.getServerSessionCallback(); + + if (sscb != null) { + run_after_sending_success = sscb.requestShell(server_session); + } + + if (wantReply) { + if (run_after_sending_success != null) { + tm.sendAsynchronousMessage(new PacketChannelSuccess(c.remoteID).getPayload()); + } + else { + tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); + } + } + + if (run_after_sending_success != null) { + runAsync(run_after_sending_success); + } + + return; + } + + if ((server_session != null) && (type.equals("exec"))) { + String command = tr.readString(); + + if (tr.remain() != 0) { + throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); + } + + Runnable run_after_sending_success = null; + ServerSessionCallback sscb = server_session.getServerSessionCallback(); + + if (sscb != null) { + run_after_sending_success = sscb.requestExec(server_session, command); + } + + if (wantReply) { + if (run_after_sending_success != null) { + tm.sendAsynchronousMessage(new PacketChannelSuccess(c.remoteID).getPayload()); + } + else { + tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); + } + } + + if (run_after_sending_success != null) { + runAsync(run_after_sending_success); + } + + return; + } + + /* We simply ignore unknown channel requests, however, if the server wants a reply, + * then we signal that we have no idea what it is about. + */ + + if (wantReply) { + tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); + } + + log.debug("Channel request '" + type + "' is not known, ignoring it"); + } + + public void msgChannelEOF(byte[] msg) throws IOException { + if (msg.length != 5) { + throw new PacketFormatException(String.format("SSH_MSG_CHANNEL_EOF message has wrong size (%d)", msg.length)); + } + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + Channel c = getChannel(id); + + if (c == null) { + throw new PacketTypeException("Unexpected SSH_MSG_CHANNEL_EOF message for non-existent channel " + id); + } + + synchronized (c) { + c.EOF = true; + c.notifyAll(); + } + + log.debug("Got SSH_MSG_CHANNEL_EOF (channel " + id + ")"); + } + + public void msgChannelClose(byte[] msg) throws IOException { + if (msg.length != 5) { + throw new PacketFormatException("SSH_MSG_CHANNEL_CLOSE message has wrong size (" + msg.length + ")"); + } + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + Channel c = getChannel(id); + + if (c == null) { + throw new IOException("Unexpected SSH_MSG_CHANNEL_CLOSE message for non-existent channel " + id); + } + + synchronized (c) { + c.EOF = true; + c.state = Channel.STATE_CLOSED; + c.setReasonClosed(new ChannelClosedException("Close requested by remote")); + c.closeMessageRecv = true; + removeChannel(c.localID); + c.notifyAll(); + } + + log.debug("Got SSH_MSG_CHANNEL_CLOSE (channel " + id + ")"); + } + + public void msgChannelSuccess(byte[] msg) throws IOException { + if (msg.length != 5) { + throw new PacketFormatException("SSH_MSG_CHANNEL_SUCCESS message has wrong size (" + msg.length + ")"); + } + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + Channel c = getChannel(id); + + if (c == null) { + throw new PacketTypeException("Unexpected SSH_MSG_CHANNEL_SUCCESS message for non-existent channel " + id); + } + + synchronized (c) { + c.successCounter++; + c.notifyAll(); + } + + log.debug("Got SSH_MSG_CHANNEL_SUCCESS (channel " + id + ")"); + } + + public void msgChannelFailure(byte[] msg) throws IOException { + if (msg.length != 5) { + throw new PacketFormatException(String.format("SSH_MSG_CHANNEL_FAILURE message has wrong size (%d)", msg.length)); + } + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + Channel c = getChannel(id); + + if (c == null) { + throw new PacketTypeException("Unexpected SSH_MSG_CHANNEL_FAILURE message for non-existent channel " + id); + } + + synchronized (c) { + c.failedCounter++; + c.notifyAll(); + } + + log.debug("Got SSH_MSG_CHANNEL_FAILURE (channel " + id + ")"); + } + + public void msgChannelOpenConfirmation(byte[] msg) throws IOException { + PacketChannelOpenConfirmation sm = new PacketChannelOpenConfirmation(msg); + Channel c = getChannel(sm.getRecipientChannelID()); + + if (c == null) { + throw new PacketTypeException("Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for non-existent channel " + + sm.getRecipientChannelID()); + } + + synchronized (c) { + if (c.state != Channel.STATE_OPENING) { + throw new PacketTypeException("Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for channel " + + sm.getRecipientChannelID()); + } + + c.remoteID = sm.getSenderChannelID(); + c.remoteWindow = sm.getInitialWindowSize() & 0xFFFFffffL; /* convert UINT32 to long */ + c.remoteMaxPacketSize = sm.getMaxPacketSize(); + c.state = Channel.STATE_OPEN; + c.notifyAll(); + } + + log.debug("Got SSH_MSG_CHANNEL_OPEN_CONFIRMATION (channel " + sm.getRecipientChannelID() + " / remote: " + + sm.getSenderChannelID() + ")"); + } + + public void msgChannelOpenFailure(byte[] msg) throws IOException { + if (msg.length < 5) { + throw new PacketFormatException(String.format("SSH_MSG_CHANNEL_OPEN_FAILURE message has wrong size (%d)", msg.length)); + } + + TypesReader tr = new TypesReader(msg); + tr.readByte(); // skip packet type + int id = tr.readUINT32(); /* sender channel */ + Channel c = getChannel(id); + + if (c == null) { + throw new PacketTypeException("Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE message for non-existent channel " + id); + } + + int reasonCode = tr.readUINT32(); + String description = tr.readString("UTF-8"); + String reasonCodeSymbolicName; + + switch (reasonCode) { + case 1: + reasonCodeSymbolicName = "SSH_OPEN_ADMINISTRATIVELY_PROHIBITED"; + break; + + case 2: + reasonCodeSymbolicName = "SSH_OPEN_CONNECT_FAILED"; + break; + + case 3: + reasonCodeSymbolicName = "SSH_OPEN_UNKNOWN_CHANNEL_TYPE"; + break; + + case 4: + reasonCodeSymbolicName = "SSH_OPEN_RESOURCE_SHORTAGE"; + break; + + default: + reasonCodeSymbolicName = "UNKNOWN REASON CODE (" + reasonCode + ")"; + } + + StringBuilder descriptionBuffer = new StringBuilder(); + descriptionBuffer.append(description); + + for (int i = 0; i < descriptionBuffer.length(); i++) { + char cc = descriptionBuffer.charAt(i); + + if ((cc >= 32) && (cc <= 126)) { + continue; + } + + descriptionBuffer.setCharAt(i, '\uFFFD'); + } + + synchronized (c) { + c.EOF = true; + c.state = Channel.STATE_CLOSED; + c.setReasonClosed(new ChannelClosedException(String.format("The server refused to open the channel (%s, '%s')", + reasonCodeSymbolicName, descriptionBuffer.toString()))); + c.notifyAll(); + } + + log.debug("Got SSH_MSG_CHANNEL_OPEN_FAILURE (channel " + id + ")"); + } + + public void msgGlobalRequest(byte[] msg) throws IOException { + /* Currently we do not support any kind of global request */ + TypesReader tr = new TypesReader(msg); + tr.readByte(); // skip packet type + String requestName = tr.readString(); + boolean wantReply = tr.readBoolean(); + + if (wantReply) { + byte[] reply_failure = new byte[1]; + reply_failure[0] = Packets.SSH_MSG_REQUEST_FAILURE; + tm.sendAsynchronousMessage(reply_failure); + } + + /* We do not clean up the requestName String - that is OK for debug */ + log.debug("Got SSH_MSG_GLOBAL_REQUEST (" + requestName + ")"); + } + + public void msgGlobalSuccess() throws IOException { + synchronized (channels) { + globalSuccessCounter++; + channels.notifyAll(); + } + + log.debug("Got SSH_MSG_REQUEST_SUCCESS"); + } + + public void msgGlobalFailure() throws IOException { + synchronized (channels) { + globalFailedCounter++; + channels.notifyAll(); + } + + log.debug("Got SSH_MSG_REQUEST_FAILURE"); + } + + public void handleFailure(final IOException failure) { + log.debug("HandleMessage: got shutdown"); + + synchronized (listenerThreads) { + for (IChannelWorkerThread lat : listenerThreads) { + lat.stopWorking(); + } + + listenerThreadsAllowed = false; + } + + synchronized (channels) { + shutdown = true; + + for (Channel c : channels) { + synchronized (c) { + c.EOF = true; + c.state = Channel.STATE_CLOSED; + c.setReasonClosed(failure); + c.closeMessageRecv = true; + c.notifyAll(); + } + } + + channels.clear(); + channels.notifyAll(); /* Notify global response waiters */ + } + } + + public void handleMessage(byte[] msg) throws IOException { + switch (msg[0]) { + case Packets.SSH_MSG_CHANNEL_OPEN_CONFIRMATION: + msgChannelOpenConfirmation(msg); + break; + + case Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST: + msgChannelWindowAdjust(msg); + break; + + case Packets.SSH_MSG_CHANNEL_DATA: + msgChannelData(msg); + break; + + case Packets.SSH_MSG_CHANNEL_EXTENDED_DATA: + msgChannelExtendedData(msg); + break; + + case Packets.SSH_MSG_CHANNEL_REQUEST: + msgChannelRequest(msg); + break; + + case Packets.SSH_MSG_CHANNEL_EOF: + msgChannelEOF(msg); + break; + + case Packets.SSH_MSG_CHANNEL_OPEN: + msgChannelOpen(msg); + break; + + case Packets.SSH_MSG_CHANNEL_CLOSE: + msgChannelClose(msg); + break; + + case Packets.SSH_MSG_CHANNEL_SUCCESS: + msgChannelSuccess(msg); + break; + + case Packets.SSH_MSG_CHANNEL_FAILURE: + msgChannelFailure(msg); + break; + + case Packets.SSH_MSG_CHANNEL_OPEN_FAILURE: + msgChannelOpenFailure(msg); + break; + + case Packets.SSH_MSG_GLOBAL_REQUEST: + msgGlobalRequest(msg); + break; + + case Packets.SSH_MSG_REQUEST_SUCCESS: + msgGlobalSuccess(); + break; + + case Packets.SSH_MSG_REQUEST_FAILURE: + msgGlobalFailure(); + break; + + default: + throw new PacketTypeException(msg[0]); + } + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/channel/ChannelOutputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/channel/ChannelOutputStream.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.channel; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * ChannelOutputStream. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public final class ChannelOutputStream extends OutputStream { + Channel c; + + boolean isClosed = false; + + ChannelOutputStream(Channel c) { + this.c = c; + } + + @Override + public void write(int b) throws IOException { + byte[] buff = new byte[1]; + buff[0] = (byte) b; + write(buff, 0, 1); + } + + @Override + public void close() throws IOException { + if (isClosed == false) { + isClosed = true; + c.cm.sendEOF(c); + } + } + + @Override + public void flush() throws IOException { + if (isClosed) + throw new IOException("This OutputStream is closed."); + + /* This is a no-op, since this stream is unbuffered */ + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (isClosed) + throw new IOException("This OutputStream is closed."); + + if (b == null) + throw new NullPointerException(); + + if ((off < 0) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) || (off > b.length)) + throw new IndexOutOfBoundsException(); + + if (len == 0) + return; + + c.cm.sendData(c, b, off, len); + } + + @Override + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 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 Thu Jul 31 16:33:38 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 ch.ethz.ssh2.channel.IChannelWorkerThread#stopWorking() + */ + public void stopWorking() { + try { + /* This will lead to an IOException in the ss.accept() call */ + ss.close(); + } + catch (IOException e) { + } + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/channel/IChannelWorkerThread.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/channel/IChannelWorkerThread.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.channel; + +/** + * IChannelWorkerThread. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +interface IChannelWorkerThread { + public void stopWorking(); +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/channel/LocalAcceptThread.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/channel/LocalAcceptThread.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.channel; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; + +/** + * LocalAcceptThread. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class LocalAcceptThread extends Thread implements IChannelWorkerThread { + ChannelManager cm; + String host_to_connect; + int port_to_connect; + + final ServerSocket ss; + + public LocalAcceptThread(ChannelManager cm, int local_port, String host_to_connect, int port_to_connect) + throws IOException { + this.cm = cm; + this.host_to_connect = host_to_connect; + this.port_to_connect = port_to_connect; + ss = new ServerSocket(local_port); + } + + public LocalAcceptThread(ChannelManager cm, InetSocketAddress localAddress, String host_to_connect, + int port_to_connect) throws IOException { + this.cm = cm; + this.host_to_connect = host_to_connect; + this.port_to_connect = port_to_connect; + ss = new ServerSocket(); + ss.bind(localAddress); + } + + public ServerSocket getServerSocket() { + return ss; + } + + @Override + public void run() { + try { + cm.registerThread(this); + } + catch (IOException e) { + stopWorking(); + return; + } + + while (true) { + Socket s = null; + + try { + s = ss.accept(); + } + catch (IOException e) { + stopWorking(); + return; + } + + Channel cn = null; + StreamForwarder r2l = null; + StreamForwarder l2r = null; + + try { + /* This may fail, e.g., if the remote port is closed (in optimistic terms: not open yet) */ + cn = cm.openDirectTCPIPChannel(host_to_connect, port_to_connect, s.getInetAddress().getHostAddress(), s + .getPort()); + } + catch (IOException e) { + /* Simply close the local socket and wait for the next incoming connection */ + try { + s.close(); + } + catch (IOException ignore) { + } + + continue; + } + + try { + r2l = new StreamForwarder(cn, null, null, cn.stdoutStream, s.getOutputStream(), "RemoteToLocal"); + l2r = new StreamForwarder(cn, r2l, s, s.getInputStream(), cn.stdinStream, "LocalToRemote"); + } + catch (IOException e) { + try { + /* This message is only visible during debugging, since we discard the channel immediatelly */ + cn.cm.closeChannel(cn, e, true); + } + catch (IOException ignore) { + } + + continue; + } + + r2l.setDaemon(true); + l2r.setDaemon(true); + r2l.start(); + l2r.start(); + } + } + + public void stopWorking() { + try { + /* This will lead to an IOException in the ss.accept() call */ + ss.close(); + } + catch (IOException ignored) { + } + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/channel/RemoteAcceptThread.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/channel/RemoteAcceptThread.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.channel; + +import java.io.IOException; +import java.net.Socket; + +import ch.ethz.ssh2.log.Logger; + +/** + * RemoteAcceptThread. + * + * @author Christian Plattner + * @version $Id: RemoteAcceptThread.java 119 2014-04-12 20:30:58Z dkocher@sudo.ch $ + */ +public class RemoteAcceptThread extends Thread { + private static final Logger log = Logger.getLogger(RemoteAcceptThread.class); + + Channel c; + + String remoteConnectedAddress; + int remoteConnectedPort; + String remoteOriginatorAddress; + int remoteOriginatorPort; + String targetAddress; + int targetPort; + + Socket s; + + public RemoteAcceptThread(Channel c, String remoteConnectedAddress, int remoteConnectedPort, + String remoteOriginatorAddress, int remoteOriginatorPort, String targetAddress, int targetPort) { + this.c = c; + this.remoteConnectedAddress = remoteConnectedAddress; + this.remoteConnectedPort = remoteConnectedPort; + this.remoteOriginatorAddress = remoteOriginatorAddress; + this.remoteOriginatorPort = remoteOriginatorPort; + this.targetAddress = targetAddress; + this.targetPort = targetPort; + log.debug("RemoteAcceptThread: " + remoteConnectedAddress + "/" + remoteConnectedPort + ", R: " + + remoteOriginatorAddress + "/" + remoteOriginatorPort); + } + + @Override + public void run() { + try { + c.cm.sendOpenConfirmation(c); + s = new Socket(targetAddress, targetPort); + StreamForwarder r2l = new StreamForwarder(c, null, null, c.getStdoutStream(), s.getOutputStream(), + "RemoteToLocal"); + StreamForwarder l2r = new StreamForwarder(c, null, null, s.getInputStream(), c.getStdinStream(), + "LocalToRemote"); + /* No need to start two threads, one can be executed in the current thread */ + r2l.setDaemon(true); + r2l.start(); + l2r.run(); + + while (r2l.isAlive()) { + try { + r2l.join(); + } + catch (InterruptedException ignored) { + } + } + + /* If the channel is already closed, then this is a no-op */ + c.cm.closeChannel(c, "EOF on both streams reached.", true); + s.close(); + } + catch (IOException e) { + log.warning("IOException in proxy code: " + e.getMessage()); + + try { + c.cm.closeChannel(c, e, true); + } + catch (IOException ignored) { + } + + try { + if (s != null) + s.close(); + } + catch (IOException ignored) { + } + } + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/channel/RemoteForwardingData.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/channel/RemoteForwardingData.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.channel; + +/** + * RemoteForwardingData. Data about a requested remote forwarding. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class RemoteForwardingData { + public String bindAddress; + public int bindPort; + + String targetAddress; + int targetPort; +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/channel/RemoteX11AcceptThread.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/channel/RemoteX11AcceptThread.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.channel; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +import ch.ethz.ssh2.log.Logger; +import ch.ethz.ssh2.util.StringEncoder; + +/** + * RemoteX11AcceptThread. + * + * @author Christian Plattner + * @version $Id: RemoteX11AcceptThread.java 119 2014-04-12 20:30:58Z dkocher@sudo.ch $ + */ +public class RemoteX11AcceptThread extends Thread { + private static final Logger log = Logger.getLogger(RemoteX11AcceptThread.class); + + Channel c; + + String remoteOriginatorAddress; + int remoteOriginatorPort; + + Socket s; + + public RemoteX11AcceptThread(Channel c, String remoteOriginatorAddress, int remoteOriginatorPort) { + this.c = c; + this.remoteOriginatorAddress = remoteOriginatorAddress; + this.remoteOriginatorPort = remoteOriginatorPort; + } + + @Override + public void run() { + try { + /* Send Open Confirmation */ + c.cm.sendOpenConfirmation(c); + /* Read startup packet from client */ + OutputStream remote_os = c.getStdinStream(); + InputStream remote_is = c.getStdoutStream(); + /* The following code is based on the protocol description given in: + * Scheifler/Gettys, + * X Windows System: Core and Extension Protocols: + * X Version 11, Releases 6 and 6.1 ISBN 1-55558-148-X + * (from the ETH library - after being here for almost ten + * years one of the few books I borrowed... sad but true =) + */ + /* + * Client startup: + * + * 1 0X42 MSB first/0x6c lSB first - byteorder + * 1 - unused + * 2 card16 - protocol-major-version + * 2 card16 - protocol-minor-version + * 2 n - lenght of authorization-protocol-name + * 2 d - lenght of authorization-protocol-data + * 2 - unused + * string8 - authorization-protocol-name + * p - unused, p=pad(n) + * string8 - authorization-protocol-data + * q - unused, q=pad(d) + * + * pad(X) = (4 - (X mod 4)) mod 4 + * + * Server response: + * + * 1 (0 failed, 2 authenticate, 1 success) + * ... + * + */ + /* Later on we will simply forward the first 6 header bytes to the "real" X11 server */ + byte[] header = new byte[6]; + + if (remote_is.read(header) != 6) + throw new IOException("Unexpected EOF on X11 startup!"); + + if ((header[0] != 0x42) && (header[0] != 0x6c)) // 0x42 MSB first, 0x6C LSB first + throw new IOException("Unknown endian format in X11 message!"); + + /* Yes, I came up with this myself - shall I file an application for a patent? =) */ + int idxMSB = (header[0] == 0x42) ? 0 : 1; + /* Read authorization data header */ + byte[] auth_buff = new byte[6]; + + if (remote_is.read(auth_buff) != 6) + throw new IOException("Unexpected EOF on X11 startup!"); + + int authProtocolNameLength = ((auth_buff[idxMSB] & 0xff) << 8) | (auth_buff[1 - idxMSB] & 0xff); + int authProtocolDataLength = ((auth_buff[2 + idxMSB] & 0xff) << 8) | (auth_buff[3 - idxMSB] & 0xff); + + if ((authProtocolNameLength > 256) || (authProtocolDataLength > 256)) + throw new IOException("Buggy X11 authorization data"); + + int authProtocolNamePadding = ((4 - (authProtocolNameLength % 4)) % 4); + int authProtocolDataPadding = ((4 - (authProtocolDataLength % 4)) % 4); + byte[] authProtocolName = new byte[authProtocolNameLength]; + byte[] authProtocolData = new byte[authProtocolDataLength]; + byte[] paddingBuffer = new byte[4]; + + if (remote_is.read(authProtocolName) != authProtocolNameLength) + throw new IOException("Unexpected EOF on X11 startup! (authProtocolName)"); + + if (remote_is.read(paddingBuffer, 0, authProtocolNamePadding) != authProtocolNamePadding) + throw new IOException("Unexpected EOF on X11 startup! (authProtocolNamePadding)"); + + if (remote_is.read(authProtocolData) != authProtocolDataLength) + throw new IOException("Unexpected EOF on X11 startup! (authProtocolData)"); + + if (remote_is.read(paddingBuffer, 0, authProtocolDataPadding) != authProtocolDataPadding) + throw new IOException("Unexpected EOF on X11 startup! (authProtocolDataPadding)"); + + if ("MIT-MAGIC-COOKIE-1".equals(StringEncoder.GetString(authProtocolName)) == false) + throw new IOException("Unknown X11 authorization protocol!"); + + if (authProtocolDataLength != 16) + throw new IOException("Wrong data length for X11 authorization data!"); + + StringBuilder tmp = new StringBuilder(32); + + for (int i = 0; i < authProtocolData.length; i++) { + String digit2 = Integer.toHexString(authProtocolData[i] & 0xff); + tmp.append((digit2.length() == 2) ? digit2 : "0" + digit2); + } + + String hexEncodedFakeCookie = tmp.toString(); + + /* Order is very important here - it may be that a certain x11 forwarding + * gets disabled right in the moment when we check and register our connection + * */ + + synchronized (c) { + /* Please read the comment in Channel.java */ + c.hexX11FakeCookie = hexEncodedFakeCookie; + } + + /* Now check our fake cookie directory to see if we produced this cookie */ + X11ServerData sd = c.cm.checkX11Cookie(hexEncodedFakeCookie); + + if (sd == null) + throw new IOException("Invalid X11 cookie received."); + + /* If the session which corresponds to this cookie is closed then we will + * detect this: the session's close code will close all channels + * with the session's assigned x11 fake cookie. + */ + s = new Socket(sd.hostname, sd.port); + OutputStream x11_os = s.getOutputStream(); + InputStream x11_is = s.getInputStream(); + /* Now we are sending the startup packet to the real X11 server */ + x11_os.write(header); + + if (sd.x11_magic_cookie == null) { + byte[] emptyAuthData = new byte[6]; + /* empty auth data, hopefully you are connecting to localhost =) */ + x11_os.write(emptyAuthData); + } + else { + if (sd.x11_magic_cookie.length != 16) + throw new IOException("The real X11 cookie has an invalid length!"); + + /* send X11 cookie specified by client */ + x11_os.write(auth_buff); + x11_os.write(authProtocolName); /* re-use */ + x11_os.write(paddingBuffer, 0, authProtocolNamePadding); + x11_os.write(sd.x11_magic_cookie); + x11_os.write(paddingBuffer, 0, authProtocolDataPadding); + } + + x11_os.flush(); + /* Start forwarding traffic */ + StreamForwarder r2l = new StreamForwarder(c, null, null, remote_is, x11_os, "RemoteToX11"); + StreamForwarder l2r = new StreamForwarder(c, null, null, x11_is, remote_os, "X11ToRemote"); + /* No need to start two threads, one can be executed in the current thread */ + r2l.setDaemon(true); + r2l.start(); + l2r.run(); + + while (r2l.isAlive()) { + try { + r2l.join(); + } + catch (InterruptedException ignored) { + } + } + + /* If the channel is already closed, then this is a no-op */ + c.cm.closeChannel(c, "EOF on both X11 streams reached.", true); + s.close(); + } + catch (IOException e) { + log.warning("IOException in X11 proxy code: " + e.getMessage()); + + try { + c.cm.closeChannel(c, e, true); + } + catch (IOException ignored) { + } + + try { + if (s != null) + s.close(); + } + catch (IOException ignored) { + } + } + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/channel/ServerSessionImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/channel/ServerSessionImpl.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,50 @@ + +package ch.ethz.ssh2.channel; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import ch.ethz.ssh2.ServerSession; +import ch.ethz.ssh2.ServerSessionCallback; + +public class ServerSessionImpl implements ServerSession { + Channel c; + public ServerSessionCallback sscb; + + public ServerSessionImpl(Channel c) { + this.c = c; + } + + public int getState() { + return c.getState(); + } + + public InputStream getStdout() { + return c.getStdoutStream(); + } + + public InputStream getStderr() { + return c.getStderrStream(); + } + + public OutputStream getStdin() { + return c.getStdinStream(); + } + + public void close() { + try { + c.cm.closeChannel(c, "Closed due to server request", true); + } + catch (IOException ignored) { + } + } + + public synchronized ServerSessionCallback getServerSessionCallback() { + return sscb; + } + + public synchronized void setServerSessionCallback(ServerSessionCallback sscb) { + this.sscb = sscb; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/channel/StreamForwarder.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/channel/StreamForwarder.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.channel; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +/** + * A StreamForwarder forwards data between two given streams. + * If two StreamForwarder threads are used (one for each direction) + * then one can be configured to shutdown the underlying channel/socket + * if both threads have finished forwarding (EOF). + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class StreamForwarder extends Thread { + OutputStream os; + InputStream is; + byte[] buffer = new byte[Channel.CHANNEL_BUFFER_SIZE]; + Channel c; + StreamForwarder sibling; + Socket s; + String mode; + + StreamForwarder(Channel c, StreamForwarder sibling, Socket s, InputStream is, OutputStream os, String mode) + throws IOException { + this.is = is; + this.os = os; + this.mode = mode; + this.c = c; + this.sibling = sibling; + this.s = s; + } + + @Override + public void run() { + try { + while (true) { + int len = is.read(buffer); + + if (len <= 0) + break; + + os.write(buffer, 0, len); + os.flush(); + } + } + catch (IOException e) { + try { + c.cm.closeChannel(c, e, true); + } + catch (IOException ignored) { + } + } + finally { + try { + os.close(); + } + catch (IOException ignored) { + } + + try { + is.close(); + } + catch (IOException ignored) { + } + + if (sibling != null) { + while (sibling.isAlive()) { + try { + sibling.join(); + } + catch (InterruptedException ignored) { + } + } + + try { + c.cm.closeChannel(c, "StreamForwarder (" + mode + ") is cleaning up the connection", true); + } + catch (IOException ignored) { + } + + try { + if (s != null) + s.close(); + } + catch (IOException ignored) { + } + } + } + } +} \ No newline at end of file diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/channel/X11ServerData.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/channel/X11ServerData.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.channel; + +/** + * X11ServerData. Data regarding an x11 forwarding target. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + * + */ +public class X11ServerData { + public String hostname; + public int port; + public byte[] x11_magic_cookie; /* not the remote (fake) one, the local (real) one */ +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/compression/CompressionFactory.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/compression/CompressionFactory.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,88 @@ +/* + * 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.compression; + +import java.util.Vector; + +/** + * @author Kenny Root + * + */ +public class CompressionFactory { + static class CompressorEntry { + String type; + String compressorClass; + + public CompressorEntry(String type, String compressorClass) { + this.type = type; + this.compressorClass = compressorClass; + } + } + + static Vector compressors = new Vector(); + + static { + /* Higher Priority First */ + compressors.addElement(new CompressorEntry("zlib", "ch.ethz.ssh2.compression.Zlib")); + compressors.addElement(new CompressorEntry("zlib@openssh.com", "ch.ethz.ssh2.compression.ZlibOpenSSH")); + compressors.addElement(new CompressorEntry("none", "")); + } + + public static String[] getDefaultCompressorList() { + String list[] = new String[compressors.size()]; + + for (int i = 0; i < compressors.size(); i++) { + CompressorEntry ce = compressors.elementAt(i); + list[i] = new String(ce.type); + } + + return list; + } + + public static void checkCompressorList(String[] compressorCandidates) { + for (int i = 0; i < compressorCandidates.length; i++) + getEntry(compressorCandidates[i]); + } + + public static Compressor createCompressor(String type) { + try { + CompressorEntry ce = getEntry(type); + + if ("".equals(ce.compressorClass)) + return null; + + Class cc = Class.forName(ce.compressorClass); + Compressor cmp = (Compressor) cc.newInstance(); + return cmp; + } + catch (Exception e) { + throw new IllegalArgumentException("Cannot instantiate " + type); + } + } + + private static CompressorEntry getEntry(String type) { + for (int i = 0; i < compressors.size(); i++) { + CompressorEntry ce = compressors.elementAt(i); + + if (ce.type.equals(type)) + return ce; + } + + throw new IllegalArgumentException("Unkown algorithm " + type); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/compression/Compressor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/compression/Compressor.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,32 @@ +/* + * 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.compression; + +/** + * @author Kenny Root + * + */ +public interface Compressor { + int getBufferSize(); + + int compress(byte[] buf, int start, int len, byte[] output); + + byte[] uncompress(byte[] buf, int start, int[] len); + + boolean canCompressPreauth(); +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/compression/Zlib.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/compression/Zlib.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,131 @@ +/* + * 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.compression; + +import com.jcraft.jzlib.JZlib; +import com.jcraft.jzlib.ZStream; + +/** + * @author Kenny Root + * + */ +public class Zlib implements Compressor { + static private final int DEFAULT_BUF_SIZE = 4096; + static private final int LEVEL = 5; + + private ZStream deflate; + private byte[] deflate_tmpbuf; + + private ZStream inflate; + private byte[] inflate_tmpbuf; + private byte[] inflated_buf; + + public Zlib() { + deflate = new ZStream(); + inflate = new ZStream(); + deflate.deflateInit(LEVEL); + inflate.inflateInit(); + deflate_tmpbuf = new byte[DEFAULT_BUF_SIZE]; + inflate_tmpbuf = new byte[DEFAULT_BUF_SIZE]; + inflated_buf = new byte[DEFAULT_BUF_SIZE]; + } + + public boolean canCompressPreauth() { + return true; + } + + public int getBufferSize() { + return DEFAULT_BUF_SIZE; + } + + public int compress(byte[] buf, int start, int len, byte[] output) { + deflate.next_in = buf; + deflate.next_in_index = start; + deflate.avail_in = len - start; + + if ((buf.length + 1024) > deflate_tmpbuf.length) { + deflate_tmpbuf = new byte[buf.length + 1024]; + } + + deflate.next_out = deflate_tmpbuf; + deflate.next_out_index = 0; + deflate.avail_out = output.length; + + if (deflate.deflate(JZlib.Z_PARTIAL_FLUSH) != JZlib.Z_OK) { + System.err.println("compress: compression failure"); + } + + if (deflate.avail_in > 0) { + System.err.println("compress: deflated data too large"); + } + + int outputlen = output.length - deflate.avail_out; + System.arraycopy(deflate_tmpbuf, 0, output, 0, outputlen); + return outputlen; + } + + public byte[] uncompress(byte[] buffer, int start, int[] length) { + int inflated_end = 0; + inflate.next_in = buffer; + inflate.next_in_index = start; + inflate.avail_in = length[0]; + + while (true) { + inflate.next_out = inflate_tmpbuf; + inflate.next_out_index = 0; + inflate.avail_out = DEFAULT_BUF_SIZE; + int status = inflate.inflate(JZlib.Z_PARTIAL_FLUSH); + + switch (status) { + case JZlib.Z_OK: + if (inflated_buf.length < inflated_end + DEFAULT_BUF_SIZE + - inflate.avail_out) { + byte[] foo = new byte[inflated_end + DEFAULT_BUF_SIZE + - inflate.avail_out]; + System.arraycopy(inflated_buf, 0, foo, 0, inflated_end); + inflated_buf = foo; + } + + System.arraycopy(inflate_tmpbuf, 0, inflated_buf, inflated_end, + DEFAULT_BUF_SIZE - inflate.avail_out); + inflated_end += (DEFAULT_BUF_SIZE - inflate.avail_out); + length[0] = inflated_end; + break; + + case JZlib.Z_BUF_ERROR: + if (inflated_end > buffer.length - start) { + byte[] foo = new byte[inflated_end + start]; + System.arraycopy(buffer, 0, foo, 0, start); + System.arraycopy(inflated_buf, 0, foo, start, inflated_end); + buffer = foo; + } + else { + System.arraycopy(inflated_buf, 0, buffer, start, + inflated_end); + } + + length[0] = inflated_end; + return buffer; + + default: + System.err.println("uncompress: inflate returnd " + status); + return null; + } + } + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/compression/ZlibOpenSSH.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/compression/ZlibOpenSSH.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,35 @@ +/* + * 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.compression; + +/** + * Defines how zlib@openssh.org compression works. + * See + * http://www.openssh.org/txt/draft-miller-secsh-compression-delayed-00.txt + * compression is disabled until userauth has occurred. + * + * @author Matt Johnston + * + */ +public class ZlibOpenSSH extends Zlib { + + public boolean canCompressPreauth() { + return false; + } + +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/Base64.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/Base64.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto; + +import java.io.CharArrayWriter; +import java.io.IOException; + +/** + * Basic Base64 Support. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class Base64 { + static final char[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); + + public static char[] encode(byte[] content) { + CharArrayWriter cw = new CharArrayWriter((4 * content.length) / 3); + int idx = 0; + int x = 0; + + for (int i = 0; i < content.length; i++) { + if (idx == 0) + x = (content[i] & 0xff) << 16; + else if (idx == 1) + x = x | ((content[i] & 0xff) << 8); + else + x = x | (content[i] & 0xff); + + idx++; + + if (idx == 3) { + cw.write(alphabet[x >> 18]); + cw.write(alphabet[(x >> 12) & 0x3f]); + cw.write(alphabet[(x >> 6) & 0x3f]); + cw.write(alphabet[x & 0x3f]); + idx = 0; + } + } + + if (idx == 1) { + cw.write(alphabet[x >> 18]); + cw.write(alphabet[(x >> 12) & 0x3f]); + cw.write('='); + cw.write('='); + } + + if (idx == 2) { + cw.write(alphabet[x >> 18]); + cw.write(alphabet[(x >> 12) & 0x3f]); + cw.write(alphabet[(x >> 6) & 0x3f]); + cw.write('='); + } + + return cw.toCharArray(); + } + + public static byte[] decode(char[] message) throws IOException { + byte buff[] = new byte[4]; + byte dest[] = new byte[message.length]; + int bpos = 0; + int destpos = 0; + + for (int i = 0; i < message.length; i++) { + int c = message[i]; + + if ((c == '\n') || (c == '\r') || (c == ' ') || (c == '\t')) + continue; + + if ((c >= 'A') && (c <= 'Z')) { + buff[bpos++] = (byte)(c - 'A'); + } + else if ((c >= 'a') && (c <= 'z')) { + buff[bpos++] = (byte)((c - 'a') + 26); + } + else if ((c >= '0') && (c <= '9')) { + buff[bpos++] = (byte)((c - '0') + 52); + } + else if (c == '+') { + buff[bpos++] = 62; + } + else if (c == '/') { + buff[bpos++] = 63; + } + else if (c == '=') { + buff[bpos++] = 64; + } + else { + throw new IOException("Illegal char in base64 code."); + } + + if (bpos == 4) { + bpos = 0; + + if (buff[0] == 64) + break; + + if (buff[1] == 64) + throw new IOException("Unexpected '=' in base64 code."); + + if (buff[2] == 64) { + int v = (((buff[0] & 0x3f) << 6) | ((buff[1] & 0x3f))); + dest[destpos++] = (byte)(v >> 4); + break; + } + else if (buff[3] == 64) { + int v = (((buff[0] & 0x3f) << 12) | ((buff[1] & 0x3f) << 6) | ((buff[2] & 0x3f))); + dest[destpos++] = (byte)(v >> 10); + dest[destpos++] = (byte)(v >> 2); + break; + } + else { + int v = (((buff[0] & 0x3f) << 18) | ((buff[1] & 0x3f) << 12) | ((buff[2] & 0x3f) << 6) | ((buff[3] & 0x3f))); + dest[destpos++] = (byte)(v >> 16); + dest[destpos++] = (byte)(v >> 8); + dest[destpos++] = (byte)(v); + } + } + } + + byte[] res = new byte[destpos]; + System.arraycopy(dest, 0, res, 0, destpos); + return res; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/CryptoWishList.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/CryptoWishList.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto; + +import ch.ethz.ssh2.compression.CompressionFactory; +import ch.ethz.ssh2.crypto.cipher.BlockCipherFactory; +import ch.ethz.ssh2.crypto.digest.MAC; +import ch.ethz.ssh2.transport.KexManager; + +/** + * CryptoWishList. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class CryptoWishList { + public String[] kexAlgorithms = KexManager.getDefaultClientKexAlgorithmList(); + public String[] serverHostKeyAlgorithms = KexManager.getDefaultServerHostkeyAlgorithmList(); + public String[] c2s_enc_algos = BlockCipherFactory.getDefaultCipherList(); + public String[] s2c_enc_algos = BlockCipherFactory.getDefaultCipherList(); + public String[] c2s_mac_algos = MAC.getMacList(); + public String[] s2c_mac_algos = MAC.getMacList(); + public String[] c2s_comp_algos = CompressionFactory.getDefaultCompressorList(); + public String[] s2c_comp_algos = CompressionFactory.getDefaultCompressorList(); + + public static CryptoWishList forServer() { + CryptoWishList cwl = new CryptoWishList(); + cwl.kexAlgorithms = KexManager.getDefaultServerKexAlgorithmList(); + return cwl; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/KeyMaterial.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/KeyMaterial.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto; + +import java.io.IOException; +import java.math.BigInteger; + +import ch.ethz.ssh2.crypto.digest.HashForSSH2Types; + +/** + * Establishes key material for iv/key/mac (both directions). + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class KeyMaterial { + public byte[] initial_iv_client_to_server; + public byte[] initial_iv_server_to_client; + public byte[] enc_key_client_to_server; + public byte[] enc_key_server_to_client; + public byte[] integrity_key_client_to_server; + public byte[] integrity_key_server_to_client; + + private static byte[] calculateKey(HashForSSH2Types sh, BigInteger K, byte[] H, byte type, byte[] SessionID, + int keyLength) throws IOException { + byte[] res = new byte[keyLength]; + int dglen = sh.getDigestLength(); + int numRounds = (keyLength + dglen - 1) / dglen; + byte[][] tmp = new byte[numRounds][]; + sh.reset(); + sh.updateBigInt(K); + sh.updateBytes(H); + sh.updateByte(type); + sh.updateBytes(SessionID); + tmp[0] = sh.getDigest(); + int off = 0; + int produced = Math.min(dglen, keyLength); + System.arraycopy(tmp[0], 0, res, off, produced); + keyLength -= produced; + off += produced; + + for (int i = 1; i < numRounds; i++) { + sh.updateBigInt(K); + sh.updateBytes(H); + + for (int j = 0; j < i; j++) { + sh.updateBytes(tmp[j]); + } + + tmp[i] = sh.getDigest(); + produced = Math.min(dglen, keyLength); + System.arraycopy(tmp[i], 0, res, off, produced); + keyLength -= produced; + off += produced; + } + + return res; + } + + public static KeyMaterial create(String hashType, byte[] H, BigInteger K, byte[] SessionID, int keyLengthCS, + int blockSizeCS, int macLengthCS, int keyLengthSC, int blockSizeSC, int macLengthSC) + throws IOException { + KeyMaterial km = new KeyMaterial(); + HashForSSH2Types sh = new HashForSSH2Types(hashType); + km.initial_iv_client_to_server = calculateKey(sh, K, H, (byte) 'A', SessionID, blockSizeCS); + km.initial_iv_server_to_client = calculateKey(sh, K, H, (byte) 'B', SessionID, blockSizeSC); + km.enc_key_client_to_server = calculateKey(sh, K, H, (byte) 'C', SessionID, keyLengthCS); + km.enc_key_server_to_client = calculateKey(sh, K, H, (byte) 'D', SessionID, keyLengthSC); + km.integrity_key_client_to_server = calculateKey(sh, K, H, (byte) 'E', SessionID, macLengthCS); + km.integrity_key_server_to_client = calculateKey(sh, K, H, (byte) 'F', SessionID, macLengthSC); + return km; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/PEMDecoder.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/PEMDecoder.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,444 @@ + +package ch.ethz.ssh2.crypto; + +import java.io.BufferedReader; +import java.io.CharArrayReader; +import java.io.IOException; +import java.math.BigInteger; +import java.security.DigestException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +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.RSAPrivateKeySpec; +import java.security.spec.RSAPublicKeySpec; + +import ch.ethz.ssh2.crypto.cipher.AES; +import ch.ethz.ssh2.crypto.cipher.BlockCipher; +import ch.ethz.ssh2.crypto.cipher.CBCMode; +import ch.ethz.ssh2.crypto.cipher.DES; +import ch.ethz.ssh2.crypto.cipher.DESede; +import ch.ethz.ssh2.signature.ECDSASHA2Verify; + +/** + * PEM Support. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PEMDecoder.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ + */ +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; + + private static final int hexToInt(char c) { + if ((c >= 'a') && (c <= 'f')) { + return (c - 'a') + 10; + } + + if ((c >= 'A') && (c <= 'F')) { + return (c - 'A') + 10; + } + + if ((c >= '0') && (c <= '9')) { + return (c - '0'); + } + + throw new IllegalArgumentException("Need hex char"); + } + + private static byte[] hexToByteArray(String hex) { + if (hex == null) + throw new IllegalArgumentException("null argument"); + + if ((hex.length() % 2) != 0) + throw new IllegalArgumentException("Uneven string length in hex encoding."); + + byte decoded[] = new byte[hex.length() / 2]; + + for (int i = 0; i < decoded.length; i++) { + int hi = hexToInt(hex.charAt(i * 2)); + int lo = hexToInt(hex.charAt((i * 2) + 1)); + decoded[i] = (byte)(hi * 16 + lo); + } + + return decoded; + } + + private static byte[] generateKeyFromPasswordSaltWithMD5(byte[] password, byte[] salt, int keyLen) + throws IOException { + if (salt.length < 8) + throw new IllegalArgumentException("Salt needs to be at least 8 bytes for key generation."); + + MessageDigest md5; + + try { + md5 = MessageDigest.getInstance("MD5"); + } + catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException("VM does not support MD5", e); + } + + byte[] key = new byte[keyLen]; + byte[] tmp = new byte[md5.getDigestLength()]; + + while (true) { + md5.update(password, 0, password.length); + md5.update(salt, 0, 8); // ARGH we only use the first 8 bytes of the + // salt in this step. + // This took me two hours until I got AES-xxx running. + int copy = (keyLen < tmp.length) ? keyLen : tmp.length; + + try { + md5.digest(tmp, 0, tmp.length); + } + catch (DigestException e) { + IOException ex = new IOException("could not digest password"); + ex.initCause(e); + throw ex; + } + + System.arraycopy(tmp, 0, key, key.length - keyLen, copy); + keyLen -= copy; + + if (keyLen == 0) + return key; + + md5.update(tmp, 0, tmp.length); + } + } + + private static byte[] removePadding(byte[] buff, int blockSize) throws IOException { + /* Removes RFC 1423/PKCS #7 padding */ + int rfc_1423_padding = buff[buff.length - 1] & 0xff; + + if ((rfc_1423_padding < 1) || (rfc_1423_padding > blockSize)) + throw new IOException("Decrypted PEM has wrong padding, did you specify the correct password?"); + + for (int i = 2; i <= rfc_1423_padding; i++) { + if (buff[buff.length - i] != rfc_1423_padding) + throw new IOException("Decrypted PEM has wrong padding, did you specify the correct password?"); + } + + byte[] tmp = new byte[buff.length - rfc_1423_padding]; + System.arraycopy(buff, 0, tmp, 0, buff.length - rfc_1423_padding); + return tmp; + } + + public static final PEMStructure parsePEM(char[] pem) throws IOException { + PEMStructure ps = new PEMStructure(); + String line = null; + BufferedReader br = new BufferedReader(new CharArrayReader(pem)); + String endLine = null; + + while (true) { + line = br.readLine(); + + if (line == null) + throw new IOException("Invalid PEM structure, '-----BEGIN...' missing"); + + line = line.trim(); + + if (line.startsWith("-----BEGIN DSA PRIVATE KEY-----")) { + endLine = "-----END DSA PRIVATE KEY-----"; + ps.pemType = PEM_DSA_PRIVATE_KEY; + break; + } + + if (line.startsWith("-----BEGIN RSA PRIVATE KEY-----")) { + endLine = "-----END RSA PRIVATE KEY-----"; + ps.pemType = PEM_RSA_PRIVATE_KEY; + break; + } + + if (line.startsWith("-----BEGIN EC PRIVATE KEY-----")) { + endLine = "-----END EC PRIVATE KEY-----"; + ps.pemType = PEM_EC_PRIVATE_KEY; + break; + } + } + + while (true) { + line = br.readLine(); + + if (line == null) + throw new IOException("Invalid PEM structure, " + endLine + " missing"); + + line = line.trim(); + int sem_idx = line.indexOf(':'); + + if (sem_idx == -1) + break; + + String name = line.substring(0, sem_idx + 1); + String value = line.substring(sem_idx + 1); + String values[] = value.split(","); + + for (int i = 0; i < values.length; i++) + values[i] = values[i].trim(); + + // Proc-Type: 4,ENCRYPTED + // DEK-Info: DES-EDE3-CBC,579B6BE3E5C60483 + + if ("Proc-Type:".equals(name)) { + ps.procType = values; + continue; + } + + if ("DEK-Info:".equals(name)) { + ps.dekInfo = values; + continue; + } + + /* Ignore line */ + } + + StringBuffer keyData = new StringBuffer(); + + while (true) { + if (line == null) + throw new IOException("Invalid PEM structure, " + endLine + " missing"); + + line = line.trim(); + + if (line.startsWith(endLine)) + break; + + keyData.append(line); + line = br.readLine(); + } + + char[] pem_chars = new char[keyData.length()]; + keyData.getChars(0, pem_chars.length, pem_chars, 0); + ps.data = Base64.decode(pem_chars); + + if (ps.data.length == 0) + throw new IOException("Invalid PEM structure, no data available"); + + return ps; + } + + private static final void decryptPEM(PEMStructure ps, byte[] pw) throws IOException { + if (ps.dekInfo == null) + throw new IOException("Broken PEM, no mode and salt given, but encryption enabled"); + + if (ps.dekInfo.length != 2) + throw new IOException("Broken PEM, DEK-Info is incomplete!"); + + String algo = ps.dekInfo[0]; + byte[] salt = hexToByteArray(ps.dekInfo[1]); + BlockCipher bc = null; + + if (algo.equals("DES-EDE3-CBC")) { + DESede des3 = new DESede(); + des3.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 24)); + bc = new CBCMode(des3, salt, false); + } + else if (algo.equals("DES-CBC")) { + DES des = new DES(); + des.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 8)); + bc = new CBCMode(des, salt, false); + } + else if (algo.equals("AES-128-CBC")) { + AES aes = new AES(); + aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 16)); + bc = new CBCMode(aes, salt, false); + } + else if (algo.equals("AES-192-CBC")) { + AES aes = new AES(); + aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 24)); + bc = new CBCMode(aes, salt, false); + } + else if (algo.equals("AES-256-CBC")) { + AES aes = new AES(); + aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 32)); + bc = new CBCMode(aes, salt, false); + } + else { + throw new IOException("Cannot decrypt PEM structure, unknown cipher " + algo); + } + + if ((ps.data.length % bc.getBlockSize()) != 0) + throw new IOException("Invalid PEM structure, size of encrypted block is not a multiple of " + + bc.getBlockSize()); + + /* Now decrypt the content */ + byte[] dz = new byte[ps.data.length]; + + for (int i = 0; i < ps.data.length / bc.getBlockSize(); i++) { + bc.transformBlock(ps.data, i * bc.getBlockSize(), dz, i * bc.getBlockSize()); + } + + /* Now check and remove RFC 1423/PKCS #7 padding */ + dz = removePadding(dz, bc.getBlockSize()); + ps.data = dz; + ps.dekInfo = null; + ps.procType = null; + } + + public static final boolean isPEMEncrypted(PEMStructure ps) throws IOException { + if (ps.procType == null) + return false; + + if (ps.procType.length != 2) + throw new IOException("Unknown Proc-Type field."); + + if ("4".equals(ps.procType[0]) == false) + throw new IOException("Unknown Proc-Type field (" + ps.procType[0] + ")"); + + if ("ENCRYPTED".equals(ps.procType[1])) + return true; + + return false; + } + + public static KeyPair decode(char[] pem, String password) throws IOException { + PEMStructure ps = parsePEM(pem); + return decode(ps, password); + } + + public static KeyPair decode(PEMStructure ps, String password) throws IOException { + if (isPEMEncrypted(ps)) { + if (password == null) + throw new IOException("PEM is encrypted, but no password was specified"); + + decryptPEM(ps, password.getBytes("ISO-8859-1")); + } + + if (ps.pemType == PEM_DSA_PRIVATE_KEY) { + SimpleDERReader dr = new SimpleDERReader(ps.data); + byte[] seq = dr.readSequenceAsByteArray(); + + if (dr.available() != 0) + throw new IOException("Padding in DSA PRIVATE KEY DER stream."); + + dr.resetInput(seq); + BigInteger version = dr.readInt(); + + if (version.compareTo(BigInteger.ZERO) != 0) + throw new IOException("Wrong version (" + version + ") in DSA PRIVATE KEY DER stream."); + + BigInteger p = dr.readInt(); + BigInteger q = dr.readInt(); + BigInteger g = dr.readInt(); + BigInteger y = dr.readInt(); + BigInteger x = dr.readInt(); + + if (dr.available() != 0) + throw new IOException("Padding in DSA PRIVATE KEY DER stream."); + + DSAPrivateKeySpec privSpec = new DSAPrivateKeySpec(x, p, q, g); + DSAPublicKeySpec pubSpec = new DSAPublicKeySpec(y, p, q, g); + return generateKeyPair("DSA", privSpec, pubSpec); + } + + if (ps.pemType == PEM_RSA_PRIVATE_KEY) { + SimpleDERReader dr = new SimpleDERReader(ps.data); + byte[] seq = dr.readSequenceAsByteArray(); + + if (dr.available() != 0) + throw new IOException("Padding in RSA PRIVATE KEY DER stream."); + + dr.resetInput(seq); + BigInteger version = dr.readInt(); + + if ((version.compareTo(BigInteger.ZERO) != 0) && (version.compareTo(BigInteger.ONE) != 0)) + throw new IOException("Wrong version (" + version + ") in RSA PRIVATE KEY DER stream."); + + BigInteger n = dr.readInt(); + BigInteger e = dr.readInt(); + BigInteger d = dr.readInt(); + // TODO: is this right? + BigInteger primeP = dr.readInt(); + BigInteger primeQ = dr.readInt(); + BigInteger expP = dr.readInt(); + BigInteger expQ = dr.readInt(); + BigInteger coeff = dr.readInt(); + RSAPrivateKeySpec privSpec = new RSAPrivateCrtKeySpec(n, e, d, primeP, primeQ, expP, expQ, coeff); + RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(n, e); + return generateKeyPair("RSA", privSpec, pubSpec); + } + + if (ps.pemType == PEM_EC_PRIVATE_KEY) { + SimpleDERReader dr = new SimpleDERReader(ps.data); + byte[] seq = dr.readSequenceAsByteArray(); + + if (dr.available() != 0) + throw new IOException("Padding in EC PRIVATE KEY DER stream."); + + dr.resetInput(seq); + BigInteger version = dr.readInt(); + + if ((version.compareTo(BigInteger.ONE) != 0)) + throw new IOException("Wrong version (" + version + ") in EC PRIVATE KEY DER stream."); + + byte[] privateBytes = dr.readOctetString(); + String curveOid = null; + byte[] publicBytes = null; + + while (dr.available() > 0) { + int type = dr.readConstructedType(); + SimpleDERReader cr = dr.readConstructed(); + + switch (type) { + case 0: + curveOid = cr.readOid(); + break; + + case 1: + publicBytes = cr.readOctetString(); + break; + } + } + + ECParameterSpec params = ECDSASHA2Verify.getCurveForOID(curveOid); + + if (params == null) + throw new IOException("invalid OID"); + + BigInteger s = new BigInteger(privateBytes); + byte[] publicBytesSlice = new byte[publicBytes.length - 1]; + System.arraycopy(publicBytes, 1, publicBytesSlice, 0, publicBytesSlice.length); + ECPoint w = ECDSASHA2Verify.decodeECPoint(publicBytesSlice, params.getCurve()); + ECPrivateKeySpec privSpec = new ECPrivateKeySpec(s, params); + ECPublicKeySpec pubSpec = new ECPublicKeySpec(w, params); + return generateKeyPair("EC", privSpec, pubSpec); + } + + throw new IOException("PEM problem: it is of unknown type"); + } + + /** + * Generate a {@code KeyPair} given an {@code algorithm} and {@code KeySpec}. + */ + private static KeyPair generateKeyPair(String algorithm, KeySpec privSpec, KeySpec pubSpec) + throws IOException { + try { + final KeyFactory kf = KeyFactory.getInstance(algorithm); + final PublicKey pubKey = kf.generatePublic(pubSpec); + final PrivateKey privKey = kf.generatePrivate(privSpec); + 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; + } + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/PEMDecryptException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/PEMDecryptException.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2006-2014 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto; + +import java.io.IOException; + +/** + * @version $Id: PEMDecryptException.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public class PEMDecryptException extends IOException { + /** + * + */ + private static final long serialVersionUID = 1L; + + public PEMDecryptException(String message) { + super(message); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/PEMStructure.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/PEMStructure.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto; + +/** + * Parsed PEM structure. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ + +public class PEMStructure { + public int pemType; + String dekInfo[]; + String procType[]; + byte[] data; +} \ No newline at end of file diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/SimpleDERReader.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/SimpleDERReader.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,205 @@ +package ch.ethz.ssh2.crypto; + +import java.io.IOException; + +import java.math.BigInteger; + +/** + * SimpleDERReader. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: SimpleDERReader.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public class SimpleDERReader { + private static final int CONSTRUCTED = 0x20; + + byte[] buffer; + int pos; + int count; + + public SimpleDERReader(byte[] b) { + resetInput(b); + } + + public SimpleDERReader(byte[] b, int off, int len) { + resetInput(b, off, len); + } + + public void resetInput(byte[] b) { + resetInput(b, 0, b.length); + } + + public void resetInput(byte[] b, int off, int len) { + buffer = b; + pos = off; + count = len; + } + + private byte readByte() throws IOException { + if (count <= 0) + throw new IOException("DER byte array: out of data"); + + count--; + return buffer[pos++]; + } + + private byte[] readBytes(int len) throws IOException { + if (len > count) + throw new IOException("DER byte array: out of data"); + + byte[] b = new byte[len]; + System.arraycopy(buffer, pos, b, 0, len); + pos += len; + count -= len; + return b; + } + + public int available() { + return count; + } + + private int readLength() throws IOException { + int len = readByte() & 0xff; + + if ((len & 0x80) == 0) + return len; + + int remain = len & 0x7F; + + if (remain == 0) + return -1; + + len = 0; + + while (remain > 0) { + len = len << 8; + len = len | (readByte() & 0xff); + remain--; + } + + return len; + } + + public int ignoreNextObject() throws IOException { + int type = readByte() & 0xff; + int len = readLength(); + + if ((len < 0) || len > available()) + throw new IOException("Illegal len in DER object (" + len + ")"); + + readBytes(len); + return type; + } + + public BigInteger readInt() throws IOException { + int type = readByte() & 0xff; + + if (type != 0x02) + throw new IOException("Expected DER Integer, but found type " + type); + + int len = readLength(); + + if ((len < 0) || len > available()) + throw new IOException("Illegal len in DER object (" + len + ")"); + + byte[] b = readBytes(len); + BigInteger bi = new BigInteger(b); + return bi; + } + + public int readConstructedType() throws IOException { + int type = readByte() & 0xff; + + if ((type & CONSTRUCTED) != CONSTRUCTED) + throw new IOException("Expected constructed type, but was " + type); + + return type & 0x1f; + } + + public SimpleDERReader readConstructed() throws IOException { + int len = readLength(); + + if ((len < 0) || len > available()) + throw new IOException("Illegal len in DER object (" + len + ")"); + + SimpleDERReader cr = new SimpleDERReader(buffer, pos, len); + pos += len; + count -= len; + return cr; + } + + public byte[] readSequenceAsByteArray() throws IOException { + int type = readByte() & 0xff; + + if (type != 0x30) + throw new IOException("Expected DER Sequence, but found type " + type); + + int len = readLength(); + + if ((len < 0) || len > available()) + throw new IOException("Illegal len in DER object (" + len + ")"); + + byte[] b = readBytes(len); + return b; + } + + public String readOid() throws IOException { + int type = readByte() & 0xff; + + if (type != 0x06) + throw new IOException("Expected DER OID, but found type " + type); + + int len = readLength(); + + if ((len < 1) || len > available()) + throw new IOException("Illegal len in DER object (" + len + ")"); + + byte[] b = readBytes(len); + long value = 0; + StringBuilder sb = new StringBuilder(64); + + switch (b[0] / 40) { + case 0: + sb.append('0'); + break; + + case 1: + sb.append('1'); + b[0] -= 40; + break; + + default: + sb.append('2'); + b[0] -= 80; + break; + } + + for (int i = 0; i < len; i++) { + value = (value << 7) + (b[i] & 0x7F); + + if ((b[i] & 0x80) == 0) { + sb.append('.'); + sb.append(value); + value = 0; + } + } + + return sb.toString(); + } + + public byte[] readOctetString() throws IOException { + int type = readByte() & 0xff; + + if (type != 0x04 && type != 0x03) + throw new IOException("Expected DER Octetstring, but found type " + type); + + int len = readLength(); + + if ((len < 0) || len > available()) + throw new IOException("Illegal len in DER object (" + len + ")"); + + byte[] b = readBytes(len); + return b; + } + +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/cipher/AES.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/cipher/AES.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,661 @@ +package ch.ethz.ssh2.crypto.cipher; + +/* + This file was shamelessly taken from the Bouncy Castle Crypto package. + Their licence file states the following: + + Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle + (http://www.bouncycastle.org) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +/** + * An implementation of the AES (Rijndael), from FIPS-197. + *

+ * For further details see: http://csrc.nist.gov/encryption/aes/ + * . + * + * This implementation is based on optimizations from Dr. Brian Gladman's paper + * and C code at http://fp.gladman.plus.com/cryptography_technology/rijndael/ + * + * + * There are three levels of tradeoff of speed vs memory Because java has no + * preprocessor, they are written as three separate classes from which to choose + * + * The fastest uses 8Kbytes of static tables to precompute round calculations, 4 + * 256 word tables for encryption and 4 for decryption. + * + * The middle performance version uses only one 256 word table for each, for a + * total of 2Kbytes, adding 12 rotate operations per round to compute the values + * contained in the other tables from the contents of the first + * + * The slowest version uses no static tables at all and computes the values in + * each round + *

+ * This file contains the fast version with 8Kbytes of static tables for round + * precomputation + * + * @author See comments in the source file + * @version 2.50, 03/15/10 + */ +public class AES implements BlockCipher { + // The S box + private static final byte[] S = { (byte) 99, (byte) 124, (byte) 119, (byte) 123, (byte) 242, (byte) 107, + (byte) 111, (byte) 197, (byte) 48, (byte) 1, (byte) 103, (byte) 43, (byte) 254, (byte) 215, (byte) 171, + (byte) 118, (byte) 202, (byte) 130, (byte) 201, (byte) 125, (byte) 250, (byte) 89, (byte) 71, (byte) 240, + (byte) 173, (byte) 212, (byte) 162, (byte) 175, (byte) 156, (byte) 164, (byte) 114, (byte) 192, (byte) 183, + (byte) 253, (byte) 147, (byte) 38, (byte) 54, (byte) 63, (byte) 247, (byte) 204, (byte) 52, (byte) 165, + (byte) 229, (byte) 241, (byte) 113, (byte) 216, (byte) 49, (byte) 21, (byte) 4, (byte) 199, (byte) 35, + (byte) 195, (byte) 24, (byte) 150, (byte) 5, (byte) 154, (byte) 7, (byte) 18, (byte) 128, (byte) 226, + (byte) 235, (byte) 39, (byte) 178, (byte) 117, (byte) 9, (byte) 131, (byte) 44, (byte) 26, (byte) 27, + (byte) 110, (byte) 90, (byte) 160, (byte) 82, (byte) 59, (byte) 214, (byte) 179, (byte) 41, (byte) 227, + (byte) 47, (byte) 132, (byte) 83, (byte) 209, (byte) 0, (byte) 237, (byte) 32, (byte) 252, (byte) 177, + (byte) 91, (byte) 106, (byte) 203, (byte) 190, (byte) 57, (byte) 74, (byte) 76, (byte) 88, (byte) 207, + (byte) 208, (byte) 239, (byte) 170, (byte) 251, (byte) 67, (byte) 77, (byte) 51, (byte) 133, (byte) 69, + (byte) 249, (byte) 2, (byte) 127, (byte) 80, (byte) 60, (byte) 159, (byte) 168, (byte) 81, (byte) 163, + (byte) 64, (byte) 143, (byte) 146, (byte) 157, (byte) 56, (byte) 245, (byte) 188, (byte) 182, (byte) 218, + (byte) 33, (byte) 16, (byte) 255, (byte) 243, (byte) 210, (byte) 205, (byte) 12, (byte) 19, (byte) 236, + (byte) 95, (byte) 151, (byte) 68, (byte) 23, (byte) 196, (byte) 167, (byte) 126, (byte) 61, (byte) 100, + (byte) 93, (byte) 25, (byte) 115, (byte) 96, (byte) 129, (byte) 79, (byte) 220, (byte) 34, (byte) 42, + (byte) 144, (byte) 136, (byte) 70, (byte) 238, (byte) 184, (byte) 20, (byte) 222, (byte) 94, (byte) 11, + (byte) 219, (byte) 224, (byte) 50, (byte) 58, (byte) 10, (byte) 73, (byte) 6, (byte) 36, (byte) 92, + (byte) 194, (byte) 211, (byte) 172, (byte) 98, (byte) 145, (byte) 149, (byte) 228, (byte) 121, (byte) 231, + (byte) 200, (byte) 55, (byte) 109, (byte) 141, (byte) 213, (byte) 78, (byte) 169, (byte) 108, (byte) 86, + (byte) 244, (byte) 234, (byte) 101, (byte) 122, (byte) 174, (byte) 8, (byte) 186, (byte) 120, (byte) 37, + (byte) 46, (byte) 28, (byte) 166, (byte) 180, (byte) 198, (byte) 232, (byte) 221, (byte) 116, (byte) 31, + (byte) 75, (byte) 189, (byte) 139, (byte) 138, (byte) 112, (byte) 62, (byte) 181, (byte) 102, (byte) 72, + (byte) 3, (byte) 246, (byte) 14, (byte) 97, (byte) 53, (byte) 87, (byte) 185, (byte) 134, (byte) 193, + (byte) 29, (byte) 158, (byte) 225, (byte) 248, (byte) 152, (byte) 17, (byte) 105, (byte) 217, (byte) 142, + (byte) 148, (byte) 155, (byte) 30, (byte) 135, (byte) 233, (byte) 206, (byte) 85, (byte) 40, (byte) 223, + (byte) 140, (byte) 161, (byte) 137, (byte) 13, (byte) 191, (byte) 230, (byte) 66, (byte) 104, (byte) 65, + (byte) 153, (byte) 45, (byte) 15, (byte) 176, (byte) 84, (byte) 187, (byte) 22, + }; + + // The inverse S-box + private static final byte[] Si = { (byte) 82, (byte) 9, (byte) 106, (byte) 213, (byte) 48, (byte) 54, (byte) 165, + (byte) 56, (byte) 191, (byte) 64, (byte) 163, (byte) 158, (byte) 129, (byte) 243, (byte) 215, (byte) 251, + (byte) 124, (byte) 227, (byte) 57, (byte) 130, (byte) 155, (byte) 47, (byte) 255, (byte) 135, (byte) 52, + (byte) 142, (byte) 67, (byte) 68, (byte) 196, (byte) 222, (byte) 233, (byte) 203, (byte) 84, (byte) 123, + (byte) 148, (byte) 50, (byte) 166, (byte) 194, (byte) 35, (byte) 61, (byte) 238, (byte) 76, (byte) 149, + (byte) 11, (byte) 66, (byte) 250, (byte) 195, (byte) 78, (byte) 8, (byte) 46, (byte) 161, (byte) 102, + (byte) 40, (byte) 217, (byte) 36, (byte) 178, (byte) 118, (byte) 91, (byte) 162, (byte) 73, (byte) 109, + (byte) 139, (byte) 209, (byte) 37, (byte) 114, (byte) 248, (byte) 246, (byte) 100, (byte) 134, (byte) 104, + (byte) 152, (byte) 22, (byte) 212, (byte) 164, (byte) 92, (byte) 204, (byte) 93, (byte) 101, (byte) 182, + (byte) 146, (byte) 108, (byte) 112, (byte) 72, (byte) 80, (byte) 253, (byte) 237, (byte) 185, (byte) 218, + (byte) 94, (byte) 21, (byte) 70, (byte) 87, (byte) 167, (byte) 141, (byte) 157, (byte) 132, (byte) 144, + (byte) 216, (byte) 171, (byte) 0, (byte) 140, (byte) 188, (byte) 211, (byte) 10, (byte) 247, (byte) 228, + (byte) 88, (byte) 5, (byte) 184, (byte) 179, (byte) 69, (byte) 6, (byte) 208, (byte) 44, (byte) 30, + (byte) 143, (byte) 202, (byte) 63, (byte) 15, (byte) 2, (byte) 193, (byte) 175, (byte) 189, (byte) 3, + (byte) 1, (byte) 19, (byte) 138, (byte) 107, (byte) 58, (byte) 145, (byte) 17, (byte) 65, (byte) 79, + (byte) 103, (byte) 220, (byte) 234, (byte) 151, (byte) 242, (byte) 207, (byte) 206, (byte) 240, (byte) 180, + (byte) 230, (byte) 115, (byte) 150, (byte) 172, (byte) 116, (byte) 34, (byte) 231, (byte) 173, (byte) 53, + (byte) 133, (byte) 226, (byte) 249, (byte) 55, (byte) 232, (byte) 28, (byte) 117, (byte) 223, (byte) 110, + (byte) 71, (byte) 241, (byte) 26, (byte) 113, (byte) 29, (byte) 41, (byte) 197, (byte) 137, (byte) 111, + (byte) 183, (byte) 98, (byte) 14, (byte) 170, (byte) 24, (byte) 190, (byte) 27, (byte) 252, (byte) 86, + (byte) 62, (byte) 75, (byte) 198, (byte) 210, (byte) 121, (byte) 32, (byte) 154, (byte) 219, (byte) 192, + (byte) 254, (byte) 120, (byte) 205, (byte) 90, (byte) 244, (byte) 31, (byte) 221, (byte) 168, (byte) 51, + (byte) 136, (byte) 7, (byte) 199, (byte) 49, (byte) 177, (byte) 18, (byte) 16, (byte) 89, (byte) 39, + (byte) 128, (byte) 236, (byte) 95, (byte) 96, (byte) 81, (byte) 127, (byte) 169, (byte) 25, (byte) 181, + (byte) 74, (byte) 13, (byte) 45, (byte) 229, (byte) 122, (byte) 159, (byte) 147, (byte) 201, (byte) 156, + (byte) 239, (byte) 160, (byte) 224, (byte) 59, (byte) 77, (byte) 174, (byte) 42, (byte) 245, (byte) 176, + (byte) 200, (byte) 235, (byte) 187, (byte) 60, (byte) 131, (byte) 83, (byte) 153, (byte) 97, (byte) 23, + (byte) 43, (byte) 4, (byte) 126, (byte) 186, (byte) 119, (byte) 214, (byte) 38, (byte) 225, (byte) 105, + (byte) 20, (byte) 99, (byte) 85, (byte) 33, (byte) 12, (byte) 125, + }; + + // vector used in calculating key schedule (powers of x in GF(256)) + private static final int[] rcon = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, + 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 + }; + + // precomputation tables of calculations for rounds + private static final int[] T0 = { 0xa56363c6, 0x847c7cf8, 0x997777ee, 0x8d7b7bf6, 0x0df2f2ff, 0xbd6b6bd6, + 0xb16f6fde, 0x54c5c591, 0x50303060, 0x03010102, 0xa96767ce, 0x7d2b2b56, 0x19fefee7, 0x62d7d7b5, 0xe6abab4d, + 0x9a7676ec, 0x45caca8f, 0x9d82821f, 0x40c9c989, 0x877d7dfa, 0x15fafaef, 0xeb5959b2, 0xc947478e, 0x0bf0f0fb, + 0xecadad41, 0x67d4d4b3, 0xfda2a25f, 0xeaafaf45, 0xbf9c9c23, 0xf7a4a453, 0x967272e4, 0x5bc0c09b, 0xc2b7b775, + 0x1cfdfde1, 0xae93933d, 0x6a26264c, 0x5a36366c, 0x413f3f7e, 0x02f7f7f5, 0x4fcccc83, 0x5c343468, 0xf4a5a551, + 0x34e5e5d1, 0x08f1f1f9, 0x937171e2, 0x73d8d8ab, 0x53313162, 0x3f15152a, 0x0c040408, 0x52c7c795, 0x65232346, + 0x5ec3c39d, 0x28181830, 0xa1969637, 0x0f05050a, 0xb59a9a2f, 0x0907070e, 0x36121224, 0x9b80801b, 0x3de2e2df, + 0x26ebebcd, 0x6927274e, 0xcdb2b27f, 0x9f7575ea, 0x1b090912, 0x9e83831d, 0x742c2c58, 0x2e1a1a34, 0x2d1b1b36, + 0xb26e6edc, 0xee5a5ab4, 0xfba0a05b, 0xf65252a4, 0x4d3b3b76, 0x61d6d6b7, 0xceb3b37d, 0x7b292952, 0x3ee3e3dd, + 0x712f2f5e, 0x97848413, 0xf55353a6, 0x68d1d1b9, 0x00000000, 0x2cededc1, 0x60202040, 0x1ffcfce3, 0xc8b1b179, + 0xed5b5bb6, 0xbe6a6ad4, 0x46cbcb8d, 0xd9bebe67, 0x4b393972, 0xde4a4a94, 0xd44c4c98, 0xe85858b0, 0x4acfcf85, + 0x6bd0d0bb, 0x2aefefc5, 0xe5aaaa4f, 0x16fbfbed, 0xc5434386, 0xd74d4d9a, 0x55333366, 0x94858511, 0xcf45458a, + 0x10f9f9e9, 0x06020204, 0x817f7ffe, 0xf05050a0, 0x443c3c78, 0xba9f9f25, 0xe3a8a84b, 0xf35151a2, 0xfea3a35d, + 0xc0404080, 0x8a8f8f05, 0xad92923f, 0xbc9d9d21, 0x48383870, 0x04f5f5f1, 0xdfbcbc63, 0xc1b6b677, 0x75dadaaf, + 0x63212142, 0x30101020, 0x1affffe5, 0x0ef3f3fd, 0x6dd2d2bf, 0x4ccdcd81, 0x140c0c18, 0x35131326, 0x2fececc3, + 0xe15f5fbe, 0xa2979735, 0xcc444488, 0x3917172e, 0x57c4c493, 0xf2a7a755, 0x827e7efc, 0x473d3d7a, 0xac6464c8, + 0xe75d5dba, 0x2b191932, 0x957373e6, 0xa06060c0, 0x98818119, 0xd14f4f9e, 0x7fdcdca3, 0x66222244, 0x7e2a2a54, + 0xab90903b, 0x8388880b, 0xca46468c, 0x29eeeec7, 0xd3b8b86b, 0x3c141428, 0x79dedea7, 0xe25e5ebc, 0x1d0b0b16, + 0x76dbdbad, 0x3be0e0db, 0x56323264, 0x4e3a3a74, 0x1e0a0a14, 0xdb494992, 0x0a06060c, 0x6c242448, 0xe45c5cb8, + 0x5dc2c29f, 0x6ed3d3bd, 0xefacac43, 0xa66262c4, 0xa8919139, 0xa4959531, 0x37e4e4d3, 0x8b7979f2, 0x32e7e7d5, + 0x43c8c88b, 0x5937376e, 0xb76d6dda, 0x8c8d8d01, 0x64d5d5b1, 0xd24e4e9c, 0xe0a9a949, 0xb46c6cd8, 0xfa5656ac, + 0x07f4f4f3, 0x25eaeacf, 0xaf6565ca, 0x8e7a7af4, 0xe9aeae47, 0x18080810, 0xd5baba6f, 0x887878f0, 0x6f25254a, + 0x722e2e5c, 0x241c1c38, 0xf1a6a657, 0xc7b4b473, 0x51c6c697, 0x23e8e8cb, 0x7cdddda1, 0x9c7474e8, 0x211f1f3e, + 0xdd4b4b96, 0xdcbdbd61, 0x868b8b0d, 0x858a8a0f, 0x907070e0, 0x423e3e7c, 0xc4b5b571, 0xaa6666cc, 0xd8484890, + 0x05030306, 0x01f6f6f7, 0x120e0e1c, 0xa36161c2, 0x5f35356a, 0xf95757ae, 0xd0b9b969, 0x91868617, 0x58c1c199, + 0x271d1d3a, 0xb99e9e27, 0x38e1e1d9, 0x13f8f8eb, 0xb398982b, 0x33111122, 0xbb6969d2, 0x70d9d9a9, 0x898e8e07, + 0xa7949433, 0xb69b9b2d, 0x221e1e3c, 0x92878715, 0x20e9e9c9, 0x49cece87, 0xff5555aa, 0x78282850, 0x7adfdfa5, + 0x8f8c8c03, 0xf8a1a159, 0x80898909, 0x170d0d1a, 0xdabfbf65, 0x31e6e6d7, 0xc6424284, 0xb86868d0, 0xc3414182, + 0xb0999929, 0x772d2d5a, 0x110f0f1e, 0xcbb0b07b, 0xfc5454a8, 0xd6bbbb6d, 0x3a16162c + }; + + private static final int[] T1 = { 0x6363c6a5, 0x7c7cf884, 0x7777ee99, 0x7b7bf68d, 0xf2f2ff0d, 0x6b6bd6bd, + 0x6f6fdeb1, 0xc5c59154, 0x30306050, 0x01010203, 0x6767cea9, 0x2b2b567d, 0xfefee719, 0xd7d7b562, 0xabab4de6, + 0x7676ec9a, 0xcaca8f45, 0x82821f9d, 0xc9c98940, 0x7d7dfa87, 0xfafaef15, 0x5959b2eb, 0x47478ec9, 0xf0f0fb0b, + 0xadad41ec, 0xd4d4b367, 0xa2a25ffd, 0xafaf45ea, 0x9c9c23bf, 0xa4a453f7, 0x7272e496, 0xc0c09b5b, 0xb7b775c2, + 0xfdfde11c, 0x93933dae, 0x26264c6a, 0x36366c5a, 0x3f3f7e41, 0xf7f7f502, 0xcccc834f, 0x3434685c, 0xa5a551f4, + 0xe5e5d134, 0xf1f1f908, 0x7171e293, 0xd8d8ab73, 0x31316253, 0x15152a3f, 0x0404080c, 0xc7c79552, 0x23234665, + 0xc3c39d5e, 0x18183028, 0x969637a1, 0x05050a0f, 0x9a9a2fb5, 0x07070e09, 0x12122436, 0x80801b9b, 0xe2e2df3d, + 0xebebcd26, 0x27274e69, 0xb2b27fcd, 0x7575ea9f, 0x0909121b, 0x83831d9e, 0x2c2c5874, 0x1a1a342e, 0x1b1b362d, + 0x6e6edcb2, 0x5a5ab4ee, 0xa0a05bfb, 0x5252a4f6, 0x3b3b764d, 0xd6d6b761, 0xb3b37dce, 0x2929527b, 0xe3e3dd3e, + 0x2f2f5e71, 0x84841397, 0x5353a6f5, 0xd1d1b968, 0x00000000, 0xededc12c, 0x20204060, 0xfcfce31f, 0xb1b179c8, + 0x5b5bb6ed, 0x6a6ad4be, 0xcbcb8d46, 0xbebe67d9, 0x3939724b, 0x4a4a94de, 0x4c4c98d4, 0x5858b0e8, 0xcfcf854a, + 0xd0d0bb6b, 0xefefc52a, 0xaaaa4fe5, 0xfbfbed16, 0x434386c5, 0x4d4d9ad7, 0x33336655, 0x85851194, 0x45458acf, + 0xf9f9e910, 0x02020406, 0x7f7ffe81, 0x5050a0f0, 0x3c3c7844, 0x9f9f25ba, 0xa8a84be3, 0x5151a2f3, 0xa3a35dfe, + 0x404080c0, 0x8f8f058a, 0x92923fad, 0x9d9d21bc, 0x38387048, 0xf5f5f104, 0xbcbc63df, 0xb6b677c1, 0xdadaaf75, + 0x21214263, 0x10102030, 0xffffe51a, 0xf3f3fd0e, 0xd2d2bf6d, 0xcdcd814c, 0x0c0c1814, 0x13132635, 0xececc32f, + 0x5f5fbee1, 0x979735a2, 0x444488cc, 0x17172e39, 0xc4c49357, 0xa7a755f2, 0x7e7efc82, 0x3d3d7a47, 0x6464c8ac, + 0x5d5dbae7, 0x1919322b, 0x7373e695, 0x6060c0a0, 0x81811998, 0x4f4f9ed1, 0xdcdca37f, 0x22224466, 0x2a2a547e, + 0x90903bab, 0x88880b83, 0x46468cca, 0xeeeec729, 0xb8b86bd3, 0x1414283c, 0xdedea779, 0x5e5ebce2, 0x0b0b161d, + 0xdbdbad76, 0xe0e0db3b, 0x32326456, 0x3a3a744e, 0x0a0a141e, 0x494992db, 0x06060c0a, 0x2424486c, 0x5c5cb8e4, + 0xc2c29f5d, 0xd3d3bd6e, 0xacac43ef, 0x6262c4a6, 0x919139a8, 0x959531a4, 0xe4e4d337, 0x7979f28b, 0xe7e7d532, + 0xc8c88b43, 0x37376e59, 0x6d6ddab7, 0x8d8d018c, 0xd5d5b164, 0x4e4e9cd2, 0xa9a949e0, 0x6c6cd8b4, 0x5656acfa, + 0xf4f4f307, 0xeaeacf25, 0x6565caaf, 0x7a7af48e, 0xaeae47e9, 0x08081018, 0xbaba6fd5, 0x7878f088, 0x25254a6f, + 0x2e2e5c72, 0x1c1c3824, 0xa6a657f1, 0xb4b473c7, 0xc6c69751, 0xe8e8cb23, 0xdddda17c, 0x7474e89c, 0x1f1f3e21, + 0x4b4b96dd, 0xbdbd61dc, 0x8b8b0d86, 0x8a8a0f85, 0x7070e090, 0x3e3e7c42, 0xb5b571c4, 0x6666ccaa, 0x484890d8, + 0x03030605, 0xf6f6f701, 0x0e0e1c12, 0x6161c2a3, 0x35356a5f, 0x5757aef9, 0xb9b969d0, 0x86861791, 0xc1c19958, + 0x1d1d3a27, 0x9e9e27b9, 0xe1e1d938, 0xf8f8eb13, 0x98982bb3, 0x11112233, 0x6969d2bb, 0xd9d9a970, 0x8e8e0789, + 0x949433a7, 0x9b9b2db6, 0x1e1e3c22, 0x87871592, 0xe9e9c920, 0xcece8749, 0x5555aaff, 0x28285078, 0xdfdfa57a, + 0x8c8c038f, 0xa1a159f8, 0x89890980, 0x0d0d1a17, 0xbfbf65da, 0xe6e6d731, 0x424284c6, 0x6868d0b8, 0x414182c3, + 0x999929b0, 0x2d2d5a77, 0x0f0f1e11, 0xb0b07bcb, 0x5454a8fc, 0xbbbb6dd6, 0x16162c3a + }; + + private static final int[] T2 = { 0x63c6a563, 0x7cf8847c, 0x77ee9977, 0x7bf68d7b, 0xf2ff0df2, 0x6bd6bd6b, + 0x6fdeb16f, 0xc59154c5, 0x30605030, 0x01020301, 0x67cea967, 0x2b567d2b, 0xfee719fe, 0xd7b562d7, 0xab4de6ab, + 0x76ec9a76, 0xca8f45ca, 0x821f9d82, 0xc98940c9, 0x7dfa877d, 0xfaef15fa, 0x59b2eb59, 0x478ec947, 0xf0fb0bf0, + 0xad41ecad, 0xd4b367d4, 0xa25ffda2, 0xaf45eaaf, 0x9c23bf9c, 0xa453f7a4, 0x72e49672, 0xc09b5bc0, 0xb775c2b7, + 0xfde11cfd, 0x933dae93, 0x264c6a26, 0x366c5a36, 0x3f7e413f, 0xf7f502f7, 0xcc834fcc, 0x34685c34, 0xa551f4a5, + 0xe5d134e5, 0xf1f908f1, 0x71e29371, 0xd8ab73d8, 0x31625331, 0x152a3f15, 0x04080c04, 0xc79552c7, 0x23466523, + 0xc39d5ec3, 0x18302818, 0x9637a196, 0x050a0f05, 0x9a2fb59a, 0x070e0907, 0x12243612, 0x801b9b80, 0xe2df3de2, + 0xebcd26eb, 0x274e6927, 0xb27fcdb2, 0x75ea9f75, 0x09121b09, 0x831d9e83, 0x2c58742c, 0x1a342e1a, 0x1b362d1b, + 0x6edcb26e, 0x5ab4ee5a, 0xa05bfba0, 0x52a4f652, 0x3b764d3b, 0xd6b761d6, 0xb37dceb3, 0x29527b29, 0xe3dd3ee3, + 0x2f5e712f, 0x84139784, 0x53a6f553, 0xd1b968d1, 0x00000000, 0xedc12ced, 0x20406020, 0xfce31ffc, 0xb179c8b1, + 0x5bb6ed5b, 0x6ad4be6a, 0xcb8d46cb, 0xbe67d9be, 0x39724b39, 0x4a94de4a, 0x4c98d44c, 0x58b0e858, 0xcf854acf, + 0xd0bb6bd0, 0xefc52aef, 0xaa4fe5aa, 0xfbed16fb, 0x4386c543, 0x4d9ad74d, 0x33665533, 0x85119485, 0x458acf45, + 0xf9e910f9, 0x02040602, 0x7ffe817f, 0x50a0f050, 0x3c78443c, 0x9f25ba9f, 0xa84be3a8, 0x51a2f351, 0xa35dfea3, + 0x4080c040, 0x8f058a8f, 0x923fad92, 0x9d21bc9d, 0x38704838, 0xf5f104f5, 0xbc63dfbc, 0xb677c1b6, 0xdaaf75da, + 0x21426321, 0x10203010, 0xffe51aff, 0xf3fd0ef3, 0xd2bf6dd2, 0xcd814ccd, 0x0c18140c, 0x13263513, 0xecc32fec, + 0x5fbee15f, 0x9735a297, 0x4488cc44, 0x172e3917, 0xc49357c4, 0xa755f2a7, 0x7efc827e, 0x3d7a473d, 0x64c8ac64, + 0x5dbae75d, 0x19322b19, 0x73e69573, 0x60c0a060, 0x81199881, 0x4f9ed14f, 0xdca37fdc, 0x22446622, 0x2a547e2a, + 0x903bab90, 0x880b8388, 0x468cca46, 0xeec729ee, 0xb86bd3b8, 0x14283c14, 0xdea779de, 0x5ebce25e, 0x0b161d0b, + 0xdbad76db, 0xe0db3be0, 0x32645632, 0x3a744e3a, 0x0a141e0a, 0x4992db49, 0x060c0a06, 0x24486c24, 0x5cb8e45c, + 0xc29f5dc2, 0xd3bd6ed3, 0xac43efac, 0x62c4a662, 0x9139a891, 0x9531a495, 0xe4d337e4, 0x79f28b79, 0xe7d532e7, + 0xc88b43c8, 0x376e5937, 0x6ddab76d, 0x8d018c8d, 0xd5b164d5, 0x4e9cd24e, 0xa949e0a9, 0x6cd8b46c, 0x56acfa56, + 0xf4f307f4, 0xeacf25ea, 0x65caaf65, 0x7af48e7a, 0xae47e9ae, 0x08101808, 0xba6fd5ba, 0x78f08878, 0x254a6f25, + 0x2e5c722e, 0x1c38241c, 0xa657f1a6, 0xb473c7b4, 0xc69751c6, 0xe8cb23e8, 0xdda17cdd, 0x74e89c74, 0x1f3e211f, + 0x4b96dd4b, 0xbd61dcbd, 0x8b0d868b, 0x8a0f858a, 0x70e09070, 0x3e7c423e, 0xb571c4b5, 0x66ccaa66, 0x4890d848, + 0x03060503, 0xf6f701f6, 0x0e1c120e, 0x61c2a361, 0x356a5f35, 0x57aef957, 0xb969d0b9, 0x86179186, 0xc19958c1, + 0x1d3a271d, 0x9e27b99e, 0xe1d938e1, 0xf8eb13f8, 0x982bb398, 0x11223311, 0x69d2bb69, 0xd9a970d9, 0x8e07898e, + 0x9433a794, 0x9b2db69b, 0x1e3c221e, 0x87159287, 0xe9c920e9, 0xce8749ce, 0x55aaff55, 0x28507828, 0xdfa57adf, + 0x8c038f8c, 0xa159f8a1, 0x89098089, 0x0d1a170d, 0xbf65dabf, 0xe6d731e6, 0x4284c642, 0x68d0b868, 0x4182c341, + 0x9929b099, 0x2d5a772d, 0x0f1e110f, 0xb07bcbb0, 0x54a8fc54, 0xbb6dd6bb, 0x162c3a16 + }; + + private static final int[] T3 = { 0xc6a56363, 0xf8847c7c, 0xee997777, 0xf68d7b7b, 0xff0df2f2, 0xd6bd6b6b, + 0xdeb16f6f, 0x9154c5c5, 0x60503030, 0x02030101, 0xcea96767, 0x567d2b2b, 0xe719fefe, 0xb562d7d7, 0x4de6abab, + 0xec9a7676, 0x8f45caca, 0x1f9d8282, 0x8940c9c9, 0xfa877d7d, 0xef15fafa, 0xb2eb5959, 0x8ec94747, 0xfb0bf0f0, + 0x41ecadad, 0xb367d4d4, 0x5ffda2a2, 0x45eaafaf, 0x23bf9c9c, 0x53f7a4a4, 0xe4967272, 0x9b5bc0c0, 0x75c2b7b7, + 0xe11cfdfd, 0x3dae9393, 0x4c6a2626, 0x6c5a3636, 0x7e413f3f, 0xf502f7f7, 0x834fcccc, 0x685c3434, 0x51f4a5a5, + 0xd134e5e5, 0xf908f1f1, 0xe2937171, 0xab73d8d8, 0x62533131, 0x2a3f1515, 0x080c0404, 0x9552c7c7, 0x46652323, + 0x9d5ec3c3, 0x30281818, 0x37a19696, 0x0a0f0505, 0x2fb59a9a, 0x0e090707, 0x24361212, 0x1b9b8080, 0xdf3de2e2, + 0xcd26ebeb, 0x4e692727, 0x7fcdb2b2, 0xea9f7575, 0x121b0909, 0x1d9e8383, 0x58742c2c, 0x342e1a1a, 0x362d1b1b, + 0xdcb26e6e, 0xb4ee5a5a, 0x5bfba0a0, 0xa4f65252, 0x764d3b3b, 0xb761d6d6, 0x7dceb3b3, 0x527b2929, 0xdd3ee3e3, + 0x5e712f2f, 0x13978484, 0xa6f55353, 0xb968d1d1, 0x00000000, 0xc12ceded, 0x40602020, 0xe31ffcfc, 0x79c8b1b1, + 0xb6ed5b5b, 0xd4be6a6a, 0x8d46cbcb, 0x67d9bebe, 0x724b3939, 0x94de4a4a, 0x98d44c4c, 0xb0e85858, 0x854acfcf, + 0xbb6bd0d0, 0xc52aefef, 0x4fe5aaaa, 0xed16fbfb, 0x86c54343, 0x9ad74d4d, 0x66553333, 0x11948585, 0x8acf4545, + 0xe910f9f9, 0x04060202, 0xfe817f7f, 0xa0f05050, 0x78443c3c, 0x25ba9f9f, 0x4be3a8a8, 0xa2f35151, 0x5dfea3a3, + 0x80c04040, 0x058a8f8f, 0x3fad9292, 0x21bc9d9d, 0x70483838, 0xf104f5f5, 0x63dfbcbc, 0x77c1b6b6, 0xaf75dada, + 0x42632121, 0x20301010, 0xe51affff, 0xfd0ef3f3, 0xbf6dd2d2, 0x814ccdcd, 0x18140c0c, 0x26351313, 0xc32fecec, + 0xbee15f5f, 0x35a29797, 0x88cc4444, 0x2e391717, 0x9357c4c4, 0x55f2a7a7, 0xfc827e7e, 0x7a473d3d, 0xc8ac6464, + 0xbae75d5d, 0x322b1919, 0xe6957373, 0xc0a06060, 0x19988181, 0x9ed14f4f, 0xa37fdcdc, 0x44662222, 0x547e2a2a, + 0x3bab9090, 0x0b838888, 0x8cca4646, 0xc729eeee, 0x6bd3b8b8, 0x283c1414, 0xa779dede, 0xbce25e5e, 0x161d0b0b, + 0xad76dbdb, 0xdb3be0e0, 0x64563232, 0x744e3a3a, 0x141e0a0a, 0x92db4949, 0x0c0a0606, 0x486c2424, 0xb8e45c5c, + 0x9f5dc2c2, 0xbd6ed3d3, 0x43efacac, 0xc4a66262, 0x39a89191, 0x31a49595, 0xd337e4e4, 0xf28b7979, 0xd532e7e7, + 0x8b43c8c8, 0x6e593737, 0xdab76d6d, 0x018c8d8d, 0xb164d5d5, 0x9cd24e4e, 0x49e0a9a9, 0xd8b46c6c, 0xacfa5656, + 0xf307f4f4, 0xcf25eaea, 0xcaaf6565, 0xf48e7a7a, 0x47e9aeae, 0x10180808, 0x6fd5baba, 0xf0887878, 0x4a6f2525, + 0x5c722e2e, 0x38241c1c, 0x57f1a6a6, 0x73c7b4b4, 0x9751c6c6, 0xcb23e8e8, 0xa17cdddd, 0xe89c7474, 0x3e211f1f, + 0x96dd4b4b, 0x61dcbdbd, 0x0d868b8b, 0x0f858a8a, 0xe0907070, 0x7c423e3e, 0x71c4b5b5, 0xccaa6666, 0x90d84848, + 0x06050303, 0xf701f6f6, 0x1c120e0e, 0xc2a36161, 0x6a5f3535, 0xaef95757, 0x69d0b9b9, 0x17918686, 0x9958c1c1, + 0x3a271d1d, 0x27b99e9e, 0xd938e1e1, 0xeb13f8f8, 0x2bb39898, 0x22331111, 0xd2bb6969, 0xa970d9d9, 0x07898e8e, + 0x33a79494, 0x2db69b9b, 0x3c221e1e, 0x15928787, 0xc920e9e9, 0x8749cece, 0xaaff5555, 0x50782828, 0xa57adfdf, + 0x038f8c8c, 0x59f8a1a1, 0x09808989, 0x1a170d0d, 0x65dabfbf, 0xd731e6e6, 0x84c64242, 0xd0b86868, 0x82c34141, + 0x29b09999, 0x5a772d2d, 0x1e110f0f, 0x7bcbb0b0, 0xa8fc5454, 0x6dd6bbbb, 0x2c3a1616 + }; + + private static final int[] Tinv0 = { 0x50a7f451, 0x5365417e, 0xc3a4171a, 0x965e273a, 0xcb6bab3b, 0xf1459d1f, + 0xab58faac, 0x9303e34b, 0x55fa3020, 0xf66d76ad, 0x9176cc88, 0x254c02f5, 0xfcd7e54f, 0xd7cb2ac5, 0x80443526, + 0x8fa362b5, 0x495ab1de, 0x671bba25, 0x980eea45, 0xe1c0fe5d, 0x02752fc3, 0x12f04c81, 0xa397468d, 0xc6f9d36b, + 0xe75f8f03, 0x959c9215, 0xeb7a6dbf, 0xda595295, 0x2d83bed4, 0xd3217458, 0x2969e049, 0x44c8c98e, 0x6a89c275, + 0x78798ef4, 0x6b3e5899, 0xdd71b927, 0xb64fe1be, 0x17ad88f0, 0x66ac20c9, 0xb43ace7d, 0x184adf63, 0x82311ae5, + 0x60335197, 0x457f5362, 0xe07764b1, 0x84ae6bbb, 0x1ca081fe, 0x942b08f9, 0x58684870, 0x19fd458f, 0x876cde94, + 0xb7f87b52, 0x23d373ab, 0xe2024b72, 0x578f1fe3, 0x2aab5566, 0x0728ebb2, 0x03c2b52f, 0x9a7bc586, 0xa50837d3, + 0xf2872830, 0xb2a5bf23, 0xba6a0302, 0x5c8216ed, 0x2b1ccf8a, 0x92b479a7, 0xf0f207f3, 0xa1e2694e, 0xcdf4da65, + 0xd5be0506, 0x1f6234d1, 0x8afea6c4, 0x9d532e34, 0xa055f3a2, 0x32e18a05, 0x75ebf6a4, 0x39ec830b, 0xaaef6040, + 0x069f715e, 0x51106ebd, 0xf98a213e, 0x3d06dd96, 0xae053edd, 0x46bde64d, 0xb58d5491, 0x055dc471, 0x6fd40604, + 0xff155060, 0x24fb9819, 0x97e9bdd6, 0xcc434089, 0x779ed967, 0xbd42e8b0, 0x888b8907, 0x385b19e7, 0xdbeec879, + 0x470a7ca1, 0xe90f427c, 0xc91e84f8, 0x00000000, 0x83868009, 0x48ed2b32, 0xac70111e, 0x4e725a6c, 0xfbff0efd, + 0x5638850f, 0x1ed5ae3d, 0x27392d36, 0x64d90f0a, 0x21a65c68, 0xd1545b9b, 0x3a2e3624, 0xb1670a0c, 0x0fe75793, + 0xd296eeb4, 0x9e919b1b, 0x4fc5c080, 0xa220dc61, 0x694b775a, 0x161a121c, 0x0aba93e2, 0xe52aa0c0, 0x43e0223c, + 0x1d171b12, 0x0b0d090e, 0xadc78bf2, 0xb9a8b62d, 0xc8a91e14, 0x8519f157, 0x4c0775af, 0xbbdd99ee, 0xfd607fa3, + 0x9f2601f7, 0xbcf5725c, 0xc53b6644, 0x347efb5b, 0x7629438b, 0xdcc623cb, 0x68fcedb6, 0x63f1e4b8, 0xcadc31d7, + 0x10856342, 0x40229713, 0x2011c684, 0x7d244a85, 0xf83dbbd2, 0x1132f9ae, 0x6da129c7, 0x4b2f9e1d, 0xf330b2dc, + 0xec52860d, 0xd0e3c177, 0x6c16b32b, 0x99b970a9, 0xfa489411, 0x2264e947, 0xc48cfca8, 0x1a3ff0a0, 0xd82c7d56, + 0xef903322, 0xc74e4987, 0xc1d138d9, 0xfea2ca8c, 0x360bd498, 0xcf81f5a6, 0x28de7aa5, 0x268eb7da, 0xa4bfad3f, + 0xe49d3a2c, 0x0d927850, 0x9bcc5f6a, 0x62467e54, 0xc2138df6, 0xe8b8d890, 0x5ef7392e, 0xf5afc382, 0xbe805d9f, + 0x7c93d069, 0xa92dd56f, 0xb31225cf, 0x3b99acc8, 0xa77d1810, 0x6e639ce8, 0x7bbb3bdb, 0x097826cd, 0xf418596e, + 0x01b79aec, 0xa89a4f83, 0x656e95e6, 0x7ee6ffaa, 0x08cfbc21, 0xe6e815ef, 0xd99be7ba, 0xce366f4a, 0xd4099fea, + 0xd67cb029, 0xafb2a431, 0x31233f2a, 0x3094a5c6, 0xc066a235, 0x37bc4e74, 0xa6ca82fc, 0xb0d090e0, 0x15d8a733, + 0x4a9804f1, 0xf7daec41, 0x0e50cd7f, 0x2ff69117, 0x8dd64d76, 0x4db0ef43, 0x544daacc, 0xdf0496e4, 0xe3b5d19e, + 0x1b886a4c, 0xb81f2cc1, 0x7f516546, 0x04ea5e9d, 0x5d358c01, 0x737487fa, 0x2e410bfb, 0x5a1d67b3, 0x52d2db92, + 0x335610e9, 0x1347d66d, 0x8c61d79a, 0x7a0ca137, 0x8e14f859, 0x893c13eb, 0xee27a9ce, 0x35c961b7, 0xede51ce1, + 0x3cb1477a, 0x59dfd29c, 0x3f73f255, 0x79ce1418, 0xbf37c773, 0xeacdf753, 0x5baafd5f, 0x146f3ddf, 0x86db4478, + 0x81f3afca, 0x3ec468b9, 0x2c342438, 0x5f40a3c2, 0x72c31d16, 0x0c25e2bc, 0x8b493c28, 0x41950dff, 0x7101a839, + 0xdeb30c08, 0x9ce4b4d8, 0x90c15664, 0x6184cb7b, 0x70b632d5, 0x745c6c48, 0x4257b8d0 + }; + + private static final int[] Tinv1 = { 0xa7f45150, 0x65417e53, 0xa4171ac3, 0x5e273a96, 0x6bab3bcb, 0x459d1ff1, + 0x58faacab, 0x03e34b93, 0xfa302055, 0x6d76adf6, 0x76cc8891, 0x4c02f525, 0xd7e54ffc, 0xcb2ac5d7, 0x44352680, + 0xa362b58f, 0x5ab1de49, 0x1bba2567, 0x0eea4598, 0xc0fe5de1, 0x752fc302, 0xf04c8112, 0x97468da3, 0xf9d36bc6, + 0x5f8f03e7, 0x9c921595, 0x7a6dbfeb, 0x595295da, 0x83bed42d, 0x217458d3, 0x69e04929, 0xc8c98e44, 0x89c2756a, + 0x798ef478, 0x3e58996b, 0x71b927dd, 0x4fe1beb6, 0xad88f017, 0xac20c966, 0x3ace7db4, 0x4adf6318, 0x311ae582, + 0x33519760, 0x7f536245, 0x7764b1e0, 0xae6bbb84, 0xa081fe1c, 0x2b08f994, 0x68487058, 0xfd458f19, 0x6cde9487, + 0xf87b52b7, 0xd373ab23, 0x024b72e2, 0x8f1fe357, 0xab55662a, 0x28ebb207, 0xc2b52f03, 0x7bc5869a, 0x0837d3a5, + 0x872830f2, 0xa5bf23b2, 0x6a0302ba, 0x8216ed5c, 0x1ccf8a2b, 0xb479a792, 0xf207f3f0, 0xe2694ea1, 0xf4da65cd, + 0xbe0506d5, 0x6234d11f, 0xfea6c48a, 0x532e349d, 0x55f3a2a0, 0xe18a0532, 0xebf6a475, 0xec830b39, 0xef6040aa, + 0x9f715e06, 0x106ebd51, 0x8a213ef9, 0x06dd963d, 0x053eddae, 0xbde64d46, 0x8d5491b5, 0x5dc47105, 0xd406046f, + 0x155060ff, 0xfb981924, 0xe9bdd697, 0x434089cc, 0x9ed96777, 0x42e8b0bd, 0x8b890788, 0x5b19e738, 0xeec879db, + 0x0a7ca147, 0x0f427ce9, 0x1e84f8c9, 0x00000000, 0x86800983, 0xed2b3248, 0x70111eac, 0x725a6c4e, 0xff0efdfb, + 0x38850f56, 0xd5ae3d1e, 0x392d3627, 0xd90f0a64, 0xa65c6821, 0x545b9bd1, 0x2e36243a, 0x670a0cb1, 0xe757930f, + 0x96eeb4d2, 0x919b1b9e, 0xc5c0804f, 0x20dc61a2, 0x4b775a69, 0x1a121c16, 0xba93e20a, 0x2aa0c0e5, 0xe0223c43, + 0x171b121d, 0x0d090e0b, 0xc78bf2ad, 0xa8b62db9, 0xa91e14c8, 0x19f15785, 0x0775af4c, 0xdd99eebb, 0x607fa3fd, + 0x2601f79f, 0xf5725cbc, 0x3b6644c5, 0x7efb5b34, 0x29438b76, 0xc623cbdc, 0xfcedb668, 0xf1e4b863, 0xdc31d7ca, + 0x85634210, 0x22971340, 0x11c68420, 0x244a857d, 0x3dbbd2f8, 0x32f9ae11, 0xa129c76d, 0x2f9e1d4b, 0x30b2dcf3, + 0x52860dec, 0xe3c177d0, 0x16b32b6c, 0xb970a999, 0x489411fa, 0x64e94722, 0x8cfca8c4, 0x3ff0a01a, 0x2c7d56d8, + 0x903322ef, 0x4e4987c7, 0xd138d9c1, 0xa2ca8cfe, 0x0bd49836, 0x81f5a6cf, 0xde7aa528, 0x8eb7da26, 0xbfad3fa4, + 0x9d3a2ce4, 0x9278500d, 0xcc5f6a9b, 0x467e5462, 0x138df6c2, 0xb8d890e8, 0xf7392e5e, 0xafc382f5, 0x805d9fbe, + 0x93d0697c, 0x2dd56fa9, 0x1225cfb3, 0x99acc83b, 0x7d1810a7, 0x639ce86e, 0xbb3bdb7b, 0x7826cd09, 0x18596ef4, + 0xb79aec01, 0x9a4f83a8, 0x6e95e665, 0xe6ffaa7e, 0xcfbc2108, 0xe815efe6, 0x9be7bad9, 0x366f4ace, 0x099fead4, + 0x7cb029d6, 0xb2a431af, 0x233f2a31, 0x94a5c630, 0x66a235c0, 0xbc4e7437, 0xca82fca6, 0xd090e0b0, 0xd8a73315, + 0x9804f14a, 0xdaec41f7, 0x50cd7f0e, 0xf691172f, 0xd64d768d, 0xb0ef434d, 0x4daacc54, 0x0496e4df, 0xb5d19ee3, + 0x886a4c1b, 0x1f2cc1b8, 0x5165467f, 0xea5e9d04, 0x358c015d, 0x7487fa73, 0x410bfb2e, 0x1d67b35a, 0xd2db9252, + 0x5610e933, 0x47d66d13, 0x61d79a8c, 0x0ca1377a, 0x14f8598e, 0x3c13eb89, 0x27a9ceee, 0xc961b735, 0xe51ce1ed, + 0xb1477a3c, 0xdfd29c59, 0x73f2553f, 0xce141879, 0x37c773bf, 0xcdf753ea, 0xaafd5f5b, 0x6f3ddf14, 0xdb447886, + 0xf3afca81, 0xc468b93e, 0x3424382c, 0x40a3c25f, 0xc31d1672, 0x25e2bc0c, 0x493c288b, 0x950dff41, 0x01a83971, + 0xb30c08de, 0xe4b4d89c, 0xc1566490, 0x84cb7b61, 0xb632d570, 0x5c6c4874, 0x57b8d042 + }; + + private static final int[] Tinv2 = { 0xf45150a7, 0x417e5365, 0x171ac3a4, 0x273a965e, 0xab3bcb6b, 0x9d1ff145, + 0xfaacab58, 0xe34b9303, 0x302055fa, 0x76adf66d, 0xcc889176, 0x02f5254c, 0xe54ffcd7, 0x2ac5d7cb, 0x35268044, + 0x62b58fa3, 0xb1de495a, 0xba25671b, 0xea45980e, 0xfe5de1c0, 0x2fc30275, 0x4c8112f0, 0x468da397, 0xd36bc6f9, + 0x8f03e75f, 0x9215959c, 0x6dbfeb7a, 0x5295da59, 0xbed42d83, 0x7458d321, 0xe0492969, 0xc98e44c8, 0xc2756a89, + 0x8ef47879, 0x58996b3e, 0xb927dd71, 0xe1beb64f, 0x88f017ad, 0x20c966ac, 0xce7db43a, 0xdf63184a, 0x1ae58231, + 0x51976033, 0x5362457f, 0x64b1e077, 0x6bbb84ae, 0x81fe1ca0, 0x08f9942b, 0x48705868, 0x458f19fd, 0xde94876c, + 0x7b52b7f8, 0x73ab23d3, 0x4b72e202, 0x1fe3578f, 0x55662aab, 0xebb20728, 0xb52f03c2, 0xc5869a7b, 0x37d3a508, + 0x2830f287, 0xbf23b2a5, 0x0302ba6a, 0x16ed5c82, 0xcf8a2b1c, 0x79a792b4, 0x07f3f0f2, 0x694ea1e2, 0xda65cdf4, + 0x0506d5be, 0x34d11f62, 0xa6c48afe, 0x2e349d53, 0xf3a2a055, 0x8a0532e1, 0xf6a475eb, 0x830b39ec, 0x6040aaef, + 0x715e069f, 0x6ebd5110, 0x213ef98a, 0xdd963d06, 0x3eddae05, 0xe64d46bd, 0x5491b58d, 0xc471055d, 0x06046fd4, + 0x5060ff15, 0x981924fb, 0xbdd697e9, 0x4089cc43, 0xd967779e, 0xe8b0bd42, 0x8907888b, 0x19e7385b, 0xc879dbee, + 0x7ca1470a, 0x427ce90f, 0x84f8c91e, 0x00000000, 0x80098386, 0x2b3248ed, 0x111eac70, 0x5a6c4e72, 0x0efdfbff, + 0x850f5638, 0xae3d1ed5, 0x2d362739, 0x0f0a64d9, 0x5c6821a6, 0x5b9bd154, 0x36243a2e, 0x0a0cb167, 0x57930fe7, + 0xeeb4d296, 0x9b1b9e91, 0xc0804fc5, 0xdc61a220, 0x775a694b, 0x121c161a, 0x93e20aba, 0xa0c0e52a, 0x223c43e0, + 0x1b121d17, 0x090e0b0d, 0x8bf2adc7, 0xb62db9a8, 0x1e14c8a9, 0xf1578519, 0x75af4c07, 0x99eebbdd, 0x7fa3fd60, + 0x01f79f26, 0x725cbcf5, 0x6644c53b, 0xfb5b347e, 0x438b7629, 0x23cbdcc6, 0xedb668fc, 0xe4b863f1, 0x31d7cadc, + 0x63421085, 0x97134022, 0xc6842011, 0x4a857d24, 0xbbd2f83d, 0xf9ae1132, 0x29c76da1, 0x9e1d4b2f, 0xb2dcf330, + 0x860dec52, 0xc177d0e3, 0xb32b6c16, 0x70a999b9, 0x9411fa48, 0xe9472264, 0xfca8c48c, 0xf0a01a3f, 0x7d56d82c, + 0x3322ef90, 0x4987c74e, 0x38d9c1d1, 0xca8cfea2, 0xd498360b, 0xf5a6cf81, 0x7aa528de, 0xb7da268e, 0xad3fa4bf, + 0x3a2ce49d, 0x78500d92, 0x5f6a9bcc, 0x7e546246, 0x8df6c213, 0xd890e8b8, 0x392e5ef7, 0xc382f5af, 0x5d9fbe80, + 0xd0697c93, 0xd56fa92d, 0x25cfb312, 0xacc83b99, 0x1810a77d, 0x9ce86e63, 0x3bdb7bbb, 0x26cd0978, 0x596ef418, + 0x9aec01b7, 0x4f83a89a, 0x95e6656e, 0xffaa7ee6, 0xbc2108cf, 0x15efe6e8, 0xe7bad99b, 0x6f4ace36, 0x9fead409, + 0xb029d67c, 0xa431afb2, 0x3f2a3123, 0xa5c63094, 0xa235c066, 0x4e7437bc, 0x82fca6ca, 0x90e0b0d0, 0xa73315d8, + 0x04f14a98, 0xec41f7da, 0xcd7f0e50, 0x91172ff6, 0x4d768dd6, 0xef434db0, 0xaacc544d, 0x96e4df04, 0xd19ee3b5, + 0x6a4c1b88, 0x2cc1b81f, 0x65467f51, 0x5e9d04ea, 0x8c015d35, 0x87fa7374, 0x0bfb2e41, 0x67b35a1d, 0xdb9252d2, + 0x10e93356, 0xd66d1347, 0xd79a8c61, 0xa1377a0c, 0xf8598e14, 0x13eb893c, 0xa9ceee27, 0x61b735c9, 0x1ce1ede5, + 0x477a3cb1, 0xd29c59df, 0xf2553f73, 0x141879ce, 0xc773bf37, 0xf753eacd, 0xfd5f5baa, 0x3ddf146f, 0x447886db, + 0xafca81f3, 0x68b93ec4, 0x24382c34, 0xa3c25f40, 0x1d1672c3, 0xe2bc0c25, 0x3c288b49, 0x0dff4195, 0xa8397101, + 0x0c08deb3, 0xb4d89ce4, 0x566490c1, 0xcb7b6184, 0x32d570b6, 0x6c48745c, 0xb8d04257 + }; + + private static final int[] Tinv3 = { 0x5150a7f4, 0x7e536541, 0x1ac3a417, 0x3a965e27, 0x3bcb6bab, 0x1ff1459d, + 0xacab58fa, 0x4b9303e3, 0x2055fa30, 0xadf66d76, 0x889176cc, 0xf5254c02, 0x4ffcd7e5, 0xc5d7cb2a, 0x26804435, + 0xb58fa362, 0xde495ab1, 0x25671bba, 0x45980eea, 0x5de1c0fe, 0xc302752f, 0x8112f04c, 0x8da39746, 0x6bc6f9d3, + 0x03e75f8f, 0x15959c92, 0xbfeb7a6d, 0x95da5952, 0xd42d83be, 0x58d32174, 0x492969e0, 0x8e44c8c9, 0x756a89c2, + 0xf478798e, 0x996b3e58, 0x27dd71b9, 0xbeb64fe1, 0xf017ad88, 0xc966ac20, 0x7db43ace, 0x63184adf, 0xe582311a, + 0x97603351, 0x62457f53, 0xb1e07764, 0xbb84ae6b, 0xfe1ca081, 0xf9942b08, 0x70586848, 0x8f19fd45, 0x94876cde, + 0x52b7f87b, 0xab23d373, 0x72e2024b, 0xe3578f1f, 0x662aab55, 0xb20728eb, 0x2f03c2b5, 0x869a7bc5, 0xd3a50837, + 0x30f28728, 0x23b2a5bf, 0x02ba6a03, 0xed5c8216, 0x8a2b1ccf, 0xa792b479, 0xf3f0f207, 0x4ea1e269, 0x65cdf4da, + 0x06d5be05, 0xd11f6234, 0xc48afea6, 0x349d532e, 0xa2a055f3, 0x0532e18a, 0xa475ebf6, 0x0b39ec83, 0x40aaef60, + 0x5e069f71, 0xbd51106e, 0x3ef98a21, 0x963d06dd, 0xddae053e, 0x4d46bde6, 0x91b58d54, 0x71055dc4, 0x046fd406, + 0x60ff1550, 0x1924fb98, 0xd697e9bd, 0x89cc4340, 0x67779ed9, 0xb0bd42e8, 0x07888b89, 0xe7385b19, 0x79dbeec8, + 0xa1470a7c, 0x7ce90f42, 0xf8c91e84, 0x00000000, 0x09838680, 0x3248ed2b, 0x1eac7011, 0x6c4e725a, 0xfdfbff0e, + 0x0f563885, 0x3d1ed5ae, 0x3627392d, 0x0a64d90f, 0x6821a65c, 0x9bd1545b, 0x243a2e36, 0x0cb1670a, 0x930fe757, + 0xb4d296ee, 0x1b9e919b, 0x804fc5c0, 0x61a220dc, 0x5a694b77, 0x1c161a12, 0xe20aba93, 0xc0e52aa0, 0x3c43e022, + 0x121d171b, 0x0e0b0d09, 0xf2adc78b, 0x2db9a8b6, 0x14c8a91e, 0x578519f1, 0xaf4c0775, 0xeebbdd99, 0xa3fd607f, + 0xf79f2601, 0x5cbcf572, 0x44c53b66, 0x5b347efb, 0x8b762943, 0xcbdcc623, 0xb668fced, 0xb863f1e4, 0xd7cadc31, + 0x42108563, 0x13402297, 0x842011c6, 0x857d244a, 0xd2f83dbb, 0xae1132f9, 0xc76da129, 0x1d4b2f9e, 0xdcf330b2, + 0x0dec5286, 0x77d0e3c1, 0x2b6c16b3, 0xa999b970, 0x11fa4894, 0x472264e9, 0xa8c48cfc, 0xa01a3ff0, 0x56d82c7d, + 0x22ef9033, 0x87c74e49, 0xd9c1d138, 0x8cfea2ca, 0x98360bd4, 0xa6cf81f5, 0xa528de7a, 0xda268eb7, 0x3fa4bfad, + 0x2ce49d3a, 0x500d9278, 0x6a9bcc5f, 0x5462467e, 0xf6c2138d, 0x90e8b8d8, 0x2e5ef739, 0x82f5afc3, 0x9fbe805d, + 0x697c93d0, 0x6fa92dd5, 0xcfb31225, 0xc83b99ac, 0x10a77d18, 0xe86e639c, 0xdb7bbb3b, 0xcd097826, 0x6ef41859, + 0xec01b79a, 0x83a89a4f, 0xe6656e95, 0xaa7ee6ff, 0x2108cfbc, 0xefe6e815, 0xbad99be7, 0x4ace366f, 0xead4099f, + 0x29d67cb0, 0x31afb2a4, 0x2a31233f, 0xc63094a5, 0x35c066a2, 0x7437bc4e, 0xfca6ca82, 0xe0b0d090, 0x3315d8a7, + 0xf14a9804, 0x41f7daec, 0x7f0e50cd, 0x172ff691, 0x768dd64d, 0x434db0ef, 0xcc544daa, 0xe4df0496, 0x9ee3b5d1, + 0x4c1b886a, 0xc1b81f2c, 0x467f5165, 0x9d04ea5e, 0x015d358c, 0xfa737487, 0xfb2e410b, 0xb35a1d67, 0x9252d2db, + 0xe9335610, 0x6d1347d6, 0x9a8c61d7, 0x377a0ca1, 0x598e14f8, 0xeb893c13, 0xceee27a9, 0xb735c961, 0xe1ede51c, + 0x7a3cb147, 0x9c59dfd2, 0x553f73f2, 0x1879ce14, 0x73bf37c7, 0x53eacdf7, 0x5f5baafd, 0xdf146f3d, 0x7886db44, + 0xca81f3af, 0xb93ec468, 0x382c3424, 0xc25f40a3, 0x1672c31d, 0xbc0c25e2, 0x288b493c, 0xff41950d, 0x397101a8, + 0x08deb30c, 0xd89ce4b4, 0x6490c156, 0x7b6184cb, 0xd570b632, 0x48745c6c, 0xd04257b8 + }; + + private int shift(int r, int shift) { + return (((r >>> shift) | (r << (32 - shift)))); + } + + /* multiply four bytes in GF(2^8) by 'x' {02} in parallel */ + + private static final int m1 = 0x80808080; + private static final int m2 = 0x7f7f7f7f; + private static final int m3 = 0x0000001b; + + private int FFmulX(int x) { + return (((x & m2) << 1) ^ (((x & m1) >>> 7) * m3)); + } + + /* + * The following defines provide alternative definitions of FFmulX that + * might give improved performance if a fast 32-bit multiply is not + * available. + * + * private int FFmulX(int x) { int u = x & m1; u |= (u >> 1); return ((x & + * m2) < < 1) ^ ((u >>> 3) | (u >>> 6)); } private static final int m4 = + * 0x1b1b1b1b; private int FFmulX(int x) { int u = x & m1; return ((x & m2) < < + * 1) ^ ((u - (u >>> 7)) & m4); } + * + */ + + private int inv_mcol(int x) { + int f2 = FFmulX(x); + int f4 = FFmulX(f2); + int f8 = FFmulX(f4); + int f9 = x ^ f8; + return f2 ^ f4 ^ f8 ^ shift(f2 ^ f9, 8) ^ shift(f4 ^ f9, 16) ^ shift(f9, 24); + } + + private int subWord(int x) { + return (S[x & 255] & 255 | ((S[(x >> 8) & 255] & 255) << 8) | ((S[(x >> 16) & 255] & 255) << 16) | S[(x >> 24) & 255] << 24); + } + + /** + * Calculate the necessary round keys The number of calculations depends on + * key size and block size AES specified a fixed block size of 128 bits and + * key sizes 128/192/256 bits This code is written assuming those are the + * only possible values + */ + private int[][] generateWorkingKey(byte[] key, boolean forEncryption) { + int KC = key.length / 4; // key length in words + int t; + + if (((KC != 4) && (KC != 6) && (KC != 8)) || ((KC * 4) != key.length)) { + throw new IllegalArgumentException("Key length not 128/192/256 bits."); + } + + ROUNDS = KC + 6; // This is not always true for the generalized + // Rijndael that allows larger block sizes + int[][] W = new int[ROUNDS + 1][4]; // 4 words in a block + // + // copy the key into the round key array + // + t = 0; + + for (int i = 0; i < key.length; t++) { + W[t >> 2][t & 3] = (key[i] & 0xff) | ((key[i + 1] & 0xff) << 8) | ((key[i + 2] & 0xff) << 16) + | (key[i + 3] << 24); + i += 4; + } + + // + // while not enough round key material calculated + // calculate new values + // + int k = (ROUNDS + 1) << 2; + + for (int i = KC; (i < k); i++) { + int temp = W[(i - 1) >> 2][(i - 1) & 3]; + + if ((i % KC) == 0) { + temp = subWord(shift(temp, 8)) ^ rcon[(i / KC) - 1]; + } + else if ((KC > 6) && ((i % KC) == 4)) { + temp = subWord(temp); + } + + W[i >> 2][i & 3] = W[(i - KC) >> 2][(i - KC) & 3] ^ temp; + } + + if (!forEncryption) { + for (int j = 1; j < ROUNDS; j++) { + for (int i = 0; i < 4; i++) { + W[j][i] = inv_mcol(W[j][i]); + } + } + } + + return W; + } + + private int ROUNDS; + private int[][] WorkingKey = null; + private int C0, C1, C2, C3; + private boolean doEncrypt; + + private static final int BLOCK_SIZE = 16; + + /** + * default constructor - 128 bit block size. + */ + public AES() { + } + + /** + * initialise an AES cipher. + * + * @param forEncryption + * whether or not we are for encryption. + * @param key + * the key required to set up the cipher. + * @exception IllegalArgumentException + * if the params argument is inappropriate. + */ + + public final void init(boolean forEncryption, byte[] key) { + WorkingKey = generateWorkingKey(key, forEncryption); + this.doEncrypt = forEncryption; + } + + public final String getAlgorithmName() { + return "AES"; + } + + public final int getBlockSize() { + return BLOCK_SIZE; + } + + public final int processBlock(byte[] in, int inOff, byte[] out, int outOff) { + if (WorkingKey == null) { + throw new IllegalStateException("AES engine not initialised"); + } + + if ((inOff + (32 / 2)) > in.length) { + throw new IllegalArgumentException("input buffer too short"); + } + + if ((outOff + (32 / 2)) > out.length) { + throw new IllegalArgumentException("output buffer too short"); + } + + if (doEncrypt) { + unpackBlock(in, inOff); + encryptBlock(WorkingKey); + packBlock(out, outOff); + } + else { + unpackBlock(in, inOff); + decryptBlock(WorkingKey); + packBlock(out, outOff); + } + + return BLOCK_SIZE; + } + + public final void reset() { + } + + private void unpackBlock(byte[] bytes, int off) { + int index = off; + C0 = (bytes[index++] & 0xff); + C0 |= (bytes[index++] & 0xff) << 8; + C0 |= (bytes[index++] & 0xff) << 16; + C0 |= bytes[index++] << 24; + C1 = (bytes[index++] & 0xff); + C1 |= (bytes[index++] & 0xff) << 8; + C1 |= (bytes[index++] & 0xff) << 16; + C1 |= bytes[index++] << 24; + C2 = (bytes[index++] & 0xff); + C2 |= (bytes[index++] & 0xff) << 8; + C2 |= (bytes[index++] & 0xff) << 16; + C2 |= bytes[index++] << 24; + C3 = (bytes[index++] & 0xff); + C3 |= (bytes[index++] & 0xff) << 8; + C3 |= (bytes[index++] & 0xff) << 16; + C3 |= bytes[index++] << 24; + } + + private void packBlock(byte[] bytes, int off) { + int index = off; + bytes[index++] = (byte) C0; + bytes[index++] = (byte)(C0 >> 8); + bytes[index++] = (byte)(C0 >> 16); + bytes[index++] = (byte)(C0 >> 24); + bytes[index++] = (byte) C1; + bytes[index++] = (byte)(C1 >> 8); + bytes[index++] = (byte)(C1 >> 16); + bytes[index++] = (byte)(C1 >> 24); + bytes[index++] = (byte) C2; + bytes[index++] = (byte)(C2 >> 8); + bytes[index++] = (byte)(C2 >> 16); + bytes[index++] = (byte)(C2 >> 24); + bytes[index++] = (byte) C3; + bytes[index++] = (byte)(C3 >> 8); + bytes[index++] = (byte)(C3 >> 16); + bytes[index++] = (byte)(C3 >> 24); + } + + private void encryptBlock(int[][] KW) { + int r, r0, r1, r2, r3; + C0 ^= KW[0][0]; + C1 ^= KW[0][1]; + C2 ^= KW[0][2]; + C3 ^= KW[0][3]; + + for (r = 1; r < ROUNDS - 1;) { + r0 = T0[C0 & 255] ^ T1[(C1 >> 8) & 255] ^ T2[(C2 >> 16) & 255] ^ T3[(C3 >> 24) & 255] ^ KW[r][0]; + r1 = T0[C1 & 255] ^ T1[(C2 >> 8) & 255] ^ T2[(C3 >> 16) & 255] ^ T3[(C0 >> 24) & 255] ^ KW[r][1]; + r2 = T0[C2 & 255] ^ T1[(C3 >> 8) & 255] ^ T2[(C0 >> 16) & 255] ^ T3[(C1 >> 24) & 255] ^ KW[r][2]; + r3 = T0[C3 & 255] ^ T1[(C0 >> 8) & 255] ^ T2[(C1 >> 16) & 255] ^ T3[(C2 >> 24) & 255] ^ KW[r++][3]; + C0 = T0[r0 & 255] ^ T1[(r1 >> 8) & 255] ^ T2[(r2 >> 16) & 255] ^ T3[(r3 >> 24) & 255] ^ KW[r][0]; + C1 = T0[r1 & 255] ^ T1[(r2 >> 8) & 255] ^ T2[(r3 >> 16) & 255] ^ T3[(r0 >> 24) & 255] ^ KW[r][1]; + C2 = T0[r2 & 255] ^ T1[(r3 >> 8) & 255] ^ T2[(r0 >> 16) & 255] ^ T3[(r1 >> 24) & 255] ^ KW[r][2]; + C3 = T0[r3 & 255] ^ T1[(r0 >> 8) & 255] ^ T2[(r1 >> 16) & 255] ^ T3[(r2 >> 24) & 255] ^ KW[r++][3]; + } + + r0 = T0[C0 & 255] ^ T1[(C1 >> 8) & 255] ^ T2[(C2 >> 16) & 255] ^ T3[(C3 >> 24) & 255] ^ KW[r][0]; + r1 = T0[C1 & 255] ^ T1[(C2 >> 8) & 255] ^ T2[(C3 >> 16) & 255] ^ T3[(C0 >> 24) & 255] ^ KW[r][1]; + r2 = T0[C2 & 255] ^ T1[(C3 >> 8) & 255] ^ T2[(C0 >> 16) & 255] ^ T3[(C1 >> 24) & 255] ^ KW[r][2]; + r3 = T0[C3 & 255] ^ T1[(C0 >> 8) & 255] ^ T2[(C1 >> 16) & 255] ^ T3[(C2 >> 24) & 255] ^ KW[r++][3]; + // the final round's table is a simple function of S so we don't use a + // whole other four tables for it + C0 = (S[r0 & 255] & 255) ^ ((S[(r1 >> 8) & 255] & 255) << 8) ^ ((S[(r2 >> 16) & 255] & 255) << 16) + ^ (S[(r3 >> 24) & 255] << 24) ^ KW[r][0]; + C1 = (S[r1 & 255] & 255) ^ ((S[(r2 >> 8) & 255] & 255) << 8) ^ ((S[(r3 >> 16) & 255] & 255) << 16) + ^ (S[(r0 >> 24) & 255] << 24) ^ KW[r][1]; + C2 = (S[r2 & 255] & 255) ^ ((S[(r3 >> 8) & 255] & 255) << 8) ^ ((S[(r0 >> 16) & 255] & 255) << 16) + ^ (S[(r1 >> 24) & 255] << 24) ^ KW[r][2]; + C3 = (S[r3 & 255] & 255) ^ ((S[(r0 >> 8) & 255] & 255) << 8) ^ ((S[(r1 >> 16) & 255] & 255) << 16) + ^ (S[(r2 >> 24) & 255] << 24) ^ KW[r][3]; + } + + private void decryptBlock(int[][] KW) { + int r, r0, r1, r2, r3; + C0 ^= KW[ROUNDS][0]; + C1 ^= KW[ROUNDS][1]; + C2 ^= KW[ROUNDS][2]; + C3 ^= KW[ROUNDS][3]; + + for (r = ROUNDS - 1; r > 1;) { + r0 = Tinv0[C0 & 255] ^ Tinv1[(C3 >> 8) & 255] ^ Tinv2[(C2 >> 16) & 255] ^ Tinv3[(C1 >> 24) & 255] + ^ KW[r][0]; + r1 = Tinv0[C1 & 255] ^ Tinv1[(C0 >> 8) & 255] ^ Tinv2[(C3 >> 16) & 255] ^ Tinv3[(C2 >> 24) & 255] + ^ KW[r][1]; + r2 = Tinv0[C2 & 255] ^ Tinv1[(C1 >> 8) & 255] ^ Tinv2[(C0 >> 16) & 255] ^ Tinv3[(C3 >> 24) & 255] + ^ KW[r][2]; + r3 = Tinv0[C3 & 255] ^ Tinv1[(C2 >> 8) & 255] ^ Tinv2[(C1 >> 16) & 255] ^ Tinv3[(C0 >> 24) & 255] + ^ KW[r--][3]; + C0 = Tinv0[r0 & 255] ^ Tinv1[(r3 >> 8) & 255] ^ Tinv2[(r2 >> 16) & 255] ^ Tinv3[(r1 >> 24) & 255] + ^ KW[r][0]; + C1 = Tinv0[r1 & 255] ^ Tinv1[(r0 >> 8) & 255] ^ Tinv2[(r3 >> 16) & 255] ^ Tinv3[(r2 >> 24) & 255] + ^ KW[r][1]; + C2 = Tinv0[r2 & 255] ^ Tinv1[(r1 >> 8) & 255] ^ Tinv2[(r0 >> 16) & 255] ^ Tinv3[(r3 >> 24) & 255] + ^ KW[r][2]; + C3 = Tinv0[r3 & 255] ^ Tinv1[(r2 >> 8) & 255] ^ Tinv2[(r1 >> 16) & 255] ^ Tinv3[(r0 >> 24) & 255] + ^ KW[r--][3]; + } + + r0 = Tinv0[C0 & 255] ^ Tinv1[(C3 >> 8) & 255] ^ Tinv2[(C2 >> 16) & 255] ^ Tinv3[(C1 >> 24) & 255] ^ KW[r][0]; + r1 = Tinv0[C1 & 255] ^ Tinv1[(C0 >> 8) & 255] ^ Tinv2[(C3 >> 16) & 255] ^ Tinv3[(C2 >> 24) & 255] ^ KW[r][1]; + r2 = Tinv0[C2 & 255] ^ Tinv1[(C1 >> 8) & 255] ^ Tinv2[(C0 >> 16) & 255] ^ Tinv3[(C3 >> 24) & 255] ^ KW[r][2]; + r3 = Tinv0[C3 & 255] ^ Tinv1[(C2 >> 8) & 255] ^ Tinv2[(C1 >> 16) & 255] ^ Tinv3[(C0 >> 24) & 255] ^ KW[r--][3]; + // the final round's table is a simple function of Si so we don't use a + // whole other four tables for it + C0 = (Si[r0 & 255] & 255) ^ ((Si[(r3 >> 8) & 255] & 255) << 8) ^ ((Si[(r2 >> 16) & 255] & 255) << 16) + ^ (Si[(r1 >> 24) & 255] << 24) ^ KW[0][0]; + C1 = (Si[r1 & 255] & 255) ^ ((Si[(r0 >> 8) & 255] & 255) << 8) ^ ((Si[(r3 >> 16) & 255] & 255) << 16) + ^ (Si[(r2 >> 24) & 255] << 24) ^ KW[0][1]; + C2 = (Si[r2 & 255] & 255) ^ ((Si[(r1 >> 8) & 255] & 255) << 8) ^ ((Si[(r0 >> 16) & 255] & 255) << 16) + ^ (Si[(r3 >> 24) & 255] << 24) ^ KW[0][2]; + C3 = (Si[r3 & 255] & 255) ^ ((Si[(r2 >> 8) & 255] & 255) << 8) ^ ((Si[(r1 >> 16) & 255] & 255) << 16) + ^ (Si[(r0 >> 24) & 255] << 24) ^ KW[0][3]; + } + + public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) { + processBlock(src, srcoff, dst, dstoff); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/cipher/BlockCipher.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/cipher/BlockCipher.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.cipher; + +/** + * BlockCipher. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public interface BlockCipher { + public void init(boolean forEncryption, byte[] key); + + public int getBlockSize(); + + public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff); +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/cipher/BlockCipherFactory.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/cipher/BlockCipherFactory.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.cipher; + +import java.util.ArrayList; +import java.util.List; +import java.util.Vector; + +/** + * BlockCipherFactory. + * + * @author Christian Plattner + * @version $Id: BlockCipherFactory.java 86 2014-04-07 14:15:18Z dkocher@sudo.ch $ + */ +public class BlockCipherFactory { + private static final class CipherEntry { + String type; + int blocksize; + int keysize; + String cipherClass; + + public CipherEntry(String type, int blockSize, int keySize, String cipherClass) { + this.type = type; + this.blocksize = blockSize; + this.keysize = keySize; + this.cipherClass = cipherClass; + } + } + + private static final List ciphers = new ArrayList(); + + static { + /* Higher Priority First */ + ciphers.add(new CipherEntry("aes128-ctr", 16, 16, "ch.ethz.ssh2.crypto.cipher.AES")); + ciphers.add(new CipherEntry("aes192-ctr", 16, 24, "ch.ethz.ssh2.crypto.cipher.AES")); + ciphers.add(new CipherEntry("aes256-ctr", 16, 32, "ch.ethz.ssh2.crypto.cipher.AES")); + ciphers.add(new CipherEntry("blowfish-ctr", 8, 16, "ch.ethz.ssh2.crypto.cipher.BlowFish")); + ciphers.add(new CipherEntry("aes128-cbc", 16, 16, "ch.ethz.ssh2.crypto.cipher.AES")); + ciphers.add(new CipherEntry("aes192-cbc", 16, 24, "ch.ethz.ssh2.crypto.cipher.AES")); + ciphers.add(new CipherEntry("aes256-cbc", 16, 32, "ch.ethz.ssh2.crypto.cipher.AES")); + ciphers.add(new CipherEntry("blowfish-cbc", 8, 16, "ch.ethz.ssh2.crypto.cipher.BlowFish")); + ciphers.add(new CipherEntry("3des-ctr", 8, 24, "ch.ethz.ssh2.crypto.cipher.DESede")); + ciphers.add(new CipherEntry("3des-cbc", 8, 24, "ch.ethz.ssh2.crypto.cipher.DESede")); + } + + public static String[] getDefaultCipherList() { + List list = new ArrayList(ciphers.size()); + + for (CipherEntry ce : ciphers) { + list.add(ce.type); + } + + return list.toArray(new String[ciphers.size()]); + } + + public static void checkCipherList(String[] cipherCandidates) { + for (String cipherCandidate : cipherCandidates) { + getEntry(cipherCandidate); + } + } + + // @SuppressWarnings("rawtypes") + public static BlockCipher createCipher(String type, boolean encrypt, byte[] key, byte[] iv) { + try { + CipherEntry ce = getEntry(type); + Class cc = Class.forName(ce.cipherClass); + BlockCipher bc = (BlockCipher) cc.newInstance(); + + if (type.endsWith("-cbc")) { + bc.init(encrypt, key); + return new CBCMode(bc, iv, encrypt); + } + else if (type.endsWith("-ctr")) { + bc.init(true, key); + return new CTRMode(bc, iv, encrypt); + } + + throw new IllegalArgumentException("Cannot instantiate " + type); + } + catch (ClassNotFoundException e) { + throw new IllegalArgumentException("Cannot instantiate " + type, e); + } + catch (InstantiationException e) { + throw new IllegalArgumentException("Cannot instantiate " + type, e); + } + catch (IllegalAccessException e) { + throw new IllegalArgumentException("Cannot instantiate " + type, e); + } + } + + private static CipherEntry getEntry(String type) { + for (CipherEntry ce : ciphers) { + if (ce.type.equals(type)) { + return ce; + } + } + + throw new IllegalArgumentException("Unkown algorithm " + type); + } + + public static int getBlockSize(String type) { + CipherEntry ce = getEntry(type); + return ce.blocksize; + } + + public static int getKeySize(String type) { + CipherEntry ce = getEntry(type); + return ce.keysize; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/cipher/BlowFish.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/cipher/BlowFish.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,376 @@ +package ch.ethz.ssh2.crypto.cipher; + +/* + This file was shamelessly taken from the Bouncy Castle Crypto package. + Their licence file states the following: + + Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle + (http://www.bouncycastle.org) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +/** + * A class that provides Blowfish key encryption operations, such as encoding + * data and generating keys. All the algorithms herein are from Applied + * Cryptography and implement a simplified cryptography interface. + * + * @author See comments in the source file + * @version 2.50, 03/15/10 + */ +public class BlowFish implements BlockCipher { + + private final static int[] KP = { 0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, 0xA4093822, 0x299F31D0, + 0x082EFA98, 0xEC4E6C89, 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C, 0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, + 0xB5470917, 0x9216D5D9, 0x8979FB1B + }, + + KS0 = { 0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7, 0xB8E1AFED, 0x6A267E96, 0xBA7C9045, 0xF12C7F99, 0x24A19947, + 0xB3916CF7, 0x0801F2E2, 0x858EFC16, 0x636920D8, 0x71574E69, 0xA458FEA3, 0xF4933D7E, 0x0D95748F, 0x728EB658, + 0x718BCD58, 0x82154AEE, 0x7B54A41D, 0xC25A59B5, 0x9C30D539, 0x2AF26013, 0xC5D1B023, 0x286085F0, 0xCA417918, + 0xB8DB38EF, 0x8E79DCB0, 0x603A180E, 0x6C9E0E8B, 0xB01E8A3E, 0xD71577C1, 0xBD314B27, 0x78AF2FDA, 0x55605C60, + 0xE65525F3, 0xAA55AB94, 0x57489862, 0x63E81440, 0x55CA396A, 0x2AAB10B6, 0xB4CC5C34, 0x1141E8CE, 0xA15486AF, + 0x7C72E993, 0xB3EE1411, 0x636FBC2A, 0x2BA9C55D, 0x741831F6, 0xCE5C3E16, 0x9B87931E, 0xAFD6BA33, 0x6C24CF5C, + 0x7A325381, 0x28958677, 0x3B8F4898, 0x6B4BB9AF, 0xC4BFE81B, 0x66282193, 0x61D809CC, 0xFB21A991, 0x487CAC60, + 0x5DEC8032, 0xEF845D5D, 0xE98575B1, 0xDC262302, 0xEB651B88, 0x23893E81, 0xD396ACC5, 0x0F6D6FF3, 0x83F44239, + 0x2E0B4482, 0xA4842004, 0x69C8F04A, 0x9E1F9B5E, 0x21C66842, 0xF6E96C9A, 0x670C9C61, 0xABD388F0, 0x6A51A0D2, + 0xD8542F68, 0x960FA728, 0xAB5133A3, 0x6EEF0B6C, 0x137A3BE4, 0xBA3BF050, 0x7EFB2A98, 0xA1F1651D, 0x39AF0176, + 0x66CA593E, 0x82430E88, 0x8CEE8619, 0x456F9FB4, 0x7D84A5C3, 0x3B8B5EBE, 0xE06F75D8, 0x85C12073, 0x401A449F, + 0x56C16AA6, 0x4ED3AA62, 0x363F7706, 0x1BFEDF72, 0x429B023D, 0x37D0D724, 0xD00A1248, 0xDB0FEAD3, 0x49F1C09B, + 0x075372C9, 0x80991B7B, 0x25D479D8, 0xF6E8DEF7, 0xE3FE501A, 0xB6794C3B, 0x976CE0BD, 0x04C006BA, 0xC1A94FB6, + 0x409F60C4, 0x5E5C9EC2, 0x196A2463, 0x68FB6FAF, 0x3E6C53B5, 0x1339B2EB, 0x3B52EC6F, 0x6DFC511F, 0x9B30952C, + 0xCC814544, 0xAF5EBD09, 0xBEE3D004, 0xDE334AFD, 0x660F2807, 0x192E4BB3, 0xC0CBA857, 0x45C8740F, 0xD20B5F39, + 0xB9D3FBDB, 0x5579C0BD, 0x1A60320A, 0xD6A100C6, 0x402C7279, 0x679F25FE, 0xFB1FA3CC, 0x8EA5E9F8, 0xDB3222F8, + 0x3C7516DF, 0xFD616B15, 0x2F501EC8, 0xAD0552AB, 0x323DB5FA, 0xFD238760, 0x53317B48, 0x3E00DF82, 0x9E5C57BB, + 0xCA6F8CA0, 0x1A87562E, 0xDF1769DB, 0xD542A8F6, 0x287EFFC3, 0xAC6732C6, 0x8C4F5573, 0x695B27B0, 0xBBCA58C8, + 0xE1FFA35D, 0xB8F011A0, 0x10FA3D98, 0xFD2183B8, 0x4AFCB56C, 0x2DD1D35B, 0x9A53E479, 0xB6F84565, 0xD28E49BC, + 0x4BFB9790, 0xE1DDF2DA, 0xA4CB7E33, 0x62FB1341, 0xCEE4C6E8, 0xEF20CADA, 0x36774C01, 0xD07E9EFE, 0x2BF11FB4, + 0x95DBDA4D, 0xAE909198, 0xEAAD8E71, 0x6B93D5A0, 0xD08ED1D0, 0xAFC725E0, 0x8E3C5B2F, 0x8E7594B7, 0x8FF6E2FB, + 0xF2122B64, 0x8888B812, 0x900DF01C, 0x4FAD5EA0, 0x688FC31C, 0xD1CFF191, 0xB3A8C1AD, 0x2F2F2218, 0xBE0E1777, + 0xEA752DFE, 0x8B021FA1, 0xE5A0CC0F, 0xB56F74E8, 0x18ACF3D6, 0xCE89E299, 0xB4A84FE0, 0xFD13E0B7, 0x7CC43B81, + 0xD2ADA8D9, 0x165FA266, 0x80957705, 0x93CC7314, 0x211A1477, 0xE6AD2065, 0x77B5FA86, 0xC75442F5, 0xFB9D35CF, + 0xEBCDAF0C, 0x7B3E89A0, 0xD6411BD3, 0xAE1E7E49, 0x00250E2D, 0x2071B35E, 0x226800BB, 0x57B8E0AF, 0x2464369B, + 0xF009B91E, 0x5563911D, 0x59DFA6AA, 0x78C14389, 0xD95A537F, 0x207D5BA2, 0x02E5B9C5, 0x83260376, 0x6295CFA9, + 0x11C81968, 0x4E734A41, 0xB3472DCA, 0x7B14A94A, 0x1B510052, 0x9A532915, 0xD60F573F, 0xBC9BC6E4, 0x2B60A476, + 0x81E67400, 0x08BA6FB5, 0x571BE91F, 0xF296EC6B, 0x2A0DD915, 0xB6636521, 0xE7B9F9B6, 0xFF34052E, 0xC5855664, + 0x53B02D5D, 0xA99F8FA1, 0x08BA4799, 0x6E85076A + }, + + KS1 = { 0x4B7A70E9, 0xB5B32944, 0xDB75092E, 0xC4192623, 0xAD6EA6B0, 0x49A7DF7D, 0x9CEE60B8, 0x8FEDB266, 0xECAA8C71, + 0x699A17FF, 0x5664526C, 0xC2B19EE1, 0x193602A5, 0x75094C29, 0xA0591340, 0xE4183A3E, 0x3F54989A, 0x5B429D65, + 0x6B8FE4D6, 0x99F73FD6, 0xA1D29C07, 0xEFE830F5, 0x4D2D38E6, 0xF0255DC1, 0x4CDD2086, 0x8470EB26, 0x6382E9C6, + 0x021ECC5E, 0x09686B3F, 0x3EBAEFC9, 0x3C971814, 0x6B6A70A1, 0x687F3584, 0x52A0E286, 0xB79C5305, 0xAA500737, + 0x3E07841C, 0x7FDEAE5C, 0x8E7D44EC, 0x5716F2B8, 0xB03ADA37, 0xF0500C0D, 0xF01C1F04, 0x0200B3FF, 0xAE0CF51A, + 0x3CB574B2, 0x25837A58, 0xDC0921BD, 0xD19113F9, 0x7CA92FF6, 0x94324773, 0x22F54701, 0x3AE5E581, 0x37C2DADC, + 0xC8B57634, 0x9AF3DDA7, 0xA9446146, 0x0FD0030E, 0xECC8C73E, 0xA4751E41, 0xE238CD99, 0x3BEA0E2F, 0x3280BBA1, + 0x183EB331, 0x4E548B38, 0x4F6DB908, 0x6F420D03, 0xF60A04BF, 0x2CB81290, 0x24977C79, 0x5679B072, 0xBCAF89AF, + 0xDE9A771F, 0xD9930810, 0xB38BAE12, 0xDCCF3F2E, 0x5512721F, 0x2E6B7124, 0x501ADDE6, 0x9F84CD87, 0x7A584718, + 0x7408DA17, 0xBC9F9ABC, 0xE94B7D8C, 0xEC7AEC3A, 0xDB851DFA, 0x63094366, 0xC464C3D2, 0xEF1C1847, 0x3215D908, + 0xDD433B37, 0x24C2BA16, 0x12A14D43, 0x2A65C451, 0x50940002, 0x133AE4DD, 0x71DFF89E, 0x10314E55, 0x81AC77D6, + 0x5F11199B, 0x043556F1, 0xD7A3C76B, 0x3C11183B, 0x5924A509, 0xF28FE6ED, 0x97F1FBFA, 0x9EBABF2C, 0x1E153C6E, + 0x86E34570, 0xEAE96FB1, 0x860E5E0A, 0x5A3E2AB3, 0x771FE71C, 0x4E3D06FA, 0x2965DCB9, 0x99E71D0F, 0x803E89D6, + 0x5266C825, 0x2E4CC978, 0x9C10B36A, 0xC6150EBA, 0x94E2EA78, 0xA5FC3C53, 0x1E0A2DF4, 0xF2F74EA7, 0x361D2B3D, + 0x1939260F, 0x19C27960, 0x5223A708, 0xF71312B6, 0xEBADFE6E, 0xEAC31F66, 0xE3BC4595, 0xA67BC883, 0xB17F37D1, + 0x018CFF28, 0xC332DDEF, 0xBE6C5AA5, 0x65582185, 0x68AB9802, 0xEECEA50F, 0xDB2F953B, 0x2AEF7DAD, 0x5B6E2F84, + 0x1521B628, 0x29076170, 0xECDD4775, 0x619F1510, 0x13CCA830, 0xEB61BD96, 0x0334FE1E, 0xAA0363CF, 0xB5735C90, + 0x4C70A239, 0xD59E9E0B, 0xCBAADE14, 0xEECC86BC, 0x60622CA7, 0x9CAB5CAB, 0xB2F3846E, 0x648B1EAF, 0x19BDF0CA, + 0xA02369B9, 0x655ABB50, 0x40685A32, 0x3C2AB4B3, 0x319EE9D5, 0xC021B8F7, 0x9B540B19, 0x875FA099, 0x95F7997E, + 0x623D7DA8, 0xF837889A, 0x97E32D77, 0x11ED935F, 0x16681281, 0x0E358829, 0xC7E61FD6, 0x96DEDFA1, 0x7858BA99, + 0x57F584A5, 0x1B227263, 0x9B83C3FF, 0x1AC24696, 0xCDB30AEB, 0x532E3054, 0x8FD948E4, 0x6DBC3128, 0x58EBF2EF, + 0x34C6FFEA, 0xFE28ED61, 0xEE7C3C73, 0x5D4A14D9, 0xE864B7E3, 0x42105D14, 0x203E13E0, 0x45EEE2B6, 0xA3AAABEA, + 0xDB6C4F15, 0xFACB4FD0, 0xC742F442, 0xEF6ABBB5, 0x654F3B1D, 0x41CD2105, 0xD81E799E, 0x86854DC7, 0xE44B476A, + 0x3D816250, 0xCF62A1F2, 0x5B8D2646, 0xFC8883A0, 0xC1C7B6A3, 0x7F1524C3, 0x69CB7492, 0x47848A0B, 0x5692B285, + 0x095BBF00, 0xAD19489D, 0x1462B174, 0x23820E00, 0x58428D2A, 0x0C55F5EA, 0x1DADF43E, 0x233F7061, 0x3372F092, + 0x8D937E41, 0xD65FECF1, 0x6C223BDB, 0x7CDE3759, 0xCBEE7460, 0x4085F2A7, 0xCE77326E, 0xA6078084, 0x19F8509E, + 0xE8EFD855, 0x61D99735, 0xA969A7AA, 0xC50C06C2, 0x5A04ABFC, 0x800BCADC, 0x9E447A2E, 0xC3453484, 0xFDD56705, + 0x0E1E9EC9, 0xDB73DBD3, 0x105588CD, 0x675FDA79, 0xE3674340, 0xC5C43465, 0x713E38D8, 0x3D28F89E, 0xF16DFF20, + 0x153E21E7, 0x8FB03D4A, 0xE6E39F2B, 0xDB83ADF7 + }, + + KS2 = { 0xE93D5A68, 0x948140F7, 0xF64C261C, 0x94692934, 0x411520F7, 0x7602D4F7, 0xBCF46B2E, 0xD4A20068, 0xD4082471, + 0x3320F46A, 0x43B7D4B7, 0x500061AF, 0x1E39F62E, 0x97244546, 0x14214F74, 0xBF8B8840, 0x4D95FC1D, 0x96B591AF, + 0x70F4DDD3, 0x66A02F45, 0xBFBC09EC, 0x03BD9785, 0x7FAC6DD0, 0x31CB8504, 0x96EB27B3, 0x55FD3941, 0xDA2547E6, + 0xABCA0A9A, 0x28507825, 0x530429F4, 0x0A2C86DA, 0xE9B66DFB, 0x68DC1462, 0xD7486900, 0x680EC0A4, 0x27A18DEE, + 0x4F3FFEA2, 0xE887AD8C, 0xB58CE006, 0x7AF4D6B6, 0xAACE1E7C, 0xD3375FEC, 0xCE78A399, 0x406B2A42, 0x20FE9E35, + 0xD9F385B9, 0xEE39D7AB, 0x3B124E8B, 0x1DC9FAF7, 0x4B6D1856, 0x26A36631, 0xEAE397B2, 0x3A6EFA74, 0xDD5B4332, + 0x6841E7F7, 0xCA7820FB, 0xFB0AF54E, 0xD8FEB397, 0x454056AC, 0xBA489527, 0x55533A3A, 0x20838D87, 0xFE6BA9B7, + 0xD096954B, 0x55A867BC, 0xA1159A58, 0xCCA92963, 0x99E1DB33, 0xA62A4A56, 0x3F3125F9, 0x5EF47E1C, 0x9029317C, + 0xFDF8E802, 0x04272F70, 0x80BB155C, 0x05282CE3, 0x95C11548, 0xE4C66D22, 0x48C1133F, 0xC70F86DC, 0x07F9C9EE, + 0x41041F0F, 0x404779A4, 0x5D886E17, 0x325F51EB, 0xD59BC0D1, 0xF2BCC18F, 0x41113564, 0x257B7834, 0x602A9C60, + 0xDFF8E8A3, 0x1F636C1B, 0x0E12B4C2, 0x02E1329E, 0xAF664FD1, 0xCAD18115, 0x6B2395E0, 0x333E92E1, 0x3B240B62, + 0xEEBEB922, 0x85B2A20E, 0xE6BA0D99, 0xDE720C8C, 0x2DA2F728, 0xD0127845, 0x95B794FD, 0x647D0862, 0xE7CCF5F0, + 0x5449A36F, 0x877D48FA, 0xC39DFD27, 0xF33E8D1E, 0x0A476341, 0x992EFF74, 0x3A6F6EAB, 0xF4F8FD37, 0xA812DC60, + 0xA1EBDDF8, 0x991BE14C, 0xDB6E6B0D, 0xC67B5510, 0x6D672C37, 0x2765D43B, 0xDCD0E804, 0xF1290DC7, 0xCC00FFA3, + 0xB5390F92, 0x690FED0B, 0x667B9FFB, 0xCEDB7D9C, 0xA091CF0B, 0xD9155EA3, 0xBB132F88, 0x515BAD24, 0x7B9479BF, + 0x763BD6EB, 0x37392EB3, 0xCC115979, 0x8026E297, 0xF42E312D, 0x6842ADA7, 0xC66A2B3B, 0x12754CCC, 0x782EF11C, + 0x6A124237, 0xB79251E7, 0x06A1BBE6, 0x4BFB6350, 0x1A6B1018, 0x11CAEDFA, 0x3D25BDD8, 0xE2E1C3C9, 0x44421659, + 0x0A121386, 0xD90CEC6E, 0xD5ABEA2A, 0x64AF674E, 0xDA86A85F, 0xBEBFE988, 0x64E4C3FE, 0x9DBC8057, 0xF0F7C086, + 0x60787BF8, 0x6003604D, 0xD1FD8346, 0xF6381FB0, 0x7745AE04, 0xD736FCCC, 0x83426B33, 0xF01EAB71, 0xB0804187, + 0x3C005E5F, 0x77A057BE, 0xBDE8AE24, 0x55464299, 0xBF582E61, 0x4E58F48F, 0xF2DDFDA2, 0xF474EF38, 0x8789BDC2, + 0x5366F9C3, 0xC8B38E74, 0xB475F255, 0x46FCD9B9, 0x7AEB2661, 0x8B1DDF84, 0x846A0E79, 0x915F95E2, 0x466E598E, + 0x20B45770, 0x8CD55591, 0xC902DE4C, 0xB90BACE1, 0xBB8205D0, 0x11A86248, 0x7574A99E, 0xB77F19B6, 0xE0A9DC09, + 0x662D09A1, 0xC4324633, 0xE85A1F02, 0x09F0BE8C, 0x4A99A025, 0x1D6EFE10, 0x1AB93D1D, 0x0BA5A4DF, 0xA186F20F, + 0x2868F169, 0xDCB7DA83, 0x573906FE, 0xA1E2CE9B, 0x4FCD7F52, 0x50115E01, 0xA70683FA, 0xA002B5C4, 0x0DE6D027, + 0x9AF88C27, 0x773F8641, 0xC3604C06, 0x61A806B5, 0xF0177A28, 0xC0F586E0, 0x006058AA, 0x30DC7D62, 0x11E69ED7, + 0x2338EA63, 0x53C2DD94, 0xC2C21634, 0xBBCBEE56, 0x90BCB6DE, 0xEBFC7DA1, 0xCE591D76, 0x6F05E409, 0x4B7C0188, + 0x39720A3D, 0x7C927C24, 0x86E3725F, 0x724D9DB9, 0x1AC15BB4, 0xD39EB8FC, 0xED545578, 0x08FCA5B5, 0xD83D7CD3, + 0x4DAD0FC4, 0x1E50EF5E, 0xB161E6F8, 0xA28514D9, 0x6C51133C, 0x6FD5C7E7, 0x56E14EC4, 0x362ABFCE, 0xDDC6C837, + 0xD79A3234, 0x92638212, 0x670EFA8E, 0x406000E0 + }, + + KS3 = { 0x3A39CE37, 0xD3FAF5CF, 0xABC27737, 0x5AC52D1B, 0x5CB0679E, 0x4FA33742, 0xD3822740, 0x99BC9BBE, 0xD5118E9D, + 0xBF0F7315, 0xD62D1C7E, 0xC700C47B, 0xB78C1B6B, 0x21A19045, 0xB26EB1BE, 0x6A366EB4, 0x5748AB2F, 0xBC946E79, + 0xC6A376D2, 0x6549C2C8, 0x530FF8EE, 0x468DDE7D, 0xD5730A1D, 0x4CD04DC6, 0x2939BBDB, 0xA9BA4650, 0xAC9526E8, + 0xBE5EE304, 0xA1FAD5F0, 0x6A2D519A, 0x63EF8CE2, 0x9A86EE22, 0xC089C2B8, 0x43242EF6, 0xA51E03AA, 0x9CF2D0A4, + 0x83C061BA, 0x9BE96A4D, 0x8FE51550, 0xBA645BD6, 0x2826A2F9, 0xA73A3AE1, 0x4BA99586, 0xEF5562E9, 0xC72FEFD3, + 0xF752F7DA, 0x3F046F69, 0x77FA0A59, 0x80E4A915, 0x87B08601, 0x9B09E6AD, 0x3B3EE593, 0xE990FD5A, 0x9E34D797, + 0x2CF0B7D9, 0x022B8B51, 0x96D5AC3A, 0x017DA67D, 0xD1CF3ED6, 0x7C7D2D28, 0x1F9F25CF, 0xADF2B89B, 0x5AD6B472, + 0x5A88F54C, 0xE029AC71, 0xE019A5E6, 0x47B0ACFD, 0xED93FA9B, 0xE8D3C48D, 0x283B57CC, 0xF8D56629, 0x79132E28, + 0x785F0191, 0xED756055, 0xF7960E44, 0xE3D35E8C, 0x15056DD4, 0x88F46DBA, 0x03A16125, 0x0564F0BD, 0xC3EB9E15, + 0x3C9057A2, 0x97271AEC, 0xA93A072A, 0x1B3F6D9B, 0x1E6321F5, 0xF59C66FB, 0x26DCF319, 0x7533D928, 0xB155FDF5, + 0x03563482, 0x8ABA3CBB, 0x28517711, 0xC20AD9F8, 0xABCC5167, 0xCCAD925F, 0x4DE81751, 0x3830DC8E, 0x379D5862, + 0x9320F991, 0xEA7A90C2, 0xFB3E7BCE, 0x5121CE64, 0x774FBE32, 0xA8B6E37E, 0xC3293D46, 0x48DE5369, 0x6413E680, + 0xA2AE0810, 0xDD6DB224, 0x69852DFD, 0x09072166, 0xB39A460A, 0x6445C0DD, 0x586CDECF, 0x1C20C8AE, 0x5BBEF7DD, + 0x1B588D40, 0xCCD2017F, 0x6BB4E3BB, 0xDDA26A7E, 0x3A59FF45, 0x3E350A44, 0xBCB4CDD5, 0x72EACEA8, 0xFA6484BB, + 0x8D6612AE, 0xBF3C6F47, 0xD29BE463, 0x542F5D9E, 0xAEC2771B, 0xF64E6370, 0x740E0D8D, 0xE75B1357, 0xF8721671, + 0xAF537D5D, 0x4040CB08, 0x4EB4E2CC, 0x34D2466A, 0x0115AF84, 0xE1B00428, 0x95983A1D, 0x06B89FB4, 0xCE6EA048, + 0x6F3F3B82, 0x3520AB82, 0x011A1D4B, 0x277227F8, 0x611560B1, 0xE7933FDC, 0xBB3A792B, 0x344525BD, 0xA08839E1, + 0x51CE794B, 0x2F32C9B7, 0xA01FBAC9, 0xE01CC87E, 0xBCC7D1F6, 0xCF0111C3, 0xA1E8AAC7, 0x1A908749, 0xD44FBD9A, + 0xD0DADECB, 0xD50ADA38, 0x0339C32A, 0xC6913667, 0x8DF9317C, 0xE0B12B4F, 0xF79E59B7, 0x43F5BB3A, 0xF2D519FF, + 0x27D9459C, 0xBF97222C, 0x15E6FC2A, 0x0F91FC71, 0x9B941525, 0xFAE59361, 0xCEB69CEB, 0xC2A86459, 0x12BAA8D1, + 0xB6C1075E, 0xE3056A0C, 0x10D25065, 0xCB03A442, 0xE0EC6E0E, 0x1698DB3B, 0x4C98A0BE, 0x3278E964, 0x9F1F9532, + 0xE0D392DF, 0xD3A0342B, 0x8971F21E, 0x1B0A7441, 0x4BA3348C, 0xC5BE7120, 0xC37632D8, 0xDF359F8D, 0x9B992F2E, + 0xE60B6F47, 0x0FE3F11D, 0xE54CDA54, 0x1EDAD891, 0xCE6279CF, 0xCD3E7E6F, 0x1618B166, 0xFD2C1D05, 0x848FD2C5, + 0xF6FB2299, 0xF523F357, 0xA6327623, 0x93A83531, 0x56CCCD02, 0xACF08162, 0x5A75EBB5, 0x6E163697, 0x88D273CC, + 0xDE966292, 0x81B949D0, 0x4C50901B, 0x71C65614, 0xE6C6C7BD, 0x327A140A, 0x45E1D006, 0xC3F27B9A, 0xC9AA53FD, + 0x62A80F00, 0xBB25BFE2, 0x35BDD2F6, 0x71126905, 0xB2040222, 0xB6CBCF7C, 0xCD769C2B, 0x53113EC0, 0x1640E3D3, + 0x38ABBD60, 0x2547ADF0, 0xBA38209C, 0xF746CE76, 0x77AFA1C5, 0x20756060, 0x85CBFE4E, 0x8AE88DD8, 0x7AAAF9B0, + 0x4CF9AA7E, 0x1948C25C, 0x02FB8A8C, 0x01C36AE4, 0xD6EBE1F9, 0x90D4F869, 0xA65CDEA0, 0x3F09252D, 0xC208E69F, + 0xB74E6132, 0xCE77E25B, 0x578FDFE3, 0x3AC372E6 + }; + + // ==================================== + // Useful constants + // ==================================== + + private static final int ROUNDS = 16; + private static final int BLOCK_SIZE = 8; // bytes = 64 bits + private static final int SBOX_SK = 256; + private static final int P_SZ = ROUNDS + 2; + + private final int[] S0, S1, S2, S3; // the s-boxes + private final int[] P; // the p-array + + private boolean doEncrypt = false; + + private byte[] workingKey = null; + + public BlowFish() { + S0 = new int[SBOX_SK]; + S1 = new int[SBOX_SK]; + S2 = new int[SBOX_SK]; + S3 = new int[SBOX_SK]; + P = new int[P_SZ]; + } + + /** + * initialise a Blowfish cipher. + * + * @param encrypting + * whether or not we are for encryption. + * @param key + * the key required to set up the cipher. + * @exception IllegalArgumentException + * if the params argument is inappropriate. + */ + public void init(boolean encrypting, byte[] key) { + this.doEncrypt = encrypting; + this.workingKey = key; + setKey(this.workingKey); + } + + public String getAlgorithmName() { + return "Blowfish"; + } + + public final void transformBlock(byte[] in, int inOff, byte[] out, int outOff) { + if (workingKey == null) { + throw new IllegalStateException("Blowfish not initialised"); + } + + if (doEncrypt) { + encryptBlock(in, inOff, out, outOff); + } + else { + decryptBlock(in, inOff, out, outOff); + } + } + + public void reset() { + } + + public int getBlockSize() { + return BLOCK_SIZE; + } + + // ================================== + // Private Implementation + // ================================== + + private int F(int x) { + return (((S0[(x >>> 24)] + S1[(x >>> 16) & 0xff]) ^ S2[(x >>> 8) & 0xff]) + S3[x & 0xff]); + } + + /** + * apply the encryption cycle to each value pair in the table. + */ + private void processTable(int xl, int xr, int[] table) { + int size = table.length; + + for (int s = 0; s < size; s += 2) { + xl ^= P[0]; + + for (int i = 1; i < ROUNDS; i += 2) { + xr ^= F(xl) ^ P[i]; + xl ^= F(xr) ^ P[i + 1]; + } + + xr ^= P[ROUNDS + 1]; + table[s] = xr; + table[s + 1] = xl; + xr = xl; // end of cycle swap + xl = table[s]; + } + } + + private void setKey(byte[] key) { + /* + * - comments are from _Applied Crypto_, Schneier, p338 please be + * careful comparing the two, AC numbers the arrays from 1, the enclosed + * code from 0. + * + * (1) Initialise the S-boxes and the P-array, with a fixed string This + * string contains the hexadecimal digits of pi (3.141...) + */ + System.arraycopy(KS0, 0, S0, 0, SBOX_SK); + System.arraycopy(KS1, 0, S1, 0, SBOX_SK); + System.arraycopy(KS2, 0, S2, 0, SBOX_SK); + System.arraycopy(KS3, 0, S3, 0, SBOX_SK); + System.arraycopy(KP, 0, P, 0, P_SZ); + /* + * (2) Now, XOR P[0] with the first 32 bits of the key, XOR P[1] with + * the second 32-bits of the key, and so on for all bits of the key (up + * to P[17]). Repeatedly cycle through the key bits until the entire + * P-array has been XOR-ed with the key bits + */ + int keyLength = key.length; + int keyIndex = 0; + + for (int i = 0; i < P_SZ; i++) { + // get the 32 bits of the key, in 4 * 8 bit chunks + int data = 0x0000000; + + for (int j = 0; j < 4; j++) { + // create a 32 bit block + data = (data << 8) | (key[keyIndex++] & 0xff); + + // wrap when we get to the end of the key + if (keyIndex >= keyLength) { + keyIndex = 0; + } + } + + // XOR the newly created 32 bit chunk onto the P-array + P[i] ^= data; + } + + /* + * (3) Encrypt the all-zero string with the Blowfish algorithm, using + * the subkeys described in (1) and (2) + * + * (4) Replace P1 and P2 with the output of step (3) + * + * (5) Encrypt the output of step(3) using the Blowfish algorithm, with + * the modified subkeys. + * + * (6) Replace P3 and P4 with the output of step (5) + * + * (7) Continue the process, replacing all elements of the P-array and + * then all four S-boxes in order, with the output of the continuously + * changing Blowfish algorithm + */ + processTable(0, 0, P); + processTable(P[P_SZ - 2], P[P_SZ - 1], S0); + processTable(S0[SBOX_SK - 2], S0[SBOX_SK - 1], S1); + processTable(S1[SBOX_SK - 2], S1[SBOX_SK - 1], S2); + processTable(S2[SBOX_SK - 2], S2[SBOX_SK - 1], S3); + } + + /** + * Encrypt the given input starting at the given offset and place the result + * in the provided buffer starting at the given offset. The input will be an + * exact multiple of our blocksize. + */ + private void encryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex) { + int xl = BytesTo32bits(src, srcIndex); + int xr = BytesTo32bits(src, srcIndex + 4); + xl ^= P[0]; + + for (int i = 1; i < ROUNDS; i += 2) { + xr ^= F(xl) ^ P[i]; + xl ^= F(xr) ^ P[i + 1]; + } + + xr ^= P[ROUNDS + 1]; + Bits32ToBytes(xr, dst, dstIndex); + Bits32ToBytes(xl, dst, dstIndex + 4); + } + + /** + * Decrypt the given input starting at the given offset and place the result + * in the provided buffer starting at the given offset. The input will be an + * exact multiple of our blocksize. + */ + private void decryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex) { + int xl = BytesTo32bits(src, srcIndex); + int xr = BytesTo32bits(src, srcIndex + 4); + xl ^= P[ROUNDS + 1]; + + for (int i = ROUNDS; i > 0; i -= 2) { + xr ^= F(xl) ^ P[i]; + xl ^= F(xr) ^ P[i - 1]; + } + + xr ^= P[0]; + Bits32ToBytes(xr, dst, dstIndex); + Bits32ToBytes(xl, dst, dstIndex + 4); + } + + private int BytesTo32bits(byte[] b, int i) { + return ((b[i] & 0xff) << 24) | ((b[i + 1] & 0xff) << 16) | ((b[i + 2] & 0xff) << 8) | ((b[i + 3] & 0xff)); + } + + private void Bits32ToBytes(int in, byte[] b, int offset) { + b[offset + 3] = (byte) in; + b[offset + 2] = (byte)(in >> 8); + b[offset + 1] = (byte)(in >> 16); + b[offset] = (byte)(in >> 24); + } + +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/cipher/CBCMode.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/cipher/CBCMode.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.cipher; + +/** + * CBCMode. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class CBCMode implements BlockCipher { + BlockCipher tc; + int blockSize; + boolean doEncrypt; + + byte[] cbc_vector; + byte[] tmp_vector; + + public void init(boolean forEncryption, byte[] key) { + } + + public CBCMode(BlockCipher tc, byte[] iv, boolean doEncrypt) + throws IllegalArgumentException { + this.tc = tc; + this.blockSize = tc.getBlockSize(); + this.doEncrypt = doEncrypt; + + if (this.blockSize != iv.length) + throw new IllegalArgumentException("IV must be " + blockSize + + " bytes long! (currently " + iv.length + ")"); + + this.cbc_vector = new byte[blockSize]; + this.tmp_vector = new byte[blockSize]; + System.arraycopy(iv, 0, cbc_vector, 0, blockSize); + } + + public int getBlockSize() { + return blockSize; + } + + private void encryptBlock(byte[] src, int srcoff, byte[] dst, int dstoff) { + for (int i = 0; i < blockSize; i++) + cbc_vector[i] ^= src[srcoff + i]; + + tc.transformBlock(cbc_vector, 0, dst, dstoff); + System.arraycopy(dst, dstoff, cbc_vector, 0, blockSize); + } + + private void decryptBlock(byte[] src, int srcoff, byte[] dst, int dstoff) { + /* Assume the worst, src and dst are overlapping... */ + System.arraycopy(src, srcoff, tmp_vector, 0, blockSize); + tc.transformBlock(src, srcoff, dst, dstoff); + + for (int i = 0; i < blockSize; i++) + dst[dstoff + i] ^= cbc_vector[i]; + + /* ...that is why we need a tmp buffer. */ + byte[] swap = cbc_vector; + cbc_vector = tmp_vector; + tmp_vector = swap; + } + + public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) { + if (doEncrypt) + encryptBlock(src, srcoff, dst, dstoff); + else + decryptBlock(src, srcoff, dst, dstoff); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/cipher/CTRMode.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/cipher/CTRMode.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.cipher; + +/** + * This is CTR mode as described in draft-ietf-secsh-newmodes-XY.txt + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class CTRMode implements BlockCipher { + byte[] X; + byte[] Xenc; + + BlockCipher bc; + int blockSize; + boolean doEncrypt; + + int count = 0; + + public void init(boolean forEncryption, byte[] key) { + } + + public CTRMode(BlockCipher tc, byte[] iv, boolean doEnc) throws IllegalArgumentException { + bc = tc; + blockSize = bc.getBlockSize(); + doEncrypt = doEnc; + + if (blockSize != iv.length) + throw new IllegalArgumentException("IV must be " + blockSize + " bytes long! (currently " + iv.length + ")"); + + X = new byte[blockSize]; + Xenc = new byte[blockSize]; + System.arraycopy(iv, 0, X, 0, blockSize); + } + + public final int getBlockSize() { + return blockSize; + } + + public final void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) { + bc.transformBlock(X, 0, Xenc, 0); + + for (int i = 0; i < blockSize; i++) { + dst[dstoff + i] = (byte)(src[srcoff + i] ^ Xenc[i]); + } + + for (int i = (blockSize - 1); i >= 0; i--) { + X[i]++; + + if (X[i] != 0) + break; + } + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/cipher/CipherInputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/cipher/CipherInputStream.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.cipher; + +import java.io.IOException; +import java.io.InputStream; + +/** + * CipherInputStream. + * + * @author Christian Plattner + * @version $Id: CipherInputStream.java 11 2011-05-27 14:14:06Z dkocher@sudo.ch $ + */ +public class CipherInputStream { + BlockCipher currentCipher; + InputStream bi; + byte[] buffer; + byte[] enc; + int blockSize; + int pos; + + /* + * We cannot use java.io.BufferedInputStream, since that is not available in + * J2ME. Everything could be improved alot here. + */ + + private static final int BUFF_SIZE = 8192; + byte[] input_buffer = new byte[BUFF_SIZE]; + int input_buffer_pos = 0; + int input_buffer_size = 0; + + public CipherInputStream(BlockCipher tc, InputStream bi) { + this.bi = bi; + changeCipher(tc); + } + + private int fill_buffer() throws IOException { + input_buffer_pos = 0; + input_buffer_size = 0; + input_buffer_size = bi.read(input_buffer, 0, BUFF_SIZE); + return input_buffer_size; + } + + private int internal_read(byte[] b, int off, int len) throws IOException { + if (input_buffer_size < 0) { + return -1; + } + + if (input_buffer_pos >= input_buffer_size) { + if (fill_buffer() <= 0) { + return -1; + } + } + + int avail = input_buffer_size - input_buffer_pos; + int thiscopy = (len > avail) ? avail : len; + System.arraycopy(input_buffer, input_buffer_pos, b, off, thiscopy); + input_buffer_pos += thiscopy; + return thiscopy; + } + + public void changeCipher(BlockCipher bc) { + this.currentCipher = bc; + blockSize = bc.getBlockSize(); + buffer = new byte[blockSize]; + enc = new byte[blockSize]; + pos = blockSize; + } + + private void getBlock() throws IOException { + int n = 0; + + while (n < blockSize) { + int len = internal_read(enc, n, blockSize - n); + + if (len < 0) { + throw new IOException("Cannot read full block, EOF reached."); + } + + n += len; + } + + try { + currentCipher.transformBlock(enc, 0, buffer, 0); + } + catch (Exception e) { + throw new IOException("Error while decrypting block."); + } + + pos = 0; + } + + public int read(byte[] dst) throws IOException { + return read(dst, 0, dst.length); + } + + public int read(byte[] dst, int off, int len) throws IOException { + int count = 0; + + while (len > 0) { + if (pos >= blockSize) { + getBlock(); + } + + int avail = blockSize - pos; + int copy = Math.min(avail, len); + System.arraycopy(buffer, pos, dst, off, copy); + pos += copy; + off += copy; + len -= copy; + count += copy; + } + + return count; + } + + public int read() throws IOException { + if (pos >= blockSize) { + getBlock(); + } + + return buffer[pos++] & 0xff; + } + + public int readPlain(byte[] b, int off, int len) throws IOException { + if (pos != blockSize) { + throw new IOException("Cannot read plain since crypto buffer is not aligned."); + } + + int n = 0; + + while (n < len) { + int cnt = internal_read(b, off + n, len - n); + + if (cnt < 0) { + throw new IOException("Cannot fill buffer, EOF reached."); + } + + n += cnt; + } + + return n; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/cipher/CipherOutputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/cipher/CipherOutputStream.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.cipher; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * CipherOutputStream. + * + * @author Christian Plattner + * @version $Id: CipherOutputStream.java 85 2014-04-07 14:05:09Z dkocher@sudo.ch $ + */ +public class CipherOutputStream { + BlockCipher currentCipher; + OutputStream bo; + byte[] buffer; + byte[] enc; + int blockSize; + int pos; + + /* + * We cannot use java.io.BufferedOutputStream, since that is not available + * in J2ME. Everything could be improved here alot. + */ + + private static final int BUFF_SIZE = 8192; + byte[] out_buffer = new byte[BUFF_SIZE]; + int out_buffer_pos = 0; + + public CipherOutputStream(BlockCipher tc, OutputStream bo) { + this.bo = bo; + changeCipher(tc); + } + + private void internal_write(byte[] src, int off, int len) throws IOException { + while (len > 0) { + int space = BUFF_SIZE - out_buffer_pos; + int copy = (len > space) ? space : len; + System.arraycopy(src, off, out_buffer, out_buffer_pos, copy); + off += copy; + out_buffer_pos += copy; + len -= copy; + + if (out_buffer_pos >= BUFF_SIZE) { + bo.write(out_buffer, 0, BUFF_SIZE); + out_buffer_pos = 0; + } + } + } + + private void internal_write(int b) throws IOException { + out_buffer[out_buffer_pos++] = (byte) b; + + if (out_buffer_pos >= BUFF_SIZE) { + bo.write(out_buffer, 0, BUFF_SIZE); + out_buffer_pos = 0; + } + } + + public void flush() throws IOException { + if (pos != 0) { + throw new IOException("FATAL: cannot flush since crypto buffer is not aligned."); + } + + if (out_buffer_pos > 0) { + bo.write(out_buffer, 0, out_buffer_pos); + out_buffer_pos = 0; + } + + bo.flush(); + } + + public void changeCipher(BlockCipher bc) { + this.currentCipher = bc; + blockSize = bc.getBlockSize(); + buffer = new byte[blockSize]; + enc = new byte[blockSize]; + pos = 0; + } + + private void writeBlock() throws IOException { + try { + currentCipher.transformBlock(buffer, 0, enc, 0); + } + catch (Exception e) { + throw new IOException("Error while decrypting block.", e); + } + + internal_write(enc, 0, blockSize); + pos = 0; + } + + public void write(byte[] src, int off, int len) throws IOException { + while (len > 0) { + int avail = blockSize - pos; + int copy = Math.min(avail, len); + System.arraycopy(src, off, buffer, pos, copy); + pos += copy; + off += copy; + len -= copy; + + if (pos >= blockSize) { + writeBlock(); + } + } + } + + public void write(int b) throws IOException { + buffer[pos++] = (byte) b; + + if (pos >= blockSize) { + writeBlock(); + } + } + + public void writePlain(int b) throws IOException { + if (pos != 0) { + throw new IOException("Cannot write plain since crypto buffer is not aligned."); + } + + internal_write(b); + } + + public void writePlain(byte[] b, int off, int len) throws IOException { + if (pos != 0) { + throw new IOException("Cannot write plain since crypto buffer is not aligned."); + } + + internal_write(b, off, len); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/cipher/DES.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/cipher/DES.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,352 @@ +package ch.ethz.ssh2.crypto.cipher; + +/* + This file is based on the 3DES implementation from the Bouncy Castle Crypto package. + Their licence file states the following: + + Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle + (http://www.bouncycastle.org) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +/** + * DES. + * + * @author See comments in the source file + * @version 2.50, 03/15/10 + * + */ +public class DES implements BlockCipher { + private int[] workingKey = null; + + /** + * standard constructor. + */ + public DES() { + } + + /** + * initialise a DES cipher. + * + * @param encrypting + * whether or not we are for encryption. + * @param key + * the parameters required to set up the cipher. + * @exception IllegalArgumentException + * if the params argument is inappropriate. + */ + public void init(boolean encrypting, byte[] key) { + this.workingKey = generateWorkingKey(encrypting, key, 0); + } + + public String getAlgorithmName() { + return "DES"; + } + + public int getBlockSize() { + return 8; + } + + public void transformBlock(byte[] in, int inOff, byte[] out, int outOff) { + if (workingKey == null) { + throw new IllegalStateException("DES engine not initialised!"); + } + + desFunc(workingKey, in, inOff, out, outOff); + } + + public void reset() { + } + + /** + * what follows is mainly taken from "Applied Cryptography", by Bruce + * Schneier, however it also bears great resemblance to Richard + * Outerbridge's D3DES... + */ + + static short[] Df_Key = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, + 0x10, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67 + }; + + static short[] bytebit = { 0200, 0100, 040, 020, 010, 04, 02, 01 }; + + static int[] bigbyte = { 0x800000, 0x400000, 0x200000, 0x100000, 0x80000, 0x40000, 0x20000, 0x10000, 0x8000, + 0x4000, 0x2000, 0x1000, 0x800, 0x400, 0x200, 0x100, 0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1 + }; + + /* + * Use the key schedule specified in the Standard (ANSI X3.92-1981). + */ + + static byte[] pc1 = { 56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, + 59, 51, 43, 35, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 60, 52, 44, 36, 28, 20, 12, + 4, 27, 19, 11, 3 + }; + + static byte[] totrot = { 1, 2, 4, 6, 8, 10, 12, 14, 15, 17, 19, 21, 23, 25, 27, 28 }; + + static byte[] pc2 = { 13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9, 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1, 40, + 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47, 43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31 + }; + + static int[] SP1 = { 0x01010400, 0x00000000, 0x00010000, 0x01010404, 0x01010004, 0x00010404, 0x00000004, + 0x00010000, 0x00000400, 0x01010400, 0x01010404, 0x00000400, 0x01000404, 0x01010004, 0x01000000, 0x00000004, + 0x00000404, 0x01000400, 0x01000400, 0x00010400, 0x00010400, 0x01010000, 0x01010000, 0x01000404, 0x00010004, + 0x01000004, 0x01000004, 0x00010004, 0x00000000, 0x00000404, 0x00010404, 0x01000000, 0x00010000, 0x01010404, + 0x00000004, 0x01010000, 0x01010400, 0x01000000, 0x01000000, 0x00000400, 0x01010004, 0x00010000, 0x00010400, + 0x01000004, 0x00000400, 0x00000004, 0x01000404, 0x00010404, 0x01010404, 0x00010004, 0x01010000, 0x01000404, + 0x01000004, 0x00000404, 0x00010404, 0x01010400, 0x00000404, 0x01000400, 0x01000400, 0x00000000, 0x00010004, + 0x00010400, 0x00000000, 0x01010004 + }; + + static int[] SP2 = { 0x80108020, 0x80008000, 0x00008000, 0x00108020, 0x00100000, 0x00000020, 0x80100020, + 0x80008020, 0x80000020, 0x80108020, 0x80108000, 0x80000000, 0x80008000, 0x00100000, 0x00000020, 0x80100020, + 0x00108000, 0x00100020, 0x80008020, 0x00000000, 0x80000000, 0x00008000, 0x00108020, 0x80100000, 0x00100020, + 0x80000020, 0x00000000, 0x00108000, 0x00008020, 0x80108000, 0x80100000, 0x00008020, 0x00000000, 0x00108020, + 0x80100020, 0x00100000, 0x80008020, 0x80100000, 0x80108000, 0x00008000, 0x80100000, 0x80008000, 0x00000020, + 0x80108020, 0x00108020, 0x00000020, 0x00008000, 0x80000000, 0x00008020, 0x80108000, 0x00100000, 0x80000020, + 0x00100020, 0x80008020, 0x80000020, 0x00100020, 0x00108000, 0x00000000, 0x80008000, 0x00008020, 0x80000000, + 0x80100020, 0x80108020, 0x00108000 + }; + + static int[] SP3 = { 0x00000208, 0x08020200, 0x00000000, 0x08020008, 0x08000200, 0x00000000, 0x00020208, + 0x08000200, 0x00020008, 0x08000008, 0x08000008, 0x00020000, 0x08020208, 0x00020008, 0x08020000, 0x00000208, + 0x08000000, 0x00000008, 0x08020200, 0x00000200, 0x00020200, 0x08020000, 0x08020008, 0x00020208, 0x08000208, + 0x00020200, 0x00020000, 0x08000208, 0x00000008, 0x08020208, 0x00000200, 0x08000000, 0x08020200, 0x08000000, + 0x00020008, 0x00000208, 0x00020000, 0x08020200, 0x08000200, 0x00000000, 0x00000200, 0x00020008, 0x08020208, + 0x08000200, 0x08000008, 0x00000200, 0x00000000, 0x08020008, 0x08000208, 0x00020000, 0x08000000, 0x08020208, + 0x00000008, 0x00020208, 0x00020200, 0x08000008, 0x08020000, 0x08000208, 0x00000208, 0x08020000, 0x00020208, + 0x00000008, 0x08020008, 0x00020200 + }; + + static int[] SP4 = { 0x00802001, 0x00002081, 0x00002081, 0x00000080, 0x00802080, 0x00800081, 0x00800001, + 0x00002001, 0x00000000, 0x00802000, 0x00802000, 0x00802081, 0x00000081, 0x00000000, 0x00800080, 0x00800001, + 0x00000001, 0x00002000, 0x00800000, 0x00802001, 0x00000080, 0x00800000, 0x00002001, 0x00002080, 0x00800081, + 0x00000001, 0x00002080, 0x00800080, 0x00002000, 0x00802080, 0x00802081, 0x00000081, 0x00800080, 0x00800001, + 0x00802000, 0x00802081, 0x00000081, 0x00000000, 0x00000000, 0x00802000, 0x00002080, 0x00800080, 0x00800081, + 0x00000001, 0x00802001, 0x00002081, 0x00002081, 0x00000080, 0x00802081, 0x00000081, 0x00000001, 0x00002000, + 0x00800001, 0x00002001, 0x00802080, 0x00800081, 0x00002001, 0x00002080, 0x00800000, 0x00802001, 0x00000080, + 0x00800000, 0x00002000, 0x00802080 + }; + + static int[] SP5 = { 0x00000100, 0x02080100, 0x02080000, 0x42000100, 0x00080000, 0x00000100, 0x40000000, + 0x02080000, 0x40080100, 0x00080000, 0x02000100, 0x40080100, 0x42000100, 0x42080000, 0x00080100, 0x40000000, + 0x02000000, 0x40080000, 0x40080000, 0x00000000, 0x40000100, 0x42080100, 0x42080100, 0x02000100, 0x42080000, + 0x40000100, 0x00000000, 0x42000000, 0x02080100, 0x02000000, 0x42000000, 0x00080100, 0x00080000, 0x42000100, + 0x00000100, 0x02000000, 0x40000000, 0x02080000, 0x42000100, 0x40080100, 0x02000100, 0x40000000, 0x42080000, + 0x02080100, 0x40080100, 0x00000100, 0x02000000, 0x42080000, 0x42080100, 0x00080100, 0x42000000, 0x42080100, + 0x02080000, 0x00000000, 0x40080000, 0x42000000, 0x00080100, 0x02000100, 0x40000100, 0x00080000, 0x00000000, + 0x40080000, 0x02080100, 0x40000100 + }; + + static int[] SP6 = { 0x20000010, 0x20400000, 0x00004000, 0x20404010, 0x20400000, 0x00000010, 0x20404010, + 0x00400000, 0x20004000, 0x00404010, 0x00400000, 0x20000010, 0x00400010, 0x20004000, 0x20000000, 0x00004010, + 0x00000000, 0x00400010, 0x20004010, 0x00004000, 0x00404000, 0x20004010, 0x00000010, 0x20400010, 0x20400010, + 0x00000000, 0x00404010, 0x20404000, 0x00004010, 0x00404000, 0x20404000, 0x20000000, 0x20004000, 0x00000010, + 0x20400010, 0x00404000, 0x20404010, 0x00400000, 0x00004010, 0x20000010, 0x00400000, 0x20004000, 0x20000000, + 0x00004010, 0x20000010, 0x20404010, 0x00404000, 0x20400000, 0x00404010, 0x20404000, 0x00000000, 0x20400010, + 0x00000010, 0x00004000, 0x20400000, 0x00404010, 0x00004000, 0x00400010, 0x20004010, 0x00000000, 0x20404000, + 0x20000000, 0x00400010, 0x20004010 + }; + + static int[] SP7 = { 0x00200000, 0x04200002, 0x04000802, 0x00000000, 0x00000800, 0x04000802, 0x00200802, + 0x04200800, 0x04200802, 0x00200000, 0x00000000, 0x04000002, 0x00000002, 0x04000000, 0x04200002, 0x00000802, + 0x04000800, 0x00200802, 0x00200002, 0x04000800, 0x04000002, 0x04200000, 0x04200800, 0x00200002, 0x04200000, + 0x00000800, 0x00000802, 0x04200802, 0x00200800, 0x00000002, 0x04000000, 0x00200800, 0x04000000, 0x00200800, + 0x00200000, 0x04000802, 0x04000802, 0x04200002, 0x04200002, 0x00000002, 0x00200002, 0x04000000, 0x04000800, + 0x00200000, 0x04200800, 0x00000802, 0x00200802, 0x04200800, 0x00000802, 0x04000002, 0x04200802, 0x04200000, + 0x00200800, 0x00000000, 0x00000002, 0x04200802, 0x00000000, 0x00200802, 0x04200000, 0x00000800, 0x04000002, + 0x04000800, 0x00000800, 0x00200002 + }; + + static int[] SP8 = { 0x10001040, 0x00001000, 0x00040000, 0x10041040, 0x10000000, 0x10001040, 0x00000040, + 0x10000000, 0x00040040, 0x10040000, 0x10041040, 0x00041000, 0x10041000, 0x00041040, 0x00001000, 0x00000040, + 0x10040000, 0x10000040, 0x10001000, 0x00001040, 0x00041000, 0x00040040, 0x10040040, 0x10041000, 0x00001040, + 0x00000000, 0x00000000, 0x10040040, 0x10000040, 0x10001000, 0x00041040, 0x00040000, 0x00041040, 0x00040000, + 0x10041000, 0x00001000, 0x00000040, 0x10040040, 0x00001000, 0x00041040, 0x10001000, 0x00000040, 0x10000040, + 0x10040000, 0x10040040, 0x10000000, 0x00040000, 0x10001040, 0x00000000, 0x10041040, 0x00040040, 0x10000040, + 0x10040000, 0x10001000, 0x10001040, 0x00000000, 0x10041040, 0x00041000, 0x00041000, 0x00001040, 0x00001040, + 0x00040040, 0x10000000, 0x10041000 + }; + + /** + * generate an integer based working key based on our secret key and what we + * processing we are planning to do. + * + * Acknowledgements for this routine go to James Gillogly & Phil Karn. + * (whoever, and wherever they are!). + */ + protected int[] generateWorkingKey(boolean encrypting, byte[] key, int off) { + int[] newKey = new int[32]; + boolean[] pc1m = new boolean[56], pcr = new boolean[56]; + + for (int j = 0; j < 56; j++) { + int l = pc1[j]; + pc1m[j] = ((key[off + (l >>> 3)] & bytebit[l & 07]) != 0); + } + + for (int i = 0; i < 16; i++) { + int l, m, n; + + if (encrypting) { + m = i << 1; + } + else { + m = (15 - i) << 1; + } + + n = m + 1; + newKey[m] = newKey[n] = 0; + + for (int j = 0; j < 28; j++) { + l = j + totrot[i]; + + if (l < 28) { + pcr[j] = pc1m[l]; + } + else { + pcr[j] = pc1m[l - 28]; + } + } + + for (int j = 28; j < 56; j++) { + l = j + totrot[i]; + + if (l < 56) { + pcr[j] = pc1m[l]; + } + else { + pcr[j] = pc1m[l - 28]; + } + } + + for (int j = 0; j < 24; j++) { + if (pcr[pc2[j]]) { + newKey[m] |= bigbyte[j]; + } + + if (pcr[pc2[j + 24]]) { + newKey[n] |= bigbyte[j]; + } + } + } + + // + // store the processed key + // + for (int i = 0; i != 32; i += 2) { + int i1, i2; + i1 = newKey[i]; + i2 = newKey[i + 1]; + newKey[i] = ((i1 & 0x00fc0000) << 6) | ((i1 & 0x00000fc0) << 10) | ((i2 & 0x00fc0000) >>> 10) + | ((i2 & 0x00000fc0) >>> 6); + newKey[i + 1] = ((i1 & 0x0003f000) << 12) | ((i1 & 0x0000003f) << 16) | ((i2 & 0x0003f000) >>> 4) + | (i2 & 0x0000003f); + } + + return newKey; + } + + /** + * the DES engine. + */ + protected void desFunc(int[] wKey, byte[] in, int inOff, byte[] out, int outOff) { + int work, right, left; + left = (in[inOff + 0] & 0xff) << 24; + left |= (in[inOff + 1] & 0xff) << 16; + left |= (in[inOff + 2] & 0xff) << 8; + left |= (in[inOff + 3] & 0xff); + right = (in[inOff + 4] & 0xff) << 24; + right |= (in[inOff + 5] & 0xff) << 16; + right |= (in[inOff + 6] & 0xff) << 8; + right |= (in[inOff + 7] & 0xff); + work = ((left >>> 4) ^ right) & 0x0f0f0f0f; + right ^= work; + left ^= (work << 4); + work = ((left >>> 16) ^ right) & 0x0000ffff; + right ^= work; + left ^= (work << 16); + work = ((right >>> 2) ^ left) & 0x33333333; + left ^= work; + right ^= (work << 2); + work = ((right >>> 8) ^ left) & 0x00ff00ff; + left ^= work; + right ^= (work << 8); + right = ((right << 1) | ((right >>> 31) & 1)) & 0xffffffff; + work = (left ^ right) & 0xaaaaaaaa; + left ^= work; + right ^= work; + left = ((left << 1) | ((left >>> 31) & 1)) & 0xffffffff; + + for (int round = 0; round < 8; round++) { + int fval; + work = (right << 28) | (right >>> 4); + work ^= wKey[round * 4 + 0]; + fval = SP7[work & 0x3f]; + fval |= SP5[(work >>> 8) & 0x3f]; + fval |= SP3[(work >>> 16) & 0x3f]; + fval |= SP1[(work >>> 24) & 0x3f]; + work = right ^ wKey[round * 4 + 1]; + fval |= SP8[work & 0x3f]; + fval |= SP6[(work >>> 8) & 0x3f]; + fval |= SP4[(work >>> 16) & 0x3f]; + fval |= SP2[(work >>> 24) & 0x3f]; + left ^= fval; + work = (left << 28) | (left >>> 4); + work ^= wKey[round * 4 + 2]; + fval = SP7[work & 0x3f]; + fval |= SP5[(work >>> 8) & 0x3f]; + fval |= SP3[(work >>> 16) & 0x3f]; + fval |= SP1[(work >>> 24) & 0x3f]; + work = left ^ wKey[round * 4 + 3]; + fval |= SP8[work & 0x3f]; + fval |= SP6[(work >>> 8) & 0x3f]; + fval |= SP4[(work >>> 16) & 0x3f]; + fval |= SP2[(work >>> 24) & 0x3f]; + right ^= fval; + } + + right = (right << 31) | (right >>> 1); + work = (left ^ right) & 0xaaaaaaaa; + left ^= work; + right ^= work; + left = (left << 31) | (left >>> 1); + work = ((left >>> 8) ^ right) & 0x00ff00ff; + right ^= work; + left ^= (work << 8); + work = ((left >>> 2) ^ right) & 0x33333333; + right ^= work; + left ^= (work << 2); + work = ((right >>> 16) ^ left) & 0x0000ffff; + left ^= work; + right ^= (work << 16); + work = ((right >>> 4) ^ left) & 0x0f0f0f0f; + left ^= work; + right ^= (work << 4); + out[outOff + 0] = (byte)((right >>> 24) & 0xff); + out[outOff + 1] = (byte)((right >>> 16) & 0xff); + out[outOff + 2] = (byte)((right >>> 8) & 0xff); + out[outOff + 3] = (byte)(right & 0xff); + out[outOff + 4] = (byte)((left >>> 24) & 0xff); + out[outOff + 5] = (byte)((left >>> 16) & 0xff); + out[outOff + 6] = (byte)((left >>> 8) & 0xff); + out[outOff + 7] = (byte)(left & 0xff); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/cipher/DESede.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/cipher/DESede.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,98 @@ +package ch.ethz.ssh2.crypto.cipher; + +/* + This file was shamelessly taken (and modified) from the Bouncy Castle Crypto package. + Their licence file states the following: + + Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle + (http://www.bouncycastle.org) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +/** + * DESede. + * + * @author See comments in the source file + * @version 2.50, 03/15/10 + * + */ +public class DESede extends DES { + private int[] key1 = null; + private int[] key2 = null; + private int[] key3 = null; + + private boolean encrypt; + + /** + * standard constructor. + */ + public DESede() { + } + + /** + * initialise a DES cipher. + * + * @param encrypting + * whether or not we are for encryption. + * @param key + * the parameters required to set up the cipher. + * @exception IllegalArgumentException + * if the params argument is inappropriate. + */ + @Override + public void init(boolean encrypting, byte[] key) { + key1 = generateWorkingKey(encrypting, key, 0); + key2 = generateWorkingKey(!encrypting, key, 8); + key3 = generateWorkingKey(encrypting, key, 16); + encrypt = encrypting; + } + + @Override + public String getAlgorithmName() { + return "DESede"; + } + + @Override + public int getBlockSize() { + return 8; + } + + @Override + public void transformBlock(byte[] in, int inOff, byte[] out, int outOff) { + if (key1 == null) { + throw new IllegalStateException("DESede engine not initialised!"); + } + + if (encrypt) { + desFunc(key1, in, inOff, out, outOff); + desFunc(key2, out, outOff, out, outOff); + desFunc(key3, out, outOff, out, outOff); + } + else { + desFunc(key3, in, inOff, out, outOff); + desFunc(key2, out, outOff, out, outOff); + desFunc(key1, out, outOff, out, outOff); + } + } + + @Override + public void reset() { + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/cipher/NullCipher.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/cipher/NullCipher.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.cipher; + +/** + * NullCipher. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class NullCipher implements BlockCipher { + private int blockSize = 8; + + public NullCipher() { + } + + public NullCipher(int blockSize) { + this.blockSize = blockSize; + } + + public void init(boolean forEncryption, byte[] key) { + } + + public int getBlockSize() { + return blockSize; + } + + public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) { + System.arraycopy(src, srcoff, dst, dstoff, blockSize); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/dh/DhExchange.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/dh/DhExchange.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,147 @@ +/** + * + */ +package ch.ethz.ssh2.crypto.dh; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; + +import javax.crypto.KeyAgreement; +import javax.crypto.interfaces.DHPrivateKey; +import javax.crypto.interfaces.DHPublicKey; +import javax.crypto.spec.DHParameterSpec; +import javax.crypto.spec.DHPublicKeySpec; + +/** + * @author kenny + * + */ +public class DhExchange extends GenericDhExchange { + + /* Given by the standard */ + + private static final BigInteger P1 = new BigInteger( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381" + + "FFFFFFFFFFFFFFFF", 16); + + private static final BigInteger P14 = new BigInteger( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + + "15728E5A8AACAA68FFFFFFFFFFFFFFFF", 16); + + private static final BigInteger G = BigInteger.valueOf(2); + + /* Client public and private */ + + private DHPrivateKey clientPrivate; + private DHPublicKey clientPublic; + + /* Server public */ + + private DHPublicKey serverPublic; + private byte[] f; + + @Override + public void init(String name) throws IOException { + final DHParameterSpec spec; + + if ("diffie-hellman-group1-sha1".equals(name)) { + spec = new DHParameterSpec(P1, G); + } + else if ("diffie-hellman-group14-sha1".equals(name)) { + spec = new DHParameterSpec(P14, G); + } + else { + throw new IllegalArgumentException("Unknown DH group " + name); + } + + try { + KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH"); + kpg.initialize(spec); + KeyPair pair = kpg.generateKeyPair(); + clientPrivate = (DHPrivateKey) pair.getPrivate(); + clientPublic = (DHPublicKey) pair.getPublic(); + } + catch (NoSuchAlgorithmException e) { + throw(IOException) new IOException("No DH keypair generator").initCause(e); + } + catch (InvalidAlgorithmParameterException e) { + throw(IOException) new IOException("Invalid DH parameters").initCause(e); + } + } + + @Override + public byte[] getE() { + if (clientPublic == null) + throw new IllegalStateException("DhExchange not initialized!"); + + return clientPublic.getY().toByteArray(); + } + + @Override + protected byte[] getServerE() { + if (serverPublic == null) + throw new IllegalStateException("DhExchange not initialized!"); + + return serverPublic.getY().toByteArray(); + } + + @Override + public byte[] getF() { + return f; + } + + @Override + public void setF(byte[] f) throws IOException { + if (clientPublic == null) + throw new IllegalStateException("DhExchange not initialized!"); + + final KeyAgreement ka; + + try { + KeyFactory kf = KeyFactory.getInstance("DH"); + DHParameterSpec params = clientPublic.getParams(); + this.f = f; + this.serverPublic = (DHPublicKey) kf.generatePublic(new DHPublicKeySpec( + new BigInteger(f), params.getP(), params.getG())); + ka = KeyAgreement.getInstance("DH"); + ka.init(clientPrivate); + ka.doPhase(serverPublic, true); + } + catch (NoSuchAlgorithmException e) { + throw(IOException) new IOException("No DH key agreement method").initCause(e); + } + catch (InvalidKeyException e) { + throw(IOException) new IOException("Invalid DH key").initCause(e); + } + catch (InvalidKeySpecException e) { + throw(IOException) new IOException("Invalid DH key").initCause(e); + } + + sharedSecret = new BigInteger(ka.generateSecret()); + } + + @Override + public String getHashAlgo() { + return "SHA1"; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/dh/DhGroupExchange.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/dh/DhGroupExchange.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,108 @@ + +package ch.ethz.ssh2.crypto.dh; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.io.IOException; + +import ch.ethz.ssh2.DHGexParameters; +import ch.ethz.ssh2.crypto.digest.HashForSSH2Types; + + +/** + * DhGroupExchange. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: DhGroupExchange.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $ + */ +public class DhGroupExchange { + /* Given by the standard */ + + private BigInteger p; + private BigInteger g; + + /* Client public and private */ + + private BigInteger e; + private BigInteger x; + + /* Server public */ + + private BigInteger f; + + /* Shared secret */ + + private BigInteger k; + + public DhGroupExchange(BigInteger p, BigInteger g) { + this.p = p; + this.g = g; + } + + public void init(SecureRandom rnd) { + k = null; + x = new BigInteger(p.bitLength() - 1, rnd); + e = g.modPow(x, p); + } + + /** + * @return Returns the e. + */ + public BigInteger getE() { + if (e == null) + throw new IllegalStateException("Not initialized!"); + + return e; + } + + /** + * @return Returns the shared secret k. + */ + public BigInteger getK() { + if (k == null) + throw new IllegalStateException("Shared secret not yet known, need f first!"); + + return k; + } + + /** + * Sets f and calculates the shared secret. + */ + public void setF(BigInteger f) { + if (e == null) + throw new IllegalStateException("Not initialized!"); + + BigInteger zero = BigInteger.valueOf(0); + + if (zero.compareTo(f) >= 0 || p.compareTo(f) <= 0) + throw new IllegalArgumentException("Invalid f specified!"); + + this.f = f; + this.k = f.modPow(x, p); + } + + public byte[] calculateH(String hashAlgo, byte[] clientversion, byte[] serverversion, + byte[] clientKexPayload, byte[] serverKexPayload, byte[] hostKey, DHGexParameters para) throws IOException { + HashForSSH2Types hash = new HashForSSH2Types(hashAlgo); + hash.updateByteString(clientversion); + hash.updateByteString(serverversion); + hash.updateByteString(clientKexPayload); + hash.updateByteString(serverKexPayload); + hash.updateByteString(hostKey); + + if (para.getMin_group_len() > 0) + hash.updateUINT32(para.getMin_group_len()); + + hash.updateUINT32(para.getPref_group_len()); + + if (para.getMax_group_len() > 0) + hash.updateUINT32(para.getMax_group_len()); + + hash.updateBigInt(p); + hash.updateBigInt(g); + hash.updateBigInt(e); + hash.updateBigInt(f); + hash.updateBigInt(k); + return hash.getDigest(); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/dh/EcDhExchange.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/dh/EcDhExchange.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,127 @@ +/** + * + */ +package ch.ethz.ssh2.crypto.dh; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; + +import javax.crypto.KeyAgreement; + +import ch.ethz.ssh2.signature.ECDSASHA2Verify; + +/** + * @author kenny + * + */ +public class EcDhExchange extends GenericDhExchange { + + /* Client public and private */ + + private ECPrivateKey clientPrivate; + private ECPublicKey clientPublic; + + /* Server public */ + + private ECPublicKey serverPublic; + private byte[] f; + + @Override + public void init(String name) throws IOException { + final ECParameterSpec spec; + + if ("ecdh-sha2-nistp256".equals(name)) { + spec = ECDSASHA2Verify.EllipticCurves.nistp256; + } + else if ("ecdh-sha2-nistp384".equals(name)) { + spec = ECDSASHA2Verify.EllipticCurves.nistp384; + } + else if ("ecdh-sha2-nistp521".equals(name)) { + spec = ECDSASHA2Verify.EllipticCurves.nistp521; + } + else { + throw new IllegalArgumentException("Unknown EC curve " + name); + } + + KeyPairGenerator kpg; + + try { + kpg = KeyPairGenerator.getInstance("EC"); + kpg.initialize(spec); + KeyPair pair = kpg.generateKeyPair(); + clientPrivate = (ECPrivateKey) pair.getPrivate(); + clientPublic = (ECPublicKey) pair.getPublic(); + } + catch (NoSuchAlgorithmException e) { + throw(IOException) new IOException("No DH keypair generator").initCause(e); + } + catch (InvalidAlgorithmParameterException e) { + throw(IOException) new IOException("Invalid DH parameters").initCause(e); + } + } + + @Override + public byte[] getE() { + return ECDSASHA2Verify.encodeECPoint(clientPublic.getW(), clientPublic.getParams() + .getCurve()); + } + + @Override + protected byte[] getServerE() { + return ECDSASHA2Verify.encodeECPoint(serverPublic.getW(), serverPublic.getParams() + .getCurve()); + } + + @Override + public byte[] getF() { + return f; + } + + @Override + public void setF(byte[] f) throws IOException { + if (clientPublic == null) + throw new IllegalStateException("DhDsaExchange not initialized!"); + + final KeyAgreement ka; + + try { + KeyFactory kf = KeyFactory.getInstance("EC"); + ECParameterSpec params = clientPublic.getParams(); + ECPoint serverPoint = ECDSASHA2Verify.decodeECPoint(f, params.getCurve()); + this.f = f; + this.serverPublic = (ECPublicKey) kf.generatePublic(new ECPublicKeySpec(serverPoint, + params)); + ka = KeyAgreement.getInstance("ECDH"); + ka.init(clientPrivate); + ka.doPhase(serverPublic, true); + } + catch (NoSuchAlgorithmException e) { + throw(IOException) new IOException("No ECDH key agreement method").initCause(e); + } + catch (InvalidKeyException e) { + throw(IOException) new IOException("Invalid ECDH key").initCause(e); + } + catch (InvalidKeySpecException e) { + throw(IOException) new IOException("Invalid ECDH key").initCause(e); + } + + sharedSecret = new BigInteger(ka.generateSecret()); + } + + @Override + public String getHashAlgo() { + return ECDSASHA2Verify.getDigestAlgorithmForParams(clientPublic.getParams()); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/dh/GenericDhExchange.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/dh/GenericDhExchange.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,94 @@ + +package ch.ethz.ssh2.crypto.dh; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; + +import ch.ethz.ssh2.crypto.digest.HashForSSH2Types; +import ch.ethz.ssh2.log.Logger; + + +/** + * DhExchange. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: DhExchange.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ + */ +public abstract class GenericDhExchange { + private static final Logger log = Logger.getLogger(GenericDhExchange.class); + + /* Shared secret */ + + BigInteger sharedSecret; + + protected GenericDhExchange() { + } + + public static GenericDhExchange getInstance(String algo) { + if (algo.startsWith("ecdh-sha2-")) { + return new EcDhExchange(); + } + else { + return new DhExchange(); + } + } + + public abstract void init(String name) throws IOException; + + /** + * @return Returns the e (public value) + * @throws IllegalStateException + */ + public abstract byte[] getE(); + + public void setE(BigInteger e) throws IOException { + throw new IOException(); + } + + /** + * @return Returns the server's e (public value) + * @throws IllegalStateException + */ + protected abstract byte[] getServerE(); + + /** + * @return Returns the shared secret k. + * @throws IllegalStateException + */ + public BigInteger getK() { + if (sharedSecret == null) + throw new IllegalStateException("Shared secret not yet known, need f first!"); + + return sharedSecret; + } + + /** + * @param f + */ + public void setF(BigInteger f) throws IOException { + setF(f.toByteArray()); + } + + public abstract byte[] getF(); + + public abstract void setF(byte[] f) throws IOException; + + public byte[] calculateH(byte[] clientversion, byte[] serverversion, byte[] clientKexPayload, + byte[] serverKexPayload, byte[] hostKey) throws UnsupportedEncodingException, IOException { + HashForSSH2Types hash = new HashForSSH2Types(getHashAlgo()); + log.debug("Client: '" + new String(clientversion, "ISO-8859-1") + "'"); + log.debug("Server: '" + new String(serverversion, "ISO-8859-1") + "'"); + hash.updateByteString(clientversion); + hash.updateByteString(serverversion); + hash.updateByteString(clientKexPayload); + hash.updateByteString(serverKexPayload); + hash.updateByteString(hostKey); + hash.updateByteString(getE()); + hash.updateByteString(getServerE()); + hash.updateBigInt(sharedSecret); + return hash.getDigest(); + } + + public abstract String getHashAlgo(); +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/digest/Digest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/digest/Digest.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.digest; + +import java.security.DigestException; + +/** + * Digest. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public interface Digest { + public int getDigestLength(); + + public void update(byte b); + + public void update(byte[] b); + + public void update(byte b[], int off, int len); + + public void reset(); + + public void digest(byte[] out) throws DigestException; + + public void digest(byte[] out, int off) throws DigestException; +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/digest/HMAC.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/digest/HMAC.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.digest; + +import java.security.DigestException; + +/** + * HMAC. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public final class HMAC implements Digest { + Digest md; + byte[] k_xor_ipad; + byte[] k_xor_opad; + + byte[] tmp; + + int size; + + public HMAC(Digest md, byte[] key, int size) throws DigestException { + this.md = md; + this.size = size; + tmp = new byte[md.getDigestLength()]; + final int BLOCKSIZE = 64; + k_xor_ipad = new byte[BLOCKSIZE]; + k_xor_opad = new byte[BLOCKSIZE]; + + if (key.length > BLOCKSIZE) { + md.reset(); + md.update(key); + md.digest(tmp); + key = tmp; + } + + System.arraycopy(key, 0, k_xor_ipad, 0, key.length); + System.arraycopy(key, 0, k_xor_opad, 0, key.length); + + for (int i = 0; i < BLOCKSIZE; i++) { + k_xor_ipad[i] ^= 0x36; + k_xor_opad[i] ^= 0x5C; + } + + md.update(k_xor_ipad); + } + + public final int getDigestLength() { + return size; + } + + public final void update(byte b) { + md.update(b); + } + + public final void update(byte[] b) { + md.update(b); + } + + public final void update(byte[] b, int off, int len) { + md.update(b, off, len); + } + + public final void reset() { + md.reset(); + md.update(k_xor_ipad); + } + + public final void digest(byte[] out) throws DigestException { + digest(out, 0); + } + + public final void digest(byte[] out, int off) throws DigestException { + md.digest(tmp); + md.update(k_xor_opad); + md.update(tmp); + md.digest(tmp); + System.arraycopy(tmp, 0, out, off, size); + md.update(k_xor_ipad); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/digest/HashForSSH2Types.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/digest/HashForSSH2Types.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.digest; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.DigestException; + +/** + * HashForSSH2Types. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class HashForSSH2Types { + Digest md; + + public HashForSSH2Types(Digest md) { + this.md = md; + } + + public HashForSSH2Types(String type) { + if (type.equals("SHA1")) { + md = new SHA1(); + } + else if (type.equals("SHA2")) { + md = new SHA256(); + } + else if (type.equals("MD5")) { + md = new MD5(); + } + else { + throw new IllegalArgumentException(String.format("Unknown algorithm %s", type)); + } + } + + public void updateByte(byte b) { + md.update(b); + } + + public void updateBytes(byte[] b) { + md.update(b); + } + + public void updateUINT32(int v) { + md.update((byte)(v >> 24)); + md.update((byte)(v >> 16)); + md.update((byte)(v >> 8)); + md.update((byte)(v)); + } + + public void updateByteString(byte[] b) { + updateUINT32(b.length); + updateBytes(b); + } + + public void updateBigInt(BigInteger b) { + updateByteString(b.toByteArray()); + } + + public void reset() { + md.reset(); + } + + public int getDigestLength() { + return md.getDigestLength(); + } + + public byte[] getDigest() throws IOException { + byte[] tmp = new byte[md.getDigestLength()]; + getDigest(tmp); + return tmp; + } + + public void getDigest(byte[] out) throws IOException { + try { + getDigest(out, 0); + } + catch (DigestException e) { + throw new IOException(e); + } + } + + public void getDigest(byte[] out, int off) throws DigestException { + md.digest(out, off); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/digest/MAC.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/digest/MAC.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.digest; + +import java.io.IOException; +import java.security.DigestException; + +/** + * MAC. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public final class MAC { + private Digest mac; + private int size; + + public static String[] getMacList() { + // Higher priority first. Added SHA-2 algorithms as in RFC 6668 + return new String[] {"hmac-sha1-96", "hmac-sha1", "hmac-md5-96", "hmac-md5", "hmac-sha2-256", "hmac-sha2-512"}; + } + + public static void checkMacList(final String[] macs) { + for (String m : macs) { + getKeyLen(m); + } + } + + public static int getKeyLen(final String type) { + if (type.equals("hmac-sha1")) { + return 20; + } + + if (type.equals("hmac-sha1-96")) { + return 20; + } + + if (type.equals("hmac-md5")) { + return 16; + } + + if (type.equals("hmac-md5-96")) { + return 16; + } + + if (type.equals("hmac-sha2-256")) { + return 32; + } + + if (type.equals("hmac-sha2-512")) { + return 64; + } + + throw new IllegalArgumentException(String.format("Unknown algorithm %s", type)); + } + + public MAC(final String type, final byte[] key) throws DigestException { + if (type.equals("hmac-sha1")) { + mac = new HMAC(new SHA1(), key, 20); + } + else if (type.equals("hmac-sha1-96")) { + mac = new HMAC(new SHA1(), key, 12); + } + else if (type.equals("hmac-md5")) { + mac = new HMAC(new MD5(), key, 16); + } + else if (type.equals("hmac-md5-96")) { + mac = new HMAC(new MD5(), key, 12); + } + else if (type.equals("hmac-sha2-256")) { + mac = new HMAC(new SHA256(), key, 32); + } + else if (type.equals("hmac-sha2-512")) { + mac = new HMAC(new SHA512(), key, 64); + } + else { + throw new IllegalArgumentException(String.format("Unknown algorithm %s", type)); + } + + size = mac.getDigestLength(); + } + + public final void initMac(final int seq) { + mac.reset(); + mac.update((byte)(seq >> 24)); + mac.update((byte)(seq >> 16)); + mac.update((byte)(seq >> 8)); + mac.update((byte)(seq)); + } + + public final void update(byte[] packetdata, int off, int len) { + mac.update(packetdata, off, len); + } + + public final void getMac(byte[] out, int off) throws IOException { + try { + mac.digest(out, off); + } + catch (DigestException e) { + throw new IOException(e); + } + } + + public final int size() { + return size; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/digest/MD5.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/digest/MD5.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.digest; + +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * @version $Id: MD5.java 154 2014-04-28 11:45:02Z dkocher@sudo.ch $ + */ +public final class MD5 implements Digest { + + private MessageDigest md; + + public MD5() { + try { + md = MessageDigest.getInstance("MD5"); + } + catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException(e); + } + } + + public final int getDigestLength() { + return md.getDigestLength(); + } + + public final void reset() { + md.reset(); + } + + public final void update(byte b[]) { + this.update(b, 0, b.length); + } + + public final void update(byte b[], int off, int len) { + md.update(b, off, len); + } + + public final void update(byte b) { + md.update(b); + } + + public final void digest(byte[] out) throws DigestException { + this.digest(out, 0); + } + + public final void digest(byte[] out, int off) throws DigestException { + md.digest(out, off, out.length); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/digest/SHA1.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/digest/SHA1.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.digest; + +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * @version $Id: SHA1.java 154 2014-04-28 11:45:02Z dkocher@sudo.ch $ + */ +public final class SHA1 implements Digest { + + private MessageDigest md; + + public SHA1() { + try { + md = MessageDigest.getInstance("SHA-1"); + } + catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException(e); + } + } + + public final int getDigestLength() { + return md.getDigestLength(); + } + + public final void reset() { + md.reset(); + } + + public final void update(byte b[]) { + this.update(b, 0, b.length); + } + + public final void update(byte b[], int off, int len) { + md.update(b, off, len); + } + + public final void update(byte b) { + md.update(b); + } + + public final void digest(byte[] out) throws DigestException { + this.digest(out, 0); + } + + public final void digest(byte[] out, int off) throws DigestException { + md.digest(out, off, out.length); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/digest/SHA256.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/digest/SHA256.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.digest; + +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * @version $Id: SHA256.java 152 2014-04-28 11:02:23Z dkocher@sudo.ch $ + */ +public final class SHA256 implements Digest { + + private MessageDigest md; + + public SHA256() { + try { + md = MessageDigest.getInstance("SHA-256"); + } + catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException(e); + } + } + + public final int getDigestLength() { + return md.getDigestLength(); + } + + public final void reset() { + md.reset(); + } + + public final void update(byte b[]) { + this.update(b, 0, b.length); + } + + public final void update(byte b[], int off, int len) { + md.update(b, off, len); + } + + public final void update(byte b) { + md.update(b); + } + + public final void digest(byte[] out) throws DigestException { + this.digest(out, 0); + } + + public final void digest(byte[] out, int off) throws DigestException { + md.digest(out, off, out.length); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/crypto/digest/SHA512.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/crypto/digest/SHA512.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.digest; + +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * @version $Id: SHA512.java 152 2014-04-28 11:02:23Z dkocher@sudo.ch $ + */ +public final class SHA512 implements Digest { + + private MessageDigest md; + + public SHA512() { + try { + md = MessageDigest.getInstance("SHA-512"); + } + catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException(e); + } + } + + public final int getDigestLength() { + return md.getDigestLength(); + } + + public final void reset() { + md.reset(); + } + + public final void update(byte b[]) { + md.update(b); + } + + public final void update(byte b[], int off, int len) { + md.update(b, off, len); + } + + public final void update(byte b) { + md.update(b); + } + + public final void digest(byte[] out) { + md.digest(out); + } + + public final void digest(byte[] out, int off) throws DigestException { + md.digest(out, off, out.length); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/log/Logger.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/log/Logger.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2.log; + +import android.util.Log; + +public class Logger { + private static final String TAG = "ConnectBot.ssh"; + public static boolean enabled = true; + + public static Logger getLogger(Class x) { + return new Logger(); + } + + public Logger() { + } + + public final boolean isEnabled() { + return enabled; + } + + public boolean isDebugEnabled() { + return enabled; + } + + public void debug(String message) { + if (enabled) Log.d(TAG, message); + } + + public boolean isInfoEnabled() { + return enabled; + } + + public void info(String message) { + if (enabled) Log.i(TAG, message); + } + + public boolean isWarningEnabled() { + return enabled; + } + + public void warning(String message) { + if (enabled) Log.w(TAG, message); + } +} \ No newline at end of file diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketChannelAuthAgentReq.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketChannelAuthAgentReq.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,30 @@ +package ch.ethz.ssh2.packets; + +/** + * PacketGlobalAuthAgent. + * + * @author Kenny Root, kenny@the-b.org + * @version $Id$ + */ +public class PacketChannelAuthAgentReq { + byte[] payload; + + public int recipientChannelID; + + public PacketChannelAuthAgentReq(int recipientChannelID) { + this.recipientChannelID = recipientChannelID; + } + + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); + tw.writeUINT32(recipientChannelID); + tw.writeString("auth-agent-req@openssh.com"); + tw.writeBoolean(true); // want reply + payload = tw.getBytes(); + } + + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketChannelFailure.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketChannelFailure.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2006-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2.packets; + +/** + * @author Christian Plattner + * @version $Id: PacketChannelFailure.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketChannelFailure { + private final byte[] payload; + + public PacketChannelFailure(int recipientChannelID) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_FAILURE); + tw.writeUINT32(recipientChannelID); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketChannelOpenConfirmation.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketChannelOpenConfirmation.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketChannelOpenConfirmation.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketChannelOpenConfirmation { + + private final byte[] payload; + + private final int recipientChannelID; + private final int senderChannelID; + private final int initialWindowSize; + private final int maxPacketSize; + + public PacketChannelOpenConfirmation(int recipientChannelID, int senderChannelID, int initialWindowSize, + int maxPacketSize) { + this.recipientChannelID = recipientChannelID; + this.senderChannelID = senderChannelID; + this.initialWindowSize = initialWindowSize; + this.maxPacketSize = maxPacketSize; + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_OPEN_CONFIRMATION); + tw.writeUINT32(recipientChannelID); + tw.writeUINT32(senderChannelID); + tw.writeUINT32(initialWindowSize); + tw.writeUINT32(maxPacketSize); + payload = tw.getBytes(); + } + + public PacketChannelOpenConfirmation(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_CHANNEL_OPEN_CONFIRMATION) { + throw new PacketTypeException(packet_type); + } + + recipientChannelID = tr.readUINT32(); + senderChannelID = tr.readUINT32(); + initialWindowSize = tr.readUINT32(); + maxPacketSize = tr.readUINT32(); + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public int getRecipientChannelID() { + return recipientChannelID; + } + + public int getSenderChannelID() { + return senderChannelID; + } + + public int getInitialWindowSize() { + return initialWindowSize; + } + + public int getMaxPacketSize() { + return maxPacketSize; + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketChannelOpenFailure.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketChannelOpenFailure.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketFormatException; + +/** + * @author Christian Plattner + * @version $Id: PacketChannelOpenFailure.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketChannelOpenFailure { + + private final byte[] payload; + + public PacketChannelOpenFailure(int recipientChannelID, int reasonCode, String description, + String languageTag) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_OPEN_FAILURE); + tw.writeUINT32(recipientChannelID); + tw.writeUINT32(reasonCode); + tw.writeString(description); + tw.writeString(languageTag); + payload = tw.getBytes(); + } + + public PacketChannelOpenFailure(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_CHANNEL_OPEN_FAILURE) { + throw new IOException( + "This is not a SSH_MSG_CHANNEL_OPEN_FAILURE! (" + + packet_type + ")" + ); + } + + int recipientChannelID = tr.readUINT32(); + int reasonCode = tr.readUINT32(); + String description = tr.readString(); + String languageTag = tr.readString(); + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketChannelSuccess.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketChannelSuccess.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2006-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2.packets; + +/** + * @author Christian Plattner + * @version $Id: PacketChannelSuccess.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketChannelSuccess { + + private final byte[] payload; + + public PacketChannelSuccess(int recipientChannelID) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_SUCCESS); + tw.writeUINT32(recipientChannelID); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketChannelWindowAdjust.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketChannelWindowAdjust.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketFormatException; + +/** + * @author Christian Plattner + * @version $Id: PacketChannelWindowAdjust.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketChannelWindowAdjust { + + private final byte[] payload; + + public PacketChannelWindowAdjust(int recipientChannelID, int windowChange) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST); + tw.writeUINT32(recipientChannelID); + tw.writeUINT32(windowChange); + payload = tw.getBytes(); + } + + public PacketChannelWindowAdjust(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST) { + throw new IOException( + "This is not a SSH_MSG_CHANNEL_WINDOW_ADJUST! (" + + packet_type + ")" + ); + } + + int recipientChannelID = tr.readUINT32(); + int windowChange = tr.readUINT32(); + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketDisconnect.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketDisconnect.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketDisconnect.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketDisconnect { + + public enum Reason { + SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT, + SSH_DISCONNECT_PROTOCOL_ERROR, + SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + SSH_DISCONNECT_RESERVED, + SSH_DISCONNECT_MAC_ERROR, + SSH_DISCONNECT_COMPRESSION_ERROR, + SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, + SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED, + SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE, + SSH_DISCONNECT_CONNECTION_LOST, + SSH_DISCONNECT_BY_APPLICATION, + SSH_DISCONNECT_TOO_MANY_CONNECTIONS, + SSH_DISCONNECT_AUTH_CANCELLED_BY_USER, + SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, + SSH_DISCONNECT_ILLEGAL_USER_NAME + } + + private final byte[] payload; + + private final Reason reason; + + private final String message; + + public PacketDisconnect(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_DISCONNECT) { + throw new PacketTypeException(packet_type); + } + + reason = PacketDisconnect.Reason.values()[tr.readUINT32()]; + message = tr.readString(); + String lang = tr.readString(); + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public PacketDisconnect(Reason reason, String desc) { + this.reason = reason; + this.message = desc; + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_DISCONNECT); + tw.writeUINT32(reason.ordinal()); + tw.writeString(desc); + tw.writeString(""); + payload = tw.getBytes(); + } + + public Reason getReason() { + return reason; + } + + public String getMessage() { + return message; + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketGlobalCancelForwardRequest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketGlobalCancelForwardRequest.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +/** + * @author Christian Plattner + * @version $Id: PacketGlobalCancelForwardRequest.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketGlobalCancelForwardRequest { + + private final byte[] payload; + + public PacketGlobalCancelForwardRequest(boolean wantReply, String bindAddress, int bindPort) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_GLOBAL_REQUEST); + tw.writeString("cancel-tcpip-forward"); + tw.writeBoolean(wantReply); + tw.writeString(bindAddress); + tw.writeUINT32(bindPort); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketGlobalForwardRequest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketGlobalForwardRequest.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +/** + * @author Christian Plattner + * @version $Id: PacketGlobalForwardRequest.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketGlobalForwardRequest { + + private final byte[] payload; + + public PacketGlobalForwardRequest(boolean wantReply, String bindAddress, int bindPort) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_GLOBAL_REQUEST); + tw.writeString("tcpip-forward"); + tw.writeBoolean(wantReply); + tw.writeString(bindAddress); + tw.writeUINT32(bindPort); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketIgnore.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketIgnore.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketIgnore.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketIgnore { + private final byte[] payload; + + public PacketIgnore(byte[] data) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_IGNORE); + + if (data != null) { + tw.writeString(data, 0, data.length); + } + else { + tw.writeString(""); + } + + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketKexDHInit.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketKexDHInit.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; +import java.math.BigInteger; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketKexDHInit.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketKexDHInit { + + private final byte[] payload; + + private final BigInteger e; + + public PacketKexDHInit(BigInteger e) { + this.e = e; + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_KEXDH_INIT); + tw.writeMPInt(e); + payload = tw.getBytes(); + } + + public PacketKexDHInit(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_KEXDH_INIT) { + throw new PacketTypeException(packet_type); + } + + e = tr.readMPINT(); + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public BigInteger getE() { + return e; + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketKexDHReply.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketKexDHReply.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; +import java.math.BigInteger; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketKexDHReply.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketKexDHReply { + + private final byte[] payload; + + private final byte[] hostKey; + private final BigInteger f; + private final byte[] signature; + + public PacketKexDHReply(byte[] hostKey, BigInteger f, byte[] signature) { + this.hostKey = hostKey; + this.f = f; + this.signature = signature; + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_KEXDH_REPLY); + tw.writeString(hostKey, 0, hostKey.length); + tw.writeMPInt(f); + tw.writeString(signature, 0, signature.length); + payload = tw.getBytes(); + } + + public PacketKexDHReply(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_KEXDH_REPLY) { + throw new PacketTypeException(packet_type); + } + + hostKey = tr.readByteString(); + f = tr.readMPINT(); + signature = tr.readByteString(); + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public byte[] getPayload() { + return payload; + } + + public BigInteger getF() { + return f; + } + + public byte[] getHostKey() { + return hostKey; + } + + public byte[] getSignature() { + return signature; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketKexDhGexGroup.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketKexDhGexGroup.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; +import java.math.BigInteger; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketKexDhGexGroup.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketKexDhGexGroup { + + private final BigInteger p; + private final BigInteger g; + + public PacketKexDhGexGroup(byte payload[]) throws IOException { + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_KEX_DH_GEX_GROUP) { + throw new PacketTypeException(packet_type); + } + + p = tr.readMPINT(); + g = tr.readMPINT(); + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public BigInteger getG() { + return g; + } + + public BigInteger getP() { + return p; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketKexDhGexInit.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketKexDhGexInit.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.math.BigInteger; + +/** + * @author Christian Plattner + * @version $Id: PacketKexDhGexInit.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketKexDhGexInit { + + private final byte[] payload; + + public PacketKexDhGexInit(BigInteger e) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_KEX_DH_GEX_INIT); + tw.writeMPInt(e); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketKexDhGexReply.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketKexDhGexReply.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; +import java.math.BigInteger; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketKexDhGexReply.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketKexDhGexReply { + + private final byte[] hostKey; + private final BigInteger f; + private final byte[] signature; + + public PacketKexDhGexReply(byte payload[]) throws IOException { + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_KEX_DH_GEX_REPLY) { + throw new PacketTypeException(packet_type); + } + + hostKey = tr.readByteString(); + f = tr.readMPINT(); + signature = tr.readByteString(); + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public BigInteger getF() { + return f; + } + + public byte[] getHostKey() { + return hostKey; + } + + public byte[] getSignature() { + return signature; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketKexDhGexRequest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketKexDhGexRequest.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import ch.ethz.ssh2.DHGexParameters; + +/** + * @author Christian Plattner + * @version $Id: PacketKexDhGexRequest.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketKexDhGexRequest { + private final byte[] payload; + + public PacketKexDhGexRequest(DHGexParameters para) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_KEX_DH_GEX_REQUEST); + tw.writeUINT32(para.getMin_group_len()); + tw.writeUINT32(para.getPref_group_len()); + tw.writeUINT32(para.getMax_group_len()); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketKexDhGexRequestOld.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketKexDhGexRequestOld.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import ch.ethz.ssh2.DHGexParameters; + +/** + * @author Christian Plattner + * @version $Id: PacketKexDhGexRequestOld.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketKexDhGexRequestOld { + + private final byte[] payload; + + public PacketKexDhGexRequestOld(DHGexParameters para) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_KEX_DH_GEX_REQUEST_OLD); + tw.writeUINT32(para.getPref_group_len()); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketKexInit.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketKexInit.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; +import java.security.SecureRandom; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; +import ch.ethz.ssh2.crypto.CryptoWishList; +import ch.ethz.ssh2.transport.KexParameters; + +/** + * @author Christian Plattner + * @version $Id: PacketKexInit.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketKexInit { + private final byte[] payload; + + KexParameters kp = new KexParameters(); + + public PacketKexInit(CryptoWishList cwl, SecureRandom rnd) { + kp.cookie = new byte[16]; + rnd.nextBytes(kp.cookie); + kp.kex_algorithms = cwl.kexAlgorithms; + kp.server_host_key_algorithms = cwl.serverHostKeyAlgorithms; + kp.encryption_algorithms_client_to_server = cwl.c2s_enc_algos; + kp.encryption_algorithms_server_to_client = cwl.s2c_enc_algos; + kp.mac_algorithms_client_to_server = cwl.c2s_mac_algos; + kp.mac_algorithms_server_to_client = cwl.s2c_mac_algos; + kp.compression_algorithms_client_to_server = cwl.c2s_comp_algos; + kp.compression_algorithms_server_to_client = cwl.s2c_comp_algos; + kp.languages_client_to_server = new String[] {""}; + kp.languages_server_to_client = new String[] {""}; + kp.first_kex_packet_follows = false; + kp.reserved_field1 = 0; + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_KEXINIT); + tw.writeBytes(kp.cookie, 0, 16); + tw.writeNameList(kp.kex_algorithms); + tw.writeNameList(kp.server_host_key_algorithms); + tw.writeNameList(kp.encryption_algorithms_client_to_server); + tw.writeNameList(kp.encryption_algorithms_server_to_client); + tw.writeNameList(kp.mac_algorithms_client_to_server); + tw.writeNameList(kp.mac_algorithms_server_to_client); + tw.writeNameList(kp.compression_algorithms_client_to_server); + tw.writeNameList(kp.compression_algorithms_server_to_client); + tw.writeNameList(kp.languages_client_to_server); + tw.writeNameList(kp.languages_server_to_client); + tw.writeBoolean(kp.first_kex_packet_follows); + tw.writeUINT32(kp.reserved_field1); + payload = tw.getBytes(); + } + + public PacketKexInit(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_KEXINIT) { + throw new PacketTypeException(packet_type); + } + + kp.cookie = tr.readBytes(16); + kp.kex_algorithms = tr.readNameList(); + kp.server_host_key_algorithms = tr.readNameList(); + kp.encryption_algorithms_client_to_server = tr.readNameList(); + kp.encryption_algorithms_server_to_client = tr.readNameList(); + kp.mac_algorithms_client_to_server = tr.readNameList(); + kp.mac_algorithms_server_to_client = tr.readNameList(); + kp.compression_algorithms_client_to_server = tr.readNameList(); + kp.compression_algorithms_server_to_client = tr.readNameList(); + kp.languages_client_to_server = tr.readNameList(); + kp.languages_server_to_client = tr.readNameList(); + kp.first_kex_packet_follows = tr.readBoolean(); + kp.reserved_field1 = tr.readUINT32(); + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public byte[] getPayload() { + return payload; + } + + public KexParameters getKexParameters() { + return kp; + } + + public String[] getCompression_algorithms_client_to_server() { + return kp.compression_algorithms_client_to_server; + } + + public String[] getCompression_algorithms_server_to_client() { + return kp.compression_algorithms_server_to_client; + } + + public byte[] getCookie() { + return kp.cookie; + } + + public String[] getEncryption_algorithms_client_to_server() { + return kp.encryption_algorithms_client_to_server; + } + + public String[] getEncryption_algorithms_server_to_client() { + return kp.encryption_algorithms_server_to_client; + } + + public boolean isFirst_kex_packet_follows() { + return kp.first_kex_packet_follows; + } + + public String[] getKex_algorithms() { + return kp.kex_algorithms; + } + + public String[] getLanguages_client_to_server() { + return kp.languages_client_to_server; + } + + public String[] getLanguages_server_to_client() { + return kp.languages_server_to_client; + } + + public String[] getMac_algorithms_client_to_server() { + return kp.mac_algorithms_client_to_server; + } + + public String[] getMac_algorithms_server_to_client() { + return kp.mac_algorithms_server_to_client; + } + + public int getReserved_field1() { + return kp.reserved_field1; + } + + public String[] getServer_host_key_algorithms() { + return kp.server_host_key_algorithms; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketNewKeys.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketNewKeys.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketNewKeys.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketNewKeys { + private final byte[] payload; + + public PacketNewKeys() { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_NEWKEYS); + payload = tw.getBytes(); + } + + public PacketNewKeys(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_NEWKEYS) { + throw new PacketTypeException(packet_type); + } + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketOpenDirectTCPIPChannel.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketOpenDirectTCPIPChannel.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +/** + * @author Christian Plattner + * @version $Id: PacketOpenDirectTCPIPChannel.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketOpenDirectTCPIPChannel { + private final byte[] payload; + + public PacketOpenDirectTCPIPChannel(int channelID, int initialWindowSize, int maxPacketSize, + String host_to_connect, int port_to_connect, String originator_IP_address, + int originator_port) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_OPEN); + tw.writeString("direct-tcpip"); + tw.writeUINT32(channelID); + tw.writeUINT32(initialWindowSize); + tw.writeUINT32(maxPacketSize); + tw.writeString(host_to_connect); + tw.writeUINT32(port_to_connect); + tw.writeString(originator_IP_address); + tw.writeUINT32(originator_port); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketOpenSessionChannel.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketOpenSessionChannel.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketOpenSessionChannel.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketOpenSessionChannel { + private final byte[] payload; + + public PacketOpenSessionChannel(int channelID, int initialWindowSize, int maxPacketSize) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_OPEN); + tw.writeString("session"); + tw.writeUINT32(channelID); + tw.writeUINT32(initialWindowSize); + tw.writeUINT32(maxPacketSize); + payload = tw.getBytes(); + } + + public PacketOpenSessionChannel(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_CHANNEL_OPEN) { + throw new PacketTypeException(packet_type); + } + + int channelID = tr.readUINT32(); + int initialWindowSize = tr.readUINT32(); + int maxPacketSize = tr.readUINT32(); + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketServiceAccept.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketServiceAccept.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketServiceAccept.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketServiceAccept { + private final byte[] payload; + + public PacketServiceAccept(String serviceName) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_SERVICE_ACCEPT); + tw.writeString(serviceName); + payload = tw.getBytes(); + } + + public PacketServiceAccept(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_SERVICE_ACCEPT) { + throw new PacketTypeException(packet_type); + } + + if (tr.remain() != 0) { + String serviceName = tr.readString(); + } + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketServiceRequest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketServiceRequest.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketServiceRequest.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketServiceRequest { + private final byte[] payload; + + private final String serviceName; + + public PacketServiceRequest(String serviceName) { + this.serviceName = serviceName; + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_SERVICE_REQUEST); + tw.writeString(serviceName); + payload = tw.getBytes(); + } + + public String getServiceName() { + return serviceName; + } + + public PacketServiceRequest(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_SERVICE_REQUEST) { + throw new PacketTypeException(packet_type); + } + + serviceName = tr.readString(); + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketSessionExecCommand.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketSessionExecCommand.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +/** + * @author Christian Plattner + * @version $Id: PacketSessionExecCommand.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketSessionExecCommand { + private final byte[] payload; + + public PacketSessionExecCommand(int recipientChannelID, boolean wantReply, String command, String charsetName) throws UnsupportedEncodingException { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); + tw.writeUINT32(recipientChannelID); + tw.writeString("exec"); + tw.writeBoolean(wantReply); + tw.writeString(command, charsetName); + payload = tw.getBytes(); + } + + public byte[] getPayload() throws IOException { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketSessionPtyRequest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketSessionPtyRequest.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +/** + * @author Christian Plattner + * @version $Id: PacketSessionPtyRequest.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketSessionPtyRequest { + private final byte[] payload; + + public PacketSessionPtyRequest(int recipientChannelID, boolean wantReply, String term, + int character_width, int character_height, int pixel_width, int pixel_height, + byte[] terminal_modes) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); + tw.writeUINT32(recipientChannelID); + tw.writeString("pty-req"); + tw.writeBoolean(wantReply); + tw.writeString(term); + tw.writeUINT32(character_width); + tw.writeUINT32(character_height); + tw.writeUINT32(pixel_width); + tw.writeUINT32(pixel_height); + tw.writeString(terminal_modes, 0, terminal_modes.length); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketSessionStartShell.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketSessionStartShell.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +/** + * @author Christian Plattner + * @version $Id: PacketSessionStartShell.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketSessionStartShell { + private final byte[] payload; + + public PacketSessionStartShell(int recipientChannelID, boolean wantReply) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); + tw.writeUINT32(recipientChannelID); + tw.writeString("shell"); + tw.writeBoolean(wantReply); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketSessionSubsystemRequest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketSessionSubsystemRequest.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +/** + * @author Christian Plattner + * @version $Id: PacketSessionSubsystemRequest.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketSessionSubsystemRequest { + private final byte[] payload; + + public PacketSessionSubsystemRequest(int recipientChannelID, boolean wantReply, String subsystem) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); + tw.writeUINT32(recipientChannelID); + tw.writeString("subsystem"); + tw.writeBoolean(wantReply); + tw.writeString(subsystem); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketSessionX11Request.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketSessionX11Request.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +/** + * @author Christian Plattner + * @version $Id: PacketSessionX11Request.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketSessionX11Request { + private final byte[] payload; + + public PacketSessionX11Request(int recipientChannelID, boolean wantReply, boolean singleConnection, + String x11AuthenticationProtocol, String x11AuthenticationCookie, int x11ScreenNumber) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); + tw.writeUINT32(recipientChannelID); + tw.writeString("x11-req"); + tw.writeBoolean(wantReply); + tw.writeBoolean(singleConnection); + tw.writeString(x11AuthenticationProtocol); + tw.writeString(x11AuthenticationCookie); + tw.writeUINT32(x11ScreenNumber); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketUserauthBanner.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketUserauthBanner.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketUserauthBanner.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketUserauthBanner { + private final byte[] payload; + + private final String message; + + public PacketUserauthBanner(String message) { + this.message = message; + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_BANNER); + tw.writeString(message); + tw.writeString(""); + payload = tw.getBytes(); + } + + public String getBanner() { + return message; + } + + public PacketUserauthBanner(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_USERAUTH_BANNER) { + throw new PacketTypeException(packet_type); + } + + message = tr.readString("UTF-8"); + String language = tr.readString(); + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketUserauthFailure.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketUserauthFailure.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2006-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketUserauthFailure.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketUserauthFailure { + + private final byte[] payload; + + private Set authThatCanContinue; + + private boolean partialSuccess; + + public PacketUserauthFailure(Set authThatCanContinue, boolean partialSuccess) { + this.authThatCanContinue = authThatCanContinue; + this.partialSuccess = partialSuccess; + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_FAILURE); + tw.writeNameList(authThatCanContinue.toArray(new String[authThatCanContinue.size()])); + tw.writeBoolean(partialSuccess); + payload = tw.getBytes(); + } + + public PacketUserauthFailure(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_USERAUTH_FAILURE) { + throw new PacketTypeException(packet_type); + } + + authThatCanContinue = new HashSet(Arrays.asList(tr.readNameList())); + partialSuccess = tr.readBoolean(); + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public byte[] getPayload() { + return payload; + } + + public Set getAuthThatCanContinue() { + return authThatCanContinue; + } + + public boolean isPartialSuccess() { + return partialSuccess; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketUserauthInfoRequest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketUserauthInfoRequest.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketUserauthInfoRequest.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketUserauthInfoRequest { + + private final String name; + private final String instruction; + private final String languageTag; + private final int numPrompts; + + private final String prompt[]; + private final boolean echo[]; + + public PacketUserauthInfoRequest(byte payload[]) throws IOException { + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_USERAUTH_INFO_REQUEST) { + throw new PacketTypeException(packet_type); + } + + name = tr.readString(); + instruction = tr.readString(); + languageTag = tr.readString(); + numPrompts = tr.readUINT32(); + prompt = new String[numPrompts]; + echo = new boolean[numPrompts]; + + for (int i = 0; i < numPrompts; i++) { + prompt[i] = tr.readString(); + echo[i] = tr.readBoolean(); + } + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public boolean[] getEcho() { + return echo; + } + + public String getInstruction() { + return instruction; + } + + public String getLanguageTag() { + return languageTag; + } + + public String getName() { + return name; + } + + public int getNumPrompts() { + return numPrompts; + } + + public String[] getPrompt() { + return prompt; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketUserauthInfoResponse.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketUserauthInfoResponse.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +/** + * @author Christian Plattner + * @version $Id: PacketUserauthInfoResponse.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketUserauthInfoResponse { + private final byte[] payload; + + public PacketUserauthInfoResponse(String[] responses) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_INFO_RESPONSE); + tw.writeUINT32(responses.length); + + for (String response : responses) { + tw.writeString(response); + } + + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketUserauthRequestInteractive.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketUserauthRequestInteractive.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +/** + * @author Christian Plattner + * @version $Id: PacketUserauthRequestInteractive.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketUserauthRequestInteractive { + + private final byte[] payload; + + public PacketUserauthRequestInteractive(String serviceName, String user, String[] submethods) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(user); + tw.writeString(serviceName); + tw.writeString("keyboard-interactive"); + tw.writeString(""); // draft-ietf-secsh-newmodes-04.txt says that + // the language tag should be empty. + tw.writeNameList(null == submethods ? new String[] {} : submethods); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketUserauthRequestNone.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketUserauthRequestNone.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketUserauthRequestNone.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketUserauthRequestNone { + + private final byte[] payload; + + public PacketUserauthRequestNone(String serviceName, String user) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(user); + tw.writeString(serviceName); + tw.writeString("none"); + payload = tw.getBytes(); + } + + public PacketUserauthRequestNone(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_USERAUTH_REQUEST) { + throw new PacketTypeException(packet_type); + } + + String userName = tr.readString(); + String serviceName = tr.readString(); + String method = tr.readString(); + + if (!method.equals("none")) { + throw new IOException(String.format("Unexpected method %s", method)); + } + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketUserauthRequestPassword.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketUserauthRequestPassword.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketUserauthRequestPassword.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketUserauthRequestPassword { + + private final byte[] payload; + + public PacketUserauthRequestPassword(String serviceName, String user, String pass) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(user); + tw.writeString(serviceName); + tw.writeString("password"); + tw.writeBoolean(false); + tw.writeString(pass); + payload = tw.getBytes(); + } + + public PacketUserauthRequestPassword(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_USERAUTH_REQUEST) { + throw new PacketTypeException(packet_type); + } + + String userName = tr.readString(); + String serviceName = tr.readString(); + String method = tr.readString(); + + if (!method.equals("password")) { + throw new IOException(String.format("Unexpected method %s", method)); + } + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketUserauthRequestPublicKey.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketUserauthRequestPublicKey.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketUserauthRequestPublicKey.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketUserauthRequestPublicKey { + + private final byte[] payload; + + public PacketUserauthRequestPublicKey(String serviceName, String user, + String pkAlgorithmName, byte[] pk, byte[] sig) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(user); + tw.writeString(serviceName); + tw.writeString("publickey"); + tw.writeBoolean(true); + tw.writeString(pkAlgorithmName); + tw.writeString(pk, 0, pk.length); + tw.writeString(sig, 0, sig.length); + payload = tw.getBytes(); + } + + public PacketUserauthRequestPublicKey(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_USERAUTH_REQUEST) { + throw new PacketTypeException(packet_type); + } + + String userName = tr.readString(); + String serviceName = tr.readString(); + String method = tr.readString(); + + if (!method.equals("publickey")) { + throw new IOException(String.format("Unexpected method %s", method)); + } + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketUserauthSuccess.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketUserauthSuccess.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2006-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketUserauthSuccess.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketUserauthSuccess { + + private final byte[] payload; + + public PacketUserauthSuccess() { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_SUCCESS); + payload = tw.getBytes(); + } + + public PacketUserauthSuccess(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_USERAUTH_SUCCESS) { + throw new PacketTypeException(packet_type); + } + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/PacketWindowChange.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/PacketWindowChange.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,30 @@ +package ch.ethz.ssh2.packets; + +/** + * Indicates that that size of the terminal (window) size has changed on the client side. + *

+ * See section 6.7 of RFC 4254. + * + * @author Kohsuke Kawaguchi + */ +public final class PacketWindowChange { + private final byte[] payload; + + public PacketWindowChange(int recipientChannelID, + int character_width, int character_height, int pixel_width, int pixel_height) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); + tw.writeUINT32(recipientChannelID); + tw.writeString("window-change"); + tw.writeBoolean(false); + tw.writeUINT32(character_width); + tw.writeUINT32(character_height); + tw.writeUINT32(pixel_width); + tw.writeUINT32(pixel_height); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/Packets.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/Packets.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +/** + * Packets. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public final class Packets { + public static final int SSH_MSG_DISCONNECT = 1; + public static final int SSH_MSG_IGNORE = 2; + public static final int SSH_MSG_UNIMPLEMENTED = 3; + public static final int SSH_MSG_DEBUG = 4; + public static final int SSH_MSG_SERVICE_REQUEST = 5; + public static final int SSH_MSG_SERVICE_ACCEPT = 6; + + public static final int SSH_MSG_KEXINIT = 20; + public static final int SSH_MSG_NEWKEYS = 21; + + public static final int SSH_MSG_KEXDH_INIT = 30; + public static final int SSH_MSG_KEXDH_REPLY = 31; + + public static final int SSH_MSG_KEX_DH_GEX_REQUEST_OLD = 30; + public static final int SSH_MSG_KEX_DH_GEX_REQUEST = 34; + public static final int SSH_MSG_KEX_DH_GEX_GROUP = 31; + public static final int SSH_MSG_KEX_DH_GEX_INIT = 32; + public static final int SSH_MSG_KEX_DH_GEX_REPLY = 33; + + public static final int SSH_MSG_USERAUTH_REQUEST = 50; + public static final int SSH_MSG_USERAUTH_FAILURE = 51; + public static final int SSH_MSG_USERAUTH_SUCCESS = 52; + public static final int SSH_MSG_USERAUTH_BANNER = 53; + public static final int SSH_MSG_USERAUTH_INFO_REQUEST = 60; + public static final int SSH_MSG_USERAUTH_INFO_RESPONSE = 61; + + public static final int SSH_MSG_GLOBAL_REQUEST = 80; + public static final int SSH_MSG_REQUEST_SUCCESS = 81; + public static final int SSH_MSG_REQUEST_FAILURE = 82; + + public static final int SSH_MSG_CHANNEL_OPEN = 90; + public static final int SSH_MSG_CHANNEL_OPEN_CONFIRMATION = 91; + public static final int SSH_MSG_CHANNEL_OPEN_FAILURE = 92; + public static final int SSH_MSG_CHANNEL_WINDOW_ADJUST = 93; + public static final int SSH_MSG_CHANNEL_DATA = 94; + public static final int SSH_MSG_CHANNEL_EXTENDED_DATA = 95; + public static final int SSH_MSG_CHANNEL_EOF = 96; + public static final int SSH_MSG_CHANNEL_CLOSE = 97; + public static final int SSH_MSG_CHANNEL_REQUEST = 98; + public static final int SSH_MSG_CHANNEL_SUCCESS = 99; + public static final int SSH_MSG_CHANNEL_FAILURE = 100; + + public static final int SSH_EXTENDED_DATA_STDERR = 1; + + public static final int SSH_OPEN_ADMINISTRATIVELY_PROHIBITED = 1; + public static final int SSH_OPEN_CONNECT_FAILED = 2; + public static final int SSH_OPEN_UNKNOWN_CHANNEL_TYPE = 3; + public static final int SSH_OPEN_RESOURCE_SHORTAGE = 4; + + private static final String[] reverseNames = new String[101]; + + static { + reverseNames[1] = "SSH_MSG_DISCONNECT"; + reverseNames[2] = "SSH_MSG_IGNORE"; + reverseNames[3] = "SSH_MSG_UNIMPLEMENTED"; + reverseNames[4] = "SSH_MSG_DEBUG"; + reverseNames[5] = "SSH_MSG_SERVICE_REQUEST"; + reverseNames[6] = "SSH_MSG_SERVICE_ACCEPT"; + reverseNames[20] = "SSH_MSG_KEXINIT"; + reverseNames[21] = "SSH_MSG_NEWKEYS"; + reverseNames[30] = "SSH_MSG_KEXDH_INIT"; + reverseNames[31] = "SSH_MSG_KEXDH_REPLY/SSH_MSG_KEX_DH_GEX_GROUP"; + reverseNames[32] = "SSH_MSG_KEX_DH_GEX_INIT"; + reverseNames[33] = "SSH_MSG_KEX_DH_GEX_REPLY"; + reverseNames[34] = "SSH_MSG_KEX_DH_GEX_REQUEST"; + reverseNames[50] = "SSH_MSG_USERAUTH_REQUEST"; + reverseNames[51] = "SSH_MSG_USERAUTH_FAILURE"; + reverseNames[52] = "SSH_MSG_USERAUTH_SUCCESS"; + reverseNames[53] = "SSH_MSG_USERAUTH_BANNER"; + reverseNames[60] = "SSH_MSG_USERAUTH_INFO_REQUEST"; + reverseNames[61] = "SSH_MSG_USERAUTH_INFO_RESPONSE"; + reverseNames[80] = "SSH_MSG_GLOBAL_REQUEST"; + reverseNames[81] = "SSH_MSG_REQUEST_SUCCESS"; + reverseNames[82] = "SSH_MSG_REQUEST_FAILURE"; + reverseNames[90] = "SSH_MSG_CHANNEL_OPEN"; + reverseNames[91] = "SSH_MSG_CHANNEL_OPEN_CONFIRMATION"; + reverseNames[92] = "SSH_MSG_CHANNEL_OPEN_FAILURE"; + reverseNames[93] = "SSH_MSG_CHANNEL_WINDOW_ADJUST"; + reverseNames[94] = "SSH_MSG_CHANNEL_DATA"; + reverseNames[95] = "SSH_MSG_CHANNEL_EXTENDED_DATA"; + reverseNames[96] = "SSH_MSG_CHANNEL_EOF"; + reverseNames[97] = "SSH_MSG_CHANNEL_CLOSE"; + reverseNames[98] = "SSH_MSG_CHANNEL_REQUEST"; + reverseNames[99] = "SSH_MSG_CHANNEL_SUCCESS"; + reverseNames[100] = "SSH_MSG_CHANNEL_FAILURE"; + } + + public static final String getMessageName(int type) { + String res = null; + + if ((type >= 0) && (type < reverseNames.length)) { + res = reverseNames[type]; + } + + return (res == null) ? ("UNKNOWN MSG " + type) : res; + } + + // public static final void debug(String tag, byte[] msg) + // { + // System.err.println(tag + " Type: " + msg[0] + ", LEN: " + msg.length); + // + // for (int i = 0; i < msg.length; i++) + // { + // if (((msg[i] >= 'a') && (msg[i] <= 'z')) || ((msg[i] >= 'A') && (msg[i] <= 'Z')) + // || ((msg[i] >= '0') && (msg[i] <= '9')) || (msg[i] == ' ')) + // System.err.print((char) msg[i]); + // else + // System.err.print("."); + // } + // System.err.println(); + // System.err.flush(); + // } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/TypesReader.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/TypesReader.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; +import java.math.BigInteger; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.util.StringEncoder; + +/** + * @author Christian Plattner + * @version $Id: TypesReader.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class TypesReader { + byte[] arr; + int pos = 0; + int max = 0; + + public TypesReader(byte[] arr) { + this.arr = arr; + pos = 0; + max = arr.length; + } + + public TypesReader(byte[] arr, int off) { + this.arr = arr; + this.pos = off; + this.max = arr.length; + + if ((pos < 0) || (pos > arr.length)) { + throw new IllegalArgumentException("Illegal offset."); + } + } + + public TypesReader(byte[] arr, int off, int len) { + this.arr = arr; + this.pos = off; + this.max = off + len; + + if ((pos < 0) || (pos > arr.length)) { + throw new IllegalArgumentException("Illegal offset."); + } + + if ((max < 0) || (max > arr.length)) { + throw new IllegalArgumentException("Illegal length."); + } + } + + public int readByte() throws IOException { + if (pos >= max) { + throw new PacketFormatException("Packet too short."); + } + + return (arr[pos++] & 0xff); + } + + public byte[] readBytes(int len) throws IOException { + if ((pos + len) > max) { + throw new PacketFormatException("Packet too short."); + } + + byte[] res = new byte[len]; + System.arraycopy(arr, pos, res, 0, len); + pos += len; + return res; + } + + public void readBytes(byte[] dst, int off, int len) throws IOException { + if ((pos + len) > max) { + throw new PacketFormatException("Packet too short."); + } + + System.arraycopy(arr, pos, dst, off, len); + pos += len; + } + + public boolean readBoolean() throws IOException { + if (pos >= max) { + throw new PacketFormatException("Packet too short."); + } + + return (arr[pos++] != 0); + } + + public int readUINT32() throws IOException { + if ((pos + 4) > max) { + throw new PacketFormatException("Packet too short."); + } + + return ((arr[pos++] & 0xff) << 24) | ((arr[pos++] & 0xff) << 16) | ((arr[pos++] & 0xff) << 8) + | (arr[pos++] & 0xff); + } + + public long readUINT64() throws IOException { + if ((pos + 8) > max) { + throw new PacketFormatException("Packet too short."); + } + + long high = ((arr[pos++] & 0xff) << 24) | ((arr[pos++] & 0xff) << 16) | ((arr[pos++] & 0xff) << 8) + | (arr[pos++] & 0xff); /* sign extension may take place - will be shifted away =) */ + long low = ((arr[pos++] & 0xff) << 24) | ((arr[pos++] & 0xff) << 16) | ((arr[pos++] & 0xff) << 8) + | (arr[pos++] & 0xff); /* sign extension may take place - handle below */ + return (high << 32) | (low & 0xffffffffl); /* see Java language spec (15.22.1, 5.6.2) */ + } + + public BigInteger readMPINT() throws IOException { + BigInteger b; + byte raw[] = readByteString(); + + if (raw.length == 0) { + b = BigInteger.ZERO; + } + else { + b = new BigInteger(raw); + } + + return b; + } + + public byte[] readByteString() throws IOException { + int len = readUINT32(); + + if ((len + pos) > max) { + throw new PacketFormatException("Malformed SSH byte string."); + } + + byte[] res = new byte[len]; + System.arraycopy(arr, pos, res, 0, len); + pos += len; + return res; + } + + public String readString(String charsetName) throws IOException { + int len = readUINT32(); + + if ((len + pos) > max) { + throw new PacketFormatException("Malformed SSH string."); + } + + String res = (charsetName == null) ? new String(arr, pos, len) : new String(arr, pos, len, charsetName); + pos += len; + return res; + } + + public String readString() throws IOException { + int len = readUINT32(); + + if ((len + pos) > max) { + throw new PacketFormatException("Malformed SSH string."); + } + + String res = StringEncoder.GetString(arr, pos, len); + pos += len; + return res; + } + + public String[] readNameList() throws IOException { + return readString().split(","); + } + + public int remain() { + return max - pos; + } + +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/packets/TypesWriter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/packets/TypesWriter.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; + +import ch.ethz.ssh2.util.StringEncoder; + +/** + * @author Christian Plattner + * @version $Id: TypesWriter.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class TypesWriter { + byte arr[]; + int pos; + + public TypesWriter() { + arr = new byte[256]; + pos = 0; + } + + private void resize(int len) { + byte new_arr[] = new byte[len]; + System.arraycopy(arr, 0, new_arr, 0, arr.length); + arr = new_arr; + } + + public int length() { + return pos; + } + + public byte[] getBytes() { + byte[] dst = new byte[pos]; + System.arraycopy(arr, 0, dst, 0, pos); + return dst; + } + + public void getBytes(byte dst[]) { + System.arraycopy(arr, 0, dst, 0, pos); + } + + public void writeUINT32(int val, int off) { + if ((off + 4) > arr.length) { + resize(off + 32); + } + + arr[off++] = (byte)(val >> 24); + arr[off++] = (byte)(val >> 16); + arr[off++] = (byte)(val >> 8); + arr[off++] = (byte) val; + } + + public void writeUINT32(int val) { + writeUINT32(val, pos); + pos += 4; + } + + public void writeUINT64(long val) { + if ((pos + 8) > arr.length) { + resize(arr.length + 32); + } + + arr[pos++] = (byte)(val >> 56); + arr[pos++] = (byte)(val >> 48); + arr[pos++] = (byte)(val >> 40); + arr[pos++] = (byte)(val >> 32); + arr[pos++] = (byte)(val >> 24); + arr[pos++] = (byte)(val >> 16); + arr[pos++] = (byte)(val >> 8); + arr[pos++] = (byte) val; + } + + public void writeBoolean(boolean v) { + if ((pos + 1) > arr.length) { + resize(arr.length + 32); + } + + arr[pos++] = v ? (byte) 1 : (byte) 0; + } + + public void writeByte(int v, int off) { + if ((off + 1) > arr.length) { + resize(off + 32); + } + + arr[off] = (byte) v; + } + + public void writeByte(int v) { + writeByte(v, pos); + pos++; + } + + public void writeMPInt(BigInteger b) { + byte raw[] = b.toByteArray(); + + if ((raw.length == 1) && (raw[0] == 0)) { + writeUINT32(0); /* String with zero bytes of data */ + } + else { + writeString(raw, 0, raw.length); + } + } + + public void writeBytes(byte[] buff) { + writeBytes(buff, 0, buff.length); + } + + public void writeBytes(byte[] buff, int off, int len) { + if ((pos + len) > arr.length) { + resize(arr.length + len + 32); + } + + System.arraycopy(buff, off, arr, pos, len); + pos += len; + } + + public void writeString(byte[] buff, int off, int len) { + writeUINT32(len); + writeBytes(buff, off, len); + } + + public void writeString(String v) { + byte[] b = StringEncoder.GetBytes(v); + writeUINT32(b.length); + writeBytes(b, 0, b.length); + } + + public void writeString(String v, String charsetName) throws UnsupportedEncodingException { + byte[] b = (charsetName == null) ? StringEncoder.GetBytes(v) : v.getBytes(charsetName); + writeUINT32(b.length); + writeBytes(b, 0, b.length); + } + + public void writeNameList(String v[]) { + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < v.length; i++) { + if (i > 0) { + sb.append(','); + } + + sb.append(v[i]); + } + + writeString(sb.toString()); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/server/ServerConnectionState.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/server/ServerConnectionState.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2006-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.server; + +import java.net.Socket; +import java.security.SecureRandom; + +import ch.ethz.ssh2.ServerAuthenticationCallback; +import ch.ethz.ssh2.ServerConnection; +import ch.ethz.ssh2.ServerConnectionCallback; +import ch.ethz.ssh2.auth.ServerAuthenticationManager; +import ch.ethz.ssh2.channel.ChannelManager; +import ch.ethz.ssh2.crypto.CryptoWishList; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.RSAPrivateKey; +import ch.ethz.ssh2.transport.ClientServerHello; +import ch.ethz.ssh2.transport.ServerTransportManager; + +public class ServerConnectionState { + public ServerConnection conn; + + public SecureRandom generator = new SecureRandom(); + + public String softwareversion; + + //public String auth_banner = null; + public ServerConnectionCallback cb_conn; + public ServerAuthenticationCallback cb_auth; + + /* Settings for the next key exchange */ + public CryptoWishList next_cryptoWishList = CryptoWishList.forServer(); + public KeyPair next_dsa_key; + public KeyPair next_ec_key; + public KeyPair next_rsa_key; + + public Socket s; + + public ClientServerHello csh; + public ServerTransportManager tm; + public ServerAuthenticationManager am; + public ChannelManager cm; + + public boolean flag_auth_serviceRequested = false; + public boolean flag_auth_completed = false; + + public ServerConnectionState(ServerConnection conn) { + this.conn = conn; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/sftp/AceFlags.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/sftp/AceFlags.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,17 @@ +package ch.ethz.ssh2.sftp; + +/** + * @version $Id: AceFlags.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public final class AceFlags { + private AceFlags() { + } + + public static final int ACE4_FILE_INHERIT_ACE = 0x00000001; + public static final int ACE4_DIRECTORY_INHERIT_ACE = 0x00000002; + public static final int ACE4_NO_PROPAGATE_INHERIT_ACE = 0x00000004; + public static final int ACE4_INHERIT_ONLY_ACE = 0x00000008; + public static final int ACE4_SUCCESSFUL_ACCESS_ACE_FLAG = 0x00000010; + public static final int ACE4_FAILED_ACCESS_ACE_FLAG = 0x00000020; + public static final int ACE4_IDENTIFIER_GROUP = 0x00000040; +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/sftp/AceMask.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/sftp/AceMask.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,28 @@ +package ch.ethz.ssh2.sftp; + +/** + * @version $Id: AceMask.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public final class AceMask { + private AceMask() { + } + + public static final int ACE4_READ_DATA = 0x00000001; + public static final int ACE4_LIST_DIRECTORY = 0x00000001; + public static final int ACE4_WRITE_DATA = 0x00000002; + public static final int ACE4_ADD_FILE = 0x00000002; + public static final int ACE4_APPEND_DATA = 0x00000004; + public static final int ACE4_ADD_SUBDIRECTORY = 0x00000004; + public static final int ACE4_READ_NAMED_ATTRS = 0x00000008; + public static final int ACE4_WRITE_NAMED_ATTRS = 0x00000010; + public static final int ACE4_EXECUTE = 0x00000020; + public static final int ACE4_DELETE_CHILD = 0x00000040; + public static final int ACE4_READ_ATTRIBUTES = 0x00000080; + public static final int ACE4_WRITE_ATTRIBUTES = 0x00000100; + public static final int ACE4_DELETE = 0x00010000; + public static final int ACE4_READ_ACL = 0x00020000; + public static final int ACE4_WRITE_ACL = 0x00040000; + public static final int ACE4_WRITE_OWNER = 0x00080000; + public static final int ACE4_SYNCHRONIZE = 0x00100000; + +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/sftp/AceType.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/sftp/AceType.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,16 @@ +package ch.ethz.ssh2.sftp; + +/** + * @version $Id: AceType.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public final class AceType { + + private AceType() { + } + + private static final int ACE4_ACCESS_ALLOWED_ACE_TYPE = 0x00000000; + private static final int ACE4_ACCESS_DENIED_ACE_TYPE = 0x00000001; + private static final int ACE4_SYSTEM_AUDIT_ACE_TYPE = 0x00000002; + private static final int ACE4_SYSTEM_ALARM_ACE_TYPE = 0x00000003; + +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/sftp/AclFlags.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/sftp/AclFlags.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,45 @@ +package ch.ethz.ssh2.sftp; + +/** + * @version $Id: AclFlags.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public final class AclFlags { + + /** + * If INCLUDED is set during a setstat operation, then the client + * intends to modify the ALLOWED/DENIED entries of the ACL. + * Otherwise, the client intends for these entries to be + * preserved. + */ + public static final int SFX_ACL_CONTROL_INCLUDED = 0x00000001; + /** + * If the PRESENT bit is not set, then the client wishes to remove + * control entries. If the server doesn't support separate + * control and audit information, the client MUST not clear this + * bit without also clearing the AUDIT_ALARM_PRESENT bit. + */ + public static final int SFX_ACL_CONTROL_PRESENT = 0x00000002; + /** + * If INHERITED is set, then ALLOW/DENY ACEs MAY be inherited from + * the parent directory. If it is off, then they MUST not be + * INHERITED. If the server does not support controlling + * inheritance, then the client MUST clear this bit; in this case + * the inheritance properties of the server are undefined. + */ + public static final int SFX_ACL_CONTROL_INHERITED = 0x00000004; + /** + * If INCLUDE is set during a setstat operation, then the client + * intends to modify the AUDIT/ALARM entries of the ACL. + * Otherwise, the client intends for these entries to be + * preserved. + */ + public static final int SFX_ACL_AUDIT_ALARM_INCLUDED = 0x00000010; + /** + * If INHERITED is set, then AUDIT/ALARM ACEs MAY be inherited + * from the parent directory. If it is off, then they MUST not be + * INHERITED. If the server does not support controlling + * inheritance, then the client MUST clear this bit; in this case + * the inheritance properties of the server are undefined. + */ + public static final int SFX_ACL_AUDIT_ALARM_INHERITED = 0x00000020; +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/sftp/AttrTextHints.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/sftp/AttrTextHints.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.sftp; + +/** + * + * Values for the 'text-hint' field in the SFTP ATTRS data type. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + * + */ +public class AttrTextHints { + /** + * The server knows the file is a text file, and should be opened + * using the SSH_FXF_ACCESS_TEXT_MODE flag. + */ + public static final int SSH_FILEXFER_ATTR_KNOWN_TEXT = 0x00; + + /** + * The server has applied a heuristic or other mechanism and + * believes that the file should be opened with the + * SSH_FXF_ACCESS_TEXT_MODE flag. + */ + public static final int SSH_FILEXFER_ATTR_GUESSED_TEXT = 0x01; + + /** + * The server knows the file has binary content. + */ + public static final int SSH_FILEXFER_ATTR_KNOWN_BINARY = 0x02; + + /** + * The server has applied a heuristic or other mechanism and + * believes has binary content, and should not be opened with the + * SSH_FXF_ACCESS_TEXT_MODE flag. + */ + public static final int SSH_FILEXFER_ATTR_GUESSED_BINARY = 0x03; +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/sftp/AttribBits.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/sftp/AttribBits.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.sftp; + +/** + * SFTP Attribute Bits for the "attrib-bits" and "attrib-bits-valid" fields + * of the SFTP ATTR data type. + *

+ * Yes, these are the "attrib-bits", even though they have "_FLAGS_" in + * their name. Don't ask - I did not invent it. + *

+ * "These fields, taken together, reflect various attributes of the file + * or directory, on the server. Bits not set in 'attrib-bits-valid' MUST be + * ignored in the 'attrib-bits' field. This allows both the server and the + * client to communicate only the bits it knows about without inadvertently + * twiddling bits they don't understand." + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public final class AttribBits { + + private AttribBits() { + } + + /** + * Advisory, read-only bit. This bit is not part of the access + * control information on the file, but is rather an advisory field + * indicating that the file should not be written. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_READONLY = 0x00000001; + + /** + * The file is part of the operating system. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_SYSTEM = 0x00000002; + + /** + * File SHOULD NOT be shown to user unless specifically requested. + * For example, most UNIX systems SHOULD set this bit if the filename + * begins with a 'period'. This bit may be read-only (see section 5.4 of + * the SFTP standard draft). Most UNIX systems will not allow this to be + * changed. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_HIDDEN = 0x00000004; + + /** + * This attribute applies only to directories. This attribute is + * always read-only, and cannot be modified. This attribute means + * that files and directory names in this directory should be compared + * without regard to case. + *

+ * It is recommended that where possible, the server's filesystem be + * allowed to do comparisons. For example, if a client wished to prompt + * a user before overwriting a file, it should not compare the new name + * with the previously retrieved list of names in the directory. Rather, + * it should first try to create the new file by specifying + * SSH_FXF_CREATE_NEW flag. Then, if this fails and returns + * SSH_FX_FILE_ALREADY_EXISTS, it should prompt the user and then retry + * the create specifying SSH_FXF_CREATE_TRUNCATE. + *

+ * Unless otherwise specified, filenames are assumed to be case sensitive. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_CASE_INSENSITIVE = 0x00000008; + + /** + * The file should be included in backup / archive operations. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_ARCHIVE = 0x00000010; + + /** + * The file is stored on disk using file-system level transparent + * encryption. This flag does not affect the file data on the wire + * (for either READ or WRITE requests.) + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_ENCRYPTED = 0x00000020; + + /** + * The file is stored on disk using file-system level transparent + * compression. This flag does not affect the file data on the wire. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_COMPRESSED = 0x00000040; + + /** + * The file is a sparse file; this means that file blocks that have + * not been explicitly written are not stored on disk. For example, if + * a client writes a buffer at 10 M from the beginning of the file, + * the blocks between the previous EOF marker and the 10 M offset would + * not consume physical disk space. + *

+ * Some servers may store all files as sparse files, in which case + * this bit will be unconditionally set. Other servers may not have + * a mechanism for determining if the file is sparse, and so the file + * MAY be stored sparse even if this flag is not set. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_SPARSE = 0x00000080; + + /** + * Opening the file without either the SSH_FXF_ACCESS_APPEND_DATA or + * the SSH_FXF_ACCESS_APPEND_DATA_ATOMIC flag (see section 8.1.1.3 + * of the SFTP standard draft) MUST result in an + * SSH_FX_INVALID_PARAMETER error. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_APPEND_ONLY = 0x00000100; + + /** + * The file cannot be deleted or renamed, no hard link can be created + * to this file, and no data can be written to the file. + *

+ * This bit implies a stronger level of protection than + * SSH_FILEXFER_ATTR_FLAGS_READONLY, the file permission mask or ACLs. + * Typically even the superuser cannot write to immutable files, and + * only the superuser can set or remove the bit. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_IMMUTABLE = 0x00000200; + + /** + * When the file is modified, the changes are written synchronously + * to the disk. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_SYNC = 0x00000400; + + /** + * The server MAY include this bit in a directory listing or realpath + * response. It indicates there was a failure in the translation to UTF-8. + * If this flag is included, the server SHOULD also include the + * UNTRANSLATED_NAME attribute. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_TRANSLATION_ERR = 0x00000800; + +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/sftp/AttribFlags.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/sftp/AttribFlags.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.sftp; + +/** + * Attribute Flags. The 'valid-attribute-flags' field in + * the SFTP ATTRS data type specifies which of the fields are actually present. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public final class AttribFlags { + + private AttribFlags() { + } + + /** + * Indicates that the 'allocation-size' field is present. + */ + public static final int SSH_FILEXFER_ATTR_SIZE = 0x00000001; + + /** + * Protocol version 6: + * 0x00000002 was used in a previous version of this protocol. + * It is now a reserved value and MUST NOT appear in the mask. + * Some future version of this protocol may reuse this value. + */ + public static final int SSH_FILEXFER_ATTR_V3_UIDGID = 0x00000002; + + /** + * Indicates that the 'permissions' field is present. + */ + public static final int SSH_FILEXFER_ATTR_PERMISSIONS = 0x00000004; + + /** + * Indicates that the 'atime' and 'mtime' field are present + * (protocol v3). + */ + public static final int SSH_FILEXFER_ATTR_V3_ACMODTIME = 0x00000008; + + /** + * Indicates that the 'atime' field is present. + */ + public static final int SSH_FILEXFER_ATTR_ACCESSTIME = 0x00000008; + + /** + * Indicates that the 'createtime' field is present. + */ + public static final int SSH_FILEXFER_ATTR_CREATETIME = 0x00000010; + + /** + * Indicates that the 'mtime' field is present. + */ + public static final int SSH_FILEXFER_ATTR_MODIFYTIME = 0x00000020; + + /** + * Indicates that the 'acl' field is present. + */ + public static final int SSH_FILEXFER_ATTR_ACL = 0x00000040; + + /** + * Indicates that the 'owner' and 'group' fields are present. + */ + public static final int SSH_FILEXFER_ATTR_OWNERGROUP = 0x00000080; + + /** + * Indicates that additionally to the 'atime', 'createtime', + * 'mtime' and 'ctime' fields (if present), there is also + * 'atime-nseconds', 'createtime-nseconds', 'mtime-nseconds' + * and 'ctime-nseconds'. + */ + public static final int SSH_FILEXFER_ATTR_SUBSECOND_TIMES = 0x00000100; + + /** + * Indicates that the 'attrib-bits' and 'attrib-bits-valid' + * fields are present. + */ + public static final int SSH_FILEXFER_ATTR_BITS = 0x00000200; + + /** + * Indicates that the 'allocation-size' field is present. Field specifies the number of bytes that the + * file consumes on disk. + */ + public static final int SSH_FILEXFER_ATTR_ALLOCATION_SIZE = 0x00000400; + + /** + * Indicates that the 'text-hint' field is present. + */ + public static final int SSH_FILEXFER_ATTR_TEXT_HINT = 0x00000800; + + /** + * Indicates that the 'mime-type' field is present. + */ + public static final int SSH_FILEXFER_ATTR_MIME_TYPE = 0x00001000; + + /** + * Indicates that the 'link-count' field is present. + */ + public static final int SSH_FILEXFER_ATTR_LINK_COUNT = 0x00002000; + + /** + * Indicates that the 'untranslated-name' field is present. + */ + public static final int SSH_FILEXFER_ATTR_UNTRANSLATED_NAME = 0x00004000; + + /** + * Indicates that the 'ctime' field is present. + */ + public static final int SSH_FILEXFER_ATTR_CTIME = 0x00008000; + + /** + * Indicates that the 'extended-count' field (and probablby some + * 'extensions') is present. + */ + public static final int SSH_FILEXFER_ATTR_EXTENDED = 0x80000000; +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/sftp/AttribPermissions.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/sftp/AttribPermissions.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.sftp; + +/** + * Permissions for the 'permissions' field in the SFTP ATTRS data type. + *

+ * "The 'permissions' field contains a bit mask specifying file permissions. + * These permissions correspond to the st_mode field of the stat structure + * defined by POSIX [IEEE.1003-1.1996]." + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class AttribPermissions { + private AttribPermissions() { + } + + /* Octal values! */ + + public static final int S_IRUSR = 0400; + public static final int S_IWUSR = 0200; + public static final int S_IXUSR = 0100; + public static final int S_IRGRP = 0040; + public static final int S_IWGRP = 0020; + public static final int S_IXGRP = 0010; + public static final int S_IROTH = 0004; + public static final int S_IWOTH = 0002; + public static final int S_IXOTH = 0001; + public static final int S_ISUID = 04000; + public static final int S_ISGID = 02000; + public static final int S_ISVTX = 01000; +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/sftp/AttribTypes.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/sftp/AttribTypes.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.sftp; + +/** + * Types for the 'type' field in the SFTP ATTRS data type. + *

+ * "On a POSIX system, these values would be derived from the mode field + * of the stat structure. SPECIAL should be used for files that are of + * a known type which cannot be expressed in the protocol. UNKNOWN + * should be used if the type is not known." + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public final class AttribTypes { + private AttribTypes() { + } + + public static final int SSH_FILEXFER_TYPE_REGULAR = 1; + public static final int SSH_FILEXFER_TYPE_DIRECTORY = 2; + public static final int SSH_FILEXFER_TYPE_SYMLINK = 3; + public static final int SSH_FILEXFER_TYPE_SPECIAL = 4; + /** + * Should be used if the type is not known. + */ + public static final int SSH_FILEXFER_TYPE_UNKNOWN = 5; + public static final int SSH_FILEXFER_TYPE_SOCKET = 6; + public static final int SSH_FILEXFER_TYPE_CHAR_DEVICE = 7; + public static final int SSH_FILEXFER_TYPE_BLOCK_DEVICE = 8; + public static final int SSH_FILEXFER_TYPE_FIFO = 9; +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/sftp/ErrorCodes.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/sftp/ErrorCodes.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.sftp; + +/** + * SFTP Error Codes + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public final class ErrorCodes { + private ErrorCodes() { + } + + public static final int SSH_FX_OK = 0; + public static final int SSH_FX_EOF = 1; + public static final int SSH_FX_NO_SUCH_FILE = 2; + public static final int SSH_FX_PERMISSION_DENIED = 3; + public static final int SSH_FX_FAILURE = 4; + public static final int SSH_FX_BAD_MESSAGE = 5; + public static final int SSH_FX_NO_CONNECTION = 6; + public static final int SSH_FX_CONNECTION_LOST = 7; + /** + * The server MUST respond with SSH_FXP_STATUS(SSH_FX_OP_UNSUPPORTED) if + * it receives a packet it does not recognize. + */ + public static final int SSH_FX_OP_UNSUPPORTED = 8; + public static final int SSH_FX_INVALID_HANDLE = 9; + public static final int SSH_FX_NO_SUCH_PATH = 10; + public static final int SSH_FX_FILE_ALREADY_EXISTS = 11; + public static final int SSH_FX_WRITE_PROTECT = 12; + public static final int SSH_FX_NO_MEDIA = 13; + public static final int SSH_FX_NO_SPACE_ON_FILESYSTEM = 14; + public static final int SSH_FX_QUOTA_EXCEEDED = 15; + public static final int SSH_FX_UNKNOWN_PRINCIPAL = 16; + public static final int SSH_FX_LOCK_CONFLICT = 17; + public static final int SSH_FX_DIR_NOT_EMPTY = 18; + public static final int SSH_FX_NOT_A_DIRECTORY = 19; + public static final int SSH_FX_INVALID_FILENAME = 20; + public static final int SSH_FX_LINK_LOOP = 21; + public static final int SSH_FX_CANNOT_DELETE = 22; + public static final int SSH_FX_INVALID_PARAMETER = 23; + public static final int SSH_FX_FILE_IS_A_DIRECTORY = 24; + public static final int SSH_FX_BYTE_RANGE_LOCK_CONFLICT = 25; + public static final int SSH_FX_BYTE_RANGE_LOCK_REFUSED = 26; + public static final int SSH_FX_DELETE_PENDING = 27; + public static final int SSH_FX_FILE_CORRUPT = 28; + public static final int SSH_FX_OWNER_INVALID = 29; + public static final int SSH_FX_GROUP_INVALID = 30; + public static final int SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK = 31; + + private static final String[][] messages = { + + {"SSH_FX_OK", "Indicates successful completion of the operation."}, + { + "SSH_FX_EOF", + "An attempt to read past the end-of-file was made; or, there are no more directory entries to return." + }, + {"SSH_FX_NO_SUCH_FILE", "A reference was made to a file which does not exist."}, + {"SSH_FX_PERMISSION_DENIED", "The user does not have sufficient permissions to perform the operation."}, + {"SSH_FX_FAILURE", "An error occurred, but no specific error code exists to describe the failure."}, + {"SSH_FX_BAD_MESSAGE", "A badly formatted packet or other SFTP protocol incompatibility was detected."}, + {"SSH_FX_NO_CONNECTION", "There is no connection to the server."}, + {"SSH_FX_CONNECTION_LOST", "The connection to the server was lost."}, + { + "SSH_FX_OP_UNSUPPORTED", + "An attempted operation could not be completed by the server because the server does not support the operation." + }, + {"SSH_FX_INVALID_HANDLE", "The handle value was invalid."}, + {"SSH_FX_NO_SUCH_PATH", "The file path does not exist or is invalid."}, + {"SSH_FX_FILE_ALREADY_EXISTS", "The file already exists."}, + {"SSH_FX_WRITE_PROTECT", "The file is on read-only media, or the media is write protected."}, + { + "SSH_FX_NO_MEDIA", + "The requested operation cannot be completed because there is no media available in the drive." + }, + { + "SSH_FX_NO_SPACE_ON_FILESYSTEM", + "The requested operation cannot be completed because there is insufficient free space on the filesystem." + }, + { + "SSH_FX_QUOTA_EXCEEDED", + "The operation cannot be completed because it would exceed the user's storage quota." + }, + { + "SSH_FX_UNKNOWN_PRINCIPAL", + "A principal referenced by the request (either the 'owner', 'group', or 'who' field of an ACL), was unknown. The error specific data contains the problematic names." + }, + {"SSH_FX_LOCK_CONFLICT", "The file could not be opened because it is locked by another process."}, + {"SSH_FX_DIR_NOT_EMPTY", "The directory is not empty."}, + {"SSH_FX_NOT_A_DIRECTORY", "The specified file is not a directory."}, + {"SSH_FX_INVALID_FILENAME", "The filename is not valid."}, + { + "SSH_FX_LINK_LOOP", + "Too many symbolic links encountered or, an SSH_FXF_NOFOLLOW open encountered a symbolic link as the final component." + }, + { + "SSH_FX_CANNOT_DELETE", + "The file cannot be deleted. One possible reason is that the advisory READONLY attribute-bit is set." + }, + { + "SSH_FX_INVALID_PARAMETER", + "One of the parameters was out of range, or the parameters specified cannot be used together." + }, + { + "SSH_FX_FILE_IS_A_DIRECTORY", + "The specified file was a directory in a context where a directory cannot be used." + }, + { + "SSH_FX_BYTE_RANGE_LOCK_CONFLICT", + " A read or write operation failed because another process's mandatory byte-range lock overlaps with the request." + }, + {"SSH_FX_BYTE_RANGE_LOCK_REFUSED", "A request for a byte range lock was refused."}, + {"SSH_FX_DELETE_PENDING", "An operation was attempted on a file for which a delete operation is pending."}, + {"SSH_FX_FILE_CORRUPT", "The file is corrupt; an filesystem integrity check should be run."}, + {"SSH_FX_OWNER_INVALID", "The principal specified can not be assigned as an owner of a file."}, + {"SSH_FX_GROUP_INVALID", "The principal specified can not be assigned as the primary group of a file."}, + { + "SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK", + "The requested operation could not be completed because the specifed byte range lock has not been granted." + }, + + }; + + public static final String[] getDescription(int errorCode) { + if ((errorCode < 0) || (errorCode >= messages.length)) { + return null; + } + + return messages[errorCode]; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/sftp/OpenFlags.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/sftp/OpenFlags.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.sftp; + +/** + * SFTP Open Flags. + *

+ * The following table is provided to assist in mapping POSIX semantics + * to equivalent SFTP file open parameters: + *

+ *

    + *
  • O_RDONLY + *
    • desired-access = READ_DATA | READ_ATTRIBUTES
    + *
  • + *
+ *
    + *
  • O_WRONLY + *
    • desired-access = WRITE_DATA | WRITE_ATTRIBUTES
    + *
  • + *
+ *
    + *
  • O_RDWR + *
    • desired-access = READ_DATA | READ_ATTRIBUTES | WRITE_DATA | WRITE_ATTRIBUTES
    + *
  • + *
+ *
    + *
  • O_APPEND + *
      + *
    • desired-access = WRITE_DATA | WRITE_ATTRIBUTES | APPEND_DATA
    • + *
    • flags = SSH_FXF_ACCESS_APPEND_DATA and or SSH_FXF_ACCESS_APPEND_DATA_ATOMIC
    • + *
    + *
  • + *
+ *
    + *
  • O_CREAT + *
      + *
    • flags = SSH_FXF_OPEN_OR_CREATE
    • + *
    + *
  • + *
+ *
    + *
  • O_TRUNC + *
      + *
    • flags = SSH_FXF_TRUNCATE_EXISTING
    • + *
    + *
  • + *
+ *
    + *
  • O_TRUNC|O_CREATE + *
      + *
    • flags = SSH_FXF_CREATE_TRUNCATE
    • + *
    + *
  • + *
+ * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public final class OpenFlags { + private OpenFlags() { + } + + /** + * Disposition is a 3 bit field that controls how the file is opened. + * The server MUST support these bits (possible enumaration values: + * SSH_FXF_CREATE_NEW, SSH_FXF_CREATE_TRUNCATE, SSH_FXF_OPEN_EXISTING, + * SSH_FXF_OPEN_OR_CREATE or SSH_FXF_TRUNCATE_EXISTING). + */ + public static final int SSH_FXF_ACCESS_DISPOSITION = 0x00000007; + + /** + * A new file is created; if the file already exists, the server + * MUST return status SSH_FX_FILE_ALREADY_EXISTS. + */ + public static final int SSH_FXF_CREATE_NEW = 0x00000000; + + /** + * A new file is created; if the file already exists, it is opened + * and truncated. + */ + public static final int SSH_FXF_CREATE_TRUNCATE = 0x00000001; + + /** + * An existing file is opened. If the file does not exist, the + * server MUST return SSH_FX_NO_SUCH_FILE. If a directory in the + * path does not exist, the server SHOULD return + * SSH_FX_NO_SUCH_PATH. It is also acceptable if the server + * returns SSH_FX_NO_SUCH_FILE in this case. + */ + public static final int SSH_FXF_OPEN_EXISTING = 0x00000002; + + /** + * If the file exists, it is opened. If the file does not exist, + * it is created. + */ + public static final int SSH_FXF_OPEN_OR_CREATE = 0x00000003; + + /** + * An existing file is opened and truncated. If the file does not + * exist, the server MUST return the same error codes as defined + * for SSH_FXF_OPEN_EXISTING. + */ + public static final int SSH_FXF_TRUNCATE_EXISTING = 0x00000004; + + /** + * Data is always written at the end of the file. The offset field + * of the SSH_FXP_WRITE requests are ignored. + *

+ * Data is not required to be appended atomically. This means that + * if multiple writers attempt to append data simultaneously, data + * from the first may be lost. However, data MAY be appended + * atomically. + */ + public static final int SSH_FXF_ACCESS_APPEND_DATA = 0x00000008; + + /** + * Data is always written at the end of the file. The offset field + * of the SSH_FXP_WRITE requests are ignored. + *

+ * Data MUST be written atomically so that there is no chance that + * multiple appenders can collide and result in data being lost. + *

+ * If both append flags are specified, the server SHOULD use atomic + * append if it is available, but SHOULD use non-atomic appends + * otherwise. The server SHOULD NOT fail the request in this case. + */ + public static final int SSH_FXF_ACCESS_APPEND_DATA_ATOMIC = 0x00000010; + + /** + * Indicates that the server should treat the file as text and + * convert it to the canonical newline convention in use. + * (See Determining Server Newline Convention in section 5.3 in the + * SFTP standard draft). + *

+ * When a file is opened with this flag, the offset field in the read + * and write functions is ignored. + *

+ * Servers MUST process multiple, parallel reads and writes correctly + * in this mode. Naturally, it is permissible for them to do this by + * serializing the requests. + *

+ * Clients SHOULD use the SSH_FXF_ACCESS_APPEND_DATA flag to append + * data to a text file rather then using write with a calculated offset. + */ + public static final int SSH_FXF_ACCESS_TEXT_MODE = 0x00000020; + + /** + * The server MUST guarantee that no other handle has been opened + * with ACE4_READ_DATA access, and that no other handle will be + * opened with ACE4_READ_DATA access until the client closes the + * handle. (This MUST apply both to other clients and to other + * processes on the server.) + *

+ * If there is a conflicting lock the server MUST return + * SSH_FX_LOCK_CONFLICT. If the server cannot make the locking + * guarantee, it MUST return SSH_FX_OP_UNSUPPORTED. + *

+ * Other handles MAY be opened for ACE4_WRITE_DATA or any other + * combination of accesses, as long as ACE4_READ_DATA is not included + * in the mask. + */ + public static final int SSH_FXF_ACCESS_BLOCK_READ = 0x00000040; + + /** + * The server MUST guarantee that no other handle has been opened + * with ACE4_WRITE_DATA or ACE4_APPEND_DATA access, and that no other + * handle will be opened with ACE4_WRITE_DATA or ACE4_APPEND_DATA + * access until the client closes the handle. (This MUST apply both + * to other clients and to other processes on the server.) + *

+ * If there is a conflicting lock the server MUST return + * SSH_FX_LOCK_CONFLICT. If the server cannot make the locking + * guarantee, it MUST return SSH_FX_OP_UNSUPPORTED. + *

+ * Other handles MAY be opened for ACE4_READ_DATA or any other + * combination of accesses, as long as neither ACE4_WRITE_DATA nor + * ACE4_APPEND_DATA are included in the mask. + */ + public static final int SSH_FXF_ACCESS_BLOCK_WRITE = 0x00000080; + + /** + * The server MUST guarantee that no other handle has been opened + * with ACE4_DELETE access, opened with the + * SSH_FXF_ACCESS_DELETE_ON_CLOSE flag set, and that no other handle + * will be opened with ACE4_DELETE access or with the + * SSH_FXF_ACCESS_DELETE_ON_CLOSE flag set, and that the file itself + * is not deleted in any other way until the client closes the handle. + *

+ * If there is a conflicting lock the server MUST return + * SSH_FX_LOCK_CONFLICT. If the server cannot make the locking + * guarantee, it MUST return SSH_FX_OP_UNSUPPORTED. + */ + public static final int SSH_FXF_ACCESS_BLOCK_DELETE = 0x00000100; + + /** + * If this bit is set, the above BLOCK modes are advisory. In advisory + * mode, only other accesses that specify a BLOCK mode need be + * considered when determining whether the BLOCK can be granted, + * and the server need not prevent I/O operations that violate the + * block mode. + *

+ * The server MAY perform mandatory locking even if the BLOCK_ADVISORY + * bit is set. + */ + public static final int SSH_FXF_ACCESS_BLOCK_ADVISORY = 0x00000200; + + /** + * If the final component of the path is a symlink, then the open + * MUST fail, and the error SSH_FX_LINK_LOOP MUST be returned. + */ + public static final int SSH_FXF_ACCESS_NOFOLLOW = 0x00000400; + + /** + * The file should be deleted when the last handle to it is closed. + * (The last handle may not be an sftp-handle.) This MAY be emulated + * by a server if the OS doesn't support it by deleting the file when + * this handle is closed. + *

+ * It is implementation specific whether the directory entry is + * removed immediately or when the handle is closed. + */ + public static final int SSH_FXF_ACCESS_DELETE_ON_CLOSE = 0x00000800; +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/sftp/Packet.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/sftp/Packet.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.sftp; + +/** + * SFTP Paket Types + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class Packet { + public static final int SSH_FXP_INIT = 1; + public static final int SSH_FXP_VERSION = 2; + public static final int SSH_FXP_OPEN = 3; + public static final int SSH_FXP_CLOSE = 4; + public static final int SSH_FXP_READ = 5; + public static final int SSH_FXP_WRITE = 6; + public static final int SSH_FXP_LSTAT = 7; + public static final int SSH_FXP_FSTAT = 8; + public static final int SSH_FXP_SETSTAT = 9; + public static final int SSH_FXP_FSETSTAT = 10; + public static final int SSH_FXP_OPENDIR = 11; + public static final int SSH_FXP_READDIR = 12; + public static final int SSH_FXP_REMOVE = 13; + public static final int SSH_FXP_MKDIR = 14; + public static final int SSH_FXP_RMDIR = 15; + public static final int SSH_FXP_REALPATH = 16; + public static final int SSH_FXP_STAT = 17; + public static final int SSH_FXP_RENAME = 18; + public static final int SSH_FXP_READLINK = 19; + public static final int SSH_FXP_SYMLINK = 20; + public static final int SSH_FXP_LINK = 21; + + public static final int SSH_FXP_STATUS = 101; + public static final int SSH_FXP_HANDLE = 102; + public static final int SSH_FXP_DATA = 103; + public static final int SSH_FXP_NAME = 104; + public static final int SSH_FXP_ATTRS = 105; + + /** + * SSH_FXP_EXTENDED and SSH_FXP_EXTENDED_REPLY packets can be used to + * implement extensions, which can be vendor specific. + */ + public static final int SSH_FXP_EXTENDED = 200; + /** + * SSH_FXP_EXTENDED and SSH_FXP_EXTENDED_REPLY packets can be used to + * implement extensions, which can be vendor specific. + */ + public static final int SSH_FXP_EXTENDED_REPLY = 201; + + public static String forName(int type) { + switch (type) { + case SSH_FXP_INIT: + return "SSH_FXP_INIT"; + + case SSH_FXP_VERSION: + return "SSH_FXP_VERSION"; + + case SSH_FXP_OPEN: + return "SSH_FXP_OPEN"; + + case SSH_FXP_CLOSE: + return "SSH_FXP_CLOSE"; + + case SSH_FXP_READ: + return "SSH_FXP_READ"; + + case SSH_FXP_WRITE: + return "SSH_FXP_WRITE"; + + case SSH_FXP_LSTAT: + return "SSH_FXP_LSTAT"; + + case SSH_FXP_FSTAT: + return "SSH_FXP_FSTAT"; + + case SSH_FXP_SETSTAT: + return "SSH_FXP_SETSTAT"; + + case SSH_FXP_FSETSTAT: + return "SSH_FXP_FSETSTAT"; + + case SSH_FXP_OPENDIR: + return "SSH_FXP_OPENDIR"; + + case SSH_FXP_READDIR: + return "SSH_FXP_READDIR"; + + case SSH_FXP_REMOVE: + return "SSH_FXP_REMOVE"; + + case SSH_FXP_MKDIR: + return "SSH_FXP_MKDIR"; + + case SSH_FXP_RMDIR: + return "SSH_FXP_RMDIR"; + + case SSH_FXP_REALPATH: + return "SSH_FXP_REALPATH"; + + case SSH_FXP_STAT: + return "SSH_FXP_STAT"; + + case SSH_FXP_RENAME: + return "SSH_FXP_RENAME"; + + case SSH_FXP_READLINK: + return "SSH_FXP_READLINK"; + + case SSH_FXP_SYMLINK: + return "SSH_FXP_SYMLINK"; + + case SSH_FXP_STATUS: + return "SSH_FXP_STATUS"; + + case SSH_FXP_HANDLE: + return "SSH_FXP_HANDLE"; + + case SSH_FXP_DATA: + return "SSH_FXP_DATA"; + + case SSH_FXP_NAME: + return "SSH_FXP_NAME"; + + case SSH_FXP_ATTRS: + return "SSH_FXP_ATTRS"; + + case SSH_FXP_EXTENDED: + return "SSH_FXP_EXTENDED"; + + case SSH_FXP_EXTENDED_REPLY: + return "SSH_FXP_EXTENDED_REPLY"; + } + + return null; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/signature/DSASHA1Verify.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/signature/DSASHA1Verify.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,230 @@ + +package ch.ethz.ssh2.signature; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.Signature; +import java.security.SignatureException; +import java.security.interfaces.DSAParams; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.DSAPublicKey; +import java.security.spec.DSAPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; + +import ch.ethz.ssh2.log.Logger; +import ch.ethz.ssh2.packets.TypesReader; +import ch.ethz.ssh2.packets.TypesWriter; + + +/** + * DSASHA1Verify. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: DSASHA1Verify.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ + */ +public class DSASHA1Verify { + private static final Logger log = Logger.getLogger(DSASHA1Verify.class); + + public static DSAPublicKey decodeSSHDSAPublicKey(byte[] key) throws IOException { + TypesReader tr = new TypesReader(key); + String key_format = tr.readString(); + + if (key_format.equals("ssh-dss") == false) + throw new IllegalArgumentException("This is not a ssh-dss public key!"); + + BigInteger p = tr.readMPINT(); + BigInteger q = tr.readMPINT(); + BigInteger g = tr.readMPINT(); + BigInteger y = tr.readMPINT(); + + if (tr.remain() != 0) + throw new IOException("Padding in DSA public key!"); + + try { + KeyFactory kf = KeyFactory.getInstance("DSA"); + KeySpec ks = new DSAPublicKeySpec(y, p, q, g); + return (DSAPublicKey) kf.generatePublic(ks); + } + catch (NoSuchAlgorithmException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + catch (InvalidKeySpecException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + } + + public static byte[] encodeSSHDSAPublicKey(DSAPublicKey pk) throws IOException { + TypesWriter tw = new TypesWriter(); + tw.writeString("ssh-dss"); + DSAParams params = pk.getParams(); + tw.writeMPInt(params.getP()); + tw.writeMPInt(params.getQ()); + tw.writeMPInt(params.getG()); + tw.writeMPInt(pk.getY()); + return tw.getBytes(); + } + + /** + * Convert from Java's signature ASN.1 encoding to the SSH spec. + *

+ * Java ASN.1 encoding: + *

+     * SEQUENCE ::= {
+     *    r INTEGER,
+     *    s INTEGER
+     * }
+     * 
+ */ + public static byte[] encodeSSHDSASignature(byte[] ds) { + TypesWriter tw = new TypesWriter(); + tw.writeString("ssh-dss"); + int len, index; + index = 3; + len = ds[index++] & 0xff; + byte[] r = new byte[len]; + System.arraycopy(ds, index, r, 0, r.length); + index = index + len + 1; + len = ds[index++] & 0xff; + byte[] s = new byte[len]; + System.arraycopy(ds, index, s, 0, s.length); + byte[] a40 = new byte[40]; + /* Patch (unsigned) r and s into the target array. */ + int r_copylen = (r.length < 20) ? r.length : 20; + int s_copylen = (s.length < 20) ? s.length : 20; + System.arraycopy(r, r.length - r_copylen, a40, 20 - r_copylen, r_copylen); + System.arraycopy(s, s.length - s_copylen, a40, 40 - s_copylen, s_copylen); + tw.writeString(a40, 0, 40); + return tw.getBytes(); + } + + public static byte[] decodeSSHDSASignature(byte[] sig) throws IOException { + byte[] rsArray = null; + + if (sig.length == 40) { + /* OK, another broken SSH server. */ + rsArray = sig; + } + else { + /* Hopefully a server obeying the standard... */ + TypesReader tr = new TypesReader(sig); + String sig_format = tr.readString(); + + if (sig_format.equals("ssh-dss") == false) + throw new IOException("Peer sent wrong signature format"); + + rsArray = tr.readByteString(); + + if (rsArray.length != 40) + throw new IOException("Peer sent corrupt signature"); + + if (tr.remain() != 0) + throw new IOException("Padding in DSA signature!"); + } + + int i = 0; + int j = 0; + byte[] tmp; + + if (rsArray[0] == 0 && rsArray[1] == 0 && rsArray[2] == 0) { + j = ((rsArray[i++] << 24) & 0xff000000) | ((rsArray[i++] << 16) & 0x00ff0000) + | ((rsArray[i++] << 8) & 0x0000ff00) | ((rsArray[i++]) & 0x000000ff); + i += j; + j = ((rsArray[i++] << 24) & 0xff000000) | ((rsArray[i++] << 16) & 0x00ff0000) + | ((rsArray[i++] << 8) & 0x0000ff00) | ((rsArray[i++]) & 0x000000ff); + tmp = new byte[j]; + System.arraycopy(rsArray, i, tmp, 0, j); + rsArray = tmp; + } + + /* ASN.1 */ + int frst = ((rsArray[0] & 0x80) != 0 ? 1 : 0); + int scnd = ((rsArray[20] & 0x80) != 0 ? 1 : 0); + /* Calculate output length */ + int length = rsArray.length + 6 + frst + scnd; + tmp = new byte[length]; + /* DER-encoding to match Java */ + tmp[0] = (byte) 0x30; + + if (rsArray.length != 40) + throw new IOException("Peer sent corrupt signature"); + + /* Calculate length */ + tmp[1] = (byte) 0x2c; + tmp[1] += frst; + tmp[1] += scnd; + /* First item */ + tmp[2] = (byte) 0x02; + /* First item length */ + tmp[3] = (byte) 0x14; + tmp[3] += frst; + /* Copy in the data for first item */ + System.arraycopy(rsArray, 0, tmp, 4 + frst, 20); + /* Second item */ + tmp[4 + tmp[3]] = (byte) 0x02; + /* Second item length */ + tmp[5 + tmp[3]] = (byte) 0x14; + tmp[5 + tmp[3]] += scnd; + /* Copy in the data for the second item */ + System.arraycopy(rsArray, 20, tmp, 6 + tmp[3] + scnd, 20); + /* Swap buffers */ + rsArray = tmp; + return rsArray; + } + + public static boolean verifySignature(byte[] message, byte[] ds, DSAPublicKey dpk) throws IOException { + try { + Signature s = Signature.getInstance("SHA1withDSA"); + s.initVerify(dpk); + s.update(message); + return s.verify(ds); + } + catch (NoSuchAlgorithmException e) { + IOException ex = new IOException("No such algorithm"); + ex.initCause(e); + throw ex; + } + catch (InvalidKeyException e) { + IOException ex = new IOException("No such algorithm"); + ex.initCause(e); + throw ex; + } + catch (SignatureException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + } + + public static byte[] generateSignature(byte[] message, DSAPrivateKey pk, SecureRandom rnd) throws IOException { + try { + Signature s = Signature.getInstance("SHA1withDSA"); + s.initSign(pk); + s.update(message); + return s.sign(); + } + catch (NoSuchAlgorithmException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + catch (InvalidKeyException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + catch (SignatureException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/signature/ECDSASHA2Verify.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/signature/ECDSASHA2Verify.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,488 @@ +/** + * + */ +package ch.ethz.ssh2.signature; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +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.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECFieldFp; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.EllipticCurve; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.util.Map; +import java.util.TreeMap; + +import ch.ethz.ssh2.log.Logger; +import ch.ethz.ssh2.packets.TypesReader; +import ch.ethz.ssh2.packets.TypesWriter; + +/** + * @author Kenny Root + * + */ +public class ECDSASHA2Verify { + private static final Logger log = Logger.getLogger(ECDSASHA2Verify.class); + + public static final String ECDSA_SHA2_PREFIX = "ecdsa-sha2-"; + + private static final String NISTP256 = "nistp256"; + private static final String NISTP256_OID = "1.2.840.10045.3.1.7"; + private static final String NISTP384 = "nistp384"; + private static final String NISTP384_OID = "1.3.132.0.34"; + private static final String NISTP521 = "nistp521"; + private static final String NISTP521_OID = "1.3.132.0.35"; + + private static final Map CURVES = new TreeMap(); + static { + CURVES.put(NISTP256, EllipticCurves.nistp256); + CURVES.put(NISTP384, EllipticCurves.nistp384); + CURVES.put(NISTP521, EllipticCurves.nistp521); + } + + private static final Map CURVE_SIZES = new TreeMap(); + static { + CURVE_SIZES.put(256, NISTP256); + CURVE_SIZES.put(384, NISTP384); + CURVE_SIZES.put(521, NISTP521); + } + + private static final Map CURVE_OIDS = new TreeMap(); + static { + CURVE_OIDS.put(NISTP256_OID, NISTP256); + CURVE_OIDS.put(NISTP384_OID, NISTP256); + CURVE_OIDS.put(NISTP521_OID, NISTP256); + } + + public static int[] getCurveSizes() { + int[] keys = new int[CURVE_SIZES.size()]; + int i = 0; + + for (Integer n : CURVE_SIZES.keySet().toArray(new Integer[keys.length])) { + keys[i++] = n; + } + + return keys; + } + + public static ECParameterSpec getCurveForSize(int size) { + final String name = CURVE_SIZES.get(size); + + if (name == null) { + return null; + } + + return CURVES.get(name); + } + + public static ECPublicKey decodeSSHECDSAPublicKey(byte[] key) throws IOException { + TypesReader tr = new TypesReader(key); + String key_format = tr.readString(); + + if (key_format.startsWith(ECDSA_SHA2_PREFIX) == false) + throw new IllegalArgumentException("This is not an ECDSA public key"); + + String curveName = tr.readString(); + byte[] groupBytes = tr.readByteString(); + + if (tr.remain() != 0) + throw new IOException("Padding in ECDSA public key!"); + + if (key_format.equals(ECDSA_SHA2_PREFIX + curveName) == false) { + throw new IOException("Key format is inconsistent with curve name: " + key_format + + " != " + curveName); + } + + ECParameterSpec params = CURVES.get(curveName); + + if (params == null) { + throw new IOException("Curve is not supported: " + curveName); + } + + ECPoint group = ECDSASHA2Verify.decodeECPoint(groupBytes, params.getCurve()); + + if (group == null) { + throw new IOException("Invalid ECDSA group"); + } + + KeySpec keySpec = new ECPublicKeySpec(group, params); + + try { + KeyFactory kf = KeyFactory.getInstance("EC"); + return (ECPublicKey) kf.generatePublic(keySpec); + } + catch (NoSuchAlgorithmException nsae) { + IOException ioe = new IOException("No EC KeyFactory available"); + ioe.initCause(nsae); + throw ioe; + } + catch (InvalidKeySpecException ikse) { + IOException ioe = new IOException("No EC KeyFactory available"); + ioe.initCause(ikse); + throw ioe; + } + } + + public static byte[] encodeSSHECDSAPublicKey(ECPublicKey key) throws IOException { + TypesWriter tw = new TypesWriter(); + String curveName = getCurveName(key.getParams()); + String keyFormat = ECDSA_SHA2_PREFIX + curveName; + tw.writeString(keyFormat); + tw.writeString(curveName); + byte[] encoded = encodeECPoint(key.getW(), key.getParams().getCurve()); + tw.writeString(encoded, 0, encoded.length); + return tw.getBytes(); + } + + public static String getCurveName(ECParameterSpec params) throws IOException { + int fieldSize = getCurveSize(params); + final String curveName = getCurveName(fieldSize); + + if (curveName == null) { + throw new IOException("invalid curve size " + fieldSize); + } + + return curveName; + } + + public static String getCurveName(int fieldSize) { + String curveName = CURVE_SIZES.get(fieldSize); + + if (curveName == null) { + return null; + } + + return curveName; + } + + public static int getCurveSize(ECParameterSpec params) { + return params.getCurve().getField().getFieldSize(); + } + + public static ECParameterSpec getCurveForOID(String oid) { + String name = CURVE_OIDS.get(oid); + + if (name == null) + return null; + + return CURVES.get(name); + } + + public static byte[] decodeSSHECDSASignature(byte[] sig) throws IOException { + byte[] rsArray = null; + TypesReader tr = new TypesReader(sig); + String sig_format = tr.readString(); + + if (sig_format.startsWith(ECDSA_SHA2_PREFIX) == false) + throw new IOException("Peer sent wrong signature format"); + + String curveName = sig_format.substring(ECDSA_SHA2_PREFIX.length()); + + if (CURVES.containsKey(curveName) == false) { + throw new IOException("Unsupported curve: " + curveName); + } + + rsArray = tr.readByteString(); + + if (tr.remain() != 0) + throw new IOException("Padding in ECDSA signature!"); + + byte[] rArray; + byte[] sArray; + { + TypesReader rsReader = new TypesReader(rsArray); + rArray = rsReader.readMPINT().toByteArray(); + sArray = rsReader.readMPINT().toByteArray(); + } + int first = rArray.length; + int second = sArray.length; + + /* We can't have the high bit set, so add an extra zero at the beginning if so. */ + if ((rArray[0] & 0x80) != 0) { + first++; + } + + if ((sArray[0] & 0x80) != 0) { + second++; + } + + /* Calculate total output length */ + ByteArrayOutputStream os = new ByteArrayOutputStream(6 + first + second); + /* ASN.1 SEQUENCE tag */ + os.write(0x30); + /* Size of SEQUENCE */ + writeLength(4 + first + second, os); + /* ASN.1 INTEGER tag */ + os.write(0x02); + /* "r" INTEGER length */ + writeLength(first, os); + + /* Copy in the "r" INTEGER */ + if (first != rArray.length) { + os.write(0x00); + } + + os.write(rArray); + /* ASN.1 INTEGER tag */ + os.write(0x02); + /* "s" INTEGER length */ + writeLength(second, os); + + /* Copy in the "s" INTEGER */ + if (second != sArray.length) { + os.write(0x00); + } + + os.write(sArray); + return os.toByteArray(); + } + + private static final void writeLength(int length, OutputStream os) throws IOException { + if (length <= 0x7F) { + os.write(length); + return; + } + + int numOctets = 0; + int lenCopy = length; + + while (lenCopy != 0) { + lenCopy >>>= 8; + numOctets++; + } + + os.write(0x80 | numOctets); + + for (int i = (numOctets - 1) * 8; i >= 0; i -= 8) { + os.write((byte)(length >> i)); + } + } + + public static byte[] encodeSSHECDSASignature(byte[] sig, ECParameterSpec params) throws IOException { + TypesWriter tw = new TypesWriter(); + String curveName = getCurveName(params); + tw.writeString(ECDSA_SHA2_PREFIX + curveName); + + if ((sig[0] != 0x30) || (sig[1] != sig.length - 2) || (sig[2] != 0x02)) { + throw new IOException("Invalid signature format"); + } + + int rLength = sig[3]; + + if ((rLength + 6 > sig.length) || (sig[4 + rLength] != 0x02)) { + throw new IOException("Invalid signature format"); + } + + int sLength = sig[5 + rLength]; + + if (6 + rLength + sLength > sig.length) { + throw new IOException("Invalid signature format"); + } + + byte[] rArray = new byte[rLength]; + byte[] sArray = new byte[sLength]; + System.arraycopy(sig, 4, rArray, 0, rLength); + System.arraycopy(sig, 6 + rLength, sArray, 0, sLength); + BigInteger r = new BigInteger(rArray); + BigInteger s = new BigInteger(sArray); + // Write the to its own types writer. + TypesWriter rsWriter = new TypesWriter(); + rsWriter.writeMPInt(r); + rsWriter.writeMPInt(s); + byte[] encoded = rsWriter.getBytes(); + tw.writeString(encoded, 0, encoded.length); + return tw.getBytes(); + } + + public static byte[] generateSignature(byte[] message, ECPrivateKey pk) throws IOException { + final String algo = getSignatureAlgorithmForParams(pk.getParams()); + + try { + Signature s = Signature.getInstance(algo); + s.initSign(pk); + s.update(message); + return s.sign(); + } + catch (NoSuchAlgorithmException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + catch (InvalidKeyException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + catch (SignatureException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + } + + public static boolean verifySignature(byte[] message, byte[] ds, ECPublicKey dpk) throws IOException { + final String algo = getSignatureAlgorithmForParams(dpk.getParams()); + + try { + Signature s = Signature.getInstance(algo); + s.initVerify(dpk); + s.update(message); + return s.verify(ds); + } + catch (NoSuchAlgorithmException e) { + IOException ex = new IOException("No such algorithm"); + ex.initCause(e); + throw ex; + } + catch (InvalidKeyException e) { + IOException ex = new IOException("No such algorithm"); + ex.initCause(e); + throw ex; + } + catch (SignatureException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + } + + private static String getSignatureAlgorithmForParams(ECParameterSpec params) { + int size = getCurveSize(params); + + if (size <= 256) { + return "SHA256withECDSA"; + } + else if (size <= 384) { + return "SHA384withECDSA"; + } + else { + return "SHA512withECDSA"; + } + } + + public static String getDigestAlgorithmForParams(ECParameterSpec params) { + int size = getCurveSize(params); + + if (size <= 256) { + return "SHA256"; + } + else if (size <= 384) { + return "SHA384"; + } + else { + return "SHA512"; + } + } + + /** + * Decode an OctetString to EllipticCurvePoint according to SECG 2.3.4 + */ + public static ECPoint decodeECPoint(byte[] M, EllipticCurve curve) { + if (M.length == 0) { + return null; + } + + // M has len 2 ceil(log_2(q)/8) + 1 ? + int elementSize = (curve.getField().getFieldSize() + 7) / 8; + + if (M.length != 2 * elementSize + 1) { + return null; + } + + // step 3.2 + if (M[0] != 0x04) { + return null; + } + + // Step 3.3 + byte[] xp = new byte[elementSize]; + System.arraycopy(M, 1, xp, 0, elementSize); + // Step 3.4 + byte[] yp = new byte[elementSize]; + System.arraycopy(M, 1 + elementSize, yp, 0, elementSize); + ECPoint P = new ECPoint(new BigInteger(1, xp), new BigInteger(1, yp)); + // TODO check point 3.5 + // Step 3.6 + return P; + } + + /** + * Encode EllipticCurvePoint to an OctetString + */ + public static byte[] encodeECPoint(ECPoint group, EllipticCurve curve) { + // M has len 2 ceil(log_2(q)/8) + 1 ? + int elementSize = (curve.getField().getFieldSize() + 7) / 8; + byte[] M = new byte[2 * elementSize + 1]; + // Uncompressed format + M[0] = 0x04; + { + byte[] affineX = removeLeadingZeroes(group.getAffineX().toByteArray()); + System.arraycopy(affineX, 0, M, 1 + elementSize - affineX.length, affineX.length); + } + { + byte[] affineY = removeLeadingZeroes(group.getAffineY().toByteArray()); + System.arraycopy(affineY, 0, M, 1 + elementSize + elementSize - affineY.length, + affineY.length); + } + return M; + } + + private static byte[] removeLeadingZeroes(byte[] input) { + if (input[0] != 0x00) { + return input; + } + + int pos = 1; + + while (pos < input.length - 1 && input[pos] == 0x00) { + pos++; + } + + byte[] output = new byte[input.length - pos]; + System.arraycopy(input, pos, output, 0, output.length); + return output; + } + + public static class EllipticCurves { + public static ECParameterSpec nistp256 = new ECParameterSpec( + new EllipticCurve( + new ECFieldFp(new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", 16)), + new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", 16), + new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16)), + new ECPoint(new BigInteger("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", 16), + new BigInteger("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", 16)), + new BigInteger("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", 16), + 1); + + public static ECParameterSpec nistp384 = new ECParameterSpec( + new EllipticCurve( + new ECFieldFp(new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF", 16)), + new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC", 16), + new BigInteger("B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF", 16)), + new ECPoint(new BigInteger("AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7", 16), + new BigInteger("3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F", 16)), + new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973", 16), + 1); + + public static ECParameterSpec nistp521 = new ECParameterSpec( + new EllipticCurve( + new ECFieldFp(new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16)), + new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC", 16), + new BigInteger("0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00", 16)), + new ECPoint(new BigInteger("00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66", 16), + new BigInteger("011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650", 16)), + new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409", 16), + 1); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/signature/RSASHA1Verify.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/signature/RSASHA1Verify.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,174 @@ + +package ch.ethz.ssh2.signature; + +import java.io.IOException; +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 ch.ethz.ssh2.log.Logger; +import ch.ethz.ssh2.packets.TypesReader; +import ch.ethz.ssh2.packets.TypesWriter; + + +/** + * RSASHA1Verify. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: RSASHA1Verify.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $ + */ +public class RSASHA1Verify { + private static final Logger log = Logger.getLogger(RSASHA1Verify.class); + + public static RSAPublicKey decodeSSHRSAPublicKey(byte[] key) throws IOException { + TypesReader tr = new TypesReader(key); + String key_format = tr.readString(); + + if (key_format.equals("ssh-rsa") == false) + throw new IllegalArgumentException("This is not a ssh-rsa public key"); + + BigInteger e = tr.readMPINT(); + BigInteger n = tr.readMPINT(); + + if (tr.remain() != 0) + throw new IOException("Padding in RSA public key!"); + + KeySpec keySpec = new RSAPublicKeySpec(n, e); + + try { + KeyFactory kf = KeyFactory.getInstance("RSA"); + return (RSAPublicKey) kf.generatePublic(keySpec); + } + catch (NoSuchAlgorithmException nsae) { + IOException ioe = new IOException("No RSA KeyFactory available"); + ioe.initCause(nsae); + throw ioe; + } + catch (InvalidKeySpecException ikse) { + IOException ioe = new IOException("No RSA KeyFactory available"); + ioe.initCause(ikse); + throw ioe; + } + } + + public static byte[] encodeSSHRSAPublicKey(RSAPublicKey pk) throws IOException { + TypesWriter tw = new TypesWriter(); + tw.writeString("ssh-rsa"); + tw.writeMPInt(pk.getPublicExponent()); + tw.writeMPInt(pk.getModulus()); + return tw.getBytes(); + } + + public static byte[] decodeSSHRSASignature(byte[] sig) throws IOException { + TypesReader tr = new TypesReader(sig); + String sig_format = tr.readString(); + + if (sig_format.equals("ssh-rsa") == false) + throw new IOException("Peer sent wrong signature format"); + + /* S is NOT an MPINT. "The value for 'rsa_signature_blob' is encoded as a string + * containing s (which is an integer, without lengths or padding, unsigned and in + * network byte order)." See also below. + */ + byte[] s = tr.readByteString(); + + if (s.length == 0) + throw new IOException("Error in RSA signature, S is empty."); + + if (log.isEnabled()) { + log.info("Decoding ssh-rsa signature string (length: " + s.length + ")"); + } + + if (tr.remain() != 0) + throw new IOException("Padding in RSA signature!"); + + if (s[0] == 0 && s[1] == 0 && s[2] == 0) { + int i = 0; + int j = ((s[i++] << 24) & 0xff000000) | ((s[i++] << 16) & 0x00ff0000) + | ((s[i++] << 8) & 0x0000ff00) | ((s[i++]) & 0x000000ff); + i += j; + j = ((s[i++] << 24) & 0xff000000) | ((s[i++] << 16) & 0x00ff0000) + | ((s[i++] << 8) & 0x0000ff00) | ((s[i++]) & 0x000000ff); + byte[] tmp = new byte[j]; + System.arraycopy(s, i, tmp, 0, j); + sig = tmp; + } + + return s; + } + + public static byte[] encodeSSHRSASignature(byte[] s) throws IOException { + TypesWriter tw = new TypesWriter(); + tw.writeString("ssh-rsa"); + + /* S is NOT an MPINT. "The value for 'rsa_signature_blob' is encoded as a string + * containing s (which is an integer, without lengths or padding, unsigned and in + * network byte order)." + */ + + /* Remove first zero sign byte, if present */ + + if ((s.length > 1) && (s[0] == 0x00)) + tw.writeString(s, 1, s.length - 1); + else + tw.writeString(s, 0, s.length); + + return tw.getBytes(); + } + + public static byte[] generateSignature(byte[] message, RSAPrivateKey pk) throws IOException { + try { + Signature s = Signature.getInstance("SHA1withRSA"); + s.initSign(pk); + s.update(message); + return s.sign(); + } + catch (NoSuchAlgorithmException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + catch (InvalidKeyException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + catch (SignatureException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + } + + public static boolean verifySignature(byte[] message, byte[] ds, RSAPublicKey dpk) throws IOException { + try { + Signature s = Signature.getInstance("SHA1withRSA"); + s.initVerify(dpk); + s.update(message); + return s.verify(ds); + } + catch (NoSuchAlgorithmException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + catch (InvalidKeyException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + catch (SignatureException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/transport/ClientKexManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/transport/ClientKexManager.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2006-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.transport; + +import java.io.IOException; +import java.security.DigestException; +import java.security.SecureRandom; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPublicKey; + +import ch.ethz.ssh2.ConnectionInfo; +import ch.ethz.ssh2.PacketTypeException; +import ch.ethz.ssh2.ServerHostKeyVerifier; +import ch.ethz.ssh2.compression.CompressionFactory; +import ch.ethz.ssh2.compression.Compressor; +import ch.ethz.ssh2.crypto.CryptoWishList; +import ch.ethz.ssh2.crypto.cipher.BlockCipher; +import ch.ethz.ssh2.crypto.cipher.BlockCipherFactory; +import ch.ethz.ssh2.crypto.dh.GenericDhExchange; +import ch.ethz.ssh2.crypto.dh.DhGroupExchange; +import ch.ethz.ssh2.crypto.digest.MAC; +import ch.ethz.ssh2.packets.PacketKexDHInit; +import ch.ethz.ssh2.packets.PacketKexDHReply; +import ch.ethz.ssh2.packets.PacketKexDhGexGroup; +import ch.ethz.ssh2.packets.PacketKexDhGexInit; +import ch.ethz.ssh2.packets.PacketKexDhGexReply; +import ch.ethz.ssh2.packets.PacketKexDhGexRequest; +import ch.ethz.ssh2.packets.PacketKexDhGexRequestOld; +import ch.ethz.ssh2.packets.PacketKexInit; +import ch.ethz.ssh2.packets.Packets; +import ch.ethz.ssh2.signature.DSASHA1Verify; +import ch.ethz.ssh2.signature.ECDSASHA2Verify; +import ch.ethz.ssh2.signature.RSASHA1Verify; + +/** + * @version $Id: ClientKexManager.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public class ClientKexManager extends KexManager { + + private final ServerHostKeyVerifier verifier; + + private final String hostname; + + private final int port; + + public ClientKexManager(TransportManager tm, ClientServerHello csh, CryptoWishList initialCwl, String hostname, int port, + ServerHostKeyVerifier keyVerifier, SecureRandom rnd) { + super(tm, csh, initialCwl, rnd); + this.hostname = hostname; + this.port = port; + this.verifier = keyVerifier; + } + + protected boolean verifySignature(byte[] sig, byte[] hostkey) throws IOException { + if (kxs.np.server_host_key_algo.startsWith("ecdsa-sha2-")) { + byte[] rs = ECDSASHA2Verify.decodeSSHECDSASignature(sig); + ECPublicKey epk = ECDSASHA2Verify.decodeSSHECDSAPublicKey(hostkey); + log.debug("Verifying ecdsa signature"); + return ECDSASHA2Verify.verifySignature(kxs.H, rs, epk); + } + + if (kxs.np.server_host_key_algo.equals("ssh-rsa")) { + byte[] rs = RSASHA1Verify.decodeSSHRSASignature(sig); + RSAPublicKey rpk = RSASHA1Verify.decodeSSHRSAPublicKey(hostkey); + log.debug("Verifying ssh-rsa signature"); + return RSASHA1Verify.verifySignature(kxs.H, rs, rpk); + } + + if (kxs.np.server_host_key_algo.equals("ssh-dss")) { + byte[] ds = DSASHA1Verify.decodeSSHDSASignature(sig); + DSAPublicKey dpk = DSASHA1Verify.decodeSSHDSAPublicKey(hostkey); + log.debug("Verifying ssh-dss signature"); + return DSASHA1Verify.verifySignature(kxs.H, ds, dpk); + } + + throw new IOException("Unknown server host key algorithm '" + kxs.np.server_host_key_algo + "'"); + } + + public void handleFailure(final IOException failure) { + synchronized (accessLock) { + connectionClosed = true; + accessLock.notifyAll(); + } + } + + public synchronized void handleMessage(byte[] msg) throws IOException { + PacketKexInit kip; + + if (msg == null) { + synchronized (accessLock) { + connectionClosed = true; + accessLock.notifyAll(); + return; + } + } + + log.debug(String.format("client kex manager, packet type %d", msg[0])); + + if ((kxs == null) && (msg[0] != Packets.SSH_MSG_KEXINIT)) { + throw new PacketTypeException(msg[0]); + } + + if (ignore_next_kex_packet) { + ignore_next_kex_packet = false; + return; + } + + if (msg[0] == Packets.SSH_MSG_KEXINIT) { + if ((kxs != null) && (kxs.state != 0)) { + throw new PacketTypeException(msg[0]); + } + + if (kxs == null) { + /* + * Ah, OK, peer wants to do KEX. Let's be nice and play + * together. + */ + kxs = new KexState(); + kxs.dhgexParameters = nextKEXdhgexParameters; + kip = new PacketKexInit(nextKEXcryptoWishList, rnd); + kxs.localKEX = kip; + tm.sendKexMessage(kip.getPayload()); + } + + kip = new PacketKexInit(msg); + kxs.remoteKEX = kip; + kxs.np = mergeKexParameters(kxs.localKEX.getKexParameters(), kxs.remoteKEX.getKexParameters()); + + if (kxs.np == null) + throw new IOException("Cannot negotiate, proposals do not match."); + + if (kxs.remoteKEX.isFirst_kex_packet_follows() && (kxs.np.guessOK == false)) { + // Guess was wrong, we need to ignore the next kex packet. + ignore_next_kex_packet = true; + } + + if (kxs.np.kex_algo.equals("diffie-hellman-group-exchange-sha1") || + kxs.np.kex_algo.equals("diffie-hellman-group-exchange-sha256")) { + if (kxs.dhgexParameters.getMin_group_len() == 0) { + PacketKexDhGexRequestOld dhgexreq = new PacketKexDhGexRequestOld(kxs.dhgexParameters); + tm.sendKexMessage(dhgexreq.getPayload()); + } + else { + PacketKexDhGexRequest dhgexreq = new PacketKexDhGexRequest(kxs.dhgexParameters); + tm.sendKexMessage(dhgexreq.getPayload()); + } + + if (kxs.np.kex_algo.endsWith("sha1")) { + kxs.hashAlgo = "SHA1"; + } + else { + kxs.hashAlgo = "SHA-256"; + } + + kxs.state = 1; + return; + } + + if (kxs.np.kex_algo.equals("diffie-hellman-group1-sha1") || + kxs.np.kex_algo.equals("diffie-hellman-group14-sha1") || + kxs.np.kex_algo.equals("ecdh-sha2-nistp256") || + kxs.np.kex_algo.equals("ecdh-sha2-nistp384") || + kxs.np.kex_algo.equals("ecdh-sha2-nistp521")) { + kxs.dhx = GenericDhExchange.getInstance(kxs.np.kex_algo); + kxs.dhx.init(kxs.np.kex_algo); + kxs.hashAlgo = kxs.dhx.getHashAlgo(); + PacketKexDHInit kp = new PacketKexDHInit(kxs.dhx.getE()); + tm.sendKexMessage(kp.getPayload()); + kxs.state = 1; + return; + } + + throw new IllegalStateException("Unkown KEX method!"); + } + + if (msg[0] == Packets.SSH_MSG_NEWKEYS) { + if (km == null) { + throw new IOException("Peer sent SSH_MSG_NEWKEYS, but I have no key material ready!"); + } + + BlockCipher cbc; + MAC mac; + Compressor comp; + + try { + cbc = BlockCipherFactory.createCipher(kxs.np.enc_algo_server_to_client, false, + km.enc_key_server_to_client, km.initial_iv_server_to_client); + + try { + mac = new MAC(kxs.np.mac_algo_server_to_client, km.integrity_key_server_to_client); + } + catch (DigestException e) { + throw new IOException(e); + } + + comp = CompressionFactory.createCompressor(kxs.np.comp_algo_server_to_client); + } + catch (IllegalArgumentException e) { + throw new IOException(e.getMessage()); + } + + tm.changeRecvCipher(cbc, mac); + tm.changeRecvCompression(comp); + ConnectionInfo sci = new ConnectionInfo(); + kexCount++; + sci.keyExchangeAlgorithm = kxs.np.kex_algo; + sci.keyExchangeCounter = kexCount; + sci.clientToServerCryptoAlgorithm = kxs.np.enc_algo_client_to_server; + sci.serverToClientCryptoAlgorithm = kxs.np.enc_algo_server_to_client; + sci.clientToServerMACAlgorithm = kxs.np.mac_algo_client_to_server; + sci.serverToClientMACAlgorithm = kxs.np.mac_algo_server_to_client; + sci.serverHostKeyAlgorithm = kxs.np.server_host_key_algo; + sci.serverHostKey = kxs.remote_hostkey; + + synchronized (accessLock) { + lastConnInfo = sci; + accessLock.notifyAll(); + } + + kxs = null; + return; + } + + if ((kxs == null) || (kxs.state == 0)) { + throw new IOException("Unexpected Kex submessage!"); + } + + if (kxs.np.kex_algo.equals("diffie-hellman-group-exchange-sha1") || + kxs.np.kex_algo.equals("diffie-hellman-group-exchange-sha256")) { + if (kxs.state == 1) { + PacketKexDhGexGroup dhgexgrp = new PacketKexDhGexGroup(msg); + kxs.dhgx = new DhGroupExchange(dhgexgrp.getP(), dhgexgrp.getG()); + kxs.dhgx.init(rnd); + PacketKexDhGexInit dhgexinit = new PacketKexDhGexInit(kxs.dhgx.getE()); + tm.sendKexMessage(dhgexinit.getPayload()); + kxs.state = 2; + return; + } + + if (kxs.state == 2) { + PacketKexDhGexReply dhgexrpl = new PacketKexDhGexReply(msg); + kxs.remote_hostkey = dhgexrpl.getHostKey(); + + if (verifier != null) { + try { + if (!verifier.verifyServerHostKey(hostname, port, kxs.np.server_host_key_algo, kxs.remote_hostkey)) { + throw new IOException("The server host key was not accepted by the verifier callback"); + } + } + catch (Exception e) { + throw new IOException( + "The server host key was not accepted by the verifier callback.", e); + } + } + + kxs.dhgx.setF(dhgexrpl.getF()); + + try { + kxs.H = kxs.dhgx.calculateH(kxs.hashAlgo, csh.getClientString(), csh.getServerString(), + kxs.localKEX.getPayload(), kxs.remoteKEX.getPayload(), dhgexrpl.getHostKey(), + kxs.dhgexParameters); + } + catch (IllegalArgumentException e) { + throw new IOException("KEX error.", e); + } + + if (!verifySignature(dhgexrpl.getSignature(), kxs.remote_hostkey)) { + throw new IOException("Invalid remote host key signature"); + } + + kxs.K = kxs.dhgx.getK(); + finishKex(true); + kxs.state = -1; + return; + } + + throw new IllegalStateException("Illegal State in KEX Exchange!"); + } + + if (kxs.np.kex_algo.equals("diffie-hellman-group1-sha1") || + kxs.np.kex_algo.equals("diffie-hellman-group14-sha1") || + kxs.np.kex_algo.equals("ecdh-sha2-nistp256") || + kxs.np.kex_algo.equals("ecdh-sha2-nistp384") || + kxs.np.kex_algo.equals("ecdh-sha2-nistp521")) { + if (kxs.state == 1) { + PacketKexDHReply dhr = new PacketKexDHReply(msg); + kxs.remote_hostkey = dhr.getHostKey(); + + if (verifier != null) { + try { + if (!verifier.verifyServerHostKey(hostname, port, kxs.np.server_host_key_algo, kxs.remote_hostkey)) { + throw new IOException("The server host key was not accepted by the verifier callback"); + } + } + catch (Exception e) { + throw new IOException("The server host key was not accepted by the verifier callback", e); + } + } + + kxs.dhx.setF(dhr.getF().toByteArray()); + + try { + kxs.H = kxs.dhx.calculateH(csh.getClientString(), csh.getServerString(), kxs.localKEX.getPayload(), + kxs.remoteKEX.getPayload(), dhr.getHostKey()); + } + catch (IllegalArgumentException e) { + throw new IOException("KEX error.", e); + } + + if (!verifySignature(dhr.getSignature(), kxs.remote_hostkey)) { + throw new IOException("Invalid remote host key signature"); + } + + kxs.K = kxs.dhx.getK(); + finishKex(true); + kxs.state = -1; + return; + } + } + + throw new IllegalStateException(String.format("Unknown KEX method %s", kxs.np.kex_algo)); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/transport/ClientServerHello.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/transport/ClientServerHello.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2.transport; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.io.OutputStream; + +import ch.ethz.ssh2.util.StringEncoder; + +/** + * @author Christian Plattner + * @version $Id: ClientServerHello.java 155 2014-04-28 12:01:19Z dkocher@sudo.ch $ + */ +public class ClientServerHello { + private final String client_line; + private final String server_line; + + public final static int readLineRN(InputStream is, byte[] buffer) throws IOException { + int pos = 0; + boolean need10 = false; + int len = 0; + + while (true) { + int c = is.read(); + + if (c == -1) + throw new IOException("Premature connection close"); + + buffer[pos++] = (byte) c; + + if (c == 13) { + need10 = true; + continue; + } + + if (c == 10) + break; + + if (need10 == true) + throw new IOException("Malformed line sent by the server, the line does not end correctly."); + + len++; + + if (pos >= buffer.length) + throw new IOException("The server sent a too long line."); + } + + return len; + } + + private ClientServerHello(String client_line, String server_line) { + this.client_line = client_line; + this.server_line = server_line; + } + + public static ClientServerHello clientHello(String softwareversion, InputStream bi, OutputStream bo) + throws IOException { + return exchange(softwareversion, bi, bo, true); + } + + public static ClientServerHello serverHello(String softwareversion, InputStream bi, OutputStream bo) + throws IOException { + return exchange(softwareversion, bi, bo, false); + } + + private static ClientServerHello exchange(String softwareversion, InputStream bi, OutputStream bo, boolean clientMode) + throws IOException { + String localIdentifier = String.format("SSH-2.0-%s", softwareversion); + bo.write(StringEncoder.GetBytes(String.format("%s\r\n", localIdentifier))); + bo.flush(); + // Expect SSH-protoversion-softwareversion SP comments CR LF + byte[] serverVersion = new byte[512]; + String remoteIdentifier = null; + for (int i = 0; i < 50; i++) { + int len = readLineRN(bi, serverVersion); + remoteIdentifier = new String(serverVersion, 0, len, "ISO-8859-1"); + if (remoteIdentifier.startsWith("SSH-")) break; + } + + if (remoteIdentifier.equals("")) { + throw new IOException("Premature connection close"); + } + + if (!remoteIdentifier.startsWith("SSH-")) { + throw new IOException(String.format("Malformed SSH identification %s", remoteIdentifier)); + } + + if (!remoteIdentifier.startsWith("SSH-1.99-") + && !remoteIdentifier.startsWith("SSH-2.0-")) { + throw new IOException(String.format("Incompatible remote protocol version %s", remoteIdentifier)); + } + + if (clientMode) { + return new ClientServerHello(localIdentifier, remoteIdentifier); + } + else { + return new ClientServerHello(remoteIdentifier, localIdentifier); + } + } + + /** + * @return Returns the client_versioncomment. + */ + public byte[] getClientString() { + return StringEncoder.GetBytes(client_line); + } + + /** + * @return Returns the server_versioncomment. + */ + public byte[] getServerString() { + return StringEncoder.GetBytes(server_line); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/transport/ClientTransportManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/transport/ClientTransportManager.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,54 @@ +package ch.ethz.ssh2.transport; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.security.SecureRandom; + +import ch.ethz.ssh2.DHGexParameters; +import ch.ethz.ssh2.ServerHostKeyVerifier; +import ch.ethz.ssh2.crypto.CryptoWishList; + +/** + * @version $Id: ClientTransportManager.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public class ClientTransportManager extends TransportManager { + + private final Socket sock; + + public ClientTransportManager(final Socket socket) { + super(socket); + this.sock = socket; + } + + public void setTcpNoDelay(boolean state) throws IOException { + sock.setTcpNoDelay(state); + } + + public void setSoTimeout(int timeout) throws IOException { + sock.setSoTimeout(timeout); + } + + public void connect(String hostname, int port, String softwareversion, CryptoWishList cwl, + ServerHostKeyVerifier verifier, DHGexParameters dhgex, int connectTimeout, SecureRandom rnd) + throws IOException { + // Establish the TCP connection to the SSH-2 server + this.connect(hostname, port, connectTimeout); + // Parse the server line and say hello - important: this information is later needed for the + // key exchange (to stop man-in-the-middle attacks) - that is why we wrap it into an object + // for later use. + ClientServerHello csh = ClientServerHello.clientHello(softwareversion, sock.getInputStream(), + sock.getOutputStream()); + TransportConnection tc = new TransportConnection(sock.getInputStream(), sock.getOutputStream(), rnd); + KexManager km = new ClientKexManager(this, csh, cwl, hostname, port, verifier, rnd); + super.init(tc, km); + km.initiateKEX(cwl, dhgex, null, null, null); + this.startReceiver(); + } + + protected void connect(String hostname, int port, int connectTimeout) throws IOException { + log.debug(String.format("client transport manager connecting to %s:%d", hostname, port)); + sock.connect(new InetSocketAddress(hostname, port), connectTimeout); + log.debug(String.format("client transport manager connected to %s:%d", hostname, port)); + } +} \ No newline at end of file diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/transport/DisconnectException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/transport/DisconnectException.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,22 @@ +package ch.ethz.ssh2.transport; + +import java.io.IOException; + +import ch.ethz.ssh2.packets.PacketDisconnect; + +/** + * @version $Id: DisconnectException.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public class DisconnectException extends IOException { + + private PacketDisconnect.Reason reason; + + public DisconnectException(final PacketDisconnect.Reason reason, final String message) { + super(message); + this.reason = reason; + } + + public PacketDisconnect.Reason getReason() { + return reason; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/transport/HTTPProxyClientTransportManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/transport/HTTPProxyClientTransportManager.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,103 @@ +package ch.ethz.ssh2.transport; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; + +import ch.ethz.ssh2.HTTPProxyData; +import ch.ethz.ssh2.HTTPProxyException; +import ch.ethz.ssh2.crypto.Base64; +import ch.ethz.ssh2.util.StringEncoder; + +/** + * @version $Id: HTTPProxyClientTransportManager.java 155 2014-04-28 12:01:19Z dkocher@sudo.ch $ + */ +public class HTTPProxyClientTransportManager extends ClientTransportManager { + + /** + * Used to tell the library that the connection shall be established through a proxy server. + */ + + private HTTPProxyData pd; + + private final Socket sock; + + public HTTPProxyClientTransportManager(final Socket socket, final HTTPProxyData pd) { + super(socket); + this.sock = socket; + this.pd = pd; + } + + @Override + protected void connect(final String hostname, final int port, final int connectTimeout) throws IOException { + sock.connect(new InetSocketAddress(pd.proxyHost, pd.proxyPort), connectTimeout); + // Tell the proxy where we actually want to connect to + StringBuilder sb = new StringBuilder(); + sb.append("CONNECT "); + sb.append(hostname); + sb.append(':'); + sb.append(port); + sb.append(" HTTP/1.0\r\n"); + + if ((pd.proxyUser != null) && (pd.proxyPass != null)) { + String credentials = pd.proxyUser + ":" + pd.proxyPass; + char[] encoded = Base64.encode(StringEncoder.GetBytes(credentials)); + sb.append("Proxy-Authorization: Basic "); + sb.append(encoded); + sb.append("\r\n"); + } + + if (pd.requestHeaderLines != null) { + for (int i = 0; i < pd.requestHeaderLines.length; i++) { + if (pd.requestHeaderLines[i] != null) { + sb.append(pd.requestHeaderLines[i]); + sb.append("\r\n"); + } + } + } + + sb.append("\r\n"); + OutputStream out = sock.getOutputStream(); + out.write(StringEncoder.GetBytes(sb.toString())); + out.flush(); + // Parse the HTTP response + InputStream in = sock.getInputStream(); + final LineNumberReader reader = new LineNumberReader(new InputStreamReader(in)); + String httpReponse = reader.readLine(); + + if (!httpReponse.startsWith("HTTP/")) { + throw new IOException("The proxy did not send back a valid HTTP response."); + } + + // "HTTP/1.X XYZ X" => 14 characters minimum + + if ((httpReponse.length() < 14) || (httpReponse.charAt(8) != ' ') || (httpReponse.charAt(12) != ' ')) { + throw new IOException("The proxy did not send back a valid HTTP response."); + } + + int errorCode; + + try { + errorCode = Integer.parseInt(httpReponse.substring(9, 12)); + } + catch (NumberFormatException ignore) { + throw new IOException("The proxy did not send back a valid HTTP response."); + } + + if ((errorCode < 0) || (errorCode > 999)) { + throw new IOException("The proxy did not send back a valid HTTP response."); + } + + if (errorCode != 200) { + throw new HTTPProxyException(httpReponse.substring(13), errorCode); + } + + while (reader.readLine() != null) { + // Read until empty line + } + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/transport/KexManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/transport/KexManager.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.transport; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.security.DigestException; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.SecureRandom; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.ECPrivateKey; +import java.util.Arrays; +import java.util.Set; +import java.util.TreeSet; + +import ch.ethz.ssh2.ConnectionInfo; +import ch.ethz.ssh2.DHGexParameters; +import ch.ethz.ssh2.compression.CompressionFactory; +import ch.ethz.ssh2.compression.Compressor; +import ch.ethz.ssh2.crypto.CryptoWishList; +import ch.ethz.ssh2.crypto.KeyMaterial; +import ch.ethz.ssh2.crypto.cipher.BlockCipher; +import ch.ethz.ssh2.crypto.cipher.BlockCipherFactory; +import ch.ethz.ssh2.crypto.digest.MAC; +import ch.ethz.ssh2.log.Logger; +import ch.ethz.ssh2.packets.PacketKexInit; +import ch.ethz.ssh2.packets.PacketNewKeys; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.ECPrivateKey; + +/** + * @version $Id: KexManager.java 152 2014-04-28 11:02:23Z dkocher@sudo.ch $ + */ +public abstract class KexManager implements MessageHandler { + protected static final Logger log = Logger.getLogger(KexManager.class); + + private static final Set HOSTKEY_ALGS = new TreeSet(); + static { + HOSTKEY_ALGS.add("ecdsa-sha2-nistp256"); + HOSTKEY_ALGS.add("ecdsa-sha2-nistp384"); + HOSTKEY_ALGS.add("ecdsa-sha2-nistp521"); + HOSTKEY_ALGS.add("ssh-rsa"); + HOSTKEY_ALGS.add("ssh-dss"); + } + + private static final Set KEX_ALGS = new TreeSet(); + static { + KEX_ALGS.add("ecdh-sha2-nistp256"); + KEX_ALGS.add("ecdh-sha2-nistp384"); + KEX_ALGS.add("ecdh-sha2-nistp521"); + KEX_ALGS.add("diffie-hellman-group-exchange-sha256"); + KEX_ALGS.add("diffie-hellman-group-exchange-sha1"); + KEX_ALGS.add("diffie-hellman-group14-sha1"); + KEX_ALGS.add("diffie-hellman-group1-sha1"); + } + + KexState kxs; + int kexCount = 0; + KeyMaterial km; + byte[] sessionId; + ClientServerHello csh; + + final Object accessLock = new Object(); + ConnectionInfo lastConnInfo = null; + + boolean connectionClosed = false; + + boolean ignore_next_kex_packet = false; + + final TransportManager tm; + + CryptoWishList nextKEXcryptoWishList; + DHGexParameters nextKEXdhgexParameters; + KeyPair nextKEXdsakey; + KeyPair nextKEXrsakey; + KeyPair nextKEXeckey; + + final SecureRandom rnd; + + public KexManager(TransportManager tm, ClientServerHello csh, CryptoWishList initialCwl, SecureRandom rnd) { + this.tm = tm; + this.csh = csh; + this.nextKEXcryptoWishList = initialCwl; + this.nextKEXdhgexParameters = new DHGexParameters(); + this.rnd = rnd; + } + + public ConnectionInfo getOrWaitForConnectionInfo(int minKexCount) throws IOException { + synchronized (accessLock) { + while (true) { + if ((lastConnInfo != null) && (lastConnInfo.keyExchangeCounter >= minKexCount)) { + return lastConnInfo; + } + + if (connectionClosed) { + throw(IOException) new IOException("Key exchange was not finished, connection is closed.").initCause(tm.getReasonClosedCause()); + } + + try { + accessLock.wait(); + } + catch (InterruptedException e) { + throw new InterruptedIOException(e.getMessage()); + } + } + } + } + + private String getFirstMatch(String[] client, String[] server) throws NegotiateException { + if (client == null || server == null) { + throw new IllegalArgumentException(); + } + + for (String c : client) { + for (String s : server) { + if (c.equals(s)) { + return c; + } + } + } + + throw new NegotiateException(String.format("Negotiation failed for %s", Arrays.toString(server))); + } + + private boolean compareFirstOfNameList(String[] a, String[] b) { + if (a == null || b == null) { + throw new IllegalArgumentException(); + } + + if ((a.length == 0) && (b.length == 0)) { + return true; + } + + if ((a.length == 0) || (b.length == 0)) { + return false; + } + + return (a[0].equals(b[0])); + } + + private boolean isGuessOK(KexParameters cpar, KexParameters spar) { + if (cpar == null || spar == null) { + throw new IllegalArgumentException(); + } + + if (!compareFirstOfNameList(cpar.kex_algorithms, spar.kex_algorithms)) { + return false; + } + + if (!compareFirstOfNameList(cpar.server_host_key_algorithms, spar.server_host_key_algorithms)) { + return false; + } + + /* + * We do NOT check here if the other algorithms can be agreed on, this + * is just a check if kex_algorithms and server_host_key_algorithms were + * guessed right! + */ + return true; + } + + protected NegotiatedParameters mergeKexParameters(KexParameters client, KexParameters server) + throws NegotiateException { + NegotiatedParameters np = new NegotiatedParameters(); + np.kex_algo = getFirstMatch(client.kex_algorithms, server.kex_algorithms); + log.info("kex_algo=" + np.kex_algo); + np.server_host_key_algo = getFirstMatch(client.server_host_key_algorithms, + server.server_host_key_algorithms); + log.info("server_host_key_algo=" + np.server_host_key_algo); + np.enc_algo_client_to_server = getFirstMatch(client.encryption_algorithms_client_to_server, + server.encryption_algorithms_client_to_server); + np.enc_algo_server_to_client = getFirstMatch(client.encryption_algorithms_server_to_client, + server.encryption_algorithms_server_to_client); + log.info("enc_algo_client_to_server=" + np.enc_algo_client_to_server); + log.info("enc_algo_server_to_client=" + np.enc_algo_server_to_client); + np.mac_algo_client_to_server = getFirstMatch(client.mac_algorithms_client_to_server, + server.mac_algorithms_client_to_server); + np.mac_algo_server_to_client = getFirstMatch(client.mac_algorithms_server_to_client, + server.mac_algorithms_server_to_client); + log.info("mac_algo_client_to_server=" + np.mac_algo_client_to_server); + log.info("mac_algo_server_to_client=" + np.mac_algo_server_to_client); + np.comp_algo_client_to_server = getFirstMatch(client.compression_algorithms_client_to_server, + server.compression_algorithms_client_to_server); + np.comp_algo_server_to_client = getFirstMatch(client.compression_algorithms_server_to_client, + server.compression_algorithms_server_to_client); + log.info("comp_algo_client_to_server=" + np.comp_algo_client_to_server); + log.info("comp_algo_server_to_client=" + np.comp_algo_server_to_client); + np.lang_client_to_server = getFirstMatch(client.languages_client_to_server, + server.languages_client_to_server); + np.lang_server_to_client = getFirstMatch(client.languages_server_to_client, + server.languages_server_to_client); + + if (isGuessOK(client, server)) { + np.guessOK = true; + } + + return np; + } + + public synchronized void initiateKEX(CryptoWishList cwl, DHGexParameters dhgex, KeyPair dsa, KeyPair rsa, KeyPair ec) + throws IOException { + nextKEXcryptoWishList = cwl; + nextKEXdhgexParameters = dhgex; + nextKEXdsakey = dsa; + nextKEXrsakey = rsa; + nextKEXeckey = ec; + + if (kxs == null) { + kxs = new KexState(); + kxs.local_dsa_key = dsa; + kxs.local_rsa_key = rsa; + kxs.local_ec_key = ec; + kxs.dhgexParameters = nextKEXdhgexParameters; + kxs.localKEX = new PacketKexInit(nextKEXcryptoWishList, rnd); + tm.sendKexMessage(kxs.localKEX.getPayload()); + } + } + + private boolean establishKeyMaterial() throws IOException { + try { + int mac_cs_key_len = MAC.getKeyLen(kxs.np.mac_algo_client_to_server); + int enc_cs_key_len = BlockCipherFactory.getKeySize(kxs.np.enc_algo_client_to_server); + int enc_cs_block_len = BlockCipherFactory.getBlockSize(kxs.np.enc_algo_client_to_server); + int mac_sc_key_len = MAC.getKeyLen(kxs.np.mac_algo_server_to_client); + int enc_sc_key_len = BlockCipherFactory.getKeySize(kxs.np.enc_algo_server_to_client); + int enc_sc_block_len = BlockCipherFactory.getBlockSize(kxs.np.enc_algo_server_to_client); + km = KeyMaterial.create("SHA1", kxs.H, kxs.K, sessionId, enc_cs_key_len, enc_cs_block_len, mac_cs_key_len, + enc_sc_key_len, enc_sc_block_len, mac_sc_key_len); + } + catch (IllegalArgumentException e) { + return false; + } + + return true; + } + + protected void finishKex(boolean clientMode) throws IOException { + if (sessionId == null) { + sessionId = kxs.H; + } + + establishKeyMaterial(); + /* Tell the other side that we start using the new material */ + PacketNewKeys ign = new PacketNewKeys(); + tm.sendKexMessage(ign.getPayload()); + BlockCipher cbc; + MAC mac; + Compressor comp; + + try { + cbc = BlockCipherFactory.createCipher(clientMode ? kxs.np.enc_algo_client_to_server + : kxs.np.enc_algo_server_to_client, true, clientMode ? km.enc_key_client_to_server + : km.enc_key_server_to_client, clientMode ? km.initial_iv_client_to_server + : km.initial_iv_server_to_client); + + try { + mac = new MAC(clientMode ? kxs.np.mac_algo_client_to_server : kxs.np.mac_algo_server_to_client, clientMode + ? km.integrity_key_client_to_server : km.integrity_key_server_to_client); + } + catch (DigestException e) { + throw new IOException(e); + } + + comp = CompressionFactory.createCompressor(kxs.np.comp_algo_client_to_server); + } + catch (IllegalArgumentException f) { + throw new IOException(String.format("Fatal error initializing ciphers. %s", f.getMessage())); + } + + tm.changeSendCipher(cbc, mac); + tm.changeSendCompression(comp); + tm.kexFinished(); + } + + public static String[] getDefaultServerHostkeyAlgorithmList() { + return HOSTKEY_ALGS.toArray(new String[HOSTKEY_ALGS.size()]); + } + + public static void checkServerHostkeyAlgorithmsList(String[] algos) { + for (final String algo : algos) { + if (!HOSTKEY_ALGS.contains(algo)) + throw new IllegalArgumentException("Unknown server host key algorithm '" + algo + "'"); + } + } + + public static String[] getDefaultClientKexAlgorithmList() { + return KEX_ALGS.toArray(new String[KEX_ALGS.size()]); + } + + public static String[] getDefaultServerKexAlgorithmList() { + return KEX_ALGS.toArray(new String[KEX_ALGS.size()]); + } + + public static void checkKexAlgorithmList(String[] algos) { + for (final String algo : algos) { + if (!KEX_ALGS.contains(algo)) + throw new IllegalArgumentException("Unknown kex algorithm '" + algo + "'"); + } + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/transport/KexParameters.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/transport/KexParameters.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.transport; + +import java.util.Arrays; + +/** + * @author Christian Plattner + * @version $Id: KexParameters.java 159 2014-05-01 14:12:34Z dkocher@sudo.ch $ + */ +public class KexParameters { + public byte[] cookie; + public String[] kex_algorithms; + public String[] server_host_key_algorithms; + public String[] encryption_algorithms_client_to_server; + public String[] encryption_algorithms_server_to_client; + public String[] mac_algorithms_client_to_server; + public String[] mac_algorithms_server_to_client; + public String[] compression_algorithms_client_to_server; + public String[] compression_algorithms_server_to_client; + public String[] languages_client_to_server; + public String[] languages_server_to_client; + public boolean first_kex_packet_follows; + public int reserved_field1; + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("KexParameters{"); + sb.append("cookie=").append(Arrays.toString(cookie)); + sb.append(", kex_algorithms=").append(Arrays.toString(kex_algorithms)); + sb.append(", server_host_key_algorithms=").append(Arrays.toString(server_host_key_algorithms)); + sb.append(", encryption_algorithms_client_to_server=").append(Arrays.toString(encryption_algorithms_client_to_server)); + sb.append(", encryption_algorithms_server_to_client=").append(Arrays.toString(encryption_algorithms_server_to_client)); + sb.append(", mac_algorithms_client_to_server=").append(Arrays.toString(mac_algorithms_client_to_server)); + sb.append(", mac_algorithms_server_to_client=").append(Arrays.toString(mac_algorithms_server_to_client)); + sb.append(", compression_algorithms_client_to_server=").append(Arrays.toString(compression_algorithms_client_to_server)); + sb.append(", compression_algorithms_server_to_client=").append(Arrays.toString(compression_algorithms_server_to_client)); + sb.append(", languages_client_to_server=").append(Arrays.toString(languages_client_to_server)); + sb.append(", languages_server_to_client=").append(Arrays.toString(languages_server_to_client)); + sb.append(", first_kex_packet_follows=").append(first_kex_packet_follows); + sb.append(", reserved_field1=").append(reserved_field1); + sb.append('}'); + return sb.toString(); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/transport/KexState.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/transport/KexState.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.transport; + +import ch.ethz.ssh2.DHGexParameters; +import ch.ethz.ssh2.crypto.dh.GenericDhExchange; +import ch.ethz.ssh2.crypto.dh.DhGroupExchange; +import java.math.BigInteger; +import ch.ethz.ssh2.packets.PacketKexInit; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.ECPrivateKey; + +/** + * KexState. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class KexState { + public PacketKexInit localKEX; + public PacketKexInit remoteKEX; + public NegotiatedParameters np; + public int state = 0; + + public BigInteger K; + public byte[] H; + + public byte[] remote_hostkey; + + public String hashAlgo; + public GenericDhExchange dhx; + public DhGroupExchange dhgx; + public DHGexParameters dhgexParameters; + + public KeyPair local_dsa_key; + public KeyPair local_rsa_key; + public KeyPair local_ec_key; +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/transport/MessageHandler.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/transport/MessageHandler.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.transport; + +import java.io.IOException; + +/** + * @author Christian Plattner + * @version $Id: MessageHandler.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public interface MessageHandler { + public void handleMessage(byte[] msg) throws IOException; + + public void handleFailure(IOException failure); +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/transport/NegotiateException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/transport/NegotiateException.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.transport; + +import java.io.IOException; + +/** + * @version $Id: NegotiateException.java 149 2014-04-28 09:18:35Z dkocher@sudo.ch $ + */ +public class NegotiateException extends IOException { + private static final long serialVersionUID = 3689910669428143157L; + + public NegotiateException() { + // + } + + public NegotiateException(String message) { + super(message); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/transport/NegotiatedParameters.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/transport/NegotiatedParameters.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.transport; + +/** + * @author Christian Plattner + * @version $Id: NegotiatedParameters.java 159 2014-05-01 14:12:34Z dkocher@sudo.ch $ + */ +public class NegotiatedParameters { + public boolean guessOK; + public String kex_algo; + public String server_host_key_algo; + public String enc_algo_client_to_server; + public String enc_algo_server_to_client; + public String mac_algo_client_to_server; + public String mac_algo_server_to_client; + public String comp_algo_client_to_server; + public String comp_algo_server_to_client; + public String lang_client_to_server; + public String lang_server_to_client; + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("NegotiatedParameters{"); + sb.append("guessOK=").append(guessOK); + sb.append(", kex_algo='").append(kex_algo).append('\''); + sb.append(", server_host_key_algo='").append(server_host_key_algo).append('\''); + sb.append(", enc_algo_client_to_server='").append(enc_algo_client_to_server).append('\''); + sb.append(", enc_algo_server_to_client='").append(enc_algo_server_to_client).append('\''); + sb.append(", mac_algo_client_to_server='").append(mac_algo_client_to_server).append('\''); + sb.append(", mac_algo_server_to_client='").append(mac_algo_server_to_client).append('\''); + sb.append(", comp_algo_client_to_server='").append(comp_algo_client_to_server).append('\''); + sb.append(", comp_algo_server_to_client='").append(comp_algo_server_to_client).append('\''); + sb.append(", lang_client_to_server='").append(lang_client_to_server).append('\''); + sb.append(", lang_server_to_client='").append(lang_server_to_client).append('\''); + sb.append('}'); + return sb.toString(); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/transport/ServerKexManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/transport/ServerKexManager.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2006-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.transport; + +import java.io.IOException; +import java.security.DigestException; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.math.BigInteger; + +import ch.ethz.ssh2.ConnectionInfo; +import ch.ethz.ssh2.PacketTypeException; +import ch.ethz.ssh2.auth.ServerAuthenticationManager; +import ch.ethz.ssh2.crypto.cipher.BlockCipher; +import ch.ethz.ssh2.crypto.cipher.BlockCipherFactory; +import ch.ethz.ssh2.crypto.dh.GenericDhExchange; +import ch.ethz.ssh2.crypto.digest.MAC; +import ch.ethz.ssh2.packets.PacketKexDHInit; +import ch.ethz.ssh2.packets.PacketKexDHReply; +import ch.ethz.ssh2.packets.PacketKexInit; +import ch.ethz.ssh2.packets.Packets; +import ch.ethz.ssh2.server.ServerConnectionState; +import ch.ethz.ssh2.signature.DSASHA1Verify; +import ch.ethz.ssh2.signature.ECDSASHA2Verify; +import ch.ethz.ssh2.signature.RSASHA1Verify; + +/** + * @version $Id: ServerKexManager.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public class ServerKexManager extends KexManager { + + private final ServerConnectionState state; + + private boolean authenticationStarted = false; + + public ServerKexManager(ServerConnectionState state) { + super(state.tm, state.csh, state.next_cryptoWishList, state.generator); + this.state = state; + } + + public void handleFailure(final IOException failure) { + synchronized (accessLock) { + connectionClosed = true; + accessLock.notifyAll(); + } + } + + public void handleMessage(byte[] msg) throws IOException { + PacketKexInit kip; + + if (msg == null) { + synchronized (accessLock) { + connectionClosed = true; + accessLock.notifyAll(); + return; + } + } + + if ((kxs == null) && (msg[0] != Packets.SSH_MSG_KEXINIT)) { + throw new PacketTypeException(msg[0]); + } + + if (ignore_next_kex_packet) { + ignore_next_kex_packet = false; + return; + } + + if (msg[0] == Packets.SSH_MSG_KEXINIT) { + if ((kxs != null) && (kxs.state != 0)) { + throw new PacketTypeException(msg[0]); + } + + if (kxs == null) { + /* + * Ah, OK, peer wants to do KEX. Let's be nice and play + * together. + */ + kxs = new KexState(); + kxs.local_dsa_key = nextKEXdsakey; + kxs.local_rsa_key = nextKEXrsakey; + kxs.local_ec_key = nextKEXeckey; + kxs.dhgexParameters = nextKEXdhgexParameters; + kip = new PacketKexInit(nextKEXcryptoWishList, rnd); + kxs.localKEX = kip; + tm.sendKexMessage(kip.getPayload()); + } + + kip = new PacketKexInit(msg); + kxs.remoteKEX = kip; + kxs.np = mergeKexParameters(kxs.remoteKEX.getKexParameters(), kxs.localKEX.getKexParameters()); + + if (kxs.remoteKEX.isFirst_kex_packet_follows() && (kxs.np.guessOK == false)) { + // Guess was wrong, we need to ignore the next kex packet. + ignore_next_kex_packet = true; + } + + if (kxs.np.kex_algo.equals("diffie-hellman-group1-sha1") || + kxs.np.kex_algo.equals("diffie-hellman-group14-sha1") || + kxs.np.kex_algo.equals("ecdh-sha2-nistp256") || + kxs.np.kex_algo.equals("ecdh-sha2-nistp384") || + kxs.np.kex_algo.equals("ecdh-sha2-nistp521")) { + kxs.dhx = GenericDhExchange.getInstance(kxs.np.kex_algo); + kxs.dhx.init(kxs.np.kex_algo); + kxs.state = 1; + return; + } + + throw new IllegalStateException("Unkown KEX method!"); + } + + if (msg[0] == Packets.SSH_MSG_NEWKEYS) { + if (km == null) { + throw new IOException("Peer sent SSH_MSG_NEWKEYS, but I have no key material ready!"); + } + + BlockCipher cbc; + MAC mac; + + try { + cbc = BlockCipherFactory.createCipher(kxs.np.enc_algo_client_to_server, false, + km.enc_key_client_to_server, km.initial_iv_client_to_server); + + try { + mac = new MAC(kxs.np.mac_algo_client_to_server, km.integrity_key_client_to_server); + } + catch (DigestException e) { + throw new IOException(e); + } + } + catch (IllegalArgumentException e) { + throw new IOException(e); + } + + tm.changeRecvCipher(cbc, mac); + ConnectionInfo sci = new ConnectionInfo(); + kexCount++; + sci.keyExchangeAlgorithm = kxs.np.kex_algo; + sci.keyExchangeCounter = kexCount; + sci.clientToServerCryptoAlgorithm = kxs.np.enc_algo_client_to_server; + sci.serverToClientCryptoAlgorithm = kxs.np.enc_algo_server_to_client; + sci.clientToServerMACAlgorithm = kxs.np.mac_algo_client_to_server; + sci.serverToClientMACAlgorithm = kxs.np.mac_algo_server_to_client; + sci.serverHostKeyAlgorithm = kxs.np.server_host_key_algo; + sci.serverHostKey = kxs.remote_hostkey; + + synchronized (accessLock) { + lastConnInfo = sci; + accessLock.notifyAll(); + } + + kxs = null; + return; + } + + if ((kxs == null) || (kxs.state == 0)) { + throw new IOException("Unexpected Kex submessage!"); + } + + if (kxs.np.kex_algo.equals("diffie-hellman-group1-sha1") || + kxs.np.kex_algo.equals("diffie-hellman-group14-sha1") || + kxs.np.kex_algo.equals("ecdh-sha2-nistp256") || + kxs.np.kex_algo.equals("ecdh-sha2-nistp384") || + kxs.np.kex_algo.equals("ecdh-sha2-nistp521")) { + if (kxs.state == 1) { + PacketKexDHInit dhi = new PacketKexDHInit(msg); + kxs.dhx.setE(dhi.getE()); + byte[] hostKey = null; + + if (kxs.np.server_host_key_algo.startsWith("ecdsa-sha2-")) { + hostKey = ECDSASHA2Verify.encodeSSHECDSAPublicKey((ECPublicKey)kxs.local_ec_key.getPublic()); + } + + if (kxs.np.server_host_key_algo.equals("ssh-rsa")) { + hostKey = RSASHA1Verify.encodeSSHRSAPublicKey((RSAPublicKey)kxs.local_rsa_key.getPublic()); + } + + if (kxs.np.server_host_key_algo.equals("ssh-dss")) { + hostKey = DSASHA1Verify.encodeSSHDSAPublicKey((DSAPublicKey)kxs.local_dsa_key.getPublic()); + } + + try { + kxs.H = kxs.dhx.calculateH(csh.getClientString(), csh.getServerString(), + kxs.remoteKEX.getPayload(), kxs.localKEX.getPayload(), hostKey); + } + catch (IllegalArgumentException e) { + throw new IOException("KEX error.", e); + } + + kxs.K = kxs.dhx.getK(); + byte[] signature = null; + + if (kxs.np.server_host_key_algo.startsWith("ecdsa-sha2-")) { + ECPrivateKey pk = (ECPrivateKey)kxs.local_ec_key.getPrivate(); + byte[] es = ECDSASHA2Verify.generateSignature(kxs.H, pk); + signature = ECDSASHA2Verify.encodeSSHECDSASignature(es, pk.getParams()); + } + + if (kxs.np.server_host_key_algo.equals("ssh-rsa")) { + byte[] rs = RSASHA1Verify.generateSignature(kxs.H, (RSAPrivateKey)kxs.local_rsa_key.getPrivate()); + signature = RSASHA1Verify.encodeSSHRSASignature(rs); + } + + if (kxs.np.server_host_key_algo.equals("ssh-dss")) { + byte[] ds = DSASHA1Verify.generateSignature(kxs.H, (DSAPrivateKey)kxs.local_dsa_key.getPrivate(), rnd); + signature = DSASHA1Verify.encodeSSHDSASignature(ds); + } + + PacketKexDHReply dhr = new PacketKexDHReply(hostKey, new BigInteger(kxs.dhx.getF()), signature); + tm.sendKexMessage(dhr.getPayload()); + finishKex(false); + kxs.state = -1; + + if (authenticationStarted == false) { + authenticationStarted = true; + state.am = new ServerAuthenticationManager(state); + } + + return; + } + } + + throw new IllegalStateException(String.format("Unknown KEX method %s", kxs.np.kex_algo)); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/transport/ServerTransportManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/transport/ServerTransportManager.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,34 @@ +package ch.ethz.ssh2.transport; + +import java.io.IOException; +import java.net.Socket; + +import ch.ethz.ssh2.server.ServerConnectionState; + +/** + * @version $Id: ServerTransportManager.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public class ServerTransportManager extends TransportManager { + + private final Socket sock; + + public ServerTransportManager(final Socket socket) { + super(socket); + // TCP connection is already established + this.sock = socket; + } + + public void connect(ServerConnectionState state) throws IOException { + /* Parse the client lin + e and say hello - important: this information is later needed for the + * key exchange (to stop man-in-the-middle attacks) - that is why we wrap it into an object + * for later use. + */ + state.csh = ClientServerHello.serverHello(state.softwareversion, sock.getInputStream(), sock.getOutputStream()); + TransportConnection tc = new TransportConnection(sock.getInputStream(), sock.getOutputStream(), state.generator); + KexManager km = new ServerKexManager(state); + super.init(tc, km); + km.initiateKEX(state.next_cryptoWishList, null, state.next_dsa_key, state.next_rsa_key, state.next_ec_key); + this.startReceiver(); + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/transport/TransportConnection.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/transport/TransportConnection.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,317 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.transport; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.SecureRandom; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.compression.Compressor; +import ch.ethz.ssh2.crypto.cipher.BlockCipher; +import ch.ethz.ssh2.crypto.cipher.CipherInputStream; +import ch.ethz.ssh2.crypto.cipher.CipherOutputStream; +import ch.ethz.ssh2.crypto.cipher.NullCipher; +import ch.ethz.ssh2.crypto.digest.MAC; +import ch.ethz.ssh2.log.Logger; +import ch.ethz.ssh2.packets.Packets; + +/** + * TransportConnection. + * + * @author Christian Plattner + * @version $Id: TransportConnection.java 144 2014-04-25 12:48:25Z dkocher@sudo.ch $ + */ +public class TransportConnection { + private static final Logger log = Logger.getLogger(TransportConnection.class); + + int send_seq_number = 0; + + int recv_seq_number = 0; + + CipherInputStream cis; + + CipherOutputStream cos; + + boolean useRandomPadding; + + /* Depends on current MAC and CIPHER */ + + MAC send_mac; + + byte[] send_mac_buffer; + + int send_padd_blocksize = 8; + + MAC recv_mac; + + byte[] recv_mac_buffer; + + byte[] recv_mac_buffer_cmp; + + int recv_padd_blocksize = 8; + + Compressor recv_comp; + + Compressor send_comp; + + boolean can_compress; + + byte[] recv_comp_buffer; + + byte[] send_comp_buffer; + + /* won't change */ + + final byte[] send_padding_buffer = new byte[256]; + + final byte[] send_packet_header_buffer = new byte[5]; + + final byte[] recv_padding_buffer = new byte[256]; + + final byte[] recv_packet_header_buffer = new byte[5]; + + boolean recv_packet_header_present = false; + + ClientServerHello csh; + + final SecureRandom rnd; + + public TransportConnection(InputStream is, OutputStream os, SecureRandom rnd) { + this.cis = new CipherInputStream(new NullCipher(), is); + this.cos = new CipherOutputStream(new NullCipher(), os); + this.rnd = rnd; + } + + public void changeRecvCipher(BlockCipher bc, MAC mac) { + cis.changeCipher(bc); + recv_mac = mac; + recv_mac_buffer = (mac != null) ? new byte[mac.size()] : null; + recv_mac_buffer_cmp = (mac != null) ? new byte[mac.size()] : null; + recv_padd_blocksize = bc.getBlockSize(); + + if (recv_padd_blocksize < 8) { + recv_padd_blocksize = 8; + } + } + + public void changeSendCipher(BlockCipher bc, MAC mac) { + if ((bc instanceof NullCipher) == false) { + /* Only use zero byte padding for the first few packets */ + useRandomPadding = true; + /* Once we start encrypting, there is no way back */ + } + + cos.changeCipher(bc); + send_mac = mac; + send_mac_buffer = (mac != null) ? new byte[mac.size()] : null; + send_padd_blocksize = bc.getBlockSize(); + + if (send_padd_blocksize < 8) { + send_padd_blocksize = 8; + } + } + + public void changeRecvCompression(Compressor comp) { + recv_comp = comp; + + if (comp != null) { + recv_comp_buffer = new byte[comp.getBufferSize()]; + } + } + + public void changeSendCompression(Compressor comp) { + send_comp = comp; + + if (comp != null) { + send_comp_buffer = new byte[comp.getBufferSize()]; + } + } + + public void sendMessage(byte[] message) throws IOException { + sendMessage(message, 0, message.length, 0); + } + + public void sendMessage(byte[] message, int off, int len) throws IOException { + sendMessage(message, off, len, 0); + } + + public int getPacketOverheadEstimate() { + // return an estimate for the paket overhead (for send operations) + return 5 + 4 + (send_padd_blocksize - 1) + send_mac_buffer.length; + } + + public void sendMessage(byte[] message, int off, int len, int padd) throws IOException { + if (padd < 4) { + padd = 4; + } + else if (padd > 64) { + padd = 64; + } + + if (send_comp != null && can_compress) { + len = send_comp.compress(message, off, len, send_comp_buffer); + message = send_comp_buffer; + } + + int packet_len = 5 + len + padd; /* Minimum allowed padding is 4 */ + int slack = packet_len % send_padd_blocksize; + + if (slack != 0) { + packet_len += (send_padd_blocksize - slack); + } + + if (packet_len < 16) { + packet_len = 16; + } + + int padd_len = packet_len - (5 + len); + + if (useRandomPadding) { + for (int i = 0; i < padd_len; i = i + 4) { + /* + * don't waste calls to rnd.nextInt() (by using only 8bit of the + * output). just believe me: even though we may write here up to 3 + * bytes which won't be used, there is no "buffer overflow" (i.e., + * arrayindexoutofbounds). the padding buffer is big enough =) (256 + * bytes, and that is bigger than any current cipher block size + 64). + */ + int r = rnd.nextInt(); + send_padding_buffer[i] = (byte) r; + send_padding_buffer[i + 1] = (byte)(r >> 8); + send_padding_buffer[i + 2] = (byte)(r >> 16); + send_padding_buffer[i + 3] = (byte)(r >> 24); + } + } + else { + /* use zero padding for unencrypted traffic */ + for (int i = 0; i < padd_len; i++) { + send_padding_buffer[i] = 0; + } + + /* Actually this code is paranoid: we never filled any + * bytes into the padding buffer so far, therefore it should + * consist of zeros only. + */ + } + + send_packet_header_buffer[0] = (byte)((packet_len - 4) >> 24); + send_packet_header_buffer[1] = (byte)((packet_len - 4) >> 16); + send_packet_header_buffer[2] = (byte)((packet_len - 4) >> 8); + send_packet_header_buffer[3] = (byte)((packet_len - 4)); + send_packet_header_buffer[4] = (byte) padd_len; + cos.write(send_packet_header_buffer, 0, 5); + cos.write(message, off, len); + cos.write(send_padding_buffer, 0, padd_len); + + if (send_mac != null) { + send_mac.initMac(send_seq_number); + send_mac.update(send_packet_header_buffer, 0, 5); + send_mac.update(message, off, len); + send_mac.update(send_padding_buffer, 0, padd_len); + send_mac.getMac(send_mac_buffer, 0); + cos.writePlain(send_mac_buffer, 0, send_mac_buffer.length); + } + + cos.flush(); + + if (log.isDebugEnabled()) { + log.debug("Sent " + Packets.getMessageName(message[off] & 0xff) + " " + len + " bytes payload"); + } + + send_seq_number++; + } + + public int peekNextMessageLength() throws IOException { + if (recv_packet_header_present == false) { + cis.read(recv_packet_header_buffer, 0, 5); + recv_packet_header_present = true; + } + + int packet_length = ((recv_packet_header_buffer[0] & 0xff) << 24) + | ((recv_packet_header_buffer[1] & 0xff) << 16) | ((recv_packet_header_buffer[2] & 0xff) << 8) + | ((recv_packet_header_buffer[3] & 0xff)); + int padding_length = recv_packet_header_buffer[4] & 0xff; + + if (packet_length > TransportManager.MAX_PACKET_SIZE || packet_length < 12) { + throw new PacketFormatException(String.format("Illegal packet size (%d)", packet_length)); + } + + int payload_length = packet_length - padding_length - 1; + + if (payload_length < 0) { + throw new PacketFormatException(String.format("Illegal padding_length in packet from remote (%d)", padding_length)); + } + + return payload_length; + } + + public int receiveMessage(byte buffer[], int off, int len) throws IOException { + if (recv_packet_header_present == false) { + cis.read(recv_packet_header_buffer, 0, 5); + } + else { + recv_packet_header_present = false; + } + + int packet_length = ((recv_packet_header_buffer[0] & 0xff) << 24) + | ((recv_packet_header_buffer[1] & 0xff) << 16) | ((recv_packet_header_buffer[2] & 0xff) << 8) + | ((recv_packet_header_buffer[3] & 0xff)); + int padding_length = recv_packet_header_buffer[4] & 0xff; + + if (packet_length > TransportManager.MAX_PACKET_SIZE || packet_length < 12) { + throw new PacketFormatException(String.format("Illegal packet size (%d)", packet_length)); + } + + int payload_length = packet_length - padding_length - 1; + + if (payload_length < 0) { + throw new PacketFormatException(String.format("Illegal padding_length in packet from remote (%d)", padding_length)); + } + + if (payload_length >= len) { + throw new IOException("Receive buffer too small (" + len + ", need " + payload_length + ")"); + } + + cis.read(buffer, off, payload_length); + cis.read(recv_padding_buffer, 0, padding_length); + + if (recv_mac != null) { + cis.readPlain(recv_mac_buffer, 0, recv_mac_buffer.length); + recv_mac.initMac(recv_seq_number); + recv_mac.update(recv_packet_header_buffer, 0, 5); + recv_mac.update(buffer, off, payload_length); + recv_mac.update(recv_padding_buffer, 0, padding_length); + recv_mac.getMac(recv_mac_buffer_cmp, 0); + + for (int i = 0; i < recv_mac_buffer.length; i++) { + if (recv_mac_buffer[i] != recv_mac_buffer_cmp[i]) { + throw new IOException("Remote sent corrupt MAC."); + } + } + } + + recv_seq_number++; + + if (log.isDebugEnabled()) { + log.debug("Received " + Packets.getMessageName(buffer[off] & 0xff) + " " + payload_length + + " bytes payload"); + } + + if (recv_comp != null && can_compress) { + int[] uncomp_len = new int[] {payload_length}; + buffer = recv_comp.uncompress(buffer, off, uncomp_len); + return uncomp_len[0]; + } + else { + return payload_length; + } + } + + public void startCompression() { + can_compress = true; + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/transport/TransportManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/transport/TransportManager.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,505 @@ +/* + * Copyright (c) 2006-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2.transport; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.Socket; +import java.security.KeyPair; +import java.util.ArrayList; +import java.util.List; + +import ch.ethz.ssh2.ConnectionInfo; +import ch.ethz.ssh2.ConnectionMonitor; +import ch.ethz.ssh2.DHGexParameters; +import ch.ethz.ssh2.PacketTypeException; +import ch.ethz.ssh2.compression.Compressor; +import ch.ethz.ssh2.crypto.CryptoWishList; +import ch.ethz.ssh2.crypto.cipher.BlockCipher; +import ch.ethz.ssh2.crypto.digest.MAC; +import ch.ethz.ssh2.log.Logger; +import ch.ethz.ssh2.packets.PacketDisconnect; +import ch.ethz.ssh2.packets.Packets; +import ch.ethz.ssh2.packets.TypesReader; + +/** + * Yes, the "standard" is a big mess. On one side, the say that arbitrary channel + * packets are allowed during kex exchange, on the other side we need to blindly + * ignore the next _packet_ if the KEX guess was wrong. Where do we know from that + * the next packet is not a channel data packet? Yes, we could check if it is in + * the KEX range. But the standard says nothing about this. The OpenSSH guys + * block local "normal" traffic during KEX. That's fine - however, they assume + * that the other side is doing the same. During re-key, if they receive traffic + * other than KEX, they become horribly irritated and kill the connection. Since + * we are very likely going to communicate with OpenSSH servers, we have to play + * the same game - even though we could do better. + * + * @author Christian Plattner + * @version $Id: TransportManager.java 161 2014-05-01 18:01:55Z dkocher@sudo.ch $ + */ +public abstract class TransportManager { + protected static final Logger log = Logger.getLogger(TransportManager.class); + + private static final class HandlerEntry { + MessageHandler mh; + int low; + int high; + } + + /** + * Advertised maximum SSH packet size that the other side can send to us. + */ + public static final int MAX_PACKET_SIZE = 64 * 1024; + + private final List asynchronousQueue + = new ArrayList(); + + private Thread asynchronousThread = null; + private boolean asynchronousPending = false; + + private Socket socket; + + protected TransportManager(final Socket socket) { + this.socket = socket; + } + + private static final class AsynchronousEntry { + public byte[] message; + + public AsynchronousEntry(byte[] message) { + this.message = message; + } + } + + private final class AsynchronousWorker implements Runnable { + public void run() { + while (true) { + final AsynchronousEntry item; + + synchronized (asynchronousQueue) { + if (asynchronousQueue.size() == 0) { + // Only now we may reset the flag, since we are sure that all queued items + // have been sent (there is a slight delay between de-queuing and sending, + // this is why we need this flag! See code below. Sending takes place outside + // of this lock, this is why a test for size()==0 (from another thread) does not ensure + // that all messages have been sent. + asynchronousPending = false; + // Notify any senders that they can proceed, all async messages have been delivered + asynchronousQueue.notifyAll(); + + // After the queue is empty for about 2 seconds, stop this thread + try { + asynchronousQueue.wait(2000); + } + catch (InterruptedException ignore) { + // + } + + if (asynchronousQueue.size() == 0) { + asynchronousThread = null; + return; + } + } + + item = asynchronousQueue.remove(0); + } + + try { + sendMessageImmediate(item.message); + } + catch (IOException e) { + // There is no point in handling it - it simply means that the connection has a problem and we should stop + // sending asynchronously messages. We do not need to signal that we have exited (asynchronousThread = null): + // further messages in the queue cannot be sent by this or any other thread. + // Other threads will sooner or later (when receiving or sending the next message) get the + // same IOException and get to the same conclusion. + log.warning(e.getMessage()); + return; + } + } + } + } + + private final Object connectionSemaphore = new Object(); + + private boolean flagKexOngoing; + + private boolean connectionClosed; + private Throwable reasonClosedCause; + + private TransportConnection tc; + private KexManager km; + + private final List messageHandlers = new ArrayList(); + + private List connectionMonitors = new ArrayList(); + boolean monitorsWereInformed = false; + + protected void init(TransportConnection tc, KexManager km) { + this.tc = tc; + this.km = km; + } + + public int getPacketOverheadEstimate() { + return tc.getPacketOverheadEstimate(); + } + + public ConnectionInfo getConnectionInfo(int kexNumber) throws IOException { + return km.getOrWaitForConnectionInfo(kexNumber); + } + + public Throwable getReasonClosedCause() { + synchronized (connectionSemaphore) { + return reasonClosedCause; + } + } + + public byte[] getSessionIdentifier() { + return km.sessionId; + } + + public void close(Throwable cause, boolean useDisconnectPacket) { + if (useDisconnectPacket == false) { + // OK, hard shutdown - do not acquire the semaphore, + // perhaps somebody is inside (and waits until + // the remote side is ready to accept new data). + try { + socket.close(); + } + catch (IOException ignore) { + } + + // OK, whoever tried to send data, should now agree that + // there is no point in further waiting =) + // It is safe now to acquire the semaphore. + } + + synchronized (connectionSemaphore) { + if (!connectionClosed) { + if (useDisconnectPacket == true) { + try { + if (tc != null) + tc.sendMessage(new PacketDisconnect(PacketDisconnect.Reason.SSH_DISCONNECT_BY_APPLICATION, "").getPayload()); + } + catch (IOException ignore) { + } + + try { + socket.close(); + } + catch (IOException ignore) { + } + } + + connectionClosed = true; + reasonClosedCause = cause; + } + + connectionSemaphore.notifyAll(); + } + + // check if we need to inform the monitors + List monitors = null; + + synchronized (this) { + // Short term lock to protect "connectionMonitors" + // and "monitorsWereInformed" + // (they may be modified concurrently) + if (monitorsWereInformed == false) { + monitorsWereInformed = true; + monitors = new ArrayList(connectionMonitors); + } + } + + if (monitors != null) { + for (ConnectionMonitor cmon : monitors) { + try { + cmon.connectionLost(reasonClosedCause); + } + catch (Exception ignore) { + } + } + } + } + + protected void startReceiver() throws IOException { + final Thread receiveThread = new Thread(new Runnable() { + public void run() { + try { + receiveLoop(); + // Can only exit with exception + } + catch (IOException e) { + close(e, false); + log.warning(e.getMessage()); + + // Tell all handlers that it is time to say goodbye + if (km != null) { + km.handleFailure(e); + } + + for (HandlerEntry he : messageHandlers) { + he.mh.handleFailure(e); + } + } + + if (log.isDebugEnabled()) { + log.debug("Receive thread: back from receiveLoop"); + } + } + }); + receiveThread.setName("Transport Manager"); + receiveThread.setDaemon(true); + receiveThread.start(); + } + + public void registerMessageHandler(MessageHandler mh, int low, int high) { + HandlerEntry he = new HandlerEntry(); + he.mh = mh; + he.low = low; + he.high = high; + + synchronized (messageHandlers) { + messageHandlers.add(he); + } + } + + public void removeMessageHandler(MessageHandler handler) { + synchronized (messageHandlers) { + for (int i = 0; i < messageHandlers.size(); i++) { + HandlerEntry he = messageHandlers.get(i); + + if (he.mh == handler) { + messageHandlers.remove(i); + break; + } + } + } + } + + public void sendKexMessage(byte[] msg) throws IOException { + synchronized (connectionSemaphore) { + if (connectionClosed) { + throw(IOException) new IOException("Sorry, this connection is closed.").initCause(reasonClosedCause); + } + + flagKexOngoing = true; + + try { + tc.sendMessage(msg); + } + catch (IOException e) { + close(e, false); + throw e; + } + } + } + + public void kexFinished() throws IOException { + synchronized (connectionSemaphore) { + flagKexOngoing = false; + connectionSemaphore.notifyAll(); + } + } + + /** + * @param cwl Crypto wishlist + * @param dhgex Diffie-hellman group exchange + * @param dsa may be null if this is a client connection + * @param rsa may be null if this is a client connection + * @throws IOException + */ + public void forceKeyExchange(CryptoWishList cwl, DHGexParameters dhgex, KeyPair dsa, KeyPair rsa, KeyPair ec) + throws IOException { + synchronized (connectionSemaphore) { + if (connectionClosed) { + throw(IOException) new IOException("Sorry, this connection is closed.").initCause(reasonClosedCause); + } + } + + km.initiateKEX(cwl, dhgex, dsa, rsa, ec); + } + + public void changeRecvCipher(BlockCipher bc, MAC mac) { + tc.changeRecvCipher(bc, mac); + } + + public void changeSendCipher(BlockCipher bc, MAC mac) { + tc.changeSendCipher(bc, mac); + } + + public void changeRecvCompression(Compressor comp) { + tc.changeRecvCompression(comp); + } + + public void changeSendCompression(Compressor comp) { + tc.changeSendCompression(comp); + } + + public void sendAsynchronousMessage(byte[] msg) throws IOException { + synchronized (asynchronousQueue) { + asynchronousQueue.add(new AsynchronousEntry(msg)); + asynchronousPending = true; + + /* This limit should be flexible enough. We need this, otherwise the peer + * can flood us with global requests (and other stuff where we have to reply + * with an asynchronous message) and (if the server just sends data and does not + * read what we send) this will probably put us in a low memory situation + * (our send queue would grow and grow and...) */ + + if (asynchronousQueue.size() > 100) { + throw new IOException("The peer is not consuming our asynchronous replies."); + } + + // Check if we have an asynchronous sending thread + if (asynchronousThread == null) { + asynchronousThread = new Thread(new AsynchronousWorker()); + asynchronousThread.setDaemon(true); + asynchronousThread.start(); + // The thread will stop after 2 seconds of inactivity (i.e., empty queue) + } + + asynchronousQueue.notifyAll(); + } + } + + public void setConnectionMonitors(List monitors) { + synchronized (this) { + connectionMonitors = new ArrayList(monitors); + } + } + + /** + * Send a message but ensure that all queued messages are being sent first. + * + * @param msg Message + * @throws IOException + */ + public void sendMessage(byte[] msg) throws IOException { + synchronized (asynchronousQueue) { + while (asynchronousPending) { + try { + asynchronousQueue.wait(); + } + catch (InterruptedException e) { + throw new InterruptedIOException(e.getMessage()); + } + } + } + + sendMessageImmediate(msg); + } + + /** + * Send message, ignore queued async messages that have not been delivered yet. + * Will be called directly from the asynchronousThread thread. + * + * @param msg Message + * @throws IOException + */ + public void sendMessageImmediate(byte[] msg) throws IOException { + synchronized (connectionSemaphore) { + while (true) { + if (connectionClosed) { + throw(IOException) new IOException("Sorry, this connection is closed.").initCause(reasonClosedCause); + } + + if (!flagKexOngoing) { + break; + } + + try { + connectionSemaphore.wait(); + } + catch (InterruptedException e) { + throw new InterruptedIOException(e.getMessage()); + } + } + + try { + tc.sendMessage(msg); + } + catch (IOException e) { + close(e, false); + throw e; + } + } + } + + private void receiveLoop() throws IOException { + while (true) { + final byte[] buffer = new byte[MAX_PACKET_SIZE]; + final int length = tc.receiveMessage(buffer, 0, buffer.length); + final byte[] packet = new byte[length]; + System.arraycopy(buffer, 0, packet, 0, length); + final int type = packet[0] & 0xff; + log.debug(String.format("transport manager receive loop type %d", type)); + + switch (type) { + case Packets.SSH_MSG_IGNORE: + break; + + case Packets.SSH_MSG_DEBUG: { + TypesReader tr = new TypesReader(packet); + tr.readByte(); + // always_display + tr.readBoolean(); + String message = tr.readString(); + + if (log.isDebugEnabled()) { + log.debug(String.format("Debug message from remote: '%s'", message)); + } + + break; + } + + case Packets.SSH_MSG_UNIMPLEMENTED: + throw new PacketTypeException(type); + + case Packets.SSH_MSG_DISCONNECT: { + final PacketDisconnect disconnect = new PacketDisconnect(packet); + throw new DisconnectException(disconnect.getReason(), disconnect.getMessage()); + } + + case Packets.SSH_MSG_KEXINIT: + case Packets.SSH_MSG_NEWKEYS: + case Packets.SSH_MSG_KEXDH_INIT: + case Packets.SSH_MSG_KEXDH_REPLY: + case Packets.SSH_MSG_KEX_DH_GEX_REQUEST: + case Packets.SSH_MSG_KEX_DH_GEX_INIT: + case Packets.SSH_MSG_KEX_DH_GEX_REPLY: + // Is it a KEX Packet + km.handleMessage(packet); + break; + + case Packets.SSH_MSG_USERAUTH_SUCCESS: + tc.startCompression(); + + // Continue with message handlers + default: + boolean handled = false; + + for (HandlerEntry handler : messageHandlers) { + if ((handler.low <= type) && (type <= handler.high)) { + handler.mh.handleMessage(packet); + handled = true; + break; + } + } + + if (!handled) { + throw new PacketTypeException(type); + } + + break; + } + + if (log.isDebugEnabled()) { + log.debug(String.format("Handled packet %d", type)); + } + } + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/util/StringEncoder.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/util/StringEncoder.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. + * All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.util; + +import java.io.UnsupportedEncodingException; + +/** + * @author Christian Plattner + * @version $Id: StringEncoder.java 43 2011-06-21 18:34:06Z dkocher@sudo.ch $ + */ +public class StringEncoder { + public static byte[] GetBytes(String data) { + try { + return data.getBytes("UTF-8"); + } + catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + public static String GetString(byte[] data) { + return GetString(data, 0, data.length); + } + + public static String GetString(byte[] data, int off, int len) { + try { + return new String(data, off, len, "UTF-8"); + } + catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/ch/ethz/ssh2/util/TimeoutService.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/util/TimeoutService.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2.util; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedList; + +import ch.ethz.ssh2.log.Logger; + +/** + * TimeoutService (beta). Here you can register a timeout. + *

+ * Implemented having large scale programs in mind: if you open many concurrent SSH connections + * that rely on timeouts, then there will be only one timeout thread. Once all timeouts + * have expired/are cancelled, the thread will (sooner or later) exit. + * Only after new timeouts arrive a new thread (singleton) will be instantiated. + * + * @author Christian Plattner + * @version $Id: TimeoutService.java 89 2014-04-07 14:36:24Z dkocher@sudo.ch $ + */ +public class TimeoutService { + private static final Logger log = Logger.getLogger(TimeoutService.class); + + public static class TimeoutToken { + private long runTime; + private Runnable handler; + + private TimeoutToken(long runTime, Runnable handler) { + this.runTime = runTime; + this.handler = handler; + } + } + + private static class TimeoutThread extends Thread { + @Override + public void run() { + synchronized (todolist) { + while (true) { + if (todolist.size() == 0) { + timeoutThread = null; + return; + } + + long now = System.currentTimeMillis(); + TimeoutToken tt = todolist.getFirst(); + + if (tt.runTime > now) { + /* Not ready yet, sleep a little bit */ + try { + todolist.wait(tt.runTime - now); + } + catch (InterruptedException ignored) { + } + + /* We cannot simply go on, since it could be that the token + * was removed (cancelled) or another one has been inserted in + * the meantime. + */ + continue; + } + + todolist.removeFirst(); + + try { + tt.handler.run(); + } + catch (Exception e) { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + log.warning("Exeception in Timeout handler:" + e.getMessage() + "(" + sw.toString() + ")"); + } + } + } + } + } + + /* The list object is also used for locking purposes */ + private static final LinkedList todolist = new LinkedList(); + + private static Thread timeoutThread = null; + + /** + * It is assumed that the passed handler will not execute for a long time. + * + * @param runTime + * @param handler + * @return a TimeoutToken that can be used to cancel the timeout. + */ + public static TimeoutToken addTimeoutHandler(long runTime, Runnable handler) { + TimeoutToken token = new TimeoutToken(runTime, handler); + + synchronized (todolist) { + todolist.add(token); + Collections.sort(todolist, new Comparator() { + public int compare(TimeoutToken o1, TimeoutToken o2) { + if (o1.runTime > o2.runTime) + return 1; + + if (o1.runTime == o2.runTime) + return 0; + + return -1; + } + }); + + if (timeoutThread != null) + timeoutThread.interrupt(); + else { + timeoutThread = new TimeoutThread(); + timeoutThread.setDaemon(true); + timeoutThread.start(); + } + } + + return token; + } + + public static void cancelTimeoutHandler(TimeoutToken token) { + synchronized (todolist) { + todolist.remove(token); + + if (timeoutThread != null) + timeoutThread.interrupt(); + } + } + +} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/five_ten_sg/connectbot/GeneratePubkeyActivity.java --- a/src/com/five_ten_sg/connectbot/GeneratePubkeyActivity.java Thu Jul 17 22:09:05 2014 -0700 +++ b/src/com/five_ten_sg/connectbot/GeneratePubkeyActivity.java Thu Jul 31 16:33:38 2014 -0700 @@ -48,7 +48,7 @@ import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; -import com.trilead.ssh2.signature.ECDSASHA2Verify; +import ch.ethz.ssh2.signature.ECDSASHA2Verify; public class GeneratePubkeyActivity extends Activity implements OnEntropyGatheredListener { public final static String TAG = "ConnectBot.GeneratePubkeyActivity"; diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/five_ten_sg/connectbot/HelpActivity.java --- a/src/com/five_ten_sg/connectbot/HelpActivity.java Thu Jul 17 22:09:05 2014 -0700 +++ b/src/com/five_ten_sg/connectbot/HelpActivity.java Thu Jul 31 16:33:38 2014 -0700 @@ -50,6 +50,7 @@ getResources().getText(R.string.title_help))); AssetManager am = this.getAssets(); LinearLayout content = (LinearLayout)this.findViewById(R.id.topics); + try { for (String name : am.list(HELPDIR)) { if (name.endsWith(SUFFIX)) { diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/five_ten_sg/connectbot/HostEditorActivity.java --- a/src/com/five_ten_sg/connectbot/HostEditorActivity.java Thu Jul 17 22:09:05 2014 -0700 +++ b/src/com/five_ten_sg/connectbot/HostEditorActivity.java Thu Jul 31 16:33:38 2014 -0700 @@ -323,8 +323,11 @@ // get all the original text summaries for (String key : this.pref.values.keySet()) { Preference pref = this.findPreference(key); + if (pref == null) continue; + if (pref instanceof CheckBoxPreference) continue; + CharSequence value = pref.getSummary(); summaries.put(key, value); } @@ -334,8 +337,11 @@ // for all text preferences, set hint as current database value if it is non-empty for (String key : this.pref.values.keySet()) { Preference pref = this.findPreference(key); + if (pref == null) continue; + if (pref instanceof CheckBoxPreference) continue; + CharSequence value = this.pref.getString(key, ""); if (key.equals(HostDatabase.FIELD_HOST_PUBKEYID)) { @@ -358,10 +364,12 @@ else if ((pref instanceof ListPreference) && (value != null)) { ListPreference listPref = (ListPreference) pref; int entryIndex = listPref.findIndexOfValue(value.toString()); + if (entryIndex >= 0) value = listPref.getEntries()[entryIndex]; } if ((value == null) || (value.length() == 0)) value = summaries.get(key); + pref.setSummary(value); } } diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/five_ten_sg/connectbot/HostListActivity.java --- a/src/com/five_ten_sg/connectbot/HostListActivity.java Thu Jul 17 22:09:05 2014 -0700 +++ b/src/com/five_ten_sg/connectbot/HostListActivity.java Thu Jul 31 16:33:38 2014 -0700 @@ -403,21 +403,30 @@ File.separator + "deployment.connections"; BufferedReader reader = new BufferedReader(new FileReader(fn)); String line = null; + while ((line = reader.readLine()) != null) { if (line.length() == 0) continue; // empty - if (line.substring(0,1).equals("#")) continue; // comment + + if (line.substring(0, 1).equals("#")) continue; // comment + if (!line.contains("://")) continue; // invalid uri + Uri uri = Uri.parse(line); ContentValues values = null; + while ((line = reader.readLine()).length() > 0) { String [] parts = line.split("="); + if (parts.length != 2) continue; + if (values == null) values = new ContentValues(); + values.put(parts[0].trim(), parts[1].trim()); } + if (uri.getScheme().equals("global")) { Editor editor = prefs.edit(); - HashMap types = new HashMap(); + HashMap types = new HashMap(); types.put("memkeys", "boolean"); types.put("connPersist", "boolean"); types.put("emulation", "string"); @@ -451,30 +460,38 @@ types.put("upload_dest_prompt", "boolean"); types.put("background_file_transfer", "boolean"); types.put("debug_keycodes", "boolean"); + for (String key : values.keySet()) { if (types.containsKey(key)) { String sv = values.getAsString(key); editor.putString(key, sv); } } + editor.commit(); } else { HostBean host = TransportFactory.findHost(hostdb, uri); + if (host == null) { host = TransportFactory.getTransport(uri.getScheme()).createHost(uri); host.setColor(HostDatabase.COLOR_GRAY); host.setPubkeyId(HostDatabase.PUBKEYID_ANY); hostdb.saveHost(host); } + host = TransportFactory.findHost(hostdb, uri); + if (host == null) continue; + if (values == null) continue; + SQLiteDatabase db = hostdb.getWritableDatabase(); db.update(HostDatabase.TABLE_HOSTS, values, "_id = ?", new String[] { String.valueOf(host.getId()) }); db.close(); } } + reader.close(); (new File(fn)).delete(); } diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/five_ten_sg/connectbot/PubkeyListActivity.java --- a/src/com/five_ten_sg/connectbot/PubkeyListActivity.java Thu Jul 17 22:09:05 2014 -0700 +++ b/src/com/five_ten_sg/connectbot/PubkeyListActivity.java Thu Jul 31 16:33:38 2014 -0700 @@ -63,9 +63,9 @@ import android.widget.TextView; import android.widget.Toast; -import com.trilead.ssh2.crypto.Base64; -import com.trilead.ssh2.crypto.PEMDecoder; -import com.trilead.ssh2.crypto.PEMStructure; +import ch.ethz.ssh2.crypto.Base64; +import ch.ethz.ssh2.crypto.PEMDecoder; +import ch.ethz.ssh2.crypto.PEMStructure; /** * List public keys in database by nickname and describe their properties. Allow users to import, @@ -638,7 +638,8 @@ if (imported) { try { PEMStructure struct = PEMDecoder.parsePEM(new String(pubkey.getPrivateKey()).toCharArray()); - String type = (struct.pemType == PEMDecoder.PEM_RSA_PRIVATE_KEY) ? "RSA" : "DSA"; + String type = (struct.pemType == PEMDecoder.PEM_RSA_PRIVATE_KEY) ? "RSA" : + (struct.pemType == PEMDecoder.PEM_DSA_PRIVATE_KEY) ? "DSA" : "EC"; holder.caption.setText(String.format("%s unknown-bit", type)); } catch (IOException e) { diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/five_ten_sg/connectbot/WizardActivity.java --- a/src/com/five_ten_sg/connectbot/WizardActivity.java Thu Jul 17 22:09:05 2014 -0700 +++ b/src/com/five_ten_sg/connectbot/WizardActivity.java Thu Jul 31 16:33:38 2014 -0700 @@ -48,7 +48,6 @@ // inflate the layout for EULA step LayoutInflater inflater = LayoutInflater.from(this); View helpView = inflater.inflate(R.layout.wiz_eula, this.flipper, false); - this.flipper.addView(helpView); // Add a view for each help topic we want the user to see. String[] topics = getResources().getStringArray(R.array.list_wizard_topics); diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/five_ten_sg/connectbot/bean/PubkeyBean.java --- a/src/com/five_ten_sg/connectbot/bean/PubkeyBean.java Thu Jul 17 22:09:05 2014 -0700 +++ b/src/com/five_ten_sg/connectbot/bean/PubkeyBean.java Thu Jul 31 16:33:38 2014 -0700 @@ -35,12 +35,6 @@ public class PubkeyBean extends AbstractBean { public static final String BEAN_NAME = "pubkey"; - private static final String KEY_TYPE_RSA = "RSA"; - - private static final String KEY_TYPE_DSA = "DSA"; - - private static final String KEY_TYPE_EC = "EC"; - /* Database fields */ private long id; private String nickname; @@ -167,7 +161,7 @@ final StringBuilder sb = new StringBuilder(); try { - final PublicKey pubKey = PubkeyUtils.decodePublic(privateKey, type); + final PublicKey pubKey = PubkeyUtils.decodePublic(publicKey, type); if (PubkeyDatabase.KEY_TYPE_RSA.equals(type)) { int bits = ((RSAPublicKey) pubKey).getModulus().bitLength(); diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/five_ten_sg/connectbot/service/AuthAgentService.java --- a/src/com/five_ten_sg/connectbot/service/AuthAgentService.java Thu Jul 17 22:09:05 2014 -0700 +++ b/src/com/five_ten_sg/connectbot/service/AuthAgentService.java Thu Jul 31 16:33:38 2014 -0700 @@ -29,9 +29,9 @@ import android.util.Log; import com.madgag.ssh.android.authagent.AndroidAuthAgent; -import com.trilead.ssh2.signature.DSASHA1Verify; -import com.trilead.ssh2.signature.ECDSASHA2Verify; -import com.trilead.ssh2.signature.RSASHA1Verify; +import ch.ethz.ssh2.signature.DSASHA1Verify; +import ch.ethz.ssh2.signature.ECDSASHA2Verify; +import ch.ethz.ssh2.signature.RSASHA1Verify; public class AuthAgentService extends Service { private static final String TAG = "ConnectBot.AuthAgentService"; @@ -90,6 +90,9 @@ else if (privKey instanceof DSAPrivateKey) { return sshEncodedSignatureFor(data, (DSAPrivateKey) privKey); } + else if (privKey instanceof ECPrivateKey) { + return sshEncodedSignatureFor(data, (ECPrivateKey) privKey); + } return null; } @@ -136,6 +139,10 @@ DSAPublicKey pubkey = (DSAPublicKey)pair.getPublic(); return DSASHA1Verify.encodeSSHDSAPublicKey(pubkey); } + else if (privKey instanceof ECPrivateKey) { + ECPublicKey pubkey = (ECPublicKey) pair.getPublic(); + return ECDSASHA2Verify.encodeSSHECDSAPublicKey(pubkey); + } } catch (IOException e) { Log.e(TAG, "Couldn't encode " + pair, e); @@ -161,6 +168,15 @@ throw new RuntimeException(e); } } + private byte[] sshEncodedSignatureFor(byte[] data, ECPrivateKey privKey) { + try { + byte[] signature = ECDSASHA2Verify.generateSignature(data, privKey); + return ECDSASHA2Verify.encodeSSHECDSASignature(signature, privKey.getParams()); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } private KeyPair keyPairFor(byte[] publicKey) { String nickname = manager.getKeyNickname(publicKey); diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/five_ten_sg/connectbot/service/TerminalBridge.java --- a/src/com/five_ten_sg/connectbot/service/TerminalBridge.java Thu Jul 17 22:09:05 2014 -0700 +++ b/src/com/five_ten_sg/connectbot/service/TerminalBridge.java Thu Jul 31 16:33:38 2014 -0700 @@ -332,7 +332,6 @@ // get proper font size setMyFontSize(); - // finally send any post-login string, if requested injectString(host.getPostLogin()); } @@ -445,6 +444,7 @@ */ final void setFontSize(float size) { if (size <= 0.0) size = 12.0f; + size = (float)(int)((size * 10.0f) + 0.5f) / 10.0f; defaultPaint.setTextSize(size); fontSize = size; @@ -782,6 +782,7 @@ this.columns = cols; this.rows = rows; forcedSize = true; + if (fixed) setFontSize(host.getFontSize()); else setFontSize(size); } @@ -1136,7 +1137,7 @@ */ public boolean showArrowsDialog() { final String []pickerStrings = {"←", "→", "↑", "↓", "tab", "ins", "del", "ret"}; - final HashMap keymap = new HashMap(); + final HashMap keymap = new HashMap(); keymap.put("←", vt320.KEY_LEFT); keymap.put("→", vt320.KEY_RIGHT); keymap.put("↑", vt320.KEY_UP); @@ -1145,15 +1146,14 @@ keymap.put("ins", vt320.KEY_INSERT); keymap.put("del", vt320.KEY_DELETE); keymap.put("ret", vt320.KEY_ENTER); - CharSequence str = ""; Editable content = Editable.Factory.getInstance().newEditable(str); if (parent == null) return false; StringPickerDialog cpd = new StringPickerDialog(parent.getContext(), - parent, content, - pickerStrings, true) { + parent, content, + pickerStrings, true) { private void buttonPressed(String s) { if (keymap.containsKey(s)) buffer.keyPressed(keymap.get(s), ' ', 0); } @@ -1165,6 +1165,7 @@ public void onClick(View v) { if (v instanceof Button) { final String s = ((Button) v).getText().toString(); + if (s.equals("")) dismiss(); else buttonPressed(s); } @@ -1260,7 +1261,7 @@ */ public boolean showFKeysDialog() { final String []pickerStrings = {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "F16", "F17", "F18", "F19", "F20", "F21", "F22", "F23", "F24", "←", "→", "↑", "↓", "tab", "ins", "del", "ret"}; - final HashMap keymap = new HashMap(); + final HashMap keymap = new HashMap(); keymap.put("F1", vt320.KEY_F1); keymap.put("F2", vt320.KEY_F2); keymap.put("F3", vt320.KEY_F3); @@ -1293,17 +1294,17 @@ keymap.put("ins", vt320.KEY_INSERT); keymap.put("del", vt320.KEY_DELETE); keymap.put("ret", vt320.KEY_ENTER); - CharSequence str = ""; Editable content = Editable.Factory.getInstance().newEditable(str); if (parent == null) return false; StringPickerDialog cpd = new StringPickerDialog(parent.getContext(), - parent, content, - pickerStrings, true) { + parent, content, + pickerStrings, true) { private void buttonPressed(String s) { if (keymap.containsKey(s)) buffer.keyPressed(keymap.get(s), ' ', 0); + dismiss(); } @Override @@ -1314,6 +1315,7 @@ public void onClick(View v) { if (v instanceof Button) { final String s = ((Button) v).getText().toString(); + if (s.equals("")) dismiss(); else buttonPressed(s); } diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/five_ten_sg/connectbot/service/TerminalKeyListener.java --- a/src/com/five_ten_sg/connectbot/service/TerminalKeyListener.java Thu Jul 17 22:09:05 2014 -0700 +++ b/src/com/five_ten_sg/connectbot/service/TerminalKeyListener.java Thu Jul 31 16:33:38 2014 -0700 @@ -569,40 +569,41 @@ private boolean handleShortcuts(View v, int keyCode, KeyEvent event, int repeat, boolean down) { String hwbuttonShortcut; + switch (keyCode) { case KeyEvent.KEYCODE_CAMERA: // check to see which shortcut the camera button triggers hwbuttonShortcut = manager.prefs.getString( - PreferenceConstants.CAMERA, - PreferenceConstants.HWBUTTON_SCREEN_CAPTURE); + PreferenceConstants.CAMERA, + PreferenceConstants.HWBUTTON_SCREEN_CAPTURE); return (handleShortcut(v, hwbuttonShortcut, repeat, down)); case KeyEvent.KEYCODE_VOLUME_UP: // check to see which shortcut the volume button triggers hwbuttonShortcut = manager.prefs.getString( - PreferenceConstants.VOLUP, - PreferenceConstants.HWBUTTON_FUNCTION_KEYS); + PreferenceConstants.VOLUP, + PreferenceConstants.HWBUTTON_FUNCTION_KEYS); return (handleShortcut(v, hwbuttonShortcut, repeat, down)); case KeyEvent.KEYCODE_VOLUME_DOWN: // check to see which shortcut the camera button triggers hwbuttonShortcut = manager.prefs.getString( - PreferenceConstants.VOLDN, - PreferenceConstants.HWBUTTON_TAB); + PreferenceConstants.VOLDN, + PreferenceConstants.HWBUTTON_TAB); return (handleShortcut(v, hwbuttonShortcut, repeat, down)); case KeyEvent.KEYCODE_SEARCH: // check to see which shortcut the search button triggers hwbuttonShortcut = manager.prefs.getString( - PreferenceConstants.SEARCH, - PreferenceConstants.HWBUTTON_ESC); + PreferenceConstants.SEARCH, + PreferenceConstants.HWBUTTON_ESC); return (handleShortcut(v, hwbuttonShortcut, repeat, down)); case KeyEvent.KEYCODE_BUTTON_L2: // check to see which shortcut the ptt button triggers hwbuttonShortcut = manager.prefs.getString( - PreferenceConstants.PTT, - PreferenceConstants.HWBUTTON_MONITOR); + PreferenceConstants.PTT, + PreferenceConstants.HWBUTTON_MONITOR); return (handleShortcut(v, hwbuttonShortcut, repeat, down)); default: return false; @@ -612,51 +613,64 @@ private boolean handleShortcut(View v, String shortcut, int repeat, boolean down) { if (PreferenceConstants.HWBUTTON_DECREASE_FONTSIZE.equals(shortcut)) { if (!down) return false; + bridge.decreaseFontSize(); } else if (PreferenceConstants.HWBUTTON_INCREASE_FONTSIZE.equals(shortcut)) { if (!down) return false; + bridge.increaseFontSize(); } else if (PreferenceConstants.HWBUTTON_FUNCTION_KEYS.equals(shortcut)) { if (repeat > 0) return false; + if (!down) return false; + bridge.showFKeysDialog(); } else if (PreferenceConstants.HWBUTTON_MONITOR.equals(shortcut)) { if (repeat > 0) return false; + buffer.monitorKey(down); } else if (PreferenceConstants.HWBUTTON_SCREEN_CAPTURE.equals(shortcut)) { if (repeat > 0) return false; + if (!down) return false; + bridge.captureScreen(); } else if (PreferenceConstants.HWBUTTON_CTRL.equals(shortcut)) { if (!down) return false; + showMetakeyToast(v, PreferenceConstants.HWBUTTON_CTRL); metaPress(META_CTRL_ON); } else if (PreferenceConstants.HWBUTTON_TAB.equals(shortcut)) { if (!down) return false; + buffer.keyPressed(vt320.KEY_TAB, ' ', getStateForBuffer()); } else if (PreferenceConstants.HWBUTTON_CTRLA_SPACE.equals(shortcut)) { if (!down) return false; + buffer.write(0x01); buffer.write(' '); } else if (PreferenceConstants.HWBUTTON_CTRLA.equals(shortcut)) { if (!down) return false; + buffer.write(0x01); } else if (PreferenceConstants.HWBUTTON_ESC.equals(shortcut)) { if (!down) return false; + showMetakeyToast(v, PreferenceConstants.HWBUTTON_ESC); sendEscape(); } else if (PreferenceConstants.HWBUTTON_ESC_A.equals(shortcut)) { if (!down) return false; + sendEscape(); buffer.write('a'); } diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/five_ten_sg/connectbot/service/TerminalMonitor.java --- a/src/com/five_ten_sg/connectbot/service/TerminalMonitor.java Thu Jul 17 22:09:05 2014 -0700 +++ b/src/com/five_ten_sg/connectbot/service/TerminalMonitor.java Thu Jul 31 16:33:38 2014 -0700 @@ -378,6 +378,7 @@ public synchronized void cursorMove(int l, int c) { if ((to_line != l) || (to_column != c)) moved = true; + to_line = l; to_column = c; } @@ -421,21 +422,23 @@ int len = data.length - offset; char[] da = new char[len]; System.arraycopy(data, offset, da, 0, len); + if ((l > 60000) || (c > 60000)) { l = -1; c = -1; } else { // ignore setfield outside screen boundaries - if ((l >= buffer.height) || (c+len >= buffer.width)) return; + if ((l >= buffer.height) || (c + len >= buffer.width)) return; } + buffer.setField(l, c, da); } public synchronized void showUrl(char [] data, int offset) { Log.i(TAG, "setField()"); char[] da = new char[data.length - offset]; - System.arraycopy(data, offset, da, 0, data.length-offset); + System.arraycopy(data, offset, da, 0, data.length - offset); String url = new String(da); Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); parent.startActivity(intent); @@ -447,12 +450,14 @@ arg2[2] = (char)(l & 0x0000ffff); arg2[3] = (char)(c & 0x0000ffff); int base = 4; - if ((l >= buffer.height) || (c+len >= buffer.width)) { - Arrays.fill(arg2, base, len-1, ' '); + + if ((l >= buffer.height) || (c + len >= buffer.width)) { + Arrays.fill(arg2, base, len - 1, ' '); } else { System.arraycopy(buffer.charArray[buffer.screenBase + l], c, arg2, base, len); } + monitorWrite(MONITOR_CMD_FIELDVALUE, arg2); } @@ -467,6 +472,7 @@ public synchronized void depress(int vk_key) { Log.i(TAG, String.format("depress() %d", vk_key)); Integer x = keymap.get(new Integer(vk_key)); + if (x != null) buffer.keyDepressed(x, ' ', 0); } diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/five_ten_sg/connectbot/transport/SSH.java --- a/src/com/five_ten_sg/connectbot/transport/SSH.java Thu Jul 17 22:09:05 2014 -0700 +++ b/src/com/five_ten_sg/connectbot/transport/SSH.java Thu Jul 31 16:33:38 2014 -0700 @@ -31,6 +31,8 @@ import java.security.PublicKey; import java.security.interfaces.DSAPrivateKey; import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; @@ -59,23 +61,24 @@ import android.os.Environment; import android.util.Log; -import com.trilead.ssh2.AuthAgentCallback; -import com.trilead.ssh2.ChannelCondition; -import com.trilead.ssh2.Connection; -import com.trilead.ssh2.ConnectionInfo; -import com.trilead.ssh2.ConnectionMonitor; -import com.trilead.ssh2.DynamicPortForwarder; -import com.trilead.ssh2.InteractiveCallback; -import com.trilead.ssh2.KnownHosts; -import com.trilead.ssh2.LocalPortForwarder; -import com.trilead.ssh2.SCPClient; -import com.trilead.ssh2.ServerHostKeyVerifier; -import com.trilead.ssh2.Session; -import com.trilead.ssh2.HTTPProxyData; -import com.trilead.ssh2.HTTPProxyException; -import com.trilead.ssh2.crypto.PEMDecoder; -import com.trilead.ssh2.signature.DSASHA1Verify; -import com.trilead.ssh2.signature.RSASHA1Verify; +import ch.ethz.ssh2.AuthAgentCallback; +import ch.ethz.ssh2.ChannelCondition; +import ch.ethz.ssh2.Connection; +import ch.ethz.ssh2.ConnectionInfo; +import ch.ethz.ssh2.ConnectionMonitor; +import ch.ethz.ssh2.DynamicPortForwarder; +import ch.ethz.ssh2.InteractiveCallback; +import ch.ethz.ssh2.KnownHosts; +import ch.ethz.ssh2.LocalPortForwarder; +import ch.ethz.ssh2.SCPClient; +import ch.ethz.ssh2.ServerHostKeyVerifier; +import ch.ethz.ssh2.Session; +import ch.ethz.ssh2.HTTPProxyData; +import ch.ethz.ssh2.HTTPProxyException; +import ch.ethz.ssh2.crypto.PEMDecoder; +import ch.ethz.ssh2.signature.DSASHA1Verify; +import ch.ethz.ssh2.signature.ECDSASHA2Verify; +import ch.ethz.ssh2.signature.RSASHA1Verify; /** * @author Kenny Root @@ -144,7 +147,7 @@ algorithmName = "RSA"; else if ("ssh-dss".equals(serverHostKeyAlgorithm)) algorithmName = "DSA"; - else if (serverHostKeyAlgorithm.startsWith("ecdsa-")) + else if (serverHostKeyAlgorithm.startsWith("ecdsa-sha2-")) algorithmName = "EC"; else algorithmName = serverHostKeyAlgorithm; @@ -562,7 +565,9 @@ catch (IOException e) { Log.e(TAG, "Problem in SSH connection thread during authentication", e); // Display the reason in the text. - bridge.outputLine(e.getCause().getMessage()); + Throwable t = e.getCause(); + String m = (t == null) ? e.getMessage() : t.getMessage(); + bridge.outputLine(m); onDisconnect(); return; } @@ -1062,6 +1067,10 @@ DSAPublicKey pubkey = (DSAPublicKey) pair.getPublic(); pubKeys.put(entry.getKey(), DSASHA1Verify.encodeSSHDSAPublicKey(pubkey)); } + else if (privKey instanceof ECPrivateKey) { + ECPublicKey pubkey = (ECPublicKey) pair.getPublic(); + pubKeys.put(entry.getKey(), ECDSASHA2Verify.encodeSSHECDSAPublicKey(pubkey)); + } else continue; } diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/five_ten_sg/connectbot/transport/TN5250.java --- a/src/com/five_ten_sg/connectbot/transport/TN5250.java Thu Jul 17 22:09:05 2014 -0700 +++ b/src/com/five_ten_sg/connectbot/transport/TN5250.java Thu Jul 31 16:33:38 2014 -0700 @@ -163,6 +163,7 @@ public void keyDepressed(int keyCode, char keyChar, int modifiers) { if (mnemonics.containsKey(keyCode)) { String s = mnemonics.get(keyCode); + if (s != "") screen52.sendKeys(s); } } @@ -177,17 +178,20 @@ @Override public void write(byte[] b) { screen52.sendKeys(new String(b)); + if (bridge.monitor != null) bridge.monitor.testMoved(); } @Override public void write(int b) { if (controls.containsKey(b)) keyPressed(controls.get(b), ' ', 0); else screen52.sendKeys(new String(new byte[] {(byte)b})); + if (bridge.monitor != null) bridge.monitor.testMoved(); } @Override public void keyPressed(int keyCode, char keyChar, int modifiers) { keyDepressed(keyCode, keyChar, modifiers); + if (bridge.monitor != null) bridge.monitor.testMoved(); } @@ -281,6 +285,7 @@ screen52.setBuffer(buffer); bridge.addFontSizeChangedListener(screen52); connected = handler.connect(host, homeDirectory, buffer); + if (connected) bridge.onConnected(); } @@ -435,8 +440,8 @@ if (nickname == null || nickname.length() == 0) { nickname = getDefaultNickname(host.getUsername(), host.getHostname(), host.getPort()); } + host.setNickname(nickname); - return host; } diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/five_ten_sg/connectbot/util/HostDatabase.java --- a/src/com/five_ten_sg/connectbot/util/HostDatabase.java Thu Jul 17 22:09:05 2014 -0700 +++ b/src/com/five_ten_sg/connectbot/util/HostDatabase.java Thu Jul 31 16:33:38 2014 -0700 @@ -33,7 +33,7 @@ import android.database.sqlite.SQLiteException; import android.util.Log; -import com.trilead.ssh2.KnownHosts; +import ch.ethz.ssh2.KnownHosts; /** * Contains information about various SSH hosts, include public hostkey if known diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/five_ten_sg/connectbot/util/PubkeyUtils.java --- a/src/com/five_ten_sg/connectbot/util/PubkeyUtils.java Thu Jul 17 22:09:05 2014 -0700 +++ b/src/com/five_ten_sg/connectbot/util/PubkeyUtils.java Thu Jul 31 16:33:38 2014 -0700 @@ -64,11 +64,11 @@ import com.five_ten_sg.connectbot.bean.PubkeyBean; import android.util.Log; -import com.trilead.ssh2.crypto.Base64; -import com.trilead.ssh2.crypto.SimpleDERReader; -import com.trilead.ssh2.signature.DSASHA1Verify; -import com.trilead.ssh2.signature.ECDSASHA2Verify; -import com.trilead.ssh2.signature.RSASHA1Verify; +import ch.ethz.ssh2.crypto.Base64; +import ch.ethz.ssh2.crypto.SimpleDERReader; +import ch.ethz.ssh2.signature.DSASHA1Verify; +import ch.ethz.ssh2.signature.ECDSASHA2Verify; +import ch.ethz.ssh2.signature.RSASHA1Verify; public class PubkeyUtils { private static final String TAG = "PubkeyUtils"; diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/five_ten_sg/connectbot/util/StringPickerDialog.java --- a/src/com/five_ten_sg/connectbot/util/StringPickerDialog.java Thu Jul 17 22:09:05 2014 -0700 +++ b/src/com/five_ten_sg/connectbot/util/StringPickerDialog.java Thu Jul 31 16:33:38 2014 -0700 @@ -38,7 +38,7 @@ * Dialog for choosing accented characters related to a base character. */ public class StringPickerDialog extends Dialog - implements OnItemClickListener, OnClickListener { + implements OnItemClickListener, OnClickListener { private View mView; private Editable mText; private String []mOptions; @@ -52,10 +52,9 @@ * the sense of insert) into text. */ public StringPickerDialog(Context context, View view, - Editable text, String []options, - boolean insert) { + Editable text, String []options, + boolean insert) { //super(context, com.android.internal.R.style.Theme_Panel); - //Resources res = Resources.getSystem(); //int id = res.getIdentifier("Theme_Panel", "style", "android"); //int id = android.R.style.Theme_Panel; @@ -70,18 +69,14 @@ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - WindowManager.LayoutParams params = getWindow().getAttributes(); params.token = mView.getApplicationWindowToken(); params.type = params.TYPE_APPLICATION_ATTACHED_DIALOG; params.flags = params.flags | Window.FEATURE_NO_TITLE; - setContentView(R.layout.string_picker); - GridView grid = (GridView) findViewById(R.id.stringPicker); grid.setAdapter(new OptionsAdapter(getContext())); grid.setOnItemClickListener(this); - mCancelButton = (Button) findViewById(R.id.cancel); mCancelButton.setOnClickListener(this); } @@ -96,9 +91,11 @@ private void replaceCharacterAndClose(CharSequence replace) { int selEnd = Selection.getSelectionEnd(mText); + if (mInsert || selEnd == 0) { mText.insert(selEnd, replace); - } else { + } + else { mText.replace(selEnd - 1, selEnd, replace); } @@ -111,7 +108,8 @@ public void onClick(View v) { if (v == mCancelButton) { dismiss(); - } else if (v instanceof Button) { + } + else if (v instanceof Button) { CharSequence result = ((Button) v).getText(); replaceCharacterAndClose(result); } @@ -125,7 +123,7 @@ public View getView(int position, View convertView, ViewGroup parent) { Button b = (Button) - mInflater.inflate(R.layout.string_picker_button, null); + mInflater.inflate(R.layout.string_picker_button, null); b.setText(mOptions[position]); b.setOnClickListener(StringPickerDialog.this); return b; diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/five_ten_sg/connectbot/util/XmlBuilder.java --- a/src/com/five_ten_sg/connectbot/util/XmlBuilder.java Thu Jul 17 22:09:05 2014 -0700 +++ b/src/com/five_ten_sg/connectbot/util/XmlBuilder.java Thu Jul 31 16:33:38 2014 -0700 @@ -17,7 +17,7 @@ package com.five_ten_sg.connectbot.util; -import com.trilead.ssh2.crypto.Base64; +import ch.ethz.ssh2.crypto.Base64; /** * @author Kenny Root diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/AuthAgentCallback.java --- a/src/com/trilead/ssh2/AuthAgentCallback.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ -package com.trilead.ssh2; - -import java.security.KeyPair; -import java.util.Map; - -/** - * AuthAgentCallback. - * - * @author Kenny Root - * @version $Id$ - */ -public interface AuthAgentCallback { - - /** - * @return array of blobs containing the OpenSSH-format encoded public keys - */ - Map retrieveIdentities(); - - /** - * @param key A RSAPrivateKey or DSAPrivateKey - * containing a DSA or RSA private key of - * the user in Trilead object format. - * @param comment comment associated with this key - * @param confirmUse whether to prompt before using this key - * @param lifetime lifetime in seconds for key to be remembered - * @return success or failure - */ - boolean addIdentity(KeyPair pair, String comment, boolean confirmUse, int lifetime); - - /** - * @param publicKey byte blob containing the OpenSSH-format encoded public key - * @return success or failure - */ - boolean removeIdentity(byte[] publicKey); - - /** - * @return success or failure - */ - boolean removeAllIdentities(); - - /** - * @param publicKey byte blob containing the OpenSSH-format encoded public key - * @return A RSAPrivateKey or DSAPrivateKey - * containing a DSA or RSA private key of - * the user in Trilead object format. - */ - KeyPair getKeyPair(byte[] publicKey); - - /** - * @return - */ - boolean isAgentLocked(); - - /** - * @param lockPassphrase - */ - boolean setAgentLock(String lockPassphrase); - - /** - * @param unlockPassphrase - * @return - */ - boolean requestAgentUnlock(String unlockPassphrase); -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/ChannelCondition.java --- a/src/com/trilead/ssh2/ChannelCondition.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ - -package com.trilead.ssh2; - -/** - * Contains constants that can be used to specify what conditions to wait for on - * a SSH-2 channel (e.g., represented by a {@link Session}). - * - * @see Session#waitForCondition(int, long) - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: ChannelCondition.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ - -public abstract interface ChannelCondition { - /** - * A timeout has occurred, none of your requested conditions is fulfilled. - * However, other conditions may be true - therefore, NEVER use the "==" - * operator to test for this (or any other) condition. Always use - * something like ((cond & ChannelCondition.CLOSED) != 0). - */ - public static final int TIMEOUT = 1; - - /** - * The underlying SSH-2 channel, however not necessarily the whole connection, - * has been closed. This implies EOF. Note that there may still - * be unread stdout or stderr data in the local window, i.e, STDOUT_DATA - * or/and STDERR_DATA may be set at the same time. - */ - public static final int CLOSED = 2; - - /** - * There is stdout data available that is ready to be consumed. - */ - public static final int STDOUT_DATA = 4; - - /** - * There is stderr data available that is ready to be consumed. - */ - public static final int STDERR_DATA = 8; - - /** - * EOF on has been reached, no more _new_ stdout or stderr data will arrive - * from the remote server. However, there may be unread stdout or stderr - * data, i.e, STDOUT_DATA or/and STDERR_DATA - * may be set at the same time. - */ - public static final int EOF = 16; - - /** - * The exit status of the remote process is available. - * Some servers never send the exist status, or occasionally "forget" to do so. - */ - public static final int EXIT_STATUS = 32; - - /** - * The exit signal of the remote process is available. - */ - public static final int EXIT_SIGNAL = 64; - -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/Connection.java --- a/src/com/trilead/ssh2/Connection.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1585 +0,0 @@ - -package com.trilead.ssh2; - -import java.io.CharArrayWriter; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.SocketTimeoutException; -import java.security.KeyPair; -import java.security.SecureRandom; -import java.security.Security; -import java.util.Set; -import java.util.Vector; - -import com.trilead.ssh2.auth.AuthenticationManager; -import com.trilead.ssh2.channel.ChannelManager; -import com.trilead.ssh2.crypto.CryptoWishList; -import com.trilead.ssh2.crypto.cipher.BlockCipherFactory; -import com.trilead.ssh2.crypto.digest.MAC; -import com.trilead.ssh2.log.Logger; -import com.trilead.ssh2.packets.PacketIgnore; -import com.trilead.ssh2.transport.KexManager; -import com.trilead.ssh2.transport.TransportManager; -import com.trilead.ssh2.util.TimeoutService; -import com.trilead.ssh2.util.TimeoutService.TimeoutToken; - -/** - * A Connection is used to establish an encrypted TCP/IP - * connection to a SSH-2 server. - *

- * Typically, one - *

    - *
  1. creates a {@link #Connection(String) Connection} object.
  2. - *
  3. calls the {@link #connect() connect()} method.
  4. - *
  5. calls some of the authentication methods (e.g., - * {@link #authenticateWithPublicKey(String, File, String) authenticateWithPublicKey()}).
  6. - *
  7. calls one or several times the {@link #openSession() openSession()} - * method.
  8. - *
  9. finally, one must close the connection and release resources with the - * {@link #close() close()} method.
  10. - *
- * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: Connection.java,v 1.3 2008/04/01 12:38:09 cplattne Exp $ - */ - -public class Connection { - /** - * The identifier presented to the SSH-2 server. - */ - public final static String identification = "TrileadSSH2Java_213"; - - /** - * Will be used to generate all random data needed for the current - * connection. Note: SecureRandom.nextBytes() is thread safe. - */ - private SecureRandom generator; - - /** - * Unless you know what you are doing, you will never need this. - * - * @return The list of supported cipher algorithms by this implementation. - */ - - public static synchronized String[] getAvailableCiphers() { - return BlockCipherFactory.getDefaultCipherList(); - } - - /** - * Unless you know what you are doing, you will never need this. - * - * @return The list of supported MAC algorthims by this implementation. - */ - - public static synchronized String[] getAvailableMACs() { - return MAC.getMacList(); - } - - /** - * Unless you know what you are doing, you will never need this. - * - * @return The list of supported server host key algorthims by this - * implementation. - */ - - public static synchronized String[] getAvailableServerHostKeyAlgorithms() { - return KexManager.getDefaultServerHostkeyAlgorithmList(); - } - - private AuthenticationManager am; - - private boolean authenticated = false; - private boolean compression = false; - private ChannelManager cm; - - private CryptoWishList cryptoWishList = new CryptoWishList(); - - private DHGexParameters dhgexpara = new DHGexParameters(); - - private final String hostname; - - private final int port; - - private TransportManager tm; - - private boolean tcpNoDelay = false; - - private ProxyData proxyData = null; - - private Vector connectionMonitors = new Vector(); - - /** - * Prepares a fresh Connection object which can then be used - * to establish a connection to the specified SSH-2 server. - *

- * Same as {@link #Connection(String, int) Connection(hostname, 22)}. - * - * @param hostname - * the hostname of the SSH-2 server. - */ - public Connection(String hostname) { - this(hostname, 22); - } - - /** - * Prepares a fresh Connection object which can then be used - * to establish a connection to the specified SSH-2 server. - * - * @param hostname - * the host where we later want to connect to. - * @param port - * port on the server, normally 22. - */ - public Connection(String hostname, int port) { - this.hostname = hostname; - this.port = port; - } - - /** - * After a successful connect, one has to authenticate oneself. This method - * is based on DSA (it uses DSA to sign a challenge sent by the server). - *

- * If the authentication phase is complete, true will be - * returned. If the server does not accept the request (or if further - * authentication steps are needed), false is returned and - * one can retry either by using this or any other authentication method - * (use the getRemainingAuthMethods method to get a list of - * the remaining possible methods). - * - * @param user - * A String holding the username. - * @param pem - * A String containing the DSA private key of the - * user in OpenSSH key format (PEM, you can't miss the - * "-----BEGIN DSA PRIVATE KEY-----" tag). The string may contain - * linefeeds. - * @param password - * If the PEM string is 3DES encrypted ("DES-EDE3-CBC"), then you - * must specify the password. Otherwise, this argument will be - * ignored and can be set to null. - * - * @return whether the connection is now authenticated. - * @throws IOException - * - * @deprecated You should use one of the - * {@link #authenticateWithPublicKey(String, File, String) authenticateWithPublicKey()} - * methods, this method is just a wrapper for it and will - * disappear in future builds. - * - */ - - public synchronized boolean authenticateWithDSA(String user, String pem, String password) throws IOException { - if (tm == null) - throw new IllegalStateException("Connection is not established!"); - - if (authenticated) - throw new IllegalStateException("Connection is already authenticated!"); - - if (am == null) - am = new AuthenticationManager(tm); - - if (cm == null) - cm = new ChannelManager(tm); - - if (user == null) - throw new IllegalArgumentException("user argument is null"); - - if (pem == null) - throw new IllegalArgumentException("pem argument is null"); - - authenticated = am.authenticatePublicKey(user, pem.toCharArray(), password, getOrCreateSecureRND()); - return authenticated; - } - - /** - * A wrapper that calls - * {@link #authenticateWithKeyboardInteractive(String, String[], InteractiveCallback) - * authenticateWithKeyboardInteractivewith} a null submethod - * list. - * - * @param user - * A String holding the username. - * @param cb - * An InteractiveCallback which will be used to - * determine the responses to the questions asked by the server. - * @return whether the connection is now authenticated. - * @throws IOException - */ - - public synchronized boolean authenticateWithKeyboardInteractive(String user, InteractiveCallback cb) - throws IOException { - return authenticateWithKeyboardInteractive(user, null, cb); - } - - /** - * After a successful connect, one has to authenticate oneself. This method - * is based on "keyboard-interactive", specified in - * draft-ietf-secsh-auth-kbdinteract-XX. Basically, you have to define a - * callback object which will be feeded with challenges generated by the - * server. Answers are then sent back to the server. It is possible that the - * callback will be called several times during the invocation of this - * method (e.g., if the server replies to the callback's answer(s) with - * another challenge...) - *

- * If the authentication phase is complete, true will be - * returned. If the server does not accept the request (or if further - * authentication steps are needed), false is returned and - * one can retry either by using this or any other authentication method - * (use the getRemainingAuthMethods method to get a list of - * the remaining possible methods). - *

- * Note: some SSH servers advertise "keyboard-interactive", however, any - * interactive request will be denied (without having sent any challenge to - * the client). - * - * @param user - * A String holding the username. - * @param submethods - * An array of submethod names, see - * draft-ietf-secsh-auth-kbdinteract-XX. May be null - * to indicate an empty list. - * @param cb - * An InteractiveCallback which will be used to - * determine the responses to the questions asked by the server. - * - * @return whether the connection is now authenticated. - * @throws IOException - */ - - public synchronized boolean authenticateWithKeyboardInteractive(String user, String[] submethods, - InteractiveCallback cb) throws IOException { - if (cb == null) - throw new IllegalArgumentException("Callback may not ne NULL!"); - - if (tm == null) - throw new IllegalStateException("Connection is not established!"); - - if (authenticated) - throw new IllegalStateException("Connection is already authenticated!"); - - if (am == null) - am = new AuthenticationManager(tm); - - if (cm == null) - cm = new ChannelManager(tm); - - if (user == null) - throw new IllegalArgumentException("user argument is null"); - - authenticated = am.authenticateInteractive(user, submethods, cb); - return authenticated; - } - - /** - * After a successful connect, one has to authenticate oneself. This method - * sends username and password to the server. - *

- * If the authentication phase is complete, true will be - * returned. If the server does not accept the request (or if further - * authentication steps are needed), false is returned and - * one can retry either by using this or any other authentication method - * (use the getRemainingAuthMethods method to get a list of - * the remaining possible methods). - *

- * Note: if this method fails, then please double-check that it is actually - * offered by the server (use - * {@link #getRemainingAuthMethods(String) getRemainingAuthMethods()}. - *

- * Often, password authentication is disabled, but users are not aware of - * it. Many servers only offer "publickey" and "keyboard-interactive". - * However, even though "keyboard-interactive" *feels* like password - * authentication (e.g., when using the putty or openssh clients) it is - * *not* the same mechanism. - * - * @param user - * @param password - * @return if the connection is now authenticated. - * @throws IOException - */ - - public synchronized boolean authenticateWithPassword(String user, String password) throws IOException { - if (tm == null) - throw new IllegalStateException("Connection is not established!"); - - if (authenticated) - throw new IllegalStateException("Connection is already authenticated!"); - - if (am == null) - am = new AuthenticationManager(tm); - - if (cm == null) - cm = new ChannelManager(tm); - - if (user == null) - throw new IllegalArgumentException("user argument is null"); - - if (password == null) - throw new IllegalArgumentException("password argument is null"); - - authenticated = am.authenticatePassword(user, password); - return authenticated; - } - - /** - * After a successful connect, one has to authenticate oneself. This method - * can be used to explicitly use the special "none" authentication method - * (where only a username has to be specified). - *

- * Note 1: The "none" method may always be tried by clients, however as by - * the specs, the server will not explicitly announce it. In other words, - * the "none" token will never show up in the list returned by - * {@link #getRemainingAuthMethods(String)}. - *

- * Note 2: no matter which one of the authenticateWithXXX() methods you - * call, the library will always issue exactly one initial "none" - * authentication request to retrieve the initially allowed list of - * authentication methods by the server. Please read RFC 4252 for the - * details. - *

- * If the authentication phase is complete, true will be - * returned. If further authentication steps are needed, false - * is returned and one can retry by any other authentication method (use the - * getRemainingAuthMethods method to get a list of the - * remaining possible methods). - * - * @param user - * @return if the connection is now authenticated. - * @throws IOException - */ - - public synchronized boolean authenticateWithNone(String user) throws IOException { - if (tm == null) - throw new IllegalStateException("Connection is not established!"); - - if (authenticated) - throw new IllegalStateException("Connection is already authenticated!"); - - if (am == null) - am = new AuthenticationManager(tm); - - if (cm == null) - cm = new ChannelManager(tm); - - if (user == null) - throw new IllegalArgumentException("user argument is null"); - - /* Trigger the sending of the PacketUserauthRequestNone packet */ - /* (if not already done) */ - authenticated = am.authenticateNone(user); - return authenticated; - } - - /** - * After a successful connect, one has to authenticate oneself. The - * authentication method "publickey" works by signing a challenge sent by - * the server. The signature is either DSA or RSA based - it just depends on - * the type of private key you specify, either a DSA or RSA private key in - * PEM format. And yes, this is may seem to be a little confusing, the - * method is called "publickey" in the SSH-2 protocol specification, however - * since we need to generate a signature, you actually have to supply a - * private key =). - *

- * The private key contained in the PEM file may also be encrypted - * ("Proc-Type: 4,ENCRYPTED"). The library supports DES-CBC and DES-EDE3-CBC - * encryption, as well as the more exotic PEM encrpytions AES-128-CBC, - * AES-192-CBC and AES-256-CBC. - *

- * If the authentication phase is complete, true will be - * returned. If the server does not accept the request (or if further - * authentication steps are needed), false is returned and - * one can retry either by using this or any other authentication method - * (use the getRemainingAuthMethods method to get a list of - * the remaining possible methods). - *

- * NOTE PUTTY USERS: Event though your key file may start with - * "-----BEGIN..." it is not in the expected format. You have to convert it - * to the OpenSSH key format by using the "puttygen" tool (can be downloaded - * from the Putty website). Simply load your key and then use the - * "Conversions/Export OpenSSH key" functionality to get a proper PEM file. - * - * @param user - * A String holding the username. - * @param pemPrivateKey - * A char[] containing a DSA or RSA private key of - * the user in OpenSSH key format (PEM, you can't miss the - * "-----BEGIN DSA PRIVATE KEY-----" or "-----BEGIN RSA PRIVATE - * KEY-----" tag). The char array may contain - * linebreaks/linefeeds. - * @param password - * If the PEM structure is encrypted ("Proc-Type: 4,ENCRYPTED") - * then you must specify a password. Otherwise, this argument - * will be ignored and can be set to null. - * - * @return whether the connection is now authenticated. - * @throws IOException - */ - - public synchronized boolean authenticateWithPublicKey(String user, char[] pemPrivateKey, String password) - throws IOException { - if (tm == null) - throw new IllegalStateException("Connection is not established!"); - - if (authenticated) - throw new IllegalStateException("Connection is already authenticated!"); - - if (am == null) - am = new AuthenticationManager(tm); - - if (cm == null) - cm = new ChannelManager(tm); - - if (user == null) - throw new IllegalArgumentException("user argument is null"); - - if (pemPrivateKey == null) - throw new IllegalArgumentException("pemPrivateKey argument is null"); - - authenticated = am.authenticatePublicKey(user, pemPrivateKey, password, getOrCreateSecureRND()); - return authenticated; - } - - /** - * After a successful connect, one has to authenticate oneself. The - * authentication method "publickey" works by signing a challenge sent by - * the server. The signature is either DSA or RSA based - it just depends on - * the type of private key you specify, either a DSA or RSA private key in - * PEM format. And yes, this is may seem to be a little confusing, the - * method is called "publickey" in the SSH-2 protocol specification, however - * since we need to generate a signature, you actually have to supply a - * private key =). - *

- * If the authentication phase is complete, true will be - * returned. If the server does not accept the request (or if further - * authentication steps are needed), false is returned and - * one can retry either by using this or any other authentication method - * (use the getRemainingAuthMethods method to get a list of - * the remaining possible methods). - * - * @param user - * A String holding the username. - * @param key - * A RSAPrivateKey or DSAPrivateKey - * containing a DSA or RSA private key of - * the user in Trilead object format. - * - * @return whether the connection is now authenticated. - * @throws IOException - */ - - public synchronized boolean authenticateWithPublicKey(String user, KeyPair pair) - throws IOException { - if (tm == null) - throw new IllegalStateException("Connection is not established!"); - - if (authenticated) - throw new IllegalStateException("Connection is already authenticated!"); - - if (am == null) - am = new AuthenticationManager(tm); - - if (cm == null) - cm = new ChannelManager(tm); - - if (user == null) - throw new IllegalArgumentException("user argument is null"); - - if (pair == null) - throw new IllegalArgumentException("Key pair argument is null"); - - authenticated = am.authenticatePublicKey(user, pair, getOrCreateSecureRND()); - return authenticated; - } - - /** - * A convenience wrapper function which reads in a private key (PEM format, - * either DSA or RSA) and then calls - * authenticateWithPublicKey(String, char[], String). - *

- * NOTE PUTTY USERS: Event though your key file may start with - * "-----BEGIN..." it is not in the expected format. You have to convert it - * to the OpenSSH key format by using the "puttygen" tool (can be downloaded - * from the Putty website). Simply load your key and then use the - * "Conversions/Export OpenSSH key" functionality to get a proper PEM file. - * - * @param user - * A String holding the username. - * @param pemFile - * A File object pointing to a file containing a - * DSA or RSA private key of the user in OpenSSH key format (PEM, - * you can't miss the "-----BEGIN DSA PRIVATE KEY-----" or - * "-----BEGIN RSA PRIVATE KEY-----" tag). - * @param password - * If the PEM file is encrypted then you must specify the - * password. Otherwise, this argument will be ignored and can be - * set to null. - * - * @return whether the connection is now authenticated. - * @throws IOException - */ - - public synchronized boolean authenticateWithPublicKey(String user, File pemFile, String password) - throws IOException { - if (pemFile == null) - throw new IllegalArgumentException("pemFile argument is null"); - - char[] buff = new char[256]; - CharArrayWriter cw = new CharArrayWriter(); - FileReader fr = new FileReader(pemFile); - - while (true) { - int len = fr.read(buff); - - if (len < 0) - break; - - cw.write(buff, 0, len); - } - - fr.close(); - return authenticateWithPublicKey(user, cw.toCharArray(), password); - } - - /** - * Add a {@link ConnectionMonitor} to this connection. Can be invoked at any - * time, but it is best to add connection monitors before invoking - * connect() to avoid glitches (e.g., you add a connection - * monitor after a successful connect(), but the connection has died in the - * mean time. Then, your connection monitor won't be notified.) - *

- * You can add as many monitors as you like. - * - * @see ConnectionMonitor - * - * @param cmon - * An object implementing the ConnectionMonitor - * interface. - */ - - public synchronized void addConnectionMonitor(ConnectionMonitor cmon) { - if (cmon == null) - throw new IllegalArgumentException("cmon argument is null"); - - connectionMonitors.addElement(cmon); - - if (tm != null) - tm.setConnectionMonitors(connectionMonitors); - } - - /** - * Controls whether compression is used on the link or not. - *

- * Note: This can only be called before connect() - * @param enabled whether to enable compression - * @throws IOException - */ - - public synchronized void setCompression(boolean enabled) throws IOException { - if (tm != null) - throw new IOException("Connection to " + hostname + " is already in connected state!"); - - compression = enabled; - } - - /** - * Close the connection to the SSH-2 server. All assigned sessions will be - * closed, too. Can be called at any time. Don't forget to call this once - * you don't need a connection anymore - otherwise the receiver thread may - * run forever. - */ - - public synchronized void close() { - Throwable t = new Throwable("Closed due to user request."); - close(t, false); - } - - private void close(Throwable t, boolean hard) { - if (cm != null) - cm.closeAllChannels(); - - if (tm != null) { - tm.close(t, hard == false); - tm = null; - } - - am = null; - cm = null; - authenticated = false; - } - - /** - * Same as - * {@link #connect(ServerHostKeyVerifier, int, int) connect(null, 0, 0)}. - * - * @return see comments for the - * {@link #connect(ServerHostKeyVerifier, int, int) connect(ServerHostKeyVerifier, int, int)} - * method. - * @throws IOException - */ - - public synchronized ConnectionInfo connect() throws IOException { - return connect(null, 0, 0); - } - - /** - * Same as - * {@link #connect(ServerHostKeyVerifier, int, int) connect(verifier, 0, 0)}. - * - * @return see comments for the - * {@link #connect(ServerHostKeyVerifier, int, int) connect(ServerHostKeyVerifier, int, int)} - * method. - * @throws IOException - */ - - public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier) throws IOException { - return connect(verifier, 0, 0); - } - - /** - * Connect to the SSH-2 server and, as soon as the server has presented its - * host key, use the - * {@link ServerHostKeyVerifier#verifyServerHostKey(String, int, String, - * byte[]) ServerHostKeyVerifier.verifyServerHostKey()} method of the - * verifier to ask for permission to proceed. If - * verifier is null, then any host key will - * be accepted - this is NOT recommended, since it makes man-in-the-middle - * attackes VERY easy (somebody could put a proxy SSH server between you and - * the real server). - *

- * Note: The verifier will be called before doing any crypto calculations - * (i.e., diffie-hellman). Therefore, if you don't like the presented host - * key then no CPU cycles are wasted (and the evil server has less - * information about us). - *

- * However, it is still possible that the server presented a fake host key: - * the server cheated (typically a sign for a man-in-the-middle attack) and - * is not able to generate a signature that matches its host key. Don't - * worry, the library will detect such a scenario later when checking the - * signature (the signature cannot be checked before having completed the - * diffie-hellman exchange). - *

- * Note 2: The {@link ServerHostKeyVerifier#verifyServerHostKey(String, int, - * String, byte[]) ServerHostKeyVerifier.verifyServerHostKey()} method will - * *NOT* be called from the current thread, the call is being made from a - * background thread (there is a background dispatcher thread for every - * established connection). - *

- * Note 3: This method will block as long as the key exchange of the - * underlying connection has not been completed (and you have not specified - * any timeouts). - *

- * Note 4: If you want to re-use a connection object that was successfully - * connected, then you must call the {@link #close()} method before invoking - * connect() again. - * - * @param verifier - * An object that implements the {@link ServerHostKeyVerifier} - * interface. Pass null to accept any server host - * key - NOT recommended. - * - * @param connectTimeout - * Connect the underlying TCP socket to the server with the given - * timeout value (non-negative, in milliseconds). Zero means no - * timeout. If a proxy is being used (see - * {@link #setProxyData(ProxyData)}), then this timeout is used - * for the connection establishment to the proxy. - * - * @param kexTimeout - * Timeout for complete connection establishment (non-negative, - * in milliseconds). Zero means no timeout. The timeout counts - * from the moment you invoke the connect() method and is - * cancelled as soon as the first key-exchange round has - * finished. It is possible that the timeout event will be fired - * during the invocation of the verifier callback, - * but it will only have an effect after the - * verifier returns. - * - * @return A {@link ConnectionInfo} object containing the details of the - * established connection. - * - * @throws IOException - * If any problem occurs, e.g., the server's host key is not - * accepted by the verifier or there is problem - * during the initial crypto setup (e.g., the signature sent by - * the server is wrong). - *

- * In case of a timeout (either connectTimeout or kexTimeout) a - * SocketTimeoutException is thrown. - *

- * An exception may also be thrown if the connection was already - * successfully connected (no matter if the connection broke in - * the mean time) and you invoke connect() again - * without having called {@link #close()} first. - *

- * If a HTTP proxy is being used and the proxy refuses the - * connection, then a {@link HTTPProxyException} may be thrown, - * which contains the details returned by the proxy. If the - * proxy is buggy and does not return a proper HTTP response, - * then a normal IOException is thrown instead. - */ - - public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier, int connectTimeout, int kexTimeout) - throws IOException { - final class TimeoutState { - boolean isCancelled = false; - boolean timeoutSocketClosed = false; - } - - if (tm != null) - throw new IOException("Connection to " + hostname + " is already in connected state!"); - - if (connectTimeout < 0) - throw new IllegalArgumentException("connectTimeout must be non-negative!"); - - if (kexTimeout < 0) - throw new IllegalArgumentException("kexTimeout must be non-negative!"); - - final TimeoutState state = new TimeoutState(); - tm = new TransportManager(hostname, port); - tm.setConnectionMonitors(connectionMonitors); - - // Don't offer compression if not requested - if (!compression) { - cryptoWishList.c2s_comp_algos = new String[] { "none" }; - cryptoWishList.s2c_comp_algos = new String[] { "none" }; - } - - /* - * Make sure that the runnable below will observe the new value of "tm" - * and "state" (the runnable will be executed in a different thread, - * which may be already running, that is why we need a memory barrier - * here). See also the comment in Channel.java if you are interested in - * the details. - * - * OKOK, this is paranoid since adding the runnable to the todo list of - * the TimeoutService will ensure that all writes have been flushed - * before the Runnable reads anything (there is a synchronized block in - * TimeoutService.addTimeoutHandler). - */ - - synchronized (tm) { - /* We could actually synchronize on anything. */ - } - - try { - TimeoutToken token = null; - - if (kexTimeout > 0) { - final Runnable timeoutHandler = new Runnable() { - public void run() { - synchronized (state) { - if (state.isCancelled) - return; - - state.timeoutSocketClosed = true; - tm.close(new SocketTimeoutException("The connect timeout expired"), false); - } - } - }; - long timeoutHorizont = System.currentTimeMillis() + kexTimeout; - token = TimeoutService.addTimeoutHandler(timeoutHorizont, timeoutHandler); - } - - try { - tm.initialize(cryptoWishList, verifier, dhgexpara, connectTimeout, getOrCreateSecureRND(), proxyData); - } - catch (SocketTimeoutException se) { - throw(SocketTimeoutException) new SocketTimeoutException( - "The connect() operation on the socket timed out.").initCause(se); - } - - tm.setTcpNoDelay(tcpNoDelay); - /* Wait until first KEX has finished */ - ConnectionInfo ci = tm.getConnectionInfo(1); - - /* Now try to cancel the timeout, if needed */ - - if (token != null) { - TimeoutService.cancelTimeoutHandler(token); - - /* Were we too late? */ - - synchronized (state) { - if (state.timeoutSocketClosed) - throw new IOException("This exception will be replaced by the one below =)"); - - /* - * Just in case the "cancelTimeoutHandler" invocation came - * just a little bit too late but the handler did not enter - * the semaphore yet - we can still stop it. - */ - state.isCancelled = true; - } - } - - return ci; - } - catch (SocketTimeoutException ste) { - throw ste; - } - catch (IOException e1) { - /* This will also invoke any registered connection monitors */ - close(new Throwable("There was a problem during connect."), false); - - synchronized (state) { - /* - * Show a clean exception, not something like "the socket is - * closed!?!" - */ - if (state.timeoutSocketClosed) - throw new SocketTimeoutException("The kexTimeout (" + kexTimeout + " ms) expired."); - } - - /* Do not wrap a HTTPProxyException */ - if (e1 instanceof HTTPProxyException) - throw e1; - - throw(IOException) new IOException("There was a problem while connecting to " + hostname + ":" + port) - .initCause(e1); - } - } - - /** - * Creates a new {@link LocalPortForwarder}. A - * LocalPortForwarder forwards TCP/IP connections that arrive - * at a local port via the secure tunnel to another host (which may or may - * not be identical to the remote SSH-2 server). - *

- * This method must only be called after one has passed successfully the - * authentication step. There is no limit on the number of concurrent - * forwardings. - * - * @param local_port - * the local port the LocalPortForwarder shall bind to. - * @param host_to_connect - * target address (IP or hostname) - * @param port_to_connect - * target port - * @return A {@link LocalPortForwarder} object. - * @throws IOException - */ - - public synchronized LocalPortForwarder createLocalPortForwarder(int local_port, String host_to_connect, - int port_to_connect) throws IOException { - if (tm == null) - throw new IllegalStateException("Cannot forward ports, you need to establish a connection first."); - - if (!authenticated) - throw new IllegalStateException("Cannot forward ports, connection is not authenticated."); - - return new LocalPortForwarder(cm, local_port, host_to_connect, port_to_connect); - } - - /** - * Creates a new {@link LocalPortForwarder}. A - * LocalPortForwarder forwards TCP/IP connections that arrive - * at a local port via the secure tunnel to another host (which may or may - * not be identical to the remote SSH-2 server). - *

- * This method must only be called after one has passed successfully the - * authentication step. There is no limit on the number of concurrent - * forwardings. - * - * @param addr - * specifies the InetSocketAddress where the local socket shall - * be bound to. - * @param host_to_connect - * target address (IP or hostname) - * @param port_to_connect - * target port - * @return A {@link LocalPortForwarder} object. - * @throws IOException - */ - - public synchronized LocalPortForwarder createLocalPortForwarder(InetSocketAddress addr, String host_to_connect, - int port_to_connect) throws IOException { - if (tm == null) - throw new IllegalStateException("Cannot forward ports, you need to establish a connection first."); - - if (!authenticated) - throw new IllegalStateException("Cannot forward ports, connection is not authenticated."); - - return new LocalPortForwarder(cm, addr, host_to_connect, port_to_connect); - } - - /** - * Creates a new {@link LocalStreamForwarder}. A - * LocalStreamForwarder manages an Input/Outputstream pair - * that is being forwarded via the secure tunnel into a TCP/IP connection to - * another host (which may or may not be identical to the remote SSH-2 - * server). - * - * @param host_to_connect - * @param port_to_connect - * @return A {@link LocalStreamForwarder} object. - * @throws IOException - */ - - public synchronized LocalStreamForwarder createLocalStreamForwarder(String host_to_connect, int port_to_connect) - throws IOException { - if (tm == null) - throw new IllegalStateException("Cannot forward, you need to establish a connection first."); - - if (!authenticated) - throw new IllegalStateException("Cannot forward, connection is not authenticated."); - - return new LocalStreamForwarder(cm, host_to_connect, port_to_connect); - } - - /** - * Creates a new {@link DynamicPortForwarder}. A - * DynamicPortForwarder forwards TCP/IP connections that arrive - * at a local port via the secure tunnel to another host that is chosen via - * the SOCKS protocol. - *

- * This method must only be called after one has passed successfully the - * authentication step. There is no limit on the number of concurrent - * forwardings. - * - * @param local_port - * @return A {@link DynamicPortForwarder} object. - * @throws IOException - */ - - public synchronized DynamicPortForwarder createDynamicPortForwarder(int local_port) throws IOException { - if (tm == null) - throw new IllegalStateException("Cannot forward ports, you need to establish a connection first."); - - if (!authenticated) - throw new IllegalStateException("Cannot forward ports, connection is not authenticated."); - - return new DynamicPortForwarder(cm, local_port); - } - - /** - * Creates a new {@link DynamicPortForwarder}. A - * DynamicPortForwarder forwards TCP/IP connections that arrive - * at a local port via the secure tunnel to another host that is chosen via - * the SOCKS protocol. - *

- * This method must only be called after one has passed successfully the - * authentication step. There is no limit on the number of concurrent - * forwardings. - * - * @param addr - * specifies the InetSocketAddress where the local socket shall - * be bound to. - * @return A {@link DynamicPortForwarder} object. - * @throws IOException - */ - - public synchronized DynamicPortForwarder createDynamicPortForwarder(InetSocketAddress addr) throws IOException { - if (tm == null) - throw new IllegalStateException("Cannot forward ports, you need to establish a connection first."); - - if (!authenticated) - throw new IllegalStateException("Cannot forward ports, connection is not authenticated."); - - return new DynamicPortForwarder(cm, addr); - } - - /** - * Create a very basic {@link SCPClient} that can be used to copy files - * from/to the SSH-2 server. - *

- * Works only after one has passed successfully the authentication step. - * There is no limit on the number of concurrent SCP clients. - *

- * Note: This factory method will probably disappear in the future. - * - * @return A {@link SCPClient} object. - * @throws IOException - */ - - public synchronized SCPClient createSCPClient() throws IOException { - if (tm == null) - throw new IllegalStateException("Cannot create SCP client, you need to establish a connection first."); - - if (!authenticated) - throw new IllegalStateException("Cannot create SCP client, connection is not authenticated."); - - return new SCPClient(this); - } - - /** - * Force an asynchronous key re-exchange (the call does not block). The - * latest values set for MAC, Cipher and DH group exchange parameters will - * be used. If a key exchange is currently in progress, then this method has - * the only effect that the so far specified parameters will be used for the - * next (server driven) key exchange. - *

- * Note: This implementation will never start a key exchange (other than the - * initial one) unless you or the SSH-2 server ask for it. - * - * @throws IOException - * In case of any failure behind the scenes. - */ - - public synchronized void forceKeyExchange() throws IOException { - if (tm == null) - throw new IllegalStateException("You need to establish a connection first."); - - tm.forceKeyExchange(cryptoWishList, dhgexpara); - } - - /** - * Returns the hostname that was passed to the constructor. - * - * @return the hostname - */ - - public synchronized String getHostname() { - return hostname; - } - - /** - * Returns the port that was passed to the constructor. - * - * @return the TCP port - */ - - public synchronized int getPort() { - return port; - } - - /** - * Returns a {@link ConnectionInfo} object containing the details of the - * connection. Can be called as soon as the connection has been established - * (successfully connected). - * - * @return A {@link ConnectionInfo} object. - * @throws IOException - * In case of any failure behind the scenes. - */ - - public synchronized ConnectionInfo getConnectionInfo() throws IOException { - if (tm == null) - throw new IllegalStateException( - "Cannot get details of connection, you need to establish a connection first."); - - return tm.getConnectionInfo(1); - } - - /** - * After a successful connect, one has to authenticate oneself. This method - * can be used to tell which authentication methods are supported by the - * server at a certain stage of the authentication process (for the given - * username). - *

- * Note 1: the username will only be used if no authentication step was done - * so far (it will be used to ask the server for a list of possible - * authentication methods by sending the initial "none" request). Otherwise, - * this method ignores the user name and returns a cached method list (which - * is based on the information contained in the last negative server - * response). - *

- * Note 2: the server may return method names that are not supported by this - * implementation. - *

- * After a successful authentication, this method must not be called - * anymore. - * - * @param user - * A String holding the username. - * - * @return a (possibly emtpy) array holding authentication method names. - * @throws IOException - */ - - public synchronized String[] getRemainingAuthMethods(String user) throws IOException { - if (user == null) - throw new IllegalArgumentException("user argument may not be NULL!"); - - if (tm == null) - throw new IllegalStateException("Connection is not established!"); - - if (authenticated) - throw new IllegalStateException("Connection is already authenticated!"); - - if (am == null) - am = new AuthenticationManager(tm); - - if (cm == null) - cm = new ChannelManager(tm); - - return am.getRemainingMethods(user); - } - - /** - * Determines if the authentication phase is complete. Can be called at any - * time. - * - * @return true if no further authentication steps are - * needed. - */ - - public synchronized boolean isAuthenticationComplete() { - return authenticated; - } - - /** - * Returns true if there was at least one failed authentication request and - * the last failed authentication request was marked with "partial success" - * by the server. This is only needed in the rare case of SSH-2 server - * setups that cannot be satisfied with a single successful authentication - * request (i.e., multiple authentication steps are needed.) - *

- * If you are interested in the details, then have a look at RFC4252. - * - * @return if the there was a failed authentication step and the last one - * was marked as a "partial success". - */ - - public synchronized boolean isAuthenticationPartialSuccess() { - if (am == null) - return false; - - return am.getPartialSuccess(); - } - - /** - * Checks if a specified authentication method is available. This method is - * actually just a wrapper for {@link #getRemainingAuthMethods(String) - * getRemainingAuthMethods()}. - * - * @param user - * A String holding the username. - * @param method - * An authentication method name (e.g., "publickey", "password", - * "keyboard-interactive") as specified by the SSH-2 standard. - * @return if the specified authentication method is currently available. - * @throws IOException - */ - - public synchronized boolean isAuthMethodAvailable(String user, String method) throws IOException { - if (method == null) - throw new IllegalArgumentException("method argument may not be NULL!"); - - String methods[] = getRemainingAuthMethods(user); - - for (int i = 0; i < methods.length; i++) { - if (methods[i].compareTo(method) == 0) - return true; - } - - return false; - } - - private final SecureRandom getOrCreateSecureRND() { - if (generator == null) - generator = new SecureRandom(); - - return generator; - } - - /** - * Open a new {@link Session} on this connection. Works only after one has - * passed successfully the authentication step. There is no limit on the - * number of concurrent sessions. - * - * @return A {@link Session} object. - * @throws IOException - */ - - public synchronized Session openSession() throws IOException { - if (tm == null) - throw new IllegalStateException("Cannot open session, you need to establish a connection first."); - - if (!authenticated) - throw new IllegalStateException("Cannot open session, connection is not authenticated."); - - return new Session(cm, getOrCreateSecureRND()); - } - - /** - * Send an SSH_MSG_IGNORE packet. This method will generate a random data - * attribute (length between 0 (invlusive) and 16 (exclusive) bytes, - * contents are random bytes). - *

- * This method must only be called once the connection is established. - * - * @throws IOException - */ - - public synchronized void sendIgnorePacket() throws IOException { - SecureRandom rnd = getOrCreateSecureRND(); - byte[] data = new byte[rnd.nextInt(16)]; - rnd.nextBytes(data); - sendIgnorePacket(data); - } - - /** - * Send an SSH_MSG_IGNORE packet with the given data attribute. - *

- * This method must only be called once the connection is established. - * - * @throws IOException - */ - - public synchronized void sendIgnorePacket(byte[] data) throws IOException { - if (data == null) - throw new IllegalArgumentException("data argument must not be null."); - - if (tm == null) - throw new IllegalStateException( - "Cannot send SSH_MSG_IGNORE packet, you need to establish a connection first."); - - PacketIgnore pi = new PacketIgnore(); - pi.setData(data); - tm.sendMessage(pi.getPayload()); - } - - /** - * Removes duplicates from a String array, keeps only first occurence of - * each element. Does not destroy order of elements; can handle nulls. Uses - * a very efficient O(N^2) algorithm =) - * - * @param list - * a String array. - * @return a cleaned String array. - */ - private String[] removeDuplicates(String[] list) { - if ((list == null) || (list.length < 2)) - return list; - - String[] list2 = new String[list.length]; - int count = 0; - - for (int i = 0; i < list.length; i++) { - boolean duplicate = false; - String element = list[i]; - - for (int j = 0; j < count; j++) { - if (((element == null) && (list2[j] == null)) || ((element != null) && (element.equals(list2[j])))) { - duplicate = true; - break; - } - } - - if (duplicate) - continue; - - list2[count++] = list[i]; - } - - if (count == list2.length) - return list2; - - String[] tmp = new String[count]; - System.arraycopy(list2, 0, tmp, 0, count); - return tmp; - } - - /** - * Unless you know what you are doing, you will never need this. - * - * @param ciphers - */ - - public synchronized void setClient2ServerCiphers(String[] ciphers) { - if ((ciphers == null) || (ciphers.length == 0)) - throw new IllegalArgumentException(); - - ciphers = removeDuplicates(ciphers); - BlockCipherFactory.checkCipherList(ciphers); - cryptoWishList.c2s_enc_algos = ciphers; - } - - /** - * Unless you know what you are doing, you will never need this. - * - * @param macs - */ - - public synchronized void setClient2ServerMACs(String[] macs) { - if ((macs == null) || (macs.length == 0)) - throw new IllegalArgumentException(); - - macs = removeDuplicates(macs); - MAC.checkMacList(macs); - cryptoWishList.c2s_mac_algos = macs; - } - - /** - * Sets the parameters for the diffie-hellman group exchange. Unless you - * know what you are doing, you will never need this. Default values are - * defined in the {@link DHGexParameters} class. - * - * @param dgp - * {@link DHGexParameters}, non null. - * - */ - - public synchronized void setDHGexParameters(DHGexParameters dgp) { - if (dgp == null) - throw new IllegalArgumentException(); - - dhgexpara = dgp; - } - - /** - * Unless you know what you are doing, you will never need this. - * - * @param ciphers - */ - - public synchronized void setServer2ClientCiphers(String[] ciphers) { - if ((ciphers == null) || (ciphers.length == 0)) - throw new IllegalArgumentException(); - - ciphers = removeDuplicates(ciphers); - BlockCipherFactory.checkCipherList(ciphers); - cryptoWishList.s2c_enc_algos = ciphers; - } - - /** - * Unless you know what you are doing, you will never need this. - * - * @param macs - */ - - public synchronized void setServer2ClientMACs(String[] macs) { - if ((macs == null) || (macs.length == 0)) - throw new IllegalArgumentException(); - - macs = removeDuplicates(macs); - MAC.checkMacList(macs); - cryptoWishList.s2c_mac_algos = macs; - } - - /** - * Define the set of allowed server host key algorithms to be used for the - * following key exchange operations. - *

- * Unless you know what you are doing, you will never need this. - * - * @param algos - * An array of allowed server host key algorithms. SSH-2 defines - * ssh-dss and ssh-rsa. The - * entries of the array must be ordered after preference, i.e., - * the entry at index 0 is the most preferred one. You must - * specify at least one entry. - */ - - public synchronized void setServerHostKeyAlgorithms(String[] algos) { - if ((algos == null) || (algos.length == 0)) - throw new IllegalArgumentException(); - - algos = removeDuplicates(algos); - KexManager.checkServerHostkeyAlgorithmsList(algos); - cryptoWishList.serverHostKeyAlgorithms = algos; - } - - /** - * Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm) on the - * underlying socket. - *

- * Can be called at any time. If the connection has not yet been established - * then the passed value will be stored and set after the socket has been - * set up. The default value that will be used is false. - * - * @param enable - * the argument passed to the Socket.setTCPNoDelay() - * method. - * @throws IOException - */ - - public synchronized void setTCPNoDelay(boolean enable) throws IOException { - tcpNoDelay = enable; - - if (tm != null) - tm.setTcpNoDelay(enable); - } - - /** - * Used to tell the library that the connection shall be established through - * a proxy server. It only makes sense to call this method before calling - * the {@link #connect() connect()} method. - *

- * At the moment, only HTTP proxies are supported. - *

- * Note: This method can be called any number of times. The - * {@link #connect() connect()} method will use the value set in the last - * preceding invocation of this method. - * - * @see HTTPProxyData - * - * @param proxyData - * Connection information about the proxy. If null, - * then no proxy will be used (non surprisingly, this is also the - * default). - */ - - public synchronized void setProxyData(ProxyData proxyData) { - this.proxyData = proxyData; - } - - /** - * Request a remote port forwarding. If successful, then forwarded - * connections will be redirected to the given target address. You can - * cancle a requested remote port forwarding by calling - * {@link #cancelRemotePortForwarding(int) cancelRemotePortForwarding()}. - *

- * A call of this method will block until the peer either agreed or - * disagreed to your request- - *

- * Note 1: this method typically fails if you - *

    - *
  • pass a port number for which the used remote user has not enough - * permissions (i.e., port < 1024)
  • - *
  • or pass a port number that is already in use on the remote server
  • - *
  • or if remote port forwarding is disabled on the server.
  • - *
- *

- * Note 2: (from the openssh man page): By default, the listening socket on - * the server will be bound to the loopback interface only. This may be - * overriden by specifying a bind address. Specifying a remote bind address - * will only succeed if the server's GatewayPorts option is enabled - * (see sshd_config(5)). - * - * @param bindAddress - * address to bind to on the server: - *

    - *
  • "" means that connections are to be accepted on all - * protocol families supported by the SSH implementation
  • - *
  • "0.0.0.0" means to listen on all IPv4 addresses
  • - *
  • "::" means to listen on all IPv6 addresses
  • - *
  • "localhost" means to listen on all protocol families - * supported by the SSH implementation on loopback addresses - * only, [RFC3330] and RFC3513]
  • - *
  • "127.0.0.1" and "::1" indicate listening on the loopback - * interfaces for IPv4 and IPv6 respectively
  • - *
- * @param bindPort - * port number to bind on the server (must be > 0) - * @param targetAddress - * the target address (IP or hostname) - * @param targetPort - * the target port - * @throws IOException - */ - - public synchronized void requestRemotePortForwarding(String bindAddress, int bindPort, String targetAddress, - int targetPort) throws IOException { - if (tm == null) - throw new IllegalStateException("You need to establish a connection first."); - - if (!authenticated) - throw new IllegalStateException("The connection is not authenticated."); - - if ((bindAddress == null) || (targetAddress == null) || (bindPort <= 0) || (targetPort <= 0)) - throw new IllegalArgumentException(); - - cm.requestGlobalForward(bindAddress, bindPort, targetAddress, targetPort); - } - - /** - * Cancel an earlier requested remote port forwarding. Currently active - * forwardings will not be affected (e.g., disrupted). Note that further - * connection forwarding requests may be received until this method has - * returned. - * - * @param bindPort - * the allocated port number on the server - * @throws IOException - * if the remote side refuses the cancel request or another low - * level error occurs (e.g., the underlying connection is - * closed) - */ - - public synchronized void cancelRemotePortForwarding(int bindPort) throws IOException { - if (tm == null) - throw new IllegalStateException("You need to establish a connection first."); - - if (!authenticated) - throw new IllegalStateException("The connection is not authenticated."); - - cm.requestCancelGlobalForward(bindPort); - } - - /** - * Provide your own instance of SecureRandom. Can be used, e.g., if you want - * to seed the used SecureRandom generator manually. - *

- * The SecureRandom instance is used during key exchanges, public key - * authentication, x11 cookie generation and the like. - * - * @param rnd - * a SecureRandom instance - */ - - public synchronized void setSecureRandom(SecureRandom rnd) { - if (rnd == null) - throw new IllegalArgumentException(); - - this.generator = rnd; - } - - /** - * Enable/disable debug logging. Only do this when requested by Trilead - * support. - *

- * For speed reasons, some static variables used to check whether debugging - * is enabled are not protected with locks. In other words, if you - * dynamicaly enable/disable debug logging, then some threads may still use - * the old setting. To be on the safe side, enable debugging before doing - * the connect() call. - * - * @param enable - * on/off - * @param logger - * a {@link DebugLogger DebugLogger} instance, null - * means logging using the simple logger which logs all messages - * to to stderr. Ignored if enabled is false - */ - - public synchronized void enableDebugging(boolean enable, DebugLogger logger) { - Logger.enabled = enable; - - if (enable == false) { - Logger.logger = null; - } - else { - if (logger == null) { - logger = new DebugLogger() { - public void log(int level, String className, String message) { - long now = System.currentTimeMillis(); - System.err.println(now + " : " + className + ": " + message); - } - }; - } - - Logger.logger = logger; - } - } - - /** - * This method can be used to perform end-to-end connection testing. It - * sends a 'ping' message to the server and waits for the 'pong' from the - * server. - *

- * When this method throws an exception, then you can assume that the - * connection should be abandoned. - *

- * Note: Works only after one has passed successfully the authentication - * step. - *

- * Implementation details: this method sends a SSH_MSG_GLOBAL_REQUEST - * request ('trilead-ping') to the server and waits for the - * SSH_MSG_REQUEST_FAILURE reply packet from the server. - * - * @throws IOException - * in case of any problem - */ - - public synchronized void ping() throws IOException { - if (tm == null) - throw new IllegalStateException("You need to establish a connection first."); - - if (!authenticated) - throw new IllegalStateException("The connection is not authenticated."); - - cm.requestGlobalTrileadPing(); - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/ConnectionInfo.java --- a/src/com/trilead/ssh2/ConnectionInfo.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ - -package com.trilead.ssh2; - -/** - * In most cases you probably do not need the information contained in here. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: ConnectionInfo.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -public class ConnectionInfo { - /** - * The used key exchange (KEX) algorithm in the latest key exchange. - */ - public String keyExchangeAlgorithm; - - /** - * The currently used crypto algorithm for packets from to the client to the - * server. - */ - public String clientToServerCryptoAlgorithm; - /** - * The currently used crypto algorithm for packets from to the server to the - * client. - */ - public String serverToClientCryptoAlgorithm; - - /** - * The currently used MAC algorithm for packets from to the client to the - * server. - */ - public String clientToServerMACAlgorithm; - /** - * The currently used MAC algorithm for packets from to the server to the - * client. - */ - public String serverToClientMACAlgorithm; - - /** - * The type of the server host key (currently either "ssh-dss" or - * "ssh-rsa"). - */ - public String serverHostKeyAlgorithm; - /** - * The server host key that was sent during the latest key exchange. - */ - public byte[] serverHostKey; - - /** - * Number of kex exchanges performed on this connection so far. - */ - public int keyExchangeCounter = 0; -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/ConnectionMonitor.java --- a/src/com/trilead/ssh2/ConnectionMonitor.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ - -package com.trilead.ssh2; - -/** - * A ConnectionMonitor is used to get notified when the - * underlying socket of a connection is closed. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: ConnectionMonitor.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ - -public interface ConnectionMonitor { - /** - * This method is called after the connection's underlying - * socket has been closed. E.g., due to the {@link Connection#close()} request of the - * user, if the peer closed the connection, due to a fatal error during connect() - * (also if the socket cannot be established) or if a fatal error occured on - * an established connection. - *

- * This is an experimental feature. - *

- * You MUST NOT make any assumption about the thread that invokes this method. - *

- * Please note: if the connection is not connected (e.g., there was no successful - * connect() call), then the invocation of {@link Connection#close()} will NOT trigger - * this method. - * - * @see Connection#addConnectionMonitor(ConnectionMonitor) - * - * @param reason Includes an indication why the socket was closed. - */ - public void connectionLost(Throwable reason); -} \ No newline at end of file diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/DHGexParameters.java --- a/src/com/trilead/ssh2/DHGexParameters.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,114 +0,0 @@ - -package com.trilead.ssh2; - -/** - * A DHGexParameters object can be used to specify parameters for - * the diffie-hellman group exchange. - *

- * Depending on which constructor is used, either the use of a - * SSH_MSG_KEX_DH_GEX_REQUEST or SSH_MSG_KEX_DH_GEX_REQUEST_OLD - * can be forced. - * - * @see Connection#setDHGexParameters(DHGexParameters) - * @author Christian Plattner, plattner@trilead.com - * @version $Id: DHGexParameters.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ - -public class DHGexParameters { - private final int min_group_len; - private final int pref_group_len; - private final int max_group_len; - - private static final int MIN_ALLOWED = 1024; - private static final int MAX_ALLOWED = 8192; - - /** - * Same as calling {@link #DHGexParameters(int, int, int) DHGexParameters(1024, 1024, 4096)}. - * This is also the default used by the Connection class. - * - */ - public DHGexParameters() { - this(1024, 1024, 4096); - } - - /** - * This constructor can be used to force the sending of a - * SSH_MSG_KEX_DH_GEX_REQUEST_OLD request. - * Internally, the minimum and maximum group lengths will - * be set to zero. - * - * @param pref_group_len has to be >= 1024 and <= 8192 - */ - public DHGexParameters(int pref_group_len) { - if ((pref_group_len < MIN_ALLOWED) || (pref_group_len > MAX_ALLOWED)) - throw new IllegalArgumentException("pref_group_len out of range!"); - - this.pref_group_len = pref_group_len; - this.min_group_len = 0; - this.max_group_len = 0; - } - - /** - * This constructor can be used to force the sending of a - * SSH_MSG_KEX_DH_GEX_REQUEST request. - *

- * Note: older OpenSSH servers don't understand this request, in which - * case you should use the {@link #DHGexParameters(int)} constructor. - *

- * All values have to be >= 1024 and <= 8192. Furthermore, - * min_group_len <= pref_group_len <= max_group_len. - * - * @param min_group_len - * @param pref_group_len - * @param max_group_len - */ - public DHGexParameters(int min_group_len, int pref_group_len, int max_group_len) { - if ((min_group_len < MIN_ALLOWED) || (min_group_len > MAX_ALLOWED)) - throw new IllegalArgumentException("min_group_len out of range!"); - - if ((pref_group_len < MIN_ALLOWED) || (pref_group_len > MAX_ALLOWED)) - throw new IllegalArgumentException("pref_group_len out of range!"); - - if ((max_group_len < MIN_ALLOWED) || (max_group_len > MAX_ALLOWED)) - throw new IllegalArgumentException("max_group_len out of range!"); - - if ((pref_group_len < min_group_len) || (pref_group_len > max_group_len)) - throw new IllegalArgumentException("pref_group_len is incompatible with min and max!"); - - if (max_group_len < min_group_len) - throw new IllegalArgumentException("max_group_len must not be smaller than min_group_len!"); - - this.min_group_len = min_group_len; - this.pref_group_len = pref_group_len; - this.max_group_len = max_group_len; - } - - /** - * Get the maximum group length. - * - * @return the maximum group length, may be zero if - * SSH_MSG_KEX_DH_GEX_REQUEST_OLD should be requested - */ - public int getMax_group_len() { - return max_group_len; - } - - /** - * Get the minimum group length. - * - * @return minimum group length, may be zero if - * SSH_MSG_KEX_DH_GEX_REQUEST_OLD should be requested - */ - public int getMin_group_len() { - return min_group_len; - } - - /** - * Get the preferred group length. - * - * @return the preferred group length - */ - public int getPref_group_len() { - return pref_group_len; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/DebugLogger.java --- a/src/com/trilead/ssh2/DebugLogger.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -package com.trilead.ssh2; - -/** - * An interface which needs to be implemented if you - * want to capture debugging messages. - * - * @see Connection#enableDebugging(boolean, DebugLogger) - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: DebugLogger.java,v 1.1 2008/03/03 07:01:36 cplattne Exp $ - */ -public interface DebugLogger { - - /** - * Log a debug message. - * - * @param level 0-99, 99 is a the most verbose level - * @param className the class that generated the message - * @param message the debug message - */ - public void log(int level, String className, String message); -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/DynamicPortForwarder.java --- a/src/com/trilead/ssh2/DynamicPortForwarder.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +0,0 @@ -/* - * 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 com.trilead.ssh2; - -import java.io.IOException; -import java.net.InetSocketAddress; - -import com.trilead.ssh2.channel.ChannelManager; -import com.trilead.ssh2.channel.DynamicAcceptThread; - -/** - * A DynamicPortForwarder forwards TCP/IP connections to a local - * port via the secure tunnel to another host which is selected via the - * SOCKS protocol. Checkout {@link Connection#createDynamicPortForwarder(int)} - * on how to create one. - * - * @author Kenny Root - * @version $Id: $ - */ -public class DynamicPortForwarder { - ChannelManager cm; - - DynamicAcceptThread dat; - - DynamicPortForwarder(ChannelManager cm, int local_port) - throws IOException { - this.cm = cm; - dat = new DynamicAcceptThread(cm, local_port); - dat.setDaemon(true); - dat.start(); - } - - DynamicPortForwarder(ChannelManager cm, InetSocketAddress addr) throws IOException { - this.cm = cm; - dat = new DynamicAcceptThread(cm, addr); - dat.setDaemon(true); - dat.start(); - } - - /** - * Stop TCP/IP forwarding of newly arriving connections. - * - * @throws IOException - */ - public void close() throws IOException { - dat.stopWorking(); - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/HTTPProxyData.java --- a/src/com/trilead/ssh2/HTTPProxyData.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,79 +0,0 @@ - -package com.trilead.ssh2; - -/** - * A HTTPProxyData object is used to specify the needed connection data - * to connect through a HTTP proxy. - * - * @see Connection#setProxyData(ProxyData) - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: HTTPProxyData.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ - -public class HTTPProxyData implements ProxyData { - public final String proxyHost; - public final int proxyPort; - public final String proxyUser; - public final String proxyPass; - public final String[] requestHeaderLines; - - /** - * Same as calling {@link #HTTPProxyData(String, int, String, String) HTTPProxyData(proxyHost, proxyPort, null, null)} - * - * @param proxyHost Proxy hostname. - * @param proxyPort Proxy port. - */ - public HTTPProxyData(String proxyHost, int proxyPort) { - this(proxyHost, proxyPort, null, null); - } - - /** - * Same as calling {@link #HTTPProxyData(String, int, String, String, String[]) HTTPProxyData(proxyHost, proxyPort, null, null, null)} - * - * @param proxyHost Proxy hostname. - * @param proxyPort Proxy port. - * @param proxyUser Username for basic authentication (null if no authentication is needed). - * @param proxyPass Password for basic authentication (null if no authentication is needed). - */ - public HTTPProxyData(String proxyHost, int proxyPort, String proxyUser, String proxyPass) { - this(proxyHost, proxyPort, proxyUser, proxyPass, null); - } - - /** - * Connection data for a HTTP proxy. It is possible to specify a username and password - * if the proxy requires basic authentication. Also, additional request header lines can - * be specified (e.g., "User-Agent: CERN-LineMode/2.15 libwww/2.17b3"). - *

- * Please note: if you want to use basic authentication, then both proxyUser - * and proxyPass must be non-null. - *

- * Here is an example: - *

- * - * new HTTPProxyData("192.168.1.1", "3128", "proxyuser", "secret", new String[] {"User-Agent: TrileadBasedClient/1.0", "X-My-Proxy-Option: something"}); - * - * - * @param proxyHost Proxy hostname. - * @param proxyPort Proxy port. - * @param proxyUser Username for basic authentication (null if no authentication is needed). - * @param proxyPass Password for basic authentication (null if no authentication is needed). - * @param requestHeaderLines An array with additional request header lines (without end-of-line markers) - * that have to be sent to the server. May be null. - */ - - public HTTPProxyData(String proxyHost, int proxyPort, String proxyUser, String proxyPass, - String[] requestHeaderLines) { - if (proxyHost == null) - throw new IllegalArgumentException("proxyHost must be non-null"); - - if (proxyPort < 0) - throw new IllegalArgumentException("proxyPort must be non-negative"); - - this.proxyHost = proxyHost; - this.proxyPort = proxyPort; - this.proxyUser = proxyUser; - this.proxyPass = proxyPass; - this.requestHeaderLines = requestHeaderLines; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/HTTPProxyException.java --- a/src/com/trilead/ssh2/HTTPProxyException.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ - -package com.trilead.ssh2; - -import java.io.IOException; - -/** - * May be thrown upon connect() if a HTTP proxy is being used. - * - * @see Connection#connect() - * @see Connection#setProxyData(ProxyData) - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: HTTPProxyException.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ - -public class HTTPProxyException extends IOException { - private static final long serialVersionUID = 2241537397104426186L; - - public final String httpResponse; - public final int httpErrorCode; - - public HTTPProxyException(String httpResponse, int httpErrorCode) { - super("HTTP Proxy Error (" + httpErrorCode + " " + httpResponse + ")"); - this.httpResponse = httpResponse; - this.httpErrorCode = httpErrorCode; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/InteractiveCallback.java --- a/src/com/trilead/ssh2/InteractiveCallback.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ - -package com.trilead.ssh2; - -/** - * An InteractiveCallback is used to respond to challenges sent - * by the server if authentication mode "keyboard-interactive" is selected. - * - * @see Connection#authenticateWithKeyboardInteractive(String, - * String[], InteractiveCallback) - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: InteractiveCallback.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ - -public interface InteractiveCallback { - /** - * This callback interface is used during a "keyboard-interactive" - * authentication. Every time the server sends a set of challenges (however, - * most often just one challenge at a time), this callback function will be - * called to give your application a chance to talk to the user and to - * determine the response(s). - *

- * Some copy-paste information from the standard: a command line interface - * (CLI) client SHOULD print the name and instruction (if non-empty), adding - * newlines. Then for each prompt in turn, the client SHOULD display the - * prompt and read the user input. The name and instruction fields MAY be - * empty strings, the client MUST be prepared to handle this correctly. The - * prompt field(s) MUST NOT be empty strings. - *

- * Please refer to draft-ietf-secsh-auth-kbdinteract-XX.txt for the details. - *

- * Note: clients SHOULD use control character filtering as discussed in - * RFC4251 to avoid attacks by including - * terminal control characters in the fields to be displayed. - * - * @param name - * the name String sent by the server. - * @param instruction - * the instruction String sent by the server. - * @param numPrompts - * number of prompts - may be zero (in this case, you should just - * return a String array of length zero). - * @param prompt - * an array (length numPrompts) of Strings - * @param echo - * an array (length numPrompts) of booleans. For - * each prompt, the corresponding echo field indicates whether or - * not the user input should be echoed as characters are typed. - * @return an array of reponses - the array size must match the parameter - * numPrompts. - */ - public String[] replyToChallenge(String name, String instruction, int numPrompts, String[] prompt, boolean[] echo) - throws Exception; -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/KnownHosts.java --- a/src/com/trilead/ssh2/KnownHosts.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,759 +0,0 @@ - -package com.trilead.ssh2; - -import java.io.BufferedReader; -import java.io.CharArrayReader; -import java.io.CharArrayWriter; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.io.UnsupportedEncodingException; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.security.InvalidKeyException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.SecureRandom; -import java.security.interfaces.DSAPublicKey; -import java.security.interfaces.ECPublicKey; -import java.security.interfaces.RSAPublicKey; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.Locale; -import java.util.Vector; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - -import com.trilead.ssh2.crypto.Base64; -import com.trilead.ssh2.signature.DSASHA1Verify; -import com.trilead.ssh2.signature.ECDSASHA2Verify; -import com.trilead.ssh2.signature.RSASHA1Verify; - - -/** - * The KnownHosts class is a handy tool to verify received server hostkeys - * based on the information in known_hosts files (the ones used by OpenSSH). - *

- * It offers basically an in-memory database for known_hosts entries, as well as some - * helper functions. Entries from a known_hosts file can be loaded at construction time. - * It is also possible to add more keys later (e.g., one can parse different - * known_hosts files). - *

- * It is a thread safe implementation, therefore, you need only to instantiate one - * KnownHosts for your whole application. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: KnownHosts.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ - */ - -public class KnownHosts { - public static final int HOSTKEY_IS_OK = 0; - public static final int HOSTKEY_IS_NEW = 1; - public static final int HOSTKEY_HAS_CHANGED = 2; - - private class KnownHostsEntry { - String[] patterns; - PublicKey key; - - KnownHostsEntry(String[] patterns, PublicKey key) { - this.patterns = patterns; - this.key = key; - } - } - - private LinkedList publicKeys = new LinkedList(); - - public KnownHosts() { - } - - public KnownHosts(char[] knownHostsData) throws IOException { - initialize(knownHostsData); - } - - public KnownHosts(File knownHosts) throws IOException { - initialize(knownHosts); - } - - /** - * Adds a single public key entry to the database. Note: this will NOT add the public key - * to any physical file (e.g., "~/.ssh/known_hosts") - use addHostkeyToFile() for that purpose. - * This method is designed to be used in a {@link ServerHostKeyVerifier}. - * - * @param hostnames a list of hostname patterns - at least one most be specified. Check out the - * OpenSSH sshd man page for a description of the pattern matching algorithm. - * @param serverHostKeyAlgorithm as passed to the {@link ServerHostKeyVerifier}. - * @param serverHostKey as passed to the {@link ServerHostKeyVerifier}. - * @throws IOException - */ - public void addHostkey(String hostnames[], String serverHostKeyAlgorithm, byte[] serverHostKey) throws IOException { - if (hostnames == null) - throw new IllegalArgumentException("hostnames may not be null"); - - if ("ssh-rsa".equals(serverHostKeyAlgorithm)) { - RSAPublicKey rpk = RSASHA1Verify.decodeSSHRSAPublicKey(serverHostKey); - - synchronized (publicKeys) { - publicKeys.add(new KnownHostsEntry(hostnames, rpk)); - } - } - else if ("ssh-dss".equals(serverHostKeyAlgorithm)) { - DSAPublicKey dpk = DSASHA1Verify.decodeSSHDSAPublicKey(serverHostKey); - - synchronized (publicKeys) { - publicKeys.add(new KnownHostsEntry(hostnames, dpk)); - } - } - else if (serverHostKeyAlgorithm.startsWith(ECDSASHA2Verify.ECDSA_SHA2_PREFIX)) { - ECPublicKey epk = ECDSASHA2Verify.decodeSSHECDSAPublicKey(serverHostKey); - - synchronized (publicKeys) { - publicKeys.add(new KnownHostsEntry(hostnames, epk)); - } - } - else - throw new IOException("Unknwon host key type (" + serverHostKeyAlgorithm + ")"); - } - - /** - * Parses the given known_hosts data and adds entries to the database. - * - * @param knownHostsData - * @throws IOException - */ - public void addHostkeys(char[] knownHostsData) throws IOException { - initialize(knownHostsData); - } - - /** - * Parses the given known_hosts file and adds entries to the database. - * - * @param knownHosts - * @throws IOException - */ - public void addHostkeys(File knownHosts) throws IOException { - initialize(knownHosts); - } - - /** - * Generate the hashed representation of the given hostname. Useful for adding entries - * with hashed hostnames to a known_hosts file. (see -H option of OpenSSH key-gen). - * - * @param hostname - * @return the hashed representation, e.g., "|1|cDhrv7zwEUV3k71CEPHnhHZezhA=|Xo+2y6rUXo2OIWRAYhBOIijbJMA=" - */ - public static final String createHashedHostname(String hostname) { - MessageDigest sha1; - - try { - sha1 = MessageDigest.getInstance("SHA1"); - } - catch (NoSuchAlgorithmException e) { - throw new RuntimeException("VM doesn't support SHA1", e); - } - - byte[] salt = new byte[sha1.getDigestLength()]; - new SecureRandom().nextBytes(salt); - byte[] hash = hmacSha1Hash(salt, hostname); - String base64_salt = new String(Base64.encode(salt)); - String base64_hash = new String(Base64.encode(hash)); - return new String("|1|" + base64_salt + "|" + base64_hash); - } - - private static final byte[] hmacSha1Hash(byte[] salt, String hostname) { - Mac hmac; - - try { - hmac = Mac.getInstance("HmacSHA1"); - - if (salt.length != hmac.getMacLength()) - throw new IllegalArgumentException("Salt has wrong length (" + salt.length + ")"); - - hmac.init(new SecretKeySpec(salt, "HmacSHA1")); - } - catch (NoSuchAlgorithmException e) { - throw new RuntimeException("Unable to HMAC-SHA1", e); - } - catch (InvalidKeyException e) { - throw new RuntimeException("Unable to create SecretKey", e); - } - - try { - hmac.update(hostname.getBytes("ISO-8859-1")); - } - catch (UnsupportedEncodingException ignore) { - /* Actually, ISO-8859-1 is supported by all correct - * Java implementations. But... you never know. */ - hmac.update(hostname.getBytes()); - } - - return hmac.doFinal(); - } - - private final boolean checkHashed(String entry, String hostname) { - if (entry.startsWith("|1|") == false) - return false; - - int delim_idx = entry.indexOf('|', 3); - - if (delim_idx == -1) - return false; - - String salt_base64 = entry.substring(3, delim_idx); - String hash_base64 = entry.substring(delim_idx + 1); - byte[] salt = null; - byte[] hash = null; - - try { - salt = Base64.decode(salt_base64.toCharArray()); - hash = Base64.decode(hash_base64.toCharArray()); - } - catch (IOException e) { - return false; - } - - try { - MessageDigest sha1 = MessageDigest.getInstance("SHA1"); - - if (salt.length != sha1.getDigestLength()) - return false; - } - catch (NoSuchAlgorithmException e) { - throw new RuntimeException("VM does not support SHA1", e); - } - - byte[] dig = hmacSha1Hash(salt, hostname); - - for (int i = 0; i < dig.length; i++) - if (dig[i] != hash[i]) - return false; - - return true; - } - - private int checkKey(String remoteHostname, PublicKey remoteKey) { - int result = HOSTKEY_IS_NEW; - - synchronized (publicKeys) { - Iterator i = publicKeys.iterator(); - - while (i.hasNext()) { - KnownHostsEntry ke = i.next(); - - if (hostnameMatches(ke.patterns, remoteHostname) == false) - continue; - - boolean res = matchKeys(ke.key, remoteKey); - - if (res == true) - return HOSTKEY_IS_OK; - - result = HOSTKEY_HAS_CHANGED; - } - } - - return result; - } - - private Vector getAllKeys(String hostname) { - Vector keys = new Vector(); - - synchronized (publicKeys) { - Iterator i = publicKeys.iterator(); - - while (i.hasNext()) { - KnownHostsEntry ke = i.next(); - - if (hostnameMatches(ke.patterns, hostname) == false) - continue; - - keys.addElement(ke.key); - } - } - - return keys; - } - - /** - * Try to find the preferred order of hostkey algorithms for the given hostname. - * Based on the type of hostkey that is present in the internal database - * (i.e., either ssh-rsa or ssh-dss) - * an ordered list of hostkey algorithms is returned which can be passed - * to Connection.setServerHostKeyAlgorithms. - * - * @param hostname - * @return null if no key for the given hostname is present or - * there are keys of multiple types present for the given hostname. Otherwise, - * an array with hostkey algorithms is returned (i.e., an array of length 2). - */ - public String[] getPreferredServerHostkeyAlgorithmOrder(String hostname) { - String[] algos = recommendHostkeyAlgorithms(hostname); - - if (algos != null) - return algos; - - InetAddress[] ipAdresses = null; - - try { - ipAdresses = InetAddress.getAllByName(hostname); - } - catch (UnknownHostException e) { - return null; - } - - for (int i = 0; i < ipAdresses.length; i++) { - algos = recommendHostkeyAlgorithms(ipAdresses[i].getHostAddress()); - - if (algos != null) - return algos; - } - - return null; - } - - private final boolean hostnameMatches(String[] hostpatterns, String hostname) { - boolean isMatch = false; - boolean negate = false; - hostname = hostname.toLowerCase(Locale.US); - - for (int k = 0; k < hostpatterns.length; k++) { - if (hostpatterns[k] == null) - continue; - - String pattern = null; - - /* In contrast to OpenSSH we also allow negated hash entries (as well as hashed - * entries in lines with multiple entries). - */ - - if ((hostpatterns[k].length() > 0) && (hostpatterns[k].charAt(0) == '!')) { - pattern = hostpatterns[k].substring(1); - negate = true; - } - else { - pattern = hostpatterns[k]; - negate = false; - } - - /* Optimize, no need to check this entry */ - - if ((isMatch) && (negate == false)) - continue; - - /* Now compare */ - - if (pattern.charAt(0) == '|') { - if (checkHashed(pattern, hostname)) { - if (negate) - return false; - - isMatch = true; - } - } - else { - pattern = pattern.toLowerCase(Locale.US); - - if ((pattern.indexOf('?') != -1) || (pattern.indexOf('*') != -1)) { - if (pseudoRegex(pattern.toCharArray(), 0, hostname.toCharArray(), 0)) { - if (negate) - return false; - - isMatch = true; - } - } - else if (pattern.compareTo(hostname) == 0) { - if (negate) - return false; - - isMatch = true; - } - } - } - - return isMatch; - } - - private void initialize(char[] knownHostsData) throws IOException { - BufferedReader br = new BufferedReader(new CharArrayReader(knownHostsData)); - - while (true) { - String line = br.readLine(); - - if (line == null) - break; - - line = line.trim(); - - if (line.startsWith("#")) - continue; - - String[] arr = line.split(" "); - - if (arr.length >= 3) { - if ((arr[1].compareTo("ssh-rsa") == 0) || (arr[1].compareTo("ssh-dss") == 0)) { - String[] hostnames = arr[0].split(","); - byte[] msg = Base64.decode(arr[2].toCharArray()); - addHostkey(hostnames, arr[1], msg); - } - } - } - } - - private void initialize(File knownHosts) throws IOException { - char[] buff = new char[512]; - CharArrayWriter cw = new CharArrayWriter(); - knownHosts.createNewFile(); - FileReader fr = new FileReader(knownHosts); - - while (true) { - int len = fr.read(buff); - - if (len < 0) - break; - - cw.write(buff, 0, len); - } - - fr.close(); - initialize(cw.toCharArray()); - } - - private final boolean matchKeys(PublicKey key1, PublicKey key2) { - return key1.equals(key2); - } - - private final boolean pseudoRegex(char[] pattern, int i, char[] match, int j) { - /* This matching logic is equivalent to the one present in OpenSSH 4.1 */ - while (true) { - /* Are we at the end of the pattern? */ - if (pattern.length == i) - return (match.length == j); - - if (pattern[i] == '*') { - i++; - - if (pattern.length == i) - return true; - - if ((pattern[i] != '*') && (pattern[i] != '?')) { - while (true) { - if ((pattern[i] == match[j]) && pseudoRegex(pattern, i + 1, match, j + 1)) - return true; - - j++; - - if (match.length == j) - return false; - } - } - - while (true) { - if (pseudoRegex(pattern, i, match, j)) - return true; - - j++; - - if (match.length == j) - return false; - } - } - - if (match.length == j) - return false; - - if ((pattern[i] != '?') && (pattern[i] != match[j])) - return false; - - i++; - j++; - } - } - - private String[] recommendHostkeyAlgorithms(String hostname) { - String preferredAlgo = null; - Vector keys = getAllKeys(hostname); - - for (int i = 0; i < keys.size(); i++) { - String thisAlgo = null; - - if (keys.elementAt(i) instanceof RSAPublicKey) - thisAlgo = "ssh-rsa"; - else if (keys.elementAt(i) instanceof DSAPublicKey) - thisAlgo = "ssh-dss"; - else - continue; - - if (preferredAlgo != null) { - /* If we find different key types, then return null */ - if (preferredAlgo.compareTo(thisAlgo) != 0) - return null; - - /* OK, we found the same algo again, optimize */ - continue; - } - } - - /* If we did not find anything that we know of, return null */ - - if (preferredAlgo == null) - return null; - - /* Now put the preferred algo to the start of the array. - * You may ask yourself why we do it that way - basically, we could just - * return only the preferred algorithm: since we have a saved key of that - * type (sent earlier from the remote host), then that should work out. - * However, imagine that the server is (for whatever reasons) not offering - * that type of hostkey anymore (e.g., "ssh-rsa" was disabled and - * now "ssh-dss" is being used). If we then do not let the server send us - * a fresh key of the new type, then we shoot ourself into the foot: - * the connection cannot be established and hence the user cannot decide - * if he/she wants to accept the new key. - */ - - if (preferredAlgo.equals("ssh-rsa")) - return new String[] { "ssh-rsa", "ssh-dss" }; - - return new String[] { "ssh-dss", "ssh-rsa" }; - } - - /** - * Checks the internal hostkey database for the given hostkey. - * If no matching key can be found, then the hostname is resolved to an IP address - * and the search is repeated using that IP address. - * - * @param hostname the server's hostname, will be matched with all hostname patterns - * @param serverHostKeyAlgorithm type of hostkey, either ssh-rsa or ssh-dss - * @param serverHostKey the key blob - * @return

    - *
  • HOSTKEY_IS_OK: the given hostkey matches an entry for the given hostname
  • - *
  • HOSTKEY_IS_NEW: no entries found for this hostname and this type of hostkey
  • - *
  • HOSTKEY_HAS_CHANGED: hostname is known, but with another key of the same type - * (man-in-the-middle attack?)
  • - *
- * @throws IOException if the supplied key blob cannot be parsed or does not match the given hostkey type. - */ - public int verifyHostkey(String hostname, String serverHostKeyAlgorithm, byte[] serverHostKey) throws IOException { - PublicKey remoteKey = null; - - if ("ssh-rsa".equals(serverHostKeyAlgorithm)) { - remoteKey = RSASHA1Verify.decodeSSHRSAPublicKey(serverHostKey); - } - else if ("ssh-dss".equals(serverHostKeyAlgorithm)) { - remoteKey = DSASHA1Verify.decodeSSHDSAPublicKey(serverHostKey); - } - else if (serverHostKeyAlgorithm.startsWith("ecdsa-sha2-")) { - remoteKey = ECDSASHA2Verify.decodeSSHECDSAPublicKey(serverHostKey); - } - else - throw new IllegalArgumentException("Unknown hostkey type " + serverHostKeyAlgorithm); - - int result = checkKey(hostname, remoteKey); - - if (result == HOSTKEY_IS_OK) - return result; - - InetAddress[] ipAdresses = null; - - try { - ipAdresses = InetAddress.getAllByName(hostname); - } - catch (UnknownHostException e) { - return result; - } - - for (int i = 0; i < ipAdresses.length; i++) { - int newresult = checkKey(ipAdresses[i].getHostAddress(), remoteKey); - - if (newresult == HOSTKEY_IS_OK) - return newresult; - - if (newresult == HOSTKEY_HAS_CHANGED) - result = HOSTKEY_HAS_CHANGED; - } - - return result; - } - - /** - * Adds a single public key entry to the a known_hosts file. - * This method is designed to be used in a {@link ServerHostKeyVerifier}. - * - * @param knownHosts the file where the publickey entry will be appended. - * @param hostnames a list of hostname patterns - at least one most be specified. Check out the - * OpenSSH sshd man page for a description of the pattern matching algorithm. - * @param serverHostKeyAlgorithm as passed to the {@link ServerHostKeyVerifier}. - * @param serverHostKey as passed to the {@link ServerHostKeyVerifier}. - * @throws IOException - */ - public final static void addHostkeyToFile(File knownHosts, String[] hostnames, String serverHostKeyAlgorithm, - byte[] serverHostKey) throws IOException { - if ((hostnames == null) || (hostnames.length == 0)) - throw new IllegalArgumentException("Need at least one hostname specification"); - - if ((serverHostKeyAlgorithm == null) || (serverHostKey == null)) - throw new IllegalArgumentException(); - - CharArrayWriter writer = new CharArrayWriter(); - - for (int i = 0; i < hostnames.length; i++) { - if (i != 0) - writer.write(','); - - writer.write(hostnames[i]); - } - - writer.write(' '); - writer.write(serverHostKeyAlgorithm); - writer.write(' '); - writer.write(Base64.encode(serverHostKey)); - writer.write("\n"); - char[] entry = writer.toCharArray(); - RandomAccessFile raf = new RandomAccessFile(knownHosts, "rw"); - long len = raf.length(); - - if (len > 0) { - raf.seek(len - 1); - int last = raf.read(); - - if (last != '\n') - raf.write('\n'); - } - - raf.write(new String(entry).getBytes("ISO-8859-1")); - raf.close(); - } - - /** - * Generates a "raw" fingerprint of a hostkey. - * - * @param type either "md5" or "sha1" - * @param keyType either "ssh-rsa" or "ssh-dss" - * @param hostkey the hostkey - * @return the raw fingerprint - */ - static final private byte[] rawFingerPrint(String type, String keyType, byte[] hostkey) { - MessageDigest dig = null; - - try { - if ("md5".equals(type)) { - dig = MessageDigest.getInstance("MD5"); - } - else if ("sha1".equals(type)) { - dig = MessageDigest.getInstance("SHA1"); - } - else { - throw new IllegalArgumentException("Unknown hash type " + type); - } - } - catch (NoSuchAlgorithmException e) { - throw new IllegalArgumentException("Unknown hash type " + type); - } - - if (keyType.startsWith("ecdsa-sha2-")) { - } - else if ("ssh-rsa".equals(keyType)) { - } - else if ("ssh-dss".equals(keyType)) { - } - else - throw new IllegalArgumentException("Unknown key type " + keyType); - - if (hostkey == null) - throw new IllegalArgumentException("hostkey is null"); - - dig.update(hostkey); - return dig.digest(); - } - - /** - * Convert a raw fingerprint to hex representation (XX:YY:ZZ...). - * @param fingerprint raw fingerprint - * @return the hex representation - */ - static final private String rawToHexFingerprint(byte[] fingerprint) { - final char[] alpha = "0123456789abcdef".toCharArray(); - StringBuffer sb = new StringBuffer(); - - for (int i = 0; i < fingerprint.length; i++) { - if (i != 0) - sb.append(':'); - - int b = fingerprint[i] & 0xff; - sb.append(alpha[b >> 4]); - sb.append(alpha[b & 15]); - } - - return sb.toString(); - } - - /** - * Convert a raw fingerprint to bubblebabble representation. - * @param raw raw fingerprint - * @return the bubblebabble representation - */ - static final private String rawToBubblebabbleFingerprint(byte[] raw) { - final char[] v = "aeiouy".toCharArray(); - final char[] c = "bcdfghklmnprstvzx".toCharArray(); - StringBuffer sb = new StringBuffer(); - int seed = 1; - int rounds = (raw.length / 2) + 1; - sb.append('x'); - - for (int i = 0; i < rounds; i++) { - if (((i + 1) < rounds) || ((raw.length) % 2 != 0)) { - sb.append(v[(((raw[2 * i] >> 6) & 3) + seed) % 6]); - sb.append(c[(raw[2 * i] >> 2) & 15]); - sb.append(v[((raw[2 * i] & 3) + (seed / 6)) % 6]); - - if ((i + 1) < rounds) { - sb.append(c[(((raw[(2 * i) + 1])) >> 4) & 15]); - sb.append('-'); - sb.append(c[(((raw[(2 * i) + 1]))) & 15]); - // As long as seed >= 0, seed will be >= 0 afterwards - seed = ((seed * 5) + (((raw[2 * i] & 0xff) * 7) + (raw[(2 * i) + 1] & 0xff))) % 36; - } - } - else { - sb.append(v[seed % 6]); // seed >= 0, therefore index positive - sb.append('x'); - sb.append(v[seed / 6]); - } - } - - sb.append('x'); - return sb.toString(); - } - - /** - * Convert a ssh2 key-blob into a human readable hex fingerprint. - * Generated fingerprints are identical to those generated by OpenSSH. - *

- * Example fingerprint: d0:cb:76:19:99:5a:03:fc:73:10:70:93:f2:44:63:47. - - * @param keytype either "ssh-rsa" or "ssh-dss" - * @param publickey key blob - * @return Hex fingerprint - */ - public final static String createHexFingerprint(String keytype, byte[] publickey) { - byte[] raw = rawFingerPrint("md5", keytype, publickey); - return rawToHexFingerprint(raw); - } - - /** - * Convert a ssh2 key-blob into a human readable bubblebabble fingerprint. - * The used bubblebabble algorithm (taken from OpenSSH) generates fingerprints - * that are easier to remember for humans. - *

- * Example fingerprint: xofoc-bubuz-cazin-zufyl-pivuk-biduk-tacib-pybur-gonar-hotat-lyxux. - * - * @param keytype either "ssh-rsa" or "ssh-dss" - * @param publickey key data - * @return Bubblebabble fingerprint - */ - public final static String createBubblebabbleFingerprint(String keytype, byte[] publickey) { - byte[] raw = rawFingerPrint("sha1", keytype, publickey); - return rawToBubblebabbleFingerprint(raw); - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/LocalPortForwarder.java --- a/src/com/trilead/ssh2/LocalPortForwarder.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ - -package com.trilead.ssh2; - -import java.io.IOException; -import java.net.InetSocketAddress; - -import com.trilead.ssh2.channel.ChannelManager; -import com.trilead.ssh2.channel.LocalAcceptThread; - - -/** - * A LocalPortForwarder forwards TCP/IP connections to a local - * port via the secure tunnel to another host (which may or may not be identical - * to the remote SSH-2 server). Checkout {@link Connection#createLocalPortForwarder(int, String, int)} - * on how to create one. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: LocalPortForwarder.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -public class LocalPortForwarder { - ChannelManager cm; - - String host_to_connect; - - int port_to_connect; - - LocalAcceptThread lat; - - LocalPortForwarder(ChannelManager cm, int local_port, String host_to_connect, int port_to_connect) - throws IOException { - this.cm = cm; - this.host_to_connect = host_to_connect; - this.port_to_connect = port_to_connect; - lat = new LocalAcceptThread(cm, local_port, host_to_connect, port_to_connect); - lat.setDaemon(true); - lat.start(); - } - - LocalPortForwarder(ChannelManager cm, InetSocketAddress addr, String host_to_connect, int port_to_connect) - throws IOException { - this.cm = cm; - this.host_to_connect = host_to_connect; - this.port_to_connect = port_to_connect; - lat = new LocalAcceptThread(cm, addr, host_to_connect, port_to_connect); - lat.setDaemon(true); - lat.start(); - } - - /** - * Stop TCP/IP forwarding of newly arriving connections. - * - * @throws IOException - */ - public void close() throws IOException { - lat.stopWorking(); - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/LocalStreamForwarder.java --- a/src/com/trilead/ssh2/LocalStreamForwarder.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ - -package com.trilead.ssh2; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import com.trilead.ssh2.channel.Channel; -import com.trilead.ssh2.channel.ChannelManager; -import com.trilead.ssh2.channel.LocalAcceptThread; - - -/** - * A LocalStreamForwarder forwards an Input- and Outputstream - * pair via the secure tunnel to another host (which may or may not be identical - * to the remote SSH-2 server). - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: LocalStreamForwarder.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -public class LocalStreamForwarder { - ChannelManager cm; - - String host_to_connect; - int port_to_connect; - LocalAcceptThread lat; - - Channel cn; - - LocalStreamForwarder(ChannelManager cm, String host_to_connect, int port_to_connect) throws IOException { - this.cm = cm; - this.host_to_connect = host_to_connect; - this.port_to_connect = port_to_connect; - cn = cm.openDirectTCPIPChannel(host_to_connect, port_to_connect, "127.0.0.1", 0); - } - - /** - * @return An InputStream object. - * @throws IOException - */ - public InputStream getInputStream() throws IOException { - return cn.getStdoutStream(); - } - - /** - * Get the OutputStream. Please be aware that the implementation MAY use an - * internal buffer. To make sure that the buffered data is sent over the - * tunnel, you have to call the flush method of the - * OutputStream. To signal EOF, please use the - * close method of the OutputStream. - * - * @return An OutputStream object. - * @throws IOException - */ - public OutputStream getOutputStream() throws IOException { - return cn.getStdinStream(); - } - - /** - * Close the underlying SSH forwarding channel and free up resources. - * You can also use this method to force the shutdown of the underlying - * forwarding channel. Pending output (OutputStream not flushed) will NOT - * be sent. Pending input (InputStream) can still be read. If the shutdown - * operation is already in progress (initiated from either side), then this - * call is a no-op. - * - * @throws IOException - */ - public void close() throws IOException { - cm.closeChannel(cn, "Closed due to user request.", true); - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/ProxyData.java --- a/src/com/trilead/ssh2/ProxyData.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ - -package com.trilead.ssh2; - -/** - * An abstract marker interface implemented by all proxy data implementations. - * - * @see HTTPProxyData - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: ProxyData.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ - -public abstract interface ProxyData { -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/SCPClient.java --- a/src/com/trilead/ssh2/SCPClient.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,644 +0,0 @@ - -package com.trilead.ssh2; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * A very basic SCPClient that can be used to copy files from/to - * the SSH-2 server. On the server side, the "scp" program must be in the PATH. - *

- * This scp client is thread safe - you can download (and upload) different sets - * of files concurrently without any troubles. The SCPClient is - * actually mapping every request to a distinct {@link Session}. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: SCPClient.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ - */ - -public class SCPClient { - Connection conn; - - class LenNamePair { - long length; - String filename; - } - - public SCPClient(Connection conn) { - if (conn == null) - throw new IllegalArgumentException("Cannot accept null argument!"); - - this.conn = conn; - } - - private void readResponse(InputStream is) throws IOException { - int c = is.read(); - - if (c == 0) - return; - - if (c == -1) - throw new IOException("Remote scp terminated unexpectedly."); - - if ((c != 1) && (c != 2)) - throw new IOException("Remote scp sent illegal error code."); - - if (c == 2) - throw new IOException("Remote scp terminated with error."); - - String err = receiveLine(is); - throw new IOException("Remote scp terminated with error (" + err + ")."); - } - - private String receiveLine(InputStream is) throws IOException { - StringBuffer sb = new StringBuffer(30); - - while (true) { - /* - * This is a random limit - if your path names are longer, then - * adjust it - */ - if (sb.length() > 8192) - throw new IOException("Remote scp sent a too long line"); - - int c = is.read(); - - if (c < 0) - throw new IOException("Remote scp terminated unexpectedly."); - - if (c == '\n') - break; - - sb.append((char) c); - } - - return sb.toString(); - } - - private LenNamePair parseCLine(String line) throws IOException { - /* Minimum line: "xxxx y z" ---> 8 chars */ - long len; - - if (line.length() < 8) - throw new IOException("Malformed C line sent by remote SCP binary, line too short."); - - if ((line.charAt(4) != ' ') || (line.charAt(5) == ' ')) - throw new IOException("Malformed C line sent by remote SCP binary."); - - int length_name_sep = line.indexOf(' ', 5); - - if (length_name_sep == -1) - throw new IOException("Malformed C line sent by remote SCP binary."); - - String length_substring = line.substring(5, length_name_sep); - String name_substring = line.substring(length_name_sep + 1); - - if ((length_substring.length() <= 0) || (name_substring.length() <= 0)) - throw new IOException("Malformed C line sent by remote SCP binary."); - - if ((6 + length_substring.length() + name_substring.length()) != line.length()) - throw new IOException("Malformed C line sent by remote SCP binary."); - - try { - len = Long.parseLong(length_substring); - } - catch (NumberFormatException e) { - throw new IOException("Malformed C line sent by remote SCP binary, cannot parse file length."); - } - - if (len < 0) - throw new IOException("Malformed C line sent by remote SCP binary, illegal file length."); - - LenNamePair lnp = new LenNamePair(); - lnp.length = len; - lnp.filename = name_substring; - return lnp; - } - - private void sendBytes(Session sess, byte[] data, String fileName, String mode) throws IOException { - OutputStream os = sess.getStdin(); - InputStream is = new BufferedInputStream(sess.getStdout(), 512); - readResponse(is); - String cline = "C" + mode + " " + data.length + " " + fileName + "\n"; - os.write(cline.getBytes("ISO-8859-1")); - os.flush(); - readResponse(is); - os.write(data, 0, data.length); - os.write(0); - os.flush(); - readResponse(is); - os.write("E\n".getBytes("ISO-8859-1")); - os.flush(); - } - - private void sendFiles(Session sess, String[] files, String[] remoteFiles, String mode) throws IOException { - byte[] buffer = new byte[8192]; - OutputStream os = new BufferedOutputStream(sess.getStdin(), 40000); - InputStream is = new BufferedInputStream(sess.getStdout(), 512); - readResponse(is); - - for (int i = 0; i < files.length; i++) { - File f = new File(files[i]); - long remain = f.length(); - String remoteName; - - if ((remoteFiles != null) && (remoteFiles.length > i) && (remoteFiles[i] != null)) - remoteName = remoteFiles[i]; - else - remoteName = f.getName(); - - String cline = "C" + mode + " " + remain + " " + remoteName + "\n"; - os.write(cline.getBytes("ISO-8859-1")); - os.flush(); - readResponse(is); - FileInputStream fis = null; - - try { - fis = new FileInputStream(f); - - while (remain > 0) { - int trans; - - if (remain > buffer.length) - trans = buffer.length; - else - trans = (int) remain; - - if (fis.read(buffer, 0, trans) != trans) - throw new IOException("Cannot read enough from local file " + files[i]); - - os.write(buffer, 0, trans); - remain -= trans; - } - } - finally { - if (fis != null) - fis.close(); - } - - os.write(0); - os.flush(); - readResponse(is); - } - - os.write("E\n".getBytes("ISO-8859-1")); - os.flush(); - } - - private void receiveFiles(Session sess, OutputStream[] targets) throws IOException { - byte[] buffer = new byte[8192]; - OutputStream os = new BufferedOutputStream(sess.getStdin(), 512); - InputStream is = new BufferedInputStream(sess.getStdout(), 40000); - os.write(0x0); - os.flush(); - - for (int i = 0; i < targets.length; i++) { - LenNamePair lnp = null; - - while (true) { - int c = is.read(); - - if (c < 0) - throw new IOException("Remote scp terminated unexpectedly."); - - String line = receiveLine(is); - - if (c == 'T') { - /* Ignore modification times */ - continue; - } - - if ((c == 1) || (c == 2)) - throw new IOException("Remote SCP error: " + line); - - if (c == 'C') { - lnp = parseCLine(line); - break; - } - - throw new IOException("Remote SCP error: " + ((char) c) + line); - } - - os.write(0x0); - os.flush(); - long remain = lnp.length; - - while (remain > 0) { - int trans; - - if (remain > buffer.length) - trans = buffer.length; - else - trans = (int) remain; - - int this_time_received = is.read(buffer, 0, trans); - - if (this_time_received < 0) { - throw new IOException("Remote scp terminated connection unexpectedly"); - } - - targets[i].write(buffer, 0, this_time_received); - remain -= this_time_received; - } - - readResponse(is); - os.write(0x0); - os.flush(); - } - } - - private void receiveFiles(Session sess, String[] files, String target) throws IOException { - byte[] buffer = new byte[8192]; - OutputStream os = new BufferedOutputStream(sess.getStdin(), 512); - InputStream is = new BufferedInputStream(sess.getStdout(), 40000); - os.write(0x0); - os.flush(); - - for (int i = 0; i < files.length; i++) { - LenNamePair lnp = null; - - while (true) { - int c = is.read(); - - if (c < 0) - throw new IOException("Remote scp terminated unexpectedly."); - - String line = receiveLine(is); - - if (c == 'T') { - /* Ignore modification times */ - continue; - } - - if ((c == 1) || (c == 2)) - throw new IOException("Remote SCP error: " + line); - - if (c == 'C') { - lnp = parseCLine(line); - break; - } - - throw new IOException("Remote SCP error: " + ((char) c) + line); - } - - os.write(0x0); - os.flush(); - File f = new File(target + File.separatorChar + lnp.filename); - FileOutputStream fop = null; - - try { - fop = new FileOutputStream(f); - long remain = lnp.length; - - while (remain > 0) { - int trans; - - if (remain > buffer.length) - trans = buffer.length; - else - trans = (int) remain; - - int this_time_received = is.read(buffer, 0, trans); - - if (this_time_received < 0) { - throw new IOException("Remote scp terminated connection unexpectedly"); - } - - fop.write(buffer, 0, this_time_received); - remain -= this_time_received; - } - } - finally { - if (fop != null) - fop.close(); - } - - readResponse(is); - os.write(0x0); - os.flush(); - } - } - - /** - * Copy a local file to a remote directory, uses mode 0600 when creating the - * file on the remote side. - * - * @param localFile - * Path and name of local file. - * @param remoteTargetDirectory - * Remote target directory. Use an empty string to specify the - * default directory. - * - * @throws IOException - */ - public void put(String localFile, String remoteTargetDirectory) throws IOException { - put(new String[] { localFile }, remoteTargetDirectory, "0600"); - } - - /** - * Copy a set of local files to a remote directory, uses mode 0600 when - * creating files on the remote side. - * - * @param localFiles - * Paths and names of local file names. - * @param remoteTargetDirectory - * Remote target directory. Use an empty string to specify the - * default directory. - * - * @throws IOException - */ - - public void put(String[] localFiles, String remoteTargetDirectory) throws IOException { - put(localFiles, remoteTargetDirectory, "0600"); - } - - /** - * Copy a local file to a remote directory, uses the specified mode when - * creating the file on the remote side. - * - * @param localFile - * Path and name of local file. - * @param remoteTargetDirectory - * Remote target directory. Use an empty string to specify the - * default directory. - * @param mode - * a four digit string (e.g., 0644, see "man chmod", "man open") - * @throws IOException - */ - public void put(String localFile, String remoteTargetDirectory, String mode) throws IOException { - put(new String[] { localFile }, remoteTargetDirectory, mode); - } - - /** - * Copy a local file to a remote directory, uses the specified mode and - * remote filename when creating the file on the remote side. - * - * @param localFile - * Path and name of local file. - * @param remoteFileName - * The name of the file which will be created in the remote - * target directory. - * @param remoteTargetDirectory - * Remote target directory. Use an empty string to specify the - * default directory. - * @param mode - * a four digit string (e.g., 0644, see "man chmod", "man open") - * @throws IOException - */ - public void put(String localFile, String remoteFileName, String remoteTargetDirectory, String mode) - throws IOException { - put(new String[] { localFile }, new String[] { remoteFileName }, remoteTargetDirectory, mode); - } - - /** - * Create a remote file and copy the contents of the passed byte array into - * it. Uses mode 0600 for creating the remote file. - * - * @param data - * the data to be copied into the remote file. - * @param remoteFileName - * The name of the file which will be created in the remote - * target directory. - * @param remoteTargetDirectory - * Remote target directory. Use an empty string to specify the - * default directory. - * @throws IOException - */ - - public void put(byte[] data, String remoteFileName, String remoteTargetDirectory) throws IOException { - put(data, remoteFileName, remoteTargetDirectory, "0600"); - } - - /** - * Create a remote file and copy the contents of the passed byte array into - * it. The method use the specified mode when creating the file on the - * remote side. - * - * @param data - * the data to be copied into the remote file. - * @param remoteFileName - * The name of the file which will be created in the remote - * target directory. - * @param remoteTargetDirectory - * Remote target directory. Use an empty string to specify the - * default directory. - * @param mode - * a four digit string (e.g., 0644, see "man chmod", "man open") - * @throws IOException - */ - public void put(byte[] data, String remoteFileName, String remoteTargetDirectory, String mode) throws IOException { - Session sess = null; - - if ((remoteFileName == null) || (remoteTargetDirectory == null) || (mode == null)) - throw new IllegalArgumentException("Null argument."); - - if (mode.length() != 4) - throw new IllegalArgumentException("Invalid mode."); - - for (int i = 0; i < mode.length(); i++) - if (Character.isDigit(mode.charAt(i)) == false) - throw new IllegalArgumentException("Invalid mode."); - - remoteTargetDirectory = remoteTargetDirectory.trim(); - remoteTargetDirectory = (remoteTargetDirectory.length() > 0) ? remoteTargetDirectory : "."; - String cmd = "scp -t -d " + remoteTargetDirectory; - - try { - sess = conn.openSession(); - sess.execCommand(cmd); - sendBytes(sess, data, remoteFileName, mode); - } - catch (IOException e) { - throw(IOException) new IOException("Error during SCP transfer.").initCause(e); - } - finally { - if (sess != null) - sess.close(); - } - } - - /** - * Copy a set of local files to a remote directory, uses the specified mode - * when creating the files on the remote side. - * - * @param localFiles - * Paths and names of the local files. - * @param remoteTargetDirectory - * Remote target directory. Use an empty string to specify the - * default directory. - * @param mode - * a four digit string (e.g., 0644, see "man chmod", "man open") - * @throws IOException - */ - public void put(String[] localFiles, String remoteTargetDirectory, String mode) throws IOException { - put(localFiles, null, remoteTargetDirectory, mode); - } - - public void put(String[] localFiles, String[] remoteFiles, String remoteTargetDirectory, String mode) - throws IOException { - Session sess = null; - - /* - * remoteFiles may be null, indicating that the local filenames shall be - * used - */ - - if ((localFiles == null) || (remoteTargetDirectory == null) || (mode == null)) - throw new IllegalArgumentException("Null argument."); - - if (mode.length() != 4) - throw new IllegalArgumentException("Invalid mode."); - - for (int i = 0; i < mode.length(); i++) - if (Character.isDigit(mode.charAt(i)) == false) - throw new IllegalArgumentException("Invalid mode."); - - if (localFiles.length == 0) - return; - - remoteTargetDirectory = remoteTargetDirectory.trim(); - remoteTargetDirectory = (remoteTargetDirectory.length() > 0) ? remoteTargetDirectory : "."; - String cmd = "scp -t -d " + remoteTargetDirectory; - - for (int i = 0; i < localFiles.length; i++) { - if (localFiles[i] == null) - throw new IllegalArgumentException("Cannot accept null filename."); - } - - try { - sess = conn.openSession(); - sess.execCommand(cmd); - sendFiles(sess, localFiles, remoteFiles, mode); - } - catch (IOException e) { - throw(IOException) new IOException("Error during SCP transfer.").initCause(e); - } - finally { - if (sess != null) - sess.close(); - } - } - - /** - * Download a file from the remote server to a local directory. - * - * @param remoteFile - * Path and name of the remote file. - * @param localTargetDirectory - * Local directory to put the downloaded file. - * - * @throws IOException - */ - public void get(String remoteFile, String localTargetDirectory) throws IOException { - get(new String[] { remoteFile }, localTargetDirectory); - } - - /** - * Download a file from the remote server and pipe its contents into an - * OutputStream. Please note that, to enable flexible usage - * of this method, the OutputStream will not be closed nor - * flushed. - * - * @param remoteFile - * Path and name of the remote file. - * @param target - * OutputStream where the contents of the file will be sent to. - * @throws IOException - */ - public void get(String remoteFile, OutputStream target) throws IOException { - get(new String[] { remoteFile }, new OutputStream[] { target }); - } - - private void get(String remoteFiles[], OutputStream[] targets) throws IOException { - Session sess = null; - - if ((remoteFiles == null) || (targets == null)) - throw new IllegalArgumentException("Null argument."); - - if (remoteFiles.length != targets.length) - throw new IllegalArgumentException("Length of arguments does not match."); - - if (remoteFiles.length == 0) - return; - - String cmd = "scp -f"; - - for (int i = 0; i < remoteFiles.length; i++) { - if (remoteFiles[i] == null) - throw new IllegalArgumentException("Cannot accept null filename."); - - String tmp = remoteFiles[i].trim(); - - if (tmp.length() == 0) - throw new IllegalArgumentException("Cannot accept empty filename."); - - cmd += (" " + tmp); - } - - try { - sess = conn.openSession(); - sess.execCommand(cmd); - receiveFiles(sess, targets); - } - catch (IOException e) { - throw(IOException) new IOException("Error during SCP transfer.").initCause(e); - } - finally { - if (sess != null) - sess.close(); - } - } - - /** - * Download a set of files from the remote server to a local directory. - * - * @param remoteFiles - * Paths and names of the remote files. - * @param localTargetDirectory - * Local directory to put the downloaded files. - * - * @throws IOException - */ - public void get(String remoteFiles[], String localTargetDirectory) throws IOException { - Session sess = null; - - if ((remoteFiles == null) || (localTargetDirectory == null)) - throw new IllegalArgumentException("Null argument."); - - if (remoteFiles.length == 0) - return; - - String cmd = "scp -f"; - - for (int i = 0; i < remoteFiles.length; i++) { - if (remoteFiles[i] == null) - throw new IllegalArgumentException("Cannot accept null filename."); - - String tmp = remoteFiles[i].trim(); - - if (tmp.length() == 0) - throw new IllegalArgumentException("Cannot accept empty filename."); - - cmd += (" " + tmp); - } - - try { - sess = conn.openSession(); - sess.execCommand(cmd); - receiveFiles(sess, remoteFiles, localTargetDirectory); - } - catch (IOException e) { - throw(IOException) new IOException("Error during SCP transfer.").initCause(e); - } - finally { - if (sess != null) - sess.close(); - } - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/SFTPException.java --- a/src/com/trilead/ssh2/SFTPException.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,84 +0,0 @@ - -package com.trilead.ssh2; - -import java.io.IOException; - -import com.trilead.ssh2.sftp.ErrorCodes; - - -/** - * Used in combination with the SFTPv3Client. This exception wraps - * error messages sent by the SFTP server. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: SFTPException.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ - -public class SFTPException extends IOException { - private static final long serialVersionUID = 578654644222421811L; - - private final String sftpErrorMessage; - private final int sftpErrorCode; - - private static String constructMessage(String s, int errorCode) { - String[] detail = ErrorCodes.getDescription(errorCode); - - if (detail == null) - return s + " (UNKNOW SFTP ERROR CODE)"; - - return s + " (" + detail[0] + ": " + detail[1] + ")"; - } - - SFTPException(String msg, int errorCode) { - super(constructMessage(msg, errorCode)); - sftpErrorMessage = msg; - sftpErrorCode = errorCode; - } - - /** - * Get the error message sent by the server. Often, this - * message does not help a lot (e.g., "failure"). - * - * @return the plain string as sent by the server. - */ - public String getServerErrorMessage() { - return sftpErrorMessage; - } - - /** - * Get the error code sent by the server. - * - * @return an error code as defined in the SFTP specs. - */ - public int getServerErrorCode() { - return sftpErrorCode; - } - - /** - * Get the symbolic name of the error code as given in the SFTP specs. - * - * @return e.g., "SSH_FX_INVALID_FILENAME". - */ - public String getServerErrorCodeSymbol() { - String[] detail = ErrorCodes.getDescription(sftpErrorCode); - - if (detail == null) - return "UNKNOW SFTP ERROR CODE " + sftpErrorCode; - - return detail[0]; - } - - /** - * Get the description of the error code as given in the SFTP specs. - * - * @return e.g., "The filename is not valid." - */ - public String getServerErrorCodeVerbose() { - String[] detail = ErrorCodes.getDescription(sftpErrorCode); - - if (detail == null) - return "The error code " + sftpErrorCode + " is unknown."; - - return detail[1]; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/SFTPv3Client.java --- a/src/com/trilead/ssh2/SFTPv3Client.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1215 +0,0 @@ - -package com.trilead.ssh2; - -import java.io.BufferedOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.PrintStream; -import java.nio.charset.Charset; -import java.util.HashMap; -import java.util.Vector; - -import com.trilead.ssh2.packets.TypesReader; -import com.trilead.ssh2.packets.TypesWriter; -import com.trilead.ssh2.sftp.AttribFlags; -import com.trilead.ssh2.sftp.ErrorCodes; -import com.trilead.ssh2.sftp.Packet; - - -/** - * A SFTPv3Client represents a SFTP (protocol version 3) - * client connection tunnelled over a SSH-2 connection. This is a very simple - * (synchronous) implementation. - *

- * Basically, most methods in this class map directly to one of - * the packet types described in draft-ietf-secsh-filexfer-02.txt. - *

- * Note: this is experimental code. - *

- * Error handling: the methods of this class throw IOExceptions. However, unless - * there is catastrophic failure, exceptions of the type {@link SFTPv3Client} will - * be thrown (a subclass of IOException). Therefore, you can implement more verbose - * behavior by checking if a thrown exception if of this type. If yes, then you - * can cast the exception and access detailed information about the failure. - *

- * Notes about file names, directory names and paths, copy-pasted - * from the specs: - *

    - *
  • SFTP v3 represents file names as strings. File names are - * assumed to use the slash ('/') character as a directory separator.
  • - *
  • File names starting with a slash are "absolute", and are relative to - * the root of the file system. Names starting with any other character - * are relative to the user's default directory (home directory).
  • - *
  • Servers SHOULD interpret a path name component ".." as referring to - * the parent directory, and "." as referring to the current directory. - * If the server implementation limits access to certain parts of the - * file system, it must be extra careful in parsing file names when - * enforcing such restrictions. There have been numerous reported - * security bugs where a ".." in a path name has allowed access outside - * the intended area.
  • - *
  • An empty path name is valid, and it refers to the user's default - * directory (usually the user's home directory).
  • - *
- *

- * If you are still not tired then please go on and read the comment for - * {@link #setCharset(String)}. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: SFTPv3Client.java,v 1.3 2008/04/01 12:38:09 cplattne Exp $ - */ -public class SFTPv3Client { - final Connection conn; - final Session sess; - final PrintStream debug; - - boolean flag_closed = false; - - InputStream is; - OutputStream os; - - int protocol_version = 0; - HashMap server_extensions = new HashMap(); - - int next_request_id = 1000; - - String charsetName = null; - - /** - * Create a SFTP v3 client. - * - * @param conn The underlying SSH-2 connection to be used. - * @param debug - * @throws IOException - * - * @deprecated this constructor (debug version) will disappear in the future, - * use {@link #SFTPv3Client(Connection)} instead. - */ - @Deprecated - public SFTPv3Client(Connection conn, PrintStream debug) throws IOException { - if (conn == null) - throw new IllegalArgumentException("Cannot accept null argument!"); - - this.conn = conn; - this.debug = debug; - - if (debug != null) - debug.println("Opening session and starting SFTP subsystem."); - - sess = conn.openSession(); - sess.startSubSystem("sftp"); - is = sess.getStdout(); - os = new BufferedOutputStream(sess.getStdin(), 2048); - - if ((is == null) || (os == null)) - throw new IOException("There is a problem with the streams of the underlying channel."); - - init(); - } - - /** - * Create a SFTP v3 client. - * - * @param conn The underlying SSH-2 connection to be used. - * @throws IOException - */ - public SFTPv3Client(Connection conn) throws IOException { - this(conn, null); - } - - /** - * Set the charset used to convert between Java Unicode Strings and byte encodings - * used by the server for paths and file names. Unfortunately, the SFTP v3 draft - * says NOTHING about such conversions (well, with the exception of error messages - * which have to be in UTF-8). Newer drafts specify to use UTF-8 for file names - * (if I remember correctly). However, a quick test using OpenSSH serving a EXT-3 - * filesystem has shown that UTF-8 seems to be a bad choice for SFTP v3 (tested with - * filenames containing german umlauts). "windows-1252" seems to work better for Europe. - * Luckily, "windows-1252" is the platform default in my case =). - *

- * If you don't set anything, then the platform default will be used (this is the default - * behavior). - * - * @see #getCharset() - * - * @param charset the name of the charset to be used or null to use the platform's - * default encoding. - * @throws IOException - */ - public void setCharset(String charset) throws IOException { - if (charset == null) { - charsetName = charset; - return; - } - - try { - Charset.forName(charset); - } - catch (Exception e) { - throw(IOException) new IOException("This charset is not supported").initCause(e); - } - - charsetName = charset; - } - - /** - * The currently used charset for filename encoding/decoding. - * - * @see #setCharset(String) - * - * @return The name of the charset (null if the platform's default charset is being used) - */ - public String getCharset() { - return charsetName; - } - - private final void checkHandleValidAndOpen(SFTPv3FileHandle handle) throws IOException { - if (handle.client != this) - throw new IOException("The file handle was created with another SFTPv3FileHandle instance."); - - if (handle.isClosed == true) - throw new IOException("The file handle is closed."); - } - - private final void sendMessage(int type, int requestId, byte[] msg, int off, int len) throws IOException { - int msglen = len + 1; - - if (type != Packet.SSH_FXP_INIT) - msglen += 4; - - os.write(msglen >> 24); - os.write(msglen >> 16); - os.write(msglen >> 8); - os.write(msglen); - os.write(type); - - if (type != Packet.SSH_FXP_INIT) { - os.write(requestId >> 24); - os.write(requestId >> 16); - os.write(requestId >> 8); - os.write(requestId); - } - - os.write(msg, off, len); - os.flush(); - } - - private final void sendMessage(int type, int requestId, byte[] msg) throws IOException { - sendMessage(type, requestId, msg, 0, msg.length); - } - - private final void readBytes(byte[] buff, int pos, int len) throws IOException { - while (len > 0) { - int count = is.read(buff, pos, len); - - if (count < 0) - throw new IOException("Unexpected end of sftp stream."); - - if ((count == 0) || (count > len)) - throw new IOException("Underlying stream implementation is bogus!"); - - len -= count; - pos += count; - } - } - - /** - * Read a message and guarantee that the contents is not larger than - * maxlen bytes. - *

- * Note: receiveMessage(34000) actually means that the message may be up to 34004 - * bytes (the length attribute preceeding the contents is 4 bytes). - * - * @param maxlen - * @return the message contents - * @throws IOException - */ - private final byte[] receiveMessage(int maxlen) throws IOException { - byte[] msglen = new byte[4]; - readBytes(msglen, 0, 4); - int len = (((msglen[0] & 0xff) << 24) | ((msglen[1] & 0xff) << 16) | ((msglen[2] & 0xff) << 8) | (msglen[3] & 0xff)); - - if ((len > maxlen) || (len <= 0)) - throw new IOException("Illegal sftp packet len: " + len); - - byte[] msg = new byte[len]; - readBytes(msg, 0, len); - return msg; - } - - private final int generateNextRequestID() { - synchronized (this) { - return next_request_id++; - } - } - - private final void closeHandle(byte[] handle) throws IOException { - int req_id = generateNextRequestID(); - TypesWriter tw = new TypesWriter(); - tw.writeString(handle, 0, handle.length); - sendMessage(Packet.SSH_FXP_CLOSE, req_id, tw.getBytes()); - expectStatusOKMessage(req_id); - } - - private SFTPv3FileAttributes readAttrs(TypesReader tr) throws IOException { - /* - * uint32 flags - * uint64 size present only if flag SSH_FILEXFER_ATTR_SIZE - * uint32 uid present only if flag SSH_FILEXFER_ATTR_V3_UIDGID - * uint32 gid present only if flag SSH_FILEXFER_ATTR_V3_UIDGID - * uint32 permissions present only if flag SSH_FILEXFER_ATTR_PERMISSIONS - * uint32 atime present only if flag SSH_FILEXFER_ATTR_V3_ACMODTIME - * uint32 mtime present only if flag SSH_FILEXFER_ATTR_V3_ACMODTIME - * uint32 extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED - * string extended_type - * string extended_data - * ... more extended data (extended_type - extended_data pairs), - * so that number of pairs equals extended_count - */ - SFTPv3FileAttributes fa = new SFTPv3FileAttributes(); - int flags = tr.readUINT32(); - - if ((flags & AttribFlags.SSH_FILEXFER_ATTR_SIZE) != 0) { - if (debug != null) - debug.println("SSH_FILEXFER_ATTR_SIZE"); - - fa.size = Long.valueOf(tr.readUINT64()); - } - - if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID) != 0) { - if (debug != null) - debug.println("SSH_FILEXFER_ATTR_V3_UIDGID"); - - fa.uid = Integer.valueOf(tr.readUINT32()); - fa.gid = Integer.valueOf(tr.readUINT32()); - } - - if ((flags & AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS) != 0) { - if (debug != null) - debug.println("SSH_FILEXFER_ATTR_PERMISSIONS"); - - fa.permissions = Integer.valueOf(tr.readUINT32()); - } - - if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME) != 0) { - if (debug != null) - debug.println("SSH_FILEXFER_ATTR_V3_ACMODTIME"); - - fa.atime = Long.valueOf((tr.readUINT32()) & 0xffffffffl); - fa.mtime = Long.valueOf((tr.readUINT32()) & 0xffffffffl); - } - - if ((flags & AttribFlags.SSH_FILEXFER_ATTR_EXTENDED) != 0) { - int count = tr.readUINT32(); - - if (debug != null) - debug.println("SSH_FILEXFER_ATTR_EXTENDED (" + count + ")"); - - /* Read it anyway to detect corrupt packets */ - - while (count > 0) { - tr.readByteString(); - tr.readByteString(); - count--; - } - } - - return fa; - } - - /** - * Retrieve the file attributes of an open file. - * - * @param handle a SFTPv3FileHandle handle. - * @return a SFTPv3FileAttributes object. - * @throws IOException - */ - public SFTPv3FileAttributes fstat(SFTPv3FileHandle handle) throws IOException { - checkHandleValidAndOpen(handle); - int req_id = generateNextRequestID(); - TypesWriter tw = new TypesWriter(); - tw.writeString(handle.fileHandle, 0, handle.fileHandle.length); - - if (debug != null) { - debug.println("Sending SSH_FXP_FSTAT..."); - debug.flush(); - } - - sendMessage(Packet.SSH_FXP_FSTAT, req_id, tw.getBytes()); - byte[] resp = receiveMessage(34000); - - if (debug != null) { - debug.println("Got REPLY."); - debug.flush(); - } - - TypesReader tr = new TypesReader(resp); - int t = tr.readByte(); - int rep_id = tr.readUINT32(); - - if (rep_id != req_id) - throw new IOException("The server sent an invalid id field."); - - if (t == Packet.SSH_FXP_ATTRS) { - return readAttrs(tr); - } - - if (t != Packet.SSH_FXP_STATUS) - throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); - - int errorCode = tr.readUINT32(); - throw new SFTPException(tr.readString(), errorCode); - } - - private SFTPv3FileAttributes statBoth(String path, int statMethod) throws IOException { - int req_id = generateNextRequestID(); - TypesWriter tw = new TypesWriter(); - tw.writeString(path, charsetName); - - if (debug != null) { - debug.println("Sending SSH_FXP_STAT/SSH_FXP_LSTAT..."); - debug.flush(); - } - - sendMessage(statMethod, req_id, tw.getBytes()); - byte[] resp = receiveMessage(34000); - - if (debug != null) { - debug.println("Got REPLY."); - debug.flush(); - } - - TypesReader tr = new TypesReader(resp); - int t = tr.readByte(); - int rep_id = tr.readUINT32(); - - if (rep_id != req_id) - throw new IOException("The server sent an invalid id field."); - - if (t == Packet.SSH_FXP_ATTRS) { - return readAttrs(tr); - } - - if (t != Packet.SSH_FXP_STATUS) - throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); - - int errorCode = tr.readUINT32(); - throw new SFTPException(tr.readString(), errorCode); - } - - /** - * Retrieve the file attributes of a file. This method - * follows symbolic links on the server. - * - * @see #lstat(String) - * - * @param path See the {@link SFTPv3Client comment} for the class for more details. - * @return a SFTPv3FileAttributes object. - * @throws IOException - */ - public SFTPv3FileAttributes stat(String path) throws IOException { - return statBoth(path, Packet.SSH_FXP_STAT); - } - - /** - * Retrieve the file attributes of a file. This method - * does NOT follow symbolic links on the server. - * - * @see #stat(String) - * - * @param path See the {@link SFTPv3Client comment} for the class for more details. - * @return a SFTPv3FileAttributes object. - * @throws IOException - */ - public SFTPv3FileAttributes lstat(String path) throws IOException { - return statBoth(path, Packet.SSH_FXP_LSTAT); - } - - /** - * Read the target of a symbolic link. - * - * @param path See the {@link SFTPv3Client comment} for the class for more details. - * @return The target of the link. - * @throws IOException - */ - public String readLink(String path) throws IOException { - int req_id = generateNextRequestID(); - TypesWriter tw = new TypesWriter(); - tw.writeString(path, charsetName); - - if (debug != null) { - debug.println("Sending SSH_FXP_READLINK..."); - debug.flush(); - } - - sendMessage(Packet.SSH_FXP_READLINK, req_id, tw.getBytes()); - byte[] resp = receiveMessage(34000); - - if (debug != null) { - debug.println("Got REPLY."); - debug.flush(); - } - - TypesReader tr = new TypesReader(resp); - int t = tr.readByte(); - int rep_id = tr.readUINT32(); - - if (rep_id != req_id) - throw new IOException("The server sent an invalid id field."); - - if (t == Packet.SSH_FXP_NAME) { - int count = tr.readUINT32(); - - if (count != 1) - throw new IOException("The server sent an invalid SSH_FXP_NAME packet."); - - return tr.readString(charsetName); - } - - if (t != Packet.SSH_FXP_STATUS) - throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); - - int errorCode = tr.readUINT32(); - throw new SFTPException(tr.readString(), errorCode); - } - - private void expectStatusOKMessage(int id) throws IOException { - byte[] resp = receiveMessage(34000); - - if (debug != null) { - debug.println("Got REPLY."); - debug.flush(); - } - - TypesReader tr = new TypesReader(resp); - int t = tr.readByte(); - int rep_id = tr.readUINT32(); - - if (rep_id != id) - throw new IOException("The server sent an invalid id field."); - - if (t != Packet.SSH_FXP_STATUS) - throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); - - int errorCode = tr.readUINT32(); - - if (errorCode == ErrorCodes.SSH_FX_OK) - return; - - throw new SFTPException(tr.readString(), errorCode); - } - - /** - * Modify the attributes of a file. Used for operations such as changing - * the ownership, permissions or access times, as well as for truncating a file. - * - * @param path See the {@link SFTPv3Client comment} for the class for more details. - * @param attr A SFTPv3FileAttributes object. Specifies the modifications to be - * made to the attributes of the file. Empty fields will be ignored. - * @throws IOException - */ - public void setstat(String path, SFTPv3FileAttributes attr) throws IOException { - int req_id = generateNextRequestID(); - TypesWriter tw = new TypesWriter(); - tw.writeString(path, charsetName); - tw.writeBytes(createAttrs(attr)); - - if (debug != null) { - debug.println("Sending SSH_FXP_SETSTAT..."); - debug.flush(); - } - - sendMessage(Packet.SSH_FXP_SETSTAT, req_id, tw.getBytes()); - expectStatusOKMessage(req_id); - } - - /** - * Modify the attributes of a file. Used for operations such as changing - * the ownership, permissions or access times, as well as for truncating a file. - * - * @param handle a SFTPv3FileHandle handle - * @param attr A SFTPv3FileAttributes object. Specifies the modifications to be - * made to the attributes of the file. Empty fields will be ignored. - * @throws IOException - */ - public void fsetstat(SFTPv3FileHandle handle, SFTPv3FileAttributes attr) throws IOException { - checkHandleValidAndOpen(handle); - int req_id = generateNextRequestID(); - TypesWriter tw = new TypesWriter(); - tw.writeString(handle.fileHandle, 0, handle.fileHandle.length); - tw.writeBytes(createAttrs(attr)); - - if (debug != null) { - debug.println("Sending SSH_FXP_FSETSTAT..."); - debug.flush(); - } - - sendMessage(Packet.SSH_FXP_FSETSTAT, req_id, tw.getBytes()); - expectStatusOKMessage(req_id); - } - - /** - * Create a symbolic link on the server. Creates a link "src" that points - * to "target". - * - * @param src See the {@link SFTPv3Client comment} for the class for more details. - * @param target See the {@link SFTPv3Client comment} for the class for more details. - * @throws IOException - */ - public void createSymlink(String src, String target) throws IOException { - int req_id = generateNextRequestID(); - /* Either I am too stupid to understand the SFTP draft - * or the OpenSSH guys changed the semantics of src and target. - */ - TypesWriter tw = new TypesWriter(); - tw.writeString(target, charsetName); - tw.writeString(src, charsetName); - - if (debug != null) { - debug.println("Sending SSH_FXP_SYMLINK..."); - debug.flush(); - } - - sendMessage(Packet.SSH_FXP_SYMLINK, req_id, tw.getBytes()); - expectStatusOKMessage(req_id); - } - - /** - * Have the server canonicalize any given path name to an absolute path. - * This is useful for converting path names containing ".." components or - * relative pathnames without a leading slash into absolute paths. - * - * @param path See the {@link SFTPv3Client comment} for the class for more details. - * @return An absolute path. - * @throws IOException - */ - public String canonicalPath(String path) throws IOException { - int req_id = generateNextRequestID(); - TypesWriter tw = new TypesWriter(); - tw.writeString(path, charsetName); - - if (debug != null) { - debug.println("Sending SSH_FXP_REALPATH..."); - debug.flush(); - } - - sendMessage(Packet.SSH_FXP_REALPATH, req_id, tw.getBytes()); - byte[] resp = receiveMessage(34000); - - if (debug != null) { - debug.println("Got REPLY."); - debug.flush(); - } - - TypesReader tr = new TypesReader(resp); - int t = tr.readByte(); - int rep_id = tr.readUINT32(); - - if (rep_id != req_id) - throw new IOException("The server sent an invalid id field."); - - if (t == Packet.SSH_FXP_NAME) { - int count = tr.readUINT32(); - - if (count != 1) - throw new IOException("The server sent an invalid SSH_FXP_NAME packet."); - - return tr.readString(charsetName); - } - - if (t != Packet.SSH_FXP_STATUS) - throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); - - int errorCode = tr.readUINT32(); - throw new SFTPException(tr.readString(), errorCode); - } - - private final Vector scanDirectory(byte[] handle) throws IOException { - Vector files = new Vector(); - - while (true) { - int req_id = generateNextRequestID(); - TypesWriter tw = new TypesWriter(); - tw.writeString(handle, 0, handle.length); - - if (debug != null) { - debug.println("Sending SSH_FXP_READDIR..."); - debug.flush(); - } - - sendMessage(Packet.SSH_FXP_READDIR, req_id, tw.getBytes()); - /* Some servers send here a packet with size > 34000 */ - /* To whom it may concern: please learn to read the specs. */ - byte[] resp = receiveMessage(65536); - - if (debug != null) { - debug.println("Got REPLY."); - debug.flush(); - } - - TypesReader tr = new TypesReader(resp); - int t = tr.readByte(); - int rep_id = tr.readUINT32(); - - if (rep_id != req_id) - throw new IOException("The server sent an invalid id field."); - - if (t == Packet.SSH_FXP_NAME) { - int count = tr.readUINT32(); - - if (debug != null) - debug.println("Parsing " + count + " name entries..."); - - while (count > 0) { - SFTPv3DirectoryEntry dirEnt = new SFTPv3DirectoryEntry(); - dirEnt.filename = tr.readString(charsetName); - dirEnt.longEntry = tr.readString(charsetName); - dirEnt.attributes = readAttrs(tr); - files.addElement(dirEnt); - - if (debug != null) - debug.println("File: '" + dirEnt.filename + "'"); - - count--; - } - - continue; - } - - if (t != Packet.SSH_FXP_STATUS) - throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); - - int errorCode = tr.readUINT32(); - - if (errorCode == ErrorCodes.SSH_FX_EOF) - return files; - - throw new SFTPException(tr.readString(), errorCode); - } - } - - private final byte[] openDirectory(String path) throws IOException { - int req_id = generateNextRequestID(); - TypesWriter tw = new TypesWriter(); - tw.writeString(path, charsetName); - - if (debug != null) { - debug.println("Sending SSH_FXP_OPENDIR..."); - debug.flush(); - } - - sendMessage(Packet.SSH_FXP_OPENDIR, req_id, tw.getBytes()); - byte[] resp = receiveMessage(34000); - TypesReader tr = new TypesReader(resp); - int t = tr.readByte(); - int rep_id = tr.readUINT32(); - - if (rep_id != req_id) - throw new IOException("The server sent an invalid id field."); - - if (t == Packet.SSH_FXP_HANDLE) { - if (debug != null) { - debug.println("Got SSH_FXP_HANDLE."); - debug.flush(); - } - - byte[] handle = tr.readByteString(); - return handle; - } - - if (t != Packet.SSH_FXP_STATUS) - throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); - - int errorCode = tr.readUINT32(); - String errorMessage = tr.readString(); - throw new SFTPException(errorMessage, errorCode); - } - - private final String expandString(byte[] b, int off, int len) { - StringBuffer sb = new StringBuffer(); - - for (int i = 0; i < len; i++) { - int c = b[off + i] & 0xff; - - if ((c >= 32) && (c <= 126)) { - sb.append((char) c); - } - else { - sb.append("{0x" + Integer.toHexString(c) + "}"); - } - } - - return sb.toString(); - } - - private void init() throws IOException { - /* Send SSH_FXP_INIT (version 3) */ - final int client_version = 3; - - if (debug != null) - debug.println("Sending SSH_FXP_INIT (" + client_version + ")..."); - - TypesWriter tw = new TypesWriter(); - tw.writeUINT32(client_version); - sendMessage(Packet.SSH_FXP_INIT, 0, tw.getBytes()); - - /* Receive SSH_FXP_VERSION */ - - if (debug != null) - debug.println("Waiting for SSH_FXP_VERSION..."); - - TypesReader tr = new TypesReader(receiveMessage(34000)); /* Should be enough for any reasonable server */ - int type = tr.readByte(); - - if (type != Packet.SSH_FXP_VERSION) { - throw new IOException("The server did not send a SSH_FXP_VERSION packet (got " + type + ")"); - } - - protocol_version = tr.readUINT32(); - - if (debug != null) - debug.println("SSH_FXP_VERSION: protocol_version = " + protocol_version); - - if (protocol_version != 3) - throw new IOException("Server version " + protocol_version + " is currently not supported"); - - /* Read and save extensions (if any) for later use */ - - while (tr.remain() != 0) { - String name = tr.readString(); - byte[] value = tr.readByteString(); - server_extensions.put(name, value); - - if (debug != null) - debug.println("SSH_FXP_VERSION: extension: " + name + " = '" + expandString(value, 0, value.length) - + "'"); - } - } - - /** - * Returns the negotiated SFTP protocol version between the client and the server. - * - * @return SFTP protocol version, i.e., "3". - * - */ - public int getProtocolVersion() { - return protocol_version; - } - - /** - * Close this SFTP session. NEVER forget to call this method to free up - * resources - even if you got an exception from one of the other methods. - * Sometimes these other methods may throw an exception, saying that the - * underlying channel is closed (this can happen, e.g., if the other server - * sent a close message.) However, as long as you have not called the - * close() method, you are likely wasting resources. - * - */ - public void close() { - sess.close(); - } - - /** - * List the contents of a directory. - * - * @param dirName See the {@link SFTPv3Client comment} for the class for more details. - * @return A Vector containing {@link SFTPv3DirectoryEntry} objects. - * @throws IOException - */ - public Vector ls(String dirName) throws IOException { - byte[] handle = openDirectory(dirName); - Vector result = scanDirectory(handle); - closeHandle(handle); - return result; - } - - /** - * Create a new directory. - * - * @param dirName See the {@link SFTPv3Client comment} for the class for more details. - * @param posixPermissions the permissions for this directory, e.g., "0700" (remember that - * this is octal noation). The server will likely apply a umask. - * - * @throws IOException - */ - public void mkdir(String dirName, int posixPermissions) throws IOException { - int req_id = generateNextRequestID(); - TypesWriter tw = new TypesWriter(); - tw.writeString(dirName, charsetName); - tw.writeUINT32(AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS); - tw.writeUINT32(posixPermissions); - sendMessage(Packet.SSH_FXP_MKDIR, req_id, tw.getBytes()); - expectStatusOKMessage(req_id); - } - - /** - * Remove a file. - * - * @param fileName See the {@link SFTPv3Client comment} for the class for more details. - * @throws IOException - */ - public void rm(String fileName) throws IOException { - int req_id = generateNextRequestID(); - TypesWriter tw = new TypesWriter(); - tw.writeString(fileName, charsetName); - sendMessage(Packet.SSH_FXP_REMOVE, req_id, tw.getBytes()); - expectStatusOKMessage(req_id); - } - - /** - * Remove an empty directory. - * - * @param dirName See the {@link SFTPv3Client comment} for the class for more details. - * @throws IOException - */ - public void rmdir(String dirName) throws IOException { - int req_id = generateNextRequestID(); - TypesWriter tw = new TypesWriter(); - tw.writeString(dirName, charsetName); - sendMessage(Packet.SSH_FXP_RMDIR, req_id, tw.getBytes()); - expectStatusOKMessage(req_id); - } - - /** - * Move a file or directory. - * - * @param oldPath See the {@link SFTPv3Client comment} for the class for more details. - * @param newPath See the {@link SFTPv3Client comment} for the class for more details. - * @throws IOException - */ - public void mv(String oldPath, String newPath) throws IOException { - int req_id = generateNextRequestID(); - TypesWriter tw = new TypesWriter(); - tw.writeString(oldPath, charsetName); - tw.writeString(newPath, charsetName); - sendMessage(Packet.SSH_FXP_RENAME, req_id, tw.getBytes()); - expectStatusOKMessage(req_id); - } - - /** - * Open a file for reading. - * - * @param fileName See the {@link SFTPv3Client comment} for the class for more details. - * @return a SFTPv3FileHandle handle - * @throws IOException - */ - public SFTPv3FileHandle openFileRO(String fileName) throws IOException { - return openFile(fileName, 0x00000001, null); // SSH_FXF_READ - } - - /** - * Open a file for reading and writing. - * - * @param fileName See the {@link SFTPv3Client comment} for the class for more details. - * @return a SFTPv3FileHandle handle - * @throws IOException - */ - public SFTPv3FileHandle openFileRW(String fileName) throws IOException { - return openFile(fileName, 0x00000003, null); // SSH_FXF_READ | SSH_FXF_WRITE - } - - // Append is broken (already in the specification, because there is no way to - // send a write operation (what offset to use??)) - // public SFTPv3FileHandle openFileRWAppend(String fileName) throws IOException - // { - // return openFile(fileName, 0x00000007, null); // SSH_FXF_READ | SSH_FXF_WRITE | SSH_FXF_APPEND - // } - - /** - * Create a file and open it for reading and writing. - * Same as {@link #createFile(String, SFTPv3FileAttributes) createFile(fileName, null)}. - * - * @param fileName See the {@link SFTPv3Client comment} for the class for more details. - * @return a SFTPv3FileHandle handle - * @throws IOException - */ - public SFTPv3FileHandle createFile(String fileName) throws IOException { - return createFile(fileName, null); - } - - /** - * Create a file and open it for reading and writing. - * You can specify the default attributes of the file (the server may or may - * not respect your wishes). - * - * @param fileName See the {@link SFTPv3Client comment} for the class for more details. - * @param attr may be null to use server defaults. Probably only - * the uid, gid and permissions - * (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle} - * structure make sense. You need only to set those fields where you want - * to override the server's defaults. - * @return a SFTPv3FileHandle handle - * @throws IOException - */ - public SFTPv3FileHandle createFile(String fileName, SFTPv3FileAttributes attr) throws IOException { - return openFile(fileName, 0x00000008 | 0x00000003, attr); // SSH_FXF_CREAT | SSH_FXF_READ | SSH_FXF_WRITE - } - - /** - * Create a file (truncate it if it already exists) and open it for reading and writing. - * Same as {@link #createFileTruncate(String, SFTPv3FileAttributes) createFileTruncate(fileName, null)}. - * - * @param fileName See the {@link SFTPv3Client comment} for the class for more details. - * @return a SFTPv3FileHandle handle - * @throws IOException - */ - public SFTPv3FileHandle createFileTruncate(String fileName) throws IOException { - return createFileTruncate(fileName, null); - } - - /** - * reate a file (truncate it if it already exists) and open it for reading and writing. - * You can specify the default attributes of the file (the server may or may - * not respect your wishes). - * - * @param fileName See the {@link SFTPv3Client comment} for the class for more details. - * @param attr may be null to use server defaults. Probably only - * the uid, gid and permissions - * (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle} - * structure make sense. You need only to set those fields where you want - * to override the server's defaults. - * @return a SFTPv3FileHandle handle - * @throws IOException - */ - public SFTPv3FileHandle createFileTruncate(String fileName, SFTPv3FileAttributes attr) throws IOException { - return openFile(fileName, 0x00000018 | 0x00000003, attr); // SSH_FXF_CREAT | SSH_FXF_TRUNC | SSH_FXF_READ | SSH_FXF_WRITE - } - - private byte[] createAttrs(SFTPv3FileAttributes attr) { - TypesWriter tw = new TypesWriter(); - int attrFlags = 0; - - if (attr == null) { - tw.writeUINT32(0); - } - else { - if (attr.size != null) - attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_SIZE; - - if ((attr.uid != null) && (attr.gid != null)) - attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID; - - if (attr.permissions != null) - attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS; - - if ((attr.atime != null) && (attr.mtime != null)) - attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME; - - tw.writeUINT32(attrFlags); - - if (attr.size != null) - tw.writeUINT64(attr.size.longValue()); - - if ((attr.uid != null) && (attr.gid != null)) { - tw.writeUINT32(attr.uid.intValue()); - tw.writeUINT32(attr.gid.intValue()); - } - - if (attr.permissions != null) - tw.writeUINT32(attr.permissions.intValue()); - - if ((attr.atime != null) && (attr.mtime != null)) { - tw.writeUINT32(attr.atime.intValue()); - tw.writeUINT32(attr.mtime.intValue()); - } - } - - return tw.getBytes(); - } - - private SFTPv3FileHandle openFile(String fileName, int flags, SFTPv3FileAttributes attr) throws IOException { - int req_id = generateNextRequestID(); - TypesWriter tw = new TypesWriter(); - tw.writeString(fileName, charsetName); - tw.writeUINT32(flags); - tw.writeBytes(createAttrs(attr)); - - if (debug != null) { - debug.println("Sending SSH_FXP_OPEN..."); - debug.flush(); - } - - sendMessage(Packet.SSH_FXP_OPEN, req_id, tw.getBytes()); - byte[] resp = receiveMessage(34000); - TypesReader tr = new TypesReader(resp); - int t = tr.readByte(); - int rep_id = tr.readUINT32(); - - if (rep_id != req_id) - throw new IOException("The server sent an invalid id field."); - - if (t == Packet.SSH_FXP_HANDLE) { - if (debug != null) { - debug.println("Got SSH_FXP_HANDLE."); - debug.flush(); - } - - return new SFTPv3FileHandle(this, tr.readByteString()); - } - - if (t != Packet.SSH_FXP_STATUS) - throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); - - int errorCode = tr.readUINT32(); - String errorMessage = tr.readString(); - throw new SFTPException(errorMessage, errorCode); - } - - /** - * Read bytes from a file. No more than 32768 bytes may be read at once. - * Be aware that the semantics of read() are different than for Java streams. - *

- *

    - *
  • The server will read as many bytes as it can from the file (up to len), - * and return them.
  • - *
  • If EOF is encountered before reading any data, -1 is returned. - *
  • If an error occurs, an exception is thrown
  • . - *
  • For normal disk files, it is guaranteed that the server will return the specified - * number of bytes, or up to end of file. For, e.g., device files this may return - * fewer bytes than requested.
  • - *
- * - * @param handle a SFTPv3FileHandle handle - * @param fileOffset offset (in bytes) in the file - * @param dst the destination byte array - * @param dstoff offset in the destination byte array - * @param len how many bytes to read, 0 < len <= 32768 bytes - * @return the number of bytes that could be read, may be less than requested if - * the end of the file is reached, -1 is returned in case of EOF - * @throws IOException - */ - public int read(SFTPv3FileHandle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException { - checkHandleValidAndOpen(handle); - - if ((len > 32768) || (len <= 0)) - throw new IllegalArgumentException("invalid len argument"); - - int req_id = generateNextRequestID(); - TypesWriter tw = new TypesWriter(); - tw.writeString(handle.fileHandle, 0, handle.fileHandle.length); - tw.writeUINT64(fileOffset); - tw.writeUINT32(len); - - if (debug != null) { - debug.println("Sending SSH_FXP_READ..."); - debug.flush(); - } - - sendMessage(Packet.SSH_FXP_READ, req_id, tw.getBytes()); - byte[] resp = receiveMessage(34000); - TypesReader tr = new TypesReader(resp); - int t = tr.readByte(); - int rep_id = tr.readUINT32(); - - if (rep_id != req_id) - throw new IOException("The server sent an invalid id field."); - - if (t == Packet.SSH_FXP_DATA) { - if (debug != null) { - debug.println("Got SSH_FXP_DATA..."); - debug.flush(); - } - - int readLen = tr.readUINT32(); - - if ((readLen < 0) || (readLen > len)) - throw new IOException("The server sent an invalid length field."); - - tr.readBytes(dst, dstoff, readLen); - return readLen; - } - - if (t != Packet.SSH_FXP_STATUS) - throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); - - int errorCode = tr.readUINT32(); - - if (errorCode == ErrorCodes.SSH_FX_EOF) { - if (debug != null) { - debug.println("Got SSH_FX_EOF."); - debug.flush(); - } - - return -1; - } - - String errorMessage = tr.readString(); - throw new SFTPException(errorMessage, errorCode); - } - - /** - * Write bytes to a file. If len > 32768, then the write operation will - * be split into multiple writes. - * - * @param handle a SFTPv3FileHandle handle. - * @param fileOffset offset (in bytes) in the file. - * @param src the source byte array. - * @param srcoff offset in the source byte array. - * @param len how many bytes to write. - * @throws IOException - */ - public void write(SFTPv3FileHandle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException { - checkHandleValidAndOpen(handle); - - while (len > 0) { - int writeRequestLen = len; - - if (writeRequestLen > 32768) - writeRequestLen = 32768; - - int req_id = generateNextRequestID(); - TypesWriter tw = new TypesWriter(); - tw.writeString(handle.fileHandle, 0, handle.fileHandle.length); - tw.writeUINT64(fileOffset); - tw.writeString(src, srcoff, writeRequestLen); - - if (debug != null) { - debug.println("Sending SSH_FXP_WRITE..."); - debug.flush(); - } - - sendMessage(Packet.SSH_FXP_WRITE, req_id, tw.getBytes()); - fileOffset += writeRequestLen; - srcoff += writeRequestLen; - len -= writeRequestLen; - byte[] resp = receiveMessage(34000); - TypesReader tr = new TypesReader(resp); - int t = tr.readByte(); - int rep_id = tr.readUINT32(); - - if (rep_id != req_id) - throw new IOException("The server sent an invalid id field."); - - if (t != Packet.SSH_FXP_STATUS) - throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); - - int errorCode = tr.readUINT32(); - - if (errorCode == ErrorCodes.SSH_FX_OK) - continue; - - String errorMessage = tr.readString(); - throw new SFTPException(errorMessage, errorCode); - } - } - - /** - * Close a file. - * - * @param handle a SFTPv3FileHandle handle - * @throws IOException - */ - public void closeFile(SFTPv3FileHandle handle) throws IOException { - if (handle == null) - throw new IllegalArgumentException("the handle argument may not be null"); - - try { - if (handle.isClosed == false) { - closeHandle(handle.fileHandle); - } - } - finally { - handle.isClosed = true; - } - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/SFTPv3DirectoryEntry.java --- a/src/com/trilead/ssh2/SFTPv3DirectoryEntry.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,37 +0,0 @@ - -package com.trilead.ssh2; - -/** - * A SFTPv3DirectoryEntry as returned by {@link SFTPv3Client#ls(String)}. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: SFTPv3DirectoryEntry.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ - -public class SFTPv3DirectoryEntry { - /** - * A relative name within the directory, without any path components. - */ - public String filename; - - /** - * An expanded format for the file name, similar to what is returned by - * "ls -l" on Un*x systems. - *

- * The format of this field is unspecified by the SFTP v3 protocol. - * It MUST be suitable for use in the output of a directory listing - * command (in fact, the recommended operation for a directory listing - * command is to simply display this data). However, clients SHOULD NOT - * attempt to parse the longname field for file attributes; they SHOULD - * use the attrs field instead. - *

- * The recommended format for the longname field is as follows:
- * -rwxr-xr-x 1 mjos staff 348911 Mar 25 14:29 t-filexfer - */ - public String longEntry; - - /** - * The attributes of this entry. - */ - public SFTPv3FileAttributes attributes; -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/SFTPv3FileAttributes.java --- a/src/com/trilead/ssh2/SFTPv3FileAttributes.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,136 +0,0 @@ - -package com.trilead.ssh2; - -/** - * A SFTPv3FileAttributes object represents detail information - * about a file on the server. Not all fields may/must be present. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: SFTPv3FileAttributes.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ - */ - -public class SFTPv3FileAttributes { - /** - * The SIZE attribute. NULL if not present. - */ - public Long size = null; - - /** - * The UID attribute. NULL if not present. - */ - public Integer uid = null; - - /** - * The GID attribute. NULL if not present. - */ - public Integer gid = null; - - /** - * The POSIX permissions. NULL if not present. - *

- * Here is a list: - *

- *

Note: these numbers are all OCTAL.
-     *
-     *  S_IFMT     0170000   bitmask for the file type bitfields
-     *  S_IFSOCK   0140000   socket
-     *  S_IFLNK    0120000   symbolic link
-     *  S_IFREG    0100000   regular file
-     *  S_IFBLK    0060000   block device
-     *  S_IFDIR    0040000   directory
-     *  S_IFCHR    0020000   character device
-     *  S_IFIFO    0010000   fifo
-     *  S_ISUID    0004000   set UID bit
-     *  S_ISGID    0002000   set GID bit
-     *  S_ISVTX    0001000   sticky bit
-     *
-     *  S_IRWXU    00700     mask for file owner permissions
-     *  S_IRUSR    00400     owner has read permission
-     *  S_IWUSR    00200     owner has write permission
-     *  S_IXUSR    00100     owner has execute permission
-     *  S_IRWXG    00070     mask for group permissions
-     *  S_IRGRP    00040     group has read permission
-     *  S_IWGRP    00020     group has write permission
-     *  S_IXGRP    00010     group has execute permission
-     *  S_IRWXO    00007     mask for permissions for others (not in group)
-     *  S_IROTH    00004     others have read permission
-     *  S_IWOTH    00002     others have write permisson
-     *  S_IXOTH    00001     others have execute permission
-     * 
- */ - public Integer permissions = null; - - /** - * The ATIME attribute. Represented as seconds from Jan 1, 1970 in UTC. - * NULL if not present. - */ - public Long atime = null; - - /** - * The MTIME attribute. Represented as seconds from Jan 1, 1970 in UTC. - * NULL if not present. - */ - public Long mtime = null; - - /** - * Checks if this entry is a directory. - * - * @return Returns true if permissions are available and they indicate - * that this entry represents a directory. - */ - public boolean isDirectory() { - if (permissions == null) - return false; - - return ((permissions.intValue() & 0040000) != 0); - } - - /** - * Checks if this entry is a regular file. - * - * @return Returns true if permissions are available and they indicate - * that this entry represents a regular file. - */ - public boolean isRegularFile() { - if (permissions == null) - return false; - - return ((permissions.intValue() & 0100000) != 0); - } - - /** - * Checks if this entry is a a symlink. - * - * @return Returns true if permissions are available and they indicate - * that this entry represents a symlink. - */ - public boolean isSymlink() { - if (permissions == null) - return false; - - return ((permissions.intValue() & 0120000) != 0); - } - - /** - * Turn the POSIX permissions into a 7 digit octal representation. - * Note: the returned value is first masked with 0177777. - * - * @return NULL if permissions are not available. - */ - public String getOctalPermissions() { - if (permissions == null) - return null; - - String res = Integer.toString(permissions.intValue() & 0177777, 8); - StringBuffer sb = new StringBuffer(); - int leadingZeros = 7 - res.length(); - - while (leadingZeros > 0) { - sb.append('0'); - leadingZeros--; - } - - sb.append(res); - return sb.toString(); - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/SFTPv3FileHandle.java --- a/src/com/trilead/ssh2/SFTPv3FileHandle.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ - -package com.trilead.ssh2; - -/** - * A SFTPv3FileHandle. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: SFTPv3FileHandle.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ - -public class SFTPv3FileHandle { - final SFTPv3Client client; - final byte[] fileHandle; - boolean isClosed = false; - - /* The constructor is NOT public */ - - SFTPv3FileHandle(SFTPv3Client client, byte[] h) { - this.client = client; - this.fileHandle = h; - } - - /** - * Get the SFTPv3Client instance which created this handle. - * - * @return A SFTPv3Client instance. - */ - public SFTPv3Client getClient() { - return client; - } - - /** - * Check if this handle was closed with the {@link SFTPv3Client#closeFile(SFTPv3FileHandle)} method - * of the SFTPv3Client instance which created the handle. - * - * @return if the handle is closed. - */ - public boolean isClosed() { - return isClosed; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/ServerHostKeyVerifier.java --- a/src/com/trilead/ssh2/ServerHostKeyVerifier.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ - -package com.trilead.ssh2; - -/** - * A callback interface used to implement a client specific method of checking - * server host keys. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: ServerHostKeyVerifier.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ - -public interface ServerHostKeyVerifier { - /** - * The actual verifier method, it will be called by the key exchange code - * on EVERY key exchange - this can happen several times during the lifetime - * of a connection. - *

- * Note: SSH-2 servers are allowed to change their hostkey at ANY time. - * - * @param hostname the hostname used to create the {@link Connection} object - * @param port the remote TCP port - * @param serverHostKeyAlgorithm the public key algorithm (ssh-rsa or ssh-dss) - * @param serverHostKey the server's public key blob - * @return if the client wants to accept the server's host key - if not, the - * connection will be closed. - * @throws Exception Will be wrapped with an IOException, extended version of returning false =) - */ - public boolean verifyServerHostKey(String hostname, int port, String serverHostKeyAlgorithm, byte[] serverHostKey) - throws Exception; -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/Session.java --- a/src/com/trilead/ssh2/Session.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,491 +0,0 @@ - -package com.trilead.ssh2; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.security.SecureRandom; - -import com.trilead.ssh2.channel.Channel; -import com.trilead.ssh2.channel.ChannelManager; -import com.trilead.ssh2.channel.X11ServerData; - - -/** - * A Session is a remote execution of a program. "Program" means - * in this context either a shell, an application or a system command. The - * program may or may not have a tty. Only one single program can be started on - * a session. However, multiple sessions can be active simultaneously. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: Session.java,v 1.2 2008/03/03 07:01:36 cplattne Exp $ - */ -public class Session { - ChannelManager cm; - Channel cn; - - boolean flag_pty_requested = false; - boolean flag_x11_requested = false; - boolean flag_execution_started = false; - boolean flag_closed = false; - - String x11FakeCookie = null; - - final SecureRandom rnd; - - Session(ChannelManager cm, SecureRandom rnd) throws IOException { - this.cm = cm; - this.cn = cm.openSessionChannel(); - this.rnd = rnd; - } - - /** - * Basically just a wrapper for lazy people - identical to calling - * requestPTY("dumb", 0, 0, 0, 0, null). - * - * @throws IOException - */ - public void requestDumbPTY() throws IOException { - requestPTY("dumb", 0, 0, 0, 0, null); - } - - /** - * Basically just another wrapper for lazy people - identical to calling - * requestPTY(term, 0, 0, 0, 0, null). - * - * @throws IOException - */ - public void requestPTY(String term) throws IOException { - requestPTY(term, 0, 0, 0, 0, null); - } - - /** - * Allocate a pseudo-terminal for this session. - *

- * This method may only be called before a program or shell is started in - * this session. - *

- * Different aspects can be specified: - *

- *

    - *
  • The TERM environment variable value (e.g., vt100)
  • - *
  • The terminal's dimensions.
  • - *
  • The encoded terminal modes.
  • - *
- * Zero dimension parameters are ignored. The character/row dimensions - * override the pixel dimensions (when nonzero). Pixel dimensions refer to - * the drawable area of the window. The dimension parameters are only - * informational. The encoding of terminal modes (parameter - * terminal_modes) is described in RFC4254. - * - * @param term - * The TERM environment variable value (e.g., vt100) - * @param term_width_characters - * terminal width, characters (e.g., 80) - * @param term_height_characters - * terminal height, rows (e.g., 24) - * @param term_width_pixels - * terminal width, pixels (e.g., 640) - * @param term_height_pixels - * terminal height, pixels (e.g., 480) - * @param terminal_modes - * encoded terminal modes (may be null) - * @throws IOException - */ - public void requestPTY(String term, int term_width_characters, int term_height_characters, int term_width_pixels, - int term_height_pixels, byte[] terminal_modes) throws IOException { - if (term == null) - throw new IllegalArgumentException("TERM cannot be null."); - - if ((terminal_modes != null) && (terminal_modes.length > 0)) { - if (terminal_modes[terminal_modes.length - 1] != 0) - throw new IOException("Illegal terminal modes description, does not end in zero byte"); - } - else - terminal_modes = new byte[] { 0 }; - - synchronized (this) { - /* The following is just a nicer error, we would catch it anyway later in the channel code */ - if (flag_closed) - throw new IOException("This session is closed."); - - if (flag_pty_requested) - throw new IOException("A PTY was already requested."); - - if (flag_execution_started) - throw new IOException( - "Cannot request PTY at this stage anymore, a remote execution has already started."); - - flag_pty_requested = true; - } - - cm.requestPTY(cn, term, term_width_characters, term_height_characters, term_width_pixels, term_height_pixels, - terminal_modes); - } - - /** - * Inform other side of connection that our PTY has resized. - *

- * Zero dimension parameters are ignored. The character/row dimensions - * override the pixel dimensions (when nonzero). Pixel dimensions refer to - * the drawable area of the window. The dimension parameters are only - * informational. - * - * @param term_width_characters - * terminal width, characters (e.g., 80) - * @param term_height_characters - * terminal height, rows (e.g., 24) - * @param term_width_pixels - * terminal width, pixels (e.g., 640) - * @param term_height_pixels - * terminal height, pixels (e.g., 480) - * @throws IOException - */ - public void resizePTY(int term_width_characters, int term_height_characters, int term_width_pixels, - int term_height_pixels) throws IOException { - synchronized (this) { - /* The following is just a nicer error, we would catch it anyway later in the channel code */ - if (flag_closed) - throw new IOException("This session is closed."); - } - - cm.resizePTY(cn, term_width_characters, term_height_characters, term_width_pixels, term_height_pixels); - } - - /** - * Request X11 forwarding for the current session. - *

- * You have to supply the name and port of your X-server. - *

- * This method may only be called before a program or shell is started in - * this session. - * - * @param hostname the hostname of the real (target) X11 server (e.g., 127.0.0.1) - * @param port the port of the real (target) X11 server (e.g., 6010) - * @param cookie if non-null, then present this cookie to the real X11 server - * @param singleConnection if true, then the server is instructed to only forward one single - * connection, no more connections shall be forwarded after first, or after the session - * channel has been closed - * @throws IOException - */ - public void requestX11Forwarding(String hostname, int port, byte[] cookie, boolean singleConnection) - throws IOException { - if (hostname == null) - throw new IllegalArgumentException("hostname argument may not be null"); - - synchronized (this) { - /* The following is just a nicer error, we would catch it anyway later in the channel code */ - if (flag_closed) - throw new IOException("This session is closed."); - - if (flag_x11_requested) - throw new IOException("X11 forwarding was already requested."); - - if (flag_execution_started) - throw new IOException( - "Cannot request X11 forwarding at this stage anymore, a remote execution has already started."); - - flag_x11_requested = true; - } - - /* X11ServerData - used to store data about the target X11 server */ - X11ServerData x11data = new X11ServerData(); - x11data.hostname = hostname; - x11data.port = port; - x11data.x11_magic_cookie = cookie; /* if non-null, then present this cookie to the real X11 server */ - /* Generate fake cookie - this one is used between remote clients and our proxy */ - byte[] fakeCookie = new byte[16]; - String hexEncodedFakeCookie; - - /* Make sure that this fake cookie is unique for this connection */ - - while (true) { - rnd.nextBytes(fakeCookie); - /* Generate also hex representation of fake cookie */ - StringBuffer tmp = new StringBuffer(32); - - for (int i = 0; i < fakeCookie.length; i++) { - String digit2 = Integer.toHexString(fakeCookie[i] & 0xff); - tmp.append((digit2.length() == 2) ? digit2 : "0" + digit2); - } - - hexEncodedFakeCookie = tmp.toString(); - - /* Well, yes, chances are low, but we want to be on the safe side */ - - if (cm.checkX11Cookie(hexEncodedFakeCookie) == null) - break; - } - - /* Ask for X11 forwarding */ - cm.requestX11(cn, singleConnection, "MIT-MAGIC-COOKIE-1", hexEncodedFakeCookie, 0); - - /* OK, that went fine, get ready to accept X11 connections... */ - /* ... but only if the user has not called close() in the meantime =) */ - - synchronized (this) { - if (flag_closed == false) { - this.x11FakeCookie = hexEncodedFakeCookie; - cm.registerX11Cookie(hexEncodedFakeCookie, x11data); - } - } - - /* Now it is safe to start remote X11 programs */ - } - - /** - * Execute a command on the remote machine. - * - * @param cmd - * The command to execute on the remote host. - * @throws IOException - */ - public void execCommand(String cmd) throws IOException { - if (cmd == null) - throw new IllegalArgumentException("cmd argument may not be null"); - - synchronized (this) { - /* The following is just a nicer error, we would catch it anyway later in the channel code */ - if (flag_closed) - throw new IOException("This session is closed."); - - if (flag_execution_started) - throw new IOException("A remote execution has already started."); - - flag_execution_started = true; - } - - cm.requestExecCommand(cn, cmd); - } - - /** - * Start a shell on the remote machine. - * - * @throws IOException - */ - public void startShell() throws IOException { - synchronized (this) { - /* The following is just a nicer error, we would catch it anyway later in the channel code */ - if (flag_closed) - throw new IOException("This session is closed."); - - if (flag_execution_started) - throw new IOException("A remote execution has already started."); - - flag_execution_started = true; - } - - cm.requestShell(cn); - } - - /** - * Start a subsystem on the remote machine. - * Unless you know what you are doing, you will never need this. - * - * @param name the name of the subsystem. - * @throws IOException - */ - public void startSubSystem(String name) throws IOException { - if (name == null) - throw new IllegalArgumentException("name argument may not be null"); - - synchronized (this) { - /* The following is just a nicer error, we would catch it anyway later in the channel code */ - if (flag_closed) - throw new IOException("This session is closed."); - - if (flag_execution_started) - throw new IOException("A remote execution has already started."); - - flag_execution_started = true; - } - - cm.requestSubSystem(cn, name); - } - - /** - * This method can be used to perform end-to-end session (i.e., SSH channel) - * testing. It sends a 'ping' message to the server and waits for the 'pong' - * from the server. - *

- * Implementation details: this method sends a SSH_MSG_CHANNEL_REQUEST request - * ('trilead-ping') to the server and waits for the SSH_MSG_CHANNEL_FAILURE reply - * packet. - * - * @throws IOException in case of any problem or when the session is closed - */ - public void ping() throws IOException { - synchronized (this) { - /* - * The following is just a nicer error, we would catch it anyway - * later in the channel code - */ - if (flag_closed) - throw new IOException("This session is closed."); - } - - cm.requestChannelTrileadPing(cn); - } - - /** - * Request authentication agent forwarding. - * @param agent object that implements the callbacks - * - * @throws IOException in case of any problem or when the session is closed - */ - - public synchronized boolean requestAuthAgentForwarding(AuthAgentCallback agent) throws IOException { - synchronized (this) { - /* - * The following is just a nicer error, we would catch it anyway - * later in the channel code - */ - if (flag_closed) - throw new IOException("This session is closed."); - } - - return cm.requestChannelAgentForwarding(cn, agent); - } - - public InputStream getStdout() { - return cn.getStdoutStream(); - } - - public InputStream getStderr() { - return cn.getStderrStream(); - } - - public OutputStream getStdin() { - return cn.getStdinStream(); - } - - /** - * This method blocks until there is more data available on either the - * stdout or stderr InputStream of this Session. Very useful - * if you do not want to use two parallel threads for reading from the two - * InputStreams. One can also specify a timeout. NOTE: do NOT call this - * method if you use concurrent threads that operate on either of the two - * InputStreams of this Session (otherwise this method may - * block, even though more data is available). - * - * @param timeout - * The (non-negative) timeout in ms. 0 means no - * timeout, the call may block forever. - * @return - *

    - *
  • 0 if no more data will arrive.
  • - *
  • 1 if more data is available.
  • - *
  • -1 if a timeout occurred.
  • - *
- * - * @throws IOException - * @deprecated This method has been replaced with a much more powerful wait-for-condition - * interface and therefore acts only as a wrapper. - * - */ - public int waitUntilDataAvailable(long timeout) throws IOException { - if (timeout < 0) - throw new IllegalArgumentException("timeout must not be negative!"); - - int conditions = cm.waitForCondition(cn, timeout, ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA - | ChannelCondition.EOF); - - if ((conditions & ChannelCondition.TIMEOUT) != 0) - return -1; - - if ((conditions & (ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA)) != 0) - return 1; - - /* Here we do not need to check separately for CLOSED, since CLOSED implies EOF */ - - if ((conditions & ChannelCondition.EOF) != 0) - return 0; - - throw new IllegalStateException("Unexpected condition result (" + conditions + ")"); - } - - /** - * This method blocks until certain conditions hold true on the underlying SSH-2 channel. - *

- * This method returns as soon as one of the following happens: - *

    - *
  • at least of the specified conditions (see {@link ChannelCondition}) holds true
  • - *
  • timeout > 0 and a timeout occured (TIMEOUT will be set in result conditions) - *
  • the underlying channel was closed (CLOSED will be set in result conditions) - *
- *

- * In any case, the result value contains ALL current conditions, which may be more - * than the specified condition set (i.e., never use the "==" operator to test for conditions - * in the bitmask, see also comments in {@link ChannelCondition}). - *

- * Note: do NOT call this method if you want to wait for STDOUT_DATA or STDERR_DATA and - * there are concurrent threads (e.g., StreamGobblers) that operate on either of the two - * InputStreams of this Session (otherwise this method may - * block, even though more data is available in the StreamGobblers). - * - * @param condition_set a bitmask based on {@link ChannelCondition} values - * @param timeout non-negative timeout in ms, 0 means no timeout - * @return all bitmask specifying all current conditions that are true - */ - - public int waitForCondition(int condition_set, long timeout) { - if (timeout < 0) - throw new IllegalArgumentException("timeout must be non-negative!"); - - return cm.waitForCondition(cn, timeout, condition_set); - } - - /** - * Get the exit code/status from the remote command - if available. Be - * careful - not all server implementations return this value. It is - * generally a good idea to call this method only when all data from the - * remote side has been consumed (see also the method). - * - * @return An Integer holding the exit code, or - * null if no exit code is (yet) available. - */ - public Integer getExitStatus() { - return cn.getExitStatus(); - } - - /** - * Get the name of the signal by which the process on the remote side was - * stopped - if available and applicable. Be careful - not all server - * implementations return this value. - * - * @return An String holding the name of the signal, or - * null if the process exited normally or is still - * running (or if the server forgot to send this information). - */ - public String getExitSignal() { - return cn.getExitSignal(); - } - - /** - * Close this session. NEVER forget to call this method to free up resources - - * even if you got an exception from one of the other methods (or when - * getting an Exception on the Input- or OutputStreams). Sometimes these other - * methods may throw an exception, saying that the underlying channel is - * closed (this can happen, e.g., if the other server sent a close message.) - * However, as long as you have not called the close() - * method, you may be wasting (local) resources. - * - */ - public void close() { - synchronized (this) { - if (flag_closed) - return; - - flag_closed = true; - - if (x11FakeCookie != null) - cm.unRegisterX11Cookie(x11FakeCookie, true); - - try { - cm.closeChannel(cn, "Closed due to user request", true); - } - catch (IOException ignored) { - } - } - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/StreamGobbler.java --- a/src/com/trilead/ssh2/StreamGobbler.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,194 +0,0 @@ - -package com.trilead.ssh2; - -import java.io.IOException; -import java.io.InputStream; - -/** - * A StreamGobbler is an InputStream that uses an internal worker - * thread to constantly consume input from another InputStream. It uses a buffer - * to store the consumed data. The buffer size is automatically adjusted, if needed. - *

- * This class is sometimes very convenient - if you wrap a session's STDOUT and STDERR - * InputStreams with instances of this class, then you don't have to bother about - * the shared window of STDOUT and STDERR in the low level SSH-2 protocol, - * since all arriving data will be immediatelly consumed by the worker threads. - * Also, as a side effect, the streams will be buffered (e.g., single byte - * read() operations are faster). - *

- * Other SSH for Java libraries include this functionality by default in - * their STDOUT and STDERR InputStream implementations, however, please be aware - * that this approach has also a downside: - *

- * If you do not call the StreamGobbler's read() method often enough - * and the peer is constantly sending huge amounts of data, then you will sooner or later - * encounter a low memory situation due to the aggregated data (well, it also depends on the Java heap size). - * Joe Average will like this class anyway - a paranoid programmer would never use such an approach. - *

- * The term "StreamGobbler" was taken from an article called "When Runtime.exec() won't", - * see http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: StreamGobbler.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ - -public class StreamGobbler extends InputStream { - class GobblerThread extends Thread { - public void run() { - byte[] buff = new byte[8192]; - - while (true) { - try { - int avail = is.read(buff); - - synchronized (synchronizer) { - if (avail <= 0) { - isEOF = true; - synchronizer.notifyAll(); - break; - } - - int space_available = buffer.length - write_pos; - - if (space_available < avail) { - /* compact/resize buffer */ - int unread_size = write_pos - read_pos; - int need_space = unread_size + avail; - byte[] new_buffer = buffer; - - if (need_space > buffer.length) { - int inc = need_space / 3; - inc = (inc < 256) ? 256 : inc; - inc = (inc > 8192) ? 8192 : inc; - new_buffer = new byte[need_space + inc]; - } - - if (unread_size > 0) - System.arraycopy(buffer, read_pos, new_buffer, 0, unread_size); - - buffer = new_buffer; - read_pos = 0; - write_pos = unread_size; - } - - System.arraycopy(buff, 0, buffer, write_pos, avail); - write_pos += avail; - synchronizer.notifyAll(); - } - } - catch (IOException e) { - synchronized (synchronizer) { - exception = e; - synchronizer.notifyAll(); - break; - } - } - } - } - } - - private InputStream is; - private GobblerThread t; - - private Object synchronizer = new Object(); - - private boolean isEOF = false; - private boolean isClosed = false; - private IOException exception = null; - - private byte[] buffer = new byte[2048]; - private int read_pos = 0; - private int write_pos = 0; - - public StreamGobbler(InputStream is) { - this.is = is; - t = new GobblerThread(); - t.setDaemon(true); - t.start(); - } - - public int read() throws IOException { - synchronized (synchronizer) { - if (isClosed) - throw new IOException("This StreamGobbler is closed."); - - while (read_pos == write_pos) { - if (exception != null) - throw exception; - - if (isEOF) - return -1; - - try { - synchronizer.wait(); - } - catch (InterruptedException e) { - } - } - - int b = buffer[read_pos++] & 0xff; - return b; - } - } - - public int available() throws IOException { - synchronized (synchronizer) { - if (isClosed) - throw new IOException("This StreamGobbler is closed."); - - return write_pos - read_pos; - } - } - - public int read(byte[] b) throws IOException { - return read(b, 0, b.length); - } - - public void close() throws IOException { - synchronized (synchronizer) { - if (isClosed) - return; - - isClosed = true; - isEOF = true; - synchronizer.notifyAll(); - is.close(); - } - } - - public int read(byte[] b, int off, int len) throws IOException { - if (b == null) - throw new NullPointerException(); - - if ((off < 0) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) || (off > b.length)) - throw new IndexOutOfBoundsException(); - - if (len == 0) - return 0; - - synchronized (synchronizer) { - if (isClosed) - throw new IOException("This StreamGobbler is closed."); - - while (read_pos == write_pos) { - if (exception != null) - throw exception; - - if (isEOF) - return -1; - - try { - synchronizer.wait(); - } - catch (InterruptedException e) { - } - } - - int avail = write_pos - read_pos; - avail = (avail > len) ? len : avail; - System.arraycopy(buffer, read_pos, b, off, avail); - read_pos += avail; - return avail; - } - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/auth/AuthenticationManager.java --- a/src/com/trilead/ssh2/auth/AuthenticationManager.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,378 +0,0 @@ - -package com.trilead.ssh2.auth; - -import java.io.IOException; -import java.security.KeyPair; -import java.security.PrivateKey; -import java.security.SecureRandom; -import java.security.interfaces.DSAPrivateKey; -import java.security.interfaces.DSAPublicKey; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; -import java.util.Vector; - -import com.trilead.ssh2.InteractiveCallback; -import com.trilead.ssh2.crypto.PEMDecoder; -import com.trilead.ssh2.packets.PacketServiceAccept; -import com.trilead.ssh2.packets.PacketServiceRequest; -import com.trilead.ssh2.packets.PacketUserauthBanner; -import com.trilead.ssh2.packets.PacketUserauthFailure; -import com.trilead.ssh2.packets.PacketUserauthInfoRequest; -import com.trilead.ssh2.packets.PacketUserauthInfoResponse; -import com.trilead.ssh2.packets.PacketUserauthRequestInteractive; -import com.trilead.ssh2.packets.PacketUserauthRequestNone; -import com.trilead.ssh2.packets.PacketUserauthRequestPassword; -import com.trilead.ssh2.packets.PacketUserauthRequestPublicKey; -import com.trilead.ssh2.packets.Packets; -import com.trilead.ssh2.packets.TypesWriter; -import com.trilead.ssh2.signature.DSASHA1Verify; -import com.trilead.ssh2.signature.ECDSASHA2Verify; -import com.trilead.ssh2.signature.RSASHA1Verify; -import com.trilead.ssh2.transport.MessageHandler; -import com.trilead.ssh2.transport.TransportManager; - - -/** - * AuthenticationManager. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: AuthenticationManager.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $ - */ -public class AuthenticationManager implements MessageHandler { - TransportManager tm; - - Vector packets = new Vector(); - boolean connectionClosed = false; - - String banner; - - String[] remainingMethods = new String[0]; - boolean isPartialSuccess = false; - - boolean authenticated = false; - boolean initDone = false; - - public AuthenticationManager(TransportManager tm) { - this.tm = tm; - } - - boolean methodPossible(String methName) { - if (remainingMethods == null) - return false; - - for (int i = 0; i < remainingMethods.length; i++) { - if (remainingMethods[i].compareTo(methName) == 0) - return true; - } - - return false; - } - - byte[] deQueue() throws IOException { - synchronized (packets) { - while (packets.size() == 0) { - if (connectionClosed) - throw(IOException) new IOException("The connection is closed.").initCause(tm - .getReasonClosedCause()); - - try { - packets.wait(); - } - catch (InterruptedException ign) { - } - } - - /* This sequence works with J2ME */ - byte[] res = (byte[]) packets.firstElement(); - packets.removeElementAt(0); - return res; - } - } - - byte[] getNextMessage() throws IOException { - while (true) { - byte[] msg = deQueue(); - - if (msg[0] != Packets.SSH_MSG_USERAUTH_BANNER) - return msg; - - PacketUserauthBanner sb = new PacketUserauthBanner(msg, 0, msg.length); - banner = sb.getBanner(); - } - } - - public String[] getRemainingMethods(String user) throws IOException { - initialize(user); - return remainingMethods; - } - - public boolean getPartialSuccess() { - return isPartialSuccess; - } - - private boolean initialize(String user) throws IOException { - if (initDone == false) { - tm.registerMessageHandler(this, 0, 255); - PacketServiceRequest sr = new PacketServiceRequest("ssh-userauth"); - tm.sendMessage(sr.getPayload()); - PacketUserauthRequestNone urn = new PacketUserauthRequestNone("ssh-connection", user); - tm.sendMessage(urn.getPayload()); - byte[] msg = getNextMessage(); - new PacketServiceAccept(msg, 0, msg.length); - msg = getNextMessage(); - initDone = true; - - if (msg[0] == Packets.SSH_MSG_USERAUTH_SUCCESS) { - authenticated = true; - tm.removeMessageHandler(this, 0, 255); - return true; - } - - if (msg[0] == Packets.SSH_MSG_USERAUTH_FAILURE) { - PacketUserauthFailure puf = new PacketUserauthFailure(msg, 0, msg.length); - remainingMethods = puf.getAuthThatCanContinue(); - isPartialSuccess = puf.isPartialSuccess(); - return false; - } - - throw new IOException("Unexpected SSH message (type " + msg[0] + ")"); - } - - return authenticated; - } - - public boolean authenticatePublicKey(String user, char[] PEMPrivateKey, String password, SecureRandom rnd) - throws IOException { - KeyPair pair = PEMDecoder.decode(PEMPrivateKey, password); - return authenticatePublicKey(user, pair, rnd); - } - - public boolean authenticatePublicKey(String user, KeyPair pair, SecureRandom rnd) - throws IOException { - PrivateKey key = pair.getPrivate(); - - try { - initialize(user); - - if (methodPossible("publickey") == false) - throw new IOException("Authentication method publickey not supported by the server at this stage."); - - if (key instanceof DSAPrivateKey) { - DSAPrivateKey pk = (DSAPrivateKey) key; - byte[] pk_enc = DSASHA1Verify.encodeSSHDSAPublicKey((DSAPublicKey) 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-dss"); - tw.writeString(pk_enc, 0, pk_enc.length); - byte[] msg = tw.getBytes(); - byte[] ds = DSASHA1Verify.generateSignature(msg, pk, rnd); - byte[] ds_enc = DSASHA1Verify.encodeSSHDSASignature(ds); - PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user, - "ssh-dss", pk_enc, ds_enc); - tm.sendMessage(ua.getPayload()); - } - else if (key instanceof RSAPrivateKey) { - RSAPrivateKey pk = (RSAPrivateKey) 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 = RSASHA1Verify.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; - final String algo = ECDSASHA2Verify.ECDSA_SHA2_PREFIX - + ECDSASHA2Verify.getCurveName(pk.getParams()); - byte[] pk_enc = ECDSASHA2Verify.encodeSSHECDSAPublicKey((ECPublicKey) 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(algo); - tw.writeString(pk_enc, 0, pk_enc.length); - } - byte[] msg = tw.getBytes(); - byte[] ds = ECDSASHA2Verify.generateSignature(msg, pk); - byte[] ec_sig_enc = ECDSASHA2Verify.encodeSSHECDSASignature(ds, pk.getParams()); - PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user, - algo, pk_enc, ec_sig_enc); - tm.sendMessage(ua.getPayload()); - } - else { - throw new IOException("Unknown private key type returned by the PEM decoder."); - } - - byte[] ar = getNextMessage(); - - if (ar[0] == Packets.SSH_MSG_USERAUTH_SUCCESS) { - authenticated = true; - tm.removeMessageHandler(this, 0, 255); - return true; - } - - if (ar[0] == Packets.SSH_MSG_USERAUTH_FAILURE) { - PacketUserauthFailure puf = new PacketUserauthFailure(ar, 0, ar.length); - remainingMethods = puf.getAuthThatCanContinue(); - isPartialSuccess = puf.isPartialSuccess(); - return false; - } - - throw new IOException("Unexpected SSH message (type " + ar[0] + ")"); - } - catch (IOException e) { - e.printStackTrace(); - tm.close(e, false); - throw(IOException) new IOException("Publickey authentication failed.").initCause(e); - } - } - - public boolean authenticateNone(String user) throws IOException { - try { - initialize(user); - return authenticated; - } - catch (IOException e) { - tm.close(e, false); - throw(IOException) new IOException("None authentication failed.").initCause(e); - } - } - - public boolean authenticatePassword(String user, String pass) throws IOException { - try { - initialize(user); - - if (methodPossible("password") == false) - throw new IOException("Authentication method password not supported by the server at this stage."); - - PacketUserauthRequestPassword ua = new PacketUserauthRequestPassword("ssh-connection", user, pass); - tm.sendMessage(ua.getPayload()); - byte[] ar = getNextMessage(); - - if (ar[0] == Packets.SSH_MSG_USERAUTH_SUCCESS) { - authenticated = true; - tm.removeMessageHandler(this, 0, 255); - return true; - } - - if (ar[0] == Packets.SSH_MSG_USERAUTH_FAILURE) { - PacketUserauthFailure puf = new PacketUserauthFailure(ar, 0, ar.length); - remainingMethods = puf.getAuthThatCanContinue(); - isPartialSuccess = puf.isPartialSuccess(); - return false; - } - - throw new IOException("Unexpected SSH message (type " + ar[0] + ")"); - } - catch (IOException e) { - tm.close(e, false); - throw(IOException) new IOException("Password authentication failed.").initCause(e); - } - } - - public boolean authenticateInteractive(String user, String[] submethods, InteractiveCallback cb) throws IOException { - try { - initialize(user); - - if (methodPossible("keyboard-interactive") == false) - throw new IOException( - "Authentication method keyboard-interactive not supported by the server at this stage."); - - if (submethods == null) - submethods = new String[0]; - - PacketUserauthRequestInteractive ua = new PacketUserauthRequestInteractive("ssh-connection", user, - submethods); - tm.sendMessage(ua.getPayload()); - - while (true) { - byte[] ar = getNextMessage(); - - if (ar[0] == Packets.SSH_MSG_USERAUTH_SUCCESS) { - authenticated = true; - tm.removeMessageHandler(this, 0, 255); - return true; - } - - if (ar[0] == Packets.SSH_MSG_USERAUTH_FAILURE) { - PacketUserauthFailure puf = new PacketUserauthFailure(ar, 0, ar.length); - remainingMethods = puf.getAuthThatCanContinue(); - isPartialSuccess = puf.isPartialSuccess(); - return false; - } - - if (ar[0] == Packets.SSH_MSG_USERAUTH_INFO_REQUEST) { - PacketUserauthInfoRequest pui = new PacketUserauthInfoRequest(ar, 0, ar.length); - String[] responses; - - try { - responses = cb.replyToChallenge(pui.getName(), pui.getInstruction(), pui.getNumPrompts(), pui - .getPrompt(), pui.getEcho()); - } - catch (Exception e) { - throw(IOException) new IOException("Exception in callback.").initCause(e); - } - - if (responses == null) - throw new IOException("Your callback may not return NULL!"); - - PacketUserauthInfoResponse puir = new PacketUserauthInfoResponse(responses); - tm.sendMessage(puir.getPayload()); - continue; - } - - throw new IOException("Unexpected SSH message (type " + ar[0] + ")"); - } - } - catch (IOException e) { - tm.close(e, false); - throw(IOException) new IOException("Keyboard-interactive authentication failed.").initCause(e); - } - } - - public void handleMessage(byte[] msg, int msglen) throws IOException { - synchronized (packets) { - if (msg == null) { - connectionClosed = true; - } - else { - byte[] tmp = new byte[msglen]; - System.arraycopy(msg, 0, tmp, 0, msglen); - packets.addElement(tmp); - } - - packets.notifyAll(); - - if (packets.size() > 5) { - connectionClosed = true; - throw new IOException("Error, peer is flooding us with authentication packets."); - } - } - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/channel/AuthAgentForwardThread.java --- a/src/com/trilead/ssh2/channel/AuthAgentForwardThread.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,542 +0,0 @@ -/* - * 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 com.trilead.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 com.trilead.ssh2.AuthAgentCallback; -import com.trilead.ssh2.log.Logger; -import com.trilead.ssh2.packets.TypesReader; -import com.trilead.ssh2.packets.TypesWriter; -import com.trilead.ssh2.signature.DSASHA1Verify; -import com.trilead.ssh2.signature.ECDSASHA2Verify; -import com.trilead.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 ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/channel/Channel.java --- a/src/com/trilead/ssh2/channel/Channel.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,192 +0,0 @@ - -package com.trilead.ssh2.channel; - -/** - * Channel. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: Channel.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -public class Channel { - /* - * OK. Here is an important part of the JVM Specification: - * (http://java.sun.com/docs/books/vmspec/2nd-edition/html/Threads.doc.html#22214) - * - * Any association between locks and variables is purely conventional. - * Locking any lock conceptually flushes all variables from a thread's - * working memory, and unlocking any lock forces the writing out to main - * memory of all variables that the thread has assigned. That a lock may be - * associated with a particular object or a class is purely a convention. - * (...) - * - * If a thread uses a particular shared variable only after locking a - * particular lock and before the corresponding unlocking of that same lock, - * then the thread will read the shared value of that variable from main - * memory after the lock operation, if necessary, and will copy back to main - * memory the value most recently assigned to that variable before the - * unlock operation. - * - * This, in conjunction with the mutual exclusion rules for locks, suffices - * to guarantee that values are correctly transmitted from one thread to - * another through shared variables. - * - * ====> Always keep that in mind when modifying the Channel/ChannelManger - * code. - * - */ - - static final int STATE_OPENING = 1; - static final int STATE_OPEN = 2; - static final int STATE_CLOSED = 4; - - static final int CHANNEL_BUFFER_SIZE = 30000; - - /* - * To achieve correctness, the following rules have to be respected when - * accessing this object: - */ - - // These fields can always be read - final ChannelManager cm; - final ChannelOutputStream stdinStream; - final ChannelInputStream stdoutStream; - final ChannelInputStream stderrStream; - - // These two fields will only be written while the Channel is in state - // STATE_OPENING. - // The code makes sure that the two fields are written out when the state is - // changing to STATE_OPEN. - // Therefore, if you know that the Channel is in state STATE_OPEN, then you - // can read these two fields without synchronizing on the Channel. However, make - // sure that you get the latest values (e.g., flush caches by synchronizing on any - // object). However, to be on the safe side, you can lock the channel. - - int localID = -1; - int remoteID = -1; - - /* - * Make sure that we never send a data/EOF/WindowChange msg after a CLOSE - * msg. - * - * This is a little bit complicated, but we have to do it in that way, since - * we cannot keep a lock on the Channel during the send operation (this - * would block sometimes the receiver thread, and, in extreme cases, can - * lead to a deadlock on both sides of the connection (senders are blocked - * since the receive buffers on the other side are full, and receiver - * threads wait for the senders to finish). It all depends on the - * implementation on the other side. But we cannot make any assumptions, we - * have to assume the worst case. Confused? Just believe me. - */ - - /* - * If you send a message on a channel, then you have to aquire the - * "channelSendLock" and check the "closeMessageSent" flag (this variable - * may only be accessed while holding the "channelSendLock" !!! - * - * BTW: NEVER EVER SEND MESSAGES FROM THE RECEIVE THREAD - see explanation - * above. - */ - - final Object channelSendLock = new Object(); - boolean closeMessageSent = false; - - /* - * Stop memory fragmentation by allocating this often used buffer. - * May only be used while holding the channelSendLock - */ - - final byte[] msgWindowAdjust = new byte[9]; - - // If you access (read or write) any of the following fields, then you have - // to synchronize on the channel. - - int state = STATE_OPENING; - - boolean closeMessageRecv = false; - - /* This is a stupid implementation. At the moment we can only wait - * for one pending request per channel. - */ - int successCounter = 0; - int failedCounter = 0; - - int localWindow = 0; /* locally, we use a small window, < 2^31 */ - long remoteWindow = 0; /* long for readable 2^32 - 1 window support */ - - int localMaxPacketSize = -1; - int remoteMaxPacketSize = -1; - - final byte[] stdoutBuffer = new byte[CHANNEL_BUFFER_SIZE]; - final byte[] stderrBuffer = new byte[CHANNEL_BUFFER_SIZE]; - - int stdoutReadpos = 0; - int stdoutWritepos = 0; - int stderrReadpos = 0; - int stderrWritepos = 0; - - boolean EOF = false; - - Integer exit_status; - - String exit_signal; - - // we keep the x11 cookie so that this channel can be closed when this - // specific x11 forwarding gets stopped - - String hexX11FakeCookie; - - // reasonClosed is special, since we sometimes need to access it - // while holding the channelSendLock. - // We protect it with a private short term lock. - - private final Object reasonClosedLock = new Object(); - private String reasonClosed = null; - - public Channel(ChannelManager cm) { - this.cm = cm; - this.localWindow = CHANNEL_BUFFER_SIZE; - this.localMaxPacketSize = 35000 - 1024; // leave enough slack - this.stdinStream = new ChannelOutputStream(this); - this.stdoutStream = new ChannelInputStream(this, false); - this.stderrStream = new ChannelInputStream(this, true); - } - - /* Methods to allow access from classes outside of this package */ - - public ChannelInputStream getStderrStream() { - return stderrStream; - } - - public ChannelOutputStream getStdinStream() { - return stdinStream; - } - - public ChannelInputStream getStdoutStream() { - return stdoutStream; - } - - public String getExitSignal() { - synchronized (this) { - return exit_signal; - } - } - - public Integer getExitStatus() { - synchronized (this) { - return exit_status; - } - } - - public String getReasonClosed() { - synchronized (reasonClosedLock) { - return reasonClosed; - } - } - - public void setReasonClosed(String reasonClosed) { - synchronized (reasonClosedLock) { - if (this.reasonClosed == null) - this.reasonClosed = reasonClosed; - } - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/channel/ChannelInputStream.java --- a/src/com/trilead/ssh2/channel/ChannelInputStream.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,74 +0,0 @@ - -package com.trilead.ssh2.channel; - -import java.io.IOException; -import java.io.InputStream; - -/** - * ChannelInputStream. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: ChannelInputStream.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -public final class ChannelInputStream extends InputStream { - Channel c; - - boolean isClosed = false; - boolean isEOF = false; - boolean extendedFlag = false; - - ChannelInputStream(Channel c, boolean isExtended) { - this.c = c; - this.extendedFlag = isExtended; - } - - public int available() throws IOException { - if (isEOF) - return 0; - - int avail = c.cm.getAvailable(c, extendedFlag); - /* We must not return -1 on EOF */ - return (avail > 0) ? avail : 0; - } - - public void close() throws IOException { - isClosed = true; - } - - public int read(byte[] b, int off, int len) throws IOException { - if (b == null) - throw new NullPointerException(); - - if ((off < 0) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) || (off > b.length)) - throw new IndexOutOfBoundsException(); - - if (len == 0) - return 0; - - if (isEOF) - return -1; - - int ret = c.cm.getChannelData(c, extendedFlag, b, off, len); - - if (ret == -1) { - isEOF = true; - } - - return ret; - } - - public int read(byte[] b) throws IOException { - return read(b, 0, b.length); - } - - public int read() throws IOException { - /* Yes, this stream is pure and unbuffered, a single byte read() is slow */ - final byte b[] = new byte[1]; - int ret = read(b, 0, 1); - - if (ret != 1) - return -1; - - return b[0] & 0xff; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/channel/ChannelManager.java --- a/src/com/trilead/ssh2/channel/ChannelManager.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1535 +0,0 @@ - -package com.trilead.ssh2.channel; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Vector; - -import com.trilead.ssh2.AuthAgentCallback; -import com.trilead.ssh2.ChannelCondition; -import com.trilead.ssh2.log.Logger; -import com.trilead.ssh2.packets.PacketChannelAuthAgentReq; -import com.trilead.ssh2.packets.PacketChannelOpenConfirmation; -import com.trilead.ssh2.packets.PacketChannelOpenFailure; -import com.trilead.ssh2.packets.PacketChannelTrileadPing; -import com.trilead.ssh2.packets.PacketGlobalCancelForwardRequest; -import com.trilead.ssh2.packets.PacketGlobalForwardRequest; -import com.trilead.ssh2.packets.PacketGlobalTrileadPing; -import com.trilead.ssh2.packets.PacketOpenDirectTCPIPChannel; -import com.trilead.ssh2.packets.PacketOpenSessionChannel; -import com.trilead.ssh2.packets.PacketSessionExecCommand; -import com.trilead.ssh2.packets.PacketSessionPtyRequest; -import com.trilead.ssh2.packets.PacketSessionPtyResize; -import com.trilead.ssh2.packets.PacketSessionStartShell; -import com.trilead.ssh2.packets.PacketSessionSubsystemRequest; -import com.trilead.ssh2.packets.PacketSessionX11Request; -import com.trilead.ssh2.packets.Packets; -import com.trilead.ssh2.packets.TypesReader; -import com.trilead.ssh2.transport.MessageHandler; -import com.trilead.ssh2.transport.TransportManager; - -/** - * ChannelManager. Please read the comments in Channel.java. - *

- * Besides the crypto part, this is the core of the library. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: ChannelManager.java,v 1.2 2008/03/03 07:01:36 cplattne Exp $ - */ -public class ChannelManager implements MessageHandler { - private static final Logger log = Logger.getLogger(ChannelManager.class); - - private HashMap x11_magic_cookies = new HashMap(); - - private TransportManager tm; - - private Vector channels = new Vector(); - private int nextLocalChannel = 100; - private boolean shutdown = false; - private int globalSuccessCounter = 0; - private int globalFailedCounter = 0; - - private HashMap remoteForwardings = new HashMap(); - - private AuthAgentCallback authAgent; - - private Vector listenerThreads = new Vector(); - - private boolean listenerThreadsAllowed = true; - - public ChannelManager(TransportManager tm) { - this.tm = tm; - tm.registerMessageHandler(this, 80, 100); - } - - private Channel getChannel(int id) { - synchronized (channels) { - for (int i = 0; i < channels.size(); i++) { - Channel c = channels.elementAt(i); - - if (c.localID == id) - return c; - } - } - - return null; - } - - private void removeChannel(int id) { - synchronized (channels) { - for (int i = 0; i < channels.size(); i++) { - Channel c = channels.elementAt(i); - - if (c.localID == id) { - channels.removeElementAt(i); - break; - } - } - } - } - - private int addChannel(Channel c) { - synchronized (channels) { - channels.addElement(c); - return nextLocalChannel++; - } - } - - private void waitUntilChannelOpen(Channel c) throws IOException { - synchronized (c) { - while (c.state == Channel.STATE_OPENING) { - try { - c.wait(); - } - catch (InterruptedException ignore) { - } - } - - if (c.state != Channel.STATE_OPEN) { - removeChannel(c.localID); - String detail = c.getReasonClosed(); - - if (detail == null) - detail = "state: " + c.state; - - throw new IOException("Could not open channel (" + detail + ")"); - } - } - } - - private final boolean waitForGlobalRequestResult() throws IOException { - synchronized (channels) { - while ((globalSuccessCounter == 0) && (globalFailedCounter == 0)) { - if (shutdown) { - throw new IOException("The connection is being shutdown"); - } - - try { - channels.wait(); - } - catch (InterruptedException ignore) { - } - } - - if ((globalFailedCounter == 0) && (globalSuccessCounter == 1)) - return true; - - if ((globalFailedCounter == 1) && (globalSuccessCounter == 0)) - return false; - - throw new IOException("Illegal state. The server sent " + globalSuccessCounter - + " SSH_MSG_REQUEST_SUCCESS and " + globalFailedCounter + " SSH_MSG_REQUEST_FAILURE messages."); - } - } - - private final boolean waitForChannelRequestResult(Channel c) throws IOException { - synchronized (c) { - while ((c.successCounter == 0) && (c.failedCounter == 0)) { - if (c.state != Channel.STATE_OPEN) { - String detail = c.getReasonClosed(); - - if (detail == null) - detail = "state: " + c.state; - - throw new IOException("This SSH2 channel is not open (" + detail + ")"); - } - - try { - c.wait(); - } - catch (InterruptedException ignore) { - } - } - - if ((c.failedCounter == 0) && (c.successCounter == 1)) - return true; - - if ((c.failedCounter == 1) && (c.successCounter == 0)) - return false; - - throw new IOException("Illegal state. The server sent " + c.successCounter - + " SSH_MSG_CHANNEL_SUCCESS and " + c.failedCounter + " SSH_MSG_CHANNEL_FAILURE messages."); - } - } - - public void registerX11Cookie(String hexFakeCookie, X11ServerData data) { - synchronized (x11_magic_cookies) { - x11_magic_cookies.put(hexFakeCookie, data); - } - } - - public void unRegisterX11Cookie(String hexFakeCookie, boolean killChannels) { - if (hexFakeCookie == null) - throw new IllegalStateException("hexFakeCookie may not be null"); - - synchronized (x11_magic_cookies) { - x11_magic_cookies.remove(hexFakeCookie); - } - - if (killChannels == false) - return; - - if (log.isEnabled()) - log.log(50, "Closing all X11 channels for the given fake cookie"); - - Vector channel_copy; - - synchronized (channels) { - channel_copy = (Vector) channels.clone(); - } - - for (int i = 0; i < channel_copy.size(); i++) { - Channel c = channel_copy.elementAt(i); - - synchronized (c) { - if (hexFakeCookie.equals(c.hexX11FakeCookie) == false) - continue; - } - - try { - closeChannel(c, "Closing X11 channel since the corresponding session is closing", true); - } - catch (IOException e) { - } - } - } - - public X11ServerData checkX11Cookie(String hexFakeCookie) { - synchronized (x11_magic_cookies) { - if (hexFakeCookie != null) - return x11_magic_cookies.get(hexFakeCookie); - } - - return null; - } - - public void closeAllChannels() { - if (log.isEnabled()) - log.log(50, "Closing all channels"); - - Vector channel_copy; - - synchronized (channels) { - channel_copy = (Vector) channels.clone(); - } - - for (int i = 0; i < channel_copy.size(); i++) { - Channel c = channel_copy.elementAt(i); - - try { - closeChannel(c, "Closing all channels", true); - } - catch (IOException e) { - } - } - } - - public void closeChannel(Channel c, String reason, boolean force) throws IOException { - byte msg[] = new byte[5]; - - synchronized (c) { - if (force) { - c.state = Channel.STATE_CLOSED; - c.EOF = true; - } - - c.setReasonClosed(reason); - msg[0] = Packets.SSH_MSG_CHANNEL_CLOSE; - msg[1] = (byte)(c.remoteID >> 24); - msg[2] = (byte)(c.remoteID >> 16); - msg[3] = (byte)(c.remoteID >> 8); - msg[4] = (byte)(c.remoteID); - c.notifyAll(); - } - - synchronized (c.channelSendLock) { - if (c.closeMessageSent == true) - return; - - tm.sendMessage(msg); - c.closeMessageSent = true; - } - - if (log.isEnabled()) - log.log(50, "Sent SSH_MSG_CHANNEL_CLOSE (channel " + c.localID + ")"); - } - - public void sendEOF(Channel c) throws IOException { - byte[] msg = new byte[5]; - - synchronized (c) { - if (c.state != Channel.STATE_OPEN) - return; - - msg[0] = Packets.SSH_MSG_CHANNEL_EOF; - msg[1] = (byte)(c.remoteID >> 24); - msg[2] = (byte)(c.remoteID >> 16); - msg[3] = (byte)(c.remoteID >> 8); - msg[4] = (byte)(c.remoteID); - } - - synchronized (c.channelSendLock) { - if (c.closeMessageSent == true) - return; - - tm.sendMessage(msg); - } - - if (log.isEnabled()) - log.log(50, "Sent EOF (Channel " + c.localID + "/" + c.remoteID + ")"); - } - - public void sendOpenConfirmation(Channel c) throws IOException { - PacketChannelOpenConfirmation pcoc = null; - - synchronized (c) { - if (c.state != Channel.STATE_OPENING) - return; - - c.state = Channel.STATE_OPEN; - pcoc = new PacketChannelOpenConfirmation(c.remoteID, c.localID, c.localWindow, c.localMaxPacketSize); - } - - synchronized (c.channelSendLock) { - if (c.closeMessageSent == true) - return; - - tm.sendMessage(pcoc.getPayload()); - } - } - - public void sendData(Channel c, byte[] buffer, int pos, int len) throws IOException { - while (len > 0) { - int thislen = 0; - byte[] msg; - - synchronized (c) { - while (true) { - if (c.state == Channel.STATE_CLOSED) - throw new IOException("SSH channel is closed. (" + c.getReasonClosed() + ")"); - - if (c.state != Channel.STATE_OPEN) - throw new IOException("SSH channel in strange state. (" + c.state + ")"); - - if (c.remoteWindow != 0) - break; - - try { - c.wait(); - } - catch (InterruptedException ignore) { - } - } - - /* len > 0, no sign extension can happen when comparing */ - thislen = (c.remoteWindow >= len) ? len : (int) c.remoteWindow; - int estimatedMaxDataLen = c.remoteMaxPacketSize - (tm.getPacketOverheadEstimate() + 9); - - /* The worst case scenario =) a true bottleneck */ - - if (estimatedMaxDataLen <= 0) { - estimatedMaxDataLen = 1; - } - - if (thislen > estimatedMaxDataLen) - thislen = estimatedMaxDataLen; - - c.remoteWindow -= thislen; - msg = new byte[1 + 8 + thislen]; - msg[0] = Packets.SSH_MSG_CHANNEL_DATA; - msg[1] = (byte)(c.remoteID >> 24); - msg[2] = (byte)(c.remoteID >> 16); - msg[3] = (byte)(c.remoteID >> 8); - msg[4] = (byte)(c.remoteID); - msg[5] = (byte)(thislen >> 24); - msg[6] = (byte)(thislen >> 16); - msg[7] = (byte)(thislen >> 8); - msg[8] = (byte)(thislen); - System.arraycopy(buffer, pos, msg, 9, thislen); - } - - synchronized (c.channelSendLock) { - if (c.closeMessageSent == true) - throw new IOException("SSH channel is closed. (" + c.getReasonClosed() + ")"); - - tm.sendMessage(msg); - } - - pos += thislen; - len -= thislen; - } - } - - public int requestGlobalForward(String bindAddress, int bindPort, String targetAddress, int targetPort) - throws IOException { - RemoteForwardingData rfd = new RemoteForwardingData(); - rfd.bindAddress = bindAddress; - rfd.bindPort = bindPort; - rfd.targetAddress = targetAddress; - rfd.targetPort = targetPort; - - synchronized (remoteForwardings) { - Integer key = Integer.valueOf(bindPort); - - if (remoteForwardings.get(key) != null) { - throw new IOException("There is already a forwarding for remote port " + bindPort); - } - - remoteForwardings.put(key, rfd); - } - - synchronized (channels) { - globalSuccessCounter = globalFailedCounter = 0; - } - - PacketGlobalForwardRequest pgf = new PacketGlobalForwardRequest(true, bindAddress, bindPort); - tm.sendMessage(pgf.getPayload()); - - if (log.isEnabled()) - log.log(50, "Requesting a remote forwarding ('" + bindAddress + "', " + bindPort + ")"); - - try { - if (waitForGlobalRequestResult() == false) - throw new IOException("The server denied the request (did you enable port forwarding?)"); - } - catch (IOException e) { - synchronized (remoteForwardings) { - remoteForwardings.remove(rfd); - } - - throw e; - } - - return bindPort; - } - - public void requestCancelGlobalForward(int bindPort) throws IOException { - RemoteForwardingData rfd = null; - - synchronized (remoteForwardings) { - rfd = remoteForwardings.get(Integer.valueOf(bindPort)); - - if (rfd == null) - throw new IOException("Sorry, there is no known remote forwarding for remote port " + bindPort); - } - - synchronized (channels) { - globalSuccessCounter = globalFailedCounter = 0; - } - - PacketGlobalCancelForwardRequest pgcf = new PacketGlobalCancelForwardRequest(true, rfd.bindAddress, - rfd.bindPort); - tm.sendMessage(pgcf.getPayload()); - - if (log.isEnabled()) - log.log(50, "Requesting cancelation of remote forward ('" + rfd.bindAddress + "', " + rfd.bindPort + ")"); - - try { - if (waitForGlobalRequestResult() == false) - throw new IOException("The server denied the request."); - } - finally { - synchronized (remoteForwardings) { - /* Only now we are sure that no more forwarded connections will arrive */ - remoteForwardings.remove(rfd); - } - } - } - - /** - * @param agent - * @throws IOException - */ - public boolean requestChannelAgentForwarding(Channel c, AuthAgentCallback authAgent) throws IOException { - synchronized (this) { - if (this.authAgent != null) - throw new IllegalStateException("Auth agent already exists"); - - this.authAgent = authAgent; - } - - synchronized (channels) { - globalSuccessCounter = globalFailedCounter = 0; - } - - if (log.isEnabled()) - log.log(50, "Requesting agent forwarding"); - - PacketChannelAuthAgentReq aar = new PacketChannelAuthAgentReq(c.remoteID); - tm.sendMessage(aar.getPayload()); - - if (waitForChannelRequestResult(c) == false) { - authAgent = null; - return false; - } - - return true; - } - - public void registerThread(IChannelWorkerThread thr) throws IOException { - synchronized (listenerThreads) { - if (listenerThreadsAllowed == false) - throw new IOException("Too late, this connection is closed."); - - listenerThreads.addElement(thr); - } - } - - public Channel openDirectTCPIPChannel(String host_to_connect, int port_to_connect, String originator_IP_address, - int originator_port) throws IOException { - Channel c = new Channel(this); - - synchronized (c) { - c.localID = addChannel(c); - // end of synchronized block forces writing out to main memory - } - - PacketOpenDirectTCPIPChannel dtc = new PacketOpenDirectTCPIPChannel(c.localID, c.localWindow, - c.localMaxPacketSize, host_to_connect, port_to_connect, originator_IP_address, originator_port); - tm.sendMessage(dtc.getPayload()); - waitUntilChannelOpen(c); - return c; - } - - public Channel openSessionChannel() throws IOException { - Channel c = new Channel(this); - - synchronized (c) { - c.localID = addChannel(c); - // end of synchronized block forces the writing out to main memory - } - - if (log.isEnabled()) - log.log(50, "Sending SSH_MSG_CHANNEL_OPEN (Channel " + c.localID + ")"); - - PacketOpenSessionChannel smo = new PacketOpenSessionChannel(c.localID, c.localWindow, c.localMaxPacketSize); - tm.sendMessage(smo.getPayload()); - waitUntilChannelOpen(c); - return c; - } - - public void requestGlobalTrileadPing() throws IOException { - synchronized (channels) { - globalSuccessCounter = globalFailedCounter = 0; - } - - PacketGlobalTrileadPing pgtp = new PacketGlobalTrileadPing(); - tm.sendMessage(pgtp.getPayload()); - - if (log.isEnabled()) - log.log(50, "Sending SSH_MSG_GLOBAL_REQUEST 'trilead-ping'."); - - try { - if (waitForGlobalRequestResult() == true) - throw new IOException("Your server is alive - but buggy. " - + "It replied with SSH_MSG_REQUEST_SUCCESS when it actually should not."); - } - catch (IOException e) { - throw(IOException) new IOException("The ping request failed.").initCause(e); - } - } - - public void requestChannelTrileadPing(Channel c) throws IOException { - PacketChannelTrileadPing pctp; - - synchronized (c) { - if (c.state != Channel.STATE_OPEN) - throw new IOException("Cannot ping this channel (" + c.getReasonClosed() + ")"); - - pctp = new PacketChannelTrileadPing(c.remoteID); - c.successCounter = c.failedCounter = 0; - } - - synchronized (c.channelSendLock) { - if (c.closeMessageSent) - throw new IOException("Cannot ping this channel (" + c.getReasonClosed() + ")"); - - tm.sendMessage(pctp.getPayload()); - } - - try { - if (waitForChannelRequestResult(c) == true) - throw new IOException("Your server is alive - but buggy. " - + "It replied with SSH_MSG_SESSION_SUCCESS when it actually should not."); - } - catch (IOException e) { - throw(IOException) new IOException("The ping request failed.").initCause(e); - } - } - - public void requestPTY(Channel c, String term, int term_width_characters, int term_height_characters, - int term_width_pixels, int term_height_pixels, byte[] terminal_modes) throws IOException { - PacketSessionPtyRequest spr; - - synchronized (c) { - if (c.state != Channel.STATE_OPEN) - throw new IOException("Cannot request PTY on this channel (" + c.getReasonClosed() + ")"); - - spr = new PacketSessionPtyRequest(c.remoteID, true, term, term_width_characters, term_height_characters, - term_width_pixels, term_height_pixels, terminal_modes); - c.successCounter = c.failedCounter = 0; - } - - synchronized (c.channelSendLock) { - if (c.closeMessageSent) - throw new IOException("Cannot request PTY on this channel (" + c.getReasonClosed() + ")"); - - tm.sendMessage(spr.getPayload()); - } - - try { - if (waitForChannelRequestResult(c) == false) - throw new IOException("The server denied the request."); - } - catch (IOException e) { - throw(IOException) new IOException("PTY request failed").initCause(e); - } - } - - - public void resizePTY(Channel c, int term_width_characters, int term_height_characters, - int term_width_pixels, int term_height_pixels) throws IOException { - PacketSessionPtyResize spr; - - synchronized (c) { - if (c.state != Channel.STATE_OPEN) - throw new IOException("Cannot request PTY on this channel (" - + c.getReasonClosed() + ")"); - - spr = new PacketSessionPtyResize(c.remoteID, term_width_characters, term_height_characters, - term_width_pixels, term_height_pixels); - c.successCounter = c.failedCounter = 0; - } - - synchronized (c.channelSendLock) { - if (c.closeMessageSent) - throw new IOException("Cannot request PTY on this channel (" - + c.getReasonClosed() + ")"); - - tm.sendMessage(spr.getPayload()); - } - } - - - public void requestX11(Channel c, boolean singleConnection, String x11AuthenticationProtocol, - String x11AuthenticationCookie, int x11ScreenNumber) throws IOException { - PacketSessionX11Request psr; - - synchronized (c) { - if (c.state != Channel.STATE_OPEN) - throw new IOException("Cannot request X11 on this channel (" + c.getReasonClosed() + ")"); - - psr = new PacketSessionX11Request(c.remoteID, true, singleConnection, x11AuthenticationProtocol, - x11AuthenticationCookie, x11ScreenNumber); - c.successCounter = c.failedCounter = 0; - } - - synchronized (c.channelSendLock) { - if (c.closeMessageSent) - throw new IOException("Cannot request X11 on this channel (" + c.getReasonClosed() + ")"); - - tm.sendMessage(psr.getPayload()); - } - - if (log.isEnabled()) - log.log(50, "Requesting X11 forwarding (Channel " + c.localID + "/" + c.remoteID + ")"); - - try { - if (waitForChannelRequestResult(c) == false) - throw new IOException("The server denied the request."); - } - catch (IOException e) { - throw(IOException) new IOException("The X11 request failed.").initCause(e); - } - } - - public void requestSubSystem(Channel c, String subSystemName) throws IOException { - PacketSessionSubsystemRequest ssr; - - synchronized (c) { - if (c.state != Channel.STATE_OPEN) - throw new IOException("Cannot request subsystem on this channel (" + c.getReasonClosed() + ")"); - - ssr = new PacketSessionSubsystemRequest(c.remoteID, true, subSystemName); - c.successCounter = c.failedCounter = 0; - } - - synchronized (c.channelSendLock) { - if (c.closeMessageSent) - throw new IOException("Cannot request subsystem on this channel (" + c.getReasonClosed() + ")"); - - tm.sendMessage(ssr.getPayload()); - } - - try { - if (waitForChannelRequestResult(c) == false) - throw new IOException("The server denied the request."); - } - catch (IOException e) { - throw(IOException) new IOException("The subsystem request failed.").initCause(e); - } - } - - public void requestExecCommand(Channel c, String cmd) throws IOException { - PacketSessionExecCommand sm; - - synchronized (c) { - if (c.state != Channel.STATE_OPEN) - throw new IOException("Cannot execute command on this channel (" + c.getReasonClosed() + ")"); - - sm = new PacketSessionExecCommand(c.remoteID, true, cmd); - c.successCounter = c.failedCounter = 0; - } - - synchronized (c.channelSendLock) { - if (c.closeMessageSent) - throw new IOException("Cannot execute command on this channel (" + c.getReasonClosed() + ")"); - - tm.sendMessage(sm.getPayload()); - } - - if (log.isEnabled()) - log.log(50, "Executing command (channel " + c.localID + ", '" + cmd + "')"); - - try { - if (waitForChannelRequestResult(c) == false) - throw new IOException("The server denied the request."); - } - catch (IOException e) { - throw(IOException) new IOException("The execute request failed.").initCause(e); - } - } - - public void requestShell(Channel c) throws IOException { - PacketSessionStartShell sm; - - synchronized (c) { - if (c.state != Channel.STATE_OPEN) - throw new IOException("Cannot start shell on this channel (" + c.getReasonClosed() + ")"); - - sm = new PacketSessionStartShell(c.remoteID, true); - c.successCounter = c.failedCounter = 0; - } - - synchronized (c.channelSendLock) { - if (c.closeMessageSent) - throw new IOException("Cannot start shell on this channel (" + c.getReasonClosed() + ")"); - - tm.sendMessage(sm.getPayload()); - } - - try { - if (waitForChannelRequestResult(c) == false) - throw new IOException("The server denied the request."); - } - catch (IOException e) { - throw(IOException) new IOException("The shell request failed.").initCause(e); - } - } - - public void msgChannelExtendedData(byte[] msg, int msglen) throws IOException { - if (msglen <= 13) - throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong size (" + msglen + ")"); - - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - int dataType = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); - int len = ((msg[9] & 0xff) << 24) | ((msg[10] & 0xff) << 16) | ((msg[11] & 0xff) << 8) | (msg[12] & 0xff); - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_EXTENDED_DATA message for non-existent channel " + id); - - if (dataType != Packets.SSH_EXTENDED_DATA_STDERR) - throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has unknown type (" + dataType + ")"); - - if (len != (msglen - 13)) - throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong len (calculated " + (msglen - 13) - + ", got " + len + ")"); - - if (log.isEnabled()) - log.log(80, "Got SSH_MSG_CHANNEL_EXTENDED_DATA (channel " + id + ", " + len + ")"); - - synchronized (c) { - if (c.state == Channel.STATE_CLOSED) - return; // ignore - - if (c.state != Channel.STATE_OPEN) - throw new IOException("Got SSH_MSG_CHANNEL_EXTENDED_DATA, but channel is not in correct state (" - + c.state + ")"); - - if (c.localWindow < len) - throw new IOException("Remote sent too much data, does not fit into window."); - - c.localWindow -= len; - System.arraycopy(msg, 13, c.stderrBuffer, c.stderrWritepos, len); - c.stderrWritepos += len; - c.notifyAll(); - } - } - - /** - * Wait until for a condition. - * - * @param c - * Channel - * @param timeout - * in ms, 0 means no timeout. - * @param condition_mask - * minimum event mask - * @return all current events - * - */ - public int waitForCondition(Channel c, long timeout, int condition_mask) { - long end_time = 0; - boolean end_time_set = false; - - synchronized (c) { - while (true) { - int current_cond = 0; - int stdoutAvail = c.stdoutWritepos - c.stdoutReadpos; - int stderrAvail = c.stderrWritepos - c.stderrReadpos; - - if (stdoutAvail > 0) - current_cond = current_cond | ChannelCondition.STDOUT_DATA; - - if (stderrAvail > 0) - current_cond = current_cond | ChannelCondition.STDERR_DATA; - - if (c.EOF) - current_cond = current_cond | ChannelCondition.EOF; - - if (c.getExitStatus() != null) - current_cond = current_cond | ChannelCondition.EXIT_STATUS; - - if (c.getExitSignal() != null) - current_cond = current_cond | ChannelCondition.EXIT_SIGNAL; - - if (c.state == Channel.STATE_CLOSED) - return current_cond | ChannelCondition.CLOSED | ChannelCondition.EOF; - - if ((current_cond & condition_mask) != 0) - return current_cond; - - if (timeout > 0) { - if (!end_time_set) { - end_time = System.currentTimeMillis() + timeout; - end_time_set = true; - } - else { - timeout = end_time - System.currentTimeMillis(); - - if (timeout <= 0) - return current_cond | ChannelCondition.TIMEOUT; - } - } - - try { - if (timeout > 0) - c.wait(timeout); - else - c.wait(); - } - catch (InterruptedException e) { - } - } - } - } - - public int getAvailable(Channel c, boolean extended) throws IOException { - synchronized (c) { - int avail; - - if (extended) - avail = c.stderrWritepos - c.stderrReadpos; - else - avail = c.stdoutWritepos - c.stdoutReadpos; - - return ((avail > 0) ? avail : (c.EOF ? -1 : 0)); - } - } - - public int getChannelData(Channel c, boolean extended, byte[] target, int off, int len) throws IOException { - int copylen = 0; - int increment = 0; - int remoteID = 0; - int localID = 0; - - synchronized (c) { - int stdoutAvail = 0; - int stderrAvail = 0; - - while (true) { - /* - * Data available? We have to return remaining data even if the - * channel is already closed. - */ - stdoutAvail = c.stdoutWritepos - c.stdoutReadpos; - stderrAvail = c.stderrWritepos - c.stderrReadpos; - - if ((!extended) && (stdoutAvail != 0)) - break; - - if ((extended) && (stderrAvail != 0)) - break; - - /* Do not wait if more data will never arrive (EOF or CLOSED) */ - - if ((c.EOF) || (c.state != Channel.STATE_OPEN)) - return -1; - - try { - c.wait(); - } - catch (InterruptedException ignore) { - } - } - - /* OK, there is some data. Return it. */ - - if (!extended) { - copylen = (stdoutAvail > len) ? len : stdoutAvail; - System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, target, off, copylen); - c.stdoutReadpos += copylen; - - if (c.stdoutReadpos != c.stdoutWritepos) - System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, c.stdoutBuffer, 0, c.stdoutWritepos - - c.stdoutReadpos); - - c.stdoutWritepos -= c.stdoutReadpos; - c.stdoutReadpos = 0; - } - else { - copylen = (stderrAvail > len) ? len : stderrAvail; - System.arraycopy(c.stderrBuffer, c.stderrReadpos, target, off, copylen); - c.stderrReadpos += copylen; - - if (c.stderrReadpos != c.stderrWritepos) - System.arraycopy(c.stderrBuffer, c.stderrReadpos, c.stderrBuffer, 0, c.stderrWritepos - - c.stderrReadpos); - - c.stderrWritepos -= c.stderrReadpos; - c.stderrReadpos = 0; - } - - if (c.state != Channel.STATE_OPEN) - return copylen; - - if (c.localWindow < ((Channel.CHANNEL_BUFFER_SIZE + 1) / 2)) { - int minFreeSpace = Math.min(Channel.CHANNEL_BUFFER_SIZE - c.stdoutWritepos, Channel.CHANNEL_BUFFER_SIZE - - c.stderrWritepos); - increment = minFreeSpace - c.localWindow; - c.localWindow = minFreeSpace; - } - - remoteID = c.remoteID; /* read while holding the lock */ - localID = c.localID; /* read while holding the lock */ - } - - /* - * If a consumer reads stdout and stdin in parallel, we may end up with - * sending two msgWindowAdjust messages. Luckily, it - * does not matter in which order they arrive at the server. - */ - - if (increment > 0) { - if (log.isEnabled()) - log.log(80, "Sending SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + localID + ", " + increment + ")"); - - synchronized (c.channelSendLock) { - byte[] msg = c.msgWindowAdjust; - msg[0] = Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST; - msg[1] = (byte)(remoteID >> 24); - msg[2] = (byte)(remoteID >> 16); - msg[3] = (byte)(remoteID >> 8); - msg[4] = (byte)(remoteID); - msg[5] = (byte)(increment >> 24); - msg[6] = (byte)(increment >> 16); - msg[7] = (byte)(increment >> 8); - msg[8] = (byte)(increment); - - if (c.closeMessageSent == false) - tm.sendMessage(msg); - } - } - - return copylen; - } - - public void msgChannelData(byte[] msg, int msglen) throws IOException { - if (msglen <= 9) - throw new IOException("SSH_MSG_CHANNEL_DATA message has wrong size (" + msglen + ")"); - - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - int len = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_DATA message for non-existent channel " + id); - - if (len != (msglen - 9)) - throw new IOException("SSH_MSG_CHANNEL_DATA message has wrong len (calculated " + (msglen - 9) + ", got " - + len + ")"); - - if (log.isEnabled()) - log.log(80, "Got SSH_MSG_CHANNEL_DATA (channel " + id + ", " + len + ")"); - - synchronized (c) { - if (c.state == Channel.STATE_CLOSED) - return; // ignore - - if (c.state != Channel.STATE_OPEN) - throw new IOException("Got SSH_MSG_CHANNEL_DATA, but channel is not in correct state (" + c.state + ")"); - - if (c.localWindow < len) - throw new IOException("Remote sent too much data, does not fit into window."); - - c.localWindow -= len; - System.arraycopy(msg, 9, c.stdoutBuffer, c.stdoutWritepos, len); - c.stdoutWritepos += len; - c.notifyAll(); - } - } - - public void msgChannelWindowAdjust(byte[] msg, int msglen) throws IOException { - if (msglen != 9) - throw new IOException("SSH_MSG_CHANNEL_WINDOW_ADJUST message has wrong size (" + msglen + ")"); - - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - int windowChange = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_WINDOW_ADJUST message for non-existent channel " + id); - - synchronized (c) { - final long huge = 0xFFFFffffL; /* 2^32 - 1 */ - c.remoteWindow += (windowChange & huge); /* avoid sign extension */ - - /* TODO - is this a good heuristic? */ - - if ((c.remoteWindow > huge)) - c.remoteWindow = huge; - - c.notifyAll(); - } - - if (log.isEnabled()) - log.log(80, "Got SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + id + ", " + windowChange + ")"); - } - - public void msgChannelOpen(byte[] msg, int msglen) throws IOException { - TypesReader tr = new TypesReader(msg, 0, msglen); - tr.readByte(); // skip packet type - String channelType = tr.readString(); - int remoteID = tr.readUINT32(); /* sender channel */ - int remoteWindow = tr.readUINT32(); /* initial window size */ - int remoteMaxPacketSize = tr.readUINT32(); /* maximum packet size */ - - if ("x11".equals(channelType)) { - synchronized (x11_magic_cookies) { - /* If we did not request X11 forwarding, then simply ignore this bogus request. */ - if (x11_magic_cookies.size() == 0) { - PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, - Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, "X11 forwarding not activated", ""); - tm.sendAsynchronousMessage(pcof.getPayload()); - - if (log.isEnabled()) - log.log(20, "Unexpected X11 request, denying it!"); - - return; - } - } - - String remoteOriginatorAddress = tr.readString(); - int remoteOriginatorPort = tr.readUINT32(); - Channel c = new Channel(this); - - synchronized (c) { - c.remoteID = remoteID; - c.remoteWindow = remoteWindow & 0xFFFFffffL; /* properly convert UINT32 to long */ - c.remoteMaxPacketSize = remoteMaxPacketSize; - c.localID = addChannel(c); - } - - /* - * The open confirmation message will be sent from another thread - */ - RemoteX11AcceptThread rxat = new RemoteX11AcceptThread(c, remoteOriginatorAddress, remoteOriginatorPort); - rxat.setDaemon(true); - rxat.start(); - return; - } - - if ("forwarded-tcpip".equals(channelType)) { - String remoteConnectedAddress = tr.readString(); /* address that was connected */ - int remoteConnectedPort = tr.readUINT32(); /* port that was connected */ - String remoteOriginatorAddress = tr.readString(); /* originator IP address */ - int remoteOriginatorPort = tr.readUINT32(); /* originator port */ - RemoteForwardingData rfd = null; - - synchronized (remoteForwardings) { - rfd = remoteForwardings.get(Integer.valueOf(remoteConnectedPort)); - } - - if (rfd == null) { - PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, - Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, - "No thanks, unknown port in forwarded-tcpip request", ""); - /* Always try to be polite. */ - tm.sendAsynchronousMessage(pcof.getPayload()); - - if (log.isEnabled()) - log.log(20, "Unexpected forwarded-tcpip request, denying it!"); - - return; - } - - Channel c = new Channel(this); - - synchronized (c) { - c.remoteID = remoteID; - c.remoteWindow = remoteWindow & 0xFFFFffffL; /* convert UINT32 to long */ - c.remoteMaxPacketSize = remoteMaxPacketSize; - c.localID = addChannel(c); - } - - /* - * The open confirmation message will be sent from another thread. - */ - RemoteAcceptThread rat = new RemoteAcceptThread(c, remoteConnectedAddress, remoteConnectedPort, - remoteOriginatorAddress, remoteOriginatorPort, rfd.targetAddress, rfd.targetPort); - rat.setDaemon(true); - rat.start(); - return; - } - - if ("auth-agent@openssh.com".equals(channelType)) { - Channel c = new Channel(this); - - synchronized (c) { - c.remoteID = remoteID; - c.remoteWindow = remoteWindow & 0xFFFFffffL; /* properly convert UINT32 to long */ - c.remoteMaxPacketSize = remoteMaxPacketSize; - c.localID = addChannel(c); - } - - AuthAgentForwardThread aat = new AuthAgentForwardThread(c, authAgent); - aat.setDaemon(true); - aat.start(); - return; - } - - /* Tell the server that we have no idea what it is talking about */ - PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, Packets.SSH_OPEN_UNKNOWN_CHANNEL_TYPE, - "Unknown channel type", ""); - tm.sendAsynchronousMessage(pcof.getPayload()); - - if (log.isEnabled()) - log.log(20, "The peer tried to open an unsupported channel type (" + channelType + ")"); - } - - public void msgChannelRequest(byte[] msg, int msglen) throws IOException { - TypesReader tr = new TypesReader(msg, 0, msglen); - tr.readByte(); // skip packet type - int id = tr.readUINT32(); - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_REQUEST message for non-existent channel " + id); - - String type = tr.readString("US-ASCII"); - boolean wantReply = tr.readBoolean(); - - if (log.isEnabled()) - log.log(80, "Got SSH_MSG_CHANNEL_REQUEST (channel " + id + ", '" + type + "')"); - - if (type.equals("exit-status")) { - if (wantReply != false) - throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message, 'want reply' is true"); - - int exit_status = tr.readUINT32(); - - if (tr.remain() != 0) - throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); - - synchronized (c) { - c.exit_status = Integer.valueOf(exit_status); - c.notifyAll(); - } - - if (log.isEnabled()) - log.log(50, "Got EXIT STATUS (channel " + id + ", status " + exit_status + ")"); - - return; - } - - if (type.equals("exit-signal")) { - if (wantReply != false) - throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message, 'want reply' is true"); - - String signame = tr.readString("US-ASCII"); - tr.readBoolean(); - tr.readString(); - tr.readString(); - - if (tr.remain() != 0) - throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); - - synchronized (c) { - c.exit_signal = signame; - c.notifyAll(); - } - - if (log.isEnabled()) - log.log(50, "Got EXIT SIGNAL (channel " + id + ", signal " + signame + ")"); - - return; - } - - /* We simply ignore unknown channel requests, however, if the server wants a reply, - * then we signal that we have no idea what it is about. - */ - - if (wantReply) { - byte[] reply = new byte[5]; - reply[0] = Packets.SSH_MSG_CHANNEL_FAILURE; - reply[1] = (byte)(c.remoteID >> 24); - reply[2] = (byte)(c.remoteID >> 16); - reply[3] = (byte)(c.remoteID >> 8); - reply[4] = (byte)(c.remoteID); - tm.sendAsynchronousMessage(reply); - } - - if (log.isEnabled()) - log.log(50, "Channel request '" + type + "' is not known, ignoring it"); - } - - public void msgChannelEOF(byte[] msg, int msglen) throws IOException { - if (msglen != 5) - throw new IOException("SSH_MSG_CHANNEL_EOF message has wrong size (" + msglen + ")"); - - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_EOF message for non-existent channel " + id); - - synchronized (c) { - c.EOF = true; - c.notifyAll(); - } - - if (log.isEnabled()) - log.log(50, "Got SSH_MSG_CHANNEL_EOF (channel " + id + ")"); - } - - public void msgChannelClose(byte[] msg, int msglen) throws IOException { - if (msglen != 5) - throw new IOException("SSH_MSG_CHANNEL_CLOSE message has wrong size (" + msglen + ")"); - - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_CLOSE message for non-existent channel " + id); - - synchronized (c) { - c.EOF = true; - c.state = Channel.STATE_CLOSED; - c.setReasonClosed("Close requested by remote"); - c.closeMessageRecv = true; - removeChannel(c.localID); - c.notifyAll(); - } - - if (log.isEnabled()) - log.log(50, "Got SSH_MSG_CHANNEL_CLOSE (channel " + id + ")"); - } - - public void msgChannelSuccess(byte[] msg, int msglen) throws IOException { - if (msglen != 5) - throw new IOException("SSH_MSG_CHANNEL_SUCCESS message has wrong size (" + msglen + ")"); - - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_SUCCESS message for non-existent channel " + id); - - synchronized (c) { - c.successCounter++; - c.notifyAll(); - } - - if (log.isEnabled()) - log.log(80, "Got SSH_MSG_CHANNEL_SUCCESS (channel " + id + ")"); - } - - public void msgChannelFailure(byte[] msg, int msglen) throws IOException { - if (msglen != 5) - throw new IOException("SSH_MSG_CHANNEL_FAILURE message has wrong size (" + msglen + ")"); - - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_FAILURE message for non-existent channel " + id); - - synchronized (c) { - c.failedCounter++; - c.notifyAll(); - } - - if (log.isEnabled()) - log.log(50, "Got SSH_MSG_CHANNEL_FAILURE (channel " + id + ")"); - } - - public void msgChannelOpenConfirmation(byte[] msg, int msglen) throws IOException { - PacketChannelOpenConfirmation sm = new PacketChannelOpenConfirmation(msg, 0, msglen); - Channel c = getChannel(sm.recipientChannelID); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for non-existent channel " - + sm.recipientChannelID); - - synchronized (c) { - if (c.state != Channel.STATE_OPENING) - throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for channel " - + sm.recipientChannelID); - - c.remoteID = sm.senderChannelID; - c.remoteWindow = sm.initialWindowSize & 0xFFFFffffL; /* convert UINT32 to long */ - c.remoteMaxPacketSize = sm.maxPacketSize; - c.state = Channel.STATE_OPEN; - c.notifyAll(); - } - - if (log.isEnabled()) - log.log(50, "Got SSH_MSG_CHANNEL_OPEN_CONFIRMATION (channel " + sm.recipientChannelID + " / remote: " - + sm.senderChannelID + ")"); - } - - public void msgChannelOpenFailure(byte[] msg, int msglen) throws IOException { - if (msglen < 5) - throw new IOException("SSH_MSG_CHANNEL_OPEN_FAILURE message has wrong size (" + msglen + ")"); - - TypesReader tr = new TypesReader(msg, 0, msglen); - tr.readByte(); // skip packet type - int id = tr.readUINT32(); /* sender channel */ - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE message for non-existent channel " + id); - - int reasonCode = tr.readUINT32(); - String description = tr.readString("UTF-8"); - String reasonCodeSymbolicName = null; - - switch (reasonCode) { - case 1: - reasonCodeSymbolicName = "SSH_OPEN_ADMINISTRATIVELY_PROHIBITED"; - break; - - case 2: - reasonCodeSymbolicName = "SSH_OPEN_CONNECT_FAILED"; - break; - - case 3: - reasonCodeSymbolicName = "SSH_OPEN_UNKNOWN_CHANNEL_TYPE"; - break; - - case 4: - reasonCodeSymbolicName = "SSH_OPEN_RESOURCE_SHORTAGE"; - break; - - default: - reasonCodeSymbolicName = "UNKNOWN REASON CODE (" + reasonCode + ")"; - } - - StringBuffer descriptionBuffer = new StringBuffer(); - descriptionBuffer.append(description); - - for (int i = 0; i < descriptionBuffer.length(); i++) { - char cc = descriptionBuffer.charAt(i); - - if ((cc >= 32) && (cc <= 126)) - continue; - - descriptionBuffer.setCharAt(i, '\uFFFD'); - } - - synchronized (c) { - c.EOF = true; - c.state = Channel.STATE_CLOSED; - c.setReasonClosed("The server refused to open the channel (" + reasonCodeSymbolicName + ", '" - + descriptionBuffer.toString() + "')"); - c.notifyAll(); - } - - if (log.isEnabled()) - log.log(50, "Got SSH_MSG_CHANNEL_OPEN_FAILURE (channel " + id + ")"); - } - - public void msgGlobalRequest(byte[] msg, int msglen) throws IOException { - /* Currently we do not support any kind of global request */ - TypesReader tr = new TypesReader(msg, 0, msglen); - tr.readByte(); // skip packet type - String requestName = tr.readString(); - boolean wantReply = tr.readBoolean(); - - if (wantReply) { - byte[] reply_failure = new byte[1]; - reply_failure[0] = Packets.SSH_MSG_REQUEST_FAILURE; - tm.sendAsynchronousMessage(reply_failure); - } - - /* We do not clean up the requestName String - that is OK for debug */ - - if (log.isEnabled()) - log.log(80, "Got SSH_MSG_GLOBAL_REQUEST (" + requestName + ")"); - } - - public void msgGlobalSuccess() throws IOException { - synchronized (channels) { - globalSuccessCounter++; - channels.notifyAll(); - } - - if (log.isEnabled()) - log.log(80, "Got SSH_MSG_REQUEST_SUCCESS"); - } - - public void msgGlobalFailure() throws IOException { - synchronized (channels) { - globalFailedCounter++; - channels.notifyAll(); - } - - if (log.isEnabled()) - log.log(80, "Got SSH_MSG_REQUEST_FAILURE"); - } - - public void handleMessage(byte[] msg, int msglen) throws IOException { - if (msg == null) { - if (log.isEnabled()) - log.log(50, "HandleMessage: got shutdown"); - - synchronized (listenerThreads) { - for (int i = 0; i < listenerThreads.size(); i++) { - IChannelWorkerThread lat = listenerThreads.elementAt(i); - lat.stopWorking(); - } - - listenerThreadsAllowed = false; - } - - synchronized (channels) { - shutdown = true; - - for (int i = 0; i < channels.size(); i++) { - Channel c = channels.elementAt(i); - - synchronized (c) { - c.EOF = true; - c.state = Channel.STATE_CLOSED; - c.setReasonClosed("The connection is being shutdown"); - c.closeMessageRecv = true; /* - * You never know, perhaps - * we are waiting for a - * pending close message - * from the server... - */ - c.notifyAll(); - } - } - - /* Works with J2ME */ - channels.setSize(0); - channels.trimToSize(); - channels.notifyAll(); /* Notify global response waiters */ - return; - } - } - - switch (msg[0]) { - case Packets.SSH_MSG_CHANNEL_OPEN_CONFIRMATION: - msgChannelOpenConfirmation(msg, msglen); - break; - - case Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST: - msgChannelWindowAdjust(msg, msglen); - break; - - case Packets.SSH_MSG_CHANNEL_DATA: - msgChannelData(msg, msglen); - break; - - case Packets.SSH_MSG_CHANNEL_EXTENDED_DATA: - msgChannelExtendedData(msg, msglen); - break; - - case Packets.SSH_MSG_CHANNEL_REQUEST: - msgChannelRequest(msg, msglen); - break; - - case Packets.SSH_MSG_CHANNEL_EOF: - msgChannelEOF(msg, msglen); - break; - - case Packets.SSH_MSG_CHANNEL_OPEN: - msgChannelOpen(msg, msglen); - break; - - case Packets.SSH_MSG_CHANNEL_CLOSE: - msgChannelClose(msg, msglen); - break; - - case Packets.SSH_MSG_CHANNEL_SUCCESS: - msgChannelSuccess(msg, msglen); - break; - - case Packets.SSH_MSG_CHANNEL_FAILURE: - msgChannelFailure(msg, msglen); - break; - - case Packets.SSH_MSG_CHANNEL_OPEN_FAILURE: - msgChannelOpenFailure(msg, msglen); - break; - - case Packets.SSH_MSG_GLOBAL_REQUEST: - msgGlobalRequest(msg, msglen); - break; - - case Packets.SSH_MSG_REQUEST_SUCCESS: - msgGlobalSuccess(); - break; - - case Packets.SSH_MSG_REQUEST_FAILURE: - msgGlobalFailure(); - break; - - default: - throw new IOException("Cannot handle unknown channel message " + (msg[0] & 0xff)); - } - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/channel/ChannelOutputStream.java --- a/src/com/trilead/ssh2/channel/ChannelOutputStream.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,62 +0,0 @@ -package com.trilead.ssh2.channel; - -import java.io.IOException; -import java.io.OutputStream; - -/** - * ChannelOutputStream. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: ChannelOutputStream.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -public final class ChannelOutputStream extends OutputStream { - Channel c; - - private byte[] writeBuffer; - - boolean isClosed = false; - - ChannelOutputStream(Channel c) { - this.c = c; - writeBuffer = new byte[1]; - } - - public void write(int b) throws IOException { - writeBuffer[0] = (byte) b; - write(writeBuffer, 0, 1); - } - - public void close() throws IOException { - if (isClosed == false) { - isClosed = true; - c.cm.sendEOF(c); - } - } - - public void flush() throws IOException { - if (isClosed) - throw new IOException("This OutputStream is closed."); - - /* This is a no-op, since this stream is unbuffered */ - } - - public void write(byte[] b, int off, int len) throws IOException { - if (isClosed) - throw new IOException("This OutputStream is closed."); - - if (b == null) - throw new NullPointerException(); - - if ((off < 0) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) || (off > b.length)) - throw new IndexOutOfBoundsException(); - - if (len == 0) - return; - - c.cm.sendData(c, b, off, len); - } - - public void write(byte[] b) throws IOException { - write(b, 0, b.length); - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/channel/DynamicAcceptThread.java --- a/src/com/trilead/ssh2/channel/DynamicAcceptThread.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,295 +0,0 @@ -/* - * 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 com.trilead.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) { - } - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/channel/IChannelWorkerThread.java --- a/src/com/trilead/ssh2/channel/IChannelWorkerThread.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ - -package com.trilead.ssh2.channel; - -/** - * IChannelWorkerThread. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: IChannelWorkerThread.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -interface IChannelWorkerThread { - public void stopWorking(); -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/channel/LocalAcceptThread.java --- a/src/com/trilead/ssh2/channel/LocalAcceptThread.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,110 +0,0 @@ - -package com.trilead.ssh2.channel; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.ServerSocket; -import java.net.Socket; - -/** - * LocalAcceptThread. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: LocalAcceptThread.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -public class LocalAcceptThread extends Thread implements IChannelWorkerThread { - ChannelManager cm; - String host_to_connect; - int port_to_connect; - - final ServerSocket ss; - - public LocalAcceptThread(ChannelManager cm, int local_port, String host_to_connect, int port_to_connect) - throws IOException { - this.cm = cm; - this.host_to_connect = host_to_connect; - this.port_to_connect = port_to_connect; - ss = new ServerSocket(local_port); - } - - public LocalAcceptThread(ChannelManager cm, InetSocketAddress localAddress, String host_to_connect, - int port_to_connect) throws IOException { - this.cm = cm; - this.host_to_connect = host_to_connect; - this.port_to_connect = port_to_connect; - ss = new ServerSocket(); - ss.bind(localAddress); - } - - public void run() { - try { - cm.registerThread(this); - } - catch (IOException e) { - stopWorking(); - return; - } - - while (true) { - Socket s = null; - - try { - s = ss.accept(); - } - catch (IOException e) { - stopWorking(); - return; - } - - Channel cn = null; - StreamForwarder r2l = null; - StreamForwarder l2r = null; - - try { - /* This may fail, e.g., if the remote port is closed (in optimistic terms: not open yet) */ - cn = cm.openDirectTCPIPChannel(host_to_connect, port_to_connect, s.getInetAddress().getHostAddress(), s - .getPort()); - } - catch (IOException e) { - /* Simply close the local socket and wait for the next incoming connection */ - try { - s.close(); - } - catch (IOException ignore) { - } - - continue; - } - - try { - r2l = new StreamForwarder(cn, null, s, cn.stdoutStream, s.getOutputStream(), "RemoteToLocal"); - l2r = new StreamForwarder(cn, r2l, s, s.getInputStream(), 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) { - } - - continue; - } - - r2l.setDaemon(true); - l2r.setDaemon(true); - r2l.start(); - l2r.start(); - } - } - - public void stopWorking() { - try { - /* This will lead to an IOException in the ss.accept() call */ - ss.close(); - } - catch (IOException e) { - } - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/channel/RemoteAcceptThread.java --- a/src/com/trilead/ssh2/channel/RemoteAcceptThread.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,87 +0,0 @@ - -package com.trilead.ssh2.channel; - -import java.io.IOException; -import java.net.Socket; - -import com.trilead.ssh2.log.Logger; - - -/** - * RemoteAcceptThread. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: RemoteAcceptThread.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -public class RemoteAcceptThread extends Thread { - private static final Logger log = Logger.getLogger(RemoteAcceptThread.class); - - Channel c; - - String remoteConnectedAddress; - int remoteConnectedPort; - String remoteOriginatorAddress; - int remoteOriginatorPort; - String targetAddress; - int targetPort; - - Socket s; - - public RemoteAcceptThread(Channel c, String remoteConnectedAddress, int remoteConnectedPort, - String remoteOriginatorAddress, int remoteOriginatorPort, String targetAddress, int targetPort) { - this.c = c; - this.remoteConnectedAddress = remoteConnectedAddress; - this.remoteConnectedPort = remoteConnectedPort; - this.remoteOriginatorAddress = remoteOriginatorAddress; - this.remoteOriginatorPort = remoteOriginatorPort; - this.targetAddress = targetAddress; - this.targetPort = targetPort; - - if (log.isEnabled()) - log.log(20, "RemoteAcceptThread: " + remoteConnectedAddress + "/" + remoteConnectedPort + ", R: " - + remoteOriginatorAddress + "/" + remoteOriginatorPort); - } - - public void run() { - try { - c.cm.sendOpenConfirmation(c); - s = new Socket(targetAddress, targetPort); - StreamForwarder r2l = new StreamForwarder(c, null, s, c.getStdoutStream(), s.getOutputStream(), - "RemoteToLocal"); - StreamForwarder l2r = new StreamForwarder(c, null, null, s.getInputStream(), c.getStdinStream(), - "LocalToRemote"); - /* No need to start two threads, one can be executed in the current thread */ - r2l.setDaemon(true); - r2l.start(); - l2r.run(); - - while (r2l.isAlive()) { - try { - r2l.join(); - } - catch (InterruptedException e) { - } - } - - /* If the channel is already closed, then this is a no-op */ - c.cm.closeChannel(c, "EOF on both streams reached.", true); - s.close(); - } - catch (IOException e) { - log.log(50, "IOException in proxy code: " + e.getMessage()); - - try { - c.cm.closeChannel(c, "IOException in proxy code (" + e.getMessage() + ")", true); - } - catch (IOException e1) { - } - - try { - if (s != null) - s.close(); - } - catch (IOException e1) { - } - } - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/channel/RemoteForwardingData.java --- a/src/com/trilead/ssh2/channel/RemoteForwardingData.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ - -package com.trilead.ssh2.channel; - -/** - * RemoteForwardingData. Data about a requested remote forwarding. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: RemoteForwardingData.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -public class RemoteForwardingData { - public String bindAddress; - public int bindPort; - - String targetAddress; - int targetPort; -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/channel/RemoteX11AcceptThread.java --- a/src/com/trilead/ssh2/channel/RemoteX11AcceptThread.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,205 +0,0 @@ - -package com.trilead.ssh2.channel; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Socket; - -import com.trilead.ssh2.log.Logger; - - -/** - * RemoteX11AcceptThread. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: RemoteX11AcceptThread.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ - */ -public class RemoteX11AcceptThread extends Thread { - private static final Logger log = Logger.getLogger(RemoteX11AcceptThread.class); - - Channel c; - - String remoteOriginatorAddress; - int remoteOriginatorPort; - - Socket s; - - public RemoteX11AcceptThread(Channel c, String remoteOriginatorAddress, int remoteOriginatorPort) { - this.c = c; - this.remoteOriginatorAddress = remoteOriginatorAddress; - this.remoteOriginatorPort = remoteOriginatorPort; - } - - public void run() { - try { - /* Send Open Confirmation */ - c.cm.sendOpenConfirmation(c); - /* Read startup packet from client */ - OutputStream remote_os = c.getStdinStream(); - InputStream remote_is = c.getStdoutStream(); - /* The following code is based on the protocol description given in: - * Scheifler/Gettys, - * X Windows System: Core and Extension Protocols: - * X Version 11, Releases 6 and 6.1 ISBN 1-55558-148-X - */ - /* - * Client startup: - * - * 1 0X42 MSB first/0x6c lSB first - byteorder - * 1 - unused - * 2 card16 - protocol-major-version - * 2 card16 - protocol-minor-version - * 2 n - lenght of authorization-protocol-name - * 2 d - lenght of authorization-protocol-data - * 2 - unused - * string8 - authorization-protocol-name - * p - unused, p=pad(n) - * string8 - authorization-protocol-data - * q - unused, q=pad(d) - * - * pad(X) = (4 - (X mod 4)) mod 4 - * - * Server response: - * - * 1 (0 failed, 2 authenticate, 1 success) - * ... - * - */ - /* Later on we will simply forward the first 6 header bytes to the "real" X11 server */ - byte[] header = new byte[6]; - - if (remote_is.read(header) != 6) - throw new IOException("Unexpected EOF on X11 startup!"); - - if ((header[0] != 0x42) && (header[0] != 0x6c)) // 0x42 MSB first, 0x6C LSB first - throw new IOException("Unknown endian format in X11 message!"); - - /* Yes, I came up with this myself - shall I file an application for a patent? =) */ - int idxMSB = (header[0] == 0x42) ? 0 : 1; - /* Read authorization data header */ - byte[] auth_buff = new byte[6]; - - if (remote_is.read(auth_buff) != 6) - throw new IOException("Unexpected EOF on X11 startup!"); - - int authProtocolNameLength = ((auth_buff[idxMSB] & 0xff) << 8) | (auth_buff[1 - idxMSB] & 0xff); - int authProtocolDataLength = ((auth_buff[2 + idxMSB] & 0xff) << 8) | (auth_buff[3 - idxMSB] & 0xff); - - if ((authProtocolNameLength > 256) || (authProtocolDataLength > 256)) - throw new IOException("Buggy X11 authorization data"); - - int authProtocolNamePadding = ((4 - (authProtocolNameLength % 4)) % 4); - int authProtocolDataPadding = ((4 - (authProtocolDataLength % 4)) % 4); - byte[] authProtocolName = new byte[authProtocolNameLength]; - byte[] authProtocolData = new byte[authProtocolDataLength]; - byte[] paddingBuffer = new byte[4]; - - if (remote_is.read(authProtocolName) != authProtocolNameLength) - throw new IOException("Unexpected EOF on X11 startup! (authProtocolName)"); - - if (remote_is.read(paddingBuffer, 0, authProtocolNamePadding) != authProtocolNamePadding) - throw new IOException("Unexpected EOF on X11 startup! (authProtocolNamePadding)"); - - if (remote_is.read(authProtocolData) != authProtocolDataLength) - throw new IOException("Unexpected EOF on X11 startup! (authProtocolData)"); - - if (remote_is.read(paddingBuffer, 0, authProtocolDataPadding) != authProtocolDataPadding) - throw new IOException("Unexpected EOF on X11 startup! (authProtocolDataPadding)"); - - if ("MIT-MAGIC-COOKIE-1".equals(new String(authProtocolName, "ISO-8859-1")) == false) - throw new IOException("Unknown X11 authorization protocol!"); - - if (authProtocolDataLength != 16) - throw new IOException("Wrong data length for X11 authorization data!"); - - StringBuffer tmp = new StringBuffer(32); - - for (int i = 0; i < authProtocolData.length; i++) { - String digit2 = Integer.toHexString(authProtocolData[i] & 0xff); - tmp.append((digit2.length() == 2) ? digit2 : "0" + digit2); - } - - String hexEncodedFakeCookie = tmp.toString(); - - /* Order is very important here - it may be that a certain x11 forwarding - * gets disabled right in the moment when we check and register our connection - * */ - - synchronized (c) { - /* Please read the comment in Channel.java */ - c.hexX11FakeCookie = hexEncodedFakeCookie; - } - - /* Now check our fake cookie directory to see if we produced this cookie */ - X11ServerData sd = c.cm.checkX11Cookie(hexEncodedFakeCookie); - - if (sd == null) - throw new IOException("Invalid X11 cookie received."); - - /* If the session which corresponds to this cookie is closed then we will - * detect this: the session's close code will close all channels - * with the session's assigned x11 fake cookie. - */ - s = new Socket(sd.hostname, sd.port); - OutputStream x11_os = s.getOutputStream(); - InputStream x11_is = s.getInputStream(); - /* Now we are sending the startup packet to the real X11 server */ - x11_os.write(header); - - if (sd.x11_magic_cookie == null) { - byte[] emptyAuthData = new byte[6]; - /* empty auth data, hopefully you are connecting to localhost =) */ - x11_os.write(emptyAuthData); - } - else { - if (sd.x11_magic_cookie.length != 16) - throw new IOException("The real X11 cookie has an invalid length!"); - - /* send X11 cookie specified by client */ - x11_os.write(auth_buff); - x11_os.write(authProtocolName); /* re-use */ - x11_os.write(paddingBuffer, 0, authProtocolNamePadding); - x11_os.write(sd.x11_magic_cookie); - x11_os.write(paddingBuffer, 0, authProtocolDataPadding); - } - - x11_os.flush(); - /* Start forwarding traffic */ - StreamForwarder r2l = new StreamForwarder(c, null, s, remote_is, x11_os, "RemoteToX11"); - StreamForwarder l2r = new StreamForwarder(c, null, null, x11_is, remote_os, "X11ToRemote"); - /* No need to start two threads, one can be executed in the current thread */ - r2l.setDaemon(true); - r2l.start(); - l2r.run(); - - while (r2l.isAlive()) { - try { - r2l.join(); - } - catch (InterruptedException e) { - } - } - - /* If the channel is already closed, then this is a no-op */ - c.cm.closeChannel(c, "EOF on both X11 streams reached.", true); - s.close(); - } - catch (IOException e) { - log.log(50, "IOException in X11 proxy code: " + e.getMessage()); - - try { - c.cm.closeChannel(c, "IOException in X11 proxy code (" + e.getMessage() + ")", true); - } - catch (IOException e1) { - } - - try { - if (s != null) - s.close(); - } - catch (IOException e1) { - } - } - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/channel/StreamForwarder.java --- a/src/com/trilead/ssh2/channel/StreamForwarder.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,95 +0,0 @@ - -package com.trilead.ssh2.channel; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Socket; - -/** - * A StreamForwarder forwards data between two given streams. - * If two StreamForwarder threads are used (one for each direction) - * then one can be configured to shutdown the underlying channel/socket - * if both threads have finished forwarding (EOF). - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: StreamForwarder.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -public class StreamForwarder extends Thread { - final OutputStream os; - final InputStream is; - final byte[] buffer = new byte[Channel.CHANNEL_BUFFER_SIZE]; - final Channel c; - final StreamForwarder sibling; - final Socket s; - final String mode; - - StreamForwarder(Channel c, StreamForwarder sibling, Socket s, InputStream is, OutputStream os, String mode) - throws IOException { - this.is = is; - this.os = os; - this.mode = mode; - this.c = c; - this.sibling = sibling; - this.s = s; - } - - public void run() { - try { - while (true) { - int len = is.read(buffer); - - if (len <= 0) - break; - - os.write(buffer, 0, len); - os.flush(); - } - } - catch (IOException ignore) { - try { - c.cm.closeChannel(c, "Closed due to exception in StreamForwarder (" + mode + "): " - + ignore.getMessage(), true); - } - catch (IOException e) { - } - } - finally { - try { - os.close(); - } - catch (IOException e1) { - } - - try { - is.close(); - } - catch (IOException e2) { - } - - if (sibling != null) { - while (sibling.isAlive()) { - try { - sibling.join(); - } - catch (InterruptedException e) { - } - } - - try { - c.cm.closeChannel(c, "StreamForwarder (" + mode + ") is cleaning up the connection", true); - } - catch (IOException e3) { - } - } - - if (s != null) { - try { - s.close(); - } - catch (IOException e1) { - } - } - } - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/channel/X11ServerData.java --- a/src/com/trilead/ssh2/channel/X11ServerData.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ - -package com.trilead.ssh2.channel; - -/** - * X11ServerData. Data regarding an x11 forwarding target. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: X11ServerData.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - * - */ -public class X11ServerData { - public String hostname; - public int port; - public byte[] x11_magic_cookie; /* not the remote (fake) one, the local (real) one */ -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/compression/CompressionFactory.java --- a/src/com/trilead/ssh2/compression/CompressionFactory.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,88 +0,0 @@ -/* - * 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 com.trilead.ssh2.compression; - -import java.util.Vector; - -/** - * @author Kenny Root - * - */ -public class CompressionFactory { - static class CompressorEntry { - String type; - String compressorClass; - - public CompressorEntry(String type, String compressorClass) { - this.type = type; - this.compressorClass = compressorClass; - } - } - - static Vector compressors = new Vector(); - - static { - /* Higher Priority First */ - compressors.addElement(new CompressorEntry("zlib", "com.trilead.ssh2.compression.Zlib")); - compressors.addElement(new CompressorEntry("zlib@openssh.com", "com.trilead.ssh2.compression.ZlibOpenSSH")); - compressors.addElement(new CompressorEntry("none", "")); - } - - public static String[] getDefaultCompressorList() { - String list[] = new String[compressors.size()]; - - for (int i = 0; i < compressors.size(); i++) { - CompressorEntry ce = compressors.elementAt(i); - list[i] = new String(ce.type); - } - - return list; - } - - public static void checkCompressorList(String[] compressorCandidates) { - for (int i = 0; i < compressorCandidates.length; i++) - getEntry(compressorCandidates[i]); - } - - public static ICompressor createCompressor(String type) { - try { - CompressorEntry ce = getEntry(type); - - if ("".equals(ce.compressorClass)) - return null; - - Class cc = Class.forName(ce.compressorClass); - ICompressor cmp = (ICompressor) cc.newInstance(); - return cmp; - } - catch (Exception e) { - throw new IllegalArgumentException("Cannot instantiate " + type); - } - } - - private static CompressorEntry getEntry(String type) { - for (int i = 0; i < compressors.size(); i++) { - CompressorEntry ce = compressors.elementAt(i); - - if (ce.type.equals(type)) - return ce; - } - - throw new IllegalArgumentException("Unkown algorithm " + type); - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/compression/ICompressor.java --- a/src/com/trilead/ssh2/compression/ICompressor.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,32 +0,0 @@ -/* - * 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 com.trilead.ssh2.compression; - -/** - * @author Kenny Root - * - */ -public interface ICompressor { - int getBufferSize(); - - int compress(byte[] buf, int start, int len, byte[] output); - - byte[] uncompress(byte[] buf, int start, int[] len); - - boolean canCompressPreauth(); -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/compression/Zlib.java --- a/src/com/trilead/ssh2/compression/Zlib.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,131 +0,0 @@ -/* - * 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 com.trilead.ssh2.compression; - -import com.jcraft.jzlib.JZlib; -import com.jcraft.jzlib.ZStream; - -/** - * @author Kenny Root - * - */ -public class Zlib implements ICompressor { - static private final int DEFAULT_BUF_SIZE = 4096; - static private final int LEVEL = 5; - - private ZStream deflate; - private byte[] deflate_tmpbuf; - - private ZStream inflate; - private byte[] inflate_tmpbuf; - private byte[] inflated_buf; - - public Zlib() { - deflate = new ZStream(); - inflate = new ZStream(); - deflate.deflateInit(LEVEL); - inflate.inflateInit(); - deflate_tmpbuf = new byte[DEFAULT_BUF_SIZE]; - inflate_tmpbuf = new byte[DEFAULT_BUF_SIZE]; - inflated_buf = new byte[DEFAULT_BUF_SIZE]; - } - - public boolean canCompressPreauth() { - return true; - } - - public int getBufferSize() { - return DEFAULT_BUF_SIZE; - } - - public int compress(byte[] buf, int start, int len, byte[] output) { - deflate.next_in = buf; - deflate.next_in_index = start; - deflate.avail_in = len - start; - - if ((buf.length + 1024) > deflate_tmpbuf.length) { - deflate_tmpbuf = new byte[buf.length + 1024]; - } - - deflate.next_out = deflate_tmpbuf; - deflate.next_out_index = 0; - deflate.avail_out = output.length; - - if (deflate.deflate(JZlib.Z_PARTIAL_FLUSH) != JZlib.Z_OK) { - System.err.println("compress: compression failure"); - } - - if (deflate.avail_in > 0) { - System.err.println("compress: deflated data too large"); - } - - int outputlen = output.length - deflate.avail_out; - System.arraycopy(deflate_tmpbuf, 0, output, 0, outputlen); - return outputlen; - } - - public byte[] uncompress(byte[] buffer, int start, int[] length) { - int inflated_end = 0; - inflate.next_in = buffer; - inflate.next_in_index = start; - inflate.avail_in = length[0]; - - while (true) { - inflate.next_out = inflate_tmpbuf; - inflate.next_out_index = 0; - inflate.avail_out = DEFAULT_BUF_SIZE; - int status = inflate.inflate(JZlib.Z_PARTIAL_FLUSH); - - switch (status) { - case JZlib.Z_OK: - if (inflated_buf.length < inflated_end + DEFAULT_BUF_SIZE - - inflate.avail_out) { - byte[] foo = new byte[inflated_end + DEFAULT_BUF_SIZE - - inflate.avail_out]; - System.arraycopy(inflated_buf, 0, foo, 0, inflated_end); - inflated_buf = foo; - } - - System.arraycopy(inflate_tmpbuf, 0, inflated_buf, inflated_end, - DEFAULT_BUF_SIZE - inflate.avail_out); - inflated_end += (DEFAULT_BUF_SIZE - inflate.avail_out); - length[0] = inflated_end; - break; - - case JZlib.Z_BUF_ERROR: - if (inflated_end > buffer.length - start) { - byte[] foo = new byte[inflated_end + start]; - System.arraycopy(buffer, 0, foo, 0, start); - System.arraycopy(inflated_buf, 0, foo, start, inflated_end); - buffer = foo; - } - else { - System.arraycopy(inflated_buf, 0, buffer, start, - inflated_end); - } - - length[0] = inflated_end; - return buffer; - - default: - System.err.println("uncompress: inflate returnd " + status); - return null; - } - } - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/compression/ZlibOpenSSH.java --- a/src/com/trilead/ssh2/compression/ZlibOpenSSH.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -/* - * 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 com.trilead.ssh2.compression; - -/** - * Defines how zlib@openssh.org compression works. - * See - * http://www.openssh.org/txt/draft-miller-secsh-compression-delayed-00.txt - * compression is disabled until userauth has occurred. - * - * @author Matt Johnston - * - */ -public class ZlibOpenSSH extends Zlib { - - public boolean canCompressPreauth() { - return false; - } - -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/crypto/Base64.java --- a/src/com/trilead/ssh2/crypto/Base64.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,124 +0,0 @@ - -package com.trilead.ssh2.crypto; - -import java.io.CharArrayWriter; -import java.io.IOException; - -/** - * Basic Base64 Support. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: Base64.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -public class Base64 { - static final char[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); - - public static char[] encode(byte[] content) { - CharArrayWriter cw = new CharArrayWriter((4 * content.length) / 3); - int idx = 0; - int x = 0; - - for (int i = 0; i < content.length; i++) { - if (idx == 0) - x = (content[i] & 0xff) << 16; - else if (idx == 1) - x = x | ((content[i] & 0xff) << 8); - else - x = x | (content[i] & 0xff); - - idx++; - - if (idx == 3) { - cw.write(alphabet[x >> 18]); - cw.write(alphabet[(x >> 12) & 0x3f]); - cw.write(alphabet[(x >> 6) & 0x3f]); - cw.write(alphabet[x & 0x3f]); - idx = 0; - } - } - - if (idx == 1) { - cw.write(alphabet[x >> 18]); - cw.write(alphabet[(x >> 12) & 0x3f]); - cw.write('='); - cw.write('='); - } - - if (idx == 2) { - cw.write(alphabet[x >> 18]); - cw.write(alphabet[(x >> 12) & 0x3f]); - cw.write(alphabet[(x >> 6) & 0x3f]); - cw.write('='); - } - - return cw.toCharArray(); - } - - public static byte[] decode(char[] message) throws IOException { - byte buff[] = new byte[4]; - byte dest[] = new byte[message.length]; - int bpos = 0; - int destpos = 0; - - for (int i = 0; i < message.length; i++) { - int c = message[i]; - - if ((c == '\n') || (c == '\r') || (c == ' ') || (c == '\t')) - continue; - - if ((c >= 'A') && (c <= 'Z')) { - buff[bpos++] = (byte)(c - 'A'); - } - else if ((c >= 'a') && (c <= 'z')) { - buff[bpos++] = (byte)((c - 'a') + 26); - } - else if ((c >= '0') && (c <= '9')) { - buff[bpos++] = (byte)((c - '0') + 52); - } - else if (c == '+') { - buff[bpos++] = 62; - } - else if (c == '/') { - buff[bpos++] = 63; - } - else if (c == '=') { - buff[bpos++] = 64; - } - else { - throw new IOException("Illegal char in base64 code."); - } - - if (bpos == 4) { - bpos = 0; - - if (buff[0] == 64) - break; - - if (buff[1] == 64) - throw new IOException("Unexpected '=' in base64 code."); - - if (buff[2] == 64) { - int v = (((buff[0] & 0x3f) << 6) | ((buff[1] & 0x3f))); - dest[destpos++] = (byte)(v >> 4); - break; - } - else if (buff[3] == 64) { - int v = (((buff[0] & 0x3f) << 12) | ((buff[1] & 0x3f) << 6) | ((buff[2] & 0x3f))); - dest[destpos++] = (byte)(v >> 10); - dest[destpos++] = (byte)(v >> 2); - break; - } - else { - int v = (((buff[0] & 0x3f) << 18) | ((buff[1] & 0x3f) << 12) | ((buff[2] & 0x3f) << 6) | ((buff[3] & 0x3f))); - dest[destpos++] = (byte)(v >> 16); - dest[destpos++] = (byte)(v >> 8); - dest[destpos++] = (byte)(v); - } - } - } - - byte[] res = new byte[destpos]; - System.arraycopy(dest, 0, res, 0, destpos); - return res; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/crypto/CryptoWishList.java --- a/src/com/trilead/ssh2/crypto/CryptoWishList.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ - -package com.trilead.ssh2.crypto; - -import com.trilead.ssh2.compression.CompressionFactory; -import com.trilead.ssh2.crypto.cipher.BlockCipherFactory; -import com.trilead.ssh2.crypto.digest.MAC; -import com.trilead.ssh2.transport.KexManager; - - -/** - * CryptoWishList. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: CryptoWishList.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -public class CryptoWishList { - public String[] kexAlgorithms = KexManager.getDefaultKexAlgorithmList(); - public String[] serverHostKeyAlgorithms = KexManager.getDefaultServerHostkeyAlgorithmList(); - public String[] c2s_enc_algos = BlockCipherFactory.getDefaultCipherList(); - public String[] s2c_enc_algos = BlockCipherFactory.getDefaultCipherList(); - public String[] c2s_mac_algos = MAC.getMacList(); - public String[] s2c_mac_algos = MAC.getMacList(); - public String[] c2s_comp_algos = CompressionFactory.getDefaultCompressorList(); - public String[] s2c_comp_algos = CompressionFactory.getDefaultCompressorList(); -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/crypto/KeyMaterial.java --- a/src/com/trilead/ssh2/crypto/KeyMaterial.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,71 +0,0 @@ - -package com.trilead.ssh2.crypto; - - -import java.math.BigInteger; - -import com.trilead.ssh2.crypto.digest.HashForSSH2Types; - -/** - * Establishes key material for iv/key/mac (both directions). - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: KeyMaterial.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -public class KeyMaterial { - public byte[] initial_iv_client_to_server; - public byte[] initial_iv_server_to_client; - public byte[] enc_key_client_to_server; - public byte[] enc_key_server_to_client; - public byte[] integrity_key_client_to_server; - public byte[] integrity_key_server_to_client; - - private static byte[] calculateKey(HashForSSH2Types sh, BigInteger K, byte[] H, byte type, byte[] SessionID, - int keyLength) { - byte[] res = new byte[keyLength]; - int dglen = sh.getDigestLength(); - int numRounds = (keyLength + dglen - 1) / dglen; - byte[][] tmp = new byte[numRounds][]; - sh.reset(); - sh.updateBigInt(K); - sh.updateBytes(H); - sh.updateByte(type); - sh.updateBytes(SessionID); - tmp[0] = sh.getDigest(); - int off = 0; - int produced = Math.min(dglen, keyLength); - System.arraycopy(tmp[0], 0, res, off, produced); - keyLength -= produced; - off += produced; - - for (int i = 1; i < numRounds; i++) { - sh.updateBigInt(K); - sh.updateBytes(H); - - for (int j = 0; j < i; j++) - sh.updateBytes(tmp[j]); - - tmp[i] = sh.getDigest(); - produced = Math.min(dglen, keyLength); - System.arraycopy(tmp[i], 0, res, off, produced); - keyLength -= produced; - off += produced; - } - - return res; - } - - public static KeyMaterial create(String hashAlgo, byte[] H, BigInteger K, byte[] SessionID, int keyLengthCS, - int blockSizeCS, int macLengthCS, int keyLengthSC, int blockSizeSC, int macLengthSC) - throws IllegalArgumentException { - KeyMaterial km = new KeyMaterial(); - HashForSSH2Types sh = new HashForSSH2Types(hashAlgo); - km.initial_iv_client_to_server = calculateKey(sh, K, H, (byte) 'A', SessionID, blockSizeCS); - km.initial_iv_server_to_client = calculateKey(sh, K, H, (byte) 'B', SessionID, blockSizeSC); - km.enc_key_client_to_server = calculateKey(sh, K, H, (byte) 'C', SessionID, keyLengthCS); - km.enc_key_server_to_client = calculateKey(sh, K, H, (byte) 'D', SessionID, keyLengthSC); - km.integrity_key_client_to_server = calculateKey(sh, K, H, (byte) 'E', SessionID, macLengthCS); - km.integrity_key_server_to_client = calculateKey(sh, K, H, (byte) 'F', SessionID, macLengthSC); - return km; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/crypto/PEMDecoder.java --- a/src/com/trilead/ssh2/crypto/PEMDecoder.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,444 +0,0 @@ - -package com.trilead.ssh2.crypto; - -import java.io.BufferedReader; -import java.io.CharArrayReader; -import java.io.IOException; -import java.math.BigInteger; -import java.security.DigestException; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -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.RSAPrivateKeySpec; -import java.security.spec.RSAPublicKeySpec; - -import com.trilead.ssh2.crypto.cipher.AES; -import com.trilead.ssh2.crypto.cipher.BlockCipher; -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; - -/** - * PEM Support. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PEMDecoder.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ - */ -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; - - private static final int hexToInt(char c) { - if ((c >= 'a') && (c <= 'f')) { - return (c - 'a') + 10; - } - - if ((c >= 'A') && (c <= 'F')) { - return (c - 'A') + 10; - } - - if ((c >= '0') && (c <= '9')) { - return (c - '0'); - } - - throw new IllegalArgumentException("Need hex char"); - } - - private static byte[] hexToByteArray(String hex) { - if (hex == null) - throw new IllegalArgumentException("null argument"); - - if ((hex.length() % 2) != 0) - throw new IllegalArgumentException("Uneven string length in hex encoding."); - - byte decoded[] = new byte[hex.length() / 2]; - - for (int i = 0; i < decoded.length; i++) { - int hi = hexToInt(hex.charAt(i * 2)); - int lo = hexToInt(hex.charAt((i * 2) + 1)); - decoded[i] = (byte)(hi * 16 + lo); - } - - return decoded; - } - - private static byte[] generateKeyFromPasswordSaltWithMD5(byte[] password, byte[] salt, int keyLen) - throws IOException { - if (salt.length < 8) - throw new IllegalArgumentException("Salt needs to be at least 8 bytes for key generation."); - - MessageDigest md5; - - try { - md5 = MessageDigest.getInstance("MD5"); - } - catch (NoSuchAlgorithmException e) { - throw new IllegalArgumentException("VM does not support MD5", e); - } - - byte[] key = new byte[keyLen]; - byte[] tmp = new byte[md5.getDigestLength()]; - - while (true) { - md5.update(password, 0, password.length); - md5.update(salt, 0, 8); // ARGH we only use the first 8 bytes of the - // salt in this step. - // This took me two hours until I got AES-xxx running. - int copy = (keyLen < tmp.length) ? keyLen : tmp.length; - - try { - md5.digest(tmp, 0, tmp.length); - } - catch (DigestException e) { - IOException ex = new IOException("could not digest password"); - ex.initCause(e); - throw ex; - } - - System.arraycopy(tmp, 0, key, key.length - keyLen, copy); - keyLen -= copy; - - if (keyLen == 0) - return key; - - md5.update(tmp, 0, tmp.length); - } - } - - private static byte[] removePadding(byte[] buff, int blockSize) throws IOException { - /* Removes RFC 1423/PKCS #7 padding */ - int rfc_1423_padding = buff[buff.length - 1] & 0xff; - - if ((rfc_1423_padding < 1) || (rfc_1423_padding > blockSize)) - throw new IOException("Decrypted PEM has wrong padding, did you specify the correct password?"); - - for (int i = 2; i <= rfc_1423_padding; i++) { - if (buff[buff.length - i] != rfc_1423_padding) - throw new IOException("Decrypted PEM has wrong padding, did you specify the correct password?"); - } - - byte[] tmp = new byte[buff.length - rfc_1423_padding]; - System.arraycopy(buff, 0, tmp, 0, buff.length - rfc_1423_padding); - return tmp; - } - - public static final PEMStructure parsePEM(char[] pem) throws IOException { - PEMStructure ps = new PEMStructure(); - String line = null; - BufferedReader br = new BufferedReader(new CharArrayReader(pem)); - String endLine = null; - - while (true) { - line = br.readLine(); - - if (line == null) - throw new IOException("Invalid PEM structure, '-----BEGIN...' missing"); - - line = line.trim(); - - if (line.startsWith("-----BEGIN DSA PRIVATE KEY-----")) { - endLine = "-----END DSA PRIVATE KEY-----"; - ps.pemType = PEM_DSA_PRIVATE_KEY; - break; - } - - if (line.startsWith("-----BEGIN RSA PRIVATE KEY-----")) { - endLine = "-----END RSA PRIVATE KEY-----"; - ps.pemType = PEM_RSA_PRIVATE_KEY; - break; - } - - if (line.startsWith("-----BEGIN EC PRIVATE KEY-----")) { - endLine = "-----END EC PRIVATE KEY-----"; - ps.pemType = PEM_EC_PRIVATE_KEY; - break; - } - } - - while (true) { - line = br.readLine(); - - if (line == null) - throw new IOException("Invalid PEM structure, " + endLine + " missing"); - - line = line.trim(); - int sem_idx = line.indexOf(':'); - - if (sem_idx == -1) - break; - - String name = line.substring(0, sem_idx + 1); - String value = line.substring(sem_idx + 1); - String values[] = value.split(","); - - for (int i = 0; i < values.length; i++) - values[i] = values[i].trim(); - - // Proc-Type: 4,ENCRYPTED - // DEK-Info: DES-EDE3-CBC,579B6BE3E5C60483 - - if ("Proc-Type:".equals(name)) { - ps.procType = values; - continue; - } - - if ("DEK-Info:".equals(name)) { - ps.dekInfo = values; - continue; - } - - /* Ignore line */ - } - - StringBuffer keyData = new StringBuffer(); - - while (true) { - if (line == null) - throw new IOException("Invalid PEM structure, " + endLine + " missing"); - - line = line.trim(); - - if (line.startsWith(endLine)) - break; - - keyData.append(line); - line = br.readLine(); - } - - char[] pem_chars = new char[keyData.length()]; - keyData.getChars(0, pem_chars.length, pem_chars, 0); - ps.data = Base64.decode(pem_chars); - - if (ps.data.length == 0) - throw new IOException("Invalid PEM structure, no data available"); - - return ps; - } - - private static final void decryptPEM(PEMStructure ps, byte[] pw) throws IOException { - if (ps.dekInfo == null) - throw new IOException("Broken PEM, no mode and salt given, but encryption enabled"); - - if (ps.dekInfo.length != 2) - throw new IOException("Broken PEM, DEK-Info is incomplete!"); - - String algo = ps.dekInfo[0]; - byte[] salt = hexToByteArray(ps.dekInfo[1]); - BlockCipher bc = null; - - if (algo.equals("DES-EDE3-CBC")) { - DESede des3 = new DESede(); - des3.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 24)); - bc = new CBCMode(des3, salt, false); - } - else if (algo.equals("DES-CBC")) { - DES des = new DES(); - des.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 8)); - bc = new CBCMode(des, salt, false); - } - else if (algo.equals("AES-128-CBC")) { - AES aes = new AES(); - aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 16)); - bc = new CBCMode(aes, salt, false); - } - else if (algo.equals("AES-192-CBC")) { - AES aes = new AES(); - aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 24)); - bc = new CBCMode(aes, salt, false); - } - else if (algo.equals("AES-256-CBC")) { - AES aes = new AES(); - aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 32)); - bc = new CBCMode(aes, salt, false); - } - else { - throw new IOException("Cannot decrypt PEM structure, unknown cipher " + algo); - } - - if ((ps.data.length % bc.getBlockSize()) != 0) - throw new IOException("Invalid PEM structure, size of encrypted block is not a multiple of " - + bc.getBlockSize()); - - /* Now decrypt the content */ - byte[] dz = new byte[ps.data.length]; - - for (int i = 0; i < ps.data.length / bc.getBlockSize(); i++) { - bc.transformBlock(ps.data, i * bc.getBlockSize(), dz, i * bc.getBlockSize()); - } - - /* Now check and remove RFC 1423/PKCS #7 padding */ - dz = removePadding(dz, bc.getBlockSize()); - ps.data = dz; - ps.dekInfo = null; - ps.procType = null; - } - - public static final boolean isPEMEncrypted(PEMStructure ps) throws IOException { - if (ps.procType == null) - return false; - - if (ps.procType.length != 2) - throw new IOException("Unknown Proc-Type field."); - - if ("4".equals(ps.procType[0]) == false) - throw new IOException("Unknown Proc-Type field (" + ps.procType[0] + ")"); - - if ("ENCRYPTED".equals(ps.procType[1])) - return true; - - return false; - } - - public static KeyPair decode(char[] pem, String password) throws IOException { - PEMStructure ps = parsePEM(pem); - return decode(ps, password); - } - - public static KeyPair decode(PEMStructure ps, String password) throws IOException { - if (isPEMEncrypted(ps)) { - if (password == null) - throw new IOException("PEM is encrypted, but no password was specified"); - - decryptPEM(ps, password.getBytes("ISO-8859-1")); - } - - if (ps.pemType == PEM_DSA_PRIVATE_KEY) { - SimpleDERReader dr = new SimpleDERReader(ps.data); - byte[] seq = dr.readSequenceAsByteArray(); - - if (dr.available() != 0) - throw new IOException("Padding in DSA PRIVATE KEY DER stream."); - - dr.resetInput(seq); - BigInteger version = dr.readInt(); - - if (version.compareTo(BigInteger.ZERO) != 0) - throw new IOException("Wrong version (" + version + ") in DSA PRIVATE KEY DER stream."); - - BigInteger p = dr.readInt(); - BigInteger q = dr.readInt(); - BigInteger g = dr.readInt(); - BigInteger y = dr.readInt(); - BigInteger x = dr.readInt(); - - if (dr.available() != 0) - throw new IOException("Padding in DSA PRIVATE KEY DER stream."); - - DSAPrivateKeySpec privSpec = new DSAPrivateKeySpec(x, p, q, g); - DSAPublicKeySpec pubSpec = new DSAPublicKeySpec(y, p, q, g); - return generateKeyPair("DSA", privSpec, pubSpec); - } - - if (ps.pemType == PEM_RSA_PRIVATE_KEY) { - SimpleDERReader dr = new SimpleDERReader(ps.data); - byte[] seq = dr.readSequenceAsByteArray(); - - if (dr.available() != 0) - throw new IOException("Padding in RSA PRIVATE KEY DER stream."); - - dr.resetInput(seq); - BigInteger version = dr.readInt(); - - if ((version.compareTo(BigInteger.ZERO) != 0) && (version.compareTo(BigInteger.ONE) != 0)) - throw new IOException("Wrong version (" + version + ") in RSA PRIVATE KEY DER stream."); - - BigInteger n = dr.readInt(); - BigInteger e = dr.readInt(); - BigInteger d = dr.readInt(); - // TODO: is this right? - BigInteger primeP = dr.readInt(); - BigInteger primeQ = dr.readInt(); - BigInteger expP = dr.readInt(); - BigInteger expQ = dr.readInt(); - BigInteger coeff = dr.readInt(); - RSAPrivateKeySpec privSpec = new RSAPrivateCrtKeySpec(n, e, d, primeP, primeQ, expP, expQ, coeff); - RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(n, e); - return generateKeyPair("RSA", privSpec, pubSpec); - } - - if (ps.pemType == PEM_EC_PRIVATE_KEY) { - SimpleDERReader dr = new SimpleDERReader(ps.data); - byte[] seq = dr.readSequenceAsByteArray(); - - if (dr.available() != 0) - throw new IOException("Padding in EC PRIVATE KEY DER stream."); - - dr.resetInput(seq); - BigInteger version = dr.readInt(); - - if ((version.compareTo(BigInteger.ONE) != 0)) - throw new IOException("Wrong version (" + version + ") in EC PRIVATE KEY DER stream."); - - byte[] privateBytes = dr.readOctetString(); - String curveOid = null; - byte[] publicBytes = null; - - while (dr.available() > 0) { - int type = dr.readConstructedType(); - SimpleDERReader cr = dr.readConstructed(); - - switch (type) { - case 0: - curveOid = cr.readOid(); - break; - - case 1: - publicBytes = cr.readOctetString(); - break; - } - } - - ECParameterSpec params = ECDSASHA2Verify.getCurveForOID(curveOid); - - if (params == null) - throw new IOException("invalid OID"); - - BigInteger s = new BigInteger(privateBytes); - byte[] publicBytesSlice = new byte[publicBytes.length - 1]; - System.arraycopy(publicBytes, 1, publicBytesSlice, 0, publicBytesSlice.length); - ECPoint w = ECDSASHA2Verify.decodeECPoint(publicBytesSlice, params.getCurve()); - ECPrivateKeySpec privSpec = new ECPrivateKeySpec(s, params); - ECPublicKeySpec pubSpec = new ECPublicKeySpec(w, params); - return generateKeyPair("EC", privSpec, pubSpec); - } - - throw new IOException("PEM problem: it is of unknown type"); - } - - /** - * Generate a {@code KeyPair} given an {@code algorithm} and {@code KeySpec}. - */ - private static KeyPair generateKeyPair(String algorithm, KeySpec privSpec, KeySpec pubSpec) - throws IOException { - try { - final KeyFactory kf = KeyFactory.getInstance(algorithm); - final PublicKey pubKey = kf.generatePublic(pubSpec); - final PrivateKey privKey = kf.generatePrivate(privSpec); - 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; - } - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/crypto/PEMStructure.java --- a/src/com/trilead/ssh2/crypto/PEMStructure.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ - -package com.trilead.ssh2.crypto; - -/** - * Parsed PEM structure. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PEMStructure.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ - -public class PEMStructure { - public int pemType; - String dekInfo[]; - String procType[]; - public byte[] data; -} \ No newline at end of file diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/crypto/SimpleDERReader.java --- a/src/com/trilead/ssh2/crypto/SimpleDERReader.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,205 +0,0 @@ -package com.trilead.ssh2.crypto; - -import java.io.IOException; - -import java.math.BigInteger; - -/** - * SimpleDERReader. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: SimpleDERReader.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -public class SimpleDERReader { - private static final int CONSTRUCTED = 0x20; - - byte[] buffer; - int pos; - int count; - - public SimpleDERReader(byte[] b) { - resetInput(b); - } - - public SimpleDERReader(byte[] b, int off, int len) { - resetInput(b, off, len); - } - - public void resetInput(byte[] b) { - resetInput(b, 0, b.length); - } - - public void resetInput(byte[] b, int off, int len) { - buffer = b; - pos = off; - count = len; - } - - private byte readByte() throws IOException { - if (count <= 0) - throw new IOException("DER byte array: out of data"); - - count--; - return buffer[pos++]; - } - - private byte[] readBytes(int len) throws IOException { - if (len > count) - throw new IOException("DER byte array: out of data"); - - byte[] b = new byte[len]; - System.arraycopy(buffer, pos, b, 0, len); - pos += len; - count -= len; - return b; - } - - public int available() { - return count; - } - - private int readLength() throws IOException { - int len = readByte() & 0xff; - - if ((len & 0x80) == 0) - return len; - - int remain = len & 0x7F; - - if (remain == 0) - return -1; - - len = 0; - - while (remain > 0) { - len = len << 8; - len = len | (readByte() & 0xff); - remain--; - } - - return len; - } - - public int ignoreNextObject() throws IOException { - int type = readByte() & 0xff; - int len = readLength(); - - if ((len < 0) || len > available()) - throw new IOException("Illegal len in DER object (" + len + ")"); - - readBytes(len); - return type; - } - - public BigInteger readInt() throws IOException { - int type = readByte() & 0xff; - - if (type != 0x02) - throw new IOException("Expected DER Integer, but found type " + type); - - int len = readLength(); - - if ((len < 0) || len > available()) - throw new IOException("Illegal len in DER object (" + len + ")"); - - byte[] b = readBytes(len); - BigInteger bi = new BigInteger(b); - return bi; - } - - public int readConstructedType() throws IOException { - int type = readByte() & 0xff; - - if ((type & CONSTRUCTED) != CONSTRUCTED) - throw new IOException("Expected constructed type, but was " + type); - - return type & 0x1f; - } - - public SimpleDERReader readConstructed() throws IOException { - int len = readLength(); - - if ((len < 0) || len > available()) - throw new IOException("Illegal len in DER object (" + len + ")"); - - SimpleDERReader cr = new SimpleDERReader(buffer, pos, len); - pos += len; - count -= len; - return cr; - } - - public byte[] readSequenceAsByteArray() throws IOException { - int type = readByte() & 0xff; - - if (type != 0x30) - throw new IOException("Expected DER Sequence, but found type " + type); - - int len = readLength(); - - if ((len < 0) || len > available()) - throw new IOException("Illegal len in DER object (" + len + ")"); - - byte[] b = readBytes(len); - return b; - } - - public String readOid() throws IOException { - int type = readByte() & 0xff; - - if (type != 0x06) - throw new IOException("Expected DER OID, but found type " + type); - - int len = readLength(); - - if ((len < 1) || len > available()) - throw new IOException("Illegal len in DER object (" + len + ")"); - - byte[] b = readBytes(len); - long value = 0; - StringBuilder sb = new StringBuilder(64); - - switch (b[0] / 40) { - case 0: - sb.append('0'); - break; - - case 1: - sb.append('1'); - b[0] -= 40; - break; - - default: - sb.append('2'); - b[0] -= 80; - break; - } - - for (int i = 0; i < len; i++) { - value = (value << 7) + (b[i] & 0x7F); - - if ((b[i] & 0x80) == 0) { - sb.append('.'); - sb.append(value); - value = 0; - } - } - - return sb.toString(); - } - - public byte[] readOctetString() throws IOException { - int type = readByte() & 0xff; - - if (type != 0x04 && type != 0x03) - throw new IOException("Expected DER Octetstring, but found type " + type); - - int len = readLength(); - - if ((len < 0) || len > available()) - throw new IOException("Illegal len in DER object (" + len + ")"); - - byte[] b = readBytes(len); - return b; - } - -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/crypto/cipher/AES.java --- a/src/com/trilead/ssh2/crypto/cipher/AES.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,662 +0,0 @@ - -package com.trilead.ssh2.crypto.cipher; - -/* - This file was shamelessly taken from the Bouncy Castle Crypto package. - Their licence file states the following: - - Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle - (http://www.bouncycastle.org) - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - */ - -/** - * An implementation of the AES (Rijndael), from FIPS-197. - *

- * For further details see: http://csrc.nist.gov/encryption/aes/ - * . - * - * This implementation is based on optimizations from Dr. Brian Gladman's paper - * and C code at http://fp.gladman.plus.com/cryptography_technology/rijndael/ - * - * - * There are three levels of tradeoff of speed vs memory Because java has no - * preprocessor, they are written as three separate classes from which to choose - * - * The fastest uses 8Kbytes of static tables to precompute round calculations, 4 - * 256 word tables for encryption and 4 for decryption. - * - * The middle performance version uses only one 256 word table for each, for a - * total of 2Kbytes, adding 12 rotate operations per round to compute the values - * contained in the other tables from the contents of the first - * - * The slowest version uses no static tables at all and computes the values in - * each round - *

- * This file contains the fast version with 8Kbytes of static tables for round - * precomputation - * - * @author See comments in the source file - * @version $Id: AES.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class AES implements BlockCipher { - // The S box - private static final byte[] S = { (byte) 99, (byte) 124, (byte) 119, (byte) 123, (byte) 242, (byte) 107, - (byte) 111, (byte) 197, (byte) 48, (byte) 1, (byte) 103, (byte) 43, (byte) 254, (byte) 215, (byte) 171, - (byte) 118, (byte) 202, (byte) 130, (byte) 201, (byte) 125, (byte) 250, (byte) 89, (byte) 71, (byte) 240, - (byte) 173, (byte) 212, (byte) 162, (byte) 175, (byte) 156, (byte) 164, (byte) 114, (byte) 192, (byte) 183, - (byte) 253, (byte) 147, (byte) 38, (byte) 54, (byte) 63, (byte) 247, (byte) 204, (byte) 52, (byte) 165, - (byte) 229, (byte) 241, (byte) 113, (byte) 216, (byte) 49, (byte) 21, (byte) 4, (byte) 199, (byte) 35, - (byte) 195, (byte) 24, (byte) 150, (byte) 5, (byte) 154, (byte) 7, (byte) 18, (byte) 128, (byte) 226, - (byte) 235, (byte) 39, (byte) 178, (byte) 117, (byte) 9, (byte) 131, (byte) 44, (byte) 26, (byte) 27, - (byte) 110, (byte) 90, (byte) 160, (byte) 82, (byte) 59, (byte) 214, (byte) 179, (byte) 41, (byte) 227, - (byte) 47, (byte) 132, (byte) 83, (byte) 209, (byte) 0, (byte) 237, (byte) 32, (byte) 252, (byte) 177, - (byte) 91, (byte) 106, (byte) 203, (byte) 190, (byte) 57, (byte) 74, (byte) 76, (byte) 88, (byte) 207, - (byte) 208, (byte) 239, (byte) 170, (byte) 251, (byte) 67, (byte) 77, (byte) 51, (byte) 133, (byte) 69, - (byte) 249, (byte) 2, (byte) 127, (byte) 80, (byte) 60, (byte) 159, (byte) 168, (byte) 81, (byte) 163, - (byte) 64, (byte) 143, (byte) 146, (byte) 157, (byte) 56, (byte) 245, (byte) 188, (byte) 182, (byte) 218, - (byte) 33, (byte) 16, (byte) 255, (byte) 243, (byte) 210, (byte) 205, (byte) 12, (byte) 19, (byte) 236, - (byte) 95, (byte) 151, (byte) 68, (byte) 23, (byte) 196, (byte) 167, (byte) 126, (byte) 61, (byte) 100, - (byte) 93, (byte) 25, (byte) 115, (byte) 96, (byte) 129, (byte) 79, (byte) 220, (byte) 34, (byte) 42, - (byte) 144, (byte) 136, (byte) 70, (byte) 238, (byte) 184, (byte) 20, (byte) 222, (byte) 94, (byte) 11, - (byte) 219, (byte) 224, (byte) 50, (byte) 58, (byte) 10, (byte) 73, (byte) 6, (byte) 36, (byte) 92, - (byte) 194, (byte) 211, (byte) 172, (byte) 98, (byte) 145, (byte) 149, (byte) 228, (byte) 121, (byte) 231, - (byte) 200, (byte) 55, (byte) 109, (byte) 141, (byte) 213, (byte) 78, (byte) 169, (byte) 108, (byte) 86, - (byte) 244, (byte) 234, (byte) 101, (byte) 122, (byte) 174, (byte) 8, (byte) 186, (byte) 120, (byte) 37, - (byte) 46, (byte) 28, (byte) 166, (byte) 180, (byte) 198, (byte) 232, (byte) 221, (byte) 116, (byte) 31, - (byte) 75, (byte) 189, (byte) 139, (byte) 138, (byte) 112, (byte) 62, (byte) 181, (byte) 102, (byte) 72, - (byte) 3, (byte) 246, (byte) 14, (byte) 97, (byte) 53, (byte) 87, (byte) 185, (byte) 134, (byte) 193, - (byte) 29, (byte) 158, (byte) 225, (byte) 248, (byte) 152, (byte) 17, (byte) 105, (byte) 217, (byte) 142, - (byte) 148, (byte) 155, (byte) 30, (byte) 135, (byte) 233, (byte) 206, (byte) 85, (byte) 40, (byte) 223, - (byte) 140, (byte) 161, (byte) 137, (byte) 13, (byte) 191, (byte) 230, (byte) 66, (byte) 104, (byte) 65, - (byte) 153, (byte) 45, (byte) 15, (byte) 176, (byte) 84, (byte) 187, (byte) 22, - }; - - // The inverse S-box - private static final byte[] Si = { (byte) 82, (byte) 9, (byte) 106, (byte) 213, (byte) 48, (byte) 54, (byte) 165, - (byte) 56, (byte) 191, (byte) 64, (byte) 163, (byte) 158, (byte) 129, (byte) 243, (byte) 215, (byte) 251, - (byte) 124, (byte) 227, (byte) 57, (byte) 130, (byte) 155, (byte) 47, (byte) 255, (byte) 135, (byte) 52, - (byte) 142, (byte) 67, (byte) 68, (byte) 196, (byte) 222, (byte) 233, (byte) 203, (byte) 84, (byte) 123, - (byte) 148, (byte) 50, (byte) 166, (byte) 194, (byte) 35, (byte) 61, (byte) 238, (byte) 76, (byte) 149, - (byte) 11, (byte) 66, (byte) 250, (byte) 195, (byte) 78, (byte) 8, (byte) 46, (byte) 161, (byte) 102, - (byte) 40, (byte) 217, (byte) 36, (byte) 178, (byte) 118, (byte) 91, (byte) 162, (byte) 73, (byte) 109, - (byte) 139, (byte) 209, (byte) 37, (byte) 114, (byte) 248, (byte) 246, (byte) 100, (byte) 134, (byte) 104, - (byte) 152, (byte) 22, (byte) 212, (byte) 164, (byte) 92, (byte) 204, (byte) 93, (byte) 101, (byte) 182, - (byte) 146, (byte) 108, (byte) 112, (byte) 72, (byte) 80, (byte) 253, (byte) 237, (byte) 185, (byte) 218, - (byte) 94, (byte) 21, (byte) 70, (byte) 87, (byte) 167, (byte) 141, (byte) 157, (byte) 132, (byte) 144, - (byte) 216, (byte) 171, (byte) 0, (byte) 140, (byte) 188, (byte) 211, (byte) 10, (byte) 247, (byte) 228, - (byte) 88, (byte) 5, (byte) 184, (byte) 179, (byte) 69, (byte) 6, (byte) 208, (byte) 44, (byte) 30, - (byte) 143, (byte) 202, (byte) 63, (byte) 15, (byte) 2, (byte) 193, (byte) 175, (byte) 189, (byte) 3, - (byte) 1, (byte) 19, (byte) 138, (byte) 107, (byte) 58, (byte) 145, (byte) 17, (byte) 65, (byte) 79, - (byte) 103, (byte) 220, (byte) 234, (byte) 151, (byte) 242, (byte) 207, (byte) 206, (byte) 240, (byte) 180, - (byte) 230, (byte) 115, (byte) 150, (byte) 172, (byte) 116, (byte) 34, (byte) 231, (byte) 173, (byte) 53, - (byte) 133, (byte) 226, (byte) 249, (byte) 55, (byte) 232, (byte) 28, (byte) 117, (byte) 223, (byte) 110, - (byte) 71, (byte) 241, (byte) 26, (byte) 113, (byte) 29, (byte) 41, (byte) 197, (byte) 137, (byte) 111, - (byte) 183, (byte) 98, (byte) 14, (byte) 170, (byte) 24, (byte) 190, (byte) 27, (byte) 252, (byte) 86, - (byte) 62, (byte) 75, (byte) 198, (byte) 210, (byte) 121, (byte) 32, (byte) 154, (byte) 219, (byte) 192, - (byte) 254, (byte) 120, (byte) 205, (byte) 90, (byte) 244, (byte) 31, (byte) 221, (byte) 168, (byte) 51, - (byte) 136, (byte) 7, (byte) 199, (byte) 49, (byte) 177, (byte) 18, (byte) 16, (byte) 89, (byte) 39, - (byte) 128, (byte) 236, (byte) 95, (byte) 96, (byte) 81, (byte) 127, (byte) 169, (byte) 25, (byte) 181, - (byte) 74, (byte) 13, (byte) 45, (byte) 229, (byte) 122, (byte) 159, (byte) 147, (byte) 201, (byte) 156, - (byte) 239, (byte) 160, (byte) 224, (byte) 59, (byte) 77, (byte) 174, (byte) 42, (byte) 245, (byte) 176, - (byte) 200, (byte) 235, (byte) 187, (byte) 60, (byte) 131, (byte) 83, (byte) 153, (byte) 97, (byte) 23, - (byte) 43, (byte) 4, (byte) 126, (byte) 186, (byte) 119, (byte) 214, (byte) 38, (byte) 225, (byte) 105, - (byte) 20, (byte) 99, (byte) 85, (byte) 33, (byte) 12, (byte) 125, - }; - - // vector used in calculating key schedule (powers of x in GF(256)) - private static final int[] rcon = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, - 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 - }; - - // precomputation tables of calculations for rounds - private static final int[] T0 = { 0xa56363c6, 0x847c7cf8, 0x997777ee, 0x8d7b7bf6, 0x0df2f2ff, 0xbd6b6bd6, - 0xb16f6fde, 0x54c5c591, 0x50303060, 0x03010102, 0xa96767ce, 0x7d2b2b56, 0x19fefee7, 0x62d7d7b5, 0xe6abab4d, - 0x9a7676ec, 0x45caca8f, 0x9d82821f, 0x40c9c989, 0x877d7dfa, 0x15fafaef, 0xeb5959b2, 0xc947478e, 0x0bf0f0fb, - 0xecadad41, 0x67d4d4b3, 0xfda2a25f, 0xeaafaf45, 0xbf9c9c23, 0xf7a4a453, 0x967272e4, 0x5bc0c09b, 0xc2b7b775, - 0x1cfdfde1, 0xae93933d, 0x6a26264c, 0x5a36366c, 0x413f3f7e, 0x02f7f7f5, 0x4fcccc83, 0x5c343468, 0xf4a5a551, - 0x34e5e5d1, 0x08f1f1f9, 0x937171e2, 0x73d8d8ab, 0x53313162, 0x3f15152a, 0x0c040408, 0x52c7c795, 0x65232346, - 0x5ec3c39d, 0x28181830, 0xa1969637, 0x0f05050a, 0xb59a9a2f, 0x0907070e, 0x36121224, 0x9b80801b, 0x3de2e2df, - 0x26ebebcd, 0x6927274e, 0xcdb2b27f, 0x9f7575ea, 0x1b090912, 0x9e83831d, 0x742c2c58, 0x2e1a1a34, 0x2d1b1b36, - 0xb26e6edc, 0xee5a5ab4, 0xfba0a05b, 0xf65252a4, 0x4d3b3b76, 0x61d6d6b7, 0xceb3b37d, 0x7b292952, 0x3ee3e3dd, - 0x712f2f5e, 0x97848413, 0xf55353a6, 0x68d1d1b9, 0x00000000, 0x2cededc1, 0x60202040, 0x1ffcfce3, 0xc8b1b179, - 0xed5b5bb6, 0xbe6a6ad4, 0x46cbcb8d, 0xd9bebe67, 0x4b393972, 0xde4a4a94, 0xd44c4c98, 0xe85858b0, 0x4acfcf85, - 0x6bd0d0bb, 0x2aefefc5, 0xe5aaaa4f, 0x16fbfbed, 0xc5434386, 0xd74d4d9a, 0x55333366, 0x94858511, 0xcf45458a, - 0x10f9f9e9, 0x06020204, 0x817f7ffe, 0xf05050a0, 0x443c3c78, 0xba9f9f25, 0xe3a8a84b, 0xf35151a2, 0xfea3a35d, - 0xc0404080, 0x8a8f8f05, 0xad92923f, 0xbc9d9d21, 0x48383870, 0x04f5f5f1, 0xdfbcbc63, 0xc1b6b677, 0x75dadaaf, - 0x63212142, 0x30101020, 0x1affffe5, 0x0ef3f3fd, 0x6dd2d2bf, 0x4ccdcd81, 0x140c0c18, 0x35131326, 0x2fececc3, - 0xe15f5fbe, 0xa2979735, 0xcc444488, 0x3917172e, 0x57c4c493, 0xf2a7a755, 0x827e7efc, 0x473d3d7a, 0xac6464c8, - 0xe75d5dba, 0x2b191932, 0x957373e6, 0xa06060c0, 0x98818119, 0xd14f4f9e, 0x7fdcdca3, 0x66222244, 0x7e2a2a54, - 0xab90903b, 0x8388880b, 0xca46468c, 0x29eeeec7, 0xd3b8b86b, 0x3c141428, 0x79dedea7, 0xe25e5ebc, 0x1d0b0b16, - 0x76dbdbad, 0x3be0e0db, 0x56323264, 0x4e3a3a74, 0x1e0a0a14, 0xdb494992, 0x0a06060c, 0x6c242448, 0xe45c5cb8, - 0x5dc2c29f, 0x6ed3d3bd, 0xefacac43, 0xa66262c4, 0xa8919139, 0xa4959531, 0x37e4e4d3, 0x8b7979f2, 0x32e7e7d5, - 0x43c8c88b, 0x5937376e, 0xb76d6dda, 0x8c8d8d01, 0x64d5d5b1, 0xd24e4e9c, 0xe0a9a949, 0xb46c6cd8, 0xfa5656ac, - 0x07f4f4f3, 0x25eaeacf, 0xaf6565ca, 0x8e7a7af4, 0xe9aeae47, 0x18080810, 0xd5baba6f, 0x887878f0, 0x6f25254a, - 0x722e2e5c, 0x241c1c38, 0xf1a6a657, 0xc7b4b473, 0x51c6c697, 0x23e8e8cb, 0x7cdddda1, 0x9c7474e8, 0x211f1f3e, - 0xdd4b4b96, 0xdcbdbd61, 0x868b8b0d, 0x858a8a0f, 0x907070e0, 0x423e3e7c, 0xc4b5b571, 0xaa6666cc, 0xd8484890, - 0x05030306, 0x01f6f6f7, 0x120e0e1c, 0xa36161c2, 0x5f35356a, 0xf95757ae, 0xd0b9b969, 0x91868617, 0x58c1c199, - 0x271d1d3a, 0xb99e9e27, 0x38e1e1d9, 0x13f8f8eb, 0xb398982b, 0x33111122, 0xbb6969d2, 0x70d9d9a9, 0x898e8e07, - 0xa7949433, 0xb69b9b2d, 0x221e1e3c, 0x92878715, 0x20e9e9c9, 0x49cece87, 0xff5555aa, 0x78282850, 0x7adfdfa5, - 0x8f8c8c03, 0xf8a1a159, 0x80898909, 0x170d0d1a, 0xdabfbf65, 0x31e6e6d7, 0xc6424284, 0xb86868d0, 0xc3414182, - 0xb0999929, 0x772d2d5a, 0x110f0f1e, 0xcbb0b07b, 0xfc5454a8, 0xd6bbbb6d, 0x3a16162c - }; - - private static final int[] T1 = { 0x6363c6a5, 0x7c7cf884, 0x7777ee99, 0x7b7bf68d, 0xf2f2ff0d, 0x6b6bd6bd, - 0x6f6fdeb1, 0xc5c59154, 0x30306050, 0x01010203, 0x6767cea9, 0x2b2b567d, 0xfefee719, 0xd7d7b562, 0xabab4de6, - 0x7676ec9a, 0xcaca8f45, 0x82821f9d, 0xc9c98940, 0x7d7dfa87, 0xfafaef15, 0x5959b2eb, 0x47478ec9, 0xf0f0fb0b, - 0xadad41ec, 0xd4d4b367, 0xa2a25ffd, 0xafaf45ea, 0x9c9c23bf, 0xa4a453f7, 0x7272e496, 0xc0c09b5b, 0xb7b775c2, - 0xfdfde11c, 0x93933dae, 0x26264c6a, 0x36366c5a, 0x3f3f7e41, 0xf7f7f502, 0xcccc834f, 0x3434685c, 0xa5a551f4, - 0xe5e5d134, 0xf1f1f908, 0x7171e293, 0xd8d8ab73, 0x31316253, 0x15152a3f, 0x0404080c, 0xc7c79552, 0x23234665, - 0xc3c39d5e, 0x18183028, 0x969637a1, 0x05050a0f, 0x9a9a2fb5, 0x07070e09, 0x12122436, 0x80801b9b, 0xe2e2df3d, - 0xebebcd26, 0x27274e69, 0xb2b27fcd, 0x7575ea9f, 0x0909121b, 0x83831d9e, 0x2c2c5874, 0x1a1a342e, 0x1b1b362d, - 0x6e6edcb2, 0x5a5ab4ee, 0xa0a05bfb, 0x5252a4f6, 0x3b3b764d, 0xd6d6b761, 0xb3b37dce, 0x2929527b, 0xe3e3dd3e, - 0x2f2f5e71, 0x84841397, 0x5353a6f5, 0xd1d1b968, 0x00000000, 0xededc12c, 0x20204060, 0xfcfce31f, 0xb1b179c8, - 0x5b5bb6ed, 0x6a6ad4be, 0xcbcb8d46, 0xbebe67d9, 0x3939724b, 0x4a4a94de, 0x4c4c98d4, 0x5858b0e8, 0xcfcf854a, - 0xd0d0bb6b, 0xefefc52a, 0xaaaa4fe5, 0xfbfbed16, 0x434386c5, 0x4d4d9ad7, 0x33336655, 0x85851194, 0x45458acf, - 0xf9f9e910, 0x02020406, 0x7f7ffe81, 0x5050a0f0, 0x3c3c7844, 0x9f9f25ba, 0xa8a84be3, 0x5151a2f3, 0xa3a35dfe, - 0x404080c0, 0x8f8f058a, 0x92923fad, 0x9d9d21bc, 0x38387048, 0xf5f5f104, 0xbcbc63df, 0xb6b677c1, 0xdadaaf75, - 0x21214263, 0x10102030, 0xffffe51a, 0xf3f3fd0e, 0xd2d2bf6d, 0xcdcd814c, 0x0c0c1814, 0x13132635, 0xececc32f, - 0x5f5fbee1, 0x979735a2, 0x444488cc, 0x17172e39, 0xc4c49357, 0xa7a755f2, 0x7e7efc82, 0x3d3d7a47, 0x6464c8ac, - 0x5d5dbae7, 0x1919322b, 0x7373e695, 0x6060c0a0, 0x81811998, 0x4f4f9ed1, 0xdcdca37f, 0x22224466, 0x2a2a547e, - 0x90903bab, 0x88880b83, 0x46468cca, 0xeeeec729, 0xb8b86bd3, 0x1414283c, 0xdedea779, 0x5e5ebce2, 0x0b0b161d, - 0xdbdbad76, 0xe0e0db3b, 0x32326456, 0x3a3a744e, 0x0a0a141e, 0x494992db, 0x06060c0a, 0x2424486c, 0x5c5cb8e4, - 0xc2c29f5d, 0xd3d3bd6e, 0xacac43ef, 0x6262c4a6, 0x919139a8, 0x959531a4, 0xe4e4d337, 0x7979f28b, 0xe7e7d532, - 0xc8c88b43, 0x37376e59, 0x6d6ddab7, 0x8d8d018c, 0xd5d5b164, 0x4e4e9cd2, 0xa9a949e0, 0x6c6cd8b4, 0x5656acfa, - 0xf4f4f307, 0xeaeacf25, 0x6565caaf, 0x7a7af48e, 0xaeae47e9, 0x08081018, 0xbaba6fd5, 0x7878f088, 0x25254a6f, - 0x2e2e5c72, 0x1c1c3824, 0xa6a657f1, 0xb4b473c7, 0xc6c69751, 0xe8e8cb23, 0xdddda17c, 0x7474e89c, 0x1f1f3e21, - 0x4b4b96dd, 0xbdbd61dc, 0x8b8b0d86, 0x8a8a0f85, 0x7070e090, 0x3e3e7c42, 0xb5b571c4, 0x6666ccaa, 0x484890d8, - 0x03030605, 0xf6f6f701, 0x0e0e1c12, 0x6161c2a3, 0x35356a5f, 0x5757aef9, 0xb9b969d0, 0x86861791, 0xc1c19958, - 0x1d1d3a27, 0x9e9e27b9, 0xe1e1d938, 0xf8f8eb13, 0x98982bb3, 0x11112233, 0x6969d2bb, 0xd9d9a970, 0x8e8e0789, - 0x949433a7, 0x9b9b2db6, 0x1e1e3c22, 0x87871592, 0xe9e9c920, 0xcece8749, 0x5555aaff, 0x28285078, 0xdfdfa57a, - 0x8c8c038f, 0xa1a159f8, 0x89890980, 0x0d0d1a17, 0xbfbf65da, 0xe6e6d731, 0x424284c6, 0x6868d0b8, 0x414182c3, - 0x999929b0, 0x2d2d5a77, 0x0f0f1e11, 0xb0b07bcb, 0x5454a8fc, 0xbbbb6dd6, 0x16162c3a - }; - - private static final int[] T2 = { 0x63c6a563, 0x7cf8847c, 0x77ee9977, 0x7bf68d7b, 0xf2ff0df2, 0x6bd6bd6b, - 0x6fdeb16f, 0xc59154c5, 0x30605030, 0x01020301, 0x67cea967, 0x2b567d2b, 0xfee719fe, 0xd7b562d7, 0xab4de6ab, - 0x76ec9a76, 0xca8f45ca, 0x821f9d82, 0xc98940c9, 0x7dfa877d, 0xfaef15fa, 0x59b2eb59, 0x478ec947, 0xf0fb0bf0, - 0xad41ecad, 0xd4b367d4, 0xa25ffda2, 0xaf45eaaf, 0x9c23bf9c, 0xa453f7a4, 0x72e49672, 0xc09b5bc0, 0xb775c2b7, - 0xfde11cfd, 0x933dae93, 0x264c6a26, 0x366c5a36, 0x3f7e413f, 0xf7f502f7, 0xcc834fcc, 0x34685c34, 0xa551f4a5, - 0xe5d134e5, 0xf1f908f1, 0x71e29371, 0xd8ab73d8, 0x31625331, 0x152a3f15, 0x04080c04, 0xc79552c7, 0x23466523, - 0xc39d5ec3, 0x18302818, 0x9637a196, 0x050a0f05, 0x9a2fb59a, 0x070e0907, 0x12243612, 0x801b9b80, 0xe2df3de2, - 0xebcd26eb, 0x274e6927, 0xb27fcdb2, 0x75ea9f75, 0x09121b09, 0x831d9e83, 0x2c58742c, 0x1a342e1a, 0x1b362d1b, - 0x6edcb26e, 0x5ab4ee5a, 0xa05bfba0, 0x52a4f652, 0x3b764d3b, 0xd6b761d6, 0xb37dceb3, 0x29527b29, 0xe3dd3ee3, - 0x2f5e712f, 0x84139784, 0x53a6f553, 0xd1b968d1, 0x00000000, 0xedc12ced, 0x20406020, 0xfce31ffc, 0xb179c8b1, - 0x5bb6ed5b, 0x6ad4be6a, 0xcb8d46cb, 0xbe67d9be, 0x39724b39, 0x4a94de4a, 0x4c98d44c, 0x58b0e858, 0xcf854acf, - 0xd0bb6bd0, 0xefc52aef, 0xaa4fe5aa, 0xfbed16fb, 0x4386c543, 0x4d9ad74d, 0x33665533, 0x85119485, 0x458acf45, - 0xf9e910f9, 0x02040602, 0x7ffe817f, 0x50a0f050, 0x3c78443c, 0x9f25ba9f, 0xa84be3a8, 0x51a2f351, 0xa35dfea3, - 0x4080c040, 0x8f058a8f, 0x923fad92, 0x9d21bc9d, 0x38704838, 0xf5f104f5, 0xbc63dfbc, 0xb677c1b6, 0xdaaf75da, - 0x21426321, 0x10203010, 0xffe51aff, 0xf3fd0ef3, 0xd2bf6dd2, 0xcd814ccd, 0x0c18140c, 0x13263513, 0xecc32fec, - 0x5fbee15f, 0x9735a297, 0x4488cc44, 0x172e3917, 0xc49357c4, 0xa755f2a7, 0x7efc827e, 0x3d7a473d, 0x64c8ac64, - 0x5dbae75d, 0x19322b19, 0x73e69573, 0x60c0a060, 0x81199881, 0x4f9ed14f, 0xdca37fdc, 0x22446622, 0x2a547e2a, - 0x903bab90, 0x880b8388, 0x468cca46, 0xeec729ee, 0xb86bd3b8, 0x14283c14, 0xdea779de, 0x5ebce25e, 0x0b161d0b, - 0xdbad76db, 0xe0db3be0, 0x32645632, 0x3a744e3a, 0x0a141e0a, 0x4992db49, 0x060c0a06, 0x24486c24, 0x5cb8e45c, - 0xc29f5dc2, 0xd3bd6ed3, 0xac43efac, 0x62c4a662, 0x9139a891, 0x9531a495, 0xe4d337e4, 0x79f28b79, 0xe7d532e7, - 0xc88b43c8, 0x376e5937, 0x6ddab76d, 0x8d018c8d, 0xd5b164d5, 0x4e9cd24e, 0xa949e0a9, 0x6cd8b46c, 0x56acfa56, - 0xf4f307f4, 0xeacf25ea, 0x65caaf65, 0x7af48e7a, 0xae47e9ae, 0x08101808, 0xba6fd5ba, 0x78f08878, 0x254a6f25, - 0x2e5c722e, 0x1c38241c, 0xa657f1a6, 0xb473c7b4, 0xc69751c6, 0xe8cb23e8, 0xdda17cdd, 0x74e89c74, 0x1f3e211f, - 0x4b96dd4b, 0xbd61dcbd, 0x8b0d868b, 0x8a0f858a, 0x70e09070, 0x3e7c423e, 0xb571c4b5, 0x66ccaa66, 0x4890d848, - 0x03060503, 0xf6f701f6, 0x0e1c120e, 0x61c2a361, 0x356a5f35, 0x57aef957, 0xb969d0b9, 0x86179186, 0xc19958c1, - 0x1d3a271d, 0x9e27b99e, 0xe1d938e1, 0xf8eb13f8, 0x982bb398, 0x11223311, 0x69d2bb69, 0xd9a970d9, 0x8e07898e, - 0x9433a794, 0x9b2db69b, 0x1e3c221e, 0x87159287, 0xe9c920e9, 0xce8749ce, 0x55aaff55, 0x28507828, 0xdfa57adf, - 0x8c038f8c, 0xa159f8a1, 0x89098089, 0x0d1a170d, 0xbf65dabf, 0xe6d731e6, 0x4284c642, 0x68d0b868, 0x4182c341, - 0x9929b099, 0x2d5a772d, 0x0f1e110f, 0xb07bcbb0, 0x54a8fc54, 0xbb6dd6bb, 0x162c3a16 - }; - - private static final int[] T3 = { 0xc6a56363, 0xf8847c7c, 0xee997777, 0xf68d7b7b, 0xff0df2f2, 0xd6bd6b6b, - 0xdeb16f6f, 0x9154c5c5, 0x60503030, 0x02030101, 0xcea96767, 0x567d2b2b, 0xe719fefe, 0xb562d7d7, 0x4de6abab, - 0xec9a7676, 0x8f45caca, 0x1f9d8282, 0x8940c9c9, 0xfa877d7d, 0xef15fafa, 0xb2eb5959, 0x8ec94747, 0xfb0bf0f0, - 0x41ecadad, 0xb367d4d4, 0x5ffda2a2, 0x45eaafaf, 0x23bf9c9c, 0x53f7a4a4, 0xe4967272, 0x9b5bc0c0, 0x75c2b7b7, - 0xe11cfdfd, 0x3dae9393, 0x4c6a2626, 0x6c5a3636, 0x7e413f3f, 0xf502f7f7, 0x834fcccc, 0x685c3434, 0x51f4a5a5, - 0xd134e5e5, 0xf908f1f1, 0xe2937171, 0xab73d8d8, 0x62533131, 0x2a3f1515, 0x080c0404, 0x9552c7c7, 0x46652323, - 0x9d5ec3c3, 0x30281818, 0x37a19696, 0x0a0f0505, 0x2fb59a9a, 0x0e090707, 0x24361212, 0x1b9b8080, 0xdf3de2e2, - 0xcd26ebeb, 0x4e692727, 0x7fcdb2b2, 0xea9f7575, 0x121b0909, 0x1d9e8383, 0x58742c2c, 0x342e1a1a, 0x362d1b1b, - 0xdcb26e6e, 0xb4ee5a5a, 0x5bfba0a0, 0xa4f65252, 0x764d3b3b, 0xb761d6d6, 0x7dceb3b3, 0x527b2929, 0xdd3ee3e3, - 0x5e712f2f, 0x13978484, 0xa6f55353, 0xb968d1d1, 0x00000000, 0xc12ceded, 0x40602020, 0xe31ffcfc, 0x79c8b1b1, - 0xb6ed5b5b, 0xd4be6a6a, 0x8d46cbcb, 0x67d9bebe, 0x724b3939, 0x94de4a4a, 0x98d44c4c, 0xb0e85858, 0x854acfcf, - 0xbb6bd0d0, 0xc52aefef, 0x4fe5aaaa, 0xed16fbfb, 0x86c54343, 0x9ad74d4d, 0x66553333, 0x11948585, 0x8acf4545, - 0xe910f9f9, 0x04060202, 0xfe817f7f, 0xa0f05050, 0x78443c3c, 0x25ba9f9f, 0x4be3a8a8, 0xa2f35151, 0x5dfea3a3, - 0x80c04040, 0x058a8f8f, 0x3fad9292, 0x21bc9d9d, 0x70483838, 0xf104f5f5, 0x63dfbcbc, 0x77c1b6b6, 0xaf75dada, - 0x42632121, 0x20301010, 0xe51affff, 0xfd0ef3f3, 0xbf6dd2d2, 0x814ccdcd, 0x18140c0c, 0x26351313, 0xc32fecec, - 0xbee15f5f, 0x35a29797, 0x88cc4444, 0x2e391717, 0x9357c4c4, 0x55f2a7a7, 0xfc827e7e, 0x7a473d3d, 0xc8ac6464, - 0xbae75d5d, 0x322b1919, 0xe6957373, 0xc0a06060, 0x19988181, 0x9ed14f4f, 0xa37fdcdc, 0x44662222, 0x547e2a2a, - 0x3bab9090, 0x0b838888, 0x8cca4646, 0xc729eeee, 0x6bd3b8b8, 0x283c1414, 0xa779dede, 0xbce25e5e, 0x161d0b0b, - 0xad76dbdb, 0xdb3be0e0, 0x64563232, 0x744e3a3a, 0x141e0a0a, 0x92db4949, 0x0c0a0606, 0x486c2424, 0xb8e45c5c, - 0x9f5dc2c2, 0xbd6ed3d3, 0x43efacac, 0xc4a66262, 0x39a89191, 0x31a49595, 0xd337e4e4, 0xf28b7979, 0xd532e7e7, - 0x8b43c8c8, 0x6e593737, 0xdab76d6d, 0x018c8d8d, 0xb164d5d5, 0x9cd24e4e, 0x49e0a9a9, 0xd8b46c6c, 0xacfa5656, - 0xf307f4f4, 0xcf25eaea, 0xcaaf6565, 0xf48e7a7a, 0x47e9aeae, 0x10180808, 0x6fd5baba, 0xf0887878, 0x4a6f2525, - 0x5c722e2e, 0x38241c1c, 0x57f1a6a6, 0x73c7b4b4, 0x9751c6c6, 0xcb23e8e8, 0xa17cdddd, 0xe89c7474, 0x3e211f1f, - 0x96dd4b4b, 0x61dcbdbd, 0x0d868b8b, 0x0f858a8a, 0xe0907070, 0x7c423e3e, 0x71c4b5b5, 0xccaa6666, 0x90d84848, - 0x06050303, 0xf701f6f6, 0x1c120e0e, 0xc2a36161, 0x6a5f3535, 0xaef95757, 0x69d0b9b9, 0x17918686, 0x9958c1c1, - 0x3a271d1d, 0x27b99e9e, 0xd938e1e1, 0xeb13f8f8, 0x2bb39898, 0x22331111, 0xd2bb6969, 0xa970d9d9, 0x07898e8e, - 0x33a79494, 0x2db69b9b, 0x3c221e1e, 0x15928787, 0xc920e9e9, 0x8749cece, 0xaaff5555, 0x50782828, 0xa57adfdf, - 0x038f8c8c, 0x59f8a1a1, 0x09808989, 0x1a170d0d, 0x65dabfbf, 0xd731e6e6, 0x84c64242, 0xd0b86868, 0x82c34141, - 0x29b09999, 0x5a772d2d, 0x1e110f0f, 0x7bcbb0b0, 0xa8fc5454, 0x6dd6bbbb, 0x2c3a1616 - }; - - private static final int[] Tinv0 = { 0x50a7f451, 0x5365417e, 0xc3a4171a, 0x965e273a, 0xcb6bab3b, 0xf1459d1f, - 0xab58faac, 0x9303e34b, 0x55fa3020, 0xf66d76ad, 0x9176cc88, 0x254c02f5, 0xfcd7e54f, 0xd7cb2ac5, 0x80443526, - 0x8fa362b5, 0x495ab1de, 0x671bba25, 0x980eea45, 0xe1c0fe5d, 0x02752fc3, 0x12f04c81, 0xa397468d, 0xc6f9d36b, - 0xe75f8f03, 0x959c9215, 0xeb7a6dbf, 0xda595295, 0x2d83bed4, 0xd3217458, 0x2969e049, 0x44c8c98e, 0x6a89c275, - 0x78798ef4, 0x6b3e5899, 0xdd71b927, 0xb64fe1be, 0x17ad88f0, 0x66ac20c9, 0xb43ace7d, 0x184adf63, 0x82311ae5, - 0x60335197, 0x457f5362, 0xe07764b1, 0x84ae6bbb, 0x1ca081fe, 0x942b08f9, 0x58684870, 0x19fd458f, 0x876cde94, - 0xb7f87b52, 0x23d373ab, 0xe2024b72, 0x578f1fe3, 0x2aab5566, 0x0728ebb2, 0x03c2b52f, 0x9a7bc586, 0xa50837d3, - 0xf2872830, 0xb2a5bf23, 0xba6a0302, 0x5c8216ed, 0x2b1ccf8a, 0x92b479a7, 0xf0f207f3, 0xa1e2694e, 0xcdf4da65, - 0xd5be0506, 0x1f6234d1, 0x8afea6c4, 0x9d532e34, 0xa055f3a2, 0x32e18a05, 0x75ebf6a4, 0x39ec830b, 0xaaef6040, - 0x069f715e, 0x51106ebd, 0xf98a213e, 0x3d06dd96, 0xae053edd, 0x46bde64d, 0xb58d5491, 0x055dc471, 0x6fd40604, - 0xff155060, 0x24fb9819, 0x97e9bdd6, 0xcc434089, 0x779ed967, 0xbd42e8b0, 0x888b8907, 0x385b19e7, 0xdbeec879, - 0x470a7ca1, 0xe90f427c, 0xc91e84f8, 0x00000000, 0x83868009, 0x48ed2b32, 0xac70111e, 0x4e725a6c, 0xfbff0efd, - 0x5638850f, 0x1ed5ae3d, 0x27392d36, 0x64d90f0a, 0x21a65c68, 0xd1545b9b, 0x3a2e3624, 0xb1670a0c, 0x0fe75793, - 0xd296eeb4, 0x9e919b1b, 0x4fc5c080, 0xa220dc61, 0x694b775a, 0x161a121c, 0x0aba93e2, 0xe52aa0c0, 0x43e0223c, - 0x1d171b12, 0x0b0d090e, 0xadc78bf2, 0xb9a8b62d, 0xc8a91e14, 0x8519f157, 0x4c0775af, 0xbbdd99ee, 0xfd607fa3, - 0x9f2601f7, 0xbcf5725c, 0xc53b6644, 0x347efb5b, 0x7629438b, 0xdcc623cb, 0x68fcedb6, 0x63f1e4b8, 0xcadc31d7, - 0x10856342, 0x40229713, 0x2011c684, 0x7d244a85, 0xf83dbbd2, 0x1132f9ae, 0x6da129c7, 0x4b2f9e1d, 0xf330b2dc, - 0xec52860d, 0xd0e3c177, 0x6c16b32b, 0x99b970a9, 0xfa489411, 0x2264e947, 0xc48cfca8, 0x1a3ff0a0, 0xd82c7d56, - 0xef903322, 0xc74e4987, 0xc1d138d9, 0xfea2ca8c, 0x360bd498, 0xcf81f5a6, 0x28de7aa5, 0x268eb7da, 0xa4bfad3f, - 0xe49d3a2c, 0x0d927850, 0x9bcc5f6a, 0x62467e54, 0xc2138df6, 0xe8b8d890, 0x5ef7392e, 0xf5afc382, 0xbe805d9f, - 0x7c93d069, 0xa92dd56f, 0xb31225cf, 0x3b99acc8, 0xa77d1810, 0x6e639ce8, 0x7bbb3bdb, 0x097826cd, 0xf418596e, - 0x01b79aec, 0xa89a4f83, 0x656e95e6, 0x7ee6ffaa, 0x08cfbc21, 0xe6e815ef, 0xd99be7ba, 0xce366f4a, 0xd4099fea, - 0xd67cb029, 0xafb2a431, 0x31233f2a, 0x3094a5c6, 0xc066a235, 0x37bc4e74, 0xa6ca82fc, 0xb0d090e0, 0x15d8a733, - 0x4a9804f1, 0xf7daec41, 0x0e50cd7f, 0x2ff69117, 0x8dd64d76, 0x4db0ef43, 0x544daacc, 0xdf0496e4, 0xe3b5d19e, - 0x1b886a4c, 0xb81f2cc1, 0x7f516546, 0x04ea5e9d, 0x5d358c01, 0x737487fa, 0x2e410bfb, 0x5a1d67b3, 0x52d2db92, - 0x335610e9, 0x1347d66d, 0x8c61d79a, 0x7a0ca137, 0x8e14f859, 0x893c13eb, 0xee27a9ce, 0x35c961b7, 0xede51ce1, - 0x3cb1477a, 0x59dfd29c, 0x3f73f255, 0x79ce1418, 0xbf37c773, 0xeacdf753, 0x5baafd5f, 0x146f3ddf, 0x86db4478, - 0x81f3afca, 0x3ec468b9, 0x2c342438, 0x5f40a3c2, 0x72c31d16, 0x0c25e2bc, 0x8b493c28, 0x41950dff, 0x7101a839, - 0xdeb30c08, 0x9ce4b4d8, 0x90c15664, 0x6184cb7b, 0x70b632d5, 0x745c6c48, 0x4257b8d0 - }; - - private static final int[] Tinv1 = { 0xa7f45150, 0x65417e53, 0xa4171ac3, 0x5e273a96, 0x6bab3bcb, 0x459d1ff1, - 0x58faacab, 0x03e34b93, 0xfa302055, 0x6d76adf6, 0x76cc8891, 0x4c02f525, 0xd7e54ffc, 0xcb2ac5d7, 0x44352680, - 0xa362b58f, 0x5ab1de49, 0x1bba2567, 0x0eea4598, 0xc0fe5de1, 0x752fc302, 0xf04c8112, 0x97468da3, 0xf9d36bc6, - 0x5f8f03e7, 0x9c921595, 0x7a6dbfeb, 0x595295da, 0x83bed42d, 0x217458d3, 0x69e04929, 0xc8c98e44, 0x89c2756a, - 0x798ef478, 0x3e58996b, 0x71b927dd, 0x4fe1beb6, 0xad88f017, 0xac20c966, 0x3ace7db4, 0x4adf6318, 0x311ae582, - 0x33519760, 0x7f536245, 0x7764b1e0, 0xae6bbb84, 0xa081fe1c, 0x2b08f994, 0x68487058, 0xfd458f19, 0x6cde9487, - 0xf87b52b7, 0xd373ab23, 0x024b72e2, 0x8f1fe357, 0xab55662a, 0x28ebb207, 0xc2b52f03, 0x7bc5869a, 0x0837d3a5, - 0x872830f2, 0xa5bf23b2, 0x6a0302ba, 0x8216ed5c, 0x1ccf8a2b, 0xb479a792, 0xf207f3f0, 0xe2694ea1, 0xf4da65cd, - 0xbe0506d5, 0x6234d11f, 0xfea6c48a, 0x532e349d, 0x55f3a2a0, 0xe18a0532, 0xebf6a475, 0xec830b39, 0xef6040aa, - 0x9f715e06, 0x106ebd51, 0x8a213ef9, 0x06dd963d, 0x053eddae, 0xbde64d46, 0x8d5491b5, 0x5dc47105, 0xd406046f, - 0x155060ff, 0xfb981924, 0xe9bdd697, 0x434089cc, 0x9ed96777, 0x42e8b0bd, 0x8b890788, 0x5b19e738, 0xeec879db, - 0x0a7ca147, 0x0f427ce9, 0x1e84f8c9, 0x00000000, 0x86800983, 0xed2b3248, 0x70111eac, 0x725a6c4e, 0xff0efdfb, - 0x38850f56, 0xd5ae3d1e, 0x392d3627, 0xd90f0a64, 0xa65c6821, 0x545b9bd1, 0x2e36243a, 0x670a0cb1, 0xe757930f, - 0x96eeb4d2, 0x919b1b9e, 0xc5c0804f, 0x20dc61a2, 0x4b775a69, 0x1a121c16, 0xba93e20a, 0x2aa0c0e5, 0xe0223c43, - 0x171b121d, 0x0d090e0b, 0xc78bf2ad, 0xa8b62db9, 0xa91e14c8, 0x19f15785, 0x0775af4c, 0xdd99eebb, 0x607fa3fd, - 0x2601f79f, 0xf5725cbc, 0x3b6644c5, 0x7efb5b34, 0x29438b76, 0xc623cbdc, 0xfcedb668, 0xf1e4b863, 0xdc31d7ca, - 0x85634210, 0x22971340, 0x11c68420, 0x244a857d, 0x3dbbd2f8, 0x32f9ae11, 0xa129c76d, 0x2f9e1d4b, 0x30b2dcf3, - 0x52860dec, 0xe3c177d0, 0x16b32b6c, 0xb970a999, 0x489411fa, 0x64e94722, 0x8cfca8c4, 0x3ff0a01a, 0x2c7d56d8, - 0x903322ef, 0x4e4987c7, 0xd138d9c1, 0xa2ca8cfe, 0x0bd49836, 0x81f5a6cf, 0xde7aa528, 0x8eb7da26, 0xbfad3fa4, - 0x9d3a2ce4, 0x9278500d, 0xcc5f6a9b, 0x467e5462, 0x138df6c2, 0xb8d890e8, 0xf7392e5e, 0xafc382f5, 0x805d9fbe, - 0x93d0697c, 0x2dd56fa9, 0x1225cfb3, 0x99acc83b, 0x7d1810a7, 0x639ce86e, 0xbb3bdb7b, 0x7826cd09, 0x18596ef4, - 0xb79aec01, 0x9a4f83a8, 0x6e95e665, 0xe6ffaa7e, 0xcfbc2108, 0xe815efe6, 0x9be7bad9, 0x366f4ace, 0x099fead4, - 0x7cb029d6, 0xb2a431af, 0x233f2a31, 0x94a5c630, 0x66a235c0, 0xbc4e7437, 0xca82fca6, 0xd090e0b0, 0xd8a73315, - 0x9804f14a, 0xdaec41f7, 0x50cd7f0e, 0xf691172f, 0xd64d768d, 0xb0ef434d, 0x4daacc54, 0x0496e4df, 0xb5d19ee3, - 0x886a4c1b, 0x1f2cc1b8, 0x5165467f, 0xea5e9d04, 0x358c015d, 0x7487fa73, 0x410bfb2e, 0x1d67b35a, 0xd2db9252, - 0x5610e933, 0x47d66d13, 0x61d79a8c, 0x0ca1377a, 0x14f8598e, 0x3c13eb89, 0x27a9ceee, 0xc961b735, 0xe51ce1ed, - 0xb1477a3c, 0xdfd29c59, 0x73f2553f, 0xce141879, 0x37c773bf, 0xcdf753ea, 0xaafd5f5b, 0x6f3ddf14, 0xdb447886, - 0xf3afca81, 0xc468b93e, 0x3424382c, 0x40a3c25f, 0xc31d1672, 0x25e2bc0c, 0x493c288b, 0x950dff41, 0x01a83971, - 0xb30c08de, 0xe4b4d89c, 0xc1566490, 0x84cb7b61, 0xb632d570, 0x5c6c4874, 0x57b8d042 - }; - - private static final int[] Tinv2 = { 0xf45150a7, 0x417e5365, 0x171ac3a4, 0x273a965e, 0xab3bcb6b, 0x9d1ff145, - 0xfaacab58, 0xe34b9303, 0x302055fa, 0x76adf66d, 0xcc889176, 0x02f5254c, 0xe54ffcd7, 0x2ac5d7cb, 0x35268044, - 0x62b58fa3, 0xb1de495a, 0xba25671b, 0xea45980e, 0xfe5de1c0, 0x2fc30275, 0x4c8112f0, 0x468da397, 0xd36bc6f9, - 0x8f03e75f, 0x9215959c, 0x6dbfeb7a, 0x5295da59, 0xbed42d83, 0x7458d321, 0xe0492969, 0xc98e44c8, 0xc2756a89, - 0x8ef47879, 0x58996b3e, 0xb927dd71, 0xe1beb64f, 0x88f017ad, 0x20c966ac, 0xce7db43a, 0xdf63184a, 0x1ae58231, - 0x51976033, 0x5362457f, 0x64b1e077, 0x6bbb84ae, 0x81fe1ca0, 0x08f9942b, 0x48705868, 0x458f19fd, 0xde94876c, - 0x7b52b7f8, 0x73ab23d3, 0x4b72e202, 0x1fe3578f, 0x55662aab, 0xebb20728, 0xb52f03c2, 0xc5869a7b, 0x37d3a508, - 0x2830f287, 0xbf23b2a5, 0x0302ba6a, 0x16ed5c82, 0xcf8a2b1c, 0x79a792b4, 0x07f3f0f2, 0x694ea1e2, 0xda65cdf4, - 0x0506d5be, 0x34d11f62, 0xa6c48afe, 0x2e349d53, 0xf3a2a055, 0x8a0532e1, 0xf6a475eb, 0x830b39ec, 0x6040aaef, - 0x715e069f, 0x6ebd5110, 0x213ef98a, 0xdd963d06, 0x3eddae05, 0xe64d46bd, 0x5491b58d, 0xc471055d, 0x06046fd4, - 0x5060ff15, 0x981924fb, 0xbdd697e9, 0x4089cc43, 0xd967779e, 0xe8b0bd42, 0x8907888b, 0x19e7385b, 0xc879dbee, - 0x7ca1470a, 0x427ce90f, 0x84f8c91e, 0x00000000, 0x80098386, 0x2b3248ed, 0x111eac70, 0x5a6c4e72, 0x0efdfbff, - 0x850f5638, 0xae3d1ed5, 0x2d362739, 0x0f0a64d9, 0x5c6821a6, 0x5b9bd154, 0x36243a2e, 0x0a0cb167, 0x57930fe7, - 0xeeb4d296, 0x9b1b9e91, 0xc0804fc5, 0xdc61a220, 0x775a694b, 0x121c161a, 0x93e20aba, 0xa0c0e52a, 0x223c43e0, - 0x1b121d17, 0x090e0b0d, 0x8bf2adc7, 0xb62db9a8, 0x1e14c8a9, 0xf1578519, 0x75af4c07, 0x99eebbdd, 0x7fa3fd60, - 0x01f79f26, 0x725cbcf5, 0x6644c53b, 0xfb5b347e, 0x438b7629, 0x23cbdcc6, 0xedb668fc, 0xe4b863f1, 0x31d7cadc, - 0x63421085, 0x97134022, 0xc6842011, 0x4a857d24, 0xbbd2f83d, 0xf9ae1132, 0x29c76da1, 0x9e1d4b2f, 0xb2dcf330, - 0x860dec52, 0xc177d0e3, 0xb32b6c16, 0x70a999b9, 0x9411fa48, 0xe9472264, 0xfca8c48c, 0xf0a01a3f, 0x7d56d82c, - 0x3322ef90, 0x4987c74e, 0x38d9c1d1, 0xca8cfea2, 0xd498360b, 0xf5a6cf81, 0x7aa528de, 0xb7da268e, 0xad3fa4bf, - 0x3a2ce49d, 0x78500d92, 0x5f6a9bcc, 0x7e546246, 0x8df6c213, 0xd890e8b8, 0x392e5ef7, 0xc382f5af, 0x5d9fbe80, - 0xd0697c93, 0xd56fa92d, 0x25cfb312, 0xacc83b99, 0x1810a77d, 0x9ce86e63, 0x3bdb7bbb, 0x26cd0978, 0x596ef418, - 0x9aec01b7, 0x4f83a89a, 0x95e6656e, 0xffaa7ee6, 0xbc2108cf, 0x15efe6e8, 0xe7bad99b, 0x6f4ace36, 0x9fead409, - 0xb029d67c, 0xa431afb2, 0x3f2a3123, 0xa5c63094, 0xa235c066, 0x4e7437bc, 0x82fca6ca, 0x90e0b0d0, 0xa73315d8, - 0x04f14a98, 0xec41f7da, 0xcd7f0e50, 0x91172ff6, 0x4d768dd6, 0xef434db0, 0xaacc544d, 0x96e4df04, 0xd19ee3b5, - 0x6a4c1b88, 0x2cc1b81f, 0x65467f51, 0x5e9d04ea, 0x8c015d35, 0x87fa7374, 0x0bfb2e41, 0x67b35a1d, 0xdb9252d2, - 0x10e93356, 0xd66d1347, 0xd79a8c61, 0xa1377a0c, 0xf8598e14, 0x13eb893c, 0xa9ceee27, 0x61b735c9, 0x1ce1ede5, - 0x477a3cb1, 0xd29c59df, 0xf2553f73, 0x141879ce, 0xc773bf37, 0xf753eacd, 0xfd5f5baa, 0x3ddf146f, 0x447886db, - 0xafca81f3, 0x68b93ec4, 0x24382c34, 0xa3c25f40, 0x1d1672c3, 0xe2bc0c25, 0x3c288b49, 0x0dff4195, 0xa8397101, - 0x0c08deb3, 0xb4d89ce4, 0x566490c1, 0xcb7b6184, 0x32d570b6, 0x6c48745c, 0xb8d04257 - }; - - private static final int[] Tinv3 = { 0x5150a7f4, 0x7e536541, 0x1ac3a417, 0x3a965e27, 0x3bcb6bab, 0x1ff1459d, - 0xacab58fa, 0x4b9303e3, 0x2055fa30, 0xadf66d76, 0x889176cc, 0xf5254c02, 0x4ffcd7e5, 0xc5d7cb2a, 0x26804435, - 0xb58fa362, 0xde495ab1, 0x25671bba, 0x45980eea, 0x5de1c0fe, 0xc302752f, 0x8112f04c, 0x8da39746, 0x6bc6f9d3, - 0x03e75f8f, 0x15959c92, 0xbfeb7a6d, 0x95da5952, 0xd42d83be, 0x58d32174, 0x492969e0, 0x8e44c8c9, 0x756a89c2, - 0xf478798e, 0x996b3e58, 0x27dd71b9, 0xbeb64fe1, 0xf017ad88, 0xc966ac20, 0x7db43ace, 0x63184adf, 0xe582311a, - 0x97603351, 0x62457f53, 0xb1e07764, 0xbb84ae6b, 0xfe1ca081, 0xf9942b08, 0x70586848, 0x8f19fd45, 0x94876cde, - 0x52b7f87b, 0xab23d373, 0x72e2024b, 0xe3578f1f, 0x662aab55, 0xb20728eb, 0x2f03c2b5, 0x869a7bc5, 0xd3a50837, - 0x30f28728, 0x23b2a5bf, 0x02ba6a03, 0xed5c8216, 0x8a2b1ccf, 0xa792b479, 0xf3f0f207, 0x4ea1e269, 0x65cdf4da, - 0x06d5be05, 0xd11f6234, 0xc48afea6, 0x349d532e, 0xa2a055f3, 0x0532e18a, 0xa475ebf6, 0x0b39ec83, 0x40aaef60, - 0x5e069f71, 0xbd51106e, 0x3ef98a21, 0x963d06dd, 0xddae053e, 0x4d46bde6, 0x91b58d54, 0x71055dc4, 0x046fd406, - 0x60ff1550, 0x1924fb98, 0xd697e9bd, 0x89cc4340, 0x67779ed9, 0xb0bd42e8, 0x07888b89, 0xe7385b19, 0x79dbeec8, - 0xa1470a7c, 0x7ce90f42, 0xf8c91e84, 0x00000000, 0x09838680, 0x3248ed2b, 0x1eac7011, 0x6c4e725a, 0xfdfbff0e, - 0x0f563885, 0x3d1ed5ae, 0x3627392d, 0x0a64d90f, 0x6821a65c, 0x9bd1545b, 0x243a2e36, 0x0cb1670a, 0x930fe757, - 0xb4d296ee, 0x1b9e919b, 0x804fc5c0, 0x61a220dc, 0x5a694b77, 0x1c161a12, 0xe20aba93, 0xc0e52aa0, 0x3c43e022, - 0x121d171b, 0x0e0b0d09, 0xf2adc78b, 0x2db9a8b6, 0x14c8a91e, 0x578519f1, 0xaf4c0775, 0xeebbdd99, 0xa3fd607f, - 0xf79f2601, 0x5cbcf572, 0x44c53b66, 0x5b347efb, 0x8b762943, 0xcbdcc623, 0xb668fced, 0xb863f1e4, 0xd7cadc31, - 0x42108563, 0x13402297, 0x842011c6, 0x857d244a, 0xd2f83dbb, 0xae1132f9, 0xc76da129, 0x1d4b2f9e, 0xdcf330b2, - 0x0dec5286, 0x77d0e3c1, 0x2b6c16b3, 0xa999b970, 0x11fa4894, 0x472264e9, 0xa8c48cfc, 0xa01a3ff0, 0x56d82c7d, - 0x22ef9033, 0x87c74e49, 0xd9c1d138, 0x8cfea2ca, 0x98360bd4, 0xa6cf81f5, 0xa528de7a, 0xda268eb7, 0x3fa4bfad, - 0x2ce49d3a, 0x500d9278, 0x6a9bcc5f, 0x5462467e, 0xf6c2138d, 0x90e8b8d8, 0x2e5ef739, 0x82f5afc3, 0x9fbe805d, - 0x697c93d0, 0x6fa92dd5, 0xcfb31225, 0xc83b99ac, 0x10a77d18, 0xe86e639c, 0xdb7bbb3b, 0xcd097826, 0x6ef41859, - 0xec01b79a, 0x83a89a4f, 0xe6656e95, 0xaa7ee6ff, 0x2108cfbc, 0xefe6e815, 0xbad99be7, 0x4ace366f, 0xead4099f, - 0x29d67cb0, 0x31afb2a4, 0x2a31233f, 0xc63094a5, 0x35c066a2, 0x7437bc4e, 0xfca6ca82, 0xe0b0d090, 0x3315d8a7, - 0xf14a9804, 0x41f7daec, 0x7f0e50cd, 0x172ff691, 0x768dd64d, 0x434db0ef, 0xcc544daa, 0xe4df0496, 0x9ee3b5d1, - 0x4c1b886a, 0xc1b81f2c, 0x467f5165, 0x9d04ea5e, 0x015d358c, 0xfa737487, 0xfb2e410b, 0xb35a1d67, 0x9252d2db, - 0xe9335610, 0x6d1347d6, 0x9a8c61d7, 0x377a0ca1, 0x598e14f8, 0xeb893c13, 0xceee27a9, 0xb735c961, 0xe1ede51c, - 0x7a3cb147, 0x9c59dfd2, 0x553f73f2, 0x1879ce14, 0x73bf37c7, 0x53eacdf7, 0x5f5baafd, 0xdf146f3d, 0x7886db44, - 0xca81f3af, 0xb93ec468, 0x382c3424, 0xc25f40a3, 0x1672c31d, 0xbc0c25e2, 0x288b493c, 0xff41950d, 0x397101a8, - 0x08deb30c, 0xd89ce4b4, 0x6490c156, 0x7b6184cb, 0xd570b632, 0x48745c6c, 0xd04257b8 - }; - - private final int shift(int r, int shift) { - return (((r >>> shift) | (r << (32 - shift)))); - } - - /* multiply four bytes in GF(2^8) by 'x' {02} in parallel */ - - private static final int m1 = 0x80808080; - private static final int m2 = 0x7f7f7f7f; - private static final int m3 = 0x0000001b; - - private final int FFmulX(int x) { - return (((x & m2) << 1) ^ (((x & m1) >>> 7) * m3)); - } - - /* - * The following defines provide alternative definitions of FFmulX that - * might give improved performance if a fast 32-bit multiply is not - * available. - * - * private int FFmulX(int x) { int u = x & m1; u |= (u >> 1); return ((x & - * m2) < < 1) ^ ((u >>> 3) | (u >>> 6)); } private static final int m4 = - * 0x1b1b1b1b; private int FFmulX(int x) { int u = x & m1; return ((x & m2) < < - * 1) ^ ((u - (u >>> 7)) & m4); } - * - */ - - private final int inv_mcol(int x) { - int f2 = FFmulX(x); - int f4 = FFmulX(f2); - int f8 = FFmulX(f4); - int f9 = x ^ f8; - return f2 ^ f4 ^ f8 ^ shift(f2 ^ f9, 8) ^ shift(f4 ^ f9, 16) ^ shift(f9, 24); - } - - private final int subWord(int x) { - return (S[x & 255] & 255 | ((S[(x >> 8) & 255] & 255) << 8) | ((S[(x >> 16) & 255] & 255) << 16) | S[(x >> 24) & 255] << 24); - } - - /** - * Calculate the necessary round keys The number of calculations depends on - * key size and block size AES specified a fixed block size of 128 bits and - * key sizes 128/192/256 bits This code is written assuming those are the - * only possible values - */ - private final int[][] generateWorkingKey(byte[] key, boolean forEncryption) { - int KC = key.length / 4; // key length in words - int t; - - if (((KC != 4) && (KC != 6) && (KC != 8)) || ((KC * 4) != key.length)) { - throw new IllegalArgumentException("Key length not 128/192/256 bits."); - } - - ROUNDS = KC + 6; // This is not always true for the generalized - // Rijndael that allows larger block sizes - int[][] W = new int[ROUNDS + 1][4]; // 4 words in a block - // - // copy the key into the round key array - // - t = 0; - - for (int i = 0; i < key.length; t++) { - W[t >> 2][t & 3] = (key[i] & 0xff) | ((key[i + 1] & 0xff) << 8) | ((key[i + 2] & 0xff) << 16) - | (key[i + 3] << 24); - i += 4; - } - - // - // while not enough round key material calculated - // calculate new values - // - int k = (ROUNDS + 1) << 2; - - for (int i = KC; (i < k); i++) { - int temp = W[(i - 1) >> 2][(i - 1) & 3]; - - if ((i % KC) == 0) { - temp = subWord(shift(temp, 8)) ^ rcon[(i / KC) - 1]; - } - else if ((KC > 6) && ((i % KC) == 4)) { - temp = subWord(temp); - } - - W[i >> 2][i & 3] = W[(i - KC) >> 2][(i - KC) & 3] ^ temp; - } - - if (!forEncryption) { - for (int j = 1; j < ROUNDS; j++) { - for (int i = 0; i < 4; i++) { - W[j][i] = inv_mcol(W[j][i]); - } - } - } - - return W; - } - - private int ROUNDS; - private int[][] WorkingKey = null; - private int C0, C1, C2, C3; - private boolean doEncrypt; - - private static final int BLOCK_SIZE = 16; - - /** - * default constructor - 128 bit block size. - */ - public AES() { - } - - /** - * initialise an AES cipher. - * - * @param forEncryption - * whether or not we are for encryption. - * @param key - * the key required to set up the cipher. - * @exception IllegalArgumentException - * if the params argument is inappropriate. - */ - - public final void init(boolean forEncryption, byte[] key) { - WorkingKey = generateWorkingKey(key, forEncryption); - this.doEncrypt = forEncryption; - } - - public final String getAlgorithmName() { - return "AES"; - } - - public final int getBlockSize() { - return BLOCK_SIZE; - } - - public final int processBlock(byte[] in, int inOff, byte[] out, int outOff) { - if (WorkingKey == null) { - throw new IllegalStateException("AES engine not initialised"); - } - - if ((inOff + (32 / 2)) > in.length) { - throw new IllegalArgumentException("input buffer too short"); - } - - if ((outOff + (32 / 2)) > out.length) { - throw new IllegalArgumentException("output buffer too short"); - } - - if (doEncrypt) { - unpackBlock(in, inOff); - encryptBlock(WorkingKey); - packBlock(out, outOff); - } - else { - unpackBlock(in, inOff); - decryptBlock(WorkingKey); - packBlock(out, outOff); - } - - return BLOCK_SIZE; - } - - public final void reset() { - } - - private final void unpackBlock(byte[] bytes, int off) { - int index = off; - C0 = (bytes[index++] & 0xff); - C0 |= (bytes[index++] & 0xff) << 8; - C0 |= (bytes[index++] & 0xff) << 16; - C0 |= bytes[index++] << 24; - C1 = (bytes[index++] & 0xff); - C1 |= (bytes[index++] & 0xff) << 8; - C1 |= (bytes[index++] & 0xff) << 16; - C1 |= bytes[index++] << 24; - C2 = (bytes[index++] & 0xff); - C2 |= (bytes[index++] & 0xff) << 8; - C2 |= (bytes[index++] & 0xff) << 16; - C2 |= bytes[index++] << 24; - C3 = (bytes[index++] & 0xff); - C3 |= (bytes[index++] & 0xff) << 8; - C3 |= (bytes[index++] & 0xff) << 16; - C3 |= bytes[index++] << 24; - } - - private final void packBlock(byte[] bytes, int off) { - int index = off; - bytes[index++] = (byte) C0; - bytes[index++] = (byte)(C0 >> 8); - bytes[index++] = (byte)(C0 >> 16); - bytes[index++] = (byte)(C0 >> 24); - bytes[index++] = (byte) C1; - bytes[index++] = (byte)(C1 >> 8); - bytes[index++] = (byte)(C1 >> 16); - bytes[index++] = (byte)(C1 >> 24); - bytes[index++] = (byte) C2; - bytes[index++] = (byte)(C2 >> 8); - bytes[index++] = (byte)(C2 >> 16); - bytes[index++] = (byte)(C2 >> 24); - bytes[index++] = (byte) C3; - bytes[index++] = (byte)(C3 >> 8); - bytes[index++] = (byte)(C3 >> 16); - bytes[index++] = (byte)(C3 >> 24); - } - - private final void encryptBlock(int[][] KW) { - int r, r0, r1, r2, r3; - C0 ^= KW[0][0]; - C1 ^= KW[0][1]; - C2 ^= KW[0][2]; - C3 ^= KW[0][3]; - - for (r = 1; r < ROUNDS - 1;) { - r0 = T0[C0 & 255] ^ T1[(C1 >> 8) & 255] ^ T2[(C2 >> 16) & 255] ^ T3[(C3 >> 24) & 255] ^ KW[r][0]; - r1 = T0[C1 & 255] ^ T1[(C2 >> 8) & 255] ^ T2[(C3 >> 16) & 255] ^ T3[(C0 >> 24) & 255] ^ KW[r][1]; - r2 = T0[C2 & 255] ^ T1[(C3 >> 8) & 255] ^ T2[(C0 >> 16) & 255] ^ T3[(C1 >> 24) & 255] ^ KW[r][2]; - r3 = T0[C3 & 255] ^ T1[(C0 >> 8) & 255] ^ T2[(C1 >> 16) & 255] ^ T3[(C2 >> 24) & 255] ^ KW[r++][3]; - C0 = T0[r0 & 255] ^ T1[(r1 >> 8) & 255] ^ T2[(r2 >> 16) & 255] ^ T3[(r3 >> 24) & 255] ^ KW[r][0]; - C1 = T0[r1 & 255] ^ T1[(r2 >> 8) & 255] ^ T2[(r3 >> 16) & 255] ^ T3[(r0 >> 24) & 255] ^ KW[r][1]; - C2 = T0[r2 & 255] ^ T1[(r3 >> 8) & 255] ^ T2[(r0 >> 16) & 255] ^ T3[(r1 >> 24) & 255] ^ KW[r][2]; - C3 = T0[r3 & 255] ^ T1[(r0 >> 8) & 255] ^ T2[(r1 >> 16) & 255] ^ T3[(r2 >> 24) & 255] ^ KW[r++][3]; - } - - r0 = T0[C0 & 255] ^ T1[(C1 >> 8) & 255] ^ T2[(C2 >> 16) & 255] ^ T3[(C3 >> 24) & 255] ^ KW[r][0]; - r1 = T0[C1 & 255] ^ T1[(C2 >> 8) & 255] ^ T2[(C3 >> 16) & 255] ^ T3[(C0 >> 24) & 255] ^ KW[r][1]; - r2 = T0[C2 & 255] ^ T1[(C3 >> 8) & 255] ^ T2[(C0 >> 16) & 255] ^ T3[(C1 >> 24) & 255] ^ KW[r][2]; - r3 = T0[C3 & 255] ^ T1[(C0 >> 8) & 255] ^ T2[(C1 >> 16) & 255] ^ T3[(C2 >> 24) & 255] ^ KW[r++][3]; - // the final round's table is a simple function of S so we don't use a - // whole other four tables for it - C0 = (S[r0 & 255] & 255) ^ ((S[(r1 >> 8) & 255] & 255) << 8) ^ ((S[(r2 >> 16) & 255] & 255) << 16) - ^ (S[(r3 >> 24) & 255] << 24) ^ KW[r][0]; - C1 = (S[r1 & 255] & 255) ^ ((S[(r2 >> 8) & 255] & 255) << 8) ^ ((S[(r3 >> 16) & 255] & 255) << 16) - ^ (S[(r0 >> 24) & 255] << 24) ^ KW[r][1]; - C2 = (S[r2 & 255] & 255) ^ ((S[(r3 >> 8) & 255] & 255) << 8) ^ ((S[(r0 >> 16) & 255] & 255) << 16) - ^ (S[(r1 >> 24) & 255] << 24) ^ KW[r][2]; - C3 = (S[r3 & 255] & 255) ^ ((S[(r0 >> 8) & 255] & 255) << 8) ^ ((S[(r1 >> 16) & 255] & 255) << 16) - ^ (S[(r2 >> 24) & 255] << 24) ^ KW[r][3]; - } - - private final void decryptBlock(int[][] KW) { - int r, r0, r1, r2, r3; - C0 ^= KW[ROUNDS][0]; - C1 ^= KW[ROUNDS][1]; - C2 ^= KW[ROUNDS][2]; - C3 ^= KW[ROUNDS][3]; - - for (r = ROUNDS - 1; r > 1;) { - r0 = Tinv0[C0 & 255] ^ Tinv1[(C3 >> 8) & 255] ^ Tinv2[(C2 >> 16) & 255] ^ Tinv3[(C1 >> 24) & 255] - ^ KW[r][0]; - r1 = Tinv0[C1 & 255] ^ Tinv1[(C0 >> 8) & 255] ^ Tinv2[(C3 >> 16) & 255] ^ Tinv3[(C2 >> 24) & 255] - ^ KW[r][1]; - r2 = Tinv0[C2 & 255] ^ Tinv1[(C1 >> 8) & 255] ^ Tinv2[(C0 >> 16) & 255] ^ Tinv3[(C3 >> 24) & 255] - ^ KW[r][2]; - r3 = Tinv0[C3 & 255] ^ Tinv1[(C2 >> 8) & 255] ^ Tinv2[(C1 >> 16) & 255] ^ Tinv3[(C0 >> 24) & 255] - ^ KW[r--][3]; - C0 = Tinv0[r0 & 255] ^ Tinv1[(r3 >> 8) & 255] ^ Tinv2[(r2 >> 16) & 255] ^ Tinv3[(r1 >> 24) & 255] - ^ KW[r][0]; - C1 = Tinv0[r1 & 255] ^ Tinv1[(r0 >> 8) & 255] ^ Tinv2[(r3 >> 16) & 255] ^ Tinv3[(r2 >> 24) & 255] - ^ KW[r][1]; - C2 = Tinv0[r2 & 255] ^ Tinv1[(r1 >> 8) & 255] ^ Tinv2[(r0 >> 16) & 255] ^ Tinv3[(r3 >> 24) & 255] - ^ KW[r][2]; - C3 = Tinv0[r3 & 255] ^ Tinv1[(r2 >> 8) & 255] ^ Tinv2[(r1 >> 16) & 255] ^ Tinv3[(r0 >> 24) & 255] - ^ KW[r--][3]; - } - - r0 = Tinv0[C0 & 255] ^ Tinv1[(C3 >> 8) & 255] ^ Tinv2[(C2 >> 16) & 255] ^ Tinv3[(C1 >> 24) & 255] ^ KW[r][0]; - r1 = Tinv0[C1 & 255] ^ Tinv1[(C0 >> 8) & 255] ^ Tinv2[(C3 >> 16) & 255] ^ Tinv3[(C2 >> 24) & 255] ^ KW[r][1]; - r2 = Tinv0[C2 & 255] ^ Tinv1[(C1 >> 8) & 255] ^ Tinv2[(C0 >> 16) & 255] ^ Tinv3[(C3 >> 24) & 255] ^ KW[r][2]; - r3 = Tinv0[C3 & 255] ^ Tinv1[(C2 >> 8) & 255] ^ Tinv2[(C1 >> 16) & 255] ^ Tinv3[(C0 >> 24) & 255] ^ KW[r--][3]; - // the final round's table is a simple function of Si so we don't use a - // whole other four tables for it - C0 = (Si[r0 & 255] & 255) ^ ((Si[(r3 >> 8) & 255] & 255) << 8) ^ ((Si[(r2 >> 16) & 255] & 255) << 16) - ^ (Si[(r1 >> 24) & 255] << 24) ^ KW[0][0]; - C1 = (Si[r1 & 255] & 255) ^ ((Si[(r0 >> 8) & 255] & 255) << 8) ^ ((Si[(r3 >> 16) & 255] & 255) << 16) - ^ (Si[(r2 >> 24) & 255] << 24) ^ KW[0][1]; - C2 = (Si[r2 & 255] & 255) ^ ((Si[(r1 >> 8) & 255] & 255) << 8) ^ ((Si[(r0 >> 16) & 255] & 255) << 16) - ^ (Si[(r3 >> 24) & 255] << 24) ^ KW[0][2]; - C3 = (Si[r3 & 255] & 255) ^ ((Si[(r2 >> 8) & 255] & 255) << 8) ^ ((Si[(r1 >> 16) & 255] & 255) << 16) - ^ (Si[(r0 >> 24) & 255] << 24) ^ KW[0][3]; - } - - public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) { - processBlock(src, srcoff, dst, dstoff); - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/crypto/cipher/BlockCipher.java --- a/src/com/trilead/ssh2/crypto/cipher/BlockCipher.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -package com.trilead.ssh2.crypto.cipher; - -/** - * BlockCipher. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: BlockCipher.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public interface BlockCipher { - public void init(boolean forEncryption, byte[] key); - - public int getBlockSize(); - - public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff); -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/crypto/cipher/BlockCipherFactory.java --- a/src/com/trilead/ssh2/crypto/cipher/BlockCipherFactory.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,101 +0,0 @@ - -package com.trilead.ssh2.crypto.cipher; - -import java.util.Vector; - -/** - * BlockCipherFactory. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: BlockCipherFactory.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ - */ -public class BlockCipherFactory { - static class CipherEntry { - String type; - int blocksize; - int keysize; - String cipherClass; - - public CipherEntry(String type, int blockSize, int keySize, String cipherClass) { - this.type = type; - this.blocksize = blockSize; - this.keysize = keySize; - this.cipherClass = cipherClass; - } - } - - static Vector ciphers = new Vector(); - - static { - /* Higher Priority First */ - ciphers.addElement(new CipherEntry("aes256-ctr", 16, 32, "com.trilead.ssh2.crypto.cipher.AES")); - ciphers.addElement(new CipherEntry("aes192-ctr", 16, 24, "com.trilead.ssh2.crypto.cipher.AES")); - ciphers.addElement(new CipherEntry("aes128-ctr", 16, 16, "com.trilead.ssh2.crypto.cipher.AES")); - ciphers.addElement(new CipherEntry("blowfish-ctr", 8, 16, "com.trilead.ssh2.crypto.cipher.BlowFish")); - ciphers.addElement(new CipherEntry("aes256-cbc", 16, 32, "com.trilead.ssh2.crypto.cipher.AES")); - ciphers.addElement(new CipherEntry("aes192-cbc", 16, 24, "com.trilead.ssh2.crypto.cipher.AES")); - ciphers.addElement(new CipherEntry("aes128-cbc", 16, 16, "com.trilead.ssh2.crypto.cipher.AES")); - ciphers.addElement(new CipherEntry("blowfish-cbc", 8, 16, "com.trilead.ssh2.crypto.cipher.BlowFish")); - ciphers.addElement(new CipherEntry("3des-ctr", 8, 24, "com.trilead.ssh2.crypto.cipher.DESede")); - ciphers.addElement(new CipherEntry("3des-cbc", 8, 24, "com.trilead.ssh2.crypto.cipher.DESede")); - } - - public static String[] getDefaultCipherList() { - String list[] = new String[ciphers.size()]; - - for (int i = 0; i < ciphers.size(); i++) { - CipherEntry ce = ciphers.elementAt(i); - list[i] = new String(ce.type); - } - - return list; - } - - public static void checkCipherList(String[] cipherCandidates) { - for (int i = 0; i < cipherCandidates.length; i++) - getEntry(cipherCandidates[i]); - } - - public static BlockCipher createCipher(String type, boolean encrypt, byte[] key, byte[] iv) { - try { - CipherEntry ce = getEntry(type); - Class cc = Class.forName(ce.cipherClass); - BlockCipher bc = (BlockCipher) cc.newInstance(); - - if (type.endsWith("-cbc")) { - bc.init(encrypt, key); - return new CBCMode(bc, iv, encrypt); - } - else if (type.endsWith("-ctr")) { - bc.init(true, key); - return new CTRMode(bc, iv, encrypt); - } - - throw new IllegalArgumentException("Cannot instantiate " + type); - } - catch (Exception e) { - throw new IllegalArgumentException("Cannot instantiate " + type); - } - } - - private static CipherEntry getEntry(String type) { - for (int i = 0; i < ciphers.size(); i++) { - CipherEntry ce = ciphers.elementAt(i); - - if (ce.type.equals(type)) - return ce; - } - - throw new IllegalArgumentException("Unkown algorithm " + type); - } - - public static int getBlockSize(String type) { - CipherEntry ce = getEntry(type); - return ce.blocksize; - } - - public static int getKeySize(String type) { - CipherEntry ce = getEntry(type); - return ce.keysize; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/crypto/cipher/BlowFish.java --- a/src/com/trilead/ssh2/crypto/cipher/BlowFish.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,377 +0,0 @@ - -package com.trilead.ssh2.crypto.cipher; - -/* - This file was shamelessly taken from the Bouncy Castle Crypto package. - Their licence file states the following: - - Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle - (http://www.bouncycastle.org) - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - */ - -/** - * A class that provides Blowfish key encryption operations, such as encoding - * data and generating keys. All the algorithms herein are from Applied - * Cryptography and implement a simplified cryptography interface. - * - * @author See comments in the source file - * @version $Id: BlowFish.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class BlowFish implements BlockCipher { - - private final static int[] KP = { 0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, 0xA4093822, 0x299F31D0, - 0x082EFA98, 0xEC4E6C89, 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C, 0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, - 0xB5470917, 0x9216D5D9, 0x8979FB1B - }, - - KS0 = { 0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7, 0xB8E1AFED, 0x6A267E96, 0xBA7C9045, 0xF12C7F99, 0x24A19947, - 0xB3916CF7, 0x0801F2E2, 0x858EFC16, 0x636920D8, 0x71574E69, 0xA458FEA3, 0xF4933D7E, 0x0D95748F, 0x728EB658, - 0x718BCD58, 0x82154AEE, 0x7B54A41D, 0xC25A59B5, 0x9C30D539, 0x2AF26013, 0xC5D1B023, 0x286085F0, 0xCA417918, - 0xB8DB38EF, 0x8E79DCB0, 0x603A180E, 0x6C9E0E8B, 0xB01E8A3E, 0xD71577C1, 0xBD314B27, 0x78AF2FDA, 0x55605C60, - 0xE65525F3, 0xAA55AB94, 0x57489862, 0x63E81440, 0x55CA396A, 0x2AAB10B6, 0xB4CC5C34, 0x1141E8CE, 0xA15486AF, - 0x7C72E993, 0xB3EE1411, 0x636FBC2A, 0x2BA9C55D, 0x741831F6, 0xCE5C3E16, 0x9B87931E, 0xAFD6BA33, 0x6C24CF5C, - 0x7A325381, 0x28958677, 0x3B8F4898, 0x6B4BB9AF, 0xC4BFE81B, 0x66282193, 0x61D809CC, 0xFB21A991, 0x487CAC60, - 0x5DEC8032, 0xEF845D5D, 0xE98575B1, 0xDC262302, 0xEB651B88, 0x23893E81, 0xD396ACC5, 0x0F6D6FF3, 0x83F44239, - 0x2E0B4482, 0xA4842004, 0x69C8F04A, 0x9E1F9B5E, 0x21C66842, 0xF6E96C9A, 0x670C9C61, 0xABD388F0, 0x6A51A0D2, - 0xD8542F68, 0x960FA728, 0xAB5133A3, 0x6EEF0B6C, 0x137A3BE4, 0xBA3BF050, 0x7EFB2A98, 0xA1F1651D, 0x39AF0176, - 0x66CA593E, 0x82430E88, 0x8CEE8619, 0x456F9FB4, 0x7D84A5C3, 0x3B8B5EBE, 0xE06F75D8, 0x85C12073, 0x401A449F, - 0x56C16AA6, 0x4ED3AA62, 0x363F7706, 0x1BFEDF72, 0x429B023D, 0x37D0D724, 0xD00A1248, 0xDB0FEAD3, 0x49F1C09B, - 0x075372C9, 0x80991B7B, 0x25D479D8, 0xF6E8DEF7, 0xE3FE501A, 0xB6794C3B, 0x976CE0BD, 0x04C006BA, 0xC1A94FB6, - 0x409F60C4, 0x5E5C9EC2, 0x196A2463, 0x68FB6FAF, 0x3E6C53B5, 0x1339B2EB, 0x3B52EC6F, 0x6DFC511F, 0x9B30952C, - 0xCC814544, 0xAF5EBD09, 0xBEE3D004, 0xDE334AFD, 0x660F2807, 0x192E4BB3, 0xC0CBA857, 0x45C8740F, 0xD20B5F39, - 0xB9D3FBDB, 0x5579C0BD, 0x1A60320A, 0xD6A100C6, 0x402C7279, 0x679F25FE, 0xFB1FA3CC, 0x8EA5E9F8, 0xDB3222F8, - 0x3C7516DF, 0xFD616B15, 0x2F501EC8, 0xAD0552AB, 0x323DB5FA, 0xFD238760, 0x53317B48, 0x3E00DF82, 0x9E5C57BB, - 0xCA6F8CA0, 0x1A87562E, 0xDF1769DB, 0xD542A8F6, 0x287EFFC3, 0xAC6732C6, 0x8C4F5573, 0x695B27B0, 0xBBCA58C8, - 0xE1FFA35D, 0xB8F011A0, 0x10FA3D98, 0xFD2183B8, 0x4AFCB56C, 0x2DD1D35B, 0x9A53E479, 0xB6F84565, 0xD28E49BC, - 0x4BFB9790, 0xE1DDF2DA, 0xA4CB7E33, 0x62FB1341, 0xCEE4C6E8, 0xEF20CADA, 0x36774C01, 0xD07E9EFE, 0x2BF11FB4, - 0x95DBDA4D, 0xAE909198, 0xEAAD8E71, 0x6B93D5A0, 0xD08ED1D0, 0xAFC725E0, 0x8E3C5B2F, 0x8E7594B7, 0x8FF6E2FB, - 0xF2122B64, 0x8888B812, 0x900DF01C, 0x4FAD5EA0, 0x688FC31C, 0xD1CFF191, 0xB3A8C1AD, 0x2F2F2218, 0xBE0E1777, - 0xEA752DFE, 0x8B021FA1, 0xE5A0CC0F, 0xB56F74E8, 0x18ACF3D6, 0xCE89E299, 0xB4A84FE0, 0xFD13E0B7, 0x7CC43B81, - 0xD2ADA8D9, 0x165FA266, 0x80957705, 0x93CC7314, 0x211A1477, 0xE6AD2065, 0x77B5FA86, 0xC75442F5, 0xFB9D35CF, - 0xEBCDAF0C, 0x7B3E89A0, 0xD6411BD3, 0xAE1E7E49, 0x00250E2D, 0x2071B35E, 0x226800BB, 0x57B8E0AF, 0x2464369B, - 0xF009B91E, 0x5563911D, 0x59DFA6AA, 0x78C14389, 0xD95A537F, 0x207D5BA2, 0x02E5B9C5, 0x83260376, 0x6295CFA9, - 0x11C81968, 0x4E734A41, 0xB3472DCA, 0x7B14A94A, 0x1B510052, 0x9A532915, 0xD60F573F, 0xBC9BC6E4, 0x2B60A476, - 0x81E67400, 0x08BA6FB5, 0x571BE91F, 0xF296EC6B, 0x2A0DD915, 0xB6636521, 0xE7B9F9B6, 0xFF34052E, 0xC5855664, - 0x53B02D5D, 0xA99F8FA1, 0x08BA4799, 0x6E85076A - }, - - KS1 = { 0x4B7A70E9, 0xB5B32944, 0xDB75092E, 0xC4192623, 0xAD6EA6B0, 0x49A7DF7D, 0x9CEE60B8, 0x8FEDB266, 0xECAA8C71, - 0x699A17FF, 0x5664526C, 0xC2B19EE1, 0x193602A5, 0x75094C29, 0xA0591340, 0xE4183A3E, 0x3F54989A, 0x5B429D65, - 0x6B8FE4D6, 0x99F73FD6, 0xA1D29C07, 0xEFE830F5, 0x4D2D38E6, 0xF0255DC1, 0x4CDD2086, 0x8470EB26, 0x6382E9C6, - 0x021ECC5E, 0x09686B3F, 0x3EBAEFC9, 0x3C971814, 0x6B6A70A1, 0x687F3584, 0x52A0E286, 0xB79C5305, 0xAA500737, - 0x3E07841C, 0x7FDEAE5C, 0x8E7D44EC, 0x5716F2B8, 0xB03ADA37, 0xF0500C0D, 0xF01C1F04, 0x0200B3FF, 0xAE0CF51A, - 0x3CB574B2, 0x25837A58, 0xDC0921BD, 0xD19113F9, 0x7CA92FF6, 0x94324773, 0x22F54701, 0x3AE5E581, 0x37C2DADC, - 0xC8B57634, 0x9AF3DDA7, 0xA9446146, 0x0FD0030E, 0xECC8C73E, 0xA4751E41, 0xE238CD99, 0x3BEA0E2F, 0x3280BBA1, - 0x183EB331, 0x4E548B38, 0x4F6DB908, 0x6F420D03, 0xF60A04BF, 0x2CB81290, 0x24977C79, 0x5679B072, 0xBCAF89AF, - 0xDE9A771F, 0xD9930810, 0xB38BAE12, 0xDCCF3F2E, 0x5512721F, 0x2E6B7124, 0x501ADDE6, 0x9F84CD87, 0x7A584718, - 0x7408DA17, 0xBC9F9ABC, 0xE94B7D8C, 0xEC7AEC3A, 0xDB851DFA, 0x63094366, 0xC464C3D2, 0xEF1C1847, 0x3215D908, - 0xDD433B37, 0x24C2BA16, 0x12A14D43, 0x2A65C451, 0x50940002, 0x133AE4DD, 0x71DFF89E, 0x10314E55, 0x81AC77D6, - 0x5F11199B, 0x043556F1, 0xD7A3C76B, 0x3C11183B, 0x5924A509, 0xF28FE6ED, 0x97F1FBFA, 0x9EBABF2C, 0x1E153C6E, - 0x86E34570, 0xEAE96FB1, 0x860E5E0A, 0x5A3E2AB3, 0x771FE71C, 0x4E3D06FA, 0x2965DCB9, 0x99E71D0F, 0x803E89D6, - 0x5266C825, 0x2E4CC978, 0x9C10B36A, 0xC6150EBA, 0x94E2EA78, 0xA5FC3C53, 0x1E0A2DF4, 0xF2F74EA7, 0x361D2B3D, - 0x1939260F, 0x19C27960, 0x5223A708, 0xF71312B6, 0xEBADFE6E, 0xEAC31F66, 0xE3BC4595, 0xA67BC883, 0xB17F37D1, - 0x018CFF28, 0xC332DDEF, 0xBE6C5AA5, 0x65582185, 0x68AB9802, 0xEECEA50F, 0xDB2F953B, 0x2AEF7DAD, 0x5B6E2F84, - 0x1521B628, 0x29076170, 0xECDD4775, 0x619F1510, 0x13CCA830, 0xEB61BD96, 0x0334FE1E, 0xAA0363CF, 0xB5735C90, - 0x4C70A239, 0xD59E9E0B, 0xCBAADE14, 0xEECC86BC, 0x60622CA7, 0x9CAB5CAB, 0xB2F3846E, 0x648B1EAF, 0x19BDF0CA, - 0xA02369B9, 0x655ABB50, 0x40685A32, 0x3C2AB4B3, 0x319EE9D5, 0xC021B8F7, 0x9B540B19, 0x875FA099, 0x95F7997E, - 0x623D7DA8, 0xF837889A, 0x97E32D77, 0x11ED935F, 0x16681281, 0x0E358829, 0xC7E61FD6, 0x96DEDFA1, 0x7858BA99, - 0x57F584A5, 0x1B227263, 0x9B83C3FF, 0x1AC24696, 0xCDB30AEB, 0x532E3054, 0x8FD948E4, 0x6DBC3128, 0x58EBF2EF, - 0x34C6FFEA, 0xFE28ED61, 0xEE7C3C73, 0x5D4A14D9, 0xE864B7E3, 0x42105D14, 0x203E13E0, 0x45EEE2B6, 0xA3AAABEA, - 0xDB6C4F15, 0xFACB4FD0, 0xC742F442, 0xEF6ABBB5, 0x654F3B1D, 0x41CD2105, 0xD81E799E, 0x86854DC7, 0xE44B476A, - 0x3D816250, 0xCF62A1F2, 0x5B8D2646, 0xFC8883A0, 0xC1C7B6A3, 0x7F1524C3, 0x69CB7492, 0x47848A0B, 0x5692B285, - 0x095BBF00, 0xAD19489D, 0x1462B174, 0x23820E00, 0x58428D2A, 0x0C55F5EA, 0x1DADF43E, 0x233F7061, 0x3372F092, - 0x8D937E41, 0xD65FECF1, 0x6C223BDB, 0x7CDE3759, 0xCBEE7460, 0x4085F2A7, 0xCE77326E, 0xA6078084, 0x19F8509E, - 0xE8EFD855, 0x61D99735, 0xA969A7AA, 0xC50C06C2, 0x5A04ABFC, 0x800BCADC, 0x9E447A2E, 0xC3453484, 0xFDD56705, - 0x0E1E9EC9, 0xDB73DBD3, 0x105588CD, 0x675FDA79, 0xE3674340, 0xC5C43465, 0x713E38D8, 0x3D28F89E, 0xF16DFF20, - 0x153E21E7, 0x8FB03D4A, 0xE6E39F2B, 0xDB83ADF7 - }, - - KS2 = { 0xE93D5A68, 0x948140F7, 0xF64C261C, 0x94692934, 0x411520F7, 0x7602D4F7, 0xBCF46B2E, 0xD4A20068, 0xD4082471, - 0x3320F46A, 0x43B7D4B7, 0x500061AF, 0x1E39F62E, 0x97244546, 0x14214F74, 0xBF8B8840, 0x4D95FC1D, 0x96B591AF, - 0x70F4DDD3, 0x66A02F45, 0xBFBC09EC, 0x03BD9785, 0x7FAC6DD0, 0x31CB8504, 0x96EB27B3, 0x55FD3941, 0xDA2547E6, - 0xABCA0A9A, 0x28507825, 0x530429F4, 0x0A2C86DA, 0xE9B66DFB, 0x68DC1462, 0xD7486900, 0x680EC0A4, 0x27A18DEE, - 0x4F3FFEA2, 0xE887AD8C, 0xB58CE006, 0x7AF4D6B6, 0xAACE1E7C, 0xD3375FEC, 0xCE78A399, 0x406B2A42, 0x20FE9E35, - 0xD9F385B9, 0xEE39D7AB, 0x3B124E8B, 0x1DC9FAF7, 0x4B6D1856, 0x26A36631, 0xEAE397B2, 0x3A6EFA74, 0xDD5B4332, - 0x6841E7F7, 0xCA7820FB, 0xFB0AF54E, 0xD8FEB397, 0x454056AC, 0xBA489527, 0x55533A3A, 0x20838D87, 0xFE6BA9B7, - 0xD096954B, 0x55A867BC, 0xA1159A58, 0xCCA92963, 0x99E1DB33, 0xA62A4A56, 0x3F3125F9, 0x5EF47E1C, 0x9029317C, - 0xFDF8E802, 0x04272F70, 0x80BB155C, 0x05282CE3, 0x95C11548, 0xE4C66D22, 0x48C1133F, 0xC70F86DC, 0x07F9C9EE, - 0x41041F0F, 0x404779A4, 0x5D886E17, 0x325F51EB, 0xD59BC0D1, 0xF2BCC18F, 0x41113564, 0x257B7834, 0x602A9C60, - 0xDFF8E8A3, 0x1F636C1B, 0x0E12B4C2, 0x02E1329E, 0xAF664FD1, 0xCAD18115, 0x6B2395E0, 0x333E92E1, 0x3B240B62, - 0xEEBEB922, 0x85B2A20E, 0xE6BA0D99, 0xDE720C8C, 0x2DA2F728, 0xD0127845, 0x95B794FD, 0x647D0862, 0xE7CCF5F0, - 0x5449A36F, 0x877D48FA, 0xC39DFD27, 0xF33E8D1E, 0x0A476341, 0x992EFF74, 0x3A6F6EAB, 0xF4F8FD37, 0xA812DC60, - 0xA1EBDDF8, 0x991BE14C, 0xDB6E6B0D, 0xC67B5510, 0x6D672C37, 0x2765D43B, 0xDCD0E804, 0xF1290DC7, 0xCC00FFA3, - 0xB5390F92, 0x690FED0B, 0x667B9FFB, 0xCEDB7D9C, 0xA091CF0B, 0xD9155EA3, 0xBB132F88, 0x515BAD24, 0x7B9479BF, - 0x763BD6EB, 0x37392EB3, 0xCC115979, 0x8026E297, 0xF42E312D, 0x6842ADA7, 0xC66A2B3B, 0x12754CCC, 0x782EF11C, - 0x6A124237, 0xB79251E7, 0x06A1BBE6, 0x4BFB6350, 0x1A6B1018, 0x11CAEDFA, 0x3D25BDD8, 0xE2E1C3C9, 0x44421659, - 0x0A121386, 0xD90CEC6E, 0xD5ABEA2A, 0x64AF674E, 0xDA86A85F, 0xBEBFE988, 0x64E4C3FE, 0x9DBC8057, 0xF0F7C086, - 0x60787BF8, 0x6003604D, 0xD1FD8346, 0xF6381FB0, 0x7745AE04, 0xD736FCCC, 0x83426B33, 0xF01EAB71, 0xB0804187, - 0x3C005E5F, 0x77A057BE, 0xBDE8AE24, 0x55464299, 0xBF582E61, 0x4E58F48F, 0xF2DDFDA2, 0xF474EF38, 0x8789BDC2, - 0x5366F9C3, 0xC8B38E74, 0xB475F255, 0x46FCD9B9, 0x7AEB2661, 0x8B1DDF84, 0x846A0E79, 0x915F95E2, 0x466E598E, - 0x20B45770, 0x8CD55591, 0xC902DE4C, 0xB90BACE1, 0xBB8205D0, 0x11A86248, 0x7574A99E, 0xB77F19B6, 0xE0A9DC09, - 0x662D09A1, 0xC4324633, 0xE85A1F02, 0x09F0BE8C, 0x4A99A025, 0x1D6EFE10, 0x1AB93D1D, 0x0BA5A4DF, 0xA186F20F, - 0x2868F169, 0xDCB7DA83, 0x573906FE, 0xA1E2CE9B, 0x4FCD7F52, 0x50115E01, 0xA70683FA, 0xA002B5C4, 0x0DE6D027, - 0x9AF88C27, 0x773F8641, 0xC3604C06, 0x61A806B5, 0xF0177A28, 0xC0F586E0, 0x006058AA, 0x30DC7D62, 0x11E69ED7, - 0x2338EA63, 0x53C2DD94, 0xC2C21634, 0xBBCBEE56, 0x90BCB6DE, 0xEBFC7DA1, 0xCE591D76, 0x6F05E409, 0x4B7C0188, - 0x39720A3D, 0x7C927C24, 0x86E3725F, 0x724D9DB9, 0x1AC15BB4, 0xD39EB8FC, 0xED545578, 0x08FCA5B5, 0xD83D7CD3, - 0x4DAD0FC4, 0x1E50EF5E, 0xB161E6F8, 0xA28514D9, 0x6C51133C, 0x6FD5C7E7, 0x56E14EC4, 0x362ABFCE, 0xDDC6C837, - 0xD79A3234, 0x92638212, 0x670EFA8E, 0x406000E0 - }, - - KS3 = { 0x3A39CE37, 0xD3FAF5CF, 0xABC27737, 0x5AC52D1B, 0x5CB0679E, 0x4FA33742, 0xD3822740, 0x99BC9BBE, 0xD5118E9D, - 0xBF0F7315, 0xD62D1C7E, 0xC700C47B, 0xB78C1B6B, 0x21A19045, 0xB26EB1BE, 0x6A366EB4, 0x5748AB2F, 0xBC946E79, - 0xC6A376D2, 0x6549C2C8, 0x530FF8EE, 0x468DDE7D, 0xD5730A1D, 0x4CD04DC6, 0x2939BBDB, 0xA9BA4650, 0xAC9526E8, - 0xBE5EE304, 0xA1FAD5F0, 0x6A2D519A, 0x63EF8CE2, 0x9A86EE22, 0xC089C2B8, 0x43242EF6, 0xA51E03AA, 0x9CF2D0A4, - 0x83C061BA, 0x9BE96A4D, 0x8FE51550, 0xBA645BD6, 0x2826A2F9, 0xA73A3AE1, 0x4BA99586, 0xEF5562E9, 0xC72FEFD3, - 0xF752F7DA, 0x3F046F69, 0x77FA0A59, 0x80E4A915, 0x87B08601, 0x9B09E6AD, 0x3B3EE593, 0xE990FD5A, 0x9E34D797, - 0x2CF0B7D9, 0x022B8B51, 0x96D5AC3A, 0x017DA67D, 0xD1CF3ED6, 0x7C7D2D28, 0x1F9F25CF, 0xADF2B89B, 0x5AD6B472, - 0x5A88F54C, 0xE029AC71, 0xE019A5E6, 0x47B0ACFD, 0xED93FA9B, 0xE8D3C48D, 0x283B57CC, 0xF8D56629, 0x79132E28, - 0x785F0191, 0xED756055, 0xF7960E44, 0xE3D35E8C, 0x15056DD4, 0x88F46DBA, 0x03A16125, 0x0564F0BD, 0xC3EB9E15, - 0x3C9057A2, 0x97271AEC, 0xA93A072A, 0x1B3F6D9B, 0x1E6321F5, 0xF59C66FB, 0x26DCF319, 0x7533D928, 0xB155FDF5, - 0x03563482, 0x8ABA3CBB, 0x28517711, 0xC20AD9F8, 0xABCC5167, 0xCCAD925F, 0x4DE81751, 0x3830DC8E, 0x379D5862, - 0x9320F991, 0xEA7A90C2, 0xFB3E7BCE, 0x5121CE64, 0x774FBE32, 0xA8B6E37E, 0xC3293D46, 0x48DE5369, 0x6413E680, - 0xA2AE0810, 0xDD6DB224, 0x69852DFD, 0x09072166, 0xB39A460A, 0x6445C0DD, 0x586CDECF, 0x1C20C8AE, 0x5BBEF7DD, - 0x1B588D40, 0xCCD2017F, 0x6BB4E3BB, 0xDDA26A7E, 0x3A59FF45, 0x3E350A44, 0xBCB4CDD5, 0x72EACEA8, 0xFA6484BB, - 0x8D6612AE, 0xBF3C6F47, 0xD29BE463, 0x542F5D9E, 0xAEC2771B, 0xF64E6370, 0x740E0D8D, 0xE75B1357, 0xF8721671, - 0xAF537D5D, 0x4040CB08, 0x4EB4E2CC, 0x34D2466A, 0x0115AF84, 0xE1B00428, 0x95983A1D, 0x06B89FB4, 0xCE6EA048, - 0x6F3F3B82, 0x3520AB82, 0x011A1D4B, 0x277227F8, 0x611560B1, 0xE7933FDC, 0xBB3A792B, 0x344525BD, 0xA08839E1, - 0x51CE794B, 0x2F32C9B7, 0xA01FBAC9, 0xE01CC87E, 0xBCC7D1F6, 0xCF0111C3, 0xA1E8AAC7, 0x1A908749, 0xD44FBD9A, - 0xD0DADECB, 0xD50ADA38, 0x0339C32A, 0xC6913667, 0x8DF9317C, 0xE0B12B4F, 0xF79E59B7, 0x43F5BB3A, 0xF2D519FF, - 0x27D9459C, 0xBF97222C, 0x15E6FC2A, 0x0F91FC71, 0x9B941525, 0xFAE59361, 0xCEB69CEB, 0xC2A86459, 0x12BAA8D1, - 0xB6C1075E, 0xE3056A0C, 0x10D25065, 0xCB03A442, 0xE0EC6E0E, 0x1698DB3B, 0x4C98A0BE, 0x3278E964, 0x9F1F9532, - 0xE0D392DF, 0xD3A0342B, 0x8971F21E, 0x1B0A7441, 0x4BA3348C, 0xC5BE7120, 0xC37632D8, 0xDF359F8D, 0x9B992F2E, - 0xE60B6F47, 0x0FE3F11D, 0xE54CDA54, 0x1EDAD891, 0xCE6279CF, 0xCD3E7E6F, 0x1618B166, 0xFD2C1D05, 0x848FD2C5, - 0xF6FB2299, 0xF523F357, 0xA6327623, 0x93A83531, 0x56CCCD02, 0xACF08162, 0x5A75EBB5, 0x6E163697, 0x88D273CC, - 0xDE966292, 0x81B949D0, 0x4C50901B, 0x71C65614, 0xE6C6C7BD, 0x327A140A, 0x45E1D006, 0xC3F27B9A, 0xC9AA53FD, - 0x62A80F00, 0xBB25BFE2, 0x35BDD2F6, 0x71126905, 0xB2040222, 0xB6CBCF7C, 0xCD769C2B, 0x53113EC0, 0x1640E3D3, - 0x38ABBD60, 0x2547ADF0, 0xBA38209C, 0xF746CE76, 0x77AFA1C5, 0x20756060, 0x85CBFE4E, 0x8AE88DD8, 0x7AAAF9B0, - 0x4CF9AA7E, 0x1948C25C, 0x02FB8A8C, 0x01C36AE4, 0xD6EBE1F9, 0x90D4F869, 0xA65CDEA0, 0x3F09252D, 0xC208E69F, - 0xB74E6132, 0xCE77E25B, 0x578FDFE3, 0x3AC372E6 - }; - - // ==================================== - // Useful constants - // ==================================== - - private static final int ROUNDS = 16; - private static final int BLOCK_SIZE = 8; // bytes = 64 bits - private static final int SBOX_SK = 256; - private static final int P_SZ = ROUNDS + 2; - - private final int[] S0, S1, S2, S3; // the s-boxes - private final int[] P; // the p-array - - private boolean doEncrypt = false; - - private byte[] workingKey = null; - - public BlowFish() { - S0 = new int[SBOX_SK]; - S1 = new int[SBOX_SK]; - S2 = new int[SBOX_SK]; - S3 = new int[SBOX_SK]; - P = new int[P_SZ]; - } - - /** - * initialise a Blowfish cipher. - * - * @param encrypting - * whether or not we are for encryption. - * @param key - * the key required to set up the cipher. - * @exception IllegalArgumentException - * if the params argument is inappropriate. - */ - public void init(boolean encrypting, byte[] key) { - this.doEncrypt = encrypting; - this.workingKey = key; - setKey(this.workingKey); - } - - public String getAlgorithmName() { - return "Blowfish"; - } - - public final void transformBlock(byte[] in, int inOff, byte[] out, int outOff) { - if (workingKey == null) { - throw new IllegalStateException("Blowfish not initialised"); - } - - if (doEncrypt) { - encryptBlock(in, inOff, out, outOff); - } - else { - decryptBlock(in, inOff, out, outOff); - } - } - - public void reset() { - } - - public int getBlockSize() { - return BLOCK_SIZE; - } - - // ================================== - // Private Implementation - // ================================== - - private int F(int x) { - return (((S0[(x >>> 24)] + S1[(x >>> 16) & 0xff]) ^ S2[(x >>> 8) & 0xff]) + S3[x & 0xff]); - } - - /** - * apply the encryption cycle to each value pair in the table. - */ - private void processTable(int xl, int xr, int[] table) { - int size = table.length; - - for (int s = 0; s < size; s += 2) { - xl ^= P[0]; - - for (int i = 1; i < ROUNDS; i += 2) { - xr ^= F(xl) ^ P[i]; - xl ^= F(xr) ^ P[i + 1]; - } - - xr ^= P[ROUNDS + 1]; - table[s] = xr; - table[s + 1] = xl; - xr = xl; // end of cycle swap - xl = table[s]; - } - } - - private void setKey(byte[] key) { - /* - * - comments are from _Applied Crypto_, Schneier, p338 please be - * careful comparing the two, AC numbers the arrays from 1, the enclosed - * code from 0. - * - * (1) Initialise the S-boxes and the P-array, with a fixed string This - * string contains the hexadecimal digits of pi (3.141...) - */ - System.arraycopy(KS0, 0, S0, 0, SBOX_SK); - System.arraycopy(KS1, 0, S1, 0, SBOX_SK); - System.arraycopy(KS2, 0, S2, 0, SBOX_SK); - System.arraycopy(KS3, 0, S3, 0, SBOX_SK); - System.arraycopy(KP, 0, P, 0, P_SZ); - /* - * (2) Now, XOR P[0] with the first 32 bits of the key, XOR P[1] with - * the second 32-bits of the key, and so on for all bits of the key (up - * to P[17]). Repeatedly cycle through the key bits until the entire - * P-array has been XOR-ed with the key bits - */ - int keyLength = key.length; - int keyIndex = 0; - - for (int i = 0; i < P_SZ; i++) { - // get the 32 bits of the key, in 4 * 8 bit chunks - int data = 0x0000000; - - for (int j = 0; j < 4; j++) { - // create a 32 bit block - data = (data << 8) | (key[keyIndex++] & 0xff); - - // wrap when we get to the end of the key - if (keyIndex >= keyLength) { - keyIndex = 0; - } - } - - // XOR the newly created 32 bit chunk onto the P-array - P[i] ^= data; - } - - /* - * (3) Encrypt the all-zero string with the Blowfish algorithm, using - * the subkeys described in (1) and (2) - * - * (4) Replace P1 and P2 with the output of step (3) - * - * (5) Encrypt the output of step(3) using the Blowfish algorithm, with - * the modified subkeys. - * - * (6) Replace P3 and P4 with the output of step (5) - * - * (7) Continue the process, replacing all elements of the P-array and - * then all four S-boxes in order, with the output of the continuously - * changing Blowfish algorithm - */ - processTable(0, 0, P); - processTable(P[P_SZ - 2], P[P_SZ - 1], S0); - processTable(S0[SBOX_SK - 2], S0[SBOX_SK - 1], S1); - processTable(S1[SBOX_SK - 2], S1[SBOX_SK - 1], S2); - processTable(S2[SBOX_SK - 2], S2[SBOX_SK - 1], S3); - } - - /** - * Encrypt the given input starting at the given offset and place the result - * in the provided buffer starting at the given offset. The input will be an - * exact multiple of our blocksize. - */ - private void encryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex) { - int xl = BytesTo32bits(src, srcIndex); - int xr = BytesTo32bits(src, srcIndex + 4); - xl ^= P[0]; - - for (int i = 1; i < ROUNDS; i += 2) { - xr ^= F(xl) ^ P[i]; - xl ^= F(xr) ^ P[i + 1]; - } - - xr ^= P[ROUNDS + 1]; - Bits32ToBytes(xr, dst, dstIndex); - Bits32ToBytes(xl, dst, dstIndex + 4); - } - - /** - * Decrypt the given input starting at the given offset and place the result - * in the provided buffer starting at the given offset. The input will be an - * exact multiple of our blocksize. - */ - private void decryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex) { - int xl = BytesTo32bits(src, srcIndex); - int xr = BytesTo32bits(src, srcIndex + 4); - xl ^= P[ROUNDS + 1]; - - for (int i = ROUNDS; i > 0; i -= 2) { - xr ^= F(xl) ^ P[i]; - xl ^= F(xr) ^ P[i - 1]; - } - - xr ^= P[0]; - Bits32ToBytes(xr, dst, dstIndex); - Bits32ToBytes(xl, dst, dstIndex + 4); - } - - private int BytesTo32bits(byte[] b, int i) { - return ((b[i] & 0xff) << 24) | ((b[i + 1] & 0xff) << 16) | ((b[i + 2] & 0xff) << 8) | ((b[i + 3] & 0xff)); - } - - private void Bits32ToBytes(int in, byte[] b, int offset) { - b[offset + 3] = (byte) in; - b[offset + 2] = (byte)(in >> 8); - b[offset + 1] = (byte)(in >> 16); - b[offset] = (byte)(in >> 24); - } - -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/crypto/cipher/CBCMode.java --- a/src/com/trilead/ssh2/crypto/cipher/CBCMode.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +0,0 @@ -package com.trilead.ssh2.crypto.cipher; - -/** - * CBCMode. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: CBCMode.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class CBCMode implements BlockCipher { - BlockCipher tc; - int blockSize; - boolean doEncrypt; - - byte[] cbc_vector; - byte[] tmp_vector; - - public void init(boolean forEncryption, byte[] key) { - } - - public CBCMode(BlockCipher tc, byte[] iv, boolean doEncrypt) - throws IllegalArgumentException { - this.tc = tc; - this.blockSize = tc.getBlockSize(); - this.doEncrypt = doEncrypt; - - if (this.blockSize != iv.length) - throw new IllegalArgumentException("IV must be " + blockSize - + " bytes long! (currently " + iv.length + ")"); - - this.cbc_vector = new byte[blockSize]; - this.tmp_vector = new byte[blockSize]; - System.arraycopy(iv, 0, cbc_vector, 0, blockSize); - } - - public int getBlockSize() { - return blockSize; - } - - private void encryptBlock(byte[] src, int srcoff, byte[] dst, int dstoff) { - for (int i = 0; i < blockSize; i++) - cbc_vector[i] ^= src[srcoff + i]; - - tc.transformBlock(cbc_vector, 0, dst, dstoff); - System.arraycopy(dst, dstoff, cbc_vector, 0, blockSize); - } - - private void decryptBlock(byte[] src, int srcoff, byte[] dst, int dstoff) { - /* Assume the worst, src and dst are overlapping... */ - System.arraycopy(src, srcoff, tmp_vector, 0, blockSize); - tc.transformBlock(src, srcoff, dst, dstoff); - - for (int i = 0; i < blockSize; i++) - dst[dstoff + i] ^= cbc_vector[i]; - - /* ...that is why we need a tmp buffer. */ - byte[] swap = cbc_vector; - cbc_vector = tmp_vector; - tmp_vector = swap; - } - - public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) { - if (doEncrypt) - encryptBlock(src, srcoff, dst, dstoff); - else - decryptBlock(src, srcoff, dst, dstoff); - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/crypto/cipher/CTRMode.java --- a/src/com/trilead/ssh2/crypto/cipher/CTRMode.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ - -package com.trilead.ssh2.crypto.cipher; - -/** - * This is CTR mode as described in draft-ietf-secsh-newmodes-XY.txt - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: CTRMode.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class CTRMode implements BlockCipher { - byte[] X; - byte[] Xenc; - - BlockCipher bc; - int blockSize; - boolean doEncrypt; - - int count = 0; - - public void init(boolean forEncryption, byte[] key) { - } - - public CTRMode(BlockCipher tc, byte[] iv, boolean doEnc) throws IllegalArgumentException { - bc = tc; - blockSize = bc.getBlockSize(); - doEncrypt = doEnc; - - if (blockSize != iv.length) - throw new IllegalArgumentException("IV must be " + blockSize + " bytes long! (currently " + iv.length + ")"); - - X = new byte[blockSize]; - Xenc = new byte[blockSize]; - System.arraycopy(iv, 0, X, 0, blockSize); - } - - public final int getBlockSize() { - return blockSize; - } - - public final void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) { - bc.transformBlock(X, 0, Xenc, 0); - - for (int i = 0; i < blockSize; i++) { - dst[dstoff + i] = (byte)(src[srcoff + i] ^ Xenc[i]); - } - - for (int i = (blockSize - 1); i >= 0; i--) { - X[i]++; - - if (X[i] != 0) - break; - } - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/crypto/cipher/CipherInputStream.java --- a/src/com/trilead/ssh2/crypto/cipher/CipherInputStream.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,136 +0,0 @@ - -package com.trilead.ssh2.crypto.cipher; - -import java.io.IOException; -import java.io.InputStream; - -/** - * CipherInputStream. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: CipherInputStream.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class CipherInputStream { - BlockCipher currentCipher; - InputStream bi; - byte[] buffer; - byte[] enc; - int blockSize; - int pos; - - /* - * We cannot use java.io.BufferedInputStream, since that is not available in - * J2ME. Everything could be improved alot here. - */ - - final int BUFF_SIZE = 2048; - byte[] input_buffer = new byte[BUFF_SIZE]; - int input_buffer_pos = 0; - int input_buffer_size = 0; - - public CipherInputStream(BlockCipher tc, InputStream bi) { - this.bi = bi; - changeCipher(tc); - } - - private int fill_buffer() throws IOException { - input_buffer_pos = 0; - input_buffer_size = bi.read(input_buffer, 0, BUFF_SIZE); - return input_buffer_size; - } - - private int internal_read(byte[] b, int off, int len) throws IOException { - if (input_buffer_size < 0) - return -1; - - if (input_buffer_pos >= input_buffer_size) { - if (fill_buffer() <= 0) - return -1; - } - - int avail = input_buffer_size - input_buffer_pos; - int thiscopy = (len > avail) ? avail : len; - System.arraycopy(input_buffer, input_buffer_pos, b, off, thiscopy); - input_buffer_pos += thiscopy; - return thiscopy; - } - - public void changeCipher(BlockCipher bc) { - this.currentCipher = bc; - blockSize = bc.getBlockSize(); - buffer = new byte[blockSize]; - enc = new byte[blockSize]; - pos = blockSize; - } - - private void getBlock() throws IOException { - int n = 0; - - while (n < blockSize) { - int len = internal_read(enc, n, blockSize - n); - - if (len < 0) - throw new IOException("Cannot read full block, EOF reached."); - - n += len; - } - - try { - currentCipher.transformBlock(enc, 0, buffer, 0); - } - catch (Exception e) { - throw new IOException("Error while decrypting block."); - } - - pos = 0; - } - - public int read(byte[] dst) throws IOException { - return read(dst, 0, dst.length); - } - - public int read(byte[] dst, int off, int len) throws IOException { - int count = 0; - - while (len > 0) { - if (pos >= blockSize) - getBlock(); - - int avail = blockSize - pos; - int copy = Math.min(avail, len); - System.arraycopy(buffer, pos, dst, off, copy); - pos += copy; - off += copy; - len -= copy; - count += copy; - } - - return count; - } - - public int read() throws IOException { - if (pos >= blockSize) { - getBlock(); - } - - return buffer[pos++] & 0xff; - } - - public int readPlain(byte[] b, int off, int len) throws IOException { - if (pos != blockSize) - throw new IOException("Cannot read plain since crypto buffer is not aligned."); - - int n = 0; - - while (n < len) { - int cnt = internal_read(b, off + n, len - n); - - if (cnt < 0) - throw new IOException("Cannot fill buffer, EOF reached."); - - n += cnt; - } - - return n; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/crypto/cipher/CipherOutputStream.java --- a/src/com/trilead/ssh2/crypto/cipher/CipherOutputStream.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,126 +0,0 @@ - -package com.trilead.ssh2.crypto.cipher; - -import java.io.IOException; -import java.io.OutputStream; - -/** - * CipherOutputStream. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: CipherOutputStream.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class CipherOutputStream { - BlockCipher currentCipher; - OutputStream bo; - byte[] buffer; - byte[] enc; - int blockSize; - int pos; - - /* - * We cannot use java.io.BufferedOutputStream, since that is not available - * in J2ME. Everything could be improved here alot. - */ - - final int BUFF_SIZE = 2048; - byte[] out_buffer = new byte[BUFF_SIZE]; - int out_buffer_pos = 0; - - public CipherOutputStream(BlockCipher tc, OutputStream bo) { - this.bo = bo; - changeCipher(tc); - } - - private void internal_write(byte[] src, int off, int len) throws IOException { - while (len > 0) { - int space = BUFF_SIZE - out_buffer_pos; - int copy = (len > space) ? space : len; - System.arraycopy(src, off, out_buffer, out_buffer_pos, copy); - off += copy; - out_buffer_pos += copy; - len -= copy; - - if (out_buffer_pos >= BUFF_SIZE) { - bo.write(out_buffer, 0, BUFF_SIZE); - out_buffer_pos = 0; - } - } - } - - private void internal_write(int b) throws IOException { - out_buffer[out_buffer_pos++] = (byte) b; - - if (out_buffer_pos >= BUFF_SIZE) { - bo.write(out_buffer, 0, BUFF_SIZE); - out_buffer_pos = 0; - } - } - - public void flush() throws IOException { - if (pos != 0) - throw new IOException("FATAL: cannot flush since crypto buffer is not aligned."); - - if (out_buffer_pos > 0) { - bo.write(out_buffer, 0, out_buffer_pos); - out_buffer_pos = 0; - } - - bo.flush(); - } - - public void changeCipher(BlockCipher bc) { - this.currentCipher = bc; - blockSize = bc.getBlockSize(); - buffer = new byte[blockSize]; - enc = new byte[blockSize]; - pos = 0; - } - - private void writeBlock() throws IOException { - try { - currentCipher.transformBlock(buffer, 0, enc, 0); - } - catch (Exception e) { - throw(IOException) new IOException("Error while decrypting block.").initCause(e); - } - - internal_write(enc, 0, blockSize); - pos = 0; - } - - public void write(byte[] src, int off, int len) throws IOException { - while (len > 0) { - int avail = blockSize - pos; - int copy = Math.min(avail, len); - System.arraycopy(src, off, buffer, pos, copy); - pos += copy; - off += copy; - len -= copy; - - if (pos >= blockSize) - writeBlock(); - } - } - - public void write(int b) throws IOException { - buffer[pos++] = (byte) b; - - if (pos >= blockSize) - writeBlock(); - } - - public void writePlain(int b) throws IOException { - if (pos != 0) - throw new IOException("Cannot write plain since crypto buffer is not aligned."); - - internal_write(b); - } - - public void writePlain(byte[] b, int off, int len) throws IOException { - if (pos != 0) - throw new IOException("Cannot write plain since crypto buffer is not aligned."); - - internal_write(b, off, len); - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/crypto/cipher/DES.java --- a/src/com/trilead/ssh2/crypto/cipher/DES.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,353 +0,0 @@ - -package com.trilead.ssh2.crypto.cipher; - -/* - This file is based on the 3DES implementation from the Bouncy Castle Crypto package. - Their licence file states the following: - - Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle - (http://www.bouncycastle.org) - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - */ - -/** - * DES. - * - * @author See comments in the source file - * @version $Id: DES.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - * - */ -public class DES implements BlockCipher { - private int[] workingKey = null; - - /** - * standard constructor. - */ - public DES() { - } - - /** - * initialise a DES cipher. - * - * @param encrypting - * whether or not we are for encryption. - * @param key - * the parameters required to set up the cipher. - * @exception IllegalArgumentException - * if the params argument is inappropriate. - */ - public void init(boolean encrypting, byte[] key) { - this.workingKey = generateWorkingKey(encrypting, key, 0); - } - - public String getAlgorithmName() { - return "DES"; - } - - public int getBlockSize() { - return 8; - } - - public void transformBlock(byte[] in, int inOff, byte[] out, int outOff) { - if (workingKey == null) { - throw new IllegalStateException("DES engine not initialised!"); - } - - desFunc(workingKey, in, inOff, out, outOff); - } - - public void reset() { - } - - /** - * what follows is mainly taken from "Applied Cryptography", by Bruce - * Schneier, however it also bears great resemblance to Richard - * Outerbridge's D3DES... - */ - - static short[] Df_Key = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, - 0x10, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67 - }; - - static short[] bytebit = { 0200, 0100, 040, 020, 010, 04, 02, 01 }; - - static int[] bigbyte = { 0x800000, 0x400000, 0x200000, 0x100000, 0x80000, 0x40000, 0x20000, 0x10000, 0x8000, - 0x4000, 0x2000, 0x1000, 0x800, 0x400, 0x200, 0x100, 0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1 - }; - - /* - * Use the key schedule specified in the Standard (ANSI X3.92-1981). - */ - - static byte[] pc1 = { 56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, - 59, 51, 43, 35, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 60, 52, 44, 36, 28, 20, 12, - 4, 27, 19, 11, 3 - }; - - static byte[] totrot = { 1, 2, 4, 6, 8, 10, 12, 14, 15, 17, 19, 21, 23, 25, 27, 28 }; - - static byte[] pc2 = { 13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9, 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1, 40, - 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47, 43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31 - }; - - static int[] SP1 = { 0x01010400, 0x00000000, 0x00010000, 0x01010404, 0x01010004, 0x00010404, 0x00000004, - 0x00010000, 0x00000400, 0x01010400, 0x01010404, 0x00000400, 0x01000404, 0x01010004, 0x01000000, 0x00000004, - 0x00000404, 0x01000400, 0x01000400, 0x00010400, 0x00010400, 0x01010000, 0x01010000, 0x01000404, 0x00010004, - 0x01000004, 0x01000004, 0x00010004, 0x00000000, 0x00000404, 0x00010404, 0x01000000, 0x00010000, 0x01010404, - 0x00000004, 0x01010000, 0x01010400, 0x01000000, 0x01000000, 0x00000400, 0x01010004, 0x00010000, 0x00010400, - 0x01000004, 0x00000400, 0x00000004, 0x01000404, 0x00010404, 0x01010404, 0x00010004, 0x01010000, 0x01000404, - 0x01000004, 0x00000404, 0x00010404, 0x01010400, 0x00000404, 0x01000400, 0x01000400, 0x00000000, 0x00010004, - 0x00010400, 0x00000000, 0x01010004 - }; - - static int[] SP2 = { 0x80108020, 0x80008000, 0x00008000, 0x00108020, 0x00100000, 0x00000020, 0x80100020, - 0x80008020, 0x80000020, 0x80108020, 0x80108000, 0x80000000, 0x80008000, 0x00100000, 0x00000020, 0x80100020, - 0x00108000, 0x00100020, 0x80008020, 0x00000000, 0x80000000, 0x00008000, 0x00108020, 0x80100000, 0x00100020, - 0x80000020, 0x00000000, 0x00108000, 0x00008020, 0x80108000, 0x80100000, 0x00008020, 0x00000000, 0x00108020, - 0x80100020, 0x00100000, 0x80008020, 0x80100000, 0x80108000, 0x00008000, 0x80100000, 0x80008000, 0x00000020, - 0x80108020, 0x00108020, 0x00000020, 0x00008000, 0x80000000, 0x00008020, 0x80108000, 0x00100000, 0x80000020, - 0x00100020, 0x80008020, 0x80000020, 0x00100020, 0x00108000, 0x00000000, 0x80008000, 0x00008020, 0x80000000, - 0x80100020, 0x80108020, 0x00108000 - }; - - static int[] SP3 = { 0x00000208, 0x08020200, 0x00000000, 0x08020008, 0x08000200, 0x00000000, 0x00020208, - 0x08000200, 0x00020008, 0x08000008, 0x08000008, 0x00020000, 0x08020208, 0x00020008, 0x08020000, 0x00000208, - 0x08000000, 0x00000008, 0x08020200, 0x00000200, 0x00020200, 0x08020000, 0x08020008, 0x00020208, 0x08000208, - 0x00020200, 0x00020000, 0x08000208, 0x00000008, 0x08020208, 0x00000200, 0x08000000, 0x08020200, 0x08000000, - 0x00020008, 0x00000208, 0x00020000, 0x08020200, 0x08000200, 0x00000000, 0x00000200, 0x00020008, 0x08020208, - 0x08000200, 0x08000008, 0x00000200, 0x00000000, 0x08020008, 0x08000208, 0x00020000, 0x08000000, 0x08020208, - 0x00000008, 0x00020208, 0x00020200, 0x08000008, 0x08020000, 0x08000208, 0x00000208, 0x08020000, 0x00020208, - 0x00000008, 0x08020008, 0x00020200 - }; - - static int[] SP4 = { 0x00802001, 0x00002081, 0x00002081, 0x00000080, 0x00802080, 0x00800081, 0x00800001, - 0x00002001, 0x00000000, 0x00802000, 0x00802000, 0x00802081, 0x00000081, 0x00000000, 0x00800080, 0x00800001, - 0x00000001, 0x00002000, 0x00800000, 0x00802001, 0x00000080, 0x00800000, 0x00002001, 0x00002080, 0x00800081, - 0x00000001, 0x00002080, 0x00800080, 0x00002000, 0x00802080, 0x00802081, 0x00000081, 0x00800080, 0x00800001, - 0x00802000, 0x00802081, 0x00000081, 0x00000000, 0x00000000, 0x00802000, 0x00002080, 0x00800080, 0x00800081, - 0x00000001, 0x00802001, 0x00002081, 0x00002081, 0x00000080, 0x00802081, 0x00000081, 0x00000001, 0x00002000, - 0x00800001, 0x00002001, 0x00802080, 0x00800081, 0x00002001, 0x00002080, 0x00800000, 0x00802001, 0x00000080, - 0x00800000, 0x00002000, 0x00802080 - }; - - static int[] SP5 = { 0x00000100, 0x02080100, 0x02080000, 0x42000100, 0x00080000, 0x00000100, 0x40000000, - 0x02080000, 0x40080100, 0x00080000, 0x02000100, 0x40080100, 0x42000100, 0x42080000, 0x00080100, 0x40000000, - 0x02000000, 0x40080000, 0x40080000, 0x00000000, 0x40000100, 0x42080100, 0x42080100, 0x02000100, 0x42080000, - 0x40000100, 0x00000000, 0x42000000, 0x02080100, 0x02000000, 0x42000000, 0x00080100, 0x00080000, 0x42000100, - 0x00000100, 0x02000000, 0x40000000, 0x02080000, 0x42000100, 0x40080100, 0x02000100, 0x40000000, 0x42080000, - 0x02080100, 0x40080100, 0x00000100, 0x02000000, 0x42080000, 0x42080100, 0x00080100, 0x42000000, 0x42080100, - 0x02080000, 0x00000000, 0x40080000, 0x42000000, 0x00080100, 0x02000100, 0x40000100, 0x00080000, 0x00000000, - 0x40080000, 0x02080100, 0x40000100 - }; - - static int[] SP6 = { 0x20000010, 0x20400000, 0x00004000, 0x20404010, 0x20400000, 0x00000010, 0x20404010, - 0x00400000, 0x20004000, 0x00404010, 0x00400000, 0x20000010, 0x00400010, 0x20004000, 0x20000000, 0x00004010, - 0x00000000, 0x00400010, 0x20004010, 0x00004000, 0x00404000, 0x20004010, 0x00000010, 0x20400010, 0x20400010, - 0x00000000, 0x00404010, 0x20404000, 0x00004010, 0x00404000, 0x20404000, 0x20000000, 0x20004000, 0x00000010, - 0x20400010, 0x00404000, 0x20404010, 0x00400000, 0x00004010, 0x20000010, 0x00400000, 0x20004000, 0x20000000, - 0x00004010, 0x20000010, 0x20404010, 0x00404000, 0x20400000, 0x00404010, 0x20404000, 0x00000000, 0x20400010, - 0x00000010, 0x00004000, 0x20400000, 0x00404010, 0x00004000, 0x00400010, 0x20004010, 0x00000000, 0x20404000, - 0x20000000, 0x00400010, 0x20004010 - }; - - static int[] SP7 = { 0x00200000, 0x04200002, 0x04000802, 0x00000000, 0x00000800, 0x04000802, 0x00200802, - 0x04200800, 0x04200802, 0x00200000, 0x00000000, 0x04000002, 0x00000002, 0x04000000, 0x04200002, 0x00000802, - 0x04000800, 0x00200802, 0x00200002, 0x04000800, 0x04000002, 0x04200000, 0x04200800, 0x00200002, 0x04200000, - 0x00000800, 0x00000802, 0x04200802, 0x00200800, 0x00000002, 0x04000000, 0x00200800, 0x04000000, 0x00200800, - 0x00200000, 0x04000802, 0x04000802, 0x04200002, 0x04200002, 0x00000002, 0x00200002, 0x04000000, 0x04000800, - 0x00200000, 0x04200800, 0x00000802, 0x00200802, 0x04200800, 0x00000802, 0x04000002, 0x04200802, 0x04200000, - 0x00200800, 0x00000000, 0x00000002, 0x04200802, 0x00000000, 0x00200802, 0x04200000, 0x00000800, 0x04000002, - 0x04000800, 0x00000800, 0x00200002 - }; - - static int[] SP8 = { 0x10001040, 0x00001000, 0x00040000, 0x10041040, 0x10000000, 0x10001040, 0x00000040, - 0x10000000, 0x00040040, 0x10040000, 0x10041040, 0x00041000, 0x10041000, 0x00041040, 0x00001000, 0x00000040, - 0x10040000, 0x10000040, 0x10001000, 0x00001040, 0x00041000, 0x00040040, 0x10040040, 0x10041000, 0x00001040, - 0x00000000, 0x00000000, 0x10040040, 0x10000040, 0x10001000, 0x00041040, 0x00040000, 0x00041040, 0x00040000, - 0x10041000, 0x00001000, 0x00000040, 0x10040040, 0x00001000, 0x00041040, 0x10001000, 0x00000040, 0x10000040, - 0x10040000, 0x10040040, 0x10000000, 0x00040000, 0x10001040, 0x00000000, 0x10041040, 0x00040040, 0x10000040, - 0x10040000, 0x10001000, 0x10001040, 0x00000000, 0x10041040, 0x00041000, 0x00041000, 0x00001040, 0x00001040, - 0x00040040, 0x10000000, 0x10041000 - }; - - /** - * generate an integer based working key based on our secret key and what we - * processing we are planning to do. - * - * Acknowledgements for this routine go to James Gillogly & Phil Karn. - * (whoever, and wherever they are!). - */ - protected int[] generateWorkingKey(boolean encrypting, byte[] key, int off) { - int[] newKey = new int[32]; - boolean[] pc1m = new boolean[56], pcr = new boolean[56]; - - for (int j = 0; j < 56; j++) { - int l = pc1[j]; - pc1m[j] = ((key[off + (l >>> 3)] & bytebit[l & 07]) != 0); - } - - for (int i = 0; i < 16; i++) { - int l, m, n; - - if (encrypting) { - m = i << 1; - } - else { - m = (15 - i) << 1; - } - - n = m + 1; - newKey[m] = newKey[n] = 0; - - for (int j = 0; j < 28; j++) { - l = j + totrot[i]; - - if (l < 28) { - pcr[j] = pc1m[l]; - } - else { - pcr[j] = pc1m[l - 28]; - } - } - - for (int j = 28; j < 56; j++) { - l = j + totrot[i]; - - if (l < 56) { - pcr[j] = pc1m[l]; - } - else { - pcr[j] = pc1m[l - 28]; - } - } - - for (int j = 0; j < 24; j++) { - if (pcr[pc2[j]]) { - newKey[m] |= bigbyte[j]; - } - - if (pcr[pc2[j + 24]]) { - newKey[n] |= bigbyte[j]; - } - } - } - - // - // store the processed key - // - for (int i = 0; i != 32; i += 2) { - int i1, i2; - i1 = newKey[i]; - i2 = newKey[i + 1]; - newKey[i] = ((i1 & 0x00fc0000) << 6) | ((i1 & 0x00000fc0) << 10) | ((i2 & 0x00fc0000) >>> 10) - | ((i2 & 0x00000fc0) >>> 6); - newKey[i + 1] = ((i1 & 0x0003f000) << 12) | ((i1 & 0x0000003f) << 16) | ((i2 & 0x0003f000) >>> 4) - | (i2 & 0x0000003f); - } - - return newKey; - } - - /** - * the DES engine. - */ - protected void desFunc(int[] wKey, byte[] in, int inOff, byte[] out, int outOff) { - int work, right, left; - left = (in[inOff + 0] & 0xff) << 24; - left |= (in[inOff + 1] & 0xff) << 16; - left |= (in[inOff + 2] & 0xff) << 8; - left |= (in[inOff + 3] & 0xff); - right = (in[inOff + 4] & 0xff) << 24; - right |= (in[inOff + 5] & 0xff) << 16; - right |= (in[inOff + 6] & 0xff) << 8; - right |= (in[inOff + 7] & 0xff); - work = ((left >>> 4) ^ right) & 0x0f0f0f0f; - right ^= work; - left ^= (work << 4); - work = ((left >>> 16) ^ right) & 0x0000ffff; - right ^= work; - left ^= (work << 16); - work = ((right >>> 2) ^ left) & 0x33333333; - left ^= work; - right ^= (work << 2); - work = ((right >>> 8) ^ left) & 0x00ff00ff; - left ^= work; - right ^= (work << 8); - right = ((right << 1) | ((right >>> 31) & 1)) & 0xffffffff; - work = (left ^ right) & 0xaaaaaaaa; - left ^= work; - right ^= work; - left = ((left << 1) | ((left >>> 31) & 1)) & 0xffffffff; - - for (int round = 0; round < 8; round++) { - int fval; - work = (right << 28) | (right >>> 4); - work ^= wKey[round * 4 + 0]; - fval = SP7[work & 0x3f]; - fval |= SP5[(work >>> 8) & 0x3f]; - fval |= SP3[(work >>> 16) & 0x3f]; - fval |= SP1[(work >>> 24) & 0x3f]; - work = right ^ wKey[round * 4 + 1]; - fval |= SP8[work & 0x3f]; - fval |= SP6[(work >>> 8) & 0x3f]; - fval |= SP4[(work >>> 16) & 0x3f]; - fval |= SP2[(work >>> 24) & 0x3f]; - left ^= fval; - work = (left << 28) | (left >>> 4); - work ^= wKey[round * 4 + 2]; - fval = SP7[work & 0x3f]; - fval |= SP5[(work >>> 8) & 0x3f]; - fval |= SP3[(work >>> 16) & 0x3f]; - fval |= SP1[(work >>> 24) & 0x3f]; - work = left ^ wKey[round * 4 + 3]; - fval |= SP8[work & 0x3f]; - fval |= SP6[(work >>> 8) & 0x3f]; - fval |= SP4[(work >>> 16) & 0x3f]; - fval |= SP2[(work >>> 24) & 0x3f]; - right ^= fval; - } - - right = (right << 31) | (right >>> 1); - work = (left ^ right) & 0xaaaaaaaa; - left ^= work; - right ^= work; - left = (left << 31) | (left >>> 1); - work = ((left >>> 8) ^ right) & 0x00ff00ff; - right ^= work; - left ^= (work << 8); - work = ((left >>> 2) ^ right) & 0x33333333; - right ^= work; - left ^= (work << 2); - work = ((right >>> 16) ^ left) & 0x0000ffff; - left ^= work; - right ^= (work << 16); - work = ((right >>> 4) ^ left) & 0x0f0f0f0f; - left ^= work; - right ^= (work << 4); - out[outOff + 0] = (byte)((right >>> 24) & 0xff); - out[outOff + 1] = (byte)((right >>> 16) & 0xff); - out[outOff + 2] = (byte)((right >>> 8) & 0xff); - out[outOff + 3] = (byte)(right & 0xff); - out[outOff + 4] = (byte)((left >>> 24) & 0xff); - out[outOff + 5] = (byte)((left >>> 16) & 0xff); - out[outOff + 6] = (byte)((left >>> 8) & 0xff); - out[outOff + 7] = (byte)(left & 0xff); - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/crypto/cipher/DESede.java --- a/src/com/trilead/ssh2/crypto/cipher/DESede.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,94 +0,0 @@ - -package com.trilead.ssh2.crypto.cipher; - -/* - This file was shamelessly taken (and modified) from the Bouncy Castle Crypto package. - Their licence file states the following: - - Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle - (http://www.bouncycastle.org) - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - */ - -/** - * DESede. - * - * @author See comments in the source file - * @version $Id: DESede.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - * - */ -public class DESede extends DES { - private int[] key1 = null; - private int[] key2 = null; - private int[] key3 = null; - - private boolean encrypt; - - /** - * standard constructor. - */ - public DESede() { - } - - /** - * initialise a DES cipher. - * - * @param encrypting - * whether or not we are for encryption. - * @param key - * the parameters required to set up the cipher. - * @exception IllegalArgumentException - * if the params argument is inappropriate. - */ - public void init(boolean encrypting, byte[] key) { - key1 = generateWorkingKey(encrypting, key, 0); - key2 = generateWorkingKey(!encrypting, key, 8); - key3 = generateWorkingKey(encrypting, key, 16); - encrypt = encrypting; - } - - public String getAlgorithmName() { - return "DESede"; - } - - public int getBlockSize() { - return 8; - } - - public void transformBlock(byte[] in, int inOff, byte[] out, int outOff) { - if (key1 == null) { - throw new IllegalStateException("DESede engine not initialised!"); - } - - if (encrypt) { - desFunc(key1, in, inOff, out, outOff); - desFunc(key2, out, outOff, out, outOff); - desFunc(key3, out, outOff, out, outOff); - } - else { - desFunc(key3, in, inOff, out, outOff); - desFunc(key2, out, outOff, out, outOff); - desFunc(key1, out, outOff, out, outOff); - } - } - - public void reset() { - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/crypto/cipher/NullCipher.java --- a/src/com/trilead/ssh2/crypto/cipher/NullCipher.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -package com.trilead.ssh2.crypto.cipher; - -/** - * NullCipher. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: NullCipher.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class NullCipher implements BlockCipher { - private int blockSize = 8; - - public NullCipher() { - } - - public NullCipher(int blockSize) { - this.blockSize = blockSize; - } - - public void init(boolean forEncryption, byte[] key) { - } - - public int getBlockSize() { - return blockSize; - } - - public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) { - System.arraycopy(src, srcoff, dst, dstoff, blockSize); - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/crypto/dh/DhExchange.java --- a/src/com/trilead/ssh2/crypto/dh/DhExchange.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,140 +0,0 @@ -/** - * - */ -package com.trilead.ssh2.crypto.dh; - -import java.io.IOException; -import java.math.BigInteger; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.spec.InvalidKeySpecException; - -import javax.crypto.KeyAgreement; -import javax.crypto.interfaces.DHPrivateKey; -import javax.crypto.interfaces.DHPublicKey; -import javax.crypto.spec.DHParameterSpec; -import javax.crypto.spec.DHPublicKeySpec; - -/** - * @author kenny - * - */ -public class DhExchange extends GenericDhExchange { - - /* Given by the standard */ - - private static final BigInteger P1 = new BigInteger( - "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" - + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" - + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" - + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" - + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381" - + "FFFFFFFFFFFFFFFF", 16); - - private static final BigInteger P14 = new BigInteger( - "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" - + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" - + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" - + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" - + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" - + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" - + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" - + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" - + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" - + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" - + "15728E5A8AACAA68FFFFFFFFFFFFFFFF", 16); - - private static final BigInteger G = BigInteger.valueOf(2); - - /* Client public and private */ - - private DHPrivateKey clientPrivate; - private DHPublicKey clientPublic; - - /* Server public */ - - private DHPublicKey serverPublic; - - @Override - public void init(String name) throws IOException { - final DHParameterSpec spec; - - if ("diffie-hellman-group1-sha1".equals(name)) { - spec = new DHParameterSpec(P1, G); - } - else if ("diffie-hellman-group14-sha1".equals(name)) { - spec = new DHParameterSpec(P14, G); - } - else { - throw new IllegalArgumentException("Unknown DH group " + name); - } - - try { - KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH"); - kpg.initialize(spec); - KeyPair pair = kpg.generateKeyPair(); - clientPrivate = (DHPrivateKey) pair.getPrivate(); - clientPublic = (DHPublicKey) pair.getPublic(); - } - catch (NoSuchAlgorithmException e) { - throw(IOException) new IOException("No DH keypair generator").initCause(e); - } - catch (InvalidAlgorithmParameterException e) { - throw(IOException) new IOException("Invalid DH parameters").initCause(e); - } - } - - @Override - public byte[] getE() { - if (clientPublic == null) - throw new IllegalStateException("DhExchange not initialized!"); - - return clientPublic.getY().toByteArray(); - } - - @Override - protected byte[] getServerE() { - if (serverPublic == null) - throw new IllegalStateException("DhExchange not initialized!"); - - return serverPublic.getY().toByteArray(); - } - - @Override - public void setF(byte[] f) throws IOException { - if (clientPublic == null) - throw new IllegalStateException("DhExchange not initialized!"); - - final KeyAgreement ka; - - try { - KeyFactory kf = KeyFactory.getInstance("DH"); - DHParameterSpec params = clientPublic.getParams(); - this.serverPublic = (DHPublicKey) kf.generatePublic(new DHPublicKeySpec( - new BigInteger(f), params.getP(), params.getG())); - ka = KeyAgreement.getInstance("DH"); - ka.init(clientPrivate); - ka.doPhase(serverPublic, true); - } - catch (NoSuchAlgorithmException e) { - throw(IOException) new IOException("No DH key agreement method").initCause(e); - } - catch (InvalidKeyException e) { - throw(IOException) new IOException("Invalid DH key").initCause(e); - } - catch (InvalidKeySpecException e) { - throw(IOException) new IOException("Invalid DH key").initCause(e); - } - - sharedSecret = new BigInteger(ka.generateSecret()); - } - - @Override - public String getHashAlgo() { - return "SHA1"; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/crypto/dh/DhGroupExchange.java --- a/src/com/trilead/ssh2/crypto/dh/DhGroupExchange.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,107 +0,0 @@ - -package com.trilead.ssh2.crypto.dh; - -import java.math.BigInteger; -import java.security.SecureRandom; - -import com.trilead.ssh2.DHGexParameters; -import com.trilead.ssh2.crypto.digest.HashForSSH2Types; - - -/** - * DhGroupExchange. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: DhGroupExchange.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $ - */ -public class DhGroupExchange { - /* Given by the standard */ - - private BigInteger p; - private BigInteger g; - - /* Client public and private */ - - private BigInteger e; - private BigInteger x; - - /* Server public */ - - private BigInteger f; - - /* Shared secret */ - - private BigInteger k; - - public DhGroupExchange(BigInteger p, BigInteger g) { - this.p = p; - this.g = g; - } - - public void init(SecureRandom rnd) { - k = null; - x = new BigInteger(p.bitLength() - 1, rnd); - e = g.modPow(x, p); - } - - /** - * @return Returns the e. - */ - public BigInteger getE() { - if (e == null) - throw new IllegalStateException("Not initialized!"); - - return e; - } - - /** - * @return Returns the shared secret k. - */ - public BigInteger getK() { - if (k == null) - throw new IllegalStateException("Shared secret not yet known, need f first!"); - - return k; - } - - /** - * Sets f and calculates the shared secret. - */ - public void setF(BigInteger f) { - if (e == null) - throw new IllegalStateException("Not initialized!"); - - BigInteger zero = BigInteger.valueOf(0); - - if (zero.compareTo(f) >= 0 || p.compareTo(f) <= 0) - throw new IllegalArgumentException("Invalid f specified!"); - - this.f = f; - this.k = f.modPow(x, p); - } - - public byte[] calculateH(String hashAlgo, byte[] clientversion, byte[] serverversion, - byte[] clientKexPayload, byte[] serverKexPayload, byte[] hostKey, DHGexParameters para) { - HashForSSH2Types hash = new HashForSSH2Types(hashAlgo); - hash.updateByteString(clientversion); - hash.updateByteString(serverversion); - hash.updateByteString(clientKexPayload); - hash.updateByteString(serverKexPayload); - hash.updateByteString(hostKey); - - if (para.getMin_group_len() > 0) - hash.updateUINT32(para.getMin_group_len()); - - hash.updateUINT32(para.getPref_group_len()); - - if (para.getMax_group_len() > 0) - hash.updateUINT32(para.getMax_group_len()); - - hash.updateBigInt(p); - hash.updateBigInt(g); - hash.updateBigInt(e); - hash.updateBigInt(f); - hash.updateBigInt(k); - return hash.getDigest(); - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/crypto/dh/EcDhExchange.java --- a/src/com/trilead/ssh2/crypto/dh/EcDhExchange.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,114 +0,0 @@ -/** - * - */ -package com.trilead.ssh2.crypto.dh; - -import java.io.IOException; -import java.math.BigInteger; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; -import java.security.spec.ECParameterSpec; -import java.security.spec.ECPoint; -import java.security.spec.ECPublicKeySpec; -import java.security.spec.InvalidKeySpecException; - -import javax.crypto.KeyAgreement; - -import com.trilead.ssh2.signature.ECDSASHA2Verify; - -/** - * @author kenny - * - */ -public class EcDhExchange extends GenericDhExchange { - private ECPrivateKey clientPrivate; - private ECPublicKey clientPublic; - private ECPublicKey serverPublic; - - @Override - public void init(String name) throws IOException { - final ECParameterSpec spec; - - if ("ecdh-sha2-nistp256".equals(name)) { - spec = ECDSASHA2Verify.EllipticCurves.nistp256; - } - else if ("ecdh-sha2-nistp384".equals(name)) { - spec = ECDSASHA2Verify.EllipticCurves.nistp384; - } - else if ("ecdh-sha2-nistp521".equals(name)) { - spec = ECDSASHA2Verify.EllipticCurves.nistp521; - } - else { - throw new IllegalArgumentException("Unknown EC curve " + name); - } - - KeyPairGenerator kpg; - - try { - kpg = KeyPairGenerator.getInstance("EC"); - kpg.initialize(spec); - KeyPair pair = kpg.generateKeyPair(); - clientPrivate = (ECPrivateKey) pair.getPrivate(); - clientPublic = (ECPublicKey) pair.getPublic(); - } - catch (NoSuchAlgorithmException e) { - throw(IOException) new IOException("No DH keypair generator").initCause(e); - } - catch (InvalidAlgorithmParameterException e) { - throw(IOException) new IOException("Invalid DH parameters").initCause(e); - } - } - - @Override - public byte[] getE() { - return ECDSASHA2Verify.encodeECPoint(clientPublic.getW(), clientPublic.getParams() - .getCurve()); - } - - @Override - protected byte[] getServerE() { - return ECDSASHA2Verify.encodeECPoint(serverPublic.getW(), serverPublic.getParams() - .getCurve()); - } - - @Override - public void setF(byte[] f) throws IOException { - if (clientPublic == null) - throw new IllegalStateException("DhDsaExchange not initialized!"); - - final KeyAgreement ka; - - try { - KeyFactory kf = KeyFactory.getInstance("EC"); - ECParameterSpec params = clientPublic.getParams(); - ECPoint serverPoint = ECDSASHA2Verify.decodeECPoint(f, params.getCurve()); - this.serverPublic = (ECPublicKey) kf.generatePublic(new ECPublicKeySpec(serverPoint, - params)); - ka = KeyAgreement.getInstance("ECDH"); - ka.init(clientPrivate); - ka.doPhase(serverPublic, true); - } - catch (NoSuchAlgorithmException e) { - throw(IOException) new IOException("No ECDH key agreement method").initCause(e); - } - catch (InvalidKeyException e) { - throw(IOException) new IOException("Invalid ECDH key").initCause(e); - } - catch (InvalidKeySpecException e) { - throw(IOException) new IOException("Invalid ECDH key").initCause(e); - } - - sharedSecret = new BigInteger(ka.generateSecret()); - } - - @Override - public String getHashAlgo() { - return ECDSASHA2Verify.getDigestAlgorithmForParams(clientPublic.getParams()); - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/crypto/dh/GenericDhExchange.java --- a/src/com/trilead/ssh2/crypto/dh/GenericDhExchange.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,88 +0,0 @@ - -package com.trilead.ssh2.crypto.dh; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.math.BigInteger; - -import com.trilead.ssh2.crypto.digest.HashForSSH2Types; -import com.trilead.ssh2.log.Logger; - - -/** - * DhExchange. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: DhExchange.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ - */ -public abstract class GenericDhExchange { - private static final Logger log = Logger.getLogger(GenericDhExchange.class); - - /* Shared secret */ - - BigInteger sharedSecret; - - protected GenericDhExchange() { - } - - public static GenericDhExchange getInstance(String algo) { - if (algo.startsWith("ecdh-sha2-")) { - return new EcDhExchange(); - } - else { - return new DhExchange(); - } - } - - public abstract void init(String name) throws IOException; - - /** - * @return Returns the e (public value) - * @throws IllegalStateException - */ - public abstract byte[] getE(); - - /** - * @return Returns the server's e (public value) - * @throws IllegalStateException - */ - protected abstract byte[] getServerE(); - - /** - * @return Returns the shared secret k. - * @throws IllegalStateException - */ - public BigInteger getK() { - if (sharedSecret == null) - throw new IllegalStateException("Shared secret not yet known, need f first!"); - - return sharedSecret; - } - - /** - * @param f - */ - public abstract void setF(byte[] f) throws IOException; - - public byte[] calculateH(byte[] clientversion, byte[] serverversion, byte[] clientKexPayload, - byte[] serverKexPayload, byte[] hostKey) throws UnsupportedEncodingException { - HashForSSH2Types hash = new HashForSSH2Types(getHashAlgo()); - - if (log.isEnabled()) { - log.log(90, "Client: '" + new String(clientversion, "ISO-8859-1") + "'"); - log.log(90, "Server: '" + new String(serverversion, "ISO-8859-1") + "'"); - } - - hash.updateByteString(clientversion); - hash.updateByteString(serverversion); - hash.updateByteString(clientKexPayload); - hash.updateByteString(serverKexPayload); - hash.updateByteString(hostKey); - hash.updateByteString(getE()); - hash.updateByteString(getServerE()); - hash.updateBigInt(sharedSecret); - return hash.getDigest(); - } - - public abstract String getHashAlgo(); -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/crypto/digest/HashForSSH2Types.java --- a/src/com/trilead/ssh2/crypto/digest/HashForSSH2Types.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,81 +0,0 @@ - -package com.trilead.ssh2.crypto.digest; - -import java.math.BigInteger; -import java.security.DigestException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -/** - * HashForSSH2Types. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: HashForSSH2Types.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $ - */ -public class HashForSSH2Types { - MessageDigest md; - - public HashForSSH2Types(String type) { - try { - md = MessageDigest.getInstance(type); - } - catch (NoSuchAlgorithmException e) { - throw new RuntimeException("Unsupported algorithm " + type); - } - } - - public void updateByte(byte b) { - /* HACK - to test it with J2ME */ - byte[] tmp = new byte[1]; - tmp[0] = b; - md.update(tmp); - } - - public void updateBytes(byte[] b) { - md.update(b); - } - - public void updateUINT32(int v) { - md.update((byte)(v >> 24)); - md.update((byte)(v >> 16)); - md.update((byte)(v >> 8)); - md.update((byte)(v)); - } - - public void updateByteString(byte[] b) { - updateUINT32(b.length); - updateBytes(b); - } - - public void updateBigInt(BigInteger b) { - updateByteString(b.toByteArray()); - } - - public void reset() { - md.reset(); - } - - public int getDigestLength() { - return md.getDigestLength(); - } - - public byte[] getDigest() { - byte[] tmp = new byte[md.getDigestLength()]; - getDigest(tmp); - return tmp; - } - - public void getDigest(byte[] out) { - getDigest(out, 0); - } - - public void getDigest(byte[] out, int off) { - try { - md.digest(out, off, out.length - off); - } - catch (DigestException e) { - // TODO is this right?! - throw new RuntimeException("Unable to digest", e); - } - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/crypto/digest/MAC.java --- a/src/com/trilead/ssh2/crypto/digest/MAC.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,154 +0,0 @@ - -package com.trilead.ssh2.crypto.digest; - -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; - -import javax.crypto.Mac; -import javax.crypto.ShortBufferException; -import javax.crypto.spec.SecretKeySpec; - -/** - * MAC. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: MAC.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $ - */ -public final class MAC { - /** - * From http://tools.ietf.org/html/rfc4253 - */ - private static final String HMAC_MD5 = "hmac-md5"; - - /** - * From http://tools.ietf.org/html/rfc4253 - */ - private static final String HMAC_MD5_96 = "hmac-md5-96"; - - /** - * From http://tools.ietf.org/html/rfc4253 - */ - private static final String HMAC_SHA1 = "hmac-sha1"; - - /** - * From http://tools.ietf.org/html/rfc4253 - */ - private static final String HMAC_SHA1_96 = "hmac-sha1-96"; - - /** - * From http://tools.ietf.org/html/rfc6668 - */ - private static final String HMAC_SHA2_256 = "hmac-sha2-256"; - - /** - * From http://tools.ietf.org/html/rfc6668 - */ - private static final String HMAC_SHA2_512 = "hmac-sha2-512"; - - Mac mac; - int outSize; - int macSize; - byte[] buffer; - - /* Higher Priority First */ - private static final String[] MAC_LIST = { - HMAC_SHA2_256, HMAC_SHA2_512, - HMAC_SHA1_96, HMAC_SHA1, HMAC_MD5_96, HMAC_MD5 - }; - - public final static String[] getMacList() { - return MAC_LIST; - } - - public final static void checkMacList(String[] macs) { - for (int i = 0; i < macs.length; i++) - getKeyLen(macs[i]); - } - - public final static int getKeyLen(String type) { - if (HMAC_SHA1.equals(type) || HMAC_SHA1_96.equals(type)) - return 20; - - if (HMAC_MD5.equals(type) || HMAC_MD5_96.equals(type)) - return 16; - - if (HMAC_SHA2_256.equals(type)) - return 32; - - if (HMAC_SHA2_512.equals(type)) - return 64; - - throw new IllegalArgumentException("Unkown algorithm " + type); - } - - public MAC(String type, byte[] key) { - try { - if (HMAC_SHA1.equals(type) || HMAC_SHA1_96.equals(type)) { - mac = Mac.getInstance("HmacSHA1"); - } - else if (HMAC_MD5.equals(type) || HMAC_MD5_96.equals(type)) { - mac = Mac.getInstance("HmacMD5"); - } - else if (HMAC_SHA2_256.equals(type)) { - mac = Mac.getInstance("HmacSHA256"); - } - else if (HMAC_SHA2_512.equals(type)) { - mac = Mac.getInstance("HmacSHA512"); - } - else - throw new IllegalArgumentException("Unkown algorithm " + type); - } - catch (NoSuchAlgorithmException e) { - throw new IllegalArgumentException("Unknown algorithm " + type, e); - } - - macSize = mac.getMacLength(); - - if (type.endsWith("-96")) { - outSize = 12; - buffer = new byte[macSize]; - } - else { - outSize = macSize; - buffer = null; - } - - try { - mac.init(new SecretKeySpec(key, type)); - } - catch (InvalidKeyException e) { - throw new IllegalArgumentException(e); - } - } - - public final void initMac(int seq) { - mac.reset(); - mac.update((byte)(seq >> 24)); - mac.update((byte)(seq >> 16)); - mac.update((byte)(seq >> 8)); - mac.update((byte)(seq)); - } - - public final void update(byte[] packetdata, int off, int len) { - mac.update(packetdata, off, len); - } - - public final void getMac(byte[] out, int off) { - try { - if (buffer != null) { - mac.doFinal(buffer, 0); - System.arraycopy(buffer, 0, out, off, out.length - off); - } - else { - mac.doFinal(out, off); - } - } - catch (ShortBufferException e) { - throw new IllegalStateException(e); - } - } - - public final int size() { - return outSize; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/log/Logger.java --- a/src/com/trilead/ssh2/log/Logger.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ - -package com.trilead.ssh2.log; - -import com.trilead.ssh2.DebugLogger; - -/** - * Logger - a very simple logger, mainly used during development. - * Is not based on log4j (to reduce external dependencies). - * However, if needed, something like log4j could easily be - * hooked in. - *

- * For speed reasons, the static variables are not protected - * with semaphores. In other words, if you dynamicaly change the - * logging settings, then some threads may still use the old setting. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: Logger.java,v 1.2 2008/03/03 07:01:36 cplattne Exp $ - */ - -public class Logger { - public static boolean enabled = false; - public static DebugLogger logger = null; - - private String className; - - public final static Logger getLogger(Class x) { - return new Logger(x); - } - - public Logger(Class x) { - this.className = x.getName(); - } - - public final boolean isEnabled() { - return enabled; - } - - public final void log(int level, String message) { - if (!enabled) - return; - - DebugLogger target = logger; - - if (target == null) - return; - - target.log(level, className, message); - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketChannelAuthAgentReq.java --- a/src/com/trilead/ssh2/packets/PacketChannelAuthAgentReq.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -package com.trilead.ssh2.packets; - -/** - * PacketGlobalAuthAgent. - * - * @author Kenny Root, kenny@the-b.org - * @version $Id$ - */ -public class PacketChannelAuthAgentReq { - byte[] payload; - - public int recipientChannelID; - - public PacketChannelAuthAgentReq(int recipientChannelID) { - this.recipientChannelID = recipientChannelID; - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); - tw.writeUINT32(recipientChannelID); - tw.writeString("auth-agent-req@openssh.com"); - tw.writeBoolean(true); // want reply - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketChannelOpenConfirmation.java --- a/src/com/trilead/ssh2/packets/PacketChannelOpenConfirmation.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ -package com.trilead.ssh2.packets; - -import java.io.IOException; - -/** - * PacketChannelOpenConfirmation. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketChannelOpenConfirmation.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketChannelOpenConfirmation { - byte[] payload; - - public int recipientChannelID; - public int senderChannelID; - public int initialWindowSize; - public int maxPacketSize; - - public PacketChannelOpenConfirmation(int recipientChannelID, int senderChannelID, int initialWindowSize, - int maxPacketSize) { - this.recipientChannelID = recipientChannelID; - this.senderChannelID = senderChannelID; - this.initialWindowSize = initialWindowSize; - this.maxPacketSize = maxPacketSize; - } - - public PacketChannelOpenConfirmation(byte payload[], int off, int len) throws IOException { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - TypesReader tr = new TypesReader(payload, off, len); - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_CHANNEL_OPEN_CONFIRMATION) - throw new IOException( - "This is not a SSH_MSG_CHANNEL_OPEN_CONFIRMATION! (" - + packet_type + ")"); - - recipientChannelID = tr.readUINT32(); - senderChannelID = tr.readUINT32(); - initialWindowSize = tr.readUINT32(); - maxPacketSize = tr.readUINT32(); - - if (tr.remain() != 0) - throw new IOException("Padding in SSH_MSG_CHANNEL_OPEN_CONFIRMATION packet!"); - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_CHANNEL_OPEN_CONFIRMATION); - tw.writeUINT32(recipientChannelID); - tw.writeUINT32(senderChannelID); - tw.writeUINT32(initialWindowSize); - tw.writeUINT32(maxPacketSize); - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketChannelOpenFailure.java --- a/src/com/trilead/ssh2/packets/PacketChannelOpenFailure.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ -package com.trilead.ssh2.packets; - -import java.io.IOException; - -/** - * PacketChannelOpenFailure. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketChannelOpenFailure.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketChannelOpenFailure { - byte[] payload; - - public int recipientChannelID; - public int reasonCode; - public String description; - public String languageTag; - - public PacketChannelOpenFailure(int recipientChannelID, int reasonCode, String description, - String languageTag) { - this.recipientChannelID = recipientChannelID; - this.reasonCode = reasonCode; - this.description = description; - this.languageTag = languageTag; - } - - public PacketChannelOpenFailure(byte payload[], int off, int len) throws IOException { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - TypesReader tr = new TypesReader(payload, off, len); - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_CHANNEL_OPEN_FAILURE) - throw new IOException( - "This is not a SSH_MSG_CHANNEL_OPEN_FAILURE! (" - + packet_type + ")"); - - recipientChannelID = tr.readUINT32(); - reasonCode = tr.readUINT32(); - description = tr.readString(); - languageTag = tr.readString(); - - if (tr.remain() != 0) - throw new IOException("Padding in SSH_MSG_CHANNEL_OPEN_FAILURE packet!"); - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_CHANNEL_OPEN_FAILURE); - tw.writeUINT32(recipientChannelID); - tw.writeUINT32(reasonCode); - tw.writeString(description); - tw.writeString(languageTag); - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketChannelTrileadPing.java --- a/src/com/trilead/ssh2/packets/PacketChannelTrileadPing.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,32 +0,0 @@ - -package com.trilead.ssh2.packets; - -/** - * PacketChannelTrileadPing. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketChannelTrileadPing.java,v 1.1 2008/03/03 07:01:36 - * cplattne Exp $ - */ -public class PacketChannelTrileadPing { - byte[] payload; - - public int recipientChannelID; - - public PacketChannelTrileadPing(int recipientChannelID) { - this.recipientChannelID = recipientChannelID; - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); - tw.writeUINT32(recipientChannelID); - tw.writeString("trilead-ping"); - tw.writeBoolean(true); - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketChannelWindowAdjust.java --- a/src/com/trilead/ssh2/packets/PacketChannelWindowAdjust.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,51 +0,0 @@ -package com.trilead.ssh2.packets; - -import java.io.IOException; - -/** - * PacketChannelWindowAdjust. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketChannelWindowAdjust.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketChannelWindowAdjust { - byte[] payload; - - public int recipientChannelID; - public int windowChange; - - public PacketChannelWindowAdjust(int recipientChannelID, int windowChange) { - this.recipientChannelID = recipientChannelID; - this.windowChange = windowChange; - } - - public PacketChannelWindowAdjust(byte payload[], int off, int len) throws IOException { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - TypesReader tr = new TypesReader(payload, off, len); - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST) - throw new IOException( - "This is not a SSH_MSG_CHANNEL_WINDOW_ADJUST! (" - + packet_type + ")"); - - recipientChannelID = tr.readUINT32(); - windowChange = tr.readUINT32(); - - if (tr.remain() != 0) - throw new IOException("Padding in SSH_MSG_CHANNEL_WINDOW_ADJUST packet!"); - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST); - tw.writeUINT32(recipientChannelID); - tw.writeUINT32(windowChange); - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketDisconnect.java --- a/src/com/trilead/ssh2/packets/PacketDisconnect.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,51 +0,0 @@ - -package com.trilead.ssh2.packets; - -import java.io.IOException; - -/** - * PacketDisconnect. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketDisconnect.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ - */ -public class PacketDisconnect { - byte[] payload; - - int reason; - String desc; - String lang; - - public PacketDisconnect(byte payload[], int off, int len) throws IOException { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - TypesReader tr = new TypesReader(payload, off, len); - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_DISCONNECT) - throw new IOException("This is not a Disconnect Packet! (" + packet_type + ")"); - - reason = tr.readUINT32(); - desc = tr.readString(); - lang = tr.readString(); - } - - public PacketDisconnect(int reason, String desc, String lang) { - this.reason = reason; - this.desc = desc; - this.lang = lang; - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_DISCONNECT); - tw.writeUINT32(reason); - tw.writeString(desc); - tw.writeString(lang); - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketGlobalCancelForwardRequest.java --- a/src/com/trilead/ssh2/packets/PacketGlobalCancelForwardRequest.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,37 +0,0 @@ - -package com.trilead.ssh2.packets; - -/** - * PacketGlobalCancelForwardRequest. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketGlobalCancelForwardRequest.java,v 1.1 2007/10/15 12:49:55 - * cplattne Exp $ - */ -public class PacketGlobalCancelForwardRequest { - byte[] payload; - - public boolean wantReply; - public String bindAddress; - public int bindPort; - - public PacketGlobalCancelForwardRequest(boolean wantReply, String bindAddress, int bindPort) { - this.wantReply = wantReply; - this.bindAddress = bindAddress; - this.bindPort = bindPort; - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_GLOBAL_REQUEST); - tw.writeString("cancel-tcpip-forward"); - tw.writeBoolean(wantReply); - tw.writeString(bindAddress); - tw.writeUINT32(bindPort); - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketGlobalForwardRequest.java --- a/src/com/trilead/ssh2/packets/PacketGlobalForwardRequest.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ - -package com.trilead.ssh2.packets; - -/** - * PacketGlobalForwardRequest. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketGlobalForwardRequest.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketGlobalForwardRequest { - byte[] payload; - - public boolean wantReply; - public String bindAddress; - public int bindPort; - - public PacketGlobalForwardRequest(boolean wantReply, String bindAddress, int bindPort) { - this.wantReply = wantReply; - this.bindAddress = bindAddress; - this.bindPort = bindPort; - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_GLOBAL_REQUEST); - tw.writeString("tcpip-forward"); - tw.writeBoolean(wantReply); - tw.writeString(bindAddress); - tw.writeUINT32(bindPort); - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketGlobalTrileadPing.java --- a/src/com/trilead/ssh2/packets/PacketGlobalTrileadPing.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ - -package com.trilead.ssh2.packets; - -/** - * PacketGlobalTrileadPing. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketGlobalTrileadPing.java,v 1.1 2008/03/03 07:01:36 cplattne Exp $ - */ -public class PacketGlobalTrileadPing { - byte[] payload; - - public PacketGlobalTrileadPing() { - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_GLOBAL_REQUEST); - tw.writeString("trilead-ping"); - tw.writeBoolean(true); - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketIgnore.java --- a/src/com/trilead/ssh2/packets/PacketIgnore.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ - -package com.trilead.ssh2.packets; - -import java.io.IOException; - -/** - * PacketIgnore. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketIgnore.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketIgnore { - byte[] payload; - - byte[] data; - - public void setData(byte[] data) { - this.data = data; - payload = null; - } - - public PacketIgnore() { - } - - public PacketIgnore(byte payload[], int off, int len) throws IOException { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - TypesReader tr = new TypesReader(payload, off, len); - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_IGNORE) - throw new IOException("This is not a SSH_MSG_IGNORE packet! (" + packet_type + ")"); - - /* Could parse String body */ - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_IGNORE); - - if (data != null) - tw.writeString(data, 0, data.length); - else - tw.writeString(""); - - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketKexDHInit.java --- a/src/com/trilead/ssh2/packets/PacketKexDHInit.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,28 +0,0 @@ -package com.trilead.ssh2.packets; - -/** - * PacketKexDHInit. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketKexDHInit.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketKexDHInit { - byte[] payload; - - byte[] publicKey; - - public PacketKexDHInit(byte[] publicKey) { - this.publicKey = publicKey; - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_KEXDH_INIT); - tw.writeString(publicKey, 0, publicKey.length); - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketKexDHReply.java --- a/src/com/trilead/ssh2/packets/PacketKexDHReply.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -package com.trilead.ssh2.packets; - -import java.io.IOException; - -/** - * PacketKexDHReply. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketKexDHReply.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketKexDHReply { - byte[] payload; - - byte[] hostKey; - byte[] publicKey; - byte[] signature; - - public PacketKexDHReply(byte payload[], int off, int len) throws IOException { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - TypesReader tr = new TypesReader(payload, off, len); - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_KEXDH_REPLY) - throw new IOException("This is not a SSH_MSG_KEXDH_REPLY! (" - + packet_type + ")"); - - hostKey = tr.readByteString(); - publicKey = tr.readByteString(); - signature = tr.readByteString(); - - if (tr.remain() != 0) throw new IOException("PADDING IN SSH_MSG_KEXDH_REPLY!"); - } - - public byte[] getF() { - return publicKey; - } - - public byte[] getHostKey() { - return hostKey; - } - - public byte[] getSignature() { - return signature; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketKexDhGexGroup.java --- a/src/com/trilead/ssh2/packets/PacketKexDhGexGroup.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -package com.trilead.ssh2.packets; - -import java.io.IOException; - -import java.math.BigInteger; - -/** - * PacketKexDhGexGroup. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketKexDhGexGroup.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketKexDhGexGroup { - byte[] payload; - - BigInteger p; - BigInteger g; - - public PacketKexDhGexGroup(byte payload[], int off, int len) throws IOException { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - TypesReader tr = new TypesReader(payload, off, len); - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_KEX_DH_GEX_GROUP) - throw new IllegalArgumentException( - "This is not a SSH_MSG_KEX_DH_GEX_GROUP! (" + packet_type - + ")"); - - p = tr.readMPINT(); - g = tr.readMPINT(); - - if (tr.remain() != 0) - throw new IOException("PADDING IN SSH_MSG_KEX_DH_GEX_GROUP!"); - } - - public BigInteger getG() { - return g; - } - - public BigInteger getP() { - return p; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketKexDhGexInit.java --- a/src/com/trilead/ssh2/packets/PacketKexDhGexInit.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -package com.trilead.ssh2.packets; - -import java.math.BigInteger; - -/** - * PacketKexDhGexInit. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketKexDhGexInit.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketKexDhGexInit { - byte[] payload; - - BigInteger e; - - public PacketKexDhGexInit(BigInteger e) { - this.e = e; - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_KEX_DH_GEX_INIT); - tw.writeMPInt(e); - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketKexDhGexReply.java --- a/src/com/trilead/ssh2/packets/PacketKexDhGexReply.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ - -package com.trilead.ssh2.packets; - -import java.io.IOException; - -import java.math.BigInteger; - -/** - * PacketKexDhGexReply. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketKexDhGexReply.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketKexDhGexReply { - byte[] payload; - - byte[] hostKey; - BigInteger f; - byte[] signature; - - public PacketKexDhGexReply(byte payload[], int off, int len) throws IOException { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - TypesReader tr = new TypesReader(payload, off, len); - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_KEX_DH_GEX_REPLY) - throw new IOException("This is not a SSH_MSG_KEX_DH_GEX_REPLY! (" + packet_type + ")"); - - hostKey = tr.readByteString(); - f = tr.readMPINT(); - signature = tr.readByteString(); - - if (tr.remain() != 0) - throw new IOException("PADDING IN SSH_MSG_KEX_DH_GEX_REPLY!"); - } - - public BigInteger getF() { - return f; - } - - public byte[] getHostKey() { - return hostKey; - } - - public byte[] getSignature() { - return signature; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketKexDhGexRequest.java --- a/src/com/trilead/ssh2/packets/PacketKexDhGexRequest.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ -package com.trilead.ssh2.packets; - -import com.trilead.ssh2.DHGexParameters; - -/** - * PacketKexDhGexRequest. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketKexDhGexRequest.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketKexDhGexRequest { - byte[] payload; - - int min; - int n; - int max; - - public PacketKexDhGexRequest(DHGexParameters para) { - this.min = para.getMin_group_len(); - this.n = para.getPref_group_len(); - this.max = para.getMax_group_len(); - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_KEX_DH_GEX_REQUEST); - tw.writeUINT32(min); - tw.writeUINT32(n); - tw.writeUINT32(max); - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketKexDhGexRequestOld.java --- a/src/com/trilead/ssh2/packets/PacketKexDhGexRequestOld.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ - -package com.trilead.ssh2.packets; - -import com.trilead.ssh2.DHGexParameters; - -/** - * PacketKexDhGexRequestOld. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketKexDhGexRequestOld.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketKexDhGexRequestOld { - byte[] payload; - - int n; - - public PacketKexDhGexRequestOld(DHGexParameters para) { - this.n = para.getPref_group_len(); - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_KEX_DH_GEX_REQUEST_OLD); - tw.writeUINT32(n); - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketKexInit.java --- a/src/com/trilead/ssh2/packets/PacketKexInit.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,144 +0,0 @@ - -package com.trilead.ssh2.packets; - -import java.io.IOException; -import java.security.SecureRandom; - -import com.trilead.ssh2.crypto.CryptoWishList; -import com.trilead.ssh2.transport.KexParameters; - - -/** - * PacketKexInit. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketKexInit.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketKexInit { - byte[] payload; - - KexParameters kp = new KexParameters(); - - public PacketKexInit(CryptoWishList cwl) { - kp.cookie = new byte[16]; - new SecureRandom().nextBytes(kp.cookie); - kp.kex_algorithms = cwl.kexAlgorithms; - kp.server_host_key_algorithms = cwl.serverHostKeyAlgorithms; - kp.encryption_algorithms_client_to_server = cwl.c2s_enc_algos; - kp.encryption_algorithms_server_to_client = cwl.s2c_enc_algos; - kp.mac_algorithms_client_to_server = cwl.c2s_mac_algos; - kp.mac_algorithms_server_to_client = cwl.s2c_mac_algos; - kp.compression_algorithms_client_to_server = cwl.c2s_comp_algos; - kp.compression_algorithms_server_to_client = cwl.s2c_comp_algos; - kp.languages_client_to_server = new String[] {}; - kp.languages_server_to_client = new String[] {}; - kp.first_kex_packet_follows = false; - kp.reserved_field1 = 0; - } - - public PacketKexInit(byte payload[], int off, int len) throws IOException { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - TypesReader tr = new TypesReader(payload, off, len); - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_KEXINIT) - throw new IOException("This is not a KexInitPacket! (" + packet_type + ")"); - - kp.cookie = tr.readBytes(16); - kp.kex_algorithms = tr.readNameList(); - kp.server_host_key_algorithms = tr.readNameList(); - kp.encryption_algorithms_client_to_server = tr.readNameList(); - kp.encryption_algorithms_server_to_client = tr.readNameList(); - kp.mac_algorithms_client_to_server = tr.readNameList(); - kp.mac_algorithms_server_to_client = tr.readNameList(); - kp.compression_algorithms_client_to_server = tr.readNameList(); - kp.compression_algorithms_server_to_client = tr.readNameList(); - kp.languages_client_to_server = tr.readNameList(); - kp.languages_server_to_client = tr.readNameList(); - kp.first_kex_packet_follows = tr.readBoolean(); - kp.reserved_field1 = tr.readUINT32(); - - if (tr.remain() != 0) - throw new IOException("Padding in KexInitPacket!"); - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_KEXINIT); - tw.writeBytes(kp.cookie, 0, 16); - tw.writeNameList(kp.kex_algorithms); - tw.writeNameList(kp.server_host_key_algorithms); - tw.writeNameList(kp.encryption_algorithms_client_to_server); - tw.writeNameList(kp.encryption_algorithms_server_to_client); - tw.writeNameList(kp.mac_algorithms_client_to_server); - tw.writeNameList(kp.mac_algorithms_server_to_client); - tw.writeNameList(kp.compression_algorithms_client_to_server); - tw.writeNameList(kp.compression_algorithms_server_to_client); - tw.writeNameList(kp.languages_client_to_server); - tw.writeNameList(kp.languages_server_to_client); - tw.writeBoolean(kp.first_kex_packet_follows); - tw.writeUINT32(kp.reserved_field1); - payload = tw.getBytes(); - } - - return payload; - } - - public KexParameters getKexParameters() { - return kp; - } - - public String[] getCompression_algorithms_client_to_server() { - return kp.compression_algorithms_client_to_server; - } - - public String[] getCompression_algorithms_server_to_client() { - return kp.compression_algorithms_server_to_client; - } - - public byte[] getCookie() { - return kp.cookie; - } - - public String[] getEncryption_algorithms_client_to_server() { - return kp.encryption_algorithms_client_to_server; - } - - public String[] getEncryption_algorithms_server_to_client() { - return kp.encryption_algorithms_server_to_client; - } - - public boolean isFirst_kex_packet_follows() { - return kp.first_kex_packet_follows; - } - - public String[] getKex_algorithms() { - return kp.kex_algorithms; - } - - public String[] getLanguages_client_to_server() { - return kp.languages_client_to_server; - } - - public String[] getLanguages_server_to_client() { - return kp.languages_server_to_client; - } - - public String[] getMac_algorithms_client_to_server() { - return kp.mac_algorithms_client_to_server; - } - - public String[] getMac_algorithms_server_to_client() { - return kp.mac_algorithms_server_to_client; - } - - public int getReserved_field1() { - return kp.reserved_field1; - } - - public String[] getServer_host_key_algorithms() { - return kp.server_host_key_algorithms; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketNewKeys.java --- a/src/com/trilead/ssh2/packets/PacketNewKeys.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -package com.trilead.ssh2.packets; - -import java.io.IOException; - -/** - * PacketNewKeys. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketNewKeys.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketNewKeys { - byte[] payload; - - public PacketNewKeys() { - } - - public PacketNewKeys(byte payload[], int off, int len) throws IOException { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - TypesReader tr = new TypesReader(payload, off, len); - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_NEWKEYS) - throw new IOException("This is not a SSH_MSG_NEWKEYS! (" - + packet_type + ")"); - - if (tr.remain() != 0) - throw new IOException("Padding in SSH_MSG_NEWKEYS packet!"); - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_NEWKEYS); - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketOpenDirectTCPIPChannel.java --- a/src/com/trilead/ssh2/packets/PacketOpenDirectTCPIPChannel.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,51 +0,0 @@ -package com.trilead.ssh2.packets; - - -/** - * PacketOpenDirectTCPIPChannel. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketOpenDirectTCPIPChannel.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketOpenDirectTCPIPChannel { - byte[] payload; - - int channelID; - int initialWindowSize; - int maxPacketSize; - - String host_to_connect; - int port_to_connect; - String originator_IP_address; - int originator_port; - - public PacketOpenDirectTCPIPChannel(int channelID, int initialWindowSize, int maxPacketSize, - String host_to_connect, int port_to_connect, String originator_IP_address, - int originator_port) { - this.channelID = channelID; - this.initialWindowSize = initialWindowSize; - this.maxPacketSize = maxPacketSize; - this.host_to_connect = host_to_connect; - this.port_to_connect = port_to_connect; - this.originator_IP_address = originator_IP_address; - this.originator_port = originator_port; - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_CHANNEL_OPEN); - tw.writeString("direct-tcpip"); - tw.writeUINT32(channelID); - tw.writeUINT32(initialWindowSize); - tw.writeUINT32(maxPacketSize); - tw.writeString(host_to_connect); - tw.writeUINT32(port_to_connect); - tw.writeString(originator_IP_address); - tw.writeUINT32(originator_port); - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketOpenSessionChannel.java --- a/src/com/trilead/ssh2/packets/PacketOpenSessionChannel.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -package com.trilead.ssh2.packets; - -import java.io.IOException; - -/** - * PacketOpenSessionChannel. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketOpenSessionChannel.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketOpenSessionChannel { - byte[] payload; - - int channelID; - int initialWindowSize; - int maxPacketSize; - - public PacketOpenSessionChannel(int channelID, int initialWindowSize, - int maxPacketSize) { - this.channelID = channelID; - this.initialWindowSize = initialWindowSize; - this.maxPacketSize = maxPacketSize; - } - - public PacketOpenSessionChannel(byte payload[], int off, int len) throws IOException { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - TypesReader tr = new TypesReader(payload); - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_CHANNEL_OPEN) - throw new IOException("This is not a SSH_MSG_CHANNEL_OPEN! (" - + packet_type + ")"); - - channelID = tr.readUINT32(); - initialWindowSize = tr.readUINT32(); - maxPacketSize = tr.readUINT32(); - - if (tr.remain() != 0) - throw new IOException("Padding in SSH_MSG_CHANNEL_OPEN packet!"); - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_CHANNEL_OPEN); - tw.writeString("session"); - tw.writeUINT32(channelID); - tw.writeUINT32(initialWindowSize); - tw.writeUINT32(maxPacketSize); - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketServiceAccept.java --- a/src/com/trilead/ssh2/packets/PacketServiceAccept.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ - -package com.trilead.ssh2.packets; - -import java.io.IOException; - -/** - * PacketServiceAccept. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketServiceAccept.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ - */ -public class PacketServiceAccept { - byte[] payload; - - String serviceName; - - public PacketServiceAccept(String serviceName) { - this.serviceName = serviceName; - } - - public PacketServiceAccept(byte payload[], int off, int len) throws IOException { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - TypesReader tr = new TypesReader(payload, off, len); - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_SERVICE_ACCEPT) - throw new IOException("This is not a SSH_MSG_SERVICE_ACCEPT! (" + packet_type + ")"); - - /* Be clever in case the server is not. Some servers seem to violate RFC4253 */ - - if (tr.remain() > 0) { - serviceName = tr.readString(); - } - else { - serviceName = ""; - } - - if (tr.remain() != 0) - throw new IOException("Padding in SSH_MSG_SERVICE_ACCEPT packet!"); - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_SERVICE_ACCEPT); - tw.writeString(serviceName); - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketServiceRequest.java --- a/src/com/trilead/ssh2/packets/PacketServiceRequest.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -package com.trilead.ssh2.packets; - -import java.io.IOException; - -/** - * PacketServiceRequest. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketServiceRequest.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketServiceRequest { - byte[] payload; - - String serviceName; - - public PacketServiceRequest(String serviceName) { - this.serviceName = serviceName; - } - - public PacketServiceRequest(byte payload[], int off, int len) throws IOException { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - TypesReader tr = new TypesReader(payload, off, len); - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_SERVICE_REQUEST) - throw new IOException("This is not a SSH_MSG_SERVICE_REQUEST! (" - + packet_type + ")"); - - serviceName = tr.readString(); - - if (tr.remain() != 0) - throw new IOException("Padding in SSH_MSG_SERVICE_REQUEST packet!"); - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_SERVICE_REQUEST); - tw.writeString(serviceName); - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketSessionExecCommand.java --- a/src/com/trilead/ssh2/packets/PacketSessionExecCommand.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ -package com.trilead.ssh2.packets; - - -/** - * PacketSessionExecCommand. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketSessionExecCommand.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketSessionExecCommand { - byte[] payload; - - public int recipientChannelID; - public boolean wantReply; - public String command; - - public PacketSessionExecCommand(int recipientChannelID, boolean wantReply, String command) { - this.recipientChannelID = recipientChannelID; - this.wantReply = wantReply; - this.command = command; - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); - tw.writeUINT32(recipientChannelID); - tw.writeString("exec"); - tw.writeBoolean(wantReply); - tw.writeString(command); - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketSessionPtyRequest.java --- a/src/com/trilead/ssh2/packets/PacketSessionPtyRequest.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -package com.trilead.ssh2.packets; - - -/** - * PacketSessionPtyRequest. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketSessionPtyRequest.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketSessionPtyRequest { - byte[] payload; - - public int recipientChannelID; - public boolean wantReply; - public String term; - public int character_width; - public int character_height; - public int pixel_width; - public int pixel_height; - public byte[] terminal_modes; - - public PacketSessionPtyRequest(int recipientChannelID, boolean wantReply, String term, - int character_width, int character_height, int pixel_width, int pixel_height, - byte[] terminal_modes) { - this.recipientChannelID = recipientChannelID; - this.wantReply = wantReply; - this.term = term; - this.character_width = character_width; - this.character_height = character_height; - this.pixel_width = pixel_width; - this.pixel_height = pixel_height; - this.terminal_modes = terminal_modes; - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); - tw.writeUINT32(recipientChannelID); - tw.writeString("pty-req"); - tw.writeBoolean(wantReply); - tw.writeString(term); - tw.writeUINT32(character_width); - tw.writeUINT32(character_height); - tw.writeUINT32(pixel_width); - tw.writeUINT32(pixel_height); - tw.writeString(terminal_modes, 0, terminal_modes.length); - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketSessionPtyResize.java --- a/src/com/trilead/ssh2/packets/PacketSessionPtyResize.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -package com.trilead.ssh2.packets; - -public class PacketSessionPtyResize { - byte[] payload; - - public int recipientChannelID; - public int width; - public int height; - public int pixelWidth; - public int pixelHeight; - - public PacketSessionPtyResize(int recipientChannelID, int width, int height, int pixelWidth, int pixelHeight) { - this.recipientChannelID = recipientChannelID; - this.width = width; - this.height = height; - this.pixelWidth = pixelWidth; - this.pixelHeight = pixelHeight; - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); - tw.writeUINT32(recipientChannelID); - tw.writeString("window-change"); - tw.writeBoolean(false); - tw.writeUINT32(width); - tw.writeUINT32(height); - tw.writeUINT32(pixelWidth); - tw.writeUINT32(pixelHeight); - payload = tw.getBytes(); - } - - return payload; - } -} - - diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketSessionStartShell.java --- a/src/com/trilead/ssh2/packets/PacketSessionStartShell.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ - -package com.trilead.ssh2.packets; - -/** - * PacketSessionStartShell. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketSessionStartShell.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketSessionStartShell { - byte[] payload; - - public int recipientChannelID; - public boolean wantReply; - - public PacketSessionStartShell(int recipientChannelID, boolean wantReply) { - this.recipientChannelID = recipientChannelID; - this.wantReply = wantReply; - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); - tw.writeUINT32(recipientChannelID); - tw.writeString("shell"); - tw.writeBoolean(wantReply); - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketSessionSubsystemRequest.java --- a/src/com/trilead/ssh2/packets/PacketSessionSubsystemRequest.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,37 +0,0 @@ -package com.trilead.ssh2.packets; - - -/** - * PacketSessionSubsystemRequest. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketSessionSubsystemRequest.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketSessionSubsystemRequest { - byte[] payload; - - public int recipientChannelID; - public boolean wantReply; - public String subsystem; - - public PacketSessionSubsystemRequest(int recipientChannelID, boolean wantReply, String subsystem) { - this.recipientChannelID = recipientChannelID; - this.wantReply = wantReply; - this.subsystem = subsystem; - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); - tw.writeUINT32(recipientChannelID); - tw.writeString("subsystem"); - tw.writeBoolean(wantReply); - tw.writeString(subsystem); - payload = tw.getBytes(); - tw.getBytes(payload); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketSessionX11Request.java --- a/src/com/trilead/ssh2/packets/PacketSessionX11Request.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,47 +0,0 @@ - -package com.trilead.ssh2.packets; - -/** - * PacketSessionX11Request. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketSessionX11Request.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketSessionX11Request { - byte[] payload; - - public int recipientChannelID; - public boolean wantReply; - - public boolean singleConnection; - String x11AuthenticationProtocol; - String x11AuthenticationCookie; - int x11ScreenNumber; - - public PacketSessionX11Request(int recipientChannelID, boolean wantReply, boolean singleConnection, - String x11AuthenticationProtocol, String x11AuthenticationCookie, int x11ScreenNumber) { - this.recipientChannelID = recipientChannelID; - this.wantReply = wantReply; - this.singleConnection = singleConnection; - this.x11AuthenticationProtocol = x11AuthenticationProtocol; - this.x11AuthenticationCookie = x11AuthenticationCookie; - this.x11ScreenNumber = x11ScreenNumber; - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); - tw.writeUINT32(recipientChannelID); - tw.writeString("x11-req"); - tw.writeBoolean(wantReply); - tw.writeBoolean(singleConnection); - tw.writeString(x11AuthenticationProtocol); - tw.writeString(x11AuthenticationCookie); - tw.writeUINT32(x11ScreenNumber); - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketUserauthBanner.java --- a/src/com/trilead/ssh2/packets/PacketUserauthBanner.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -package com.trilead.ssh2.packets; - -import java.io.IOException; - -/** - * PacketUserauthBanner. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketUserauthBanner.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketUserauthBanner { - byte[] payload; - - String message; - String language; - - public PacketUserauthBanner(String message, String language) { - this.message = message; - this.language = language; - } - - public String getBanner() { - return message; - } - - public PacketUserauthBanner(byte payload[], int off, int len) throws IOException { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - TypesReader tr = new TypesReader(payload, off, len); - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_USERAUTH_BANNER) - throw new IOException("This is not a SSH_MSG_USERAUTH_BANNER! (" + packet_type + ")"); - - message = tr.readString("UTF-8"); - language = tr.readString(); - - if (tr.remain() != 0) - throw new IOException("Padding in SSH_MSG_USERAUTH_REQUEST packet!"); - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_USERAUTH_BANNER); - tw.writeString(message); - tw.writeString(language); - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketUserauthFailure.java --- a/src/com/trilead/ssh2/packets/PacketUserauthFailure.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ - -package com.trilead.ssh2.packets; - -import java.io.IOException; - -/** - * PacketUserauthBanner. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketUserauthFailure.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketUserauthFailure { - byte[] payload; - - String[] authThatCanContinue; - boolean partialSuccess; - - public PacketUserauthFailure(String[] authThatCanContinue, boolean partialSuccess) { - this.authThatCanContinue = authThatCanContinue; - this.partialSuccess = partialSuccess; - } - - public PacketUserauthFailure(byte payload[], int off, int len) throws IOException { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - TypesReader tr = new TypesReader(payload, off, len); - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_USERAUTH_FAILURE) - throw new IOException("This is not a SSH_MSG_USERAUTH_FAILURE! (" + packet_type + ")"); - - authThatCanContinue = tr.readNameList(); - partialSuccess = tr.readBoolean(); - - if (tr.remain() != 0) - throw new IOException("Padding in SSH_MSG_USERAUTH_FAILURE packet!"); - } - - public String[] getAuthThatCanContinue() { - return authThatCanContinue; - } - - public boolean isPartialSuccess() { - return partialSuccess; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketUserauthInfoRequest.java --- a/src/com/trilead/ssh2/packets/PacketUserauthInfoRequest.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,71 +0,0 @@ - -package com.trilead.ssh2.packets; - -import java.io.IOException; - -/** - * PacketUserauthInfoRequest. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketUserauthInfoRequest.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketUserauthInfoRequest { - byte[] payload; - - String name; - String instruction; - String languageTag; - int numPrompts; - - String prompt[]; - boolean echo[]; - - public PacketUserauthInfoRequest(byte payload[], int off, int len) throws IOException { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - TypesReader tr = new TypesReader(payload, off, len); - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_USERAUTH_INFO_REQUEST) - throw new IOException("This is not a SSH_MSG_USERAUTH_INFO_REQUEST! (" + packet_type + ")"); - - name = tr.readString(); - instruction = tr.readString(); - languageTag = tr.readString(); - numPrompts = tr.readUINT32(); - prompt = new String[numPrompts]; - echo = new boolean[numPrompts]; - - for (int i = 0; i < numPrompts; i++) { - prompt[i] = tr.readString(); - echo[i] = tr.readBoolean(); - } - - if (tr.remain() != 0) - throw new IOException("Padding in SSH_MSG_USERAUTH_INFO_REQUEST packet!"); - } - - public boolean[] getEcho() { - return echo; - } - - public String getInstruction() { - return instruction; - } - - public String getLanguageTag() { - return languageTag; - } - - public String getName() { - return name; - } - - public int getNumPrompts() { - return numPrompts; - } - - public String[] getPrompt() { - return prompt; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketUserauthInfoResponse.java --- a/src/com/trilead/ssh2/packets/PacketUserauthInfoResponse.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ - -package com.trilead.ssh2.packets; - -/** - * PacketUserauthInfoResponse. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketUserauthInfoResponse.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketUserauthInfoResponse { - byte[] payload; - - String[] responses; - - public PacketUserauthInfoResponse(String[] responses) { - this.responses = responses; - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_USERAUTH_INFO_RESPONSE); - tw.writeUINT32(responses.length); - - for (int i = 0; i < responses.length; i++) - tw.writeString(responses[i]); - - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketUserauthRequestInteractive.java --- a/src/com/trilead/ssh2/packets/PacketUserauthRequestInteractive.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ - -package com.trilead.ssh2.packets; - -/** - * PacketUserauthRequestInteractive. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketUserauthRequestInteractive.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketUserauthRequestInteractive { - byte[] payload; - - String userName; - String serviceName; - String[] submethods; - - public PacketUserauthRequestInteractive(String serviceName, String user, String[] submethods) { - this.serviceName = serviceName; - this.userName = user; - this.submethods = submethods; - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); - tw.writeString(userName); - tw.writeString(serviceName); - tw.writeString("keyboard-interactive"); - tw.writeString(""); // draft-ietf-secsh-newmodes-04.txt says that - // the language tag should be empty. - tw.writeNameList(submethods); - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketUserauthRequestNone.java --- a/src/com/trilead/ssh2/packets/PacketUserauthRequestNone.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -package com.trilead.ssh2.packets; - -import java.io.IOException; - -/** - * PacketUserauthRequestPassword. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketUserauthRequestNone.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketUserauthRequestNone { - byte[] payload; - - String userName; - String serviceName; - - public PacketUserauthRequestNone(String serviceName, String user) { - this.serviceName = serviceName; - this.userName = user; - } - - public PacketUserauthRequestNone(byte payload[], int off, int len) throws IOException { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - TypesReader tr = new TypesReader(payload, off, len); - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_USERAUTH_REQUEST) - throw new IOException("This is not a SSH_MSG_USERAUTH_REQUEST! (" + packet_type + ")"); - - userName = tr.readString(); - serviceName = tr.readString(); - String method = tr.readString(); - - if (method.equals("none") == false) - throw new IOException("This is not a SSH_MSG_USERAUTH_REQUEST with type none!"); - - if (tr.remain() != 0) - throw new IOException("Padding in SSH_MSG_USERAUTH_REQUEST packet!"); - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); - tw.writeString(userName); - tw.writeString(serviceName); - tw.writeString("none"); - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketUserauthRequestPassword.java --- a/src/com/trilead/ssh2/packets/PacketUserauthRequestPassword.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ -package com.trilead.ssh2.packets; - -import java.io.IOException; - -/** - * PacketUserauthRequestPassword. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketUserauthRequestPassword.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketUserauthRequestPassword { - byte[] payload; - - String userName; - String serviceName; - String password; - - public PacketUserauthRequestPassword(String serviceName, String user, String pass) { - this.serviceName = serviceName; - this.userName = user; - this.password = pass; - } - - public PacketUserauthRequestPassword(byte payload[], int off, int len) throws IOException { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - TypesReader tr = new TypesReader(payload, off, len); - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_USERAUTH_REQUEST) - throw new IOException("This is not a SSH_MSG_USERAUTH_REQUEST! (" + packet_type + ")"); - - userName = tr.readString(); - serviceName = tr.readString(); - String method = tr.readString(); - - if (method.equals("password") == false) - throw new IOException("This is not a SSH_MSG_USERAUTH_REQUEST with type password!"); - - /* ... */ - - if (tr.remain() != 0) - throw new IOException("Padding in SSH_MSG_USERAUTH_REQUEST packet!"); - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); - tw.writeString(userName); - tw.writeString(serviceName); - tw.writeString("password"); - tw.writeBoolean(false); - tw.writeString(password); - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/PacketUserauthRequestPublicKey.java --- a/src/com/trilead/ssh2/packets/PacketUserauthRequestPublicKey.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +0,0 @@ -package com.trilead.ssh2.packets; - -import java.io.IOException; - -/** - * PacketUserauthRequestPublicKey. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: PacketUserauthRequestPublicKey.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class PacketUserauthRequestPublicKey { - byte[] payload; - - String userName; - String serviceName; - String password; - String pkAlgoName; - byte[] pk; - byte[] sig; - - public PacketUserauthRequestPublicKey(String serviceName, String user, - String pkAlgorithmName, byte[] pk, byte[] sig) { - this.serviceName = serviceName; - this.userName = user; - this.pkAlgoName = pkAlgorithmName; - this.pk = pk; - this.sig = sig; - } - - public PacketUserauthRequestPublicKey(byte payload[], int off, int len) throws IOException { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - TypesReader tr = new TypesReader(payload, off, len); - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_USERAUTH_REQUEST) - throw new IOException("This is not a SSH_MSG_USERAUTH_REQUEST! (" - + packet_type + ")"); - - throw new IOException("Not implemented!"); - } - - public byte[] getPayload() { - if (payload == null) { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); - tw.writeString(userName); - tw.writeString(serviceName); - tw.writeString("publickey"); - tw.writeBoolean(true); - tw.writeString(pkAlgoName); - tw.writeString(pk, 0, pk.length); - tw.writeString(sig, 0, sig.length); - payload = tw.getBytes(); - } - - return payload; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/Packets.java --- a/src/com/trilead/ssh2/packets/Packets.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,139 +0,0 @@ - -package com.trilead.ssh2.packets; - -/** - * Packets. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: Packets.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class Packets { - public static final int SSH_MSG_DISCONNECT = 1; - public static final int SSH_MSG_IGNORE = 2; - public static final int SSH_MSG_UNIMPLEMENTED = 3; - public static final int SSH_MSG_DEBUG = 4; - public static final int SSH_MSG_SERVICE_REQUEST = 5; - public static final int SSH_MSG_SERVICE_ACCEPT = 6; - - public static final int SSH_MSG_KEXINIT = 20; - public static final int SSH_MSG_NEWKEYS = 21; - - public static final int SSH_MSG_KEXDH_INIT = 30; - public static final int SSH_MSG_KEXDH_REPLY = 31; - - public static final int SSH_MSG_KEX_DH_GEX_REQUEST_OLD = 30; - public static final int SSH_MSG_KEX_DH_GEX_REQUEST = 34; - public static final int SSH_MSG_KEX_DH_GEX_GROUP = 31; - public static final int SSH_MSG_KEX_DH_GEX_INIT = 32; - public static final int SSH_MSG_KEX_DH_GEX_REPLY = 33; - - public static final int SSH_MSG_USERAUTH_REQUEST = 50; - public static final int SSH_MSG_USERAUTH_FAILURE = 51; - public static final int SSH_MSG_USERAUTH_SUCCESS = 52; - public static final int SSH_MSG_USERAUTH_BANNER = 53; - public static final int SSH_MSG_USERAUTH_INFO_REQUEST = 60; - public static final int SSH_MSG_USERAUTH_INFO_RESPONSE = 61; - - public static final int SSH_MSG_GLOBAL_REQUEST = 80; - public static final int SSH_MSG_REQUEST_SUCCESS = 81; - public static final int SSH_MSG_REQUEST_FAILURE = 82; - - public static final int SSH_MSG_CHANNEL_OPEN = 90; - public static final int SSH_MSG_CHANNEL_OPEN_CONFIRMATION = 91; - public static final int SSH_MSG_CHANNEL_OPEN_FAILURE = 92; - public static final int SSH_MSG_CHANNEL_WINDOW_ADJUST = 93; - public static final int SSH_MSG_CHANNEL_DATA = 94; - public static final int SSH_MSG_CHANNEL_EXTENDED_DATA = 95; - public static final int SSH_MSG_CHANNEL_EOF = 96; - public static final int SSH_MSG_CHANNEL_CLOSE = 97; - public static final int SSH_MSG_CHANNEL_REQUEST = 98; - public static final int SSH_MSG_CHANNEL_SUCCESS = 99; - public static final int SSH_MSG_CHANNEL_FAILURE = 100; - - public static final int SSH_EXTENDED_DATA_STDERR = 1; - - public static final int SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT = 1; - public static final int SSH_DISCONNECT_PROTOCOL_ERROR = 2; - public static final int SSH_DISCONNECT_KEY_EXCHANGE_FAILED = 3; - public static final int SSH_DISCONNECT_RESERVED = 4; - public static final int SSH_DISCONNECT_MAC_ERROR = 5; - public static final int SSH_DISCONNECT_COMPRESSION_ERROR = 6; - public static final int SSH_DISCONNECT_SERVICE_NOT_AVAILABLE = 7; - public static final int SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED = 8; - public static final int SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE = 9; - public static final int SSH_DISCONNECT_CONNECTION_LOST = 10; - public static final int SSH_DISCONNECT_BY_APPLICATION = 11; - public static final int SSH_DISCONNECT_TOO_MANY_CONNECTIONS = 12; - public static final int SSH_DISCONNECT_AUTH_CANCELLED_BY_USER = 13; - public static final int SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 14; - public static final int SSH_DISCONNECT_ILLEGAL_USER_NAME = 15; - - public static final int SSH_OPEN_ADMINISTRATIVELY_PROHIBITED = 1; - public static final int SSH_OPEN_CONNECT_FAILED = 2; - public static final int SSH_OPEN_UNKNOWN_CHANNEL_TYPE = 3; - public static final int SSH_OPEN_RESOURCE_SHORTAGE = 4; - - private static final String[] reverseNames = new String[101]; - - static { - reverseNames[1] = "SSH_MSG_DISCONNECT"; - reverseNames[2] = "SSH_MSG_IGNORE"; - reverseNames[3] = "SSH_MSG_UNIMPLEMENTED"; - reverseNames[4] = "SSH_MSG_DEBUG"; - reverseNames[5] = "SSH_MSG_SERVICE_REQUEST"; - reverseNames[6] = "SSH_MSG_SERVICE_ACCEPT"; - reverseNames[20] = "SSH_MSG_KEXINIT"; - reverseNames[21] = "SSH_MSG_NEWKEYS"; - reverseNames[30] = "SSH_MSG_KEXDH_INIT"; - reverseNames[31] = "SSH_MSG_KEXDH_REPLY/SSH_MSG_KEX_DH_GEX_GROUP"; - reverseNames[32] = "SSH_MSG_KEX_DH_GEX_INIT"; - reverseNames[33] = "SSH_MSG_KEX_DH_GEX_REPLY"; - reverseNames[34] = "SSH_MSG_KEX_DH_GEX_REQUEST"; - reverseNames[50] = "SSH_MSG_USERAUTH_REQUEST"; - reverseNames[51] = "SSH_MSG_USERAUTH_FAILURE"; - reverseNames[52] = "SSH_MSG_USERAUTH_SUCCESS"; - reverseNames[53] = "SSH_MSG_USERAUTH_BANNER"; - reverseNames[60] = "SSH_MSG_USERAUTH_INFO_REQUEST"; - reverseNames[61] = "SSH_MSG_USERAUTH_INFO_RESPONSE"; - reverseNames[80] = "SSH_MSG_GLOBAL_REQUEST"; - reverseNames[81] = "SSH_MSG_REQUEST_SUCCESS"; - reverseNames[82] = "SSH_MSG_REQUEST_FAILURE"; - reverseNames[90] = "SSH_MSG_CHANNEL_OPEN"; - reverseNames[91] = "SSH_MSG_CHANNEL_OPEN_CONFIRMATION"; - reverseNames[92] = "SSH_MSG_CHANNEL_OPEN_FAILURE"; - reverseNames[93] = "SSH_MSG_CHANNEL_WINDOW_ADJUST"; - reverseNames[94] = "SSH_MSG_CHANNEL_DATA"; - reverseNames[95] = "SSH_MSG_CHANNEL_EXTENDED_DATA"; - reverseNames[96] = "SSH_MSG_CHANNEL_EOF"; - reverseNames[97] = "SSH_MSG_CHANNEL_CLOSE"; - reverseNames[98] = "SSH_MSG_CHANNEL_REQUEST"; - reverseNames[99] = "SSH_MSG_CHANNEL_SUCCESS"; - reverseNames[100] = "SSH_MSG_CHANNEL_FAILURE"; - } - - public static final String getMessageName(int type) { - String res = null; - - if ((type >= 0) && (type < reverseNames.length)) { - res = reverseNames[type]; - } - - return (res == null) ? ("UNKNOWN MSG " + type) : res; - } - - // public static final void debug(String tag, byte[] msg) - // { - // System.err.println(tag + " Type: " + msg[0] + ", LEN: " + msg.length); - // - // for (int i = 0; i < msg.length; i++) - // { - // if (((msg[i] >= 'a') && (msg[i] <= 'z')) || ((msg[i] >= 'A') && (msg[i] <= 'Z')) - // || ((msg[i] >= '0') && (msg[i] <= '9')) || (msg[i] == ' ')) - // System.err.print((char) msg[i]); - // else - // System.err.print("."); - // } - // System.err.println(); - // System.err.flush(); - // } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/TypesReader.java --- a/src/com/trilead/ssh2/packets/TypesReader.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,153 +0,0 @@ - -package com.trilead.ssh2.packets; - -import java.io.IOException; -import java.math.BigInteger; - -import com.trilead.ssh2.util.Tokenizer; - - -/** - * TypesReader. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: TypesReader.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ - */ -public class TypesReader { - byte[] arr; - int pos = 0; - int max = 0; - - public TypesReader(byte[] arr) { - this.arr = arr; - pos = 0; - max = arr.length; - } - - public TypesReader(byte[] arr, int off) { - this.arr = arr; - this.pos = off; - this.max = arr.length; - - if ((pos < 0) || (pos > arr.length)) - throw new IllegalArgumentException("Illegal offset."); - } - - public TypesReader(byte[] arr, int off, int len) { - this.arr = arr; - this.pos = off; - this.max = off + len; - - if ((pos < 0) || (pos > arr.length)) - throw new IllegalArgumentException("Illegal offset."); - - if ((max < 0) || (max > arr.length)) - throw new IllegalArgumentException("Illegal length."); - } - - public int readByte() throws IOException { - if (pos >= max) - throw new IOException("Packet too short."); - - return (arr[pos++] & 0xff); - } - - public byte[] readBytes(int len) throws IOException { - if ((pos + len) > max) - throw new IOException("Packet too short."); - - byte[] res = new byte[len]; - System.arraycopy(arr, pos, res, 0, len); - pos += len; - return res; - } - - public void readBytes(byte[] dst, int off, int len) throws IOException { - if ((pos + len) > max) - throw new IOException("Packet too short."); - - System.arraycopy(arr, pos, dst, off, len); - pos += len; - } - - public boolean readBoolean() throws IOException { - if (pos >= max) - throw new IOException("Packet too short."); - - return (arr[pos++] != 0); - } - - public int readUINT32() throws IOException { - if ((pos + 4) > max) - throw new IOException("Packet too short."); - - return ((arr[pos++] & 0xff) << 24) | ((arr[pos++] & 0xff) << 16) | ((arr[pos++] & 0xff) << 8) - | (arr[pos++] & 0xff); - } - - public long readUINT64() throws IOException { - if ((pos + 8) > max) - throw new IOException("Packet too short."); - - long high = ((arr[pos++] & 0xff) << 24) | ((arr[pos++] & 0xff) << 16) | ((arr[pos++] & 0xff) << 8) - | (arr[pos++] & 0xff); /* sign extension may take place - will be shifted away =) */ - long low = ((arr[pos++] & 0xff) << 24) | ((arr[pos++] & 0xff) << 16) | ((arr[pos++] & 0xff) << 8) - | (arr[pos++] & 0xff); /* sign extension may take place - handle below */ - return (high << 32) | (low & 0xffffffffl); /* see Java language spec (15.22.1, 5.6.2) */ - } - - public BigInteger readMPINT() throws IOException { - BigInteger b; - byte raw[] = readByteString(); - - if (raw.length == 0) - b = BigInteger.ZERO; - else - b = new BigInteger(raw); - - return b; - } - - public byte[] readByteString() throws IOException { - int len = readUINT32(); - - if ((len + pos) > max) - throw new IOException("Malformed SSH byte string."); - - byte[] res = new byte[len]; - System.arraycopy(arr, pos, res, 0, len); - pos += len; - return res; - } - - public String readString(String charsetName) throws IOException { - int len = readUINT32(); - - if ((len + pos) > max) - throw new IOException("Malformed SSH string."); - - String res = (charsetName == null) ? new String(arr, pos, len) : new String(arr, pos, len, charsetName); - pos += len; - return res; - } - - public String readString() throws IOException { - int len = readUINT32(); - - if ((len + pos) > max) - throw new IOException("Malformed SSH string."); - - String res = new String(arr, pos, len, "ISO-8859-1"); - pos += len; - return res; - } - - public String[] readNameList() throws IOException { - return Tokenizer.parseTokens(readString(), ','); - } - - public int remain() { - return max - pos; - } - -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/packets/TypesWriter.java --- a/src/com/trilead/ssh2/packets/TypesWriter.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,149 +0,0 @@ - -package com.trilead.ssh2.packets; - -import java.io.UnsupportedEncodingException; -import java.math.BigInteger; - -/** - * TypesWriter. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: TypesWriter.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ - */ -public class TypesWriter { - byte arr[]; - int pos; - - public TypesWriter() { - arr = new byte[256]; - pos = 0; - } - - private void resize(int len) { - byte new_arr[] = new byte[len]; - System.arraycopy(arr, 0, new_arr, 0, arr.length); - arr = new_arr; - } - - public int length() { - return pos; - } - - public byte[] getBytes() { - byte[] dst = new byte[pos]; - System.arraycopy(arr, 0, dst, 0, pos); - return dst; - } - - public void getBytes(byte dst[]) { - System.arraycopy(arr, 0, dst, 0, pos); - } - - public void writeUINT32(int val, int off) { - if ((off + 4) > arr.length) - resize(off + 32); - - arr[off++] = (byte)(val >> 24); - arr[off++] = (byte)(val >> 16); - arr[off++] = (byte)(val >> 8); - arr[off++] = (byte) val; - } - - public void writeUINT32(int val) { - writeUINT32(val, pos); - pos += 4; - } - - public void writeUINT64(long val) { - if ((pos + 8) > arr.length) - resize(arr.length + 32); - - arr[pos++] = (byte)(val >> 56); - arr[pos++] = (byte)(val >> 48); - arr[pos++] = (byte)(val >> 40); - arr[pos++] = (byte)(val >> 32); - arr[pos++] = (byte)(val >> 24); - arr[pos++] = (byte)(val >> 16); - arr[pos++] = (byte)(val >> 8); - arr[pos++] = (byte) val; - } - - public void writeBoolean(boolean v) { - if ((pos + 1) > arr.length) - resize(arr.length + 32); - - arr[pos++] = v ? (byte) 1 : (byte) 0; - } - - public void writeByte(int v, int off) { - if ((off + 1) > arr.length) - resize(off + 32); - - arr[off] = (byte) v; - } - - public void writeByte(int v) { - writeByte(v, pos); - pos++; - } - - public void writeMPInt(BigInteger b) { - byte raw[] = b.toByteArray(); - - if ((raw.length == 1) && (raw[0] == 0)) - writeUINT32(0); /* String with zero bytes of data */ - else - writeString(raw, 0, raw.length); - } - - public void writeBytes(byte[] buff) { - writeBytes(buff, 0, buff.length); - } - - public void writeBytes(byte[] buff, int off, int len) { - if ((pos + len) > arr.length) - resize(arr.length + len + 32); - - System.arraycopy(buff, off, arr, pos, len); - pos += len; - } - - public void writeString(byte[] buff, int off, int len) { - writeUINT32(len); - writeBytes(buff, off, len); - } - - public void writeString(String v) { - byte[] b; - - try { - /* All Java JVMs must support ISO-8859-1 */ - b = v.getBytes("ISO-8859-1"); - } - catch (UnsupportedEncodingException ignore) { - b = v.getBytes(); - } - - writeUINT32(b.length); - writeBytes(b, 0, b.length); - } - - public void writeString(String v, String charsetName) throws UnsupportedEncodingException { - byte[] b = (charsetName == null) ? v.getBytes() : v.getBytes(charsetName); - writeUINT32(b.length); - writeBytes(b, 0, b.length); - } - - public void writeNameList(String v[]) { - StringBuffer sb = new StringBuffer(); - - for (int i = 0; i < v.length; i++) { - if (i > 0) - sb.append(','); - - sb.append(v[i]); - } - - writeString(sb.toString()); - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/sftp/AttrTextHints.java --- a/src/com/trilead/ssh2/sftp/AttrTextHints.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,37 +0,0 @@ - -package com.trilead.ssh2.sftp; - -/** - * - * Values for the 'text-hint' field in the SFTP ATTRS data type. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: AttrTextHints.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - * - */ -public class AttrTextHints { - /** - * The server knows the file is a text file, and should be opened - * using the SSH_FXF_ACCESS_TEXT_MODE flag. - */ - public static final int SSH_FILEXFER_ATTR_KNOWN_TEXT = 0x00; - - /** - * The server has applied a heuristic or other mechanism and - * believes that the file should be opened with the - * SSH_FXF_ACCESS_TEXT_MODE flag. - */ - public static final int SSH_FILEXFER_ATTR_GUESSED_TEXT = 0x01; - - /** - * The server knows the file has binary content. - */ - public static final int SSH_FILEXFER_ATTR_KNOWN_BINARY = 0x02; - - /** - * The server has applied a heuristic or other mechanism and - * believes has binary content, and should not be opened with the - * SSH_FXF_ACCESS_TEXT_MODE flag. - */ - public static final int SSH_FILEXFER_ATTR_GUESSED_BINARY = 0x03; -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/sftp/AttribBits.java --- a/src/com/trilead/ssh2/sftp/AttribBits.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,128 +0,0 @@ - -package com.trilead.ssh2.sftp; - -/** - * - * SFTP Attribute Bits for the "attrib-bits" and "attrib-bits-valid" fields - * of the SFTP ATTR data type. - *

- * Yes, these are the "attrib-bits", even though they have "_FLAGS_" in - * their name. Don't ask - I did not invent it. - *

- * "These fields, taken together, reflect various attributes of the file - * or directory, on the server. Bits not set in 'attrib-bits-valid' MUST be - * ignored in the 'attrib-bits' field. This allows both the server and the - * client to communicate only the bits it knows about without inadvertently - * twiddling bits they don't understand." - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: AttribBits.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - * - */ -public class AttribBits { - /** - * Advisory, read-only bit. This bit is not part of the access - * control information on the file, but is rather an advisory field - * indicating that the file should not be written. - */ - public static final int SSH_FILEXFER_ATTR_FLAGS_READONLY = 0x00000001; - - /** - * The file is part of the operating system. - */ - public static final int SSH_FILEXFER_ATTR_FLAGS_SYSTEM = 0x00000002; - - /** - * File SHOULD NOT be shown to user unless specifically requested. - * For example, most UNIX systems SHOULD set this bit if the filename - * begins with a 'period'. This bit may be read-only (see section 5.4 of - * the SFTP standard draft). Most UNIX systems will not allow this to be - * changed. - */ - public static final int SSH_FILEXFER_ATTR_FLAGS_HIDDEN = 0x00000004; - - /** - * This attribute applies only to directories. This attribute is - * always read-only, and cannot be modified. This attribute means - * that files and directory names in this directory should be compared - * without regard to case. - *

- * It is recommended that where possible, the server's filesystem be - * allowed to do comparisons. For example, if a client wished to prompt - * a user before overwriting a file, it should not compare the new name - * with the previously retrieved list of names in the directory. Rather, - * it should first try to create the new file by specifying - * SSH_FXF_CREATE_NEW flag. Then, if this fails and returns - * SSH_FX_FILE_ALREADY_EXISTS, it should prompt the user and then retry - * the create specifying SSH_FXF_CREATE_TRUNCATE. - *

- * Unless otherwise specified, filenames are assumed to be case sensitive. - */ - public static final int SSH_FILEXFER_ATTR_FLAGS_CASE_INSENSITIVE = 0x00000008; - - /** - * The file should be included in backup / archive operations. - */ - public static final int SSH_FILEXFER_ATTR_FLAGS_ARCHIVE = 0x00000010; - - /** - * The file is stored on disk using file-system level transparent - * encryption. This flag does not affect the file data on the wire - * (for either READ or WRITE requests.) - */ - public static final int SSH_FILEXFER_ATTR_FLAGS_ENCRYPTED = 0x00000020; - - /** - * The file is stored on disk using file-system level transparent - * compression. This flag does not affect the file data on the wire. - */ - public static final int SSH_FILEXFER_ATTR_FLAGS_COMPRESSED = 0x00000040; - - /** - * The file is a sparse file; this means that file blocks that have - * not been explicitly written are not stored on disk. For example, if - * a client writes a buffer at 10 M from the beginning of the file, - * the blocks between the previous EOF marker and the 10 M offset would - * not consume physical disk space. - *

- * Some servers may store all files as sparse files, in which case - * this bit will be unconditionally set. Other servers may not have - * a mechanism for determining if the file is sparse, and so the file - * MAY be stored sparse even if this flag is not set. - */ - public static final int SSH_FILEXFER_ATTR_FLAGS_SPARSE = 0x00000080; - - /** - * Opening the file without either the SSH_FXF_ACCESS_APPEND_DATA or - * the SSH_FXF_ACCESS_APPEND_DATA_ATOMIC flag (see section 8.1.1.3 - * of the SFTP standard draft) MUST result in an - * SSH_FX_INVALID_PARAMETER error. - */ - public static final int SSH_FILEXFER_ATTR_FLAGS_APPEND_ONLY = 0x00000100; - - /** - * The file cannot be deleted or renamed, no hard link can be created - * to this file, and no data can be written to the file. - *

- * This bit implies a stronger level of protection than - * SSH_FILEXFER_ATTR_FLAGS_READONLY, the file permission mask or ACLs. - * Typically even the superuser cannot write to immutable files, and - * only the superuser can set or remove the bit. - */ - public static final int SSH_FILEXFER_ATTR_FLAGS_IMMUTABLE = 0x00000200; - - /** - * When the file is modified, the changes are written synchronously - * to the disk. - */ - public static final int SSH_FILEXFER_ATTR_FLAGS_SYNC = 0x00000400; - - /** - * The server MAY include this bit in a directory listing or realpath - * response. It indicates there was a failure in the translation to UTF-8. - * If this flag is included, the server SHOULD also include the - * UNTRANSLATED_NAME attribute. - */ - public static final int SSH_FILEXFER_ATTR_FLAGS_TRANSLATION_ERR = 0x00000800; - -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/sftp/AttribFlags.java --- a/src/com/trilead/ssh2/sftp/AttribFlags.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,111 +0,0 @@ - -package com.trilead.ssh2.sftp; - -/** - * - * Attribute Flags. The 'valid-attribute-flags' field in - * the SFTP ATTRS data type specifies which of the fields are actually present. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: AttribFlags.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - * - */ -public class AttribFlags { - /** - * Indicates that the 'allocation-size' field is present. - */ - public static final int SSH_FILEXFER_ATTR_SIZE = 0x00000001; - - /** Protocol version 6: - * 0x00000002 was used in a previous version of this protocol. - * It is now a reserved value and MUST NOT appear in the mask. - * Some future version of this protocol may reuse this value. - */ - public static final int SSH_FILEXFER_ATTR_V3_UIDGID = 0x00000002; - - /** - * Indicates that the 'permissions' field is present. - */ - public static final int SSH_FILEXFER_ATTR_PERMISSIONS = 0x00000004; - - /** - * Indicates that the 'atime' and 'mtime' field are present - * (protocol v3). - */ - public static final int SSH_FILEXFER_ATTR_V3_ACMODTIME = 0x00000008; - - /** - * Indicates that the 'atime' field is present. - */ - public static final int SSH_FILEXFER_ATTR_ACCESSTIME = 0x00000008; - - /** - * Indicates that the 'createtime' field is present. - */ - public static final int SSH_FILEXFER_ATTR_CREATETIME = 0x00000010; - - /** - * Indicates that the 'mtime' field is present. - */ - public static final int SSH_FILEXFER_ATTR_MODIFYTIME = 0x00000020; - - /** - * Indicates that the 'acl' field is present. - */ - public static final int SSH_FILEXFER_ATTR_ACL = 0x00000040; - - /** - * Indicates that the 'owner' and 'group' fields are present. - */ - public static final int SSH_FILEXFER_ATTR_OWNERGROUP = 0x00000080; - - /** - * Indicates that additionally to the 'atime', 'createtime', - * 'mtime' and 'ctime' fields (if present), there is also - * 'atime-nseconds', 'createtime-nseconds', 'mtime-nseconds' - * and 'ctime-nseconds'. - */ - public static final int SSH_FILEXFER_ATTR_SUBSECOND_TIMES = 0x00000100; - - /** - * Indicates that the 'attrib-bits' and 'attrib-bits-valid' - * fields are present. - */ - public static final int SSH_FILEXFER_ATTR_BITS = 0x00000200; - - /** - * Indicates that the 'allocation-size' field is present. - */ - public static final int SSH_FILEXFER_ATTR_ALLOCATION_SIZE = 0x00000400; - - /** - * Indicates that the 'text-hint' field is present. - */ - public static final int SSH_FILEXFER_ATTR_TEXT_HINT = 0x00000800; - - /** - * Indicates that the 'mime-type' field is present. - */ - public static final int SSH_FILEXFER_ATTR_MIME_TYPE = 0x00001000; - - /** - * Indicates that the 'link-count' field is present. - */ - public static final int SSH_FILEXFER_ATTR_LINK_COUNT = 0x00002000; - - /** - * Indicates that the 'untranslated-name' field is present. - */ - public static final int SSH_FILEXFER_ATTR_UNTRANSLATED_NAME = 0x00004000; - - /** - * Indicates that the 'ctime' field is present. - */ - public static final int SSH_FILEXFER_ATTR_CTIME = 0x00008000; - - /** - * Indicates that the 'extended-count' field (and probablby some - * 'extensions') is present. - */ - public static final int SSH_FILEXFER_ATTR_EXTENDED = 0x80000000; -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/sftp/AttribPermissions.java --- a/src/com/trilead/ssh2/sftp/AttribPermissions.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ - -package com.trilead.ssh2.sftp; - -/** - * - * Permissions for the 'permissions' field in the SFTP ATTRS data type. - *

- * "The 'permissions' field contains a bit mask specifying file permissions. - * These permissions correspond to the st_mode field of the stat structure - * defined by POSIX [IEEE.1003-1.1996]." - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: AttribPermissions.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - * - */ -public class AttribPermissions { - /* Octal values! */ - - public static final int S_IRUSR = 0400; - public static final int S_IWUSR = 0200; - public static final int S_IXUSR = 0100; - public static final int S_IRGRP = 0040; - public static final int S_IWGRP = 0020; - public static final int S_IXGRP = 0010; - public static final int S_IROTH = 0004; - public static final int S_IWOTH = 0002; - public static final int S_IXOTH = 0001; - public static final int S_ISUID = 04000; - public static final int S_ISGID = 02000; - public static final int S_ISVTX = 01000; -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/sftp/AttribTypes.java --- a/src/com/trilead/ssh2/sftp/AttribTypes.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ - -package com.trilead.ssh2.sftp; - -/** - * - * Types for the 'type' field in the SFTP ATTRS data type. - *

- * "On a POSIX system, these values would be derived from the mode field - * of the stat structure. SPECIAL should be used for files that are of - * a known type which cannot be expressed in the protocol. UNKNOWN - * should be used if the type is not known." - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: AttribTypes.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - * - */ -public class AttribTypes { - public static final int SSH_FILEXFER_TYPE_REGULAR = 1; - public static final int SSH_FILEXFER_TYPE_DIRECTORY = 2; - public static final int SSH_FILEXFER_TYPE_SYMLINK = 3; - public static final int SSH_FILEXFER_TYPE_SPECIAL = 4; - public static final int SSH_FILEXFER_TYPE_UNKNOWN = 5; - public static final int SSH_FILEXFER_TYPE_SOCKET = 6; - public static final int SSH_FILEXFER_TYPE_CHAR_DEVICE = 7; - public static final int SSH_FILEXFER_TYPE_BLOCK_DEVICE = 8; - public static final int SSH_FILEXFER_TYPE_FIFO = 9; -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/sftp/ErrorCodes.java --- a/src/com/trilead/ssh2/sftp/ErrorCodes.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,125 +0,0 @@ - -package com.trilead.ssh2.sftp; - -/** - * - * SFTP Error Codes - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: ErrorCodes.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - * - */ -public class ErrorCodes { - public static final int SSH_FX_OK = 0; - public static final int SSH_FX_EOF = 1; - public static final int SSH_FX_NO_SUCH_FILE = 2; - public static final int SSH_FX_PERMISSION_DENIED = 3; - public static final int SSH_FX_FAILURE = 4; - public static final int SSH_FX_BAD_MESSAGE = 5; - public static final int SSH_FX_NO_CONNECTION = 6; - public static final int SSH_FX_CONNECTION_LOST = 7; - public static final int SSH_FX_OP_UNSUPPORTED = 8; - public static final int SSH_FX_INVALID_HANDLE = 9; - public static final int SSH_FX_NO_SUCH_PATH = 10; - public static final int SSH_FX_FILE_ALREADY_EXISTS = 11; - public static final int SSH_FX_WRITE_PROTECT = 12; - public static final int SSH_FX_NO_MEDIA = 13; - public static final int SSH_FX_NO_SPACE_ON_FILESYSTEM = 14; - public static final int SSH_FX_QUOTA_EXCEEDED = 15; - public static final int SSH_FX_UNKNOWN_PRINCIPAL = 16; - public static final int SSH_FX_LOCK_CONFLICT = 17; - public static final int SSH_FX_DIR_NOT_EMPTY = 18; - public static final int SSH_FX_NOT_A_DIRECTORY = 19; - public static final int SSH_FX_INVALID_FILENAME = 20; - public static final int SSH_FX_LINK_LOOP = 21; - public static final int SSH_FX_CANNOT_DELETE = 22; - public static final int SSH_FX_INVALID_PARAMETER = 23; - public static final int SSH_FX_FILE_IS_A_DIRECTORY = 24; - public static final int SSH_FX_BYTE_RANGE_LOCK_CONFLICT = 25; - public static final int SSH_FX_BYTE_RANGE_LOCK_REFUSED = 26; - public static final int SSH_FX_DELETE_PENDING = 27; - public static final int SSH_FX_FILE_CORRUPT = 28; - public static final int SSH_FX_OWNER_INVALID = 29; - public static final int SSH_FX_GROUP_INVALID = 30; - public static final int SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK = 31; - - private static final String[][] messages = { - - { "SSH_FX_OK", "Indicates successful completion of the operation." }, - { - "SSH_FX_EOF", - "An attempt to read past the end-of-file was made; or, there are no more directory entries to return." - }, - { "SSH_FX_NO_SUCH_FILE", "A reference was made to a file which does not exist." }, - { "SSH_FX_PERMISSION_DENIED", "The user does not have sufficient permissions to perform the operation." }, - { "SSH_FX_FAILURE", "An error occurred, but no specific error code exists to describe the failure." }, - { "SSH_FX_BAD_MESSAGE", "A badly formatted packet or other SFTP protocol incompatibility was detected." }, - { "SSH_FX_NO_CONNECTION", "There is no connection to the server." }, - { "SSH_FX_CONNECTION_LOST", "The connection to the server was lost." }, - { - "SSH_FX_OP_UNSUPPORTED", - "An attempted operation could not be completed by the server because the server does not support the operation." - }, - { "SSH_FX_INVALID_HANDLE", "The handle value was invalid." }, - { "SSH_FX_NO_SUCH_PATH", "The file path does not exist or is invalid." }, - { "SSH_FX_FILE_ALREADY_EXISTS", "The file already exists." }, - { "SSH_FX_WRITE_PROTECT", "The file is on read-only media, or the media is write protected." }, - { - "SSH_FX_NO_MEDIA", - "The requested operation cannot be completed because there is no media available in the drive." - }, - { - "SSH_FX_NO_SPACE_ON_FILESYSTEM", - "The requested operation cannot be completed because there is insufficient free space on the filesystem." - }, - { - "SSH_FX_QUOTA_EXCEEDED", - "The operation cannot be completed because it would exceed the user's storage quota." - }, - { - "SSH_FX_UNKNOWN_PRINCIPAL", - "A principal referenced by the request (either the 'owner', 'group', or 'who' field of an ACL), was unknown. The error specific data contains the problematic names." - }, - { "SSH_FX_LOCK_CONFLICT", "The file could not be opened because it is locked by another process." }, - { "SSH_FX_DIR_NOT_EMPTY", "The directory is not empty." }, - { "SSH_FX_NOT_A_DIRECTORY", "The specified file is not a directory." }, - { "SSH_FX_INVALID_FILENAME", "The filename is not valid." }, - { - "SSH_FX_LINK_LOOP", - "Too many symbolic links encountered or, an SSH_FXF_NOFOLLOW open encountered a symbolic link as the final component." - }, - { - "SSH_FX_CANNOT_DELETE", - "The file cannot be deleted. One possible reason is that the advisory READONLY attribute-bit is set." - }, - { - "SSH_FX_INVALID_PARAMETER", - "One of the parameters was out of range, or the parameters specified cannot be used together." - }, - { - "SSH_FX_FILE_IS_A_DIRECTORY", - "The specified file was a directory in a context where a directory cannot be used." - }, - { - "SSH_FX_BYTE_RANGE_LOCK_CONFLICT", - " A read or write operation failed because another process's mandatory byte-range lock overlaps with the request." - }, - { "SSH_FX_BYTE_RANGE_LOCK_REFUSED", "A request for a byte range lock was refused." }, - { "SSH_FX_DELETE_PENDING", "An operation was attempted on a file for which a delete operation is pending." }, - { "SSH_FX_FILE_CORRUPT", "The file is corrupt; an filesystem integrity check should be run." }, - { "SSH_FX_OWNER_INVALID", "The principal specified can not be assigned as an owner of a file." }, - { "SSH_FX_GROUP_INVALID", "The principal specified can not be assigned as the primary group of a file." }, - { - "SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK", - "The requested operation could not be completed because the specifed byte range lock has not been granted." - }, - - }; - - public static final String[] getDescription(int errorCode) { - if ((errorCode < 0) || (errorCode >= messages.length)) - return null; - - return messages[errorCode]; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/sftp/OpenFlags.java --- a/src/com/trilead/ssh2/sftp/OpenFlags.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,222 +0,0 @@ - -package com.trilead.ssh2.sftp; - -/** - * - * SFTP Open Flags. - * - * The following table is provided to assist in mapping POSIX semantics - * to equivalent SFTP file open parameters: - *

- * TODO: This comment should be moved to the open method. - *

- *

    - *
  • O_RDONLY - *
    • desired-access = READ_DATA | READ_ATTRIBUTES
    - *
  • - *
- *
    - *
  • O_WRONLY - *
    • desired-access = WRITE_DATA | WRITE_ATTRIBUTES
    - *
  • - *
- *
    - *
  • O_RDWR - *
    • desired-access = READ_DATA | READ_ATTRIBUTES | WRITE_DATA | WRITE_ATTRIBUTES
    - *
  • - *
- *
    - *
  • O_APPEND - *
      - *
    • desired-access = WRITE_DATA | WRITE_ATTRIBUTES | APPEND_DATA
    • - *
    • flags = SSH_FXF_ACCESS_APPEND_DATA and or SSH_FXF_ACCESS_APPEND_DATA_ATOMIC
    • - *
    - *
  • - *
- *
    - *
  • O_CREAT - *
      - *
    • flags = SSH_FXF_OPEN_OR_CREATE
    • - *
    - *
  • - *
- *
    - *
  • O_TRUNC - *
      - *
    • flags = SSH_FXF_TRUNCATE_EXISTING
    • - *
    - *
  • - *
- *
    - *
  • O_TRUNC|O_CREATE - *
      - *
    • flags = SSH_FXF_CREATE_TRUNCATE
    • - *
    - *
  • - *
- * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: OpenFlags.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - */ -public class OpenFlags { - /** - * Disposition is a 3 bit field that controls how the file is opened. - * The server MUST support these bits (possible enumaration values: - * SSH_FXF_CREATE_NEW, SSH_FXF_CREATE_TRUNCATE, SSH_FXF_OPEN_EXISTING, - * SSH_FXF_OPEN_OR_CREATE or SSH_FXF_TRUNCATE_EXISTING). - */ - public static final int SSH_FXF_ACCESS_DISPOSITION = 0x00000007; - - /** - * A new file is created; if the file already exists, the server - * MUST return status SSH_FX_FILE_ALREADY_EXISTS. - */ - public static final int SSH_FXF_CREATE_NEW = 0x00000000; - - /** - * A new file is created; if the file already exists, it is opened - * and truncated. - */ - public static final int SSH_FXF_CREATE_TRUNCATE = 0x00000001; - - /** - * An existing file is opened. If the file does not exist, the - * server MUST return SSH_FX_NO_SUCH_FILE. If a directory in the - * path does not exist, the server SHOULD return - * SSH_FX_NO_SUCH_PATH. It is also acceptable if the server - * returns SSH_FX_NO_SUCH_FILE in this case. - */ - public static final int SSH_FXF_OPEN_EXISTING = 0x00000002; - - /** - * If the file exists, it is opened. If the file does not exist, - * it is created. - */ - public static final int SSH_FXF_OPEN_OR_CREATE = 0x00000003; - - /** - * An existing file is opened and truncated. If the file does not - * exist, the server MUST return the same error codes as defined - * for SSH_FXF_OPEN_EXISTING. - */ - public static final int SSH_FXF_TRUNCATE_EXISTING = 0x00000004; - - /** - * Data is always written at the end of the file. The offset field - * of the SSH_FXP_WRITE requests are ignored. - *

- * Data is not required to be appended atomically. This means that - * if multiple writers attempt to append data simultaneously, data - * from the first may be lost. However, data MAY be appended - * atomically. - */ - public static final int SSH_FXF_ACCESS_APPEND_DATA = 0x00000008; - - /** - * Data is always written at the end of the file. The offset field - * of the SSH_FXP_WRITE requests are ignored. - *

- * Data MUST be written atomically so that there is no chance that - * multiple appenders can collide and result in data being lost. - *

- * If both append flags are specified, the server SHOULD use atomic - * append if it is available, but SHOULD use non-atomic appends - * otherwise. The server SHOULD NOT fail the request in this case. - */ - public static final int SSH_FXF_ACCESS_APPEND_DATA_ATOMIC = 0x00000010; - - /** - * Indicates that the server should treat the file as text and - * convert it to the canonical newline convention in use. - * (See Determining Server Newline Convention in section 5.3 in the - * SFTP standard draft). - *

- * When a file is opened with this flag, the offset field in the read - * and write functions is ignored. - *

- * Servers MUST process multiple, parallel reads and writes correctly - * in this mode. Naturally, it is permissible for them to do this by - * serializing the requests. - *

- * Clients SHOULD use the SSH_FXF_ACCESS_APPEND_DATA flag to append - * data to a text file rather then using write with a calculated offset. - */ - public static final int SSH_FXF_ACCESS_TEXT_MODE = 0x00000020; - - /** - * The server MUST guarantee that no other handle has been opened - * with ACE4_READ_DATA access, and that no other handle will be - * opened with ACE4_READ_DATA access until the client closes the - * handle. (This MUST apply both to other clients and to other - * processes on the server.) - *

- * If there is a conflicting lock the server MUST return - * SSH_FX_LOCK_CONFLICT. If the server cannot make the locking - * guarantee, it MUST return SSH_FX_OP_UNSUPPORTED. - *

- * Other handles MAY be opened for ACE4_WRITE_DATA or any other - * combination of accesses, as long as ACE4_READ_DATA is not included - * in the mask. - */ - public static final int SSH_FXF_ACCESS_BLOCK_READ = 0x00000040; - - /** - * The server MUST guarantee that no other handle has been opened - * with ACE4_WRITE_DATA or ACE4_APPEND_DATA access, and that no other - * handle will be opened with ACE4_WRITE_DATA or ACE4_APPEND_DATA - * access until the client closes the handle. (This MUST apply both - * to other clients and to other processes on the server.) - *

- * If there is a conflicting lock the server MUST return - * SSH_FX_LOCK_CONFLICT. If the server cannot make the locking - * guarantee, it MUST return SSH_FX_OP_UNSUPPORTED. - *

- * Other handles MAY be opened for ACE4_READ_DATA or any other - * combination of accesses, as long as neither ACE4_WRITE_DATA nor - * ACE4_APPEND_DATA are included in the mask. - */ - public static final int SSH_FXF_ACCESS_BLOCK_WRITE = 0x00000080; - - /** - * The server MUST guarantee that no other handle has been opened - * with ACE4_DELETE access, opened with the - * SSH_FXF_ACCESS_DELETE_ON_CLOSE flag set, and that no other handle - * will be opened with ACE4_DELETE access or with the - * SSH_FXF_ACCESS_DELETE_ON_CLOSE flag set, and that the file itself - * is not deleted in any other way until the client closes the handle. - *

- * If there is a conflicting lock the server MUST return - * SSH_FX_LOCK_CONFLICT. If the server cannot make the locking - * guarantee, it MUST return SSH_FX_OP_UNSUPPORTED. - */ - public static final int SSH_FXF_ACCESS_BLOCK_DELETE = 0x00000100; - - /** - * If this bit is set, the above BLOCK modes are advisory. In advisory - * mode, only other accesses that specify a BLOCK mode need be - * considered when determining whether the BLOCK can be granted, - * and the server need not prevent I/O operations that violate the - * block mode. - *

- * The server MAY perform mandatory locking even if the BLOCK_ADVISORY - * bit is set. - */ - public static final int SSH_FXF_ACCESS_BLOCK_ADVISORY = 0x00000200; - - /** - * If the final component of the path is a symlink, then the open - * MUST fail, and the error SSH_FX_LINK_LOOP MUST be returned. - */ - public static final int SSH_FXF_ACCESS_NOFOLLOW = 0x00000400; - - /** - * The file should be deleted when the last handle to it is closed. - * (The last handle may not be an sftp-handle.) This MAY be emulated - * by a server if the OS doesn't support it by deleting the file when - * this handle is closed. - *

- * It is implementation specific whether the directory entry is - * removed immediately or when the handle is closed. - */ - public static final int SSH_FXF_ACCESS_DELETE_ON_CLOSE = 0x00000800; -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/sftp/Packet.java --- a/src/com/trilead/ssh2/sftp/Packet.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ - -package com.trilead.ssh2.sftp; - -/** - * - * SFTP Paket Types - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: Packet.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ - * - */ -public class Packet { - public static final int SSH_FXP_INIT = 1; - public static final int SSH_FXP_VERSION = 2; - public static final int SSH_FXP_OPEN = 3; - public static final int SSH_FXP_CLOSE = 4; - public static final int SSH_FXP_READ = 5; - public static final int SSH_FXP_WRITE = 6; - public static final int SSH_FXP_LSTAT = 7; - public static final int SSH_FXP_FSTAT = 8; - public static final int SSH_FXP_SETSTAT = 9; - public static final int SSH_FXP_FSETSTAT = 10; - public static final int SSH_FXP_OPENDIR = 11; - public static final int SSH_FXP_READDIR = 12; - public static final int SSH_FXP_REMOVE = 13; - public static final int SSH_FXP_MKDIR = 14; - public static final int SSH_FXP_RMDIR = 15; - public static final int SSH_FXP_REALPATH = 16; - public static final int SSH_FXP_STAT = 17; - public static final int SSH_FXP_RENAME = 18; - public static final int SSH_FXP_READLINK = 19; - public static final int SSH_FXP_SYMLINK = 20; - - public static final int SSH_FXP_STATUS = 101; - public static final int SSH_FXP_HANDLE = 102; - public static final int SSH_FXP_DATA = 103; - public static final int SSH_FXP_NAME = 104; - public static final int SSH_FXP_ATTRS = 105; - - public static final int SSH_FXP_EXTENDED = 200; - public static final int SSH_FXP_EXTENDED_REPLY = 201; -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/signature/DSASHA1Verify.java --- a/src/com/trilead/ssh2/signature/DSASHA1Verify.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,230 +0,0 @@ - -package com.trilead.ssh2.signature; - -import java.io.IOException; -import java.math.BigInteger; -import java.security.InvalidKeyException; -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.security.Signature; -import java.security.SignatureException; -import java.security.interfaces.DSAParams; -import java.security.interfaces.DSAPrivateKey; -import java.security.interfaces.DSAPublicKey; -import java.security.spec.DSAPublicKeySpec; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.KeySpec; - -import com.trilead.ssh2.log.Logger; -import com.trilead.ssh2.packets.TypesReader; -import com.trilead.ssh2.packets.TypesWriter; - - -/** - * DSASHA1Verify. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: DSASHA1Verify.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ - */ -public class DSASHA1Verify { - private static final Logger log = Logger.getLogger(DSASHA1Verify.class); - - public static DSAPublicKey decodeSSHDSAPublicKey(byte[] key) throws IOException { - TypesReader tr = new TypesReader(key); - String key_format = tr.readString(); - - if (key_format.equals("ssh-dss") == false) - throw new IllegalArgumentException("This is not a ssh-dss public key!"); - - BigInteger p = tr.readMPINT(); - BigInteger q = tr.readMPINT(); - BigInteger g = tr.readMPINT(); - BigInteger y = tr.readMPINT(); - - if (tr.remain() != 0) - throw new IOException("Padding in DSA public key!"); - - try { - KeyFactory kf = KeyFactory.getInstance("DSA"); - KeySpec ks = new DSAPublicKeySpec(y, p, q, g); - return (DSAPublicKey) kf.generatePublic(ks); - } - catch (NoSuchAlgorithmException e) { - IOException ex = new IOException(); - ex.initCause(e); - throw ex; - } - catch (InvalidKeySpecException e) { - IOException ex = new IOException(); - ex.initCause(e); - throw ex; - } - } - - public static byte[] encodeSSHDSAPublicKey(DSAPublicKey pk) throws IOException { - TypesWriter tw = new TypesWriter(); - tw.writeString("ssh-dss"); - DSAParams params = pk.getParams(); - tw.writeMPInt(params.getP()); - tw.writeMPInt(params.getQ()); - tw.writeMPInt(params.getG()); - tw.writeMPInt(pk.getY()); - return tw.getBytes(); - } - - /** - * Convert from Java's signature ASN.1 encoding to the SSH spec. - *

- * Java ASN.1 encoding: - *

-     * SEQUENCE ::= {
-     *    r INTEGER,
-     *    s INTEGER
-     * }
-     * 
- */ - public static byte[] encodeSSHDSASignature(byte[] ds) { - TypesWriter tw = new TypesWriter(); - tw.writeString("ssh-dss"); - int len, index; - index = 3; - len = ds[index++] & 0xff; - byte[] r = new byte[len]; - System.arraycopy(ds, index, r, 0, r.length); - index = index + len + 1; - len = ds[index++] & 0xff; - byte[] s = new byte[len]; - System.arraycopy(ds, index, s, 0, s.length); - byte[] a40 = new byte[40]; - /* Patch (unsigned) r and s into the target array. */ - int r_copylen = (r.length < 20) ? r.length : 20; - int s_copylen = (s.length < 20) ? s.length : 20; - System.arraycopy(r, r.length - r_copylen, a40, 20 - r_copylen, r_copylen); - System.arraycopy(s, s.length - s_copylen, a40, 40 - s_copylen, s_copylen); - tw.writeString(a40, 0, 40); - return tw.getBytes(); - } - - public static byte[] decodeSSHDSASignature(byte[] sig) throws IOException { - byte[] rsArray = null; - - if (sig.length == 40) { - /* OK, another broken SSH server. */ - rsArray = sig; - } - else { - /* Hopefully a server obeying the standard... */ - TypesReader tr = new TypesReader(sig); - String sig_format = tr.readString(); - - if (sig_format.equals("ssh-dss") == false) - throw new IOException("Peer sent wrong signature format"); - - rsArray = tr.readByteString(); - - if (rsArray.length != 40) - throw new IOException("Peer sent corrupt signature"); - - if (tr.remain() != 0) - throw new IOException("Padding in DSA signature!"); - } - - int i = 0; - int j = 0; - byte[] tmp; - - if (rsArray[0] == 0 && rsArray[1] == 0 && rsArray[2] == 0) { - j = ((rsArray[i++] << 24) & 0xff000000) | ((rsArray[i++] << 16) & 0x00ff0000) - | ((rsArray[i++] << 8) & 0x0000ff00) | ((rsArray[i++]) & 0x000000ff); - i += j; - j = ((rsArray[i++] << 24) & 0xff000000) | ((rsArray[i++] << 16) & 0x00ff0000) - | ((rsArray[i++] << 8) & 0x0000ff00) | ((rsArray[i++]) & 0x000000ff); - tmp = new byte[j]; - System.arraycopy(rsArray, i, tmp, 0, j); - rsArray = tmp; - } - - /* ASN.1 */ - int frst = ((rsArray[0] & 0x80) != 0 ? 1 : 0); - int scnd = ((rsArray[20] & 0x80) != 0 ? 1 : 0); - /* Calculate output length */ - int length = rsArray.length + 6 + frst + scnd; - tmp = new byte[length]; - /* DER-encoding to match Java */ - tmp[0] = (byte) 0x30; - - if (rsArray.length != 40) - throw new IOException("Peer sent corrupt signature"); - - /* Calculate length */ - tmp[1] = (byte) 0x2c; - tmp[1] += frst; - tmp[1] += scnd; - /* First item */ - tmp[2] = (byte) 0x02; - /* First item length */ - tmp[3] = (byte) 0x14; - tmp[3] += frst; - /* Copy in the data for first item */ - System.arraycopy(rsArray, 0, tmp, 4 + frst, 20); - /* Second item */ - tmp[4 + tmp[3]] = (byte) 0x02; - /* Second item length */ - tmp[5 + tmp[3]] = (byte) 0x14; - tmp[5 + tmp[3]] += scnd; - /* Copy in the data for the second item */ - System.arraycopy(rsArray, 20, tmp, 6 + tmp[3] + scnd, 20); - /* Swap buffers */ - rsArray = tmp; - return rsArray; - } - - public static boolean verifySignature(byte[] message, byte[] ds, DSAPublicKey dpk) throws IOException { - try { - Signature s = Signature.getInstance("SHA1withDSA"); - s.initVerify(dpk); - s.update(message); - return s.verify(ds); - } - catch (NoSuchAlgorithmException e) { - IOException ex = new IOException("No such algorithm"); - ex.initCause(e); - throw ex; - } - catch (InvalidKeyException e) { - IOException ex = new IOException("No such algorithm"); - ex.initCause(e); - throw ex; - } - catch (SignatureException e) { - IOException ex = new IOException(); - ex.initCause(e); - throw ex; - } - } - - public static byte[] generateSignature(byte[] message, DSAPrivateKey pk, SecureRandom rnd) throws IOException { - try { - Signature s = Signature.getInstance("SHA1withDSA"); - s.initSign(pk); - s.update(message); - return s.sign(); - } - catch (NoSuchAlgorithmException e) { - IOException ex = new IOException(); - ex.initCause(e); - throw ex; - } - catch (InvalidKeyException e) { - IOException ex = new IOException(); - ex.initCause(e); - throw ex; - } - catch (SignatureException e) { - IOException ex = new IOException(); - ex.initCause(e); - throw ex; - } - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/signature/ECDSASHA2Verify.java --- a/src/com/trilead/ssh2/signature/ECDSASHA2Verify.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,488 +0,0 @@ -/** - * - */ -package com.trilead.ssh2.signature; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -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.ECPrivateKey; -import java.security.interfaces.ECPublicKey; -import java.security.spec.ECFieldFp; -import java.security.spec.ECParameterSpec; -import java.security.spec.ECPoint; -import java.security.spec.ECPublicKeySpec; -import java.security.spec.EllipticCurve; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.KeySpec; -import java.util.Map; -import java.util.TreeMap; - -import com.trilead.ssh2.log.Logger; -import com.trilead.ssh2.packets.TypesReader; -import com.trilead.ssh2.packets.TypesWriter; - -/** - * @author Kenny Root - * - */ -public class ECDSASHA2Verify { - private static final Logger log = Logger.getLogger(ECDSASHA2Verify.class); - - public static final String ECDSA_SHA2_PREFIX = "ecdsa-sha2-"; - - private static final String NISTP256 = "nistp256"; - private static final String NISTP256_OID = "1.2.840.10045.3.1.7"; - private static final String NISTP384 = "nistp384"; - private static final String NISTP384_OID = "1.3.132.0.34"; - private static final String NISTP521 = "nistp521"; - private static final String NISTP521_OID = "1.3.132.0.35"; - - private static final Map CURVES = new TreeMap(); - static { - CURVES.put(NISTP256, EllipticCurves.nistp256); - CURVES.put(NISTP384, EllipticCurves.nistp384); - CURVES.put(NISTP521, EllipticCurves.nistp521); - } - - private static final Map CURVE_SIZES = new TreeMap(); - static { - CURVE_SIZES.put(256, NISTP256); - CURVE_SIZES.put(384, NISTP384); - CURVE_SIZES.put(521, NISTP521); - } - - private static final Map CURVE_OIDS = new TreeMap(); - static { - CURVE_OIDS.put(NISTP256_OID, NISTP256); - CURVE_OIDS.put(NISTP384_OID, NISTP256); - CURVE_OIDS.put(NISTP521_OID, NISTP256); - } - - public static int[] getCurveSizes() { - int[] keys = new int[CURVE_SIZES.size()]; - int i = 0; - - for (Integer n : CURVE_SIZES.keySet().toArray(new Integer[keys.length])) { - keys[i++] = n; - } - - return keys; - } - - public static ECParameterSpec getCurveForSize(int size) { - final String name = CURVE_SIZES.get(size); - - if (name == null) { - return null; - } - - return CURVES.get(name); - } - - public static ECPublicKey decodeSSHECDSAPublicKey(byte[] key) throws IOException { - TypesReader tr = new TypesReader(key); - String key_format = tr.readString(); - - if (key_format.startsWith(ECDSA_SHA2_PREFIX) == false) - throw new IllegalArgumentException("This is not an ECDSA public key"); - - String curveName = tr.readString(); - byte[] groupBytes = tr.readByteString(); - - if (tr.remain() != 0) - throw new IOException("Padding in ECDSA public key!"); - - if (key_format.equals(ECDSA_SHA2_PREFIX + curveName) == false) { - throw new IOException("Key format is inconsistent with curve name: " + key_format - + " != " + curveName); - } - - ECParameterSpec params = CURVES.get(curveName); - - if (params == null) { - throw new IOException("Curve is not supported: " + curveName); - } - - ECPoint group = ECDSASHA2Verify.decodeECPoint(groupBytes, params.getCurve()); - - if (group == null) { - throw new IOException("Invalid ECDSA group"); - } - - KeySpec keySpec = new ECPublicKeySpec(group, params); - - try { - KeyFactory kf = KeyFactory.getInstance("EC"); - return (ECPublicKey) kf.generatePublic(keySpec); - } - catch (NoSuchAlgorithmException nsae) { - IOException ioe = new IOException("No EC KeyFactory available"); - ioe.initCause(nsae); - throw ioe; - } - catch (InvalidKeySpecException ikse) { - IOException ioe = new IOException("No EC KeyFactory available"); - ioe.initCause(ikse); - throw ioe; - } - } - - public static byte[] encodeSSHECDSAPublicKey(ECPublicKey key) throws IOException { - TypesWriter tw = new TypesWriter(); - String curveName = getCurveName(key.getParams()); - String keyFormat = ECDSA_SHA2_PREFIX + curveName; - tw.writeString(keyFormat); - tw.writeString(curveName); - byte[] encoded = encodeECPoint(key.getW(), key.getParams().getCurve()); - tw.writeString(encoded, 0, encoded.length); - return tw.getBytes(); - } - - public static String getCurveName(ECParameterSpec params) throws IOException { - int fieldSize = getCurveSize(params); - final String curveName = getCurveName(fieldSize); - - if (curveName == null) { - throw new IOException("invalid curve size " + fieldSize); - } - - return curveName; - } - - public static String getCurveName(int fieldSize) { - String curveName = CURVE_SIZES.get(fieldSize); - - if (curveName == null) { - return null; - } - - return curveName; - } - - public static int getCurveSize(ECParameterSpec params) { - return params.getCurve().getField().getFieldSize(); - } - - public static ECParameterSpec getCurveForOID(String oid) { - String name = CURVE_OIDS.get(oid); - - if (name == null) - return null; - - return CURVES.get(name); - } - - public static byte[] decodeSSHECDSASignature(byte[] sig) throws IOException { - byte[] rsArray = null; - TypesReader tr = new TypesReader(sig); - String sig_format = tr.readString(); - - if (sig_format.startsWith(ECDSA_SHA2_PREFIX) == false) - throw new IOException("Peer sent wrong signature format"); - - String curveName = sig_format.substring(ECDSA_SHA2_PREFIX.length()); - - if (CURVES.containsKey(curveName) == false) { - throw new IOException("Unsupported curve: " + curveName); - } - - rsArray = tr.readByteString(); - - if (tr.remain() != 0) - throw new IOException("Padding in ECDSA signature!"); - - byte[] rArray; - byte[] sArray; - { - TypesReader rsReader = new TypesReader(rsArray); - rArray = rsReader.readMPINT().toByteArray(); - sArray = rsReader.readMPINT().toByteArray(); - } - int first = rArray.length; - int second = sArray.length; - - /* We can't have the high bit set, so add an extra zero at the beginning if so. */ - if ((rArray[0] & 0x80) != 0) { - first++; - } - - if ((sArray[0] & 0x80) != 0) { - second++; - } - - /* Calculate total output length */ - ByteArrayOutputStream os = new ByteArrayOutputStream(6 + first + second); - /* ASN.1 SEQUENCE tag */ - os.write(0x30); - /* Size of SEQUENCE */ - writeLength(4 + first + second, os); - /* ASN.1 INTEGER tag */ - os.write(0x02); - /* "r" INTEGER length */ - writeLength(first, os); - - /* Copy in the "r" INTEGER */ - if (first != rArray.length) { - os.write(0x00); - } - - os.write(rArray); - /* ASN.1 INTEGER tag */ - os.write(0x02); - /* "s" INTEGER length */ - writeLength(second, os); - - /* Copy in the "s" INTEGER */ - if (second != sArray.length) { - os.write(0x00); - } - - os.write(sArray); - return os.toByteArray(); - } - - private static final void writeLength(int length, OutputStream os) throws IOException { - if (length <= 0x7F) { - os.write(length); - return; - } - - int numOctets = 0; - int lenCopy = length; - - while (lenCopy != 0) { - lenCopy >>>= 8; - numOctets++; - } - - os.write(0x80 | numOctets); - - for (int i = (numOctets - 1) * 8; i >= 0; i -= 8) { - os.write((byte)(length >> i)); - } - } - - public static byte[] encodeSSHECDSASignature(byte[] sig, ECParameterSpec params) throws IOException { - TypesWriter tw = new TypesWriter(); - String curveName = getCurveName(params); - tw.writeString(ECDSA_SHA2_PREFIX + curveName); - - if ((sig[0] != 0x30) || (sig[1] != sig.length - 2) || (sig[2] != 0x02)) { - throw new IOException("Invalid signature format"); - } - - int rLength = sig[3]; - - if ((rLength + 6 > sig.length) || (sig[4 + rLength] != 0x02)) { - throw new IOException("Invalid signature format"); - } - - int sLength = sig[5 + rLength]; - - if (6 + rLength + sLength > sig.length) { - throw new IOException("Invalid signature format"); - } - - byte[] rArray = new byte[rLength]; - byte[] sArray = new byte[sLength]; - System.arraycopy(sig, 4, rArray, 0, rLength); - System.arraycopy(sig, 6 + rLength, sArray, 0, sLength); - BigInteger r = new BigInteger(rArray); - BigInteger s = new BigInteger(sArray); - // Write the to its own types writer. - TypesWriter rsWriter = new TypesWriter(); - rsWriter.writeMPInt(r); - rsWriter.writeMPInt(s); - byte[] encoded = rsWriter.getBytes(); - tw.writeString(encoded, 0, encoded.length); - return tw.getBytes(); - } - - public static byte[] generateSignature(byte[] message, ECPrivateKey pk) throws IOException { - final String algo = getSignatureAlgorithmForParams(pk.getParams()); - - try { - Signature s = Signature.getInstance(algo); - s.initSign(pk); - s.update(message); - return s.sign(); - } - catch (NoSuchAlgorithmException e) { - IOException ex = new IOException(); - ex.initCause(e); - throw ex; - } - catch (InvalidKeyException e) { - IOException ex = new IOException(); - ex.initCause(e); - throw ex; - } - catch (SignatureException e) { - IOException ex = new IOException(); - ex.initCause(e); - throw ex; - } - } - - public static boolean verifySignature(byte[] message, byte[] ds, ECPublicKey dpk) throws IOException { - final String algo = getSignatureAlgorithmForParams(dpk.getParams()); - - try { - Signature s = Signature.getInstance(algo); - s.initVerify(dpk); - s.update(message); - return s.verify(ds); - } - catch (NoSuchAlgorithmException e) { - IOException ex = new IOException("No such algorithm"); - ex.initCause(e); - throw ex; - } - catch (InvalidKeyException e) { - IOException ex = new IOException("No such algorithm"); - ex.initCause(e); - throw ex; - } - catch (SignatureException e) { - IOException ex = new IOException(); - ex.initCause(e); - throw ex; - } - } - - private static String getSignatureAlgorithmForParams(ECParameterSpec params) { - int size = getCurveSize(params); - - if (size <= 256) { - return "SHA256withECDSA"; - } - else if (size <= 384) { - return "SHA384withECDSA"; - } - else { - return "SHA512withECDSA"; - } - } - - public static String getDigestAlgorithmForParams(ECParameterSpec params) { - int size = getCurveSize(params); - - if (size <= 256) { - return "SHA256"; - } - else if (size <= 384) { - return "SHA384"; - } - else { - return "SHA512"; - } - } - - /** - * Decode an OctetString to EllipticCurvePoint according to SECG 2.3.4 - */ - public static ECPoint decodeECPoint(byte[] M, EllipticCurve curve) { - if (M.length == 0) { - return null; - } - - // M has len 2 ceil(log_2(q)/8) + 1 ? - int elementSize = (curve.getField().getFieldSize() + 7) / 8; - - if (M.length != 2 * elementSize + 1) { - return null; - } - - // step 3.2 - if (M[0] != 0x04) { - return null; - } - - // Step 3.3 - byte[] xp = new byte[elementSize]; - System.arraycopy(M, 1, xp, 0, elementSize); - // Step 3.4 - byte[] yp = new byte[elementSize]; - System.arraycopy(M, 1 + elementSize, yp, 0, elementSize); - ECPoint P = new ECPoint(new BigInteger(1, xp), new BigInteger(1, yp)); - // TODO check point 3.5 - // Step 3.6 - return P; - } - - /** - * Encode EllipticCurvePoint to an OctetString - */ - public static byte[] encodeECPoint(ECPoint group, EllipticCurve curve) { - // M has len 2 ceil(log_2(q)/8) + 1 ? - int elementSize = (curve.getField().getFieldSize() + 7) / 8; - byte[] M = new byte[2 * elementSize + 1]; - // Uncompressed format - M[0] = 0x04; - { - byte[] affineX = removeLeadingZeroes(group.getAffineX().toByteArray()); - System.arraycopy(affineX, 0, M, 1 + elementSize - affineX.length, affineX.length); - } - { - byte[] affineY = removeLeadingZeroes(group.getAffineY().toByteArray()); - System.arraycopy(affineY, 0, M, 1 + elementSize + elementSize - affineY.length, - affineY.length); - } - return M; - } - - private static byte[] removeLeadingZeroes(byte[] input) { - if (input[0] != 0x00) { - return input; - } - - int pos = 1; - - while (pos < input.length - 1 && input[pos] == 0x00) { - pos++; - } - - byte[] output = new byte[input.length - pos]; - System.arraycopy(input, pos, output, 0, output.length); - return output; - } - - public static class EllipticCurves { - public static ECParameterSpec nistp256 = new ECParameterSpec( - new EllipticCurve( - new ECFieldFp(new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", 16)), - new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", 16), - new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16)), - new ECPoint(new BigInteger("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", 16), - new BigInteger("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", 16)), - new BigInteger("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", 16), - 1); - - public static ECParameterSpec nistp384 = new ECParameterSpec( - new EllipticCurve( - new ECFieldFp(new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF", 16)), - new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC", 16), - new BigInteger("B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF", 16)), - new ECPoint(new BigInteger("AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7", 16), - new BigInteger("3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F", 16)), - new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973", 16), - 1); - - public static ECParameterSpec nistp521 = new ECParameterSpec( - new EllipticCurve( - new ECFieldFp(new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16)), - new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC", 16), - new BigInteger("0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00", 16)), - new ECPoint(new BigInteger("00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66", 16), - new BigInteger("011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650", 16)), - new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409", 16), - 1); - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/signature/RSASHA1Verify.java --- a/src/com/trilead/ssh2/signature/RSASHA1Verify.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,174 +0,0 @@ - -package com.trilead.ssh2.signature; - -import java.io.IOException; -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 com.trilead.ssh2.log.Logger; -import com.trilead.ssh2.packets.TypesReader; -import com.trilead.ssh2.packets.TypesWriter; - - -/** - * RSASHA1Verify. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: RSASHA1Verify.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $ - */ -public class RSASHA1Verify { - private static final Logger log = Logger.getLogger(RSASHA1Verify.class); - - public static RSAPublicKey decodeSSHRSAPublicKey(byte[] key) throws IOException { - TypesReader tr = new TypesReader(key); - String key_format = tr.readString(); - - if (key_format.equals("ssh-rsa") == false) - throw new IllegalArgumentException("This is not a ssh-rsa public key"); - - BigInteger e = tr.readMPINT(); - BigInteger n = tr.readMPINT(); - - if (tr.remain() != 0) - throw new IOException("Padding in RSA public key!"); - - KeySpec keySpec = new RSAPublicKeySpec(n, e); - - try { - KeyFactory kf = KeyFactory.getInstance("RSA"); - return (RSAPublicKey) kf.generatePublic(keySpec); - } - catch (NoSuchAlgorithmException nsae) { - IOException ioe = new IOException("No RSA KeyFactory available"); - ioe.initCause(nsae); - throw ioe; - } - catch (InvalidKeySpecException ikse) { - IOException ioe = new IOException("No RSA KeyFactory available"); - ioe.initCause(ikse); - throw ioe; - } - } - - public static byte[] encodeSSHRSAPublicKey(RSAPublicKey pk) throws IOException { - TypesWriter tw = new TypesWriter(); - tw.writeString("ssh-rsa"); - tw.writeMPInt(pk.getPublicExponent()); - tw.writeMPInt(pk.getModulus()); - return tw.getBytes(); - } - - public static byte[] decodeSSHRSASignature(byte[] sig) throws IOException { - TypesReader tr = new TypesReader(sig); - String sig_format = tr.readString(); - - if (sig_format.equals("ssh-rsa") == false) - throw new IOException("Peer sent wrong signature format"); - - /* S is NOT an MPINT. "The value for 'rsa_signature_blob' is encoded as a string - * containing s (which is an integer, without lengths or padding, unsigned and in - * network byte order)." See also below. - */ - byte[] s = tr.readByteString(); - - if (s.length == 0) - throw new IOException("Error in RSA signature, S is empty."); - - if (log.isEnabled()) { - log.log(80, "Decoding ssh-rsa signature string (length: " + s.length + ")"); - } - - if (tr.remain() != 0) - throw new IOException("Padding in RSA signature!"); - - if (s[0] == 0 && s[1] == 0 && s[2] == 0) { - int i = 0; - int j = ((s[i++] << 24) & 0xff000000) | ((s[i++] << 16) & 0x00ff0000) - | ((s[i++] << 8) & 0x0000ff00) | ((s[i++]) & 0x000000ff); - i += j; - j = ((s[i++] << 24) & 0xff000000) | ((s[i++] << 16) & 0x00ff0000) - | ((s[i++] << 8) & 0x0000ff00) | ((s[i++]) & 0x000000ff); - byte[] tmp = new byte[j]; - System.arraycopy(s, i, tmp, 0, j); - sig = tmp; - } - - return s; - } - - public static byte[] encodeSSHRSASignature(byte[] s) throws IOException { - TypesWriter tw = new TypesWriter(); - tw.writeString("ssh-rsa"); - - /* S is NOT an MPINT. "The value for 'rsa_signature_blob' is encoded as a string - * containing s (which is an integer, without lengths or padding, unsigned and in - * network byte order)." - */ - - /* Remove first zero sign byte, if present */ - - if ((s.length > 1) && (s[0] == 0x00)) - tw.writeString(s, 1, s.length - 1); - else - tw.writeString(s, 0, s.length); - - return tw.getBytes(); - } - - public static byte[] generateSignature(byte[] message, RSAPrivateKey pk) throws IOException { - try { - Signature s = Signature.getInstance("SHA1withRSA"); - s.initSign(pk); - s.update(message); - return s.sign(); - } - catch (NoSuchAlgorithmException e) { - IOException ex = new IOException(); - ex.initCause(e); - throw ex; - } - catch (InvalidKeyException e) { - IOException ex = new IOException(); - ex.initCause(e); - throw ex; - } - catch (SignatureException e) { - IOException ex = new IOException(); - ex.initCause(e); - throw ex; - } - } - - public static boolean verifySignature(byte[] message, byte[] ds, RSAPublicKey dpk) throws IOException { - try { - Signature s = Signature.getInstance("SHA1withRSA"); - s.initVerify(dpk); - s.update(message); - return s.verify(ds); - } - catch (NoSuchAlgorithmException e) { - IOException ex = new IOException(); - ex.initCause(e); - throw ex; - } - catch (InvalidKeyException e) { - IOException ex = new IOException(); - ex.initCause(e); - throw ex; - } - catch (SignatureException e) { - IOException ex = new IOException(); - ex.initCause(e); - throw ex; - } - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/transport/ClientServerHello.java --- a/src/com/trilead/ssh2/transport/ClientServerHello.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,113 +0,0 @@ - -package com.trilead.ssh2.transport; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; - -import com.trilead.ssh2.Connection; - -/** - * ClientServerHello. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: ClientServerHello.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ - */ -public class ClientServerHello { - String server_line; - String client_line; - - String server_versioncomment; - - public final static int readLineRN(InputStream is, byte[] buffer) throws IOException { - int pos = 0; - boolean need10 = false; - int len = 0; - - while (true) { - int c = is.read(); - - if (c == -1) - throw new IOException("Premature connection close"); - - buffer[pos++] = (byte) c; - - if (c == 13) { - need10 = true; - continue; - } - - if (c == 10) - break; - - if (need10 == true) - throw new IOException("Malformed line sent by the server, the line does not end correctly."); - - len++; - - if (pos >= buffer.length) - throw new IOException("The server sent a too long line."); - } - - return len; - } - - public ClientServerHello(InputStream bi, OutputStream bo) throws IOException { - client_line = "SSH-2.0-" + Connection.identification; - bo.write((client_line + "\r\n").getBytes("ISO-8859-1")); - bo.flush(); - byte[] serverVersion = new byte[512]; - - for (int i = 0; i < 50; i++) { - int len = readLineRN(bi, serverVersion); - server_line = new String(serverVersion, 0, len, "ISO-8859-1"); - - if (server_line.startsWith("SSH-")) - break; - } - - if (server_line.startsWith("SSH-") == false) - throw new IOException( - "Malformed server identification string. There was no line starting with 'SSH-' amongst the first 50 lines."); - - if (server_line.startsWith("SSH-1.99-")) - server_versioncomment = server_line.substring(9); - else if (server_line.startsWith("SSH-2.0-")) - server_versioncomment = server_line.substring(8); - else - throw new IOException("Server uses incompatible protocol, it is not SSH-2 compatible."); - } - - /** - * @return Returns the client_versioncomment. - */ - public byte[] getClientString() { - byte[] result; - - try { - result = client_line.getBytes("ISO-8859-1"); - } - catch (UnsupportedEncodingException ign) { - result = client_line.getBytes(); - } - - return result; - } - - /** - * @return Returns the server_versioncomment. - */ - public byte[] getServerString() { - byte[] result; - - try { - result = server_line.getBytes("ISO-8859-1"); - } - catch (UnsupportedEncodingException ign) { - result = server_line.getBytes(); - } - - return result; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/transport/KexManager.java --- a/src/com/trilead/ssh2/transport/KexManager.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,568 +0,0 @@ - -package com.trilead.ssh2.transport; - -import java.io.IOException; -import java.security.SecureRandom; -import java.security.interfaces.DSAPublicKey; -import java.security.interfaces.ECPublicKey; -import java.security.interfaces.RSAPublicKey; -import java.util.Set; -import java.util.TreeSet; - -import com.trilead.ssh2.ConnectionInfo; -import com.trilead.ssh2.DHGexParameters; -import com.trilead.ssh2.ServerHostKeyVerifier; -import com.trilead.ssh2.compression.CompressionFactory; -import com.trilead.ssh2.compression.ICompressor; -import com.trilead.ssh2.crypto.CryptoWishList; -import com.trilead.ssh2.crypto.KeyMaterial; -import com.trilead.ssh2.crypto.cipher.BlockCipher; -import com.trilead.ssh2.crypto.cipher.BlockCipherFactory; -import com.trilead.ssh2.crypto.dh.DhGroupExchange; -import com.trilead.ssh2.crypto.dh.GenericDhExchange; -import com.trilead.ssh2.crypto.digest.MAC; -import com.trilead.ssh2.log.Logger; -import com.trilead.ssh2.packets.PacketKexDHInit; -import com.trilead.ssh2.packets.PacketKexDHReply; -import com.trilead.ssh2.packets.PacketKexDhGexGroup; -import com.trilead.ssh2.packets.PacketKexDhGexInit; -import com.trilead.ssh2.packets.PacketKexDhGexReply; -import com.trilead.ssh2.packets.PacketKexDhGexRequest; -import com.trilead.ssh2.packets.PacketKexDhGexRequestOld; -import com.trilead.ssh2.packets.PacketKexInit; -import com.trilead.ssh2.packets.PacketNewKeys; -import com.trilead.ssh2.packets.Packets; -import com.trilead.ssh2.signature.DSASHA1Verify; -import com.trilead.ssh2.signature.ECDSASHA2Verify; -import com.trilead.ssh2.signature.RSASHA1Verify; - - -/** - * KexManager. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: KexManager.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -public class KexManager { - private static final Logger log = Logger.getLogger(KexManager.class); - - private static final Set HOSTKEY_ALGS = new TreeSet(); - static { - HOSTKEY_ALGS.add("ecdsa-sha2-nistp256"); - HOSTKEY_ALGS.add("ecdsa-sha2-nistp384"); - HOSTKEY_ALGS.add("ecdsa-sha2-nistp521"); - HOSTKEY_ALGS.add("ssh-rsa"); - HOSTKEY_ALGS.add("ssh-dsa"); - } - - private static final Set KEX_ALGS = new TreeSet(); - static { - KEX_ALGS.add("ecdh-sha2-nistp256"); - KEX_ALGS.add("ecdh-sha2-nistp384"); - KEX_ALGS.add("ecdh-sha2-nistp521"); - KEX_ALGS.add("diffie-hellman-group-exchange-sha256"); - KEX_ALGS.add("diffie-hellman-group-exchange-sha1"); - KEX_ALGS.add("diffie-hellman-group14-sha1"); - KEX_ALGS.add("diffie-hellman-group1-sha1"); - } - - KexState kxs; - int kexCount = 0; - KeyMaterial km; - byte[] sessionId; - ClientServerHello csh; - - final Object accessLock = new Object(); - ConnectionInfo lastConnInfo = null; - - boolean connectionClosed = false; - - boolean ignore_next_kex_packet = false; - - final TransportManager tm; - - CryptoWishList nextKEXcryptoWishList; - DHGexParameters nextKEXdhgexParameters; - - ServerHostKeyVerifier verifier; - final String hostname; - final int port; - final SecureRandom rnd; - - public KexManager(TransportManager tm, ClientServerHello csh, CryptoWishList initialCwl, String hostname, int port, - ServerHostKeyVerifier keyVerifier, SecureRandom rnd) { - this.tm = tm; - this.csh = csh; - this.nextKEXcryptoWishList = initialCwl; - this.nextKEXdhgexParameters = new DHGexParameters(); - this.hostname = hostname; - this.port = port; - this.verifier = keyVerifier; - this.rnd = rnd; - } - - public ConnectionInfo getOrWaitForConnectionInfo(int minKexCount) throws IOException { - synchronized (accessLock) { - while (true) { - if ((lastConnInfo != null) && (lastConnInfo.keyExchangeCounter >= minKexCount)) - return lastConnInfo; - - if (connectionClosed) - throw(IOException) new IOException("Key exchange was not finished, connection is closed.") - .initCause(tm.getReasonClosedCause()); - - try { - accessLock.wait(); - } - catch (InterruptedException e) { - } - } - } - } - - private String getFirstMatch(String[] client, String[] server) throws NegotiateException { - if (client == null || server == null) - throw new IllegalArgumentException(); - - if (client.length == 0) - return null; - - for (int i = 0; i < client.length; i++) { - for (int j = 0; j < server.length; j++) { - if (client[i].equals(server[j])) - return client[i]; - } - } - - throw new NegotiateException(); - } - - private boolean compareFirstOfNameList(String[] a, String[] b) { - if (a == null || b == null) - throw new IllegalArgumentException(); - - if ((a.length == 0) && (b.length == 0)) - return true; - - if ((a.length == 0) || (b.length == 0)) - return false; - - return (a[0].equals(b[0])); - } - - private boolean isGuessOK(KexParameters cpar, KexParameters spar) { - if (cpar == null || spar == null) - throw new IllegalArgumentException(); - - if (compareFirstOfNameList(cpar.kex_algorithms, spar.kex_algorithms) == false) { - return false; - } - - if (compareFirstOfNameList(cpar.server_host_key_algorithms, spar.server_host_key_algorithms) == false) { - return false; - } - - /* - * We do NOT check here if the other algorithms can be agreed on, this - * is just a check if kex_algorithms and server_host_key_algorithms were - * guessed right! - */ - return true; - } - - private NegotiatedParameters mergeKexParameters(KexParameters client, KexParameters server) { - NegotiatedParameters np = new NegotiatedParameters(); - - try { - np.kex_algo = getFirstMatch(client.kex_algorithms, server.kex_algorithms); - log.log(20, "kex_algo=" + np.kex_algo); - np.server_host_key_algo = getFirstMatch(client.server_host_key_algorithms, - server.server_host_key_algorithms); - log.log(20, "server_host_key_algo=" + np.server_host_key_algo); - np.enc_algo_client_to_server = getFirstMatch(client.encryption_algorithms_client_to_server, - server.encryption_algorithms_client_to_server); - np.enc_algo_server_to_client = getFirstMatch(client.encryption_algorithms_server_to_client, - server.encryption_algorithms_server_to_client); - log.log(20, "enc_algo_client_to_server=" + np.enc_algo_client_to_server); - log.log(20, "enc_algo_server_to_client=" + np.enc_algo_server_to_client); - np.mac_algo_client_to_server = getFirstMatch(client.mac_algorithms_client_to_server, - server.mac_algorithms_client_to_server); - np.mac_algo_server_to_client = getFirstMatch(client.mac_algorithms_server_to_client, - server.mac_algorithms_server_to_client); - log.log(20, "mac_algo_client_to_server=" + np.mac_algo_client_to_server); - log.log(20, "mac_algo_server_to_client=" + np.mac_algo_server_to_client); - np.comp_algo_client_to_server = getFirstMatch(client.compression_algorithms_client_to_server, - server.compression_algorithms_client_to_server); - np.comp_algo_server_to_client = getFirstMatch(client.compression_algorithms_server_to_client, - server.compression_algorithms_server_to_client); - log.log(20, "comp_algo_client_to_server=" + np.comp_algo_client_to_server); - log.log(20, "comp_algo_server_to_client=" + np.comp_algo_server_to_client); - } - catch (NegotiateException e) { - return null; - } - - try { - np.lang_client_to_server = getFirstMatch(client.languages_client_to_server, - server.languages_client_to_server); - } - catch (NegotiateException e1) { - np.lang_client_to_server = null; - } - - try { - np.lang_server_to_client = getFirstMatch(client.languages_server_to_client, - server.languages_server_to_client); - } - catch (NegotiateException e2) { - np.lang_server_to_client = null; - } - - if (isGuessOK(client, server)) - np.guessOK = true; - - return np; - } - - public synchronized void initiateKEX(CryptoWishList cwl, DHGexParameters dhgex) throws IOException { - nextKEXcryptoWishList = cwl; - nextKEXdhgexParameters = dhgex; - - if (kxs == null) { - kxs = new KexState(); - kxs.dhgexParameters = nextKEXdhgexParameters; - PacketKexInit kp = new PacketKexInit(nextKEXcryptoWishList); - kxs.localKEX = kp; - tm.sendKexMessage(kp.getPayload()); - } - } - - private boolean establishKeyMaterial() { - try { - int mac_cs_key_len = MAC.getKeyLen(kxs.np.mac_algo_client_to_server); - int enc_cs_key_len = BlockCipherFactory.getKeySize(kxs.np.enc_algo_client_to_server); - int enc_cs_block_len = BlockCipherFactory.getBlockSize(kxs.np.enc_algo_client_to_server); - int mac_sc_key_len = MAC.getKeyLen(kxs.np.mac_algo_server_to_client); - int enc_sc_key_len = BlockCipherFactory.getKeySize(kxs.np.enc_algo_server_to_client); - int enc_sc_block_len = BlockCipherFactory.getBlockSize(kxs.np.enc_algo_server_to_client); - km = KeyMaterial.create(kxs.hashAlgo, kxs.H, kxs.K, sessionId, enc_cs_key_len, enc_cs_block_len, mac_cs_key_len, - enc_sc_key_len, enc_sc_block_len, mac_sc_key_len); - } - catch (IllegalArgumentException e) { - return false; - } - - return true; - } - - private void finishKex() throws IOException { - if (sessionId == null) - sessionId = kxs.H; - - establishKeyMaterial(); - /* Tell the other side that we start using the new material */ - PacketNewKeys ign = new PacketNewKeys(); - tm.sendKexMessage(ign.getPayload()); - BlockCipher cbc; - MAC mac; - ICompressor comp; - - try { - cbc = BlockCipherFactory.createCipher(kxs.np.enc_algo_client_to_server, true, km.enc_key_client_to_server, - km.initial_iv_client_to_server); - mac = new MAC(kxs.np.mac_algo_client_to_server, km.integrity_key_client_to_server); - comp = CompressionFactory.createCompressor(kxs.np.comp_algo_client_to_server); - } - catch (IllegalArgumentException e1) { - throw new IOException("Fatal error during MAC startup!"); - } - - tm.changeSendCipher(cbc, mac); - tm.changeSendCompression(comp); - tm.kexFinished(); - } - - public static final String[] getDefaultServerHostkeyAlgorithmList() { - return HOSTKEY_ALGS.toArray(new String[HOSTKEY_ALGS.size()]); - } - - public static final void checkServerHostkeyAlgorithmsList(String[] algos) { - for (int i = 0; i < algos.length; i++) { - if (!HOSTKEY_ALGS.contains(algos[i])) - throw new IllegalArgumentException("Unknown server host key algorithm '" + algos[i] + "'"); - } - } - - public static final String[] getDefaultKexAlgorithmList() { - return KEX_ALGS.toArray(new String[KEX_ALGS.size()]); - } - - public static final void checkKexAlgorithmList(String[] algos) { - for (int i = 0; i < algos.length; i++) { - if (!KEX_ALGS.contains(algos[i])) - throw new IllegalArgumentException("Unknown kex algorithm '" + algos[i] + "'"); - } - } - - private boolean verifySignature(byte[] sig, byte[] hostkey) throws IOException { - if (kxs.np.server_host_key_algo.startsWith("ecdsa-sha2-")) { - byte[] rs = ECDSASHA2Verify.decodeSSHECDSASignature(sig); - ECPublicKey epk = ECDSASHA2Verify.decodeSSHECDSAPublicKey(hostkey); - log.log(50, "Verifying ecdsa signature"); - return ECDSASHA2Verify.verifySignature(kxs.H, rs, epk); - } - - if (kxs.np.server_host_key_algo.equals("ssh-rsa")) { - byte[] rs = RSASHA1Verify.decodeSSHRSASignature(sig); - RSAPublicKey rpk = RSASHA1Verify.decodeSSHRSAPublicKey(hostkey); - log.log(50, "Verifying ssh-rsa signature"); - return RSASHA1Verify.verifySignature(kxs.H, rs, rpk); - } - - if (kxs.np.server_host_key_algo.equals("ssh-dss")) { - byte[] ds = DSASHA1Verify.decodeSSHDSASignature(sig); - DSAPublicKey dpk = DSASHA1Verify.decodeSSHDSAPublicKey(hostkey); - log.log(50, "Verifying ssh-dss signature"); - return DSASHA1Verify.verifySignature(kxs.H, ds, dpk); - } - - throw new IOException("Unknown server host key algorithm '" + kxs.np.server_host_key_algo + "'"); - } - - public synchronized void handleMessage(byte[] msg, int msglen) throws IOException { - PacketKexInit kip; - - if (msg == null) { - synchronized (accessLock) { - connectionClosed = true; - accessLock.notifyAll(); - return; - } - } - - if ((kxs == null) && (msg[0] != Packets.SSH_MSG_KEXINIT)) - throw new IOException("Unexpected KEX message (type " + msg[0] + ")"); - - if (ignore_next_kex_packet) { - ignore_next_kex_packet = false; - return; - } - - if (msg[0] == Packets.SSH_MSG_KEXINIT) { - if ((kxs != null) && (kxs.state != 0)) - throw new IOException("Unexpected SSH_MSG_KEXINIT message during on-going kex exchange!"); - - if (kxs == null) { - /* - * Ah, OK, peer wants to do KEX. Let's be nice and play - * together. - */ - kxs = new KexState(); - kxs.dhgexParameters = nextKEXdhgexParameters; - kip = new PacketKexInit(nextKEXcryptoWishList); - kxs.localKEX = kip; - tm.sendKexMessage(kip.getPayload()); - } - - kip = new PacketKexInit(msg, 0, msglen); - kxs.remoteKEX = kip; - kxs.np = mergeKexParameters(kxs.localKEX.getKexParameters(), kxs.remoteKEX.getKexParameters()); - - if (kxs.np == null) - throw new IOException("Cannot negotiate, proposals do not match."); - - if (kxs.remoteKEX.isFirst_kex_packet_follows() && (kxs.np.guessOK == false)) { - /* - * Guess was wrong, we need to ignore the next kex packet. - */ - ignore_next_kex_packet = true; - } - - if (kxs.np.kex_algo.equals("diffie-hellman-group-exchange-sha1") - || kxs.np.kex_algo.equals("diffie-hellman-group-exchange-sha256")) { - if (kxs.dhgexParameters.getMin_group_len() == 0 || csh.server_versioncomment.matches("OpenSSH_2\\.([0-4]\\.|5\\.[0-2]).*")) { - PacketKexDhGexRequestOld dhgexreq = new PacketKexDhGexRequestOld(kxs.dhgexParameters); - tm.sendKexMessage(dhgexreq.getPayload()); - } - else { - PacketKexDhGexRequest dhgexreq = new PacketKexDhGexRequest(kxs.dhgexParameters); - tm.sendKexMessage(dhgexreq.getPayload()); - } - - if (kxs.np.kex_algo.endsWith("sha1")) { - kxs.hashAlgo = "SHA1"; - } - else { - kxs.hashAlgo = "SHA-256"; - } - - kxs.state = 1; - return; - } - - if (kxs.np.kex_algo.equals("diffie-hellman-group1-sha1") - || kxs.np.kex_algo.equals("diffie-hellman-group14-sha1") - || kxs.np.kex_algo.equals("ecdh-sha2-nistp256") - || kxs.np.kex_algo.equals("ecdh-sha2-nistp384") - || kxs.np.kex_algo.equals("ecdh-sha2-nistp521")) { - kxs.dhx = GenericDhExchange.getInstance(kxs.np.kex_algo); - kxs.dhx.init(kxs.np.kex_algo); - kxs.hashAlgo = kxs.dhx.getHashAlgo(); - PacketKexDHInit kp = new PacketKexDHInit(kxs.dhx.getE()); - tm.sendKexMessage(kp.getPayload()); - kxs.state = 1; - return; - } - - throw new IllegalStateException("Unknown KEX method!"); - } - - if (msg[0] == Packets.SSH_MSG_NEWKEYS) { - if (km == null) - throw new IOException("Peer sent SSH_MSG_NEWKEYS, but I have no key material ready!"); - - BlockCipher cbc; - MAC mac; - ICompressor comp; - - try { - cbc = BlockCipherFactory.createCipher(kxs.np.enc_algo_server_to_client, false, - km.enc_key_server_to_client, km.initial_iv_server_to_client); - mac = new MAC(kxs.np.mac_algo_server_to_client, km.integrity_key_server_to_client); - comp = CompressionFactory.createCompressor(kxs.np.comp_algo_server_to_client); - } - catch (IllegalArgumentException e1) { - throw new IOException("Fatal error during MAC startup!"); - } - - tm.changeRecvCipher(cbc, mac); - tm.changeRecvCompression(comp); - ConnectionInfo sci = new ConnectionInfo(); - kexCount++; - sci.keyExchangeAlgorithm = kxs.np.kex_algo; - sci.keyExchangeCounter = kexCount; - sci.clientToServerCryptoAlgorithm = kxs.np.enc_algo_client_to_server; - sci.serverToClientCryptoAlgorithm = kxs.np.enc_algo_server_to_client; - sci.clientToServerMACAlgorithm = kxs.np.mac_algo_client_to_server; - sci.serverToClientMACAlgorithm = kxs.np.mac_algo_server_to_client; - sci.serverHostKeyAlgorithm = kxs.np.server_host_key_algo; - sci.serverHostKey = kxs.hostkey; - - synchronized (accessLock) { - lastConnInfo = sci; - accessLock.notifyAll(); - } - - kxs = null; - return; - } - - if ((kxs == null) || (kxs.state == 0)) - throw new IOException("Unexpected Kex submessage!"); - - if (kxs.np.kex_algo.equals("diffie-hellman-group-exchange-sha1") - || kxs.np.kex_algo.equals("diffie-hellman-group-exchange-sha256")) { - if (kxs.state == 1) { - PacketKexDhGexGroup dhgexgrp = new PacketKexDhGexGroup(msg, 0, msglen); - kxs.dhgx = new DhGroupExchange(dhgexgrp.getP(), dhgexgrp.getG()); - kxs.dhgx.init(rnd); - PacketKexDhGexInit dhgexinit = new PacketKexDhGexInit(kxs.dhgx.getE()); - tm.sendKexMessage(dhgexinit.getPayload()); - kxs.state = 2; - return; - } - - if (kxs.state == 2) { - PacketKexDhGexReply dhgexrpl = new PacketKexDhGexReply(msg, 0, msglen); - kxs.hostkey = dhgexrpl.getHostKey(); - - if (verifier != null) { - boolean vres = false; - - try { - vres = verifier.verifyServerHostKey(hostname, port, kxs.np.server_host_key_algo, kxs.hostkey); - } - catch (Exception e) { - throw(IOException) new IOException( - "The server hostkey was not accepted by the verifier callback.").initCause(e); - } - - if (vres == false) - throw new IOException("The server hostkey was not accepted by the verifier callback"); - } - - kxs.dhgx.setF(dhgexrpl.getF()); - - try { - kxs.H = kxs.dhgx.calculateH(kxs.hashAlgo, - csh.getClientString(), csh.getServerString(), - kxs.localKEX.getPayload(), kxs.remoteKEX.getPayload(), - dhgexrpl.getHostKey(), kxs.dhgexParameters); - } - catch (IllegalArgumentException e) { - throw(IOException) new IOException("KEX error.").initCause(e); - } - - boolean res = verifySignature(dhgexrpl.getSignature(), kxs.hostkey); - - if (res == false) - throw new IOException("Hostkey signature sent by remote is wrong!"); - - kxs.K = kxs.dhgx.getK(); - finishKex(); - kxs.state = -1; - return; - } - - throw new IllegalStateException("Illegal State in KEX Exchange!"); - } - - if (kxs.np.kex_algo.equals("diffie-hellman-group1-sha1") - || kxs.np.kex_algo.equals("diffie-hellman-group14-sha1") - || kxs.np.kex_algo.equals("ecdh-sha2-nistp256") - || kxs.np.kex_algo.equals("ecdh-sha2-nistp384") - || kxs.np.kex_algo.equals("ecdh-sha2-nistp521")) { - if (kxs.state == 1) { - PacketKexDHReply dhr = new PacketKexDHReply(msg, 0, msglen); - kxs.hostkey = dhr.getHostKey(); - - if (verifier != null) { - boolean vres = false; - - try { - vres = verifier.verifyServerHostKey(hostname, port, kxs.np.server_host_key_algo, kxs.hostkey); - } - catch (Exception e) { - throw(IOException) new IOException( - "The server hostkey was not accepted by the verifier callback.").initCause(e); - } - - if (vres == false) - throw new IOException("The server hostkey was not accepted by the verifier callback"); - } - - kxs.dhx.setF(dhr.getF()); - - try { - kxs.H = kxs.dhx.calculateH(csh.getClientString(), csh.getServerString(), kxs.localKEX.getPayload(), - kxs.remoteKEX.getPayload(), dhr.getHostKey()); - } - catch (IllegalArgumentException e) { - throw(IOException) new IOException("KEX error.").initCause(e); - } - - boolean res = verifySignature(dhr.getSignature(), kxs.hostkey); - - if (res == false) - throw new IOException("Hostkey signature sent by remote is wrong!"); - - kxs.K = kxs.dhx.getK(); - finishKex(); - kxs.state = -1; - return; - } - } - - throw new IllegalStateException("Unkown KEX method! (" + kxs.np.kex_algo + ")"); - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/transport/KexParameters.java --- a/src/com/trilead/ssh2/transport/KexParameters.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -package com.trilead.ssh2.transport; - -/** - * KexParameters. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: KexParameters.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -public class KexParameters { - public byte[] cookie; - public String[] kex_algorithms; - public String[] server_host_key_algorithms; - public String[] encryption_algorithms_client_to_server; - public String[] encryption_algorithms_server_to_client; - public String[] mac_algorithms_client_to_server; - public String[] mac_algorithms_server_to_client; - public String[] compression_algorithms_client_to_server; - public String[] compression_algorithms_server_to_client; - public String[] languages_client_to_server; - public String[] languages_server_to_client; - public boolean first_kex_packet_follows; - public int reserved_field1; -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/transport/KexState.java --- a/src/com/trilead/ssh2/transport/KexState.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,32 +0,0 @@ -package com.trilead.ssh2.transport; - - -import java.math.BigInteger; - -import com.trilead.ssh2.DHGexParameters; -import com.trilead.ssh2.crypto.dh.DhGroupExchange; -import com.trilead.ssh2.crypto.dh.GenericDhExchange; -import com.trilead.ssh2.packets.PacketKexInit; - -/** - * KexState. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: KexState.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $ - */ -public class KexState { - public PacketKexInit localKEX; - public PacketKexInit remoteKEX; - public NegotiatedParameters np; - public int state = 0; - - public BigInteger K; - public byte[] H; - - public byte[] hostkey; - - public String hashAlgo; - public GenericDhExchange dhx; - public DhGroupExchange dhgx; - public DHGexParameters dhgexParameters; -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/transport/MessageHandler.java --- a/src/com/trilead/ssh2/transport/MessageHandler.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -package com.trilead.ssh2.transport; - -import java.io.IOException; - -/** - * MessageHandler. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: MessageHandler.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -public interface MessageHandler { - public void handleMessage(byte[] msg, int msglen) throws IOException; -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/transport/NegotiateException.java --- a/src/com/trilead/ssh2/transport/NegotiateException.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -package com.trilead.ssh2.transport; - -/** - * NegotiateException. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: NegotiateException.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -public class NegotiateException extends Exception { - private static final long serialVersionUID = 3689910669428143157L; -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/transport/NegotiatedParameters.java --- a/src/com/trilead/ssh2/transport/NegotiatedParameters.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -package com.trilead.ssh2.transport; - -/** - * NegotiatedParameters. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: NegotiatedParameters.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $ - */ -public class NegotiatedParameters { - public boolean guessOK; - public String kex_algo; - public String server_host_key_algo; - public String enc_algo_client_to_server; - public String enc_algo_server_to_client; - public String mac_algo_client_to_server; - public String mac_algo_server_to_client; - public String comp_algo_client_to_server; - public String comp_algo_server_to_client; - public String lang_client_to_server; - public String lang_server_to_client; -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/transport/TransportConnection.java --- a/src/com/trilead/ssh2/transport/TransportConnection.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,318 +0,0 @@ - -package com.trilead.ssh2.transport; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.security.SecureRandom; - -import com.trilead.ssh2.compression.ICompressor; -import com.trilead.ssh2.crypto.cipher.BlockCipher; -import com.trilead.ssh2.crypto.cipher.CipherInputStream; -import com.trilead.ssh2.crypto.cipher.CipherOutputStream; -import com.trilead.ssh2.crypto.cipher.NullCipher; -import com.trilead.ssh2.crypto.digest.MAC; -import com.trilead.ssh2.log.Logger; -import com.trilead.ssh2.packets.Packets; - - -/** - * TransportConnection. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: TransportConnection.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -public class TransportConnection { - private static final Logger log = Logger.getLogger(TransportConnection.class); - - int send_seq_number = 0; - - int recv_seq_number = 0; - - CipherInputStream cis; - - CipherOutputStream cos; - - boolean useRandomPadding = false; - - /* Depends on current MAC and CIPHER */ - - MAC send_mac; - - byte[] send_mac_buffer; - - int send_padd_blocksize = 8; - - MAC recv_mac; - - byte[] recv_mac_buffer; - - byte[] recv_mac_buffer_cmp; - - int recv_padd_blocksize = 8; - - ICompressor recv_comp = null; - - ICompressor send_comp = null; - - boolean can_recv_compress = false; - - boolean can_send_compress = false; - - byte[] recv_comp_buffer; - - byte[] send_comp_buffer; - - /* won't change */ - - final byte[] send_padding_buffer = new byte[256]; - - final byte[] send_packet_header_buffer = new byte[5]; - - final byte[] recv_padding_buffer = new byte[256]; - - final byte[] recv_packet_header_buffer = new byte[5]; - - boolean recv_packet_header_present = false; - - ClientServerHello csh; - - final SecureRandom rnd; - - public TransportConnection(InputStream is, OutputStream os, SecureRandom rnd) { - this.cis = new CipherInputStream(new NullCipher(), is); - this.cos = new CipherOutputStream(new NullCipher(), os); - this.rnd = rnd; - } - - public void changeRecvCipher(BlockCipher bc, MAC mac) { - cis.changeCipher(bc); - recv_mac = mac; - recv_mac_buffer = (mac != null) ? new byte[mac.size()] : null; - recv_mac_buffer_cmp = (mac != null) ? new byte[mac.size()] : null; - recv_padd_blocksize = bc.getBlockSize(); - - if (recv_padd_blocksize < 8) - recv_padd_blocksize = 8; - } - - public void changeSendCipher(BlockCipher bc, MAC mac) { - if ((bc instanceof NullCipher) == false) { - /* Only use zero byte padding for the first few packets */ - useRandomPadding = true; - /* Once we start encrypting, there is no way back */ - } - - cos.changeCipher(bc); - send_mac = mac; - send_mac_buffer = (mac != null) ? new byte[mac.size()] : null; - send_padd_blocksize = bc.getBlockSize(); - - if (send_padd_blocksize < 8) - send_padd_blocksize = 8; - } - - public void changeRecvCompression(ICompressor comp) { - recv_comp = comp; - - if (comp != null) { - recv_comp_buffer = new byte[comp.getBufferSize()]; - can_recv_compress |= recv_comp.canCompressPreauth(); - } - } - - public void changeSendCompression(ICompressor comp) { - send_comp = comp; - - if (comp != null) { - send_comp_buffer = new byte[comp.getBufferSize()]; - can_send_compress |= send_comp.canCompressPreauth(); - } - } - - public void sendMessage(byte[] message) throws IOException { - sendMessage(message, 0, message.length, 0); - } - - public void sendMessage(byte[] message, int off, int len) throws IOException { - sendMessage(message, off, len, 0); - } - - public int getPacketOverheadEstimate() { - // return an estimate for the paket overhead (for send operations) - return 5 + 4 + (send_padd_blocksize - 1) + send_mac_buffer.length; - } - - public void sendMessage(byte[] message, int off, int len, int padd) throws IOException { - if (padd < 4) - padd = 4; - else if (padd > 64) - padd = 64; - - if (send_comp != null && can_send_compress) { - if (send_comp_buffer.length < message.length + 1024) - send_comp_buffer = new byte[message.length + 1024]; - - len = send_comp.compress(message, off, len, send_comp_buffer); - message = send_comp_buffer; - } - - int packet_len = 5 + len + padd; /* Minimum allowed padding is 4 */ - int slack = packet_len % send_padd_blocksize; - - if (slack != 0) { - packet_len += (send_padd_blocksize - slack); - } - - if (packet_len < 16) - packet_len = 16; - - int padd_len = packet_len - (5 + len); - - if (useRandomPadding) { - for (int i = 0; i < padd_len; i = i + 4) { - /* - * don't waste calls to rnd.nextInt() (by using only 8bit of the - * output). just believe me: even though we may write here up to 3 - * bytes which won't be used, there is no "buffer overflow" (i.e., - * arrayindexoutofbounds). the padding buffer is big enough =) (256 - * bytes, and that is bigger than any current cipher block size + 64). - */ - int r = rnd.nextInt(); - send_padding_buffer[i] = (byte) r; - send_padding_buffer[i + 1] = (byte)(r >> 8); - send_padding_buffer[i + 2] = (byte)(r >> 16); - send_padding_buffer[i + 3] = (byte)(r >> 24); - } - } - else { - /* use zero padding for unencrypted traffic */ - for (int i = 0; i < padd_len; i++) - send_padding_buffer[i] = 0; - - /* Actually this code is paranoid: we never filled any - * bytes into the padding buffer so far, therefore it should - * consist of zeros only. - */ - } - - send_packet_header_buffer[0] = (byte)((packet_len - 4) >> 24); - send_packet_header_buffer[1] = (byte)((packet_len - 4) >> 16); - send_packet_header_buffer[2] = (byte)((packet_len - 4) >> 8); - send_packet_header_buffer[3] = (byte)((packet_len - 4)); - send_packet_header_buffer[4] = (byte) padd_len; - cos.write(send_packet_header_buffer, 0, 5); - cos.write(message, off, len); - cos.write(send_padding_buffer, 0, padd_len); - - if (send_mac != null) { - send_mac.initMac(send_seq_number); - send_mac.update(send_packet_header_buffer, 0, 5); - send_mac.update(message, off, len); - send_mac.update(send_padding_buffer, 0, padd_len); - send_mac.getMac(send_mac_buffer, 0); - cos.writePlain(send_mac_buffer, 0, send_mac_buffer.length); - } - - cos.flush(); - - if (log.isEnabled()) { - log.log(90, "Sent " + Packets.getMessageName(message[off] & 0xff) + " " + len + " bytes payload"); - } - - send_seq_number++; - } - - public int peekNextMessageLength() throws IOException { - if (recv_packet_header_present == false) { - cis.read(recv_packet_header_buffer, 0, 5); - recv_packet_header_present = true; - } - - int packet_length = ((recv_packet_header_buffer[0] & 0xff) << 24) - | ((recv_packet_header_buffer[1] & 0xff) << 16) | ((recv_packet_header_buffer[2] & 0xff) << 8) - | ((recv_packet_header_buffer[3] & 0xff)); - int padding_length = recv_packet_header_buffer[4] & 0xff; - - if (packet_length > 35000 || packet_length < 12) - throw new IOException("Illegal packet size! (" + packet_length + ")"); - - int payload_length = packet_length - padding_length - 1; - - if (payload_length < 0) - throw new IOException("Illegal padding_length in packet from remote (" + padding_length + ")"); - - return payload_length; - } - - public int receiveMessage(byte buffer[], int off, int len) throws IOException { - if (recv_packet_header_present == false) { - cis.read(recv_packet_header_buffer, 0, 5); - } - else - recv_packet_header_present = false; - - int packet_length = ((recv_packet_header_buffer[0] & 0xff) << 24) - | ((recv_packet_header_buffer[1] & 0xff) << 16) | ((recv_packet_header_buffer[2] & 0xff) << 8) - | ((recv_packet_header_buffer[3] & 0xff)); - int padding_length = recv_packet_header_buffer[4] & 0xff; - - if (packet_length > 35000 || packet_length < 12) - throw new IOException("Illegal packet size! (" + packet_length + ")"); - - int payload_length = packet_length - padding_length - 1; - - if (payload_length < 0) - throw new IOException("Illegal padding_length in packet from remote (" + padding_length + ")"); - - if (payload_length >= len) - throw new IOException("Receive buffer too small (" + len + ", need " + payload_length + ")"); - - cis.read(buffer, off, payload_length); - cis.read(recv_padding_buffer, 0, padding_length); - - if (recv_mac != null) { - cis.readPlain(recv_mac_buffer, 0, recv_mac_buffer.length); - recv_mac.initMac(recv_seq_number); - recv_mac.update(recv_packet_header_buffer, 0, 5); - recv_mac.update(buffer, off, payload_length); - recv_mac.update(recv_padding_buffer, 0, padding_length); - recv_mac.getMac(recv_mac_buffer_cmp, 0); - - for (int i = 0; i < recv_mac_buffer.length; i++) { - if (recv_mac_buffer[i] != recv_mac_buffer_cmp[i]) - throw new IOException("Remote sent corrupt MAC."); - } - } - - recv_seq_number++; - - if (log.isEnabled()) { - log.log(90, "Received " + Packets.getMessageName(buffer[off] & 0xff) + " " + payload_length - + " bytes payload"); - } - - if (recv_comp != null && can_recv_compress) { - int[] uncomp_len = new int[] { payload_length }; - buffer = recv_comp.uncompress(buffer, off, uncomp_len); - - if (buffer == null) { - throw new IOException("Error while inflating remote data"); - } - else { - return uncomp_len[0]; - } - } - else { - return payload_length; - } - } - - /** - * - */ - public void startCompression() { - can_recv_compress = true; - can_send_compress = true; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/transport/TransportManager.java --- a/src/com/trilead/ssh2/transport/TransportManager.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,682 +0,0 @@ - -package com.trilead.ssh2.transport; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.UnknownHostException; -import java.security.SecureRandom; -import java.util.Vector; - -import com.trilead.ssh2.ConnectionInfo; -import com.trilead.ssh2.ConnectionMonitor; -import com.trilead.ssh2.DHGexParameters; -import com.trilead.ssh2.HTTPProxyData; -import com.trilead.ssh2.HTTPProxyException; -import com.trilead.ssh2.ProxyData; -import com.trilead.ssh2.ServerHostKeyVerifier; -import com.trilead.ssh2.compression.ICompressor; -import com.trilead.ssh2.crypto.Base64; -import com.trilead.ssh2.crypto.CryptoWishList; -import com.trilead.ssh2.crypto.cipher.BlockCipher; -import com.trilead.ssh2.crypto.digest.MAC; -import com.trilead.ssh2.log.Logger; -import com.trilead.ssh2.packets.PacketDisconnect; -import com.trilead.ssh2.packets.Packets; -import com.trilead.ssh2.packets.TypesReader; -import com.trilead.ssh2.util.Tokenizer; - - -/* - * Yes, the "standard" is a big mess. On one side, the say that arbitary channel - * packets are allowed during kex exchange, on the other side we need to blindly - * ignore the next _packet_ if the KEX guess was wrong. Where do we know from that - * the next packet is not a channel data packet? Yes, we could check if it is in - * the KEX range. But the standard says nothing about this. The OpenSSH guys - * block local "normal" traffic during KEX. That's fine - however, they assume - * that the other side is doing the same. During re-key, if they receive traffic - * other than KEX, they become horribly irritated and kill the connection. Since - * we are very likely going to communicate with OpenSSH servers, we have to play - * the same game - even though we could do better. - * - * btw: having stdout and stderr on the same channel, with a shared window, is - * also a VERY good idea... =( - */ - -/** - * TransportManager. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: TransportManager.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ - */ -public class TransportManager { - private static final Logger log = Logger.getLogger(TransportManager.class); - - class HandlerEntry { - MessageHandler mh; - int low; - int high; - } - - private final Vector asynchronousQueue = new Vector(); - private Thread asynchronousThread = null; - - class AsynchronousWorker extends Thread { - public void run() { - while (true) { - byte[] msg = null; - - synchronized (asynchronousQueue) { - if (asynchronousQueue.size() == 0) { - /* After the queue is empty for about 2 seconds, stop this thread */ - try { - asynchronousQueue.wait(2000); - } - catch (InterruptedException e) { - /* OKOK, if somebody interrupts us, then we may die earlier. */ - } - - if (asynchronousQueue.size() == 0) { - asynchronousThread = null; - return; - } - } - - msg = asynchronousQueue.remove(0); - } - - /* The following invocation may throw an IOException. - * There is no point in handling it - it simply means - * that the connection has a problem and we should stop - * sending asynchronously messages. We do not need to signal that - * we have exited (asynchronousThread = null): further - * messages in the queue cannot be sent by this or any - * other thread. - * Other threads will sooner or later (when receiving or - * sending the next message) get the same IOException and - * get to the same conclusion. - */ - - try { - sendMessage(msg); - } - catch (IOException e) { - return; - } - } - } - } - - String hostname; - int port; - final Socket sock = new Socket(); - - Object connectionSemaphore = new Object(); - - boolean flagKexOngoing = false; - boolean connectionClosed = false; - - Throwable reasonClosedCause = null; - - TransportConnection tc; - KexManager km; - - Vector messageHandlers = new Vector(); - - Thread receiveThread; - - Vector connectionMonitors = new Vector(); - boolean monitorsWereInformed = false; - - /** - * There were reports that there are JDKs which use - * the resolver even though one supplies a dotted IP - * address in the Socket constructor. That is why we - * try to generate the InetAdress "by hand". - * - * @param host - * @return the InetAddress - * @throws UnknownHostException - */ - private InetAddress createInetAddress(String host) throws UnknownHostException { - /* Check if it is a dotted IP4 address */ - InetAddress addr = parseIPv4Address(host); - - if (addr != null) - return addr; - - return InetAddress.getByName(host); - } - - private InetAddress parseIPv4Address(String host) throws UnknownHostException { - if (host == null) - return null; - - String[] quad = Tokenizer.parseTokens(host, '.'); - - if ((quad == null) || (quad.length != 4)) - return null; - - byte[] addr = new byte[4]; - - for (int i = 0; i < 4; i++) { - int part = 0; - - if ((quad[i].length() == 0) || (quad[i].length() > 3)) - return null; - - for (int k = 0; k < quad[i].length(); k++) { - char c = quad[i].charAt(k); - - /* No, Character.isDigit is not the same */ - if ((c < '0') || (c > '9')) - return null; - - part = part * 10 + (c - '0'); - } - - if (part > 255) /* 300.1.2.3 is invalid =) */ - return null; - - addr[i] = (byte) part; - } - - return InetAddress.getByAddress(host, addr); - } - - public TransportManager(String host, int port) throws IOException { - this.hostname = host; - this.port = port; - } - - public int getPacketOverheadEstimate() { - return tc.getPacketOverheadEstimate(); - } - - public void setTcpNoDelay(boolean state) throws IOException { - sock.setTcpNoDelay(state); - } - - public void setSoTimeout(int timeout) throws IOException { - sock.setSoTimeout(timeout); - } - - public ConnectionInfo getConnectionInfo(int kexNumber) throws IOException { - return km.getOrWaitForConnectionInfo(kexNumber); - } - - public Throwable getReasonClosedCause() { - synchronized (connectionSemaphore) { - return reasonClosedCause; - } - } - - public byte[] getSessionIdentifier() { - return km.sessionId; - } - - public void close(Throwable cause, boolean useDisconnectPacket) { - if (useDisconnectPacket == false) { - /* OK, hard shutdown - do not aquire the semaphore, - * perhaps somebody is inside (and waits until the remote - * side is ready to accept new data). */ - try { - sock.close(); - } - catch (IOException ignore) { - } - - /* OK, whoever tried to send data, should now agree that - * there is no point in further waiting =) - * It is safe now to aquire the semaphore. - */ - } - - synchronized (connectionSemaphore) { - if (connectionClosed == false) { - if (useDisconnectPacket == true) { - try { - byte[] msg = new PacketDisconnect(Packets.SSH_DISCONNECT_BY_APPLICATION, cause.getMessage(), "") - .getPayload(); - - if (tc != null) - tc.sendMessage(msg); - } - catch (IOException ignore) { - } - - try { - sock.close(); - } - catch (IOException ignore) { - } - } - - connectionClosed = true; - reasonClosedCause = cause; /* may be null */ - } - - connectionSemaphore.notifyAll(); - } - - /* No check if we need to inform the monitors */ - Vector monitors = null; - - synchronized (this) { - /* Short term lock to protect "connectionMonitors" - * and "monitorsWereInformed" - * (they may be modified concurrently) - */ - if (monitorsWereInformed == false) { - monitorsWereInformed = true; - monitors = (Vector) connectionMonitors.clone(); - } - } - - if (monitors != null) { - for (int i = 0; i < monitors.size(); i++) { - try { - ConnectionMonitor cmon = (ConnectionMonitor) monitors.elementAt(i); - cmon.connectionLost(reasonClosedCause); - } - catch (Exception ignore) { - } - } - } - } - - private void establishConnection(ProxyData proxyData, int connectTimeout) throws IOException { - /* See the comment for createInetAddress() */ - if (proxyData == null) { - InetAddress addr = createInetAddress(hostname); - sock.connect(new InetSocketAddress(addr, port), connectTimeout); - sock.setSoTimeout(0); - return; - } - - if (proxyData instanceof HTTPProxyData) { - HTTPProxyData pd = (HTTPProxyData) proxyData; - /* At the moment, we only support HTTP proxies */ - InetAddress addr = createInetAddress(pd.proxyHost); - sock.connect(new InetSocketAddress(addr, pd.proxyPort), connectTimeout); - sock.setSoTimeout(0); - /* OK, now tell the proxy where we actually want to connect to */ - StringBuffer sb = new StringBuffer(); - sb.append("CONNECT "); - sb.append(hostname); - sb.append(':'); - sb.append(port); - sb.append(" HTTP/1.0\r\n"); - - if ((pd.proxyUser != null) && (pd.proxyPass != null)) { - String credentials = pd.proxyUser + ":" + pd.proxyPass; - char[] encoded = Base64.encode(credentials.getBytes("ISO-8859-1")); - sb.append("Proxy-Authorization: Basic "); - sb.append(encoded); - sb.append("\r\n"); - } - - if (pd.requestHeaderLines != null) { - for (int i = 0; i < pd.requestHeaderLines.length; i++) { - if (pd.requestHeaderLines[i] != null) { - sb.append(pd.requestHeaderLines[i]); - sb.append("\r\n"); - } - } - } - - sb.append("\r\n"); - OutputStream out = sock.getOutputStream(); - out.write(sb.toString().getBytes("ISO-8859-1")); - out.flush(); - /* Now parse the HTTP response */ - byte[] buffer = new byte[1024]; - InputStream in = sock.getInputStream(); - int len = ClientServerHello.readLineRN(in, buffer); - String httpReponse = new String(buffer, 0, len, "ISO-8859-1"); - - if (httpReponse.startsWith("HTTP/") == false) - throw new IOException("The proxy did not send back a valid HTTP response."); - - /* "HTTP/1.X XYZ X" => 14 characters minimum */ - - if ((httpReponse.length() < 14) || (httpReponse.charAt(8) != ' ') || (httpReponse.charAt(12) != ' ')) - throw new IOException("The proxy did not send back a valid HTTP response."); - - int errorCode = 0; - - try { - errorCode = Integer.parseInt(httpReponse.substring(9, 12)); - } - catch (NumberFormatException ignore) { - throw new IOException("The proxy did not send back a valid HTTP response."); - } - - if ((errorCode < 0) || (errorCode > 999)) - throw new IOException("The proxy did not send back a valid HTTP response."); - - if (errorCode != 200) { - throw new HTTPProxyException(httpReponse.substring(13), errorCode); - } - - /* OK, read until empty line */ - - while (true) { - len = ClientServerHello.readLineRN(in, buffer); - - if (len == 0) - break; - } - - return; - } - - throw new IOException("Unsupported ProxyData"); - } - - public void initialize(CryptoWishList cwl, ServerHostKeyVerifier verifier, DHGexParameters dhgex, - int connectTimeout, SecureRandom rnd, ProxyData proxyData) throws IOException { - /* First, establish the TCP connection to the SSH-2 server */ - establishConnection(proxyData, connectTimeout); - /* Parse the server line and say hello - important: this information is later needed for the - * key exchange (to stop man-in-the-middle attacks) - that is why we wrap it into an object - * for later use. - */ - ClientServerHello csh = new ClientServerHello(sock.getInputStream(), sock.getOutputStream()); - tc = new TransportConnection(sock.getInputStream(), sock.getOutputStream(), rnd); - km = new KexManager(this, csh, cwl, hostname, port, verifier, rnd); - km.initiateKEX(cwl, dhgex); - receiveThread = new Thread(new Runnable() { - public void run() { - try { - receiveLoop(); - } - catch (IOException e) { - close(e, false); - - if (log.isEnabled()) - log.log(10, "Receive thread: error in receiveLoop: " + e.getMessage()); - } - - if (log.isEnabled()) - log.log(50, "Receive thread: back from receiveLoop"); - - /* Tell all handlers that it is time to say goodbye */ - - if (km != null) { - try { - km.handleMessage(null, 0); - } - catch (IOException e) { - } - } - - for (int i = 0; i < messageHandlers.size(); i++) { - HandlerEntry he = messageHandlers.elementAt(i); - - try { - he.mh.handleMessage(null, 0); - } - catch (Exception ignore) { - } - } - } - }); - receiveThread.setDaemon(true); - receiveThread.start(); - } - - public void registerMessageHandler(MessageHandler mh, int low, int high) { - HandlerEntry he = new HandlerEntry(); - he.mh = mh; - he.low = low; - he.high = high; - - synchronized (messageHandlers) { - messageHandlers.addElement(he); - } - } - - public void removeMessageHandler(MessageHandler mh, int low, int high) { - synchronized (messageHandlers) { - for (int i = 0; i < messageHandlers.size(); i++) { - HandlerEntry he = messageHandlers.elementAt(i); - - if ((he.mh == mh) && (he.low == low) && (he.high == high)) { - messageHandlers.removeElementAt(i); - break; - } - } - } - } - - public void sendKexMessage(byte[] msg) throws IOException { - synchronized (connectionSemaphore) { - if (connectionClosed) { - throw(IOException) new IOException("Sorry, this connection is closed.").initCause(reasonClosedCause); - } - - flagKexOngoing = true; - - try { - tc.sendMessage(msg); - } - catch (IOException e) { - close(e, false); - throw e; - } - } - } - - public void kexFinished() throws IOException { - synchronized (connectionSemaphore) { - flagKexOngoing = false; - connectionSemaphore.notifyAll(); - } - } - - public void forceKeyExchange(CryptoWishList cwl, DHGexParameters dhgex) throws IOException { - km.initiateKEX(cwl, dhgex); - } - - public void changeRecvCipher(BlockCipher bc, MAC mac) { - tc.changeRecvCipher(bc, mac); - } - - public void changeSendCipher(BlockCipher bc, MAC mac) { - tc.changeSendCipher(bc, mac); - } - - /** - * @param comp - */ - public void changeRecvCompression(ICompressor comp) { - tc.changeRecvCompression(comp); - } - - /** - * @param comp - */ - public void changeSendCompression(ICompressor comp) { - tc.changeSendCompression(comp); - } - - /** - * - */ - public void startCompression() { - tc.startCompression(); - } - - public void sendAsynchronousMessage(byte[] msg) throws IOException { - synchronized (asynchronousQueue) { - asynchronousQueue.addElement(msg); - - /* This limit should be flexible enough. We need this, otherwise the peer - * can flood us with global requests (and other stuff where we have to reply - * with an asynchronous message) and (if the server just sends data and does not - * read what we send) this will probably put us in a low memory situation - * (our send queue would grow and grow and...) */ - - if (asynchronousQueue.size() > 100) - throw new IOException("Error: the peer is not consuming our asynchronous replies."); - - /* Check if we have an asynchronous sending thread */ - - if (asynchronousThread == null) { - asynchronousThread = new AsynchronousWorker(); - asynchronousThread.setDaemon(true); - asynchronousThread.start(); - /* The thread will stop after 2 seconds of inactivity (i.e., empty queue) */ - } - } - } - - public void setConnectionMonitors(Vector monitors) { - synchronized (this) { - connectionMonitors = (Vector) monitors.clone(); - } - } - - public void sendMessage(byte[] msg) throws IOException { - if (Thread.currentThread() == receiveThread) - throw new IOException("Assertion error: sendMessage may never be invoked by the receiver thread!"); - - synchronized (connectionSemaphore) { - while (true) { - if (connectionClosed) { - throw(IOException) new IOException("Sorry, this connection is closed.") - .initCause(reasonClosedCause); - } - - if (flagKexOngoing == false) - break; - - try { - connectionSemaphore.wait(); - } - catch (InterruptedException e) { - } - } - - try { - tc.sendMessage(msg); - } - catch (IOException e) { - close(e, false); - throw e; - } - } - } - - public void receiveLoop() throws IOException { - byte[] msg = new byte[35000]; - - while (true) { - int msglen = tc.receiveMessage(msg, 0, msg.length); - int type = msg[0] & 0xff; - - if (type == Packets.SSH_MSG_IGNORE) - continue; - - if (type == Packets.SSH_MSG_DEBUG) { - if (log.isEnabled()) { - TypesReader tr = new TypesReader(msg, 0, msglen); - tr.readByte(); - tr.readBoolean(); - StringBuffer debugMessageBuffer = new StringBuffer(); - debugMessageBuffer.append(tr.readString("UTF-8")); - - for (int i = 0; i < debugMessageBuffer.length(); i++) { - char c = debugMessageBuffer.charAt(i); - - if ((c >= 32) && (c <= 126)) - continue; - - debugMessageBuffer.setCharAt(i, '\uFFFD'); - } - - log.log(50, "DEBUG Message from remote: '" + debugMessageBuffer.toString() + "'"); - } - - continue; - } - - if (type == Packets.SSH_MSG_UNIMPLEMENTED) { - throw new IOException("Peer sent UNIMPLEMENTED message, that should not happen."); - } - - if (type == Packets.SSH_MSG_DISCONNECT) { - TypesReader tr = new TypesReader(msg, 0, msglen); - tr.readByte(); - int reason_code = tr.readUINT32(); - StringBuffer reasonBuffer = new StringBuffer(); - reasonBuffer.append(tr.readString("UTF-8")); - - /* - * Do not get fooled by servers that send abnormal long error - * messages - */ - - if (reasonBuffer.length() > 255) { - reasonBuffer.setLength(255); - reasonBuffer.setCharAt(254, '.'); - reasonBuffer.setCharAt(253, '.'); - reasonBuffer.setCharAt(252, '.'); - } - - /* - * Also, check that the server did not send charcaters that may - * screw up the receiver -> restrict to reasonable US-ASCII - * subset -> "printable characters" (ASCII 32 - 126). Replace - * all others with 0xFFFD (UNICODE replacement character). - */ - - for (int i = 0; i < reasonBuffer.length(); i++) { - char c = reasonBuffer.charAt(i); - - if ((c >= 32) && (c <= 126)) - continue; - - reasonBuffer.setCharAt(i, '\uFFFD'); - } - - throw new IOException("Peer sent DISCONNECT message (reason code " + reason_code + "): " - + reasonBuffer.toString()); - } - - /* - * Is it a KEX Packet? - */ - - if ((type == Packets.SSH_MSG_KEXINIT) || (type == Packets.SSH_MSG_NEWKEYS) - || ((type >= 30) && (type <= 49))) { - km.handleMessage(msg, msglen); - continue; - } - - if (type == Packets.SSH_MSG_USERAUTH_SUCCESS) { - tc.startCompression(); - } - - MessageHandler mh = null; - - for (int i = 0; i < messageHandlers.size(); i++) { - HandlerEntry he = messageHandlers.elementAt(i); - - if ((he.low <= type) && (type <= he.high)) { - mh = he.mh; - break; - } - } - - if (mh == null) - throw new IOException("Unexpected SSH message (type " + type + ")"); - - mh.handleMessage(msg, msglen); - } - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/util/TimeoutService.java --- a/src/com/trilead/ssh2/util/TimeoutService.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,130 +0,0 @@ - -package com.trilead.ssh2.util; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.Collections; -import java.util.LinkedList; - -import com.trilead.ssh2.log.Logger; - - -/** - * TimeoutService (beta). Here you can register a timeout. - *

- * Implemented having large scale programs in mind: if you open many concurrent SSH connections - * that rely on timeouts, then there will be only one timeout thread. Once all timeouts - * have expired/are cancelled, the thread will (sooner or later) exit. - * Only after new timeouts arrive a new thread (singleton) will be instantiated. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: TimeoutService.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $ - */ -public class TimeoutService { - private static final Logger log = Logger.getLogger(TimeoutService.class); - - public static class TimeoutToken implements Comparable { - private long runTime; - private Runnable handler; - - private TimeoutToken(long runTime, Runnable handler) { - this.runTime = runTime; - this.handler = handler; - } - - public int compareTo(Object o) { - TimeoutToken t = (TimeoutToken) o; - - if (runTime > t.runTime) - return 1; - - if (runTime == t.runTime) - return 0; - - return -1; - } - } - - private static class TimeoutThread extends Thread { - public void run() { - synchronized (todolist) { - while (true) { - if (todolist.size() == 0) { - timeoutThread = null; - return; - } - - long now = System.currentTimeMillis(); - TimeoutToken tt = (TimeoutToken) todolist.getFirst(); - - if (tt.runTime > now) { - /* Not ready yet, sleep a little bit */ - try { - todolist.wait(tt.runTime - now); - } - catch (InterruptedException e) { - } - - /* We cannot simply go on, since it could be that the token - * was removed (cancelled) or another one has been inserted in - * the meantime. - */ - continue; - } - - todolist.removeFirst(); - - try { - tt.handler.run(); - } - catch (Exception e) { - StringWriter sw = new StringWriter(); - e.printStackTrace(new PrintWriter(sw)); - log.log(20, "Exeception in Timeout handler:" + e.getMessage() + "(" + sw.toString() + ")"); - } - } - } - } - } - - /* The list object is also used for locking purposes */ - private static final LinkedList todolist = new LinkedList(); - - private static Thread timeoutThread = null; - - /** - * It is assumed that the passed handler will not execute for a long time. - * - * @param runTime - * @param handler - * @return a TimeoutToken that can be used to cancel the timeout. - */ - public static final TimeoutToken addTimeoutHandler(long runTime, Runnable handler) { - TimeoutToken token = new TimeoutToken(runTime, handler); - - synchronized (todolist) { - todolist.add(token); - Collections.sort(todolist); - - if (timeoutThread != null) - timeoutThread.interrupt(); - else { - timeoutThread = new TimeoutThread(); - timeoutThread.setDaemon(true); - timeoutThread.start(); - } - } - - return token; - } - - public static final void cancelTimeoutHandler(TimeoutToken token) { - synchronized (todolist) { - todolist.remove(token); - - if (timeoutThread != null) - timeoutThread.interrupt(); - } - } - -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/com/trilead/ssh2/util/Tokenizer.java --- a/src/com/trilead/ssh2/util/Tokenizer.java Thu Jul 17 22:09:05 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,47 +0,0 @@ - -package com.trilead.ssh2.util; - -/** - * Tokenizer. Why? Because StringTokenizer is not available in J2ME. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: Tokenizer.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $ - */ -public class Tokenizer { - /** - * Exists because StringTokenizer is not available in J2ME. - * Returns an array with at least 1 entry. - * - * @param source must be non-null - * @param delimiter - * @return an array of Strings - */ - public static String[] parseTokens(String source, char delimiter) { - int numtoken = 1; - - for (int i = 0; i < source.length(); i++) { - if (source.charAt(i) == delimiter) - numtoken++; - } - - String list[] = new String[numtoken]; - int nextfield = 0; - - for (int i = 0; i < numtoken; i++) { - if (nextfield >= source.length()) { - list[i] = ""; - } - else { - int idx = source.indexOf(delimiter, nextfield); - - if (idx == -1) - idx = source.length(); - - list[i] = source.substring(nextfield, idx); - nextfield = idx + 1; - } - } - - return list; - } -} diff -r ce2f4e397703 -r 175c7d68f3c4 src/de/mud/terminal/VDUBuffer.java --- a/src/de/mud/terminal/VDUBuffer.java Thu Jul 17 22:09:05 2014 -0700 +++ b/src/de/mud/terminal/VDUBuffer.java Thu Jul 31 16:33:38 2014 -0700 @@ -159,7 +159,9 @@ public void putChar(int c, int l, char ch, int attributes) { int ll = screenBase + l; + if ((ll >= bufSize) || (c >= width)) return; // ignore characters outside our buffer + charArray[ll][c] = ch; charAttributes[ll][c] = attributes; @@ -588,8 +590,8 @@ * @param l line */ public void setCursorPosition(int c, int l) { - cursorX = (c > width-1) ? width-1 : c; - cursorY = (l > height-1) ? height-1 : l; + cursorX = (c > width - 1) ? width - 1 : c; + cursorY = (l > height - 1) ? height - 1 : l; } /** diff -r ce2f4e397703 -r 175c7d68f3c4 src/org/tn5250j/framework/tn5250/Screen5250.java --- a/src/org/tn5250j/framework/tn5250/Screen5250.java Thu Jul 17 22:09:05 2014 -0700 +++ b/src/org/tn5250j/framework/tn5250/Screen5250.java Thu Jul 31 16:33:38 2014 -0700 @@ -1327,6 +1327,7 @@ break; case HOME: + // position to the home position set if (lastPos + numCols + 1 != homePos) { goto_XY(homePos - numCols - 1); @@ -2661,7 +2662,9 @@ if (l >= 0) { lastPos = l * numCols + c; + while (!isInField()) advancePos(); + setDirty(lastPos); fireCursorChanged(); } @@ -2673,12 +2676,14 @@ setDirty(lastPos); setDirty(lastPos + cf.getLength()); lastPos += data.length; + if (!isInField()) { gotoFieldNext(); isInField(); cf = screenFields.getCurrentField(); lastPos = cf.getStartPos(); } + setDirty(lastPos); fireCursorChanged(); } @@ -3568,7 +3573,9 @@ */ protected void changePos(int i) { lastPos += i; + while (lastPos < 0) lastPos += lenScreen; + while (lastPos >= lenScreen) lastPos -= lenScreen; } @@ -3745,7 +3752,7 @@ } public void onFontSizeChanged(float size) { - fireScreenChanged(0, 0, numRows-1, numCols-1); + fireScreenChanged(0, 0, numRows - 1, numCols - 1); } /** diff -r ce2f4e397703 -r 175c7d68f3c4 src/org/tn5250j/framework/tn5250/tnvt.java --- a/src/org/tn5250j/framework/tn5250/tnvt.java Thu Jul 17 22:09:05 2014 -0700 +++ b/src/org/tn5250j/framework/tn5250/tnvt.java Thu Jul 31 16:33:38 2014 -0700 @@ -243,6 +243,7 @@ private String fixer(String value, String def) { if ((value == null) || (value.length() == 0)) return def; + return value; } @@ -1109,7 +1110,6 @@ cursorOn = false; buffer.testChanged(); } - } } diff -r ce2f4e397703 -r 175c7d68f3c4 xml/510connectbot.in --- a/xml/510connectbot.in Thu Jul 17 22:09:05 2014 -0700 +++ b/xml/510connectbot.in Thu Jul 31 16:33:38 2014 -0700 @@ -251,9 +251,6 @@ The tn5250 ssl/tls key storage should use the same storage mechanism as the base ssh key storage. - - com.trilead.ssh2 should be updated to the ganymed fork at ch.ethz.ssh2. - @@ -293,9 +290,29 @@ http://the-b.org and Jeffrey Sharkey http://jsharkey.org - Based on the Trilead SSH2 client provided - under a BSD-style license. Copyright (C) 2007 Trilead - AG. http://www.trilead.com + Based on the Ganymed SSH2 client provided under a BSD-style + license. Copyright (C) 2005 - 2006 Swiss Federal Institute of + Technology (ETH Zurich), Department of Computer Science + http://www.inf.ethz.ch, Christian Plattner. Copyright (C) 2006 - 2013 + Christian Plattner. http://code.google.com/p/ganymed-ssh-2/ The Java + implementations of the AES, Blowfish and 3DES ciphers have been taken + (and slightly modified) from the cryptography package released by "The + Legion Of The Bouncy Castle". Copyright (C) 2000 - 2004 The Legion Of + The Bouncy Castle http://www.bouncycastle.org The following disclaimer + applies: + + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. Based on JTA Telnet/SSH client provided under the