Home | History | Annotate | Download | only in auth
      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 
     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