001    /*
002     * Copyright (C) 2012 eXo Platform SAS.
003     *
004     * This is free software; you can redistribute it and/or modify it
005     * under the terms of the GNU Lesser General Public License as
006     * published by the Free Software Foundation; either version 2.1 of
007     * the License, or (at your option) any later version.
008     *
009     * This software is distributed in the hope that it will be useful,
010     * but WITHOUT ANY WARRANTY; without even the implied warranty of
011     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012     * Lesser General Public License for more details.
013     *
014     * You should have received a copy of the GNU Lesser General Public
015     * License along with this software; if not, write to the Free
016     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018     */
019    
020    package org.crsh.ssh;
021    
022    import org.apache.sshd.common.KeyPairProvider;
023    import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
024    import org.crsh.auth.AuthenticationPlugin;
025    import org.crsh.plugin.CRaSHPlugin;
026    import org.crsh.plugin.PropertyDescriptor;
027    import org.crsh.plugin.ResourceKind;
028    import org.crsh.ssh.term.FilePublicKeyProvider;
029    import org.crsh.ssh.term.SSHLifeCycle;
030    import org.crsh.vfs.Resource;
031    
032    import java.io.File;
033    import java.io.IOException;
034    import java.net.MalformedURLException;
035    import java.net.URL;
036    import java.security.KeyPair;
037    import java.security.PublicKey;
038    import java.util.Arrays;
039    import java.util.Collections;
040    import java.util.LinkedHashSet;
041    import java.util.Set;
042    import java.util.logging.Level;
043    
044    import org.apache.sshd.common.util.SecurityUtils;
045    
046    public class SSHPlugin extends CRaSHPlugin<SSHPlugin> {
047    
048      /** . */
049      private static final String[] TYPES = { KeyPairProvider.SSH_DSS, KeyPairProvider.SSH_RSA };
050    
051      /** The SSH port. */
052      public static final PropertyDescriptor<Integer> SSH_PORT = PropertyDescriptor.create("ssh.port", 2000, "The SSH port");
053    
054      /** The SSH server key path. */
055      public static final PropertyDescriptor<String> SSH_SERVER_KEYPATH = PropertyDescriptor.create("ssh.keypath", (String)null, "The path to the key file");
056    
057      /** The SSH authorized key path. */
058      public static final PropertyDescriptor<String> SSH_AUTHORIZED_KEYPATH = PropertyDescriptor.create("ssh.authorized.keypath", (String)null, "The path to the authorized key file");
059    
060      /** . */
061      private SSHLifeCycle lifeCycle;
062    
063      @Override
064      public SSHPlugin getImplementation() {
065        return this;
066      }
067    
068      @Override
069      protected Iterable<PropertyDescriptor<?>> createConfigurationCapabilities() {
070        return Arrays.<PropertyDescriptor<?>>asList(SSH_PORT, SSH_SERVER_KEYPATH, SSH_AUTHORIZED_KEYPATH, AuthenticationPlugin.AUTH);
071      }
072    
073      @Override
074      public void init() {
075    
076        SecurityUtils.setRegisterBouncyCastle(true);
077        //
078        Integer port = getContext().getProperty(SSH_PORT);
079        if (port == null) {
080          log.log(Level.INFO, "Could not boot SSHD due to missing due to missing port configuration");
081          return;
082        }
083    
084        //
085        Resource serverKey = null;
086    
087        // Get embedded default key
088        URL serverKeyURL = SSHPlugin.class.getResource("/crash/hostkey.pem");
089        if (serverKeyURL != null) {
090          try {
091            log.log(Level.FINE, "Found embedded key url " + serverKeyURL);
092            serverKey = new Resource(serverKeyURL);
093          }
094          catch (IOException e) {
095            log.log(Level.FINE, "Could not load ssh key from url " + serverKeyURL, e);
096          }
097        }
098    
099        // Override from config if any
100        Resource serverKeyRes = getContext().loadResource("hostkey.pem", ResourceKind.CONFIG);
101        if (serverKeyRes != null) {
102          serverKey = serverKeyRes;
103          log.log(Level.FINE, "Found server ssh key url");
104        }
105    
106        // If we have a key path, we convert is as an URL
107        String serverKeyPath = getContext().getProperty(SSH_SERVER_KEYPATH);
108        if (serverKeyPath != null) {
109          log.log(Level.FINE, "Found server key path " + serverKeyPath);
110          File f = new File(serverKeyPath);
111          if (f.exists() && f.isFile()) {
112            try {
113              serverKeyURL = f.toURI().toURL();
114            } catch (MalformedURLException e) {
115              log.log(Level.FINE, "Ignoring invalid server key " + serverKeyPath, e);
116            }
117          } else {
118            log.log(Level.FINE, "Ignoring invalid server key path " + serverKeyPath);
119          }
120        }
121    
122        //
123        if (serverKeyURL == null) {
124          log.log(Level.INFO, "Could not boot SSHD due to missing server key");
125          return;
126        }
127    
128        //
129        Set<PublicKey> authorizedKeys = Collections.emptySet();
130        String authorizedKeyPath = getContext().getProperty(SSH_AUTHORIZED_KEYPATH);
131        if (authorizedKeyPath != null) {
132          File f = new File(authorizedKeyPath);
133          if (f.exists() && f.isFile()) {
134            log.log(Level.FINE, "Found authorized key path " + authorizedKeyPath);
135            authorizedKeys = new LinkedHashSet<PublicKey>();
136            KeyPairProvider provider = new FilePublicKeyProvider(new String[]{authorizedKeyPath});
137            for (String type : TYPES) {
138              KeyPair pair = provider.loadKey(type);
139              if (pair != null) {
140                PublicKey key = pair.getPublic();
141                if (key != null) {
142                  authorizedKeys.add(key);
143                }
144              }
145            }
146          } else {
147            log.log(Level.FINE, "Ignoring invalid authorized key path " + authorizedKeyPath);
148          }
149        }
150    
151        // Get the authentication
152        AuthenticationPlugin authPlugin = AuthenticationPlugin.NULL;
153        String authentication = getContext().getProperty(AuthenticationPlugin.AUTH);
154        if (authentication != null) {
155          for (AuthenticationPlugin authenticationPlugin : getContext().getPlugins(AuthenticationPlugin.class)) {
156            if (authentication.equals(authenticationPlugin.getName())) {
157              authPlugin = authenticationPlugin;
158              break;
159            }
160          }
161        }
162    
163        //
164        log.log(Level.INFO, "Booting SSHD");
165        SSHLifeCycle lifeCycle = new SSHLifeCycle(getContext(), authPlugin, authorizedKeys);
166        lifeCycle.setPort(port);
167        lifeCycle.setKey(serverKey);
168        lifeCycle.init();
169    
170        //
171        this.lifeCycle = lifeCycle;
172      }
173    
174      @Override
175      public void destroy() {
176        if (lifeCycle != null) {
177          log.log(Level.INFO, "Shutting down SSHD");
178          lifeCycle.destroy();
179          lifeCycle = null;
180        }
181      }
182    }