| // Copyright (C) 2008 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.gerrit.sshd; |
| |
| import com.google.common.io.BaseEncoding; |
| import com.google.gerrit.entities.Account; |
| import com.google.gerrit.server.CurrentUser; |
| import com.google.gerrit.server.IdentifiedUser; |
| import com.google.gerrit.server.account.AccountSshKey; |
| import com.google.gerrit.sshd.BaseCommand.Failure; |
| import com.google.gerrit.sshd.SshScope.Context; |
| import java.io.BufferedReader; |
| import java.io.IOException; |
| import java.io.StringReader; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.NoSuchProviderException; |
| import java.security.PublicKey; |
| import java.security.interfaces.DSAPublicKey; |
| import java.security.interfaces.RSAPublicKey; |
| import java.security.spec.InvalidKeySpecException; |
| import org.apache.sshd.common.SshException; |
| import org.apache.sshd.common.io.IoAcceptor; |
| import org.apache.sshd.common.io.IoSession; |
| import org.apache.sshd.common.keyprovider.KeyPairProvider; |
| import org.apache.sshd.common.session.helpers.AbstractSession; |
| import org.apache.sshd.common.util.buffer.ByteArrayBuffer; |
| import org.apache.sshd.server.session.ServerSession; |
| |
| /** Utilities to support SSH operations. */ |
| public class SshUtil { |
| /** |
| * Parse a public key into its Java type. |
| * |
| * @param key the account key to parse. |
| * @return the valid public key object. |
| * @throws InvalidKeySpecException the key supplied is not a valid SSH key. |
| * @throws NoSuchAlgorithmException the JVM is missing the key algorithm. |
| * @throws NoSuchProviderException the JVM is missing the provider. |
| */ |
| public static PublicKey parse(AccountSshKey key) |
| throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException { |
| try { |
| final String s = key.encodedKey(); |
| if (s == null) { |
| throw new InvalidKeySpecException("No key string"); |
| } |
| final byte[] bin = BaseEncoding.base64().decode(s); |
| String publicKeyAlgo = new ByteArrayBuffer(bin).getString(); |
| PublicKey publicKey = new ByteArrayBuffer(bin).getRawPublicKey(); |
| if (!key.algorithm().equals(publicKeyAlgo)) { |
| throw new InvalidKeyAlgorithmException(key.algorithm(), publicKeyAlgo, publicKey); |
| } |
| return publicKey; |
| } catch (RuntimeException | SshException e) { |
| throw new InvalidKeySpecException("Cannot parse key", e); |
| } |
| } |
| |
| /** |
| * Convert an RFC 4716 style key to an OpenSSH style key. |
| * |
| * @param keyStr the key string to convert. |
| * @return {@code keyStr} if conversion failed; otherwise the converted key, in OpenSSH key |
| * format. |
| */ |
| public static String toOpenSshPublicKey(String keyStr) { |
| try { |
| final StringBuilder strBuf = new StringBuilder(); |
| final BufferedReader br = new BufferedReader(new StringReader(keyStr)); |
| String line = br.readLine(); // BEGIN SSH2 line... |
| if (line == null || !line.equals("---- BEGIN SSH2 PUBLIC KEY ----")) { |
| return keyStr; |
| } |
| |
| while ((line = br.readLine()) != null) { |
| if (line.indexOf(':') == -1) { |
| strBuf.append(line); |
| break; |
| } |
| } |
| |
| while ((line = br.readLine()) != null) { |
| if (line.startsWith("---- ")) { |
| break; |
| } |
| strBuf.append(line); |
| } |
| |
| final PublicKey key = |
| new ByteArrayBuffer(BaseEncoding.base64().decode(strBuf.toString())).getRawPublicKey(); |
| if (key instanceof RSAPublicKey) { |
| strBuf.insert(0, KeyPairProvider.SSH_RSA + " "); |
| |
| } else if (key instanceof DSAPublicKey) { |
| strBuf.insert(0, KeyPairProvider.SSH_DSS + " "); |
| |
| } else { |
| return keyStr; |
| } |
| |
| strBuf.append(' '); |
| strBuf.append("converted-key"); |
| return strBuf.toString(); |
| } catch (IOException | RuntimeException e) { |
| return keyStr; |
| } |
| } |
| |
| public static boolean success( |
| final String username, |
| final ServerSession session, |
| final SshScope sshScope, |
| final SshLog sshLog, |
| final SshSession sd, |
| final CurrentUser user) { |
| if (sd.getUser() == null) { |
| sd.authenticationSuccess(username, user); |
| |
| // If this is the first time we've authenticated this |
| // session, record a login event in the log and add |
| // a close listener to record a logout event. |
| // |
| Context ctx = sshScope.newContext(sd, null); |
| Context old = sshScope.set(ctx); |
| try { |
| sshLog.onLogin(); |
| } finally { |
| sshScope.set(old); |
| } |
| |
| session.addCloseFutureListener( |
| future -> { |
| final Context ctx1 = sshScope.newContext(sd, null); |
| final Context old1 = sshScope.set(ctx1); |
| try { |
| sshLog.onLogout(); |
| } finally { |
| sshScope.set(old1); |
| } |
| }); |
| } |
| |
| return true; |
| } |
| |
| public static IdentifiedUser createUser( |
| final SshSession sd, |
| final IdentifiedUser.GenericFactory userFactory, |
| final Account.Id account) { |
| return userFactory.create(sd.getRemoteAddress(), account); |
| } |
| |
| public static void forEachSshSession(SshDaemon sshDaemon, SessionConsumer consumer) |
| throws Failure { |
| IoAcceptor ioAcceptor = sshDaemon.getIoAcceptor(); |
| if (ioAcceptor == null) { |
| throw new Failure(1, "fatal: sshd no longer running"); |
| } |
| ioAcceptor |
| .getManagedSessions() |
| .forEach( |
| (id, ioSession) -> { |
| AbstractSession abstractSession = AbstractSession.getSession(ioSession, true); |
| if (abstractSession != null) { |
| SshSession sshSession = abstractSession.getAttribute(SshSession.KEY); |
| if (sshSession != null) { |
| consumer.accept(id, sshSession, abstractSession, ioSession); |
| } |
| } |
| }); |
| } |
| |
| @FunctionalInterface |
| public interface SessionConsumer { |
| public void accept( |
| Long id, SshSession sshSession, AbstractSession abstractSession, IoSession ioSession); |
| } |
| } |