1/*
2 * Copyright (c) 2006-2011 Christian Plattner. All rights reserved.
3 * Please refer to the LICENSE.txt for licensing details.
4 */
5package ch.ethz.ssh2.auth;
6
7import java.io.IOException;
8import java.security.SecureRandom;
9import java.util.List;
10import java.util.Vector;
11
12import ch.ethz.ssh2.InteractiveCallback;
13import ch.ethz.ssh2.crypto.PEMDecoder;
14import ch.ethz.ssh2.packets.PacketServiceAccept;
15import ch.ethz.ssh2.packets.PacketServiceRequest;
16import ch.ethz.ssh2.packets.PacketUserauthBanner;
17import ch.ethz.ssh2.packets.PacketUserauthFailure;
18import ch.ethz.ssh2.packets.PacketUserauthInfoRequest;
19import ch.ethz.ssh2.packets.PacketUserauthInfoResponse;
20import ch.ethz.ssh2.packets.PacketUserauthRequestInteractive;
21import ch.ethz.ssh2.packets.PacketUserauthRequestNone;
22import ch.ethz.ssh2.packets.PacketUserauthRequestPassword;
23import ch.ethz.ssh2.packets.PacketUserauthRequestPublicKey;
24import ch.ethz.ssh2.packets.Packets;
25import ch.ethz.ssh2.packets.TypesWriter;
26import ch.ethz.ssh2.signature.DSAPrivateKey;
27import ch.ethz.ssh2.signature.DSASHA1Verify;
28import ch.ethz.ssh2.signature.DSASignature;
29import ch.ethz.ssh2.signature.RSAPrivateKey;
30import ch.ethz.ssh2.signature.RSASHA1Verify;
31import ch.ethz.ssh2.signature.RSASignature;
32import ch.ethz.ssh2.transport.MessageHandler;
33import ch.ethz.ssh2.transport.TransportManager;
34
35/**
36 * AuthenticationManager.
37 *
38 * @author Christian Plattner
39 * @version 2.50, 03/15/10
40 */
41public 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
56	public AuthenticationManager(TransportManager tm)
57	{
58		this.tm = tm;
59	}
60
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
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
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
124	public String[] getRemainingMethods(String user) throws IOException
125	{
126		initialize(user);
127		return remainingMethods;
128	}
129
130	public String getBanner()
131	{
132		return banner;
133
134	}
135	public boolean getPartialSuccess()
136	{
137		return isPartialSuccess;
138	}
139
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
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
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
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
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
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