Line data Source code
1 : // Copyright (C) 2008 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.sshd; 16 : 17 : import com.google.common.io.BaseEncoding; 18 : import com.google.gerrit.entities.Account; 19 : import com.google.gerrit.server.CurrentUser; 20 : import com.google.gerrit.server.IdentifiedUser; 21 : import com.google.gerrit.server.account.AccountSshKey; 22 : import com.google.gerrit.sshd.BaseCommand.Failure; 23 : import com.google.gerrit.sshd.SshScope.Context; 24 : import java.io.BufferedReader; 25 : import java.io.IOException; 26 : import java.io.StringReader; 27 : import java.security.NoSuchAlgorithmException; 28 : import java.security.NoSuchProviderException; 29 : import java.security.PublicKey; 30 : import java.security.interfaces.DSAPublicKey; 31 : import java.security.interfaces.RSAPublicKey; 32 : import java.security.spec.InvalidKeySpecException; 33 : import org.apache.sshd.common.SshException; 34 : import org.apache.sshd.common.io.IoAcceptor; 35 : import org.apache.sshd.common.io.IoSession; 36 : import org.apache.sshd.common.keyprovider.KeyPairProvider; 37 : import org.apache.sshd.common.session.helpers.AbstractSession; 38 : import org.apache.sshd.common.util.buffer.ByteArrayBuffer; 39 : import org.apache.sshd.server.session.ServerSession; 40 : 41 : /** Utilities to support SSH operations. */ 42 0 : public class SshUtil { 43 : /** 44 : * Parse a public key into its Java type. 45 : * 46 : * @param key the account key to parse. 47 : * @return the valid public key object. 48 : * @throws InvalidKeySpecException the key supplied is not a valid SSH key. 49 : * @throws NoSuchAlgorithmException the JVM is missing the key algorithm. 50 : * @throws NoSuchProviderException the JVM is missing the provider. 51 : */ 52 : public static PublicKey parse(AccountSshKey key) 53 : throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException { 54 : try { 55 16 : final String s = key.encodedKey(); 56 16 : if (s == null) { 57 0 : throw new InvalidKeySpecException("No key string"); 58 : } 59 16 : final byte[] bin = BaseEncoding.base64().decode(s); 60 16 : return new ByteArrayBuffer(bin).getRawPublicKey(); 61 0 : } catch (RuntimeException | SshException e) { 62 0 : throw new InvalidKeySpecException("Cannot parse key", e); 63 : } 64 : } 65 : 66 : /** 67 : * Convert an RFC 4716 style key to an OpenSSH style key. 68 : * 69 : * @param keyStr the key string to convert. 70 : * @return {@code keyStr} if conversion failed; otherwise the converted key, in OpenSSH key 71 : * format. 72 : */ 73 : public static String toOpenSshPublicKey(String keyStr) { 74 : try { 75 16 : final StringBuilder strBuf = new StringBuilder(); 76 16 : final BufferedReader br = new BufferedReader(new StringReader(keyStr)); 77 16 : String line = br.readLine(); // BEGIN SSH2 line... 78 16 : if (line == null || !line.equals("---- BEGIN SSH2 PUBLIC KEY ----")) { 79 16 : return keyStr; 80 : } 81 : 82 0 : while ((line = br.readLine()) != null) { 83 0 : if (line.indexOf(':') == -1) { 84 0 : strBuf.append(line); 85 0 : break; 86 : } 87 : } 88 : 89 0 : while ((line = br.readLine()) != null) { 90 0 : if (line.startsWith("---- ")) { 91 0 : break; 92 : } 93 0 : strBuf.append(line); 94 : } 95 : 96 0 : final PublicKey key = 97 0 : new ByteArrayBuffer(BaseEncoding.base64().decode(strBuf.toString())).getRawPublicKey(); 98 0 : if (key instanceof RSAPublicKey) { 99 0 : strBuf.insert(0, KeyPairProvider.SSH_RSA + " "); 100 : 101 0 : } else if (key instanceof DSAPublicKey) { 102 0 : strBuf.insert(0, KeyPairProvider.SSH_DSS + " "); 103 : 104 : } else { 105 0 : return keyStr; 106 : } 107 : 108 0 : strBuf.append(' '); 109 0 : strBuf.append("converted-key"); 110 0 : return strBuf.toString(); 111 0 : } catch (IOException | RuntimeException e) { 112 0 : return keyStr; 113 : } 114 : } 115 : 116 : public static boolean success( 117 : final String username, 118 : final ServerSession session, 119 : final SshScope sshScope, 120 : final SshLog sshLog, 121 : final SshSession sd, 122 : final CurrentUser user) { 123 17 : if (sd.getUser() == null) { 124 17 : sd.authenticationSuccess(username, user); 125 : 126 : // If this is the first time we've authenticated this 127 : // session, record a login event in the log and add 128 : // a close listener to record a logout event. 129 : // 130 17 : Context ctx = sshScope.newContext(sd, null); 131 17 : Context old = sshScope.set(ctx); 132 : try { 133 17 : sshLog.onLogin(); 134 : } finally { 135 17 : sshScope.set(old); 136 : } 137 : 138 17 : session.addCloseFutureListener( 139 : future -> { 140 17 : final Context ctx1 = sshScope.newContext(sd, null); 141 17 : final Context old1 = sshScope.set(ctx1); 142 : try { 143 17 : sshLog.onLogout(); 144 : } finally { 145 17 : sshScope.set(old1); 146 : } 147 17 : }); 148 : } 149 : 150 17 : return true; 151 : } 152 : 153 : public static IdentifiedUser createUser( 154 : final SshSession sd, 155 : final IdentifiedUser.GenericFactory userFactory, 156 : final Account.Id account) { 157 16 : return userFactory.create(sd.getRemoteAddress(), account); 158 : } 159 : 160 : public static void forEachSshSession(SshDaemon sshDaemon, SessionConsumer consumer) 161 : throws Failure { 162 1 : IoAcceptor ioAcceptor = sshDaemon.getIoAcceptor(); 163 1 : if (ioAcceptor == null) { 164 0 : throw new Failure(1, "fatal: sshd no longer running"); 165 : } 166 1 : ioAcceptor 167 1 : .getManagedSessions() 168 1 : .forEach( 169 : (id, ioSession) -> { 170 1 : AbstractSession abstractSession = AbstractSession.getSession(ioSession, true); 171 1 : if (abstractSession != null) { 172 1 : SshSession sshSession = abstractSession.getAttribute(SshSession.KEY); 173 1 : if (sshSession != null) { 174 1 : consumer.accept(id, sshSession, abstractSession, ioSession); 175 : } 176 : } 177 1 : }); 178 1 : } 179 : 180 : @FunctionalInterface 181 : public interface SessionConsumer { 182 : public void accept( 183 : Long id, SshSession sshSession, AbstractSession abstractSession, IoSession ioSession); 184 : } 185 : }