Package

Source Code of SwingShell$AdvancedVerifier

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.InteractiveCallback;
import ch.ethz.ssh2.KnownHosts;
import ch.ethz.ssh2.ServerHostKeyVerifier;
import ch.ethz.ssh2.Session;

/**
*
* This is a very primitive SSH-2 dumb terminal (Swing based).
*
* The purpose of this class is to demonstrate:
*
* - Verifying server hostkeys with an existing known_hosts file
* - Displaying fingerprints of server hostkeys
* - Adding a server hostkey to a known_hosts file (+hashing the hostname for security)
* - Authentication with DSA, RSA, password and keyboard-interactive methods
*
*/
public class SwingShell
{

  /*
   * NOTE: to get this feature to work, replace the "tilde" with your home directory,
   * at least my JVM does not understand it. Need to check the specs.
   */

  static final String knownHostPath = "~/.ssh/known_hosts";
  static final String idDSAPath = "~/.ssh/id_dsa";
  static final String idRSAPath = "~/.ssh/id_rsa";

  JFrame loginFrame = null;
  JLabel hostLabel;
  JLabel userLabel;
  JTextField hostField;
  JTextField userField;
  JButton loginButton;

  KnownHosts database = new KnownHosts();

  public SwingShell()
  {
    File knownHostFile = new File(knownHostPath);
    if (knownHostFile.exists())
    {
      try
      {
        database.addHostkeys(knownHostFile);
      }
      catch (IOException e)
      {
      }
    }
  }

  /**
   * This dialog displays a number of text lines and a text field.
   * The text field can either be plain text or a password field.
   */
  class EnterSomethingDialog extends JDialog
  {
    private static final long serialVersionUID = 1L;

    JTextField answerField;
    JPasswordField passwordField;

    final boolean isPassword;

    String answer;

    public EnterSomethingDialog(JFrame parent, String title, String content, boolean isPassword)
    {
      this(parent, title, new String[] { content }, isPassword);
    }

    public EnterSomethingDialog(JFrame parent, String title, String[] content, boolean isPassword)
    {
      super(parent, title, true);

      this.isPassword = isPassword;

      JPanel pan = new JPanel();
      pan.setLayout(new BoxLayout(pan, BoxLayout.Y_AXIS));

      for (int i = 0; i < content.length; i++)
      {
        if ((content[i] == null) || (content[i] == ""))
          continue;
        JLabel contentLabel = new JLabel(content[i]);
        pan.add(contentLabel);

      }

      answerField = new JTextField(20);
      passwordField = new JPasswordField(20);

      if (isPassword)
        pan.add(passwordField);
      else
        pan.add(answerField);

      KeyAdapter kl = new KeyAdapter()
      {
        public void keyTyped(KeyEvent e)
        {
          if (e.getKeyChar() == '\n')
            finish();
        }
      };

      answerField.addKeyListener(kl);
      passwordField.addKeyListener(kl);

      getContentPane().add(BorderLayout.CENTER, pan);

      setResizable(false);
      pack();
      setLocationRelativeTo(null);
    }

    private void finish()
    {
      if (isPassword)
        answer = new String(passwordField.getPassword());
      else
        answer = answerField.getText();

      dispose();
    }
  }

  /**
   * TerminalDialog is probably the worst terminal emulator ever written - implementing
   * a real vt100 is left as an exercise to the reader, i.e., to you =)
   *
   */
  class TerminalDialog extends JDialog
  {
    private static final long serialVersionUID = 1L;

    JPanel botPanel;
    JButton logoffButton;
    JTextArea terminalArea;

    Session sess;
    InputStream in;
    OutputStream out;

    int x, y;

    /**
     * This thread consumes output from the remote server and displays it in
     * the terminal window.
     *
     */
    class RemoteConsumer extends Thread
    {
      char[][] lines = new char[y][];
      int posy = 0;
      int posx = 0;

      private void addText(byte[] data, int len)
      {
        for (int i = 0; i < len; i++)
        {
          char c = (char) (data[i] & 0xff);

          if (c == 8) // Backspace, VERASE
          {
            if (posx < 0)
              continue;
            posx--;
            continue;
          }

          if (c == '\r')
          {
            posx = 0;
            continue;
          }

          if (c == '\n')
          {
            posy++;
            if (posy >= y)
            {
              for (int k = 1; k < y; k++)
                lines[k - 1] = lines[k];
              posy--;
              lines[y - 1] = new char[x];
              for (int k = 0; k < x; k++)
                lines[y - 1][k] = ' ';
            }
            continue;
          }

          if (c < 32)
          {
            continue;
          }

          if (posx >= x)
          {
            posx = 0;
            posy++;
            if (posy >= y)
            {
              posy--;
              for (int k = 1; k < y; k++)
                lines[k - 1] = lines[k];
              lines[y - 1] = new char[x];
              for (int k = 0; k < x; k++)
                lines[y - 1][k] = ' ';
            }
          }

          if (lines[posy] == null)
          {
            lines[posy] = new char[x];
            for (int k = 0; k < x; k++)
              lines[posy][k] = ' ';
          }

          lines[posy][posx] = c;
          posx++;
        }

        StringBuffer sb = new StringBuffer(x * y);

        for (int i = 0; i < lines.length; i++)
        {
          if (i != 0)
            sb.append('\n');

          if (lines[i] != null)
          {
            sb.append(lines[i]);
          }

        }
        setContent(sb.toString());
      }

      public void run()
      {
        byte[] buff = new byte[8192];

        try
        {
          while (true)
          {
            int len = in.read(buff);
            if (len == -1)
              return;
            addText(buff, len);
          }
        }
        catch (Exception e)
        {
        }
      }
    }

    public TerminalDialog(JFrame parent, String title, Session sess, int x, int y) throws IOException
    {
      super(parent, title, true);

      this.sess = sess;

      in = sess.getStdout();
      out = sess.getStdin();

      this.x = x;
      this.y = y;

      botPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));

      logoffButton = new JButton("Logout");
      botPanel.add(logoffButton);

      logoffButton.addActionListener(new ActionListener()
      {
        public void actionPerformed(ActionEvent e)
        {
          /* Dispose the dialog, "setVisible(true)" method will return */
          dispose();
        }
      });

      Font f = new Font("Monospaced", Font.PLAIN, 16);

      terminalArea = new JTextArea(y, x);
      terminalArea.setFont(f);
      terminalArea.setBackground(Color.BLACK);
      terminalArea.setForeground(Color.ORANGE);
      /* This is a hack. We cannot disable the caret,
       * since setting editable to false also changes
       * the meaning of the TAB key - and I want to use it in bash.
       * Again - this is a simple DEMO terminal =)
       */
      terminalArea.setCaretColor(Color.BLACK);

      KeyAdapter kl = new KeyAdapter()
      {
        public void keyTyped(KeyEvent e)
        {
          int c = e.getKeyChar();

          try
          {
            out.write(c);
          }
          catch (IOException e1)
          {
          }
          e.consume();
        }
      };

      terminalArea.addKeyListener(kl);

      getContentPane().add(terminalArea, BorderLayout.CENTER);
      getContentPane().add(botPanel, BorderLayout.PAGE_END);

      setResizable(false);
      pack();
      setLocationRelativeTo(parent);

      new RemoteConsumer().start();
    }

    public void setContent(String lines)
    {
      // setText is thread safe, it does not have to be called from
      // the Swing GUI thread.
      terminalArea.setText(lines);
    }
  }

  /**
   * This ServerHostKeyVerifier asks the user on how to proceed if a key cannot be found
   * in the in-memory database.
   *
   */
  class AdvancedVerifier implements ServerHostKeyVerifier
  {
    public boolean verifyServerHostKey(String hostname, int port, String serverHostKeyAlgorithm,
        byte[] serverHostKey) throws Exception
    {
      final String host = hostname;
      final String algo = serverHostKeyAlgorithm;

      String message;

      /* Check database */

      int result = database.verifyHostkey(hostname, serverHostKeyAlgorithm, serverHostKey);

      switch (result)
      {
      case KnownHosts.HOSTKEY_IS_OK:
        return true;

      case KnownHosts.HOSTKEY_IS_NEW:
        message = "Do you want to accept the hostkey (type " + algo + ") from " + host + " ?\n";
        break;

      case KnownHosts.HOSTKEY_HAS_CHANGED:
        message = "WARNING! Hostkey for " + host + " has changed!\nAccept anyway?\n";
        break;

      default:
        throw new IllegalStateException();
      }

      /* Include the fingerprints in the message */

      String hexFingerprint = KnownHosts.createHexFingerprint(serverHostKeyAlgorithm, serverHostKey);
      String bubblebabbleFingerprint = KnownHosts.createBubblebabbleFingerprint(serverHostKeyAlgorithm,
          serverHostKey);

      message += "Hex Fingerprint: " + hexFingerprint + "\nBubblebabble Fingerprint: " + bubblebabbleFingerprint;

      /* Now ask the user */

      int choice = JOptionPane.showConfirmDialog(loginFrame, message);

      if (choice == JOptionPane.YES_OPTION)
      {
        /* Be really paranoid. We use a hashed hostname entry */

        String hashedHostname = KnownHosts.createHashedHostname(hostname);

        /* Add the hostkey to the in-memory database */

        database.addHostkey(new String[] { hashedHostname }, serverHostKeyAlgorithm, serverHostKey);

        /* Also try to add the key to a known_host file */

        try
        {
          KnownHosts.addHostkeyToFile(new File(knownHostPath), new String[] { hashedHostname },
              serverHostKeyAlgorithm, serverHostKey);
        }
        catch (IOException ignore)
        {
        }

        return true;
      }

      if (choice == JOptionPane.CANCEL_OPTION)
      {
        throw new Exception("The user aborted the server hostkey verification.");
      }

      return false;
    }
  }

  /**
   * The logic that one has to implement if "keyboard-interactive" autentication shall be
   * supported.
   *
   */
  class InteractiveLogic implements InteractiveCallback
  {
    int promptCount = 0;
    String lastError;

    public InteractiveLogic(String lastError)
    {
      this.lastError = lastError;
    }

    /* the callback may be invoked several times, depending on how many questions-sets the server sends */

    public String[] replyToChallenge(String name, String instruction, int numPrompts, String[] prompt,
        boolean[] echo) throws IOException
    {
      String[] result = new String[numPrompts];

      for (int i = 0; i < numPrompts; i++)
      {
        /* Often, servers just send empty strings for "name" and "instruction" */

        String[] content = new String[] { lastError, name, instruction, prompt[i] };

        if (lastError != null)
        {
          /* show lastError only once */
          lastError = null;
        }

        EnterSomethingDialog esd = new EnterSomethingDialog(loginFrame, "Keyboard Interactive Authentication",
            content, !echo[i]);

        esd.setVisible(true);

        if (esd.answer == null)
          throw new IOException("Login aborted by user");

        result[i] = esd.answer;
        promptCount++;
      }

      return result;
    }

    /* We maintain a prompt counter - this enables the detection of situations where the ssh
     * server is signaling "authentication failed" even though it did not send a single prompt.
     */

    public int getPromptCount()
    {
      return promptCount;
    }
  }

  /**
   * The SSH-2 connection is established in this thread.
   * If we would not use a separate thread (e.g., put this code in
   * the event handler of the "Login" button) then the GUI would not
   * be responsive (missing window repaints if you move the window etc.)
   */
  class ConnectionThread extends Thread
  {
    String hostname;
    String username;

    public ConnectionThread(String hostname, String username)
    {
      this.hostname = hostname;
      this.username = username;
    }

    public void run()
    {
      Connection conn = new Connection(hostname);

      try
      {
        /*
         *
         * CONNECT AND VERIFY SERVER HOST KEY (with callback)
         *
         */

        String[] hostkeyAlgos = database.getPreferredServerHostkeyAlgorithmOrder(hostname);

        if (hostkeyAlgos != null)
          conn.setServerHostKeyAlgorithms(hostkeyAlgos);

        conn.connect(new AdvancedVerifier());

        /*
         *
         * AUTHENTICATION PHASE
         *
         */

        boolean enableKeyboardInteractive = true;
        boolean enableDSA = true;
        boolean enableRSA = true;

        String lastError = null;

        while (true)
        {
          if ((enableDSA || enableRSA) && conn.isAuthMethodAvailable(username, "publickey"))
          {
            if (enableDSA)
            {
              File key = new File(idDSAPath);

              if (key.exists())
              {
                EnterSomethingDialog esd = new EnterSomethingDialog(loginFrame, "DSA Authentication",
                    new String[] { lastError, "Enter DSA private key password:" }, true);
                esd.setVisible(true);

                boolean res = conn.authenticateWithPublicKey(username, key, esd.answer);

                if (res == true)
                  break;

                lastError = "DSA authentication failed.";
              }
              enableDSA = false; // do not try again
            }

            if (enableRSA)
            {
              File key = new File(idRSAPath);

              if (key.exists())
              {
                EnterSomethingDialog esd = new EnterSomethingDialog(loginFrame, "RSA Authentication",
                    new String[] { lastError, "Enter RSA private key password:" }, true);
                esd.setVisible(true);

                boolean res = conn.authenticateWithPublicKey(username, key, esd.answer);

                if (res == true)
                  break;

                lastError = "RSA authentication failed.";
              }
              enableRSA = false; // do not try again
            }

            continue;
          }

          if (enableKeyboardInteractive && conn.isAuthMethodAvailable(username, "keyboard-interactive"))
          {
            InteractiveLogic il = new InteractiveLogic(lastError);

            boolean res = conn.authenticateWithKeyboardInteractive(username, il);

            if (res == true)
              break;

            if (il.getPromptCount() == 0)
            {
              // aha. the server announced that it supports "keyboard-interactive", but when
              // we asked for it, it just denied the request without sending us any prompt.
              // That happens with some server versions/configurations.
              // We just disable the "keyboard-interactive" method and notify the user.

              lastError = "Keyboard-interactive does not work.";

              enableKeyboardInteractive = false; // do not try this again
            }
            else
            {
              lastError = "Keyboard-interactive auth failed."; // try again, if possible
            }

            continue;
          }

          if (conn.isAuthMethodAvailable(username, "password"))
          {
            final EnterSomethingDialog esd = new EnterSomethingDialog(loginFrame,
                "Password Authentication",
                new String[] { lastError, "Enter password for " + username }, true);

            esd.setVisible(true);

            if (esd.answer == null)
              throw new IOException("Login aborted by user");

            boolean res = conn.authenticateWithPassword(username, esd.answer);

            if (res == true)
              break;

            lastError = "Password authentication failed."; // try again, if possible

            continue;
          }

          throw new IOException("No supported authentication methods available.");
        }

        /*
         *
         * AUTHENTICATION OK. DO SOMETHING.
         *
         */

        Session sess = conn.openSession();

        int x_width = 90;
        int y_width = 30;

        sess.requestPTY("dumb", x_width, y_width, 0, 0, null);
        sess.startShell();

        TerminalDialog td = new TerminalDialog(loginFrame, username + "@" + hostname, sess, x_width, y_width);

        /* The following call blocks until the dialog has been closed */

        td.setVisible(true);

      }
      catch (IOException e)
      {
        //e.printStackTrace();
        JOptionPane.showMessageDialog(loginFrame, "Exception: " + e.getMessage());
      }

      /*
       *
       * CLOSE THE CONNECTION.
       *
       */

      conn.close();

      /*
       *
       * CLOSE THE LOGIN FRAME - APPLICATION WILL BE EXITED (no more frames)
       *
       */

      Runnable r = new Runnable()
      {
        public void run()
        {
          loginFrame.dispose();
        }
      };

      SwingUtilities.invokeLater(r);
    }
  }

  void loginPressed()
  {
    String hostname = hostField.getText().trim();
    String username = userField.getText().trim();

    if ((hostname.length() == 0) || (username.length() == 0))
    {
      JOptionPane.showMessageDialog(loginFrame, "Please fill out both fields!");
      return;
    }

    loginButton.setEnabled(false);
    hostField.setEnabled(false);
    userField.setEnabled(false);

    ConnectionThread ct = new ConnectionThread(hostname, username);

    ct.start();
  }

  void showGUI()
  {
    loginFrame = new JFrame("Ganymed SSH2 SwingShell");

    hostLabel = new JLabel("Hostname:");
    userLabel = new JLabel("Username:");

    hostField = new JTextField("", 20);
    userField = new JTextField("", 10);

    loginButton = new JButton("Login");

    loginButton.addActionListener(new ActionListener()
    {
      public void actionPerformed(java.awt.event.ActionEvent e)
      {
        loginPressed();
      }
    });

    JPanel loginPanel = new JPanel();

    loginPanel.add(hostLabel);
    loginPanel.add(hostField);
    loginPanel.add(userLabel);
    loginPanel.add(userField);
    loginPanel.add(loginButton);

    loginFrame.getRootPane().setDefaultButton(loginButton);

    loginFrame.getContentPane().add(loginPanel, BorderLayout.PAGE_START);
    //loginFrame.getContentPane().add(textArea, BorderLayout.CENTER);

    loginFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    loginFrame.pack();
    loginFrame.setResizable(false);
    loginFrame.setLocationRelativeTo(null);
    loginFrame.setVisible(true);
  }

  void startGUI()
  {
    Runnable r = new Runnable()
    {
      public void run()
      {
        showGUI();
      }
    };

    SwingUtilities.invokeLater(r);

  }

  public static void main(String[] args)
  {
    SwingShell client = new SwingShell();
    client.startGUI();
  }
}
TOP

Related Classes of SwingShell$AdvancedVerifier

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.