Save This Page
Home » j2ssh-0.2.9-src » com.sshtools.j2ssh.transport » [javadoc | source]
    1   /*
    2    *  SSHTools - Java SSH2 API
    3    *
    4    *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
    5    *
    6    *  Contributions made by:
    7    *
    8    *  Brett Smith
    9    *  Richard Pernavas
   10    *  Erwin Bolwidt
   11    *
   12    *  This program is free software; you can redistribute it and/or
   13    *  modify it under the terms of the GNU General Public License
   14    *  as published by the Free Software Foundation; either version 2
   15    *  of the License, or (at your option) any later version.
   16    *
   17    *  This program is distributed in the hope that it will be useful,
   18    *  but WITHOUT ANY WARRANTY; without even the implied warranty of
   19    *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   20    *  GNU General Public License for more details.
   21    *
   22    *  You should have received a copy of the GNU General Public License
   23    *  along with this program; if not, write to the Free Software
   24    *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
   25    */
   26   package com.sshtools.j2ssh.transport;
   27   
   28   import com.sshtools.j2ssh.transport.publickey.SshKeyPairFactory;
   29   import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
   30   import com.sshtools.j2ssh.util.Base64;
   31   
   32   import org.apache.commons.logging.Log;
   33   import org.apache.commons.logging.LogFactory;
   34   
   35   import java.io.BufferedReader;
   36   import java.io.File;
   37   import java.io.FileInputStream;
   38   import java.io.FileOutputStream;
   39   import java.io.FilePermission;
   40   import java.io.IOException;
   41   import java.io.InputStream;
   42   import java.io.InputStreamReader;
   43   
   44   import java.security.AccessControlException;
   45   import java.security.AccessController;
   46   
   47   import java.util.HashMap;
   48   import java.util.Iterator;
   49   import java.util.Map;
   50   import java.util.StringTokenizer;
   51   
   52   
   53   /**
   54    * <p>
   55    * An abstract <code>HostKeyVerification</code> class providing validation
   56    * against the known_hosts format.
   57    * </p>
   58    *
   59    * @author Lee David Painter
   60    * @version $Revision: 1.18 $
   61    *
   62    * @since 0.2.0
   63    */
   64   public abstract class AbstractKnownHostsKeyVerification
   65       implements HostKeyVerification {
   66       private static String defaultHostFile;
   67       private static Log log = LogFactory.getLog(HostKeyVerification.class);
   68   
   69       //private List deniedHosts = new ArrayList();
   70       private Map allowedHosts = new HashMap();
   71       private String knownhosts;
   72       private boolean hostFileWriteable;
   73   
   74       //private boolean expectEndElement = false;
   75       //private String currentElement = null;
   76   
   77       /**
   78        * <p>
   79        * Constructs a host key verification instance reading the specified
   80        * known_hosts file.
   81        * </p>
   82        *
   83        * @param knownhosts the path of the known_hosts file
   84        *
   85        * @throws InvalidHostFileException if the known_hosts file is invalid
   86        *
   87        * @since 0.2.0
   88        */
   89       public AbstractKnownHostsKeyVerification(String knownhosts)
   90           throws InvalidHostFileException {
   91           InputStream in = null;
   92   
   93           try {
   94               //  If no host file is supplied, or there is not enough permission to load
   95               //  the file, then just create an empty list.
   96               if (knownhosts != null) {
   97                   if (System.getSecurityManager() != null) {
   98                       AccessController.checkPermission(new FilePermission(
   99                               knownhosts, "read"));
  100                   }
  101   
  102                   //  Load the hosts file. Do not worry if fle doesnt exist, just disable
  103                   //  save of
  104                   File f = new File(knownhosts);
  105   
  106                   if (f.exists()) {
  107                       in = new FileInputStream(f);
  108   
  109                       BufferedReader reader = new BufferedReader(new InputStreamReader(
  110                                   in));
  111                       String line;
  112   
  113                       while ((line = reader.readLine()) != null) {
  114                           StringTokenizer tokens = new StringTokenizer(line, " ");
  115                           String host = (String) tokens.nextElement();
  116                           String algorithm = (String) tokens.nextElement();
  117                           String key = (String) tokens.nextElement();
  118   
  119                           SshPublicKey pk = SshKeyPairFactory.decodePublicKey(Base64.decode(
  120                               key));
  121                             /*if (host.indexOf(",") > -1) {
  122                              host = host.substring(0, host.indexOf(","));
  123                              }*/
  124                           putAllowedKey(host, pk);
  125   
  126                           //allowedHosts.put(host + "#" + pk.getAlgorithmName(), pk);
  127                       }
  128   
  129                       reader.close();
  130                       hostFileWriteable = f.canWrite();
  131                   } else {
  132                       // Try to create the file and its parents if necersary
  133                       f.getParentFile().mkdirs();
  134   
  135                       if (f.createNewFile()) {
  136                           FileOutputStream out = new FileOutputStream(f);
  137                           out.write(toString().getBytes());
  138                           out.close();
  139                           hostFileWriteable = true;
  140                       } else {
  141                           hostFileWriteable = false;
  142                       }
  143                   }
  144   
  145                   if (!hostFileWriteable) {
  146                       log.warn("Host file is not writeable.");
  147                   }
  148   
  149                   this.knownhosts = knownhosts;
  150               }
  151           } catch (AccessControlException ace) {
  152               hostFileWriteable = false;
  153               log.warn(
  154                   "Not enough permission to load a hosts file, so just creating an empty list");
  155           } catch (IOException ioe) {
  156               hostFileWriteable = false;
  157               log.info("Could not open or read " + knownhosts + ": " +
  158                   ioe.getMessage());
  159           } finally {
  160               if (in != null) {
  161                   try {
  162                       in.close();
  163                   } catch (IOException ioe) {
  164                   }
  165               }
  166           }
  167       }
  168   
  169       /**
  170        * <p>
  171        * Determines whether the host file is writable.
  172        * </p>
  173        *
  174        * @return true if the host file is writable, otherwise false
  175        *
  176        * @since 0.2.0
  177        */
  178       public boolean isHostFileWriteable() {
  179           return hostFileWriteable;
  180       }
  181   
  182       /**
  183        * <p>
  184        * Called by the <code>verifyHost</code> method when the host key supplied
  185        * by the host does not match the current key recording in the known hosts
  186        * file.
  187        * </p>
  188        *
  189        * @param host the name of the host
  190        * @param allowedHostKey the current key recorded in the known_hosts file.
  191        * @param actualHostKey the actual key supplied by the user
  192        *
  193        * @throws TransportProtocolException if an error occurs
  194        *
  195        * @since 0.2.0
  196        */
  197       public abstract void onHostKeyMismatch(String host,
  198           SshPublicKey allowedHostKey, SshPublicKey actualHostKey)
  199           throws TransportProtocolException;
  200   
  201       /**
  202        * <p>
  203        * Called by the <code>verifyHost</code> method when the host key supplied
  204        * is not recorded in the known_hosts file.
  205        * </p>
  206        *
  207        * <p></p>
  208        *
  209        * @param host the name of the host
  210        * @param key the public key supplied by the host
  211        *
  212        * @throws TransportProtocolException if an error occurs
  213        *
  214        * @since 0.2.0
  215        */
  216       public abstract void onUnknownHost(String host, SshPublicKey key)
  217           throws TransportProtocolException;
  218   
  219       /**
  220        * <p>
  221        * Allows a host key, optionally recording the key to the known_hosts file.
  222        * </p>
  223        *
  224        * @param host the name of the host
  225        * @param pk the public key to allow
  226        * @param always true if the key should be written to the known_hosts file
  227        *
  228        * @throws InvalidHostFileException if the host file cannot be written
  229        *
  230        * @since 0.2.0
  231        */
  232       public void allowHost(String host, SshPublicKey pk, boolean always)
  233           throws InvalidHostFileException {
  234           if (log.isDebugEnabled()) {
  235               log.debug("Allowing " + host + " with fingerprint " +
  236                   pk.getFingerprint());
  237           }
  238   
  239           // Put the host into the allowed hosts list, overiding any previous
  240           // entry
  241           putAllowedKey(host, pk);
  242   
  243           //allowedHosts.put(host, pk);
  244           // If we always want to allow then save the host file with the
  245           // new details
  246           if (always) {
  247               saveHostFile();
  248           }
  249       }
  250   
  251       /**
  252        * <p>
  253        * Returns a Map of the allowed hosts.
  254        * </p>
  255        *
  256        * <p>
  257        * The keys of the returned Map are comma separated strings of
  258        * "hostname,ipaddress". The value objects are Maps containing a string
  259        * key of the public key alogorithm name and the public key as the value.
  260        * </p>
  261        *
  262        * @return the allowed hosts
  263        *
  264        * @since 0.2.0
  265        */
  266       public Map allowedHosts() {
  267           return allowedHosts;
  268       }
  269   
  270       /**
  271        * <p>
  272        * Removes an allowed host.
  273        * </p>
  274        *
  275        * @param host the host to remove
  276        *
  277        * @since 0.2.0
  278        */
  279       public void removeAllowedHost(String host) {
  280           Iterator it = allowedHosts.keySet().iterator();
  281   
  282           while (it.hasNext()) {
  283               StringTokenizer tokens = new StringTokenizer((String) it.next(), ",");
  284   
  285               while (tokens.hasMoreElements()) {
  286                   String name = (String) tokens.nextElement();
  287   
  288                   if (name.equals(host)) {
  289                       allowedHosts.remove(name);
  290                   }
  291               }
  292           }
  293       }
  294   
  295       /**
  296        * <p>
  297        * Verifies a host key against the list of known_hosts.
  298        * </p>
  299        *
  300        * <p>
  301        * If the host unknown or the key does not match the currently allowed host
  302        * key the abstract <code>onUnknownHost</code> or
  303        * <code>onHostKeyMismatch</code> methods are called so that the caller
  304        * may identify and allow the host.
  305        * </p>
  306        *
  307        * @param host the name of the host
  308        * @param pk the host key supplied
  309        *
  310        * @return true if the host is accepted, otherwise false
  311        *
  312        * @throws TransportProtocolException if an error occurs
  313        *
  314        * @since 0.2.0
  315        */
  316       public boolean verifyHost(String host, SshPublicKey pk)
  317           throws TransportProtocolException {
  318           String fingerprint = pk.getFingerprint();
  319           log.info("Verifying " + host + " host key");
  320   
  321           if (log.isDebugEnabled()) {
  322               log.debug("Fingerprint: " + fingerprint);
  323           }
  324   
  325           Iterator it = allowedHosts.keySet().iterator();
  326   
  327           while (it.hasNext()) {
  328               // Could be a comma delimited string of names/ip addresses
  329               String names = (String) it.next();
  330   
  331               if (names.equals(host)) {
  332                   return validateHost(names, pk);
  333               }
  334   
  335               StringTokenizer tokens = new StringTokenizer(names, ",");
  336   
  337               while (tokens.hasMoreElements()) {
  338                   // Try the allowed hosts by looking at the allowed hosts map
  339                   String name = (String) tokens.nextElement();
  340   
  341                   if (name.equalsIgnoreCase(host)) {
  342                       return validateHost(names, pk);
  343                   }
  344               }
  345           }
  346   
  347           // The host is unknown os ask the user
  348           onUnknownHost(host, pk);
  349   
  350           // Recheck ans return the result
  351           return checkKey(host, pk);
  352       }
  353   
  354       private boolean validateHost(String names, SshPublicKey pk)
  355           throws TransportProtocolException {
  356           // The host is allowed so check the fingerprint
  357           SshPublicKey pub = getAllowedKey(names, pk.getAlgorithmName()); //shPublicKey) allowedHosts.get(host + "#" + pk.getAlgorithmName());
  358   
  359           if ((pub != null) && pk.equals(pub)) {
  360               return true;
  361           } else {
  362               // The host key does not match the recorded so call the abstract
  363               // method so that the user can decide
  364               if (pub == null) {
  365                   onUnknownHost(names, pk);
  366               } else {
  367                   onHostKeyMismatch(names, pub, pk);
  368               }
  369   
  370               // Recheck the after the users input
  371               return checkKey(names, pk);
  372           }
  373       }
  374   
  375       private boolean checkKey(String host, SshPublicKey key) {
  376           SshPublicKey pk = getAllowedKey(host, key.getAlgorithmName()); //shPublicKey) allowedHosts.get(host + "#" + key.getAlgorithmName());
  377   
  378           if (pk != null) {
  379               if (pk.equals(key)) {
  380                   return true;
  381               }
  382           }
  383   
  384           return false;
  385       }
  386   
  387       private SshPublicKey getAllowedKey(String names, String algorithm) {
  388           if (allowedHosts.containsKey(names)) {
  389               Map map = (Map) allowedHosts.get(names);
  390   
  391               return (SshPublicKey) map.get(algorithm);
  392           }
  393   
  394           return null;
  395       }
  396   
  397       private void putAllowedKey(String host, SshPublicKey key) {
  398           if (!allowedHosts.containsKey(host)) {
  399               allowedHosts.put(host, new HashMap());
  400           }
  401   
  402           Map map = (Map) allowedHosts.get(host);
  403           map.put(key.getAlgorithmName(), key);
  404       }
  405   
  406       /**
  407        * <p>
  408        * Save's the host key file to be saved.
  409        * </p>
  410        *
  411        * @throws InvalidHostFileException if the host file is invalid
  412        *
  413        * @since 0.2.0
  414        */
  415       public void saveHostFile() throws InvalidHostFileException {
  416           if (!hostFileWriteable) {
  417               throw new InvalidHostFileException("Host file is not writeable.");
  418           }
  419   
  420           log.info("Saving " + defaultHostFile);
  421   
  422           try {
  423               File f = new File(knownhosts);
  424               FileOutputStream out = new FileOutputStream(f);
  425               out.write(toString().getBytes());
  426               out.close();
  427           } catch (IOException e) {
  428               throw new InvalidHostFileException("Could not write to " +
  429                   knownhosts);
  430           }
  431       }
  432   
  433       /**
  434        * <p>
  435        * Outputs the allowed hosts in the known_hosts file format.
  436        * </p>
  437        *
  438        * <p>
  439        * The format consists of any number of lines each representing one key for
  440        * a single host.
  441        * </p>
  442        * <code> titan,192.168.1.12 ssh-dss AAAAB3NzaC1kc3MAAACBAP1/U4Ed.....
  443        * titan,192.168.1.12 ssh-rsa AAAAB3NzaC1kc3MAAACBAP1/U4Ed.....
  444        * einstein,192.168.1.40 ssh-dss AAAAB3NzaC1kc3MAAACBAP1/U4Ed..... </code>
  445        *
  446        * @return
  447        *
  448        * @since 0.2.0
  449        */
  450       public String toString() {
  451           String knownhosts = "";
  452           Map.Entry entry;
  453           Map.Entry entry2;
  454           Iterator it = allowedHosts.entrySet().iterator();
  455   
  456           while (it.hasNext()) {
  457               entry = (Map.Entry) it.next();
  458   
  459               Iterator it2 = ((Map) entry.getValue()).entrySet().iterator();
  460   
  461               while (it2.hasNext()) {
  462                   entry2 = (Map.Entry) it2.next();
  463   
  464                   SshPublicKey pk = (SshPublicKey) entry2.getValue();
  465                   knownhosts += (entry.getKey().toString() + " " +
  466                   pk.getAlgorithmName() + " " +
  467                   Base64.encodeBytes(pk.getEncoded(), true) + "\n");
  468               }
  469           }
  470   
  471           return knownhosts;
  472       }
  473   }

Save This Page
Home » j2ssh-0.2.9-src » com.sshtools.j2ssh.transport » [javadoc | source]