/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.sshd.client.config.keys; import java.io.IOException; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.TreeMap; import java.util.function.Function; import org.apache.sshd.client.SshClient; import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.config.keys.BuiltinIdentities; import org.apache.sshd.common.config.keys.FilePasswordProvider; import org.apache.sshd.common.config.keys.IdentityUtils; import org.apache.sshd.common.config.keys.KeyUtils; import org.apache.sshd.common.config.keys.PublicKeyEntry; import org.apache.sshd.common.keyprovider.KeyPairProvider; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.ValidateUtils; import org.apache.sshd.common.util.io.FileInfoExtractor; import org.apache.sshd.common.util.io.IoUtils; /** * Provides keys loading capability from the user's keys folder - e.g., {@code id_rsa} * * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a> * @see org.apache.sshd.common.util.security.SecurityUtils#getKeyPairResourceParser() */ public final class ClientIdentity { public static final String ID_FILE_PREFIX = "id_"; public static final String ID_FILE_SUFFIX = ""; public static final Function<String, String> ID_GENERATOR = ClientIdentity::getIdentityFileName; private ClientIdentity() { throw new UnsupportedOperationException("No instance"); } /** * @param name The file name - ignored if {@code null}/empty * @return The identity type - {@code null} if cannot determine it - e.g., * does not start with the {@link #ID_FILE_PREFIX} */ public static String getIdentityType(String name) { if (GenericUtils.isEmpty(name) || (name.length() <= ID_FILE_PREFIX.length()) || (!name.startsWith(ID_FILE_PREFIX))) { return null; } else { return name.substring(ID_FILE_PREFIX.length()); } } public static String getIdentityFileName(NamedResource r) { return getIdentityFileName((r == null) ? null : r.getName()); } /** * @param type The identity type - e.g., {@code rsa} - ignored * if {@code null}/empty * @return The matching file name for the identity - {@code null} * if no name * @see #ID_FILE_PREFIX * @see #ID_FILE_SUFFIX * @see IdentityUtils#getIdentityFileName(String, String, String) */ public static String getIdentityFileName(String type) { return IdentityUtils.getIdentityFileName(ID_FILE_PREFIX, type, ID_FILE_SUFFIX); } /** * @param <C> The generic client class * @param client The {@link SshClient} to updated * @param strict If {@code true} then files that do not have the required * access rights are excluded from consideration * @param supportedOnly If {@code true} then ignore identities that are not * supported internally * @param provider A {@link FilePasswordProvider} - may be {@code null} * if the loaded keys are <U>guaranteed</U> not to be encrypted. The argument * to {@link FilePasswordProvider#getPassword(String)} is the path of the * file whose key is to be loaded * @param options The {@link LinkOption}s to apply when checking * for existence * @return The updated <tt>client</tt> instance - provided a non-{@code null} * {@link KeyPairProvider} was generated * @throws IOException If failed to access the file system * @throws GeneralSecurityException If failed to load the keys * @see #setKeyPairProvider(SshClient, Path, boolean, boolean, FilePasswordProvider, LinkOption...) */ public static <C extends SshClient> C setKeyPairProvider( C client, boolean strict, boolean supportedOnly, FilePasswordProvider provider, LinkOption... options) throws IOException, GeneralSecurityException { return setKeyPairProvider(client, PublicKeyEntry.getDefaultKeysFolderPath(), strict, supportedOnly, provider, options); } /** * @param <C> The generic client class * @param client The {@link SshClient} to updated * @param dir The folder to scan for the built-in identities * @param strict If {@code true} then files that do not have the required * access rights are excluded from consideration * @param supportedOnly If {@code true} then ignore identities that are not * supported internally * @param provider A {@link FilePasswordProvider} - may be {@code null} * if the loaded keys are <U>guaranteed</U> not to be encrypted. The argument * to {@link FilePasswordProvider#getPassword(String)} is the path of the * file whose key is to be loaded * @param options The {@link LinkOption}s to apply when checking * for existence * @return The updated <tt>client</tt> instance - provided a non-{@code null} * {@link KeyPairProvider} was generated * @throws IOException If failed to access the file system * @throws GeneralSecurityException If failed to load the keys * @see #loadDefaultKeyPairProvider(Path, boolean, boolean, FilePasswordProvider, LinkOption...) */ public static <C extends SshClient> C setKeyPairProvider( C client, Path dir, boolean strict, boolean supportedOnly, FilePasswordProvider provider, LinkOption... options) throws IOException, GeneralSecurityException { KeyPairProvider kpp = loadDefaultKeyPairProvider(dir, strict, supportedOnly, provider, options); if (kpp != null) { client.setKeyPairProvider(kpp); } return client; } /** * @param strict If {@code true} then files that do not have the required * access rights are excluded from consideration * @param supportedOnly If {@code true} then ignore identities that are not * supported internally * @param provider A {@link FilePasswordProvider} - may be {@code null} * if the loaded keys are <U>guaranteed</U> not to be encrypted. The argument * to {@link FilePasswordProvider#getPassword(String)} is the path of the * file whose key is to be loaded * @param options The {@link LinkOption}s to apply when checking * for existence * @return A {@link KeyPair} for the identities - {@code null} if no identities * available (e.g., after filtering unsupported ones or strict permissions) * @throws IOException If failed to access the file system * @throws GeneralSecurityException If failed to load the keys * @see PublicKeyEntry#getDefaultKeysFolderPath() * @see #loadDefaultIdentities(Path, boolean, FilePasswordProvider, LinkOption...) */ public static KeyPairProvider loadDefaultKeyPairProvider( boolean strict, boolean supportedOnly, FilePasswordProvider provider, LinkOption... options) throws IOException, GeneralSecurityException { return loadDefaultKeyPairProvider(PublicKeyEntry.getDefaultKeysFolderPath(), strict, supportedOnly, provider, options); } /** * @param dir The folder to scan for the built-in identities * @param strict If {@code true} then files that do not have the required * access rights are excluded from consideration * @param supportedOnly If {@code true} then ignore identities that are not * supported internally * @param provider A {@link FilePasswordProvider} - may be {@code null} * if the loaded keys are <U>guaranteed</U> not to be encrypted. The argument * to {@link FilePasswordProvider#getPassword(String)} is the path of the * file whose key is to be loaded * @param options The {@link LinkOption}s to apply when checking * for existence * @return A {@link KeyPair} for the identities - {@code null} if no identities * available (e.g., after filtering unsupported ones or strict permissions) * @throws IOException If failed to access the file system * @throws GeneralSecurityException If failed to load the keys * @see #loadDefaultIdentities(Path, boolean, FilePasswordProvider, LinkOption...) * @see IdentityUtils#createKeyPairProvider(Map, boolean) */ public static KeyPairProvider loadDefaultKeyPairProvider( Path dir, boolean strict, boolean supportedOnly, FilePasswordProvider provider, LinkOption... options) throws IOException, GeneralSecurityException { Map<String, KeyPair> ids = loadDefaultIdentities(dir, strict, provider, options); return IdentityUtils.createKeyPairProvider(ids, supportedOnly); } /** * @param strict If {@code true} then files that do not have the required * access rights are excluded from consideration * @param provider A {@link FilePasswordProvider} - may be {@code null} * if the loaded keys are <U>guaranteed</U> not to be encrypted. The argument * to {@link FilePasswordProvider#getPassword(String)} is the path of the * file whose key is to be loaded * @param options The {@link LinkOption}s to apply when checking * for existence * @return A {@link Map} of the found files where key=identity type (case * <U>insensitive</U>), value=the {@link KeyPair} of the identity * @throws IOException If failed to access the file system * @throws GeneralSecurityException If failed to load the keys * @see PublicKeyEntry#getDefaultKeysFolderPath() * @see #loadDefaultIdentities(Path, boolean, FilePasswordProvider, LinkOption...) */ public static Map<String, KeyPair> loadDefaultIdentities(boolean strict, FilePasswordProvider provider, LinkOption... options) throws IOException, GeneralSecurityException { return loadDefaultIdentities(PublicKeyEntry.getDefaultKeysFolderPath(), strict, provider, options); } /** * @param dir The folder to scan for the built-in identities * @param strict If {@code true} then files that do not have the required * access rights are excluded from consideration * @param provider A {@link FilePasswordProvider} - may be {@code null} * if the loaded keys are <U>guaranteed</U> not to be encrypted. The argument * to {@link FilePasswordProvider#getPassword(String)} is the path of the * file whose key is to be loaded * @param options The {@link LinkOption}s to apply when checking * for existence * @return A {@link Map} of the found files where key=identity type (case * <U>insensitive</U>), value=the {@link KeyPair} of the identity * @throws IOException If failed to access the file system * @throws GeneralSecurityException If failed to load the keys * @see #loadIdentities(Path, boolean, Collection, Function, FilePasswordProvider, LinkOption...) * @see BuiltinIdentities */ public static Map<String, KeyPair> loadDefaultIdentities(Path dir, boolean strict, FilePasswordProvider provider, LinkOption... options) throws IOException, GeneralSecurityException { return loadIdentities(dir, strict, BuiltinIdentities.NAMES, ID_GENERATOR, provider, options); } /** * Scans a folder and loads all available identity files * * @param dir The {@link Path} of the folder to scan - ignored if not exists * @param strict If {@code true} then files that do not have the required * access rights are excluded from consideration * @param types The identity types - ignored if {@code null}/empty * @param idGenerator A {@link Function} to derive the file name * holding the specified type * @param provider A {@link FilePasswordProvider} - may be {@code null} * if the loaded keys are <U>guaranteed</U> not to be encrypted. The argument * to {@link FilePasswordProvider#getPassword(String)} is the path of the * file whose key is to be loaded * @param options The {@link LinkOption}s to apply when checking * for existence * @return A {@link Map} of the found files where key=identity type (case * <U>insensitive</U>), value=the {@link KeyPair} of the identity * @throws IOException If failed to access the file system * @throws GeneralSecurityException If failed to load the keys * @see #scanIdentitiesFolder(Path, boolean, Collection, Function, LinkOption...) * @see IdentityUtils#loadIdentities(Map, FilePasswordProvider, java.nio.file.OpenOption...) */ public static Map<String, KeyPair> loadIdentities( Path dir, boolean strict, Collection<String> types, Function<String, String> idGenerator, FilePasswordProvider provider, LinkOption... options) throws IOException, GeneralSecurityException { Map<String, Path> paths = scanIdentitiesFolder(dir, strict, types, idGenerator, options); return IdentityUtils.loadIdentities(paths, provider, IoUtils.EMPTY_OPEN_OPTIONS); } /** * Scans a folder for possible identity files * * @param dir The {@link Path} of the folder to scan - ignored if not exists * @param strict If {@code true} then files that do not have the required * access rights are excluded from consideration * @param types The identity types - ignored if {@code null}/empty * @param idGenerator A {@link Function} to derive the file name * holding the specified type * @param options The {@link LinkOption}s to apply when checking * for existence * @return A {@link Map} of the found files where key=identity type (case * <U>insensitive</U>), value=the {@link Path} of the file holding the key * @throws IOException If failed to access the file system * @see KeyUtils#validateStrictKeyFilePermissions(Path, LinkOption...) */ public static Map<String, Path> scanIdentitiesFolder( Path dir, boolean strict, Collection<String> types, Function<String, String> idGenerator, LinkOption... options) throws IOException { if (GenericUtils.isEmpty(types)) { return Collections.emptyMap(); } if (!Files.exists(dir, options)) { return Collections.emptyMap(); } ValidateUtils.checkTrue(FileInfoExtractor.ISDIR.infoOf(dir, options), "Not a directory: %s", dir); Map<String, Path> paths = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); for (String t : types) { String fileName = idGenerator.apply(t); Path p = dir.resolve(fileName); if (!Files.exists(p, options)) { continue; } if (strict) { if (KeyUtils.validateStrictKeyFilePermissions(p, options) != null) { continue; } } Path prev = paths.put(t, p); ValidateUtils.checkTrue(prev == null, "Multiple mappings for type=%s", t); } return paths; } }