view src/ch/ethz/ssh2/auth/AuthenticationManager.java @ 324:a1a2e33b3565 ganymed

add ecdsa key support everywhere
author Carl Byington <carl@five-ten-sg.com>
date Thu, 31 Jul 2014 09:31:42 -0700
parents 90537ba71897
children
line wrap: on
line source

/*
 * 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);
    }
}