Mercurial > 510Connectbot
diff src/ch/ethz/ssh2/auth/AuthenticationManager.java @ 342:175c7d68f3c4
merge ganymed into mainline
author | Carl Byington <carl@five-ten-sg.com> |
---|---|
date | Thu, 31 Jul 2014 16:33:38 -0700 |
parents | a1a2e33b3565 |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ch/ethz/ssh2/auth/AuthenticationManager.java Thu Jul 31 16:33:38 2014 -0700 @@ -0,0 +1,424 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.auth; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.SecureRandom; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +import ch.ethz.ssh2.InteractiveCallback; +import ch.ethz.ssh2.PacketTypeException; +import ch.ethz.ssh2.crypto.PEMDecoder; +import ch.ethz.ssh2.packets.PacketServiceAccept; +import ch.ethz.ssh2.packets.PacketServiceRequest; +import ch.ethz.ssh2.packets.PacketUserauthBanner; +import ch.ethz.ssh2.packets.PacketUserauthFailure; +import ch.ethz.ssh2.packets.PacketUserauthInfoRequest; +import ch.ethz.ssh2.packets.PacketUserauthInfoResponse; +import ch.ethz.ssh2.packets.PacketUserauthRequestInteractive; +import ch.ethz.ssh2.packets.PacketUserauthRequestNone; +import ch.ethz.ssh2.packets.PacketUserauthRequestPassword; +import ch.ethz.ssh2.packets.PacketUserauthRequestPublicKey; +import ch.ethz.ssh2.packets.Packets; +import ch.ethz.ssh2.packets.TypesWriter; +import ch.ethz.ssh2.signature.DSASHA1Verify; +import ch.ethz.ssh2.signature.ECDSASHA2Verify; +import ch.ethz.ssh2.signature.RSASHA1Verify; +import ch.ethz.ssh2.transport.ClientTransportManager; +import ch.ethz.ssh2.transport.MessageHandler; + +/** + * @author Christian Plattner + * @version $Id: AuthenticationManager.java 161 2014-05-01 18:01:55Z dkocher@sudo.ch $ + */ +public class AuthenticationManager implements MessageHandler { + private ClientTransportManager tm; + + private final BlockingQueue<byte[]> packets + = new ArrayBlockingQueue<byte[]>(5); + + private boolean connectionClosed = false; + + private String banner; + + private Set<String> remainingMethods + = new HashSet<String>(); + + private boolean isPartialSuccess = false; + + private boolean authenticated = false; + private boolean initDone = false; + + public AuthenticationManager(ClientTransportManager tm) { + this.tm = tm; + } + + private byte[] deQueue() throws IOException { + if (connectionClosed) { + throw(IOException) new IOException("The connection is closed.").initCause(tm.getReasonClosedCause()); + } + + // Wait for packet + try { + return packets.take(); + } + catch (InterruptedException e) { + throw new InterruptedIOException(e.getMessage()); + } + } + + byte[] getNextMessage() throws IOException { + while (true) { + byte[] message = deQueue(); + + switch (message[0]) { + case Packets.SSH_MSG_USERAUTH_BANNER: + // The server may send an SSH_MSG_USERAUTH_BANNER message at any + // time after this authentication protocol starts and before + // authentication is successful. + PacketUserauthBanner sb = new PacketUserauthBanner(message); + banner = sb.getBanner(); + break; + + default: + return message; + } + } + } + + public Set<String> getRemainingMethods(String user) throws IOException { + initialize(user); + return remainingMethods; + } + + public String getBanner() { + return banner; + } + + public boolean getPartialSuccess() { + return isPartialSuccess; + } + + private boolean initialize(String user) throws IOException { + if (initDone == false) { + tm.registerMessageHandler(this, 0, 255); + PacketServiceRequest sr = new PacketServiceRequest("ssh-userauth"); + tm.sendMessage(sr.getPayload()); + final PacketServiceAccept accept = new PacketServiceAccept(this.getNextMessage()); + PacketUserauthRequestNone auth = new PacketUserauthRequestNone("ssh-connection", user); + tm.sendMessage(auth.getPayload()); + byte[] message = this.getNextMessage(); + initDone = true; + + switch (message[0]) { + case Packets.SSH_MSG_USERAUTH_SUCCESS: + authenticated = true; + tm.removeMessageHandler(this); + return true; + + case Packets.SSH_MSG_USERAUTH_FAILURE: + PacketUserauthFailure puf = new PacketUserauthFailure(message); + remainingMethods = puf.getAuthThatCanContinue(); + isPartialSuccess = puf.isPartialSuccess(); + return false; + } + + throw new PacketTypeException(message[0]); + } + + return authenticated; + } + + public boolean authenticatePublicKey(String user, AgentProxy proxy) throws IOException { + initialize(user); + boolean success; + + for (AgentIdentity identity : proxy.getIdentities()) { + success = authenticatePublicKey(user, identity); + + if (success) { + return true; + } + } + + return false; + } + + private boolean authenticatePublicKey(String user, AgentIdentity identity) throws IOException { + if (!remainingMethods.contains("publickey")) { + throw new IOException("Authentication method not supported"); + } + + byte[] pubKeyBlob = identity.getPublicKeyBlob(); + + if (pubKeyBlob == null) { + return false; + } + + TypesWriter tw = new TypesWriter(); + byte[] H = tm.getSessionIdentifier(); + tw.writeString(H, 0, H.length); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(user); + tw.writeString("ssh-connection"); + tw.writeString("publickey"); + tw.writeBoolean(true); + tw.writeString(identity.getAlgName()); + tw.writeString(pubKeyBlob, 0, pubKeyBlob.length); + byte[] msg = tw.getBytes(); + byte[] response = identity.sign(msg); + PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey( + "ssh-connection", user, identity.getAlgName(), pubKeyBlob, response); + tm.sendMessage(ua.getPayload()); + byte[] message = getNextMessage(); + final int type = message[0]; + + switch (type) { + case Packets.SSH_MSG_USERAUTH_SUCCESS: + authenticated = true; + tm.removeMessageHandler(this); + return true; + + case Packets.SSH_MSG_USERAUTH_FAILURE: + PacketUserauthFailure puf = new PacketUserauthFailure(message); + remainingMethods = puf.getAuthThatCanContinue(); + isPartialSuccess = puf.isPartialSuccess(); + return false; + } + + throw new PacketTypeException(type); + } + + public boolean authenticatePublicKey(String user, char[] PEMPrivateKey, String password, SecureRandom rnd) + throws IOException { + KeyPair pair = PEMDecoder.decode(PEMPrivateKey, password); + return authenticatePublicKey(user, pair, rnd); + } + + public boolean authenticatePublicKey(String user, KeyPair pair, SecureRandom rnd) + throws IOException { + PrivateKey key = pair.getPrivate(); + + try { + initialize(user); + + if (!remainingMethods.contains("publickey")) { + throw new IOException("Authentication method publickey not supported by the server at this stage."); + } + + if (key instanceof DSAPrivateKey) { + DSAPrivateKey pk = (DSAPrivateKey) key; + byte[] pk_enc = DSASHA1Verify.encodeSSHDSAPublicKey((DSAPublicKey) pair.getPublic()); + TypesWriter tw = new TypesWriter(); + byte[] H = tm.getSessionIdentifier(); + tw.writeString(H, 0, H.length); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(user); + tw.writeString("ssh-connection"); + tw.writeString("publickey"); + tw.writeBoolean(true); + tw.writeString("ssh-dss"); + tw.writeString(pk_enc, 0, pk_enc.length); + byte[] msg = tw.getBytes(); + byte[] ds = DSASHA1Verify.generateSignature(msg, pk, rnd); + byte[] ds_enc = DSASHA1Verify.encodeSSHDSASignature(ds); + PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user, + "ssh-dss", pk_enc, ds_enc); + tm.sendMessage(ua.getPayload()); + } + else if (key instanceof RSAPrivateKey) { + RSAPrivateKey pk = (RSAPrivateKey) key; + byte[] pk_enc = RSASHA1Verify.encodeSSHRSAPublicKey((RSAPublicKey) pair.getPublic()); + TypesWriter tw = new TypesWriter(); + { + byte[] H = tm.getSessionIdentifier(); + tw.writeString(H, 0, H.length); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(user); + tw.writeString("ssh-connection"); + tw.writeString("publickey"); + tw.writeBoolean(true); + tw.writeString("ssh-rsa"); + tw.writeString(pk_enc, 0, pk_enc.length); + } + byte[] msg = tw.getBytes(); + byte[] ds = RSASHA1Verify.generateSignature(msg, pk); + byte[] rsa_sig_enc = RSASHA1Verify.encodeSSHRSASignature(ds); + PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user, + "ssh-rsa", pk_enc, rsa_sig_enc); + tm.sendMessage(ua.getPayload()); + } + else if (key instanceof ECPrivateKey) { + ECPrivateKey pk = (ECPrivateKey) key; + final String algo = ECDSASHA2Verify.ECDSA_SHA2_PREFIX + + ECDSASHA2Verify.getCurveName(pk.getParams()); + byte[] pk_enc = ECDSASHA2Verify.encodeSSHECDSAPublicKey((ECPublicKey) pair.getPublic()); + TypesWriter tw = new TypesWriter(); + { + byte[] H = tm.getSessionIdentifier(); + tw.writeString(H, 0, H.length); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(user); + tw.writeString("ssh-connection"); + tw.writeString("publickey"); + tw.writeBoolean(true); + tw.writeString(algo); + tw.writeString(pk_enc, 0, pk_enc.length); + } + byte[] msg = tw.getBytes(); + byte[] ds = ECDSASHA2Verify.generateSignature(msg, pk); + byte[] ec_sig_enc = ECDSASHA2Verify.encodeSSHECDSASignature(ds, pk.getParams()); + PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user, + algo, pk_enc, ec_sig_enc); + tm.sendMessage(ua.getPayload()); + } + else { + throw new IOException("Unknown private key type returned by the PEM decoder."); + } + + byte[] message = getNextMessage(); + final int type = message[0]; + + switch (type) { + case Packets.SSH_MSG_USERAUTH_SUCCESS: + authenticated = true; + tm.removeMessageHandler(this); + return true; + + case Packets.SSH_MSG_USERAUTH_FAILURE: + PacketUserauthFailure puf = new PacketUserauthFailure(message); + remainingMethods = puf.getAuthThatCanContinue(); + isPartialSuccess = puf.isPartialSuccess(); + return false; + } + + throw new PacketTypeException(type); + } + catch (IOException e) { + tm.close(e, false); + throw e; + } + } + + public boolean authenticateNone(String user) throws IOException { + try { + initialize(user); + return authenticated; + } + catch (IOException e) { + tm.close(e, false); + throw e; + } + } + + public boolean authenticatePassword(String user, String pass) throws IOException { + try { + initialize(user); + + if (!remainingMethods.contains("password")) { + throw new IOException("Authentication method not supported"); + } + + PacketUserauthRequestPassword ua = new PacketUserauthRequestPassword("ssh-connection", user, pass); + tm.sendMessage(ua.getPayload()); + byte[] message = getNextMessage(); + final int type = message[0]; + + switch (type) { + case Packets.SSH_MSG_USERAUTH_SUCCESS: + authenticated = true; + tm.removeMessageHandler(this); + return true; + + case Packets.SSH_MSG_USERAUTH_FAILURE: + PacketUserauthFailure puf = new PacketUserauthFailure(message); + remainingMethods = puf.getAuthThatCanContinue(); + isPartialSuccess = puf.isPartialSuccess(); + return false; + } + + throw new PacketTypeException(type); + } + catch (IOException e) { + tm.close(e, false); + throw e; + } + } + + public boolean authenticateInteractive(String user, String[] submethods, InteractiveCallback cb) throws IOException { + try { + initialize(user); + + if (!remainingMethods.contains("keyboard-interactive")) { + throw new IOException( + "Authentication method keyboard-interactive not supported by the server at this stage."); + } + + PacketUserauthRequestInteractive ua = new PacketUserauthRequestInteractive("ssh-connection", user, + submethods); + tm.sendMessage(ua.getPayload()); + + while (true) { + byte[] message = getNextMessage(); + final int type = message[0]; + + switch (type) { + case Packets.SSH_MSG_USERAUTH_SUCCESS: + authenticated = true; + tm.removeMessageHandler(this); + return true; + + case Packets.SSH_MSG_USERAUTH_FAILURE: + PacketUserauthFailure puf = new PacketUserauthFailure(message); + remainingMethods = puf.getAuthThatCanContinue(); + isPartialSuccess = puf.isPartialSuccess(); + return false; + + case Packets.SSH_MSG_USERAUTH_INFO_REQUEST: + PacketUserauthInfoRequest info = new PacketUserauthInfoRequest(message); + String[] responses; + + try { + responses = cb.replyToChallenge(info.getName(), info.getInstruction(), info.getNumPrompts(), + info.getPrompt(), info.getEcho()); + } + catch (Exception e) { + throw new IOException("Exception in callback.", e); + } + + PacketUserauthInfoResponse puir = new PacketUserauthInfoResponse(responses); + tm.sendMessage(puir.getPayload()); + continue; + } + + throw new PacketTypeException(type); + } + } + catch (IOException e) { + tm.close(e, false); + throw e; + } + } + + public void handleFailure(final IOException failure) { + connectionClosed = true; + } + + public void handleMessage(byte[] message) throws IOException { + packets.add(message); + } +}