[8.1.0][fmd] 调试窗增加调试中:故障诊断功能
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.zhjt.mogo_core_function_devatools.rviz">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
|
||||
<queries>
|
||||
<!-- Query for specific packages -->
|
||||
<package android:name="com.mogo.launcher.f" />
|
||||
</queries>
|
||||
<application>
|
||||
|
||||
<activity
|
||||
android:name=".ui.activity.FmdAct"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
android:exported="true"
|
||||
android:label="故障管理诊断"
|
||||
android:launchMode="singleTop"
|
||||
android:screenOrientation="landscape"
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
||||
android:windowSoftInputMode="adjustPan" />
|
||||
|
||||
<!-- 故障码数据库更新服务 -->
|
||||
<service android:name=".service.FmCodeUpdateService" />
|
||||
<service android:name=".service.FaultManagementDiagnosisService" />
|
||||
</application>
|
||||
</manifest>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,64 @@
|
||||
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<String, byte[]> retrieveIdentities();
|
||||
|
||||
/**
|
||||
* @param pair A <code>RSAPrivateKey</code>, <code>ECPrivateKey</code>, or
|
||||
* <code>DSAPrivateKey</code> containing a DSA, EC, or RSA private
|
||||
* and corresponding <code>PublicKey</code>.
|
||||
* @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 <code>RSAPrivateKey</code> or <code>DSAPrivateKey</code>
|
||||
* 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);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
|
||||
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 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 <code>((cond & ChannelCondition.CLOSED) != 0)</code>.
|
||||
*/
|
||||
int TIMEOUT = 1;
|
||||
|
||||
/**
|
||||
* The underlying SSH-2 channel, however not necessarily the whole connection,
|
||||
* has been closed. This implies <code>EOF</code>. Note that there may still
|
||||
* be unread stdout or stderr data in the local window, i.e, <code>STDOUT_DATA</code>
|
||||
* or/and <code>STDERR_DATA</code> may be set at the same time.
|
||||
*/
|
||||
int CLOSED = 2;
|
||||
|
||||
/**
|
||||
* There is stdout data available that is ready to be consumed.
|
||||
*/
|
||||
int STDOUT_DATA = 4;
|
||||
|
||||
/**
|
||||
* There is stderr data available that is ready to be consumed.
|
||||
*/
|
||||
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, <code>STDOUT_DATA</code> or/and <code>STDERR_DATA</code>
|
||||
* may be set at the same time.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
int EXIT_STATUS = 32;
|
||||
|
||||
/**
|
||||
* The exit signal of the remote process is available.
|
||||
*/
|
||||
int EXIT_SIGNAL = 64;
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,65 @@
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* The currently used compression algorithm for packets from the client to
|
||||
* the server.
|
||||
*/
|
||||
public String clientToServerCompressionAlgorithm;
|
||||
|
||||
/**
|
||||
* The currently used compression algorithm for packets from the server to
|
||||
* the client.
|
||||
*/
|
||||
public String serverToClientCompressionAlgorithm;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
package com.trilead.ssh2;
|
||||
|
||||
/**
|
||||
* A <code>ConnectionMonitor</code> 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.
|
||||
* <p>
|
||||
* This is an experimental feature.
|
||||
* <p>
|
||||
* You MUST NOT make any assumption about the thread that invokes this method.
|
||||
* <p>
|
||||
* <b>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.</b>
|
||||
*
|
||||
* @see Connection#addConnectionMonitor(ConnectionMonitor)
|
||||
*
|
||||
* @param reason Includes an indication why the socket was closed.
|
||||
*/
|
||||
void connectionLost(Throwable reason);
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
|
||||
package com.trilead.ssh2;
|
||||
|
||||
/**
|
||||
* A <code>DHGexParameters</code> object can be used to specify parameters for
|
||||
* the diffie-hellman group exchange.
|
||||
* <p>
|
||||
* Depending on which constructor is used, either the use of a
|
||||
* <code>SSH_MSG_KEX_DH_GEX_REQUEST</code> or <code>SSH_MSG_KEX_DH_GEX_REQUEST_OLD</code>
|
||||
* 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
|
||||
* <code>SSH_MSG_KEX_DH_GEX_REQUEST_OLD</code> 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
|
||||
* <code>SSH_MSG_KEX_DH_GEX_REQUEST</code> request.
|
||||
* <p>
|
||||
* Note: older OpenSSH servers don't understand this request, in which
|
||||
* case you should use the {@link #DHGexParameters(int)} constructor.
|
||||
* <p>
|
||||
* 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 <code>zero</code> 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 <code>zero</code> 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
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
|
||||
*/
|
||||
void log(int level, String className, String message);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2007 Kenny Root, Jeffrey Sharkey
|
||||
*
|
||||
* 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 Trilead 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.
|
||||
*/
|
||||
|
||||
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 <code>DynamicPortForwarder</code> 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.
|
||||
*
|
||||
*/
|
||||
public void close() {
|
||||
dat.stopWorking();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.trilead.ssh2;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This extends the {@link ServerHostKeyVerifier} interface by allowing the remote server to indicate it has multiple
|
||||
* server key algorithms available. After authentication, the {@link #getKnownKeyAlgorithmsForHost(String, int)} method
|
||||
* may be called and compared against the list of server-controller keys. If a key algorithm has been added then
|
||||
* {@link #addServerHostKey(String, int, String, byte[])} will be called. If a key algorithm has been removed, then
|
||||
* {@link #removeServerHostKey(String, int, String, byte[])} will be called.
|
||||
*
|
||||
* @author Kenny Root
|
||||
*/
|
||||
public abstract class ExtendedServerHostKeyVerifier implements ServerHostKeyVerifier {
|
||||
/**
|
||||
* Called during connection to determine which keys are known for this host.
|
||||
*
|
||||
* @param hostname the hostname used to create the {@link Connection} object
|
||||
* @param port the server's remote TCP port
|
||||
* @return list of hostkey algorithms for the given <code>hostname</code> and <code>port</code> combination
|
||||
* or {@code null} if none are known.
|
||||
*/
|
||||
public abstract List<String> getKnownKeyAlgorithmsForHost(String hostname, int port);
|
||||
|
||||
/**
|
||||
* After authentication, if the server indicates it no longer uses this key, this method will be called
|
||||
* for the app to remove its record of it.
|
||||
*
|
||||
* @param hostname the hostname used to create the {@link Connection} object
|
||||
* @param port the server's remote TCP port
|
||||
* @param serverHostKeyAlgorithm key algorithm of removed key
|
||||
* @param serverHostKey key data of removed key
|
||||
*/
|
||||
public abstract void removeServerHostKey(String hostname, int port, String serverHostKeyAlgorithm,
|
||||
byte[] serverHostKey);
|
||||
|
||||
/**
|
||||
* After authentication, if the server indicates it has another <code>keyAlgorithm</code>, this method will be
|
||||
* called for the app to add it to its record of known keys for this <code>hostname</code>.
|
||||
*
|
||||
* @param hostname the hostname used to create the {@link Connection} object
|
||||
* @param port the server's remote TCP port
|
||||
* @param keyAlgorithm SSH standard name for the key to be added
|
||||
* @param serverHostKey SSH encoding of the key data for the key to be added
|
||||
*/
|
||||
public abstract void addServerHostKey(String hostname, int port, String keyAlgorithm, byte[] serverHostKey);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.trilead.ssh2;
|
||||
|
||||
import com.trilead.ssh2.packets.PacketExtInfo;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* SSH extensions reported by the server
|
||||
*
|
||||
* https://tools.ietf.org/html/draft-ietf-curdle-ssh-ext-info-15
|
||||
*/
|
||||
public class ExtensionInfo
|
||||
{
|
||||
private final Set<String> signatureAlgorithmsAccepted;
|
||||
|
||||
/**
|
||||
* @return Signature algorithms that server will accept. If empty, this extension was absent.
|
||||
*/
|
||||
public Set<String> getSignatureAlgorithmsAccepted()
|
||||
{
|
||||
return signatureAlgorithmsAccepted;
|
||||
}
|
||||
|
||||
public static ExtensionInfo fromPacketExtInfo(PacketExtInfo packetExtInfo)
|
||||
{
|
||||
String rawAlgs = packetExtInfo.getExtNameToValue().get("server-sig-algs");
|
||||
if (rawAlgs == null)
|
||||
{
|
||||
return new ExtensionInfo(Collections.<String>emptySet());
|
||||
}
|
||||
|
||||
Set<String> algsSet = new HashSet<>();
|
||||
Collections.addAll(algsSet, rawAlgs.split(","));
|
||||
return new ExtensionInfo(algsSet);
|
||||
}
|
||||
|
||||
public static ExtensionInfo noExtInfoSeen()
|
||||
{
|
||||
return new ExtensionInfo(Collections.<String>emptySet());
|
||||
}
|
||||
|
||||
private ExtensionInfo(Set<String> signatureAlgorithmsAccepted)
|
||||
{
|
||||
this.signatureAlgorithmsAccepted = Collections.unmodifiableSet(signatureAlgorithmsAccepted);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
|
||||
package com.trilead.ssh2;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
|
||||
import com.trilead.ssh2.crypto.Base64;
|
||||
import com.trilead.ssh2.transport.ClientServerHello;
|
||||
|
||||
/**
|
||||
* A <code>HTTPProxyData</code> 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
|
||||
{
|
||||
private final String proxyHost;
|
||||
private final int proxyPort;
|
||||
private final String proxyUser;
|
||||
private final String proxyPass;
|
||||
private final String[] requestHeaderLines;
|
||||
|
||||
/**
|
||||
* Same as calling {@link #HTTPProxyData(String, int, String, String) HTTPProxyData(proxyHost, proxyPort, <code>null</code>, <code>null</code>)}
|
||||
*
|
||||
* @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, <code>null</code>, <code>null</code>, <code>null</code>)}
|
||||
*
|
||||
* @param proxyHost Proxy hostname.
|
||||
* @param proxyPort Proxy port.
|
||||
* @param proxyUser Username for basic authentication (<code>null</code> if no authentication is needed).
|
||||
* @param proxyPass Password for basic authentication (<code>null</code> 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").
|
||||
* <p>
|
||||
* Please note: if you want to use basic authentication, then both <code>proxyUser</code>
|
||||
* and <code>proxyPass</code> must be non-null.
|
||||
* <p>
|
||||
* Here is an example:
|
||||
* <p>
|
||||
* <code>
|
||||
* new HTTPProxyData("192.168.1.1", "3128", "proxyuser", "secret", new String[] {"User-Agent: TrileadBasedClient/1.0", "X-My-Proxy-Option: something"});
|
||||
* </code>
|
||||
*
|
||||
* @param proxyHost Proxy hostname.
|
||||
* @param proxyPort Proxy port.
|
||||
* @param proxyUser Username for basic authentication (<code>null</code> if no authentication is needed).
|
||||
* @param proxyPass Password for basic authentication (<code>null</code> 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 <code>null</code>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket openConnection(String hostname, int port, int connectTimeout) throws IOException {
|
||||
Socket sock = new Socket();
|
||||
|
||||
InetAddress addr = InetAddress.getByName(proxyHost);
|
||||
sock.connect(new InetSocketAddress(addr, 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 ((proxyUser != null) && (proxyPass != null))
|
||||
{
|
||||
String credentials = proxyUser + ":" + proxyPass;
|
||||
char[] encoded;
|
||||
try {
|
||||
encoded = Base64.encode(credentials.getBytes("ISO-8859-1"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
encoded = Base64.encode(credentials.getBytes());
|
||||
}
|
||||
sb.append("Proxy-Authorization: Basic ");
|
||||
sb.append(encoded);
|
||||
sb.append("\r\n");
|
||||
}
|
||||
|
||||
if (requestHeaderLines != null)
|
||||
{
|
||||
for (int i = 0; i < requestHeaderLines.length; i++)
|
||||
{
|
||||
if (requestHeaderLines[i] != null)
|
||||
{
|
||||
sb.append(requestHeaderLines[i]);
|
||||
sb.append("\r\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sb.append("\r\n");
|
||||
|
||||
OutputStream out = sock.getOutputStream();
|
||||
|
||||
try {
|
||||
out.write(sb.toString().getBytes("ISO-8859-1"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
out.write(sb.toString().getBytes());
|
||||
}
|
||||
out.flush();
|
||||
|
||||
/* Now parse the HTTP response */
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
InputStream in = sock.getInputStream();
|
||||
|
||||
int len = ClientServerHello.readLineRN(in, buffer);
|
||||
|
||||
String httpReponse;
|
||||
try {
|
||||
httpReponse = new String(buffer, 0, len, "ISO-8859-1");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
httpReponse = new String(buffer, 0, len);
|
||||
}
|
||||
|
||||
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 = 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 sock;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
|
||||
package com.trilead.ssh2;
|
||||
|
||||
/**
|
||||
* An <code>InteractiveCallback</code> 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).
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* Please refer to draft-ietf-secsh-auth-kbdinteract-XX.txt for the details.
|
||||
* <p>
|
||||
* 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 <code>numPrompts</code>) of Strings
|
||||
* @param echo
|
||||
* an array (length <code>numPrompts</code>) 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
|
||||
* <code>numPrompts</code>.
|
||||
*/
|
||||
String[] replyToChallenge(String name, String instruction, int numPrompts, String[] prompt, boolean[] echo)
|
||||
throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,894 @@
|
||||
|
||||
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.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import com.trilead.ssh2.crypto.Base64;
|
||||
import com.trilead.ssh2.crypto.keys.Ed25519PublicKey;
|
||||
import com.trilead.ssh2.signature.DSASHA1Verify;
|
||||
import com.trilead.ssh2.signature.ECDSASHA2Verify;
|
||||
import com.trilead.ssh2.signature.Ed25519Verify;
|
||||
import com.trilead.ssh2.signature.RSASHA1Verify;
|
||||
import com.trilead.ssh2.signature.RSASHA256Verify;
|
||||
import com.trilead.ssh2.signature.RSASHA512Verify;
|
||||
import com.trilead.ssh2.transport.KexManager;
|
||||
|
||||
/**
|
||||
* The <code>KnownHosts</code> class is a handy tool to verify received server hostkeys
|
||||
* based on the information in <code>known_hosts</code> files (the ones used by OpenSSH).
|
||||
* <p>
|
||||
* It offers basically an in-memory database for known_hosts entries, as well as some
|
||||
* helper functions. Entries from a <code>known_hosts</code> file can be loaded at construction time.
|
||||
* It is also possible to add more keys later (e.g., one can parse different
|
||||
* <code>known_hosts</code> files).
|
||||
* <p>
|
||||
* It is a thread safe implementation, therefore, you need only to instantiate one
|
||||
* <code>KnownHosts</code> 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;
|
||||
|
||||
protected class KnownHostsEntry
|
||||
{
|
||||
String[] patterns;
|
||||
PublicKey key;
|
||||
|
||||
KnownHostsEntry(String[] patterns, PublicKey key)
|
||||
{
|
||||
this.patterns = patterns;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "KnownHostsEntry{keyType=" + key.getAlgorithm() + "}";
|
||||
}
|
||||
}
|
||||
|
||||
protected final LinkedList<KnownHostsEntry> 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 <code>addHostkeyToFile()</code> 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 (RSASHA1Verify.ID_SSH_RSA.equals(serverHostKeyAlgorithm) ||
|
||||
RSASHA512Verify.ID_RSA_SHA_2_512.equals(serverHostKeyAlgorithm) ||
|
||||
RSASHA256Verify.ID_RSA_SHA_2_256.equals(serverHostKeyAlgorithm))
|
||||
{
|
||||
PublicKey rpk = RSASHA1Verify.get().decodePublicKey(serverHostKey);
|
||||
|
||||
synchronized (publicKeys)
|
||||
{
|
||||
publicKeys.add(new KnownHostsEntry(hostnames, rpk));
|
||||
}
|
||||
} else if (serverHostKeyAlgorithm.equals(DSASHA1Verify.ID_SSH_DSS)) {
|
||||
PublicKey dpk = DSASHA1Verify.get().decodePublicKey(serverHostKey);
|
||||
|
||||
synchronized (publicKeys)
|
||||
{
|
||||
publicKeys.add(new KnownHostsEntry(hostnames, dpk));
|
||||
}
|
||||
} else if (serverHostKeyAlgorithm.equals(ECDSASHA2Verify.ECDSASHA2NISTP256Verify.get().getKeyFormat())) {
|
||||
PublicKey epk = ECDSASHA2Verify.ECDSASHA2NISTP256Verify.get().decodePublicKey(serverHostKey);
|
||||
|
||||
synchronized (publicKeys)
|
||||
{
|
||||
publicKeys.add(new KnownHostsEntry(hostnames, epk));
|
||||
}
|
||||
} else if (serverHostKeyAlgorithm.equals(ECDSASHA2Verify.ECDSASHA2NISTP384Verify.get().getKeyFormat())) {
|
||||
PublicKey epk = ECDSASHA2Verify.ECDSASHA2NISTP384Verify.get().decodePublicKey(serverHostKey);
|
||||
|
||||
synchronized (publicKeys)
|
||||
{
|
||||
publicKeys.add(new KnownHostsEntry(hostnames, epk));
|
||||
}
|
||||
} else if (serverHostKeyAlgorithm.equals(ECDSASHA2Verify.ECDSASHA2NISTP521Verify.get().getKeyFormat())) {
|
||||
PublicKey epk = ECDSASHA2Verify.ECDSASHA2NISTP521Verify.get().decodePublicKey(serverHostKey);
|
||||
|
||||
synchronized (publicKeys)
|
||||
{
|
||||
publicKeys.add(new KnownHostsEntry(hostnames, epk));
|
||||
}
|
||||
} else if (Ed25519Verify.ED25519_ID.equals(serverHostKeyAlgorithm)) {
|
||||
PublicKey edpk = Ed25519Verify.get().decodePublicKey(serverHostKey);
|
||||
|
||||
synchronized (publicKeys)
|
||||
{
|
||||
publicKeys.add(new KnownHostsEntry(hostnames, edpk));
|
||||
}
|
||||
} else {
|
||||
throw new IOException("Unknown 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 e) {
|
||||
hmac.update(hostname.getBytes());
|
||||
}
|
||||
|
||||
return hmac.doFinal();
|
||||
}
|
||||
|
||||
private final boolean checkHashed(String entry, String hostname)
|
||||
{
|
||||
if (!entry.startsWith("|1|"))
|
||||
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<KnownHostsEntry> i = publicKeys.iterator();
|
||||
|
||||
while (i.hasNext())
|
||||
{
|
||||
KnownHostsEntry ke = i.next();
|
||||
|
||||
if (!hostnameMatches(ke.patterns, remoteHostname))
|
||||
continue;
|
||||
|
||||
boolean res = matchKeys(ke.key, remoteKey);
|
||||
|
||||
if (res)
|
||||
return HOSTKEY_IS_OK;
|
||||
|
||||
result = HOSTKEY_HAS_CHANGED;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<PublicKey> getAllKeys(String hostname)
|
||||
{
|
||||
List<PublicKey> keys = new ArrayList<>();
|
||||
|
||||
synchronized (publicKeys)
|
||||
{
|
||||
Iterator<KnownHostsEntry> i = publicKeys.iterator();
|
||||
|
||||
while (i.hasNext())
|
||||
{
|
||||
KnownHostsEntry ke = i.next();
|
||||
|
||||
if (!hostnameMatches(ke.patterns, hostname))
|
||||
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 <code>ssh-rsa</code> or <code>ssh-dss</code>)
|
||||
* an ordered list of hostkey algorithms is returned which can be passed
|
||||
* to <code>Connection.setServerHostKeyAlgorithms</code>.
|
||||
*
|
||||
* @param hostname
|
||||
* @return <code>null</code> 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[] ipAddresses;
|
||||
|
||||
try
|
||||
{
|
||||
ipAddresses = InetAddress.getAllByName(hostname);
|
||||
} catch (UnknownHostException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (InetAddress ipAddress : ipAddresses) {
|
||||
algos = recommendHostkeyAlgorithms(ipAddress.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))
|
||||
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)
|
||||
{
|
||||
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 final String[] ALGOS_FOR_RSA = new String[] {
|
||||
RSASHA512Verify.ID_RSA_SHA_2_512,
|
||||
RSASHA256Verify.ID_RSA_SHA_2_256,
|
||||
RSASHA1Verify.ID_SSH_RSA,
|
||||
};
|
||||
|
||||
private final String ALGO_FOR_DSS = DSASHA1Verify.ID_SSH_DSS;
|
||||
|
||||
private final String ALGO_FOR_EDDSA = Ed25519Verify.ED25519_ID;
|
||||
|
||||
private String[] recommendHostkeyAlgorithms(String hostname) {
|
||||
List<String> preferredAlgos = new ArrayList<>();
|
||||
|
||||
List<PublicKey> keys = getAllKeys(hostname);
|
||||
|
||||
for (PublicKey key : keys) {
|
||||
if (key instanceof RSAPublicKey) {
|
||||
preferredAlgos.addAll(Arrays.asList(ALGOS_FOR_RSA));
|
||||
} else if (key instanceof DSAPublicKey) {
|
||||
preferredAlgos.add(ALGO_FOR_DSS);
|
||||
} else if (key instanceof Ed25519PublicKey) {
|
||||
preferredAlgos.add(ALGO_FOR_EDDSA);
|
||||
} else if (key instanceof ECPublicKey) {
|
||||
preferredAlgos.add(ECDSASHA2Verify.getSshKeyType((ECPublicKey) key));
|
||||
}
|
||||
}
|
||||
|
||||
/* If we did not find anything that we know of, return null */
|
||||
if (preferredAlgos.isEmpty())
|
||||
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.
|
||||
*/
|
||||
|
||||
List<String> preferredAndOthers = new ArrayList<>();
|
||||
List<String> notPreferred = new ArrayList<>();
|
||||
for (String algo : KexManager.getDefaultServerHostkeyAlgorithmList()) {
|
||||
if (preferredAlgos.contains(algo)) {
|
||||
preferredAndOthers.add(algo);
|
||||
} else {
|
||||
notPreferred.add(algo);
|
||||
}
|
||||
}
|
||||
preferredAndOthers.addAll(notPreferred);
|
||||
return preferredAndOthers.toArray(new String[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 <code>ssh-rsa</code> or <code>ssh-dss</code>
|
||||
* @param serverHostKey the key blob
|
||||
* @return <ul>
|
||||
* <li><code>HOSTKEY_IS_OK</code>: the given hostkey matches an entry for the given hostname</li>
|
||||
* <li><code>HOSTKEY_IS_NEW</code>: no entries found for this hostname and this type of hostkey</li>
|
||||
* <li><code>HOSTKEY_HAS_CHANGED</code>: hostname is known, but with another key of the same type
|
||||
* (man-in-the-middle attack?)</li>
|
||||
* </ul>
|
||||
* @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 (RSASHA1Verify.ID_SSH_RSA.equals(serverHostKeyAlgorithm) ||
|
||||
RSASHA256Verify.ID_RSA_SHA_2_256.equals(serverHostKeyAlgorithm) ||
|
||||
RSASHA512Verify.ID_RSA_SHA_2_512.equals(serverHostKeyAlgorithm))
|
||||
{
|
||||
remoteKey = RSASHA1Verify.get().decodePublicKey(serverHostKey);
|
||||
}
|
||||
else if (DSASHA1Verify.ID_SSH_DSS.equals(serverHostKeyAlgorithm))
|
||||
{
|
||||
remoteKey = DSASHA1Verify.get().decodePublicKey(serverHostKey);
|
||||
}
|
||||
else if (ECDSASHA2Verify.ECDSASHA2NISTP256Verify.get().getKeyFormat().equals(serverHostKeyAlgorithm))
|
||||
{
|
||||
remoteKey = ECDSASHA2Verify.ECDSASHA2NISTP256Verify.get().decodePublicKey(serverHostKey);
|
||||
}
|
||||
else if (ECDSASHA2Verify.ECDSASHA2NISTP384Verify.get().getKeyFormat().equals(serverHostKeyAlgorithm))
|
||||
{
|
||||
remoteKey = ECDSASHA2Verify.ECDSASHA2NISTP384Verify.get().decodePublicKey(serverHostKey);
|
||||
}
|
||||
else if (ECDSASHA2Verify.ECDSASHA2NISTP521Verify.get().getKeyFormat().equals(serverHostKeyAlgorithm))
|
||||
{
|
||||
remoteKey = ECDSASHA2Verify.ECDSASHA2NISTP521Verify.get().decodePublicKey(serverHostKey);
|
||||
}
|
||||
else if (Ed25519Verify.ED25519_ID.equals(serverHostKeyAlgorithm))
|
||||
{
|
||||
remoteKey = Ed25519Verify.get().decodePublicKey(serverHostKey);
|
||||
}
|
||||
else
|
||||
throw new IllegalArgumentException("Unknown hostkey type " + serverHostKeyAlgorithm);
|
||||
|
||||
int result = checkKey(hostname, remoteKey);
|
||||
|
||||
if (result == HOSTKEY_IS_OK)
|
||||
return result;
|
||||
|
||||
InetAddress[] ipAddresses = null;
|
||||
|
||||
try
|
||||
{
|
||||
ipAddresses = InetAddress.getAllByName(hostname);
|
||||
}
|
||||
catch (UnknownHostException e)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
for (InetAddress ipAddress : ipAddresses) {
|
||||
int newresult = checkKey(ipAddress.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');
|
||||
}
|
||||
|
||||
try {
|
||||
raf.write(new String(entry).getBytes("ISO-8859-1"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
raf.write(new String(entry).getBytes());
|
||||
}
|
||||
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
|
||||
*/
|
||||
private static 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 (Ed25519Verify.ED25519_ID.equals(keyType))
|
||||
{
|
||||
}
|
||||
else if (keyType.startsWith(ECDSASHA2Verify.ECDSA_SHA2_PREFIX))
|
||||
{
|
||||
}
|
||||
else if (RSASHA1Verify.ID_SSH_RSA.equals(keyType))
|
||||
{
|
||||
}
|
||||
else if (DSASHA1Verify.ID_SSH_DSS.equals(keyType))
|
||||
{
|
||||
}
|
||||
else if (RSASHA256Verify.ID_RSA_SHA_2_256.equals(keyType))
|
||||
{
|
||||
}
|
||||
else if (RSASHA512Verify.ID_RSA_SHA_2_512.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
|
||||
*/
|
||||
private static 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 final 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
|
||||
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 <code>LocalPortForwarder</code> 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.
|
||||
*
|
||||
*/
|
||||
public void close() {
|
||||
lat.stopWorking();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
|
||||
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 <code>LocalStreamForwarder</code> 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 <code>InputStream</code> object.
|
||||
*/
|
||||
public InputStream getInputStream() {
|
||||
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 <code>flush</code> method of the
|
||||
* <code>OutputStream</code>. To signal EOF, please use the
|
||||
* <code>close</code> method of the <code>OutputStream</code>.
|
||||
*
|
||||
* @return An <code>OutputStream</code> object.
|
||||
*/
|
||||
public OutputStream getOutputStream() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
|
||||
package com.trilead.ssh2;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
|
||||
/**
|
||||
* An abstract 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 interface ProxyData
|
||||
{
|
||||
/**
|
||||
* Connects the socket to the given destination using the proxy method that this instance
|
||||
* represents.
|
||||
* @param hostname hostname of end host (not proxy)
|
||||
* @param port port of end host (not proxy)
|
||||
* @param connectTimeout number of seconds before giving up on connecting to end host
|
||||
* @throws IOException if the connection could not be completed
|
||||
* @return connected socket instance
|
||||
*/
|
||||
Socket openConnection(String hostname, int port, int connectTimeout) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,747 @@
|
||||
|
||||
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;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
/**
|
||||
* A very basic <code>SCPClient</code> 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.
|
||||
* <p>
|
||||
* This scp client is thread safe - you can download (and upload) different sets
|
||||
* of files concurrently without any troubles. The <code>SCPClient</code> 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";
|
||||
|
||||
try {
|
||||
os.write(cline.getBytes("ISO-8859-1"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
os.write(cline.getBytes());
|
||||
}
|
||||
|
||||
os.flush();
|
||||
|
||||
readResponse(is);
|
||||
|
||||
os.write(data, 0, data.length);
|
||||
os.write(0);
|
||||
os.flush();
|
||||
|
||||
readResponse(is);
|
||||
|
||||
try {
|
||||
os.write("E\n".getBytes("ISO-8859-1"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
os.write("E\n".getBytes());
|
||||
}
|
||||
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";
|
||||
|
||||
try {
|
||||
os.write(cline.getBytes("ISO-8859-1"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
os.write(cline.getBytes());
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
try {
|
||||
os.write("E\n".getBytes("ISO-8859-1"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
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)))
|
||||
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 new IOException("Error during SCP transfer.", 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)))
|
||||
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 new IOException("Error during SCP transfer.", 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
|
||||
* <code>OutputStream</code>. Please note that, to enable flexible usage
|
||||
* of this method, the <code>OutputStream</code> 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 new IOException("Error during SCP transfer.", 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 new IOException("Error during SCP transfer.", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (sess != null)
|
||||
sess.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
||||
|
||||
package com.trilead.ssh2;
|
||||
|
||||
/**
|
||||
* A <code>SFTPv3DirectoryEntry</code> 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* The recommended format for the longname field is as follows:<br>
|
||||
* <code>-rwxr-xr-x 1 mjos staff 348911 Mar 25 14:29 t-filexfer</code>
|
||||
*/
|
||||
public String longEntry;
|
||||
|
||||
/**
|
||||
* The attributes of this entry.
|
||||
*/
|
||||
public SFTPv3FileAttributes attributes;
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
|
||||
package com.trilead.ssh2;
|
||||
|
||||
/**
|
||||
* A <code>SFTPv3FileAttributes</code> 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. <code>NULL</code> if not present.
|
||||
*/
|
||||
public Long size = null;
|
||||
|
||||
/**
|
||||
* The UID attribute. <code>NULL</code> if not present.
|
||||
*/
|
||||
public Integer uid = null;
|
||||
|
||||
/**
|
||||
* The GID attribute. <code>NULL</code> if not present.
|
||||
*/
|
||||
public Integer gid = null;
|
||||
|
||||
/**
|
||||
* The POSIX permissions. <code>NULL</code> if not present.
|
||||
* <p>
|
||||
* Here is a list:
|
||||
* <p>
|
||||
* <pre>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
|
||||
* </pre>
|
||||
*/
|
||||
public Integer permissions = null;
|
||||
|
||||
/**
|
||||
* The ATIME attribute. Represented as seconds from Jan 1, 1970 in UTC.
|
||||
* <code>NULL</code> if not present.
|
||||
*/
|
||||
public Long atime = null;
|
||||
|
||||
/**
|
||||
* The MTIME attribute. Represented as seconds from Jan 1, 1970 in UTC.
|
||||
* <code>NULL</code> 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 <code>0177777</code>.
|
||||
*
|
||||
* @return <code>NULL</code> 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
|
||||
package com.trilead.ssh2;
|
||||
|
||||
/**
|
||||
* A <code>SFTPv3FileHandle</code>.
|
||||
*
|
||||
* @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 <code>SFTPv3Client</code> instance which created the handle.
|
||||
*
|
||||
* @return if the handle is closed.
|
||||
*/
|
||||
public boolean isClosed()
|
||||
{
|
||||
return isClosed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
|
||||
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.
|
||||
* <p>
|
||||
* 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 (<code>ssh-rsa</code> or <code>ssh-dss</code>)
|
||||
* @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 =)
|
||||
*/
|
||||
boolean verifyServerHostKey(String hostname, int port, String serverHostKeyAlgorithm, byte[] serverHostKey)
|
||||
throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,529 @@
|
||||
|
||||
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 <code>Session</code> 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 implements AutoCloseable
|
||||
{
|
||||
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
|
||||
* <code>requestPTY("dumb", 0, 0, 0, 0, null)</code>.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void requestDumbPTY() throws IOException
|
||||
{
|
||||
requestPTY("dumb", 0, 0, 0, 0, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basically just another wrapper for lazy people - identical to calling
|
||||
* <code>requestPTY(term, 0, 0, 0, 0, null)</code>.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void requestPTY(String term) throws IOException
|
||||
{
|
||||
requestPTY(term, 0, 0, 0, 0, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate a pseudo-terminal for this session.
|
||||
* <p>
|
||||
* This method may only be called before a program or shell is started in
|
||||
* this session.
|
||||
* <p>
|
||||
* Different aspects can be specified:
|
||||
* <p>
|
||||
* <ul>
|
||||
* <li>The TERM environment variable value (e.g., vt100)</li>
|
||||
* <li>The terminal's dimensions.</li>
|
||||
* <li>The encoded terminal modes.</li>
|
||||
* </ul>
|
||||
* 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
|
||||
* <code>terminal_modes</code>) 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 <code>null</code>)
|
||||
* @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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* You have to supply the name and port of your X-server.
|
||||
* <p>
|
||||
* 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)
|
||||
{
|
||||
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.
|
||||
* <p>
|
||||
* 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 <code>Session</code>. 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 <code>Session</code> (otherwise this method may
|
||||
* block, even though more data is available).
|
||||
*
|
||||
* @param timeout
|
||||
* The (non-negative) timeout in <code>ms</code>. <code>0</code> means no
|
||||
* timeout, the call may block forever.
|
||||
* @return
|
||||
* <ul>
|
||||
* <li><code>0</code> if no more data will arrive.</li>
|
||||
* <li><code>1</code> if more data is available.</li>
|
||||
* <li><code>-1</code> if a timeout occurred.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @deprecated This method has been replaced with a much more powerful wait-for-condition
|
||||
* interface and therefore acts only as a wrapper.
|
||||
*
|
||||
*/
|
||||
@Deprecated
|
||||
public int waitUntilDataAvailable(long timeout) {
|
||||
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.
|
||||
* <p>
|
||||
* This method returns as soon as one of the following happens:
|
||||
* <ul>
|
||||
* <li>at least of the specified conditions (see {@link ChannelCondition}) holds true</li>
|
||||
* <li>timeout > 0 and a timeout occured (TIMEOUT will be set in result conditions)</li>
|
||||
* <li>the underlying channel was closed (CLOSED will be set in result conditions)</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* 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}).
|
||||
* <p>
|
||||
* 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 <code>Session</code> (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, <code>0</code> 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 <code>WaitForCondition</code> method).
|
||||
*
|
||||
* @return An <code>Integer</code> holding the exit code, or
|
||||
* <code>null</code> 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 <code>String</code> holding the name of the signal, or
|
||||
* <code>null</code> 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 <code>close()</code>
|
||||
* 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
|
||||
package com.trilead.ssh2;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* A <code>StreamGobbler</code> 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.
|
||||
* <p>
|
||||
* 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).
|
||||
* <p>
|
||||
* 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:
|
||||
* <p>
|
||||
* If you do not call the StreamGobbler's <code>read()</code> 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.
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,518 @@
|
||||
|
||||
package com.trilead.ssh2.auth;
|
||||
|
||||
import com.trilead.ssh2.crypto.keys.Ed25519PrivateKey;
|
||||
import com.trilead.ssh2.crypto.keys.Ed25519PublicKey;
|
||||
import com.trilead.ssh2.signature.RSASHA256Verify;
|
||||
import com.trilead.ssh2.signature.RSASHA512Verify;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PrivateKey;
|
||||
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.Set;
|
||||
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.Ed25519Verify;
|
||||
import com.trilead.ssh2.signature.RSASHA1Verify;
|
||||
import com.trilead.ssh2.signature.SSHSignature;
|
||||
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 new IOException("The connection is closed.", 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)
|
||||
{
|
||||
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
|
||||
{
|
||||
return authenticatePublicKey(user, pair, rnd, null);
|
||||
}
|
||||
|
||||
public boolean authenticatePublicKey(String user, SignatureProxy signatureProxy)
|
||||
throws IOException
|
||||
{
|
||||
return authenticatePublicKey(user, null, null, signatureProxy);
|
||||
}
|
||||
|
||||
public boolean authenticatePublicKey(String user, KeyPair pair, SecureRandom rnd, SignatureProxy signatureProxy)
|
||||
throws IOException
|
||||
{
|
||||
PrivateKey privateKey = null;
|
||||
PublicKey publicKey = null;
|
||||
if (pair != null)
|
||||
{
|
||||
privateKey = pair.getPrivate();
|
||||
publicKey = pair.getPublic();
|
||||
}
|
||||
if (signatureProxy != null)
|
||||
{
|
||||
publicKey = signatureProxy.getPublicKey();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
initialize(user);
|
||||
|
||||
if (!methodPossible("publickey"))
|
||||
throw new IOException("Authentication method publickey not supported by the server at this stage.");
|
||||
|
||||
if (publicKey instanceof DSAPublicKey)
|
||||
{
|
||||
SSHSignature s = DSASHA1Verify.get();
|
||||
byte[] pk_enc = s.encodePublicKey(publicKey);
|
||||
|
||||
byte[] msg = this.generatePublicKeyUserAuthenticationRequest(user, DSASHA1Verify.ID_SSH_DSS, pk_enc);
|
||||
|
||||
byte[] ds_enc;
|
||||
if (signatureProxy != null)
|
||||
{
|
||||
ds_enc = signatureProxy.sign(msg, SignatureProxy.SHA1);
|
||||
}
|
||||
else
|
||||
{
|
||||
ds_enc = s.generateSignature(msg, privateKey, rnd);
|
||||
}
|
||||
|
||||
PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user,
|
||||
DSASHA1Verify.ID_SSH_DSS, pk_enc, ds_enc);
|
||||
tm.sendMessage(ua.getPayload());
|
||||
}
|
||||
else if (publicKey instanceof RSAPublicKey)
|
||||
{
|
||||
byte[] pk_enc = RSASHA1Verify.get().encodePublicKey(publicKey);
|
||||
String pk_algorithm;
|
||||
|
||||
|
||||
// Servers support different hash algorithms for RSA keys
|
||||
// https://tools.ietf.org/html/draft-ietf-curdle-rsa-sha2-12
|
||||
Set<String> algsAccepted = tm.getExtensionInfo().getSignatureAlgorithmsAccepted();
|
||||
final byte[] rsa_sig_enc;
|
||||
|
||||
if (algsAccepted.contains(RSASHA512Verify.get().getKeyFormat()))
|
||||
{
|
||||
SSHSignature s = RSASHA512Verify.get();
|
||||
pk_algorithm = s.getKeyFormat();
|
||||
byte[] msg = this.generatePublicKeyUserAuthenticationRequest(user, pk_algorithm, pk_enc);
|
||||
if (signatureProxy != null)
|
||||
{
|
||||
rsa_sig_enc = signatureProxy.sign(msg, SignatureProxy.SHA512);
|
||||
}
|
||||
else
|
||||
{
|
||||
rsa_sig_enc = s.generateSignature(msg, privateKey, rnd);
|
||||
}
|
||||
}
|
||||
else if (algsAccepted.contains(RSASHA256Verify.ID_RSA_SHA_2_256))
|
||||
{
|
||||
pk_algorithm = RSASHA256Verify.ID_RSA_SHA_2_256;
|
||||
byte[] msg = this.generatePublicKeyUserAuthenticationRequest(user, pk_algorithm, pk_enc);
|
||||
|
||||
if (signatureProxy != null)
|
||||
{
|
||||
rsa_sig_enc = signatureProxy.sign(msg, SignatureProxy.SHA256);
|
||||
}
|
||||
else
|
||||
{
|
||||
rsa_sig_enc = RSASHA256Verify.get().generateSignature(msg, privateKey, rnd);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pk_algorithm = "ssh-rsa";
|
||||
byte[] msg = this.generatePublicKeyUserAuthenticationRequest(user, pk_algorithm, pk_enc);
|
||||
if (signatureProxy != null)
|
||||
{
|
||||
rsa_sig_enc = signatureProxy.sign(msg, SignatureProxy.SHA1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Server always accepts RSA with SHA1
|
||||
rsa_sig_enc = RSASHA1Verify.get().generateSignature(msg, privateKey, rnd);
|
||||
}
|
||||
}
|
||||
|
||||
PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user,
|
||||
pk_algorithm, pk_enc, rsa_sig_enc);
|
||||
|
||||
tm.sendMessage(ua.getPayload());
|
||||
}
|
||||
else if (publicKey instanceof ECPublicKey)
|
||||
{
|
||||
ECPublicKey ecPublicKey = (ECPublicKey) publicKey;
|
||||
|
||||
ECDSASHA2Verify verifier = ECDSASHA2Verify.getVerifierForKey(ecPublicKey);
|
||||
|
||||
final String algo = verifier.getKeyFormat();
|
||||
|
||||
byte[] pk_enc = verifier.encodePublicKey(ecPublicKey);
|
||||
|
||||
byte[] msg = this.generatePublicKeyUserAuthenticationRequest(user, algo, pk_enc);
|
||||
|
||||
byte[] ec_sig_enc;
|
||||
if (signatureProxy != null)
|
||||
{
|
||||
ec_sig_enc = signatureProxy.sign(msg, ECDSASHA2Verify.getDigestAlgorithmForParams(ecPublicKey));
|
||||
}
|
||||
else
|
||||
{
|
||||
ec_sig_enc = verifier.generateSignature(msg, privateKey, rnd);
|
||||
}
|
||||
|
||||
PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user,
|
||||
algo, pk_enc, ec_sig_enc);
|
||||
|
||||
tm.sendMessage(ua.getPayload());
|
||||
}
|
||||
else if (publicKey instanceof Ed25519PublicKey)
|
||||
{
|
||||
final String algo = Ed25519Verify.ED25519_ID;
|
||||
|
||||
byte[] pk_enc = Ed25519Verify.get().encodePublicKey(publicKey);
|
||||
|
||||
byte[] msg = this.generatePublicKeyUserAuthenticationRequest(user, algo, pk_enc);
|
||||
|
||||
byte[] ed_sig_enc;
|
||||
if (signatureProxy != null)
|
||||
{
|
||||
ed_sig_enc = signatureProxy.sign(msg, SignatureProxy.SHA512);
|
||||
}
|
||||
else
|
||||
{
|
||||
Ed25519PrivateKey pk = (Ed25519PrivateKey) privateKey;
|
||||
ed_sig_enc = Ed25519Verify.get().generateSignature(msg, pk, rnd);
|
||||
}
|
||||
|
||||
PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user,
|
||||
algo, pk_enc, ed_sig_enc);
|
||||
|
||||
tm.sendMessage(ua.getPayload());
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IOException("Unknown public key type.");
|
||||
}
|
||||
|
||||
byte[] ar = getNextMessage();
|
||||
|
||||
return isAuthenticationSuccessful(ar);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
tm.close(e, false);
|
||||
throw new IOException("Publickey authentication failed.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean authenticateNone(String user) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
initialize(user);
|
||||
return authenticated;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
tm.close(e, false);
|
||||
throw new IOException("None authentication failed.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean authenticatePassword(String user, String pass) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
initialize(user);
|
||||
|
||||
if (!methodPossible("password"))
|
||||
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();
|
||||
|
||||
return isAuthenticationSuccessful(ar);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
tm.close(e, false);
|
||||
throw new IOException("Password authentication failed.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean authenticateInteractive(String user, String[] submethods, InteractiveCallback cb) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
initialize(user);
|
||||
|
||||
if (!methodPossible("keyboard-interactive"))
|
||||
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_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 new IOException("Exception in callback.", e);
|
||||
}
|
||||
|
||||
if (responses == null)
|
||||
throw new IOException("Your callback may not return NULL!");
|
||||
|
||||
PacketUserauthInfoResponse puir = new PacketUserauthInfoResponse(responses);
|
||||
tm.sendMessage(puir.getPayload());
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
return isAuthenticationSuccessful(ar);
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
tm.close(e, false);
|
||||
throw new IOException("Keyboard-interactive authentication failed.", 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAuthenticationSuccessful(byte[] ar) throws IOException
|
||||
{
|
||||
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] + ")");
|
||||
}
|
||||
|
||||
private byte[] generatePublicKeyUserAuthenticationRequest(String user, String algorithm, byte[] publicKeyEncoded) {
|
||||
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(algorithm);
|
||||
tw.writeString(publicKeyEncoded, 0, publicKeyEncoded.length);
|
||||
}
|
||||
|
||||
return tw.getBytes();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2017 Jonas Dippel, Michael Perk, Marc Totzke
|
||||
*/
|
||||
|
||||
package com.trilead.ssh2.auth;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.PublicKey;
|
||||
|
||||
public abstract class SignatureProxy
|
||||
{
|
||||
public static final String SHA1 = "SHA-1";
|
||||
public static final String SHA256 = "SHA-256";
|
||||
public static final String SHA384 = "SHA-384";
|
||||
public static final String SHA512 = "SHA-512";
|
||||
|
||||
/**
|
||||
* Holds the public key which belongs to the private key which is used in the signing process.
|
||||
*/
|
||||
private PublicKey mPublicKey;
|
||||
|
||||
/**
|
||||
* Instantiates a new SignatureProxy which needs a public key for the
|
||||
* later authentication process.
|
||||
*
|
||||
* @param publicKey The public key.
|
||||
* @throws IllegalArgumentException Might be thrown id the public key is invalid.
|
||||
*/
|
||||
public SignatureProxy(PublicKey publicKey)
|
||||
{
|
||||
if (publicKey == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Public key must not be null");
|
||||
}
|
||||
mPublicKey = publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should sign a given byte array message using the private key.
|
||||
*
|
||||
* @param message The message which should be signed.
|
||||
* @param hashAlgorithm The hashing algorithm which should be used.
|
||||
* @return The signed message.
|
||||
* @throws IOException This exception might be thrown during the signing process.
|
||||
*/
|
||||
public abstract byte[] sign(byte[] message, String hashAlgorithm) throws IOException;
|
||||
|
||||
public PublicKey getPublicKey()
|
||||
{
|
||||
return mPublicKey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,605 @@
|
||||
/*
|
||||
* Copyright 2007 Kenny Root, Jeffrey Sharkey
|
||||
*
|
||||
* 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 Trilead 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.
|
||||
*/
|
||||
|
||||
package com.trilead.ssh2.channel;
|
||||
|
||||
import com.trilead.ssh2.signature.RSASHA256Verify;
|
||||
import com.trilead.ssh2.signature.RSASHA512Verify;
|
||||
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.crypto.keys.Ed25519PrivateKey;
|
||||
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.Ed25519Verify;
|
||||
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;
|
||||
// https://tools.ietf.org/html/draft-miller-ssh-agent-02#section-7.3
|
||||
private static final int SSH_AGENT_RSA_SHA2_256 = 0x02;
|
||||
private static final int SSH_AGENT_RSA_SHA2_512 = 0x04;
|
||||
|
||||
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<String, byte[]> 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<String, byte[]> 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(DSASHA1Verify.ID_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(ECDSASHA2Verify.ECDSASHA2NISTP256Verify.get().getKeyFormat())) {
|
||||
ECDSASHA2Verify verifier = ECDSASHA2Verify.ECDSASHA2NISTP256Verify.get();
|
||||
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 params = verifier.getParameterSpec();
|
||||
ECPoint group = verifier.decodeECPoint(groupBytes);
|
||||
if (group == null) {
|
||||
// TODO log error
|
||||
os.write(SSH_AGENT_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
pubSpec = new ECPublicKeySpec(group, params);
|
||||
privSpec = new ECPrivateKeySpec(exponent, params);
|
||||
} 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 & ~SSH_AGENT_RSA_SHA2_512 & ~SSH_AGENT_RSA_SHA2_256) != 0) {
|
||||
// We don't understand these flags; abort!
|
||||
log.log(2, "Unrecognized ssh-agent flags: " + flags);
|
||||
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) {
|
||||
RSAPrivateKey rsaPrivKey = (RSAPrivateKey) privKey;
|
||||
if ((flags & SSH_AGENT_RSA_SHA2_512) != 0) {
|
||||
response = RSASHA512Verify.get().generateSignature(challenge, rsaPrivKey, new SecureRandom());
|
||||
} else if ((flags & SSH_AGENT_RSA_SHA2_256) != 0) {
|
||||
response = RSASHA256Verify.get().generateSignature(challenge, rsaPrivKey, new SecureRandom());
|
||||
} else {
|
||||
response = RSASHA1Verify.get().generateSignature(challenge, rsaPrivKey, new SecureRandom());
|
||||
}
|
||||
} else if (privKey instanceof DSAPrivateKey) {
|
||||
response = DSASHA1Verify.get().generateSignature(challenge, privKey, new SecureRandom());
|
||||
} else if (privKey instanceof Ed25519PrivateKey) {
|
||||
response = Ed25519Verify.get().generateSignature(challenge, privKey, new SecureRandom());
|
||||
} 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 message
|
||||
* @throws IOException
|
||||
*/
|
||||
private void sendPacket(byte[] message) throws IOException
|
||||
{
|
||||
TypesWriter packet = new TypesWriter();
|
||||
packet.writeUINT32(message.length);
|
||||
packet.writeBytes(message);
|
||||
os.write(packet.getBytes());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
|
||||
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() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,71 @@
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright 2007 Kenny Root, Jeffrey Sharkey
|
||||
*
|
||||
* 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 Trilead 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.
|
||||
*/
|
||||
|
||||
package com.trilead.ssh2.channel;
|
||||
|
||||
import org.connectbot.simplesocks.Socks5Server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
|
||||
/**
|
||||
* DynamicAcceptThread.
|
||||
*
|
||||
* @author Kenny Root
|
||||
* @version $Id$
|
||||
*/
|
||||
public class DynamicAcceptThread extends Thread implements IChannelWorkerThread {
|
||||
private ChannelManager cm;
|
||||
private ServerSocket ss;
|
||||
|
||||
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) {
|
||||
final Socket sock;
|
||||
try {
|
||||
sock = ss.accept();
|
||||
} catch (IOException e) {
|
||||
stopWorking();
|
||||
return;
|
||||
}
|
||||
|
||||
DynamicAcceptRunnable dar = new DynamicAcceptRunnable(sock);
|
||||
Thread t = new Thread(dar);
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopWorking() {
|
||||
try {
|
||||
/* This will lead to an IOException in the ss.accept() call */
|
||||
ss.close();
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
class DynamicAcceptRunnable implements Runnable {
|
||||
private static final int idleTimeout = 180000; //3 minutes
|
||||
|
||||
private Socket sock;
|
||||
private InputStream in;
|
||||
private OutputStream out;
|
||||
|
||||
public DynamicAcceptRunnable(Socket sock) {
|
||||
this.sock = sock;
|
||||
|
||||
setName("DynamicAcceptRunnable");
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
startSession();
|
||||
} catch (IOException ioe) {
|
||||
try {
|
||||
sock.close();
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startSession() throws IOException {
|
||||
sock.setSoTimeout(idleTimeout);
|
||||
|
||||
in = sock.getInputStream();
|
||||
out = sock.getOutputStream();
|
||||
Socks5Server server = new Socks5Server(in, out);
|
||||
try {
|
||||
if (!server.acceptAuthentication() || !server.readRequest()) {
|
||||
System.out.println("Could not start SOCKS session");
|
||||
return;
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
server.sendReply(Socks5Server.ResponseCode.GENERAL_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (server.getCommand() == Socks5Server.Command.CONNECT) {
|
||||
onConnect(server);
|
||||
} else {
|
||||
server.sendReply(Socks5Server.ResponseCode.COMMAND_NOT_SUPPORTED);
|
||||
}
|
||||
}
|
||||
|
||||
private void onConnect(Socks5Server server) throws IOException {
|
||||
final Channel cn;
|
||||
|
||||
String destHost = server.getHostName();
|
||||
if (destHost == null) {
|
||||
destHost = server.getAddress().getHostAddress();
|
||||
}
|
||||
|
||||
try {
|
||||
/*
|
||||
* This may fail, e.g., if the remote port is closed (in
|
||||
* optimistic terms: not open yet)
|
||||
*/
|
||||
|
||||
cn = cm.openDirectTCPIPChannel(destHost, server.getPort(),
|
||||
"127.0.0.1", 0);
|
||||
|
||||
} catch (IOException e) {
|
||||
/*
|
||||
* Try to send a notification back to the client and then close the socket.
|
||||
*/
|
||||
try {
|
||||
server.sendReply(Socks5Server.ResponseCode.GENERAL_FAILURE);
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
|
||||
try {
|
||||
sock.close();
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
server.sendReply(Socks5Server.ResponseCode.SUCCESS);
|
||||
|
||||
final StreamForwarder r2l = new StreamForwarder(cn, null, sock, cn.stdoutStream, out, "RemoteToLocal");
|
||||
final StreamForwarder l2r = new StreamForwarder(cn, r2l, sock, in, cn.stdinStream, "LocalToRemote");
|
||||
|
||||
r2l.setDaemon(true);
|
||||
l2r.setDaemon(true);
|
||||
r2l.start();
|
||||
l2r.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
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
|
||||
{
|
||||
void stopWorking();
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
|
||||
package com.trilead.ssh2.channel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
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)");
|
||||
|
||||
String authProtocolNameStr;
|
||||
try {
|
||||
authProtocolNameStr = new String(authProtocolName, "ISO-8859-1");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
authProtocolNameStr = new String(authProtocolName);
|
||||
}
|
||||
if (!"MIT-MAGIC-COOKIE-1".equals(authProtocolNameStr))
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
|
||||
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) {
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
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 */
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright 2007 Kenny Root, Jeffrey Sharkey
|
||||
*
|
||||
* 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 Trilead 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.
|
||||
*/
|
||||
|
||||
package com.trilead.ssh2.compression;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Kenny Root
|
||||
*
|
||||
*/
|
||||
public class CompressionFactory {
|
||||
private CompressionFactory() { }
|
||||
|
||||
private static class CompressorEntry
|
||||
{
|
||||
String type;
|
||||
String compressorClass;
|
||||
|
||||
private CompressorEntry(String type, String compressorClass)
|
||||
{
|
||||
this.type = type;
|
||||
this.compressorClass = compressorClass;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<CompressorEntry> compressors = new ArrayList<>();
|
||||
|
||||
static
|
||||
{
|
||||
/* Higher Priority First */
|
||||
compressors.add(new CompressorEntry("zlib", "com.trilead.ssh2.compression.Zlib"));
|
||||
compressors.add(new CompressorEntry("zlib@openssh.com", "com.trilead.ssh2.compression.ZlibOpenSSH"));
|
||||
compressors.add(new CompressorEntry("none", ""));
|
||||
}
|
||||
|
||||
static void addCompressor(String protocolName, String className) {
|
||||
compressors.add(new CompressorEntry(protocolName, className));
|
||||
}
|
||||
|
||||
public static String[] getDefaultCompressorList()
|
||||
{
|
||||
String[] list = new String[compressors.size()];
|
||||
for (int i = 0; i < compressors.size(); i++)
|
||||
{
|
||||
CompressorEntry ce = compressors.get(i);
|
||||
list[i] = ce.type;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public static void checkCompressorList(String[] compressorCandidates)
|
||||
{
|
||||
for (String compressorCandidate : compressorCandidates) {
|
||||
getEntry(compressorCandidate);
|
||||
}
|
||||
}
|
||||
|
||||
public static ICompressor createCompressor(String type)
|
||||
{
|
||||
try
|
||||
{
|
||||
CompressorEntry ce = getEntry(type);
|
||||
if ("".equals(ce.compressorClass))
|
||||
return null;
|
||||
|
||||
Class<?> cc = Class.forName(ce.compressorClass);
|
||||
Constructor<?> constructor = cc.getConstructor();
|
||||
return (ICompressor) constructor.newInstance();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new IllegalArgumentException("Cannot instantiate " + type);
|
||||
}
|
||||
}
|
||||
|
||||
private static CompressorEntry getEntry(String type)
|
||||
{
|
||||
for (CompressorEntry ce : compressors) {
|
||||
if (ce.type.equals(type))
|
||||
return ce;
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown algorithm " + type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2007 Kenny Root, Jeffrey Sharkey
|
||||
*
|
||||
* 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 Trilead 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.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright 2007 Kenny Root, Jeffrey Sharkey
|
||||
*
|
||||
* 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 Trilead 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.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2007 Kenny Root, Jeffrey Sharkey
|
||||
*
|
||||
* 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 Trilead 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.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
|
||||
package com.trilead.ssh2.crypto;
|
||||
|
||||
import com.trilead.ssh2.compression.CompressionFactory;
|
||||
import com.trilead.ssh2.crypto.cipher.BlockCipherFactory;
|
||||
import com.trilead.ssh2.crypto.digest.MACs;
|
||||
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 = MACs.getMacList();
|
||||
public String[] s2c_mac_algos = MACs.getMacList();
|
||||
public String[] c2s_comp_algos = CompressionFactory.getDefaultCompressorList();
|
||||
public String[] s2c_comp_algos = CompressionFactory.getDefaultCompressorList();
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,696 @@
|
||||
|
||||
package com.trilead.ssh2.crypto;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.CharArrayReader;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
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 java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
import com.trilead.ssh2.crypto.cipher.AES;
|
||||
import com.trilead.ssh2.crypto.cipher.BlockCipher;
|
||||
import com.trilead.ssh2.crypto.cipher.DES;
|
||||
import com.trilead.ssh2.crypto.cipher.DESede;
|
||||
import com.trilead.ssh2.crypto.keys.Ed25519PrivateKey;
|
||||
import com.trilead.ssh2.crypto.keys.Ed25519PublicKey;
|
||||
import com.trilead.ssh2.packets.TypesReader;
|
||||
import com.trilead.ssh2.signature.DSASHA1Verify;
|
||||
import com.trilead.ssh2.signature.ECDSASHA2Verify;
|
||||
import com.trilead.ssh2.signature.Ed25519Verify;
|
||||
import com.trilead.ssh2.signature.RSASHA1Verify;
|
||||
import org.mindrot.jbcrypt.BCrypt;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
public static final int PEM_OPENSSH_PRIVATE_KEY = 4;
|
||||
|
||||
private static final byte[] OPENSSH_V1_MAGIC = new byte[] {
|
||||
'o', 'p', 'e', 'n', 's', 's', 'h', '-', 'k', 'e', 'y', '-', 'v', '1', '\0',
|
||||
};
|
||||
|
||||
private static 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) {
|
||||
throw new IOException("could not digest password", e);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (line.startsWith("-----BEGIN OPENSSH PRIVATE KEY-----")) {
|
||||
endLine = "-----END OPENSSH PRIVATE KEY-----";
|
||||
ps.pemType = PEM_OPENSSH_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 byte[] decryptData(byte[] data, byte[] pw, byte[] salt, int rounds, String algo) throws IOException
|
||||
{
|
||||
BlockCipher bc;
|
||||
int keySize;
|
||||
|
||||
String algoLower = algo.toLowerCase(Locale.US);
|
||||
if (algoLower.equals("des-ede3-cbc"))
|
||||
{
|
||||
bc = new DESede.CBC();
|
||||
keySize = 24;
|
||||
}
|
||||
else if (algoLower.equals("des-cbc"))
|
||||
{
|
||||
bc = new DES.CBC();
|
||||
keySize = 8;
|
||||
}
|
||||
else if (algoLower.equals("aes-128-cbc") || algoLower.equals("aes128-cbc"))
|
||||
{
|
||||
bc = new AES.CBC();
|
||||
keySize = 16;
|
||||
}
|
||||
else if (algoLower.equals("aes-192-cbc") || algoLower.equals("aes192-cbc"))
|
||||
{
|
||||
bc = new AES.CBC();
|
||||
keySize = 24;
|
||||
}
|
||||
else if (algoLower.equals("aes-256-cbc") || algoLower.equals("aes256-cbc"))
|
||||
{
|
||||
bc = new AES.CBC();
|
||||
keySize = 32;
|
||||
}
|
||||
else if (algoLower.equals("aes-128-ctr") || algoLower.equals("aes128-ctr"))
|
||||
{
|
||||
bc = new AES.CTR();
|
||||
keySize = 16;
|
||||
}
|
||||
else if (algoLower.equals("aes-192-ctr") || algoLower.equals("aes192-ctr"))
|
||||
{
|
||||
bc = new AES.CTR();
|
||||
keySize = 24;
|
||||
}
|
||||
else if (algoLower.equals("aes-256-ctr") || algoLower.equals("aes256-ctr"))
|
||||
{
|
||||
bc = new AES.CTR();
|
||||
keySize = 32;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IOException("Cannot decrypt PEM structure, unknown cipher " + algo);
|
||||
}
|
||||
|
||||
if (rounds == -1)
|
||||
{
|
||||
bc.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, keySize), salt);
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] key = new byte[keySize];
|
||||
byte[] iv = new byte[bc.getBlockSize()];
|
||||
|
||||
byte[] keyAndIV = new byte[key.length + iv.length];
|
||||
|
||||
new BCrypt().pbkdf(pw, salt, rounds, keyAndIV);
|
||||
|
||||
System.arraycopy(keyAndIV, 0, key, 0, key.length);
|
||||
System.arraycopy(keyAndIV, key.length, iv, 0, iv.length);
|
||||
|
||||
bc.init(false, key, iv);
|
||||
}
|
||||
|
||||
|
||||
if ((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[data.length];
|
||||
|
||||
for (int i = 0; i < data.length / bc.getBlockSize(); i++)
|
||||
{
|
||||
bc.transformBlock(data, i * bc.getBlockSize(), dz, i * bc.getBlockSize());
|
||||
}
|
||||
|
||||
if (rounds == -1) {
|
||||
/* Now check and remove RFC 1423/PKCS #7 padding */
|
||||
return removePadding(dz, bc.getBlockSize());
|
||||
} else {
|
||||
/* New style is to check the padding after reading the comment. */
|
||||
return dz;
|
||||
}
|
||||
}
|
||||
|
||||
private static 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]);
|
||||
|
||||
byte[] dz = decryptData(ps.data, pw, salt, -1, algo);
|
||||
|
||||
ps.data = dz;
|
||||
ps.dekInfo = null;
|
||||
ps.procType = null;
|
||||
}
|
||||
|
||||
public static final boolean isPEMEncrypted(PEMStructure ps) throws IOException
|
||||
{
|
||||
if (ps.pemType == PEM_OPENSSH_PRIVATE_KEY) {
|
||||
TypesReader tr = new TypesReader(ps.data);
|
||||
byte[] magic = tr.readBytes(OPENSSH_V1_MAGIC.length);
|
||||
if (!Arrays.equals(OPENSSH_V1_MAGIC, magic)) {
|
||||
throw new IOException("Could not find OPENSSH key magic: " + new String(magic));
|
||||
}
|
||||
|
||||
tr.readString();
|
||||
String kdfname = tr.readString();
|
||||
return !"none".equals(kdfname);
|
||||
}
|
||||
|
||||
if (ps.procType == null)
|
||||
return false;
|
||||
|
||||
if (ps.procType.length != 2)
|
||||
throw new IOException("Unknown Proc-Type field.");
|
||||
|
||||
if (!"4".equals(ps.procType[0]))
|
||||
throw new IOException("Unknown Proc-Type field (" + ps.procType[0] + ")");
|
||||
|
||||
return "ENCRYPTED".equals(ps.procType[1]);
|
||||
|
||||
}
|
||||
|
||||
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) && ps.pemType != PEM_OPENSSH_PRIVATE_KEY)
|
||||
{
|
||||
if (password == null)
|
||||
throw new IOException("PEM is encrypted, but no password was specified");
|
||||
|
||||
try {
|
||||
decryptPEM(ps, password.getBytes("ISO-8859-1"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
ECDSASHA2Verify verifier = ECDSASHA2Verify.getVerifierForOID(curveOid);
|
||||
if (verifier == null)
|
||||
throw new IOException("invalid OID");
|
||||
|
||||
BigInteger s = new BigInteger(1, privateBytes);
|
||||
byte[] publicBytesSlice = new byte[publicBytes.length - 1];
|
||||
System.arraycopy(publicBytes, 1, publicBytesSlice, 0, publicBytesSlice.length);
|
||||
ECParameterSpec params = verifier.getParameterSpec();
|
||||
ECPoint w = verifier.decodeECPoint(publicBytesSlice);
|
||||
|
||||
ECPrivateKeySpec privSpec = new ECPrivateKeySpec(s, params);
|
||||
ECPublicKeySpec pubSpec = new ECPublicKeySpec(w, params);
|
||||
|
||||
return generateKeyPair("EC", privSpec, pubSpec);
|
||||
}
|
||||
|
||||
if (ps.pemType == PEM_OPENSSH_PRIVATE_KEY) {
|
||||
TypesReader tr = new TypesReader(ps.data);
|
||||
byte[] magic = tr.readBytes(OPENSSH_V1_MAGIC.length);
|
||||
if (!Arrays.equals(OPENSSH_V1_MAGIC, magic)) {
|
||||
throw new IOException("Could not find OPENSSH key magic: " + new String(magic));
|
||||
}
|
||||
|
||||
String ciphername = tr.readString();
|
||||
String kdfname = tr.readString();
|
||||
byte[] kdfoptions = tr.readByteString();
|
||||
int numberOfKeys = tr.readUINT32();
|
||||
|
||||
// TODO support multiple keys
|
||||
if (numberOfKeys != 1) {
|
||||
throw new IOException("Only one key supported, but encountered bundle of " + numberOfKeys);
|
||||
}
|
||||
|
||||
// OpenSSH discards this, so we will as well.
|
||||
tr.readByteString();
|
||||
|
||||
byte[] dataBytes = tr.readByteString();
|
||||
|
||||
if ("bcrypt".equals(kdfname)) {
|
||||
if (password == null) {
|
||||
throw new IOException("PEM is encrypted, but no password was specified");
|
||||
}
|
||||
|
||||
TypesReader optionsReader = new TypesReader(kdfoptions);
|
||||
byte[] salt = optionsReader.readByteString();
|
||||
int rounds = optionsReader.readUINT32();
|
||||
byte[] passwordBytes;
|
||||
try {
|
||||
passwordBytes = password.getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
passwordBytes = password.getBytes();
|
||||
}
|
||||
dataBytes = decryptData(dataBytes, passwordBytes, salt, rounds, ciphername);
|
||||
} else if (!"none".equals(ciphername) || !"none".equals(kdfname)) {
|
||||
throw new IOException("encryption not supported");
|
||||
}
|
||||
|
||||
TypesReader trEnc = new TypesReader(dataBytes);
|
||||
|
||||
int checkInt1 = trEnc.readUINT32();
|
||||
int checkInt2 = trEnc.readUINT32();
|
||||
|
||||
if (checkInt1 != checkInt2) {
|
||||
throw new IOException("Decryption failed when trying to read private keys");
|
||||
}
|
||||
|
||||
String keyType = trEnc.readString();
|
||||
|
||||
KeyPair keyPair;
|
||||
if (Ed25519Verify.ED25519_ID.equals(keyType)) {
|
||||
byte[] publicBytes = trEnc.readByteString();
|
||||
byte[] privateBytes = trEnc.readByteString();
|
||||
PrivateKey privKey = new Ed25519PrivateKey(
|
||||
Arrays.copyOfRange(privateBytes, 0, 32));
|
||||
PublicKey pubKey = new Ed25519PublicKey(publicBytes);
|
||||
keyPair = new KeyPair(pubKey, privKey);
|
||||
} else if (keyType.startsWith("ecdsa-sha2-")) {
|
||||
String curveName = trEnc.readString();
|
||||
|
||||
byte[] groupBytes = trEnc.readByteString();
|
||||
BigInteger privateKey = trEnc.readMPINT();
|
||||
|
||||
final ECDSASHA2Verify verifier;
|
||||
if (curveName.equals(ECDSASHA2Verify.ECDSASHA2NISTP256Verify.get().getCurveName())) {
|
||||
verifier = ECDSASHA2Verify.ECDSASHA2NISTP256Verify.get();
|
||||
} else if (curveName.equals(ECDSASHA2Verify.ECDSASHA2NISTP384Verify.get().getCurveName())) {
|
||||
verifier = ECDSASHA2Verify.ECDSASHA2NISTP384Verify.get();
|
||||
} else if (curveName.equals(ECDSASHA2Verify.ECDSASHA2NISTP521Verify.get().getCurveName())) {
|
||||
verifier = ECDSASHA2Verify.ECDSASHA2NISTP521Verify.get();
|
||||
} else {
|
||||
throw new IOException("Invalid ECDSA group");
|
||||
}
|
||||
|
||||
ECParameterSpec spec = verifier.getParameterSpec();
|
||||
ECPoint group = verifier.decodeECPoint(groupBytes);
|
||||
|
||||
ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(group, spec);
|
||||
ECPrivateKeySpec privateKeySpec = new ECPrivateKeySpec(privateKey, spec);
|
||||
keyPair = generateKeyPair("EC", privateKeySpec, publicKeySpec);
|
||||
} else if (RSASHA1Verify.get().getKeyFormat().equals(keyType)) {
|
||||
BigInteger n = trEnc.readMPINT();
|
||||
BigInteger e = trEnc.readMPINT();
|
||||
BigInteger d = trEnc.readMPINT();
|
||||
|
||||
BigInteger crtCoefficient = trEnc.readMPINT();
|
||||
BigInteger p = trEnc.readMPINT();
|
||||
|
||||
RSAPrivateKeySpec privateKeySpec;
|
||||
if (null == p || null == crtCoefficient) {
|
||||
privateKeySpec = new RSAPrivateKeySpec(n, d);
|
||||
} else {
|
||||
BigInteger q = crtCoefficient.modInverse(p);
|
||||
BigInteger pE = d.mod(p.subtract(BigInteger.ONE));
|
||||
BigInteger qE = d.mod(q.subtract(BigInteger.ONE));
|
||||
privateKeySpec = new RSAPrivateCrtKeySpec(n, e, d, p, q, pE, qE, crtCoefficient);
|
||||
|
||||
}
|
||||
|
||||
RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(n, e);
|
||||
|
||||
keyPair = generateKeyPair("RSA", privateKeySpec, publicKeySpec);
|
||||
} else if (DSASHA1Verify.get().getKeyFormat().equals(keyType)) {
|
||||
BigInteger p = trEnc.readMPINT();
|
||||
BigInteger q = trEnc.readMPINT();
|
||||
BigInteger g = trEnc.readMPINT();
|
||||
BigInteger y = trEnc.readMPINT();
|
||||
BigInteger x = trEnc.readMPINT();
|
||||
|
||||
DSAPrivateKeySpec privateKeySpec = new DSAPrivateKeySpec(x, p, q, g);
|
||||
DSAPublicKeySpec publicKeySpec = new DSAPublicKeySpec(y, p, q, g);
|
||||
|
||||
keyPair = generateKeyPair("DSA", privateKeySpec, publicKeySpec);
|
||||
} else {
|
||||
throw new IOException("Unknown key type " + keyType);
|
||||
}
|
||||
|
||||
byte[] comment = trEnc.readByteString();
|
||||
|
||||
// Make sure the padding is correct first.
|
||||
int remaining = tr.remain();
|
||||
for (int i = 1; i <= remaining; i++) {
|
||||
if (i != tr.readByte()) {
|
||||
throw new IOException("Bad padding value on decrypted private keys");
|
||||
}
|
||||
}
|
||||
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
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) {
|
||||
throw new IOException(ex);
|
||||
} catch (InvalidKeySpecException ex) {
|
||||
throw new IOException("invalid keyspec", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
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;
|
||||
}
|
||||
|
||||
/* visible for testing */
|
||||
int readLength() throws IOException
|
||||
{
|
||||
int len = readByte() & 0xff;
|
||||
|
||||
if ((len & 0x80) == 0)
|
||||
return len;
|
||||
|
||||
int remain = len & 0x7F;
|
||||
|
||||
if (remain == 0)
|
||||
return -1;
|
||||
else if (remain > 4)
|
||||
return -1;
|
||||
|
||||
len = 0;
|
||||
|
||||
while (remain > 0)
|
||||
{
|
||||
len = len << 8;
|
||||
len = len | (readByte() & 0xff);
|
||||
remain--;
|
||||
}
|
||||
|
||||
if (len < 0)
|
||||
return -1;
|
||||
|
||||
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(1, 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
|
||||
package com.trilead.ssh2.crypto.cipher;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.ShortBufferException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* AES modes for SSH using the JCE.
|
||||
*/
|
||||
public abstract class AES implements BlockCipher
|
||||
{
|
||||
private final int AES_BLOCK_SIZE = 16;
|
||||
|
||||
protected Cipher cipher;
|
||||
|
||||
@Override
|
||||
public void init(boolean forEncryption, byte[] key, byte[] iv) {
|
||||
try {
|
||||
cipher.init(forEncryption ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE,
|
||||
new SecretKeySpec(key, "AES"),
|
||||
new IvParameterSpec(iv));
|
||||
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
|
||||
throw new IllegalArgumentException("Cannot initialize " + cipher.getAlgorithm(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockSize() {
|
||||
return AES_BLOCK_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) {
|
||||
try {
|
||||
cipher.update(src, srcoff, AES_BLOCK_SIZE, dst, dstoff);
|
||||
} catch (ShortBufferException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class CBC extends AES {
|
||||
public CBC() throws IllegalArgumentException {
|
||||
try {
|
||||
cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
|
||||
throw new IllegalArgumentException("Cannot initialize AES/CBC/NoPadding", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class CTR extends AES {
|
||||
public CTR() throws IllegalArgumentException {
|
||||
try {
|
||||
cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
|
||||
throw new IllegalArgumentException("Cannot initialize AES/CBC/NoPadding", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
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
|
||||
{
|
||||
void init(boolean forEncryption, byte[] key, byte[] iv) throws IllegalArgumentException;
|
||||
|
||||
int getBlockSize();
|
||||
|
||||
void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff);
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
|
||||
package com.trilead.ssh2.crypto.cipher;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
private static class CipherEntry
|
||||
{
|
||||
final String type;
|
||||
final int blocksize;
|
||||
final int keysize;
|
||||
final String cipherClass;
|
||||
|
||||
CipherEntry(String type, int blockSize, int keySize, String cipherClass)
|
||||
{
|
||||
this.type = type;
|
||||
this.blocksize = blockSize;
|
||||
this.keysize = keySize;
|
||||
this.cipherClass = cipherClass;
|
||||
}
|
||||
}
|
||||
|
||||
private static final ArrayList<CipherEntry> ciphers = new ArrayList<>();
|
||||
|
||||
static
|
||||
{
|
||||
/* Higher Priority First */
|
||||
|
||||
ciphers.add(new CipherEntry("aes256-ctr", 16, 32, "com.trilead.ssh2.crypto.cipher.AES$CTR"));
|
||||
ciphers.add(new CipherEntry("aes128-ctr", 16, 16, "com.trilead.ssh2.crypto.cipher.AES$CTR"));
|
||||
ciphers.add(new CipherEntry("blowfish-ctr", 8, 16, "com.trilead.ssh2.crypto.cipher.BlowFish$CTR"));
|
||||
|
||||
ciphers.add(new CipherEntry("aes256-cbc", 16, 32, "com.trilead.ssh2.crypto.cipher.AES$CBC"));
|
||||
ciphers.add(new CipherEntry("aes128-cbc", 16, 16, "com.trilead.ssh2.crypto.cipher.AES$CBC"));
|
||||
ciphers.add(new CipherEntry("blowfish-cbc", 8, 16, "com.trilead.ssh2.crypto.cipher.BlowFish$CBC"));
|
||||
|
||||
ciphers.add(new CipherEntry("3des-ctr", 8, 24, "com.trilead.ssh2.crypto.cipher.DESede$CTR"));
|
||||
ciphers.add(new CipherEntry("3des-cbc", 8, 24, "com.trilead.ssh2.crypto.cipher.DESede$CBC"));
|
||||
}
|
||||
|
||||
public static String[] getDefaultCipherList()
|
||||
{
|
||||
String list[] = new String[ciphers.size()];
|
||||
for (int i = 0; i < ciphers.size(); i++)
|
||||
{
|
||||
CipherEntry ce = ciphers.get(i);
|
||||
list[i] = ce.type;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public static void checkCipherList(String[] cipherCandidates)
|
||||
{
|
||||
for (String cipherCandidate : cipherCandidates)
|
||||
getEntry(cipherCandidate);
|
||||
}
|
||||
|
||||
public static BlockCipher createCipher(String type, boolean encrypt, byte[] key, byte[] iv)
|
||||
{
|
||||
try
|
||||
{
|
||||
CipherEntry ce = getEntry(type);
|
||||
Class cc = Class.forName(ce.cipherClass);
|
||||
Constructor<BlockCipher> constructor = cc.getConstructor();
|
||||
BlockCipher bc = constructor.newInstance();
|
||||
bc.init(encrypt, key, iv);
|
||||
return bc;
|
||||
}
|
||||
catch (Exception 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("Unknown 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,435 @@
|
||||
|
||||
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.
|
||||
* @param iv
|
||||
* initial vector; not used for stream ciphers
|
||||
* @exception IllegalArgumentException
|
||||
* if the params argument is inappropriate.
|
||||
*/
|
||||
@Override
|
||||
public void init(boolean encrypting, byte[] key, byte[] iv)
|
||||
{
|
||||
this.doEncrypt = encrypting;
|
||||
this.workingKey = key;
|
||||
setKey(this.workingKey);
|
||||
}
|
||||
|
||||
public String getAlgorithmName()
|
||||
{
|
||||
return "Blowfish";
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
private abstract static class Wrapper implements BlockCipher {
|
||||
protected BlockCipher bc;
|
||||
|
||||
@Override
|
||||
public int getBlockSize() {
|
||||
return bc.getBlockSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) {
|
||||
bc.transformBlock(src, srcoff, dst, dstoff);
|
||||
}
|
||||
}
|
||||
|
||||
public static class CBC extends Wrapper {
|
||||
@Override
|
||||
public void init(boolean forEncryption, byte[] key, byte[] iv) throws IllegalArgumentException {
|
||||
BlockCipher rawCipher = new BlowFish();
|
||||
rawCipher.init(forEncryption, key, iv);
|
||||
bc = new CBCMode(rawCipher, iv, forEncryption);
|
||||
}
|
||||
}
|
||||
|
||||
public static class CTR extends Wrapper {
|
||||
@Override
|
||||
public void init(boolean forEncryption, byte[] key, byte[] iv) throws IllegalArgumentException {
|
||||
BlockCipher rawCipher = new BlowFish();
|
||||
rawCipher.init(true, key, iv);
|
||||
bc = new CTRMode(rawCipher, iv, forEncryption);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
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, byte[] iv)
|
||||
{
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
|
||||
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, byte[] iv)
|
||||
{
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
|
||||
package com.trilead.ssh2.crypto.cipher;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
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
|
||||
{
|
||||
private BlockCipher currentCipher;
|
||||
private final BufferedInputStream bi;
|
||||
private byte[] buffer;
|
||||
private byte[] enc;
|
||||
private int blockSize;
|
||||
private int pos;
|
||||
|
||||
public CipherInputStream(BlockCipher tc, InputStream bi)
|
||||
{
|
||||
if (bi instanceof BufferedInputStream) {
|
||||
this.bi = (BufferedInputStream) bi;
|
||||
} else {
|
||||
this.bi = new BufferedInputStream(bi);
|
||||
}
|
||||
changeCipher(tc);
|
||||
}
|
||||
|
||||
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 = bi.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 = bi.read(b, off + n, len - n);
|
||||
if (cnt < 0)
|
||||
throw new IOException("Cannot fill buffer, EOF reached.");
|
||||
n += cnt;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
public int peekPlain(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;
|
||||
|
||||
bi.mark(len);
|
||||
try {
|
||||
while (n < len) {
|
||||
int cnt = bi.read(b, off + n, len - n);
|
||||
if (cnt < 0)
|
||||
throw new IOException("Cannot fill buffer, EOF reached.");
|
||||
n += cnt;
|
||||
}
|
||||
} finally {
|
||||
bi.reset();
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
|
||||
package com.trilead.ssh2.crypto.cipher;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
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
|
||||
{
|
||||
private BlockCipher currentCipher;
|
||||
private final BufferedOutputStream bo;
|
||||
private byte[] buffer;
|
||||
private byte[] enc;
|
||||
private int blockSize;
|
||||
private int pos;
|
||||
private boolean recordingOutput;
|
||||
private final ByteArrayOutputStream recordingOutputStream = new ByteArrayOutputStream();
|
||||
|
||||
public CipherOutputStream(BlockCipher tc, OutputStream bo)
|
||||
{
|
||||
if (bo instanceof BufferedOutputStream) {
|
||||
this.bo = (BufferedOutputStream) bo;
|
||||
} else {
|
||||
this.bo = new BufferedOutputStream(bo);
|
||||
}
|
||||
changeCipher(tc);
|
||||
}
|
||||
|
||||
public void flush() throws IOException
|
||||
{
|
||||
if (pos != 0)
|
||||
throw new IOException("FATAL: cannot flush since crypto buffer is not aligned.");
|
||||
|
||||
bo.flush();
|
||||
}
|
||||
|
||||
public void changeCipher(BlockCipher bc)
|
||||
{
|
||||
this.currentCipher = bc;
|
||||
blockSize = bc.getBlockSize();
|
||||
buffer = new byte[blockSize];
|
||||
enc = new byte[blockSize];
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
public void startRecording() {
|
||||
recordingOutput = true;
|
||||
}
|
||||
|
||||
public byte[] getRecordedOutput() {
|
||||
recordingOutput = false;
|
||||
byte[] recordedOutput = recordingOutputStream.toByteArray();
|
||||
recordingOutputStream.reset();
|
||||
return recordedOutput;
|
||||
}
|
||||
|
||||
private void writeBlock() throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
currentCipher.transformBlock(buffer, 0, enc, 0);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new IOException("Error while decrypting block.", e);
|
||||
}
|
||||
|
||||
bo.write(enc, 0, blockSize);
|
||||
pos = 0;
|
||||
|
||||
if (recordingOutput) {
|
||||
recordingOutputStream.write(enc, 0, blockSize);
|
||||
}
|
||||
}
|
||||
|
||||
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.");
|
||||
bo.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.");
|
||||
bo.write(b, off, len);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
|
||||
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.
|
||||
*/
|
||||
@Override
|
||||
public void init(boolean encrypting, byte[] key, byte[] iv)
|
||||
{
|
||||
this.workingKey = generateWorkingKey(encrypting, key, 0);
|
||||
}
|
||||
|
||||
public String getAlgorithmName()
|
||||
{
|
||||
return "DES";
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
public static class CBC implements BlockCipher {
|
||||
protected BlockCipher bc;
|
||||
|
||||
@Override
|
||||
public void init(boolean forEncryption, byte[] key, byte[] iv) throws IllegalArgumentException {
|
||||
BlockCipher rawCipher = new DESede();
|
||||
rawCipher.init(forEncryption, key, iv);
|
||||
bc = new CBCMode(rawCipher, iv, forEncryption);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockSize() {
|
||||
return bc.getBlockSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) {
|
||||
bc.transformBlock(src, srcoff, dst, dstoff);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
|
||||
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.
|
||||
*/
|
||||
@Override
|
||||
public void init(boolean encrypting, byte[] key, byte[] iv)
|
||||
{
|
||||
key1 = generateWorkingKey(encrypting, key, 0);
|
||||
key2 = generateWorkingKey(!encrypting, key, 8);
|
||||
key3 = generateWorkingKey(encrypting, key, 16);
|
||||
|
||||
encrypt = encrypting;
|
||||
}
|
||||
|
||||
public String getAlgorithmName()
|
||||
{
|
||||
return "DESede";
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
private abstract static class Wrapper implements BlockCipher {
|
||||
protected BlockCipher bc;
|
||||
|
||||
@Override
|
||||
public int getBlockSize() {
|
||||
return bc.getBlockSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) {
|
||||
bc.transformBlock(src, srcoff, dst, dstoff);
|
||||
}
|
||||
}
|
||||
|
||||
public static class CBC extends Wrapper {
|
||||
@Override
|
||||
public void init(boolean forEncryption, byte[] key, byte[] iv) throws IllegalArgumentException {
|
||||
BlockCipher rawCipher = new DESede();
|
||||
rawCipher.init(forEncryption, key, iv);
|
||||
bc = new CBCMode(rawCipher, iv, forEncryption);
|
||||
}
|
||||
}
|
||||
|
||||
public static class CTR extends Wrapper {
|
||||
@Override
|
||||
public void init(boolean forEncryption, byte[] key, byte[] iv) throws IllegalArgumentException {
|
||||
BlockCipher rawCipher = new DESede();
|
||||
rawCipher.init(true, key, iv);
|
||||
bc = new CTRMode(rawCipher, iv, forEncryption);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.trilead.ssh2.crypto.cipher;
|
||||
|
||||
public interface EtmCipher {
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
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, byte[] iv)
|
||||
{
|
||||
}
|
||||
|
||||
public int getBlockSize()
|
||||
{
|
||||
return blockSize;
|
||||
}
|
||||
|
||||
public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff)
|
||||
{
|
||||
System.arraycopy(src, srcoff, dst, dstoff, blockSize);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.trilead.ssh2.crypto.dh;
|
||||
|
||||
import com.google.crypto.tink.subtle.X25519;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.InvalidKeyException;
|
||||
|
||||
/**
|
||||
* Created by Kenny Root on 1/23/16.
|
||||
*/
|
||||
public class Curve25519Exchange extends GenericDhExchange {
|
||||
public static final String NAME = "curve25519-sha256";
|
||||
public static final String ALT_NAME = "curve25519-sha256@libssh.org";
|
||||
public static final int KEY_SIZE = 32;
|
||||
|
||||
private byte[] clientPublic;
|
||||
private byte[] clientPrivate;
|
||||
private byte[] serverPublic;
|
||||
|
||||
public Curve25519Exchange() {
|
||||
super();
|
||||
}
|
||||
|
||||
/*
|
||||
* Used to test known vectors.
|
||||
*/
|
||||
public Curve25519Exchange(byte[] secret) throws InvalidKeyException {
|
||||
if (secret.length != KEY_SIZE) {
|
||||
throw new AssertionError("secret must be key size");
|
||||
}
|
||||
clientPrivate = secret.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(String name) throws IOException {
|
||||
if (!NAME.equals(name) && !ALT_NAME.equals(name)) {
|
||||
throw new IOException("Invalid name " + name);
|
||||
}
|
||||
|
||||
clientPrivate = X25519.generatePrivateKey();
|
||||
try {
|
||||
clientPublic = X25519.publicFromPrivate(clientPrivate);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getE() {
|
||||
return clientPublic.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] getServerE() {
|
||||
return serverPublic.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setF(byte[] f) throws IOException {
|
||||
if (f.length != KEY_SIZE) {
|
||||
throw new IOException("Server sent invalid key length " + f.length + " (expected " +
|
||||
KEY_SIZE + ")");
|
||||
}
|
||||
serverPublic = f.clone();
|
||||
try {
|
||||
byte[] sharedSecretBytes = X25519.computeSharedSecret(clientPrivate, serverPublic);
|
||||
int allBytes = 0;
|
||||
for (int i = 0; i < sharedSecretBytes.length; i++) {
|
||||
allBytes |= sharedSecretBytes[i];
|
||||
}
|
||||
if (allBytes == 0) {
|
||||
throw new IOException("Invalid key computed; all zeroes");
|
||||
}
|
||||
sharedSecret = new BigInteger(1, sharedSecretBytes);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHashAlgo() {
|
||||
return "SHA-256";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package com.trilead.ssh2.crypto.dh;
|
||||
|
||||
import javax.crypto.KeyAgreement;
|
||||
import javax.crypto.interfaces.DHPrivateKey;
|
||||
import javax.crypto.interfaces.DHPublicKey;
|
||||
import javax.crypto.spec.DHParameterSpec;
|
||||
import javax.crypto.spec.DHPublicKeySpec;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @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 P16 = new BigInteger(
|
||||
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" +
|
||||
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" +
|
||||
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" +
|
||||
"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" +
|
||||
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" +
|
||||
"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" +
|
||||
"83655D23DCA3AD961C62F356208552BB9ED529077096966D" +
|
||||
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" +
|
||||
"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" +
|
||||
"DE2BCBF6955817183995497CEA956AE515D2261898FA0510" +
|
||||
"15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" +
|
||||
"ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" +
|
||||
"ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" +
|
||||
"F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" +
|
||||
"BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" +
|
||||
"43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" +
|
||||
"88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" +
|
||||
"2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" +
|
||||
"287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" +
|
||||
"1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" +
|
||||
"93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" +
|
||||
"FFFFFFFFFFFFFFFF", 16);
|
||||
|
||||
private static final BigInteger P18 = new BigInteger(
|
||||
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" +
|
||||
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" +
|
||||
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" +
|
||||
"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" +
|
||||
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" +
|
||||
"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" +
|
||||
"83655D23DCA3AD961C62F356208552BB9ED529077096966D" +
|
||||
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" +
|
||||
"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" +
|
||||
"DE2BCBF6955817183995497CEA956AE515D2261898FA0510" +
|
||||
"15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" +
|
||||
"ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" +
|
||||
"ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" +
|
||||
"F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" +
|
||||
"BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" +
|
||||
"43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" +
|
||||
"88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" +
|
||||
"2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" +
|
||||
"287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" +
|
||||
"1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" +
|
||||
"93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" +
|
||||
"36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD" +
|
||||
"F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831" +
|
||||
"179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B" +
|
||||
"DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF" +
|
||||
"5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6" +
|
||||
"D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3" +
|
||||
"23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" +
|
||||
"CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328" +
|
||||
"06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C" +
|
||||
"DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE" +
|
||||
"12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4" +
|
||||
"38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300" +
|
||||
"741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F568" +
|
||||
"3423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9" +
|
||||
"22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B" +
|
||||
"4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A" +
|
||||
"062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A36" +
|
||||
"4597E899A0255DC164F31CC50846851DF9AB48195DED7EA1" +
|
||||
"B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92" +
|
||||
"4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E47" +
|
||||
"9558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" +
|
||||
"60C980DD98EDD3DFFFFFFFFFFFFFFFFF", 16);
|
||||
|
||||
private static final BigInteger G = BigInteger.valueOf(2);
|
||||
|
||||
/* Hash algorithm to use */
|
||||
private String hashAlgo;
|
||||
|
||||
/* 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-group18-sha512".equals(name)) {
|
||||
spec = new DHParameterSpec(P18, G);
|
||||
hashAlgo = "SHA-512";
|
||||
} else if ("diffie-hellman-group16-sha512".equals(name)) {
|
||||
spec = new DHParameterSpec(P16, G);
|
||||
hashAlgo = "SHA-512";
|
||||
} else if ("diffie-hellman-group14-sha256".equals(name)) {
|
||||
spec = new DHParameterSpec(P14, G);
|
||||
hashAlgo = "SHA-256";
|
||||
} else if ("diffie-hellman-group14-sha1".equals(name)) {
|
||||
spec = new DHParameterSpec(P14, G);
|
||||
hashAlgo = "SHA-1";
|
||||
} else if ("diffie-hellman-group1-sha1".equals(name)) {
|
||||
spec = new DHParameterSpec(P1, G);
|
||||
hashAlgo = "SHA-1";
|
||||
} 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 new IOException("No DH keypair generator", e);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
throw new IOException("Invalid DH parameters", 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(1, f), params.getP(), params.getG()));
|
||||
|
||||
ka = KeyAgreement.getInstance("DH");
|
||||
ka.init(clientPrivate);
|
||||
ka.doPhase(serverPublic, true);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IOException("No DH key agreement method", e);
|
||||
} catch (InvalidKeyException | InvalidKeySpecException e) {
|
||||
throw new IOException("Invalid DH key", e);
|
||||
}
|
||||
|
||||
sharedSecret = new BigInteger(1, ka.generateSecret());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHashAlgo() {
|
||||
return hashAlgo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
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.ECDSASHA2NISTP256Verify.get().getParameterSpec();
|
||||
} else if ("ecdh-sha2-nistp384".equals(name)) {
|
||||
spec = ECDSASHA2Verify.ECDSASHA2NISTP384Verify.get().getParameterSpec();
|
||||
} else if ("ecdh-sha2-nistp521".equals(name)) {
|
||||
spec = ECDSASHA2Verify.ECDSASHA2NISTP521Verify.get().getParameterSpec();
|
||||
} 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 new IOException("No DH keypair generator", e);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
throw new IOException("Invalid DH parameters", 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");
|
||||
ECDSASHA2Verify verifier = ECDSASHA2Verify.getVerifierForKey(clientPublic);
|
||||
if (verifier == null) {
|
||||
throw new IOException("No such EC group");
|
||||
}
|
||||
|
||||
ECPoint serverPoint = verifier.decodeECPoint(f);
|
||||
ECParameterSpec params = verifier.getParameterSpec();
|
||||
this.serverPublic = (ECPublicKey) kf.generatePublic(new ECPublicKeySpec(serverPoint,
|
||||
params));
|
||||
|
||||
ka = KeyAgreement.getInstance("ECDH");
|
||||
ka.init(clientPrivate);
|
||||
ka.doPhase(serverPublic, true);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IOException("No ECDH key agreement method", e);
|
||||
} catch (InvalidKeyException | InvalidKeySpecException e) {
|
||||
throw new IOException("Invalid ECDH key", e);
|
||||
}
|
||||
|
||||
sharedSecret = new BigInteger(1, ka.generateSecret());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHashAlgo() {
|
||||
return ECDSASHA2Verify.getDigestAlgorithmForParams(clientPublic);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
|
||||
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 (Curve25519Exchange.NAME.equals(algo) || Curve25519Exchange.ALT_NAME.equals(algo)) {
|
||||
return new Curve25519Exchange();
|
||||
}
|
||||
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) + "'");
|
||||
log.log(90, "Server: '" + new String(serverversion) + "'");
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
|
||||
package com.trilead.ssh2.crypto.digest;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.ShortBufferException;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* 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 HMAC implements MAC
|
||||
{
|
||||
private static final String ETM_SUFFIX = "-etm@openssh.com";
|
||||
|
||||
/**
|
||||
* From http://tools.ietf.org/html/rfc4253
|
||||
*/
|
||||
static final String HMAC_MD5 = "hmac-md5";
|
||||
|
||||
/**
|
||||
* From http://tools.ietf.org/html/rfc4253
|
||||
*/
|
||||
static final String HMAC_MD5_96 = "hmac-md5-96";
|
||||
|
||||
/**
|
||||
* From http://tools.ietf.org/html/rfc4253
|
||||
*/
|
||||
static final String HMAC_SHA1 = "hmac-sha1";
|
||||
|
||||
/**
|
||||
* From https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL
|
||||
*/
|
||||
static final String HMAC_SHA1_ETM = "hmac-sha1-etm@openssh.com";
|
||||
|
||||
/**
|
||||
* From http://tools.ietf.org/html/rfc4253
|
||||
*/
|
||||
static final String HMAC_SHA1_96 = "hmac-sha1-96";
|
||||
|
||||
/**
|
||||
* From https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL
|
||||
*/
|
||||
static final String HMAC_SHA2_256_ETM = "hmac-sha2-256-etm@openssh.com";
|
||||
|
||||
/**
|
||||
* From https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL
|
||||
*/
|
||||
static final String HMAC_SHA2_512_ETM = "hmac-sha2-512-etm@openssh.com";
|
||||
|
||||
/**
|
||||
* From http://tools.ietf.org/html/rfc6668
|
||||
*/
|
||||
static final String HMAC_SHA2_256 = "hmac-sha2-256";
|
||||
|
||||
/**
|
||||
* From http://tools.ietf.org/html/rfc6668
|
||||
*/
|
||||
static final String HMAC_SHA2_512 = "hmac-sha2-512";
|
||||
|
||||
private final Mac mac;
|
||||
private final int outSize;
|
||||
private final boolean encryptThenMac;
|
||||
private final byte[] buffer;
|
||||
|
||||
public HMAC(String type, byte[] key)
|
||||
{
|
||||
try {
|
||||
if (HMAC_SHA1.equals(type) || HMAC_SHA1_96.equals(type))
|
||||
{
|
||||
mac = Mac.getInstance("HmacSHA1");
|
||||
encryptThenMac = false;
|
||||
}
|
||||
else if (HMAC_SHA1_ETM.equals(type))
|
||||
{
|
||||
mac = Mac.getInstance("HmacSHA1");
|
||||
encryptThenMac = true;
|
||||
}
|
||||
else if (HMAC_MD5.equals(type) || HMAC_MD5_96.equals(type))
|
||||
{
|
||||
mac = Mac.getInstance("HmacMD5");
|
||||
encryptThenMac = false;
|
||||
}
|
||||
else if (HMAC_SHA2_256.equals(type))
|
||||
{
|
||||
mac = Mac.getInstance("HmacSHA256");
|
||||
encryptThenMac = false;
|
||||
}
|
||||
else if (HMAC_SHA2_256_ETM.equals(type))
|
||||
{
|
||||
mac = Mac.getInstance("HmacSHA256");
|
||||
encryptThenMac = true;
|
||||
}
|
||||
else if (HMAC_SHA2_512.equals(type))
|
||||
{
|
||||
mac = Mac.getInstance("HmacSHA512");
|
||||
encryptThenMac = false;
|
||||
}
|
||||
else if (HMAC_SHA2_512_ETM.equals(type))
|
||||
{
|
||||
mac = Mac.getInstance("HmacSHA512");
|
||||
encryptThenMac = true;
|
||||
}
|
||||
else
|
||||
throw new IllegalArgumentException("Unknown algorithm " + type);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException("Unknown algorithm " + type, e);
|
||||
}
|
||||
|
||||
int 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;
|
||||
}
|
||||
|
||||
public boolean isEncryptThenMac() {
|
||||
return encryptThenMac;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.trilead.ssh2.crypto.digest;
|
||||
|
||||
/**
|
||||
* Created by kenny on 2/12/17.
|
||||
*/
|
||||
public interface MAC {
|
||||
void initMac(int seq);
|
||||
void update(byte[] packetdata, int off, int len);
|
||||
void getMac(byte[] out, int off);
|
||||
int size();
|
||||
boolean isEncryptThenMac();
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
|
||||
package com.trilead.ssh2.crypto.digest;
|
||||
|
||||
/**
|
||||
* 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 MACs
|
||||
{
|
||||
/* Higher Priority First */
|
||||
private static final String[] MAC_LIST = {
|
||||
HMAC.HMAC_SHA2_256_ETM,
|
||||
HMAC.HMAC_SHA2_512_ETM,
|
||||
HMAC.HMAC_SHA1_ETM,
|
||||
HMAC.HMAC_SHA2_256,
|
||||
HMAC.HMAC_SHA2_512,
|
||||
HMAC.HMAC_SHA1,
|
||||
};
|
||||
|
||||
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 (type == null)
|
||||
throw new IllegalArgumentException("type == null");
|
||||
|
||||
if (type.startsWith(HMAC.HMAC_SHA1))
|
||||
return 20;
|
||||
if (type.startsWith(HMAC.HMAC_MD5))
|
||||
return 16;
|
||||
if (type.startsWith(HMAC.HMAC_SHA2_256))
|
||||
return 32;
|
||||
if (type.startsWith(HMAC.HMAC_SHA2_512))
|
||||
return 64;
|
||||
|
||||
throw new IllegalArgumentException("Unknown algorithm " + type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.trilead.ssh2.crypto.keys;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyFactorySpi;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
|
||||
public class Ed25519KeyFactory extends KeyFactorySpi {
|
||||
@Override
|
||||
protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecException {
|
||||
if (keySpec instanceof X509EncodedKeySpec) {
|
||||
return new Ed25519PublicKey((X509EncodedKeySpec) keySpec);
|
||||
}
|
||||
throw new InvalidKeySpecException("Unrecognized key spec: " + keySpec.getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws InvalidKeySpecException {
|
||||
if (keySpec instanceof PKCS8EncodedKeySpec) {
|
||||
return new Ed25519PrivateKey((PKCS8EncodedKeySpec) keySpec);
|
||||
}
|
||||
throw new InvalidKeySpecException("Unrecognized key spec: " + keySpec.getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpec) throws InvalidKeySpecException {
|
||||
throw new InvalidKeySpecException("not implemented yet " + key + " " + keySpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Key engineTranslateKey(Key key) throws InvalidKeyException {
|
||||
throw new InvalidKeyException("No other EdDSA key providers known");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.trilead.ssh2.crypto.keys;
|
||||
|
||||
import com.google.crypto.tink.subtle.Ed25519Sign;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGeneratorSpi;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class Ed25519KeyPairGenerator extends KeyPairGeneratorSpi {
|
||||
@Override
|
||||
public void initialize(int keySize, SecureRandom secureRandom) {
|
||||
// ignored.
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyPair generateKeyPair() {
|
||||
try {
|
||||
Ed25519Sign.KeyPair kp = Ed25519Sign.KeyPair.newKeyPair();
|
||||
return new KeyPair(new Ed25519PublicKey(kp.getPublicKey()), new Ed25519PrivateKey(kp.getPrivateKey()));
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package com.trilead.ssh2.crypto.keys;
|
||||
|
||||
import com.trilead.ssh2.packets.TypesReader;
|
||||
import com.trilead.ssh2.packets.TypesWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.security.auth.DestroyFailedException;
|
||||
|
||||
public class Ed25519PrivateKey implements PrivateKey {
|
||||
private static final byte[] ED25519_OID = new byte[] {43, 101, 112};
|
||||
private static final int KEY_BYTES_LENGTH = 32;
|
||||
private static final int ENCODED_SIZE = 48;
|
||||
|
||||
private final byte[] seed;
|
||||
private boolean destroyed;
|
||||
|
||||
public Ed25519PrivateKey(byte[] hash) {
|
||||
this.seed = hash;
|
||||
}
|
||||
|
||||
public Ed25519PrivateKey(PKCS8EncodedKeySpec keySpec) throws InvalidKeySpecException {
|
||||
this.seed = decode(keySpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof Ed25519PrivateKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Ed25519PrivateKey other = (Ed25519PrivateKey) o;
|
||||
|
||||
if (seed == null || other.seed == null || seed.length != other.seed.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int difference = 0;
|
||||
for (int i = 0; i < seed.length; i++) {
|
||||
difference |= seed[i] ^ other.seed[i];
|
||||
}
|
||||
return difference == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAlgorithm() {
|
||||
return "EdDSA";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormat() {
|
||||
return "PKCS#8";
|
||||
}
|
||||
|
||||
public byte[] getSeed() {
|
||||
return seed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEncoded() {
|
||||
// From RFC 8410 section 7 "Private Key Format"
|
||||
TypesWriter tw = new TypesWriter();
|
||||
// ASN.1 Sequence
|
||||
tw.writeByte(0x30);
|
||||
tw.writeByte(11 + ED25519_OID.length + seed.length); // Length
|
||||
// Key version type
|
||||
tw.writeByte(0x02); // ASN.1 Integer
|
||||
tw.writeByte(1); // Length
|
||||
tw.writeByte(0); // v1 == RFC 5208 format
|
||||
// Algorithm OID - ASN.1 Sequence
|
||||
tw.writeByte(0x30);
|
||||
tw.writeByte(ED25519_OID.length + 2); // OID
|
||||
tw.writeByte(0x06); // ASN.1 OID type
|
||||
tw.writeByte(ED25519_OID.length);
|
||||
tw.writeBytes(ED25519_OID);
|
||||
// Private key sequence
|
||||
tw.writeByte(0x04); // ASN.1 Octet string
|
||||
tw.writeByte(2 + seed.length);
|
||||
tw.writeByte(0x04); // ASN.1 Octet string
|
||||
tw.writeByte(seed.length);
|
||||
tw.writeBytes(seed);
|
||||
|
||||
return tw.getBytes();
|
||||
}
|
||||
|
||||
private static byte[] decode(PKCS8EncodedKeySpec keySpec) throws InvalidKeySpecException {
|
||||
byte[] encoded = keySpec.getEncoded();
|
||||
if (encoded.length != ENCODED_SIZE) {
|
||||
throw new InvalidKeySpecException("Key spec is of invalid size");
|
||||
}
|
||||
try {
|
||||
TypesReader tr = new TypesReader(keySpec.getEncoded());
|
||||
if (tr.readByte() != 0x30 || // ASN.1 sequence
|
||||
tr.readByte() != ENCODED_SIZE - 2 || // Expected size
|
||||
tr.readByte() != 0x02 || // ASN.1 Integer
|
||||
tr.readByte() != 1 || // length
|
||||
tr.readByte() != 0 || // v1
|
||||
tr.readByte() != 0x30 || // ASN.1 Sequence
|
||||
tr.readByte() != ED25519_OID.length + 2 || // OID length
|
||||
tr.readByte() != 0x06 || // ASN.1 OID
|
||||
tr.readByte() != ED25519_OID.length) {
|
||||
throw new InvalidKeySpecException("Key was not encoded correctly");
|
||||
}
|
||||
byte[] oid = tr.readBytes(ED25519_OID.length);
|
||||
if (!Arrays.equals(ED25519_OID, oid) ||
|
||||
tr.readByte() != 0x04 || // ASN.1 octet string
|
||||
tr.readByte() != KEY_BYTES_LENGTH + 2 || // length
|
||||
tr.readByte() != 0x04 || // ASN.1 octet string
|
||||
tr.readByte() != KEY_BYTES_LENGTH) {
|
||||
throw new InvalidKeySpecException("Key was not encoded correctly");
|
||||
}
|
||||
return tr.readBytes(KEY_BYTES_LENGTH);
|
||||
} catch (IOException e) {
|
||||
throw new InvalidKeySpecException("Key was not encoded correctly", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws DestroyFailedException {
|
||||
Arrays.fill(seed, (byte) 0);
|
||||
destroyed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDestroyed() {
|
||||
return destroyed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.trilead.ssh2.crypto.keys;
|
||||
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.security.Provider;
|
||||
import java.security.Security;
|
||||
|
||||
public class Ed25519Provider extends Provider {
|
||||
public static final String KEY_ALGORITHM = "Ed25519";
|
||||
private static final Object sInitLock = new Object();
|
||||
private static boolean sInitialized = false;
|
||||
|
||||
public Ed25519Provider() {
|
||||
super("ConnectBot Ed25519 Provider", 1.0, "Not for use elsewhere");
|
||||
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
|
||||
setup();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
protected void setup() {
|
||||
put("KeyFactory." + KEY_ALGORITHM, getClass().getPackage().getName() + ".Ed25519KeyFactory");
|
||||
put("KeyPairGenerator." + KEY_ALGORITHM, getClass().getPackage().getName() + ".Ed25519KeyPairGenerator");
|
||||
|
||||
// id-Ed25519 OBJECT IDENTIFIER ::= { 1 3 101 112 }
|
||||
put("Alg.Alias.KeyFactory.1.3.101.112", KEY_ALGORITHM);
|
||||
put("Alg.Alias.KeyFactory.EdDSA", KEY_ALGORITHM);
|
||||
put("Alg.Alias.KeyFactory.OID.1.3.101.112", KEY_ALGORITHM);
|
||||
put("Alg.Alias.KeyPairGenerator.1.3.101.112", KEY_ALGORITHM);
|
||||
put("Alg.Alias.KeyPairGenerator.EdDSA", KEY_ALGORITHM);
|
||||
put("Alg.Alias.KeyPairGenerator.OID.1.3.101.112", KEY_ALGORITHM);
|
||||
}
|
||||
|
||||
public static void insertIfNeeded() {
|
||||
synchronized (sInitLock) {
|
||||
if (!sInitialized) {
|
||||
Security.addProvider(new Ed25519Provider());
|
||||
sInitialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.trilead.ssh2.crypto.keys;
|
||||
|
||||
import com.trilead.ssh2.packets.TypesReader;
|
||||
import com.trilead.ssh2.packets.TypesWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Ed25519PublicKey implements PublicKey {
|
||||
private static final byte[] ED25519_OID = new byte[]{43, 101, 112};
|
||||
private static final int KEY_BYTES_LENGTH = 32;
|
||||
private static final int ENCODED_SIZE = 44;
|
||||
|
||||
private final byte[] keyBytes;
|
||||
|
||||
public Ed25519PublicKey(byte[] keyBytes) {
|
||||
this.keyBytes = keyBytes;
|
||||
}
|
||||
|
||||
public Ed25519PublicKey(X509EncodedKeySpec keySpec) throws InvalidKeySpecException {
|
||||
keyBytes = decode(keySpec.getEncoded());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAlgorithm() {
|
||||
return "EdDSA";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormat() {
|
||||
return "X.509";
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEncoded() {
|
||||
TypesWriter tw = new TypesWriter();
|
||||
tw.writeByte(0x30); // ASN.1 sequence
|
||||
tw.writeByte(7 + ED25519_OID.length + keyBytes.length);
|
||||
// Algorithm identifier
|
||||
tw.writeByte(0x30); // ASN.1 sequence
|
||||
tw.writeByte(2 + ED25519_OID.length);
|
||||
tw.writeByte(0x06); // ASN.1 OID
|
||||
tw.writeByte(ED25519_OID.length);
|
||||
tw.writeBytes(ED25519_OID);
|
||||
// Public key
|
||||
tw.writeByte(0x03); // ASN.1 bit string
|
||||
tw.writeByte(keyBytes.length + 1);
|
||||
tw.writeByte(0);
|
||||
tw.writeBytes(keyBytes);
|
||||
return tw.getBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(keyBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof Ed25519PublicKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Ed25519PublicKey other = (Ed25519PublicKey) o;
|
||||
if (keyBytes == null || other.keyBytes == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Arrays.equals(keyBytes, other.keyBytes);
|
||||
}
|
||||
|
||||
private static byte[] decode(byte[] input) throws InvalidKeySpecException {
|
||||
if (input.length != ENCODED_SIZE) {
|
||||
throw new InvalidKeySpecException("Key is not of correct size");
|
||||
}
|
||||
|
||||
try {
|
||||
TypesReader tr = new TypesReader(input);
|
||||
if (tr.readByte() != 0x30 ||
|
||||
tr.readByte() != 7 + ED25519_OID.length + KEY_BYTES_LENGTH ||
|
||||
tr.readByte() != 0x30 ||
|
||||
tr.readByte() != 2 + ED25519_OID.length ||
|
||||
tr.readByte() != 0x06 ||
|
||||
tr.readByte() != ED25519_OID.length) {
|
||||
throw new InvalidKeySpecException("Key was not encoded correctly");
|
||||
}
|
||||
byte[] oid = tr.readBytes(ED25519_OID.length);
|
||||
if (!Arrays.equals(oid, ED25519_OID) ||
|
||||
tr.readByte() != 0x03 ||
|
||||
tr.readByte() != KEY_BYTES_LENGTH + 1 ||
|
||||
tr.readByte() != 0) {
|
||||
throw new InvalidKeySpecException("Key was not encoded correctly");
|
||||
}
|
||||
return tr.readBytes(KEY_BYTES_LENGTH);
|
||||
} catch (IOException e) {
|
||||
throw new InvalidKeySpecException("Key was not encoded correctly");
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getAbyte() {
|
||||
return keyBytes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
|
||||
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.
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.trilead.ssh2.packets;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* Packet format described here:
|
||||
* https://tools.ietf.org/html/draft-ietf-curdle-ssh-ext-info-15#section-2.3
|
||||
*/
|
||||
public class PacketExtInfo
|
||||
{
|
||||
private byte[] payload;
|
||||
|
||||
private final Map<String, String> extNameToValue;
|
||||
|
||||
public byte[] getPayload()
|
||||
{
|
||||
if (payload == null)
|
||||
{
|
||||
TypesWriter tw = new TypesWriter();
|
||||
tw.writeByte(Packets.SSH_MSG_EXT_INFO);
|
||||
tw.writeUINT32(extNameToValue.size());
|
||||
for (Entry<String, String> nameAndValue : extNameToValue.entrySet())
|
||||
{
|
||||
tw.writeString(nameAndValue.getKey());
|
||||
tw.writeString(nameAndValue.getValue());
|
||||
}
|
||||
payload = tw.getBytes();
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
public Map<String, String> getExtNameToValue()
|
||||
{
|
||||
return extNameToValue;
|
||||
}
|
||||
|
||||
public PacketExtInfo(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_EXT_INFO)
|
||||
{
|
||||
throw new IOException("This is not a SSH_MSG_EXT_INFO! ("
|
||||
+ packet_type + ")");
|
||||
}
|
||||
|
||||
// Type has dynamic number of fields
|
||||
// First int tells us how many pairs to expect
|
||||
int numExtensions = tr.readUINT32();
|
||||
Map<String, String> extNameToValue_ = new HashMap<>(numExtensions);
|
||||
for (int i = 0; i < numExtensions; i++)
|
||||
{
|
||||
String name = tr.readString();
|
||||
String value = tr.readString();
|
||||
extNameToValue_.put(name, value);
|
||||
}
|
||||
extNameToValue = Collections.unmodifiableMap(extNameToValue_);
|
||||
|
||||
if (tr.remain() != 0)
|
||||
{
|
||||
throw new IOException("Padding in SSH_MSG_EXT_INFO packet!");
|
||||
}
|
||||
}
|
||||
|
||||
public PacketExtInfo(Map<String, String> extNameToValue)
|
||||
{
|
||||
this.extNameToValue = Collections.unmodifiableMap(new HashMap<>(extNameToValue));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user