• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2006-2011 Christian Plattner. All rights reserved.
3  * Please refer to the LICENSE.txt for licensing details.
4  */
5 package ch.ethz.ssh2.auth;
6 
7 import java.io.IOException;
8 import java.security.SecureRandom;
9 import java.util.List;
10 import java.util.Vector;
11 
12 import ch.ethz.ssh2.InteractiveCallback;
13 import ch.ethz.ssh2.crypto.PEMDecoder;
14 import ch.ethz.ssh2.packets.PacketServiceAccept;
15 import ch.ethz.ssh2.packets.PacketServiceRequest;
16 import ch.ethz.ssh2.packets.PacketUserauthBanner;
17 import ch.ethz.ssh2.packets.PacketUserauthFailure;
18 import ch.ethz.ssh2.packets.PacketUserauthInfoRequest;
19 import ch.ethz.ssh2.packets.PacketUserauthInfoResponse;
20 import ch.ethz.ssh2.packets.PacketUserauthRequestInteractive;
21 import ch.ethz.ssh2.packets.PacketUserauthRequestNone;
22 import ch.ethz.ssh2.packets.PacketUserauthRequestPassword;
23 import ch.ethz.ssh2.packets.PacketUserauthRequestPublicKey;
24 import ch.ethz.ssh2.packets.Packets;
25 import ch.ethz.ssh2.packets.TypesWriter;
26 import ch.ethz.ssh2.signature.DSAPrivateKey;
27 import ch.ethz.ssh2.signature.DSASHA1Verify;
28 import ch.ethz.ssh2.signature.DSASignature;
29 import ch.ethz.ssh2.signature.RSAPrivateKey;
30 import ch.ethz.ssh2.signature.RSASHA1Verify;
31 import ch.ethz.ssh2.signature.RSASignature;
32 import ch.ethz.ssh2.transport.MessageHandler;
33 import ch.ethz.ssh2.transport.TransportManager;
34 
35 /**
36  * AuthenticationManager.
37  *
38  * @author Christian Plattner
39  * @version 2.50, 03/15/10
40  */
41 public class AuthenticationManager implements MessageHandler
42 {
43 	private TransportManager tm;
44 
45 	private final List<byte[]> packets = new Vector<byte[]>();
46 	private boolean connectionClosed = false;
47 
48 	private String banner;
49 
50 	private String[] remainingMethods = new String[0];
51 	private boolean isPartialSuccess = false;
52 
53 	private boolean authenticated = false;
54 	private boolean initDone = false;
55 
AuthenticationManager(TransportManager tm)56 	public AuthenticationManager(TransportManager tm)
57 	{
58 		this.tm = tm;
59 	}
60 
methodPossible(String methName)61 	boolean methodPossible(String methName)
62 	{
63 		if (remainingMethods == null)
64 			return false;
65 
66 		for (int i = 0; i < remainingMethods.length; i++)
67 		{
68 			if (remainingMethods[i].compareTo(methName) == 0)
69 				return true;
70 		}
71 		return false;
72 	}
73 
deQueue()74 	byte[] deQueue() throws IOException
75 	{
76 		boolean wasInterrupted = false;
77 
78 		try
79 		{
80 			synchronized (packets)
81 			{
82 				while (packets.size() == 0)
83 				{
84 					if (connectionClosed)
85 						throw (IOException) new IOException("The connection is closed.").initCause(tm
86 								.getReasonClosedCause());
87 
88 					try
89 					{
90 						packets.wait();
91 					}
92 					catch (InterruptedException ign)
93 					{
94 						wasInterrupted = true;
95 					}
96 				}
97 				byte[] res = packets.get(0);
98 				packets.remove(0);
99 				return res;
100 			}
101 		}
102 		finally
103 		{
104 			if (wasInterrupted)
105 				Thread.currentThread().interrupt();
106 		}
107 	}
108 
getNextMessage()109 	byte[] getNextMessage() throws IOException
110 	{
111 		while (true)
112 		{
113 			byte[] msg = deQueue();
114 
115 			if (msg[0] != Packets.SSH_MSG_USERAUTH_BANNER)
116 				return msg;
117 
118 			PacketUserauthBanner sb = new PacketUserauthBanner(msg, 0, msg.length);
119 
120 			banner = sb.getBanner();
121 		}
122 	}
123 
getRemainingMethods(String user)124 	public String[] getRemainingMethods(String user) throws IOException
125 	{
126 		initialize(user);
127 		return remainingMethods;
128 	}
129 
getBanner()130 	public String getBanner()
131 	{
132 		return banner;
133 
134 	}
getPartialSuccess()135 	public boolean getPartialSuccess()
136 	{
137 		return isPartialSuccess;
138 	}
139 
initialize(String user)140 	private boolean initialize(String user) throws IOException
141 	{
142 		if (initDone == false)
143 		{
144 			tm.registerMessageHandler(this, 0, 255);
145 
146 			PacketServiceRequest sr = new PacketServiceRequest("ssh-userauth");
147 			tm.sendMessage(sr.getPayload());
148 
149 			byte[] msg = getNextMessage();
150 			new PacketServiceAccept(msg, 0, msg.length);
151 
152 			PacketUserauthRequestNone urn = new PacketUserauthRequestNone("ssh-connection", user);
153 			tm.sendMessage(urn.getPayload());
154 
155 			msg = getNextMessage();
156 
157 			initDone = true;
158 
159 			if (msg[0] == Packets.SSH_MSG_USERAUTH_SUCCESS)
160 			{
161 				authenticated = true;
162 				tm.removeMessageHandler(this, 0, 255);
163 				return true;
164 			}
165 
166 			if (msg[0] == Packets.SSH_MSG_USERAUTH_FAILURE)
167 			{
168 				PacketUserauthFailure puf = new PacketUserauthFailure(msg, 0, msg.length);
169 
170 				remainingMethods = puf.getAuthThatCanContinue();
171 				isPartialSuccess = puf.isPartialSuccess();
172 				return false;
173 			}
174 
175 			throw new IOException("Unexpected SSH message (type " + msg[0] + ")");
176 		}
177 		return authenticated;
178 	}
179 
authenticatePublicKey(String user, char[] PEMPrivateKey, String password, SecureRandom rnd)180 	public boolean authenticatePublicKey(String user, char[] PEMPrivateKey, String password, SecureRandom rnd)
181 			throws IOException
182 	{
183 		try
184 		{
185 			initialize(user);
186 
187 			if (methodPossible("publickey") == false)
188 				throw new IOException("Authentication method publickey not supported by the server at this stage.");
189 
190 			Object key = PEMDecoder.decode(PEMPrivateKey, password);
191 
192 			if (key instanceof DSAPrivateKey)
193 			{
194 				DSAPrivateKey pk = (DSAPrivateKey) key;
195 
196 				byte[] pk_enc = DSASHA1Verify.encodeSSHDSAPublicKey(pk.getPublicKey());
197 
198 				TypesWriter tw = new TypesWriter();
199 
200 				byte[] H = tm.getSessionIdentifier();
201 
202 				tw.writeString(H, 0, H.length);
203 				tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST);
204 				tw.writeString(user);
205 				tw.writeString("ssh-connection");
206 				tw.writeString("publickey");
207 				tw.writeBoolean(true);
208 				tw.writeString("ssh-dss");
209 				tw.writeString(pk_enc, 0, pk_enc.length);
210 
211 				byte[] msg = tw.getBytes();
212 
213 				DSASignature ds = DSASHA1Verify.generateSignature(msg, pk, rnd);
214 
215 				byte[] ds_enc = DSASHA1Verify.encodeSSHDSASignature(ds);
216 
217 				PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user,
218 						"ssh-dss", pk_enc, ds_enc);
219 				tm.sendMessage(ua.getPayload());
220 			}
221 			else if (key instanceof RSAPrivateKey)
222 			{
223 				RSAPrivateKey pk = (RSAPrivateKey) key;
224 
225 				byte[] pk_enc = RSASHA1Verify.encodeSSHRSAPublicKey(pk.getPublicKey());
226 
227 				TypesWriter tw = new TypesWriter();
228 				{
229 					byte[] H = tm.getSessionIdentifier();
230 
231 					tw.writeString(H, 0, H.length);
232 					tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST);
233 					tw.writeString(user);
234 					tw.writeString("ssh-connection");
235 					tw.writeString("publickey");
236 					tw.writeBoolean(true);
237 					tw.writeString("ssh-rsa");
238 					tw.writeString(pk_enc, 0, pk_enc.length);
239 				}
240 
241 				byte[] msg = tw.getBytes();
242 
243 				RSASignature ds = RSASHA1Verify.generateSignature(msg, pk);
244 
245 				byte[] rsa_sig_enc = RSASHA1Verify.encodeSSHRSASignature(ds);
246 
247 				PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user,
248 						"ssh-rsa", pk_enc, rsa_sig_enc);
249 				tm.sendMessage(ua.getPayload());
250 			}
251 			else
252 			{
253 				throw new IOException("Unknown private key type returned by the PEM decoder.");
254 			}
255 
256 			byte[] ar = getNextMessage();
257 
258 			if (ar[0] == Packets.SSH_MSG_USERAUTH_SUCCESS)
259 			{
260 				authenticated = true;
261 				tm.removeMessageHandler(this, 0, 255);
262 				return true;
263 			}
264 
265 			if (ar[0] == Packets.SSH_MSG_USERAUTH_FAILURE)
266 			{
267 				PacketUserauthFailure puf = new PacketUserauthFailure(ar, 0, ar.length);
268 
269 				remainingMethods = puf.getAuthThatCanContinue();
270 				isPartialSuccess = puf.isPartialSuccess();
271 
272 				return false;
273 			}
274 
275 			throw new IOException("Unexpected SSH message (type " + ar[0] + ")");
276 
277 		}
278 		catch (IOException e)
279 		{
280 			tm.close(e, false);
281 			throw (IOException) new IOException("Publickey authentication failed.").initCause(e);
282 		}
283 	}
284 
authenticateNone(String user)285 	public boolean authenticateNone(String user) throws IOException
286 	{
287 		try
288 		{
289 			initialize(user);
290 			return authenticated;
291 		}
292 		catch (IOException e)
293 		{
294 			tm.close(e, false);
295 			throw (IOException) new IOException("None authentication failed.").initCause(e);
296 		}
297 	}
298 
authenticatePassword(String user, String pass)299 	public boolean authenticatePassword(String user, String pass) throws IOException
300 	{
301 		try
302 		{
303 			initialize(user);
304 
305 			if (methodPossible("password") == false)
306 				throw new IOException("Authentication method password not supported by the server at this stage.");
307 
308 			PacketUserauthRequestPassword ua = new PacketUserauthRequestPassword("ssh-connection", user, pass);
309 			tm.sendMessage(ua.getPayload());
310 
311 			byte[] ar = getNextMessage();
312 
313 			if (ar[0] == Packets.SSH_MSG_USERAUTH_SUCCESS)
314 			{
315 				authenticated = true;
316 				tm.removeMessageHandler(this, 0, 255);
317 				return true;
318 			}
319 
320 			if (ar[0] == Packets.SSH_MSG_USERAUTH_FAILURE)
321 			{
322 				PacketUserauthFailure puf = new PacketUserauthFailure(ar, 0, ar.length);
323 
324 				remainingMethods = puf.getAuthThatCanContinue();
325 				isPartialSuccess = puf.isPartialSuccess();
326 
327 				return false;
328 			}
329 
330 			throw new IOException("Unexpected SSH message (type " + ar[0] + ")");
331 
332 		}
333 		catch (IOException e)
334 		{
335 			tm.close(e, false);
336 			throw (IOException) new IOException("Password authentication failed.").initCause(e);
337 		}
338 	}
339 
authenticateInteractive(String user, String[] submethods, InteractiveCallback cb)340 	public boolean authenticateInteractive(String user, String[] submethods, InteractiveCallback cb) throws IOException
341 	{
342 		try
343 		{
344 			initialize(user);
345 
346 			if (methodPossible("keyboard-interactive") == false)
347 				throw new IOException(
348 						"Authentication method keyboard-interactive not supported by the server at this stage.");
349 
350 			if (submethods == null)
351 				submethods = new String[0];
352 
353 			PacketUserauthRequestInteractive ua = new PacketUserauthRequestInteractive("ssh-connection", user,
354 					submethods);
355 
356 			tm.sendMessage(ua.getPayload());
357 
358 			while (true)
359 			{
360 				byte[] ar = getNextMessage();
361 
362 				if (ar[0] == Packets.SSH_MSG_USERAUTH_SUCCESS)
363 				{
364 					authenticated = true;
365 					tm.removeMessageHandler(this, 0, 255);
366 					return true;
367 				}
368 
369 				if (ar[0] == Packets.SSH_MSG_USERAUTH_FAILURE)
370 				{
371 					PacketUserauthFailure puf = new PacketUserauthFailure(ar, 0, ar.length);
372 
373 					remainingMethods = puf.getAuthThatCanContinue();
374 					isPartialSuccess = puf.isPartialSuccess();
375 
376 					return false;
377 				}
378 
379 				if (ar[0] == Packets.SSH_MSG_USERAUTH_INFO_REQUEST)
380 				{
381 					PacketUserauthInfoRequest pui = new PacketUserauthInfoRequest(ar, 0, ar.length);
382 
383 					String[] responses;
384 
385 					try
386 					{
387 						responses = cb.replyToChallenge(pui.getName(), pui.getInstruction(), pui.getNumPrompts(), pui
388 								.getPrompt(), pui.getEcho());
389 					}
390 					catch (Exception e)
391 					{
392 						throw (IOException) new IOException("Exception in callback.").initCause(e);
393 					}
394 
395 					if (responses == null)
396 						throw new IOException("Your callback may not return NULL!");
397 
398 					PacketUserauthInfoResponse puir = new PacketUserauthInfoResponse(responses);
399 					tm.sendMessage(puir.getPayload());
400 
401 					continue;
402 				}
403 
404 				throw new IOException("Unexpected SSH message (type " + ar[0] + ")");
405 			}
406 		}
407 		catch (IOException e)
408 		{
409 			tm.close(e, false);
410 			throw (IOException) new IOException("Keyboard-interactive authentication failed.").initCause(e);
411 		}
412 	}
413 
handleMessage(byte[] msg, int msglen)414 	public void handleMessage(byte[] msg, int msglen) throws IOException
415 	{
416 		synchronized (packets)
417 		{
418 			if (msg == null)
419 			{
420 				connectionClosed = true;
421 			}
422 			else
423 			{
424 				byte[] tmp = new byte[msglen];
425 				System.arraycopy(msg, 0, tmp, 0, msglen);
426 				packets.add(tmp);
427 			}
428 
429 			packets.notifyAll();
430 
431 			if (packets.size() > 5)
432 			{
433 				connectionClosed = true;
434 				throw new IOException("Error, peer is flooding us with authentication packets.");
435 			}
436 		}
437 	}
438 }
439