blob: b353dbc93460491449725f6d0dad66bf09a9aad8 [file] [log] [blame]
/*
* 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.common.config.keys;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.util.Collections;
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.keyprovider.MappedKeyPairProvider;
import org.apache.sshd.common.session.SessionContext;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.MapEntryUtils;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.io.resource.PathResource;
import org.apache.sshd.common.util.security.SecurityUtils;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public final class IdentityUtils {
private IdentityUtils() {
throw new UnsupportedOperationException("No instance");
}
private static final class LazyDefaultUserHomeFolderHolder {
private static final Path PATH
= Paths.get(ValidateUtils.checkNotNullAndNotEmpty(System.getProperty("user.home"), "No user home"))
.toAbsolutePath()
.normalize();
private LazyDefaultUserHomeFolderHolder() {
throw new UnsupportedOperationException("No instance allowed");
}
}
/**
* @return The {@link Path} to the currently running user home
*/
@SuppressWarnings("synthetic-access")
public static Path getUserHomeFolder() {
return LazyDefaultUserHomeFolderHolder.PATH;
}
/**
* @param prefix The file name prefix - ignored if {@code null}/empty
* @param type The identity type - ignored if {@code null}/empty
* @param suffix The file name suffix - ignored if {@code null}/empty
* @return The identity file name or {@code null} if no name
*/
public static String getIdentityFileName(String prefix, String type, String suffix) {
if (GenericUtils.isEmpty(type)) {
return null;
} else {
return GenericUtils.trimToEmpty(prefix)
+ type.toLowerCase()
+ GenericUtils.trimToEmpty(suffix);
}
}
/**
* @param ids A {@link Map} of the loaded identities where key=the identity type, value=the matching
* {@link KeyPair} - ignored if {@code null}/empty
* @param supportedOnly If {@code true} then ignore identities that are not supported internally
* @return A {@link KeyPair} for the identities - {@code null} if no identities available (e.g., after
* filtering unsupported ones)
* @see BuiltinIdentities
*/
public static KeyPairProvider createKeyPairProvider(Map<String, KeyPair> ids, boolean supportedOnly) {
if (MapEntryUtils.isEmpty(ids)) {
return null;
}
Map<String, KeyPair> pairsMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
ids.forEach((type, kp) -> {
BuiltinIdentities id = BuiltinIdentities.fromName(type);
if (id == null) {
id = BuiltinIdentities.fromKeyPair(kp);
}
if (supportedOnly && ((id == null) || (!id.isSupported()))) {
return;
}
String keyType = KeyUtils.getKeyType(kp);
if (GenericUtils.isEmpty(keyType)) {
return;
}
KeyPair prev = pairsMap.put(keyType, kp);
if (prev != null) {
return; // less of an offense if 2 pairs mapped to same key type
}
});
if (MapEntryUtils.isEmpty(pairsMap)) {
return null;
} else {
return new MappedKeyPairProvider(pairsMap);
}
}
/**
* @param session The {@link SessionContext} for invoking this load command - may be {@code null}
* if not invoked within a session context (e.g., offline tool or session unknown).
* @param paths A {@link Map} of the identities where key=identity type (case
* <U>insensitive</U>), value=the {@link Path} of file with the identity key
* @param provider A {@link FilePasswordProvider} - may be {@code null} if the loaded keys are
* <U>guaranteed</U> not to be encrypted. The argument to
* {@code FilePasswordProvider#getPassword} is the path of the file whose key is to
* be loaded
* @param options The {@link OpenOption}s to use when reading the key data
* @return A {@link NavigableMap} of the identities 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
*/
public static NavigableMap<String, KeyPair> loadIdentities(
SessionContext session, Map<String, ? extends Path> paths, FilePasswordProvider provider, OpenOption... options)
throws IOException, GeneralSecurityException {
if (MapEntryUtils.isEmpty(paths)) {
return Collections.emptyNavigableMap();
}
NavigableMap<String, KeyPair> ids = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
// Cannot use forEach because the potential for IOExceptions being thrown
for (Map.Entry<String, ? extends Path> pe : paths.entrySet()) {
String type = pe.getKey();
Path path = pe.getValue();
PathResource location = new PathResource(path, options);
Iterable<KeyPair> pairs;
try (InputStream inputStream = location.openInputStream()) {
pairs = SecurityUtils.loadKeyPairIdentities(session, location, inputStream, provider);
}
if (pairs == null) {
continue;
}
for (KeyPair kp : pairs) {
KeyPair prev = ids.put(type, kp);
ValidateUtils.checkTrue(prev == null, "Multiple keys for type=%s due to %s", type, path);
}
}
return ids;
}
}