blob: 2cbb7b671fb7c7e2e23e0511c0b2a96f91732913 [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.openpgp;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.NavigableMap;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import org.apache.sshd.common.PropertyResolverUtils;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.OsUtils;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.io.PathUtils;
import org.c02e.jpgpj.CompressionAlgorithm;
import org.c02e.jpgpj.EncryptionAlgorithm;
import org.c02e.jpgpj.Key;
import org.c02e.jpgpj.Subkey;
/**
* TODO Add javadoc
*
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public final class PGPUtils {
public static final String DEFAULT_PGP_FILE_SUFFIX = ".gpg";
public static final String STD_LINUX_PGP_FOLDER_NAME = ".gnupg";
public static final String STD_WINDOWS_PGP_FOLDER_NAME = "gnupg";
/** Default MIME type for PGP encrypted files */
public static final String PGP_ENCRYPTED_FILE = "application/pgp-encrypted";
/** Alias for {@link EncryptionAlgorithm#Unencrypted Unencrypted} */
public static final String NO_CIPHER_PLACEHOLDER = PropertyResolverUtils.NONE_VALUE;
public static final Set<EncryptionAlgorithm> CIPHERS
= Collections.unmodifiableSet(EnumSet.allOf(EncryptionAlgorithm.class));
/** Alias for {@link CompressionAlgorithm#Uncompressed Uncompressed} */
public static final String NO_COMPRESSION_PLACEHOLDER = PropertyResolverUtils.NONE_VALUE;
public static final Set<CompressionAlgorithm> COMPRESSIONS
= Collections.unmodifiableSet(EnumSet.allOf(CompressionAlgorithm.class));
private static final AtomicReference<Supplier<? extends Path>> DEFAULT_PGP_PATH_RESOLVER_HOLDER = new AtomicReference<>();
private PGPUtils() {
throw new UnsupportedOperationException("No instance");
}
public static EncryptionAlgorithm fromCipherName(String name) {
if (GenericUtils.isEmpty(name)) {
return null;
}
if (NO_CIPHER_PLACEHOLDER.equalsIgnoreCase(name)) {
return EncryptionAlgorithm.Unencrypted;
}
return CIPHERS.stream()
.filter(c -> name.equalsIgnoreCase(c.name()))
.findFirst()
.orElse(null);
}
public static CompressionAlgorithm fromCompressionName(String name) {
if (GenericUtils.isEmpty(name)) {
return null;
}
if (NO_COMPRESSION_PLACEHOLDER.equalsIgnoreCase(name)) {
return CompressionAlgorithm.Uncompressed;
} else {
return COMPRESSIONS.stream()
.filter(c -> name.equalsIgnoreCase(c.name()))
.findFirst()
.orElse(null);
}
}
/**
* @param key The {@link Key} whose sub-keys to map - ignored if {@code null} or no sub-keys
* available
* @return A {@link NavigableMap} where key=the (case <U>insensitive</U>) fingerprint
* value, value=the matching {@link Subkey}
* @throws NullPointerException If key with {@code null} fingerprint encountered
* @throws IllegalArgumentException If key with empty fingerprint encountered
* @throws IllegalStateException If more than one key with same fingerprint found
* @see #mapSubKeysByFingerprint(Collection)
*/
public static NavigableMap<String, Subkey> mapSubKeysByFingerprint(Key key) {
return mapSubKeysByFingerprint((key == null) ? Collections.emptyList() : key.getSubkeys());
}
/**
* @param subKeys The {@link Subkey}-s to map - ignored if {@code null}/empty
* @return A {@link NavigableMap} where key=the (case <U>insensitive</U>) fingerprint
* value, value=the matching {@link Subkey}
* @throws NullPointerException If key with {@code null} fingerprint encountered
* @throws IllegalArgumentException If key with empty fingerprint encountered
* @throws IllegalStateException If more than one key with same fingerprint found
*/
public static NavigableMap<String, Subkey> mapSubKeysByFingerprint(Collection<? extends Subkey> subKeys) {
if (GenericUtils.isEmpty(subKeys)) {
return Collections.emptyNavigableMap();
}
NavigableMap<String, Subkey> keysMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
for (Subkey sk : subKeys) {
String fp = ValidateUtils.checkNotNullAndNotEmpty(sk.getFingerprint(), "No fingerprint for %s", sk);
Subkey prev = keysMap.put(fp, sk);
ValidateUtils.checkState(prev == null, "Multiple sub-keys with fingerprint=%s: %s / %s", fp, sk, prev);
}
return keysMap;
}
/**
* @param key The {@link Key} whose sub-keys to scan - ignored if {@code null} or has no sub-keys
* @param fingerprint The fingerprint to match (case <U>insensitive</U>) - ignored if {@code null}/empty
* @return The first matching {@link Subkey} - {@code null} if no match found
* @see #findSubkeyByFingerprint(Collection, String)
*/
public static Subkey findSubkeyByFingerprint(Key key, String fingerprint) {
return findSubkeyByFingerprint((key == null) ? Collections.emptyList() : key.getSubkeys(), fingerprint);
}
/**
* @param subKeys The {@link Subkey}-s to scan - ignored if {@code null}/empty
* @param fingerprint The fingerprint to match (case <U>insensitive</U>) - ignored if {@code null}/empty
* @return The first matching sub-key - {@code null} if no match found
*/
public static Subkey findSubkeyByFingerprint(Collection<? extends Subkey> subKeys, String fingerprint) {
if (GenericUtils.isEmpty(subKeys) || GenericUtils.isEmpty(fingerprint)) {
return null;
}
return subKeys.stream()
.filter(k -> fingerprint.equalsIgnoreCase(k.getFingerprint()))
.findFirst()
.orElse(null);
}
private static final class LazyDefaultPgpKeysFolderHolder {
private static final Path PATH = PathUtils.getUserHomeFolder()
.resolve(OsUtils.isWin32() ? STD_WINDOWS_PGP_FOLDER_NAME : STD_LINUX_PGP_FOLDER_NAME);
private LazyDefaultPgpKeysFolderHolder() {
throw new UnsupportedOperationException("No instance allowed");
}
}
/**
* @return The default <A HREF="https://www.gnupg.org/">Gnu Privacy Guard</A> folder used to hold key files.
*/
@SuppressWarnings("synthetic-access")
public static Path getDefaultPgpFolderPath() {
Supplier<? extends Path> resolver;
synchronized (DEFAULT_PGP_PATH_RESOLVER_HOLDER) {
resolver = DEFAULT_PGP_PATH_RESOLVER_HOLDER.get();
}
return (resolver == null) ? LazyDefaultPgpKeysFolderHolder.PATH : resolver.get();
}
/**
* Set the reported value from {@link #getDefaultPgpFolderPath()}
*
* @param resolver The {@link Path} provider to report - if {@code null} then O/S default value will be used
*/
public static void setDefaultPgpFolderPathResolver(Supplier<? extends Path> resolver) {
synchronized (DEFAULT_PGP_PATH_RESOLVER_HOLDER) {
DEFAULT_PGP_PATH_RESOLVER_HOLDER.set(resolver);
}
}
}