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