comparison app/src/main/java/ch/ethz/ssh2/ServerConnection.java @ 438:d29cce60f393

migrate from Eclipse to Android Studio
author Carl Byington <carl@five-ten-sg.com>
date Thu, 03 Dec 2015 11:23:55 -0800
parents src/ch/ethz/ssh2/ServerConnection.java@37f4a3b506d9
children
comparison
equal deleted inserted replaced
437:208b31032318 438:d29cce60f393
1 /*
2 * Copyright (c) 2012-2013 Christian Plattner. All rights reserved.
3 * Please refer to the LICENSE.txt for licensing details.
4 */
5
6 package ch.ethz.ssh2;
7
8 import java.io.CharArrayWriter;
9 import java.io.File;
10 import java.io.FileReader;
11 import java.io.IOException;
12 import java.net.Socket;
13 import java.util.List;
14 import java.util.ArrayList;
15
16 import ch.ethz.ssh2.crypto.CryptoWishList;
17 import ch.ethz.ssh2.crypto.PEMDecoder;
18 import ch.ethz.ssh2.server.ServerConnectionState;
19 import java.security.KeyPair;
20 import java.security.PrivateKey;
21 import java.security.interfaces.DSAPrivateKey;
22 import java.security.interfaces.ECPrivateKey;
23 import java.security.interfaces.RSAPrivateKey;
24 import ch.ethz.ssh2.transport.ServerTransportManager;
25
26 /**
27 * A server-side SSH-2 connection.
28 *
29 * @author Christian
30 *
31 */
32 public class ServerConnection {
33 /**
34 * The softwareversion presented to the SSH-2 client.
35 */
36 private String softwareversion = String.format("Ganymed_SSHD_%s", Version.getSpecification());
37
38 private final ServerConnectionState state = new ServerConnectionState(this);
39
40 /**
41 * Creates a new <code>ServerConnection</code> that will communicate
42 * with the client over the given <code>Socket</code>.
43 * <p>
44 * Note: you need to call {@link #connect()} or {@link #connect(int)} to
45 * perform the initial handshake and establish the encrypted communication.
46 *
47 * @see #connect(int)
48 *
49 * @param s The socket
50 */
51 public ServerConnection(Socket s) {
52 this(s, null, null, null);
53 }
54
55 public ServerConnection(Socket s, String softwareversion) {
56 this(s, null, null, null);
57 this.softwareversion = softwareversion;
58 }
59
60 /**
61 * Creates a new <code>ServerConnection</code> that will communicate
62 * with the client over the given <code>Socket</code>.
63 * <p>
64 * Note: you need to call {@link #connect()} or {@link #connect(int)} to
65 * perform the initial handshake and establish the encrypted communication.
66 * <p>
67 * Please read the javadoc for the {@link #connect(int)} method.
68 *
69 * @see #connect(int)
70 *
71 * @param s The socket
72 * @param dsa_key The DSA hostkey, may be <code>NULL</code>
73 * @param rsa_key The RSA hostkey, may be <code>NULL</code>
74 * @param ec_key The EC hostkey, may be <code>NULL</code>
75 */
76 public ServerConnection(Socket s, KeyPair dsa_key, KeyPair rsa_key, KeyPair ec_key) {
77 state.s = s;
78 state.softwareversion = softwareversion;
79 state.next_dsa_key = dsa_key;
80 state.next_rsa_key = rsa_key;
81 state.next_ec_key = ec_key;
82 fixCryptoWishList(state.next_cryptoWishList, state.next_dsa_key, state.next_rsa_key, state.next_ec_key);
83 }
84
85 /**
86 * Establish the connection and block until the first handshake has completed.
87 * <p>
88 * Note: this is a wrapper that calls <code>connect(0)</code> (i.e., connect with no timeout).
89 * <p>
90 * Please read the javadoc for the {@link #connect(int)} method.
91 *
92 * @see #connect(int)
93 *
94 * @throws IOException
95 */
96
97 public synchronized void connect() throws IOException {
98 connect(0);
99 }
100
101 /**
102 * Establish the connection and block until the first handshake has completed.
103 * <p>
104 * Note 1: at least one DSA, RSA or EC hostkey must be set before calling this method.
105 * <p>
106 * Note 2: You must set the callbacks for authentication ({@link #setAuthenticationCallback(ServerAuthenticationCallback)})
107 * and connection events ({@link #setServerConnectionCallback(ServerConnectionCallback)}).
108 *
109 * @see #setPEMHostKey(char[], String)
110 * @see #setPEMHostKey(File, String)
111 * @see #setRsaHostKey(RSAPrivateKey)
112 * @see #setDsaHostKey(DSAPrivateKey)
113 *
114 * @param timeout_milliseconds Timeout in milliseconds, <code>0</code> means no timeout.
115 * @throws IOException
116 */
117
118 public synchronized void connect(int timeout_milliseconds) throws IOException {
119 synchronized (state) {
120 if (state.cb_conn == null)
121 throw new IllegalStateException("The callback for connection events has not been set.");
122
123 if (state.cb_auth == null)
124 throw new IllegalStateException("The callback for authentication events has not been set.");
125
126 if (state.tm != null)
127 throw new IllegalStateException("The initial handshake has already been started.");
128
129 if ((state.next_dsa_key == null) && (state.next_rsa_key == null) && (state.next_ec_key == null))
130 throw new IllegalStateException("Neither an RSA nor a DSA nor an EC host key has been specified!");
131
132 state.tm = new ServerTransportManager(state.s);
133 }
134
135 state.tm.connect(state);
136 /* Wait until first KEX has finished */
137 state.tm.getConnectionInfo(1);
138 }
139
140 /**
141 * Retrieve the underlying socket.
142 *
143 * @return the socket that has been passed to the constructor.
144 */
145 public Socket getSocket() {
146 return state.s;
147 }
148
149 /**
150 * Force an asynchronous key re-exchange (the call does not block). The
151 * latest values set for MAC, Cipher and DH group exchange parameters will
152 * be used. If a key exchange is currently in progress, then this method has
153 * the only effect that the so far specified parameters will be used for the
154 * next (client driven) key exchange. You may call this method only after
155 * the initial key exchange has been established.
156 * <p>
157 * Note: This implementation will never start automatically a key exchange (other than the initial one)
158 * unless you or the connected SSH-2 client ask for it.
159 *
160 * @throws IOException
161 * In case of any failure behind the scenes.
162 */
163
164 public synchronized void forceKeyExchange() throws IOException {
165 synchronized (state) {
166 if (state.tm == null)
167 throw new IllegalStateException(
168 "Cannot force another key exchange, you need to start the key exchange first.");
169
170 state.tm.forceKeyExchange(state.next_cryptoWishList, null, state.next_dsa_key, state.next_rsa_key, state.next_ec_key);
171 }
172 }
173
174 /**
175 * Returns a {@link ConnectionInfo} object containing the details of
176 * the connection. May be called as soon as the first key exchange has been
177 * started. The method blocks in case the first key exchange has not been completed.
178 * <p>
179 * Note: upon return of this method, authentication may still be pending.
180 *
181 * @return A {@link ConnectionInfo} object.
182 * @throws IOException
183 * In case of any failure behind the scenes; e.g., first key exchange was aborted.
184 */
185
186 public synchronized ConnectionInfo getConnectionInfo() throws IOException {
187 synchronized (state) {
188 if (state.tm == null)
189 throw new IllegalStateException(
190 "Cannot get details of connection, you need to start the key exchange first.");
191 }
192
193 return state.tm.getConnectionInfo(1);
194 }
195
196 /**
197 * Change the current DSA hostkey. Either a DSA or RSA or EC private key must be set for a successful handshake with
198 * the client.
199 * <p>
200 * Note: You can change an existing DSA hostkey after the initial kex exchange (the new value will
201 * be used during the next server initiated key exchange), but you cannot remove (i.e., set to <code>null</code>) the
202 * current DSA key, otherwise the next key exchange may fail in case the client supports only DSA hostkeys.
203 *
204 * @param dsa_hostkey
205 */
206
207 public synchronized void setDsaHostKey(KeyPair dsa_hostkey) {
208 synchronized (state) {
209 if ((dsa_hostkey == null) && (state.next_dsa_key != null) && (state.tm != null))
210 throw new IllegalStateException("Cannot remove DSA hostkey after first key exchange.");
211
212 state.next_dsa_key = dsa_hostkey;
213 fixCryptoWishList(state.next_cryptoWishList, state.next_dsa_key, state.next_rsa_key, state.next_ec_key);
214 }
215 }
216
217 /**
218 * Change the current RSA hostkey. Either a DSA or RSA or EC private key must be set for a successful handshake with
219 * the client.
220 * <p>
221 * Note: You can change an existing RSA hostkey after the initial kex exchange (the new value will
222 * be used during the next server initiated key exchange), but you cannot remove (i.e., set to <code>null</code>) the
223 * current RSA key, otherwise the next key exchange may fail in case the client supports only RSA hostkeys.
224 *
225 * @param rsa_hostkey
226 */
227
228 public synchronized void setRsaHostKey(KeyPair rsa_hostkey) {
229 synchronized (state) {
230 if ((rsa_hostkey == null) && (state.next_rsa_key != null) && (state.tm != null))
231 throw new IllegalStateException("Cannot remove RSA hostkey after first key exchange.");
232
233 state.next_rsa_key = rsa_hostkey;
234 fixCryptoWishList(state.next_cryptoWishList, state.next_dsa_key, state.next_rsa_key, state.next_ec_key);
235 }
236 }
237
238 /**
239 * Change the current EC hostkey. Either a DSA or RSA or EC private key must be set for a successful handshake with
240 * the client.
241 * <p>
242 * Note: You can change an existing EC hostkey after the initial kex exchange (the new value will
243 * be used during the next server initiated key exchange), but you cannot remove (i.e., set to <code>null</code>) the
244 * current EC key, otherwise the next key exchange may fail in case the client supports only EC hostkeys.
245 *
246 * @param rsa_hostkey
247 */
248
249 public synchronized void setEcHostKey(KeyPair ec_hostkey) {
250 synchronized (state) {
251 if ((ec_hostkey == null) && (state.next_ec_key != null) && (state.tm != null))
252 throw new IllegalStateException("Cannot remove EC hostkey after first key exchange.");
253
254 state.next_ec_key = ec_hostkey;
255 fixCryptoWishList(state.next_cryptoWishList, state.next_dsa_key, state.next_rsa_key, state.next_ec_key);
256 }
257 }
258
259 /**
260 * Utility method that loads a PEM based hostkey (either RSA or DSA based) and
261 * calls either <code>setRsaHostKey()</code> or <code>setDsaHostKey()</code>.
262 *
263 * @param pemdata The PEM data
264 * @param password Password, may be null in case the PEM data is not password protected
265 * @throws IOException In case of any error.
266 */
267 public void setPEMHostKey(char[] pemdata, String password) throws IOException {
268 KeyPair pair = PEMDecoder.decode(pemdata, password);
269 PrivateKey key = pair.getPrivate();
270
271 if (key instanceof DSAPrivateKey) setDsaHostKey(pair);
272
273 if (key instanceof RSAPrivateKey) setRsaHostKey(pair);
274
275 if (key instanceof ECPrivateKey) setEcHostKey(pair);
276 }
277
278 /**
279 * Utility method that loads a hostkey from a PEM file (either RSA or DSA based) and
280 * calls either <code>setRsaHostKey()</code> or <code>setDsaHostKey()</code>.
281 *
282 * @param pemFile The PEM file
283 * @param password Password, may be null in case the PEM file is not password protected
284 * @throws IOException
285 */
286 public void setPEMHostKey(File pemFile, String password) throws IOException {
287 if (pemFile == null)
288 throw new IllegalArgumentException("pemfile argument is null");
289
290 char[] buff = new char[256];
291 CharArrayWriter cw = new CharArrayWriter();
292 FileReader fr = new FileReader(pemFile);
293
294 while (true) {
295 int len = fr.read(buff);
296
297 if (len < 0)
298 break;
299
300 cw.write(buff, 0, len);
301 }
302
303 fr.close();
304 setPEMHostKey(cw.toCharArray(), password);
305 }
306
307 private void fixCryptoWishList(CryptoWishList next_cryptoWishList, KeyPair next_dsa_key, KeyPair next_rsa_key, KeyPair next_ec_key) {
308 List<String> algos = new ArrayList<String>();
309
310 if (next_rsa_key != null) algos.add("ssh-rsa");
311 if (next_dsa_key != null) algos.add("ssh-dss");
312 if (next_ec_key != null) algos.add("ecdsa-sha2-nistp521");
313 if (next_ec_key != null) algos.add("ecdsa-sha2-nistp384");
314 if (next_ec_key != null) algos.add("ecdsa-sha2-nistp256");
315
316 next_cryptoWishList.serverHostKeyAlgorithms = new String[algos.size()];
317 algos.toArray(next_cryptoWishList.serverHostKeyAlgorithms);
318 }
319
320 /**
321 * Callback interface with methods that will be called upon events
322 * generated by the client (e.g., client opens a new Session which results in a <code>ServerSession</code>).
323 * <p>
324 * Note: This must be set before the first handshake.
325 *
326 * @param cb The callback implementation
327 */
328
329 public synchronized void setServerConnectionCallback(ServerConnectionCallback cb) {
330 synchronized (state) {
331 state.cb_conn = cb;
332 }
333 }
334
335 /**
336 * Callback interface with methods that will be called upon authentication events.
337 * <p>
338 * Note: This must be set before the first handshake.
339 *
340 * @param cb The callback implementation
341 */
342
343 public synchronized void setAuthenticationCallback(ServerAuthenticationCallback cb) {
344 synchronized (state) {
345 state.cb_auth = cb;
346 }
347 }
348
349 /**
350 * Close the connection to the SSH-2 server. All assigned sessions will be
351 * closed, too. Can be called at any time. Don't forget to call this once
352 * you don't need a connection anymore - otherwise the receiver thread may
353 * run forever.
354 */
355 public void close() {
356 synchronized (state) {
357 if (state.cm != null)
358 state.cm.closeAllChannels();
359
360 if (state.tm != null) {
361 state.tm.close(new Throwable("Closed due to user request."), false);
362 }
363 }
364 }
365
366 public void close(IOException t) {
367 synchronized (state) {
368 if (state.cm != null)
369 state.cm.closeAllChannels();
370
371 if (state.tm != null) {
372 state.tm.close(t, false);
373 }
374 }
375 }
376 }