org.apache.sshd.common.config.keys.KeyUtils Maven / Gradle / Ivy
The newest version!
/*
* 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.math.BigInteger;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermission;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.DSAKey;
import java.security.interfaces.DSAParams;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.ECKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.sshd.common.Factory;
import org.apache.sshd.common.cipher.ECCurves;
import org.apache.sshd.common.config.keys.impl.DSSPublicKeyEntryDecoder;
import org.apache.sshd.common.config.keys.impl.ECDSAPublicKeyEntryDecoder;
import org.apache.sshd.common.config.keys.impl.OpenSSHCertificateDecoder;
import org.apache.sshd.common.config.keys.impl.RSAPublicKeyDecoder;
import org.apache.sshd.common.config.keys.impl.SkECDSAPublicKeyEntryDecoder;
import org.apache.sshd.common.config.keys.impl.SkED25519PublicKeyEntryDecoder;
import org.apache.sshd.common.config.keys.u2f.SkED25519PublicKey;
import org.apache.sshd.common.config.keys.u2f.SkEcdsaPublicKey;
import org.apache.sshd.common.digest.BuiltinDigests;
import org.apache.sshd.common.digest.Digest;
import org.apache.sshd.common.digest.DigestFactory;
import org.apache.sshd.common.digest.DigestUtils;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.MapEntryUtils.MapBuilder;
import org.apache.sshd.common.util.MapEntryUtils.NavigableMapBuilder;
import org.apache.sshd.common.util.OsUtils;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
import org.apache.sshd.common.util.io.IoUtils;
import org.apache.sshd.common.util.security.SecurityUtils;
/**
* Utility class for keys
*
* @author Apache MINA SSHD Project
*/
public final class KeyUtils {
/**
* Name of algorithm for RSA keys to be used when calling security provider
*/
public static final String RSA_ALGORITHM = "RSA";
/**
* The most commonly used RSA public key exponent
*/
public static final BigInteger DEFAULT_RSA_PUBLIC_EXPONENT = BigInteger.valueOf(65537);
/**
* Name of algorithm for DSS keys to be used when calling security provider
*/
public static final String DSS_ALGORITHM = "DSA";
/**
* Name of algorithm for EC keys to be used when calling security provider
*/
public static final String EC_ALGORITHM = "EC";
/**
* The {@link Set} of {@link PosixFilePermission} not allowed if strict permissions are enforced on key files
*/
public static final Set STRICTLY_PROHIBITED_FILE_PERMISSION = Collections.unmodifiableSet(
EnumSet.of(
PosixFilePermission.GROUP_READ, PosixFilePermission.GROUP_WRITE, PosixFilePermission.GROUP_EXECUTE,
PosixFilePermission.OTHERS_READ, PosixFilePermission.OTHERS_WRITE, PosixFilePermission.OTHERS_EXECUTE));
/**
* System property that can be used to control the default fingerprint factory used for keys. If not set the
* {@link #DEFAULT_FINGERPRINT_DIGEST_FACTORY} is used
*/
public static final String KEY_FINGERPRINT_FACTORY_PROP = "org.apache.sshd.keyFingerprintFactory";
/**
* The default {@link Factory} of {@link Digest}s initialized as the value of
* {@link #getDefaultFingerPrintFactory()} if not overridden by {@link #KEY_FINGERPRINT_FACTORY_PROP} or
* {@link #setDefaultFingerPrintFactory(DigestFactory)}
*/
public static final DigestFactory DEFAULT_FINGERPRINT_DIGEST_FACTORY = BuiltinDigests.sha256;
/** @see https://tools.ietf.org/html/rfc8332#section-3 */
public static final String RSA_SHA256_KEY_TYPE_ALIAS = "rsa-sha2-256";
public static final String RSA_SHA512_KEY_TYPE_ALIAS = "rsa-sha2-512";
public static final String RSA_SHA256_CERT_TYPE_ALIAS = "[email protected]";
public static final String RSA_SHA512_CERT_TYPE_ALIAS = "[email protected]";
private static final AtomicReference DEFAULT_DIGEST_HOLDER = new AtomicReference<>();
private static final Map> BY_KEY_TYPE_DECODERS_MAP
= new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
// order matters here, when, for example, SkED25519PublicKeyEntryDecoder is used, it registers the PrivateKey
// type as java.security.PrivateKey, and all PrivateKey types are assignable to this, so it must be consulted last
private static final Map, PublicKeyEntryDecoder, ?>> BY_KEY_CLASS_DECODERS_MAP = new LinkedHashMap<>();
private static final Map KEY_TYPE_ALIASES
= NavigableMapBuilder. builder(String.CASE_INSENSITIVE_ORDER)
.put(RSA_SHA256_KEY_TYPE_ALIAS, KeyPairProvider.SSH_RSA)
.put(RSA_SHA512_KEY_TYPE_ALIAS, KeyPairProvider.SSH_RSA)
.put(RSA_SHA256_CERT_TYPE_ALIAS, KeyPairProvider.SSH_RSA_CERT)
.put(RSA_SHA512_CERT_TYPE_ALIAS, KeyPairProvider.SSH_RSA_CERT)
.build();
private static final Map SIGNATURE_ALGORITHM_MAP
= MapBuilder. builder()
.put(RSA_SHA256_CERT_TYPE_ALIAS, RSA_SHA256_KEY_TYPE_ALIAS)
.put(RSA_SHA512_CERT_TYPE_ALIAS, RSA_SHA512_KEY_TYPE_ALIAS)
.put(KeyPairProvider.SSH_RSA_CERT, KeyPairProvider.SSH_RSA)
.put(KeyPairProvider.SSH_DSS_CERT, KeyPairProvider.SSH_DSS)
.put(KeyPairProvider.SSH_ED25519_CERT, KeyPairProvider.SSH_ED25519)
.put(KeyPairProvider.SSH_ECDSA_SHA2_NISTP256_CERT, KeyPairProvider.ECDSA_SHA2_NISTP256)
.put(KeyPairProvider.SSH_ECDSA_SHA2_NISTP384_CERT, KeyPairProvider.ECDSA_SHA2_NISTP384)
.put(KeyPairProvider.SSH_ECDSA_SHA2_NISTP521_CERT, KeyPairProvider.ECDSA_SHA2_NISTP521)
.build();
static {
// order matters here, when, for example, SkED25519PublicKeyEntryDecoder is used, it registers the PrivateKey
// type as java.security.PrivateKey, and all PrivateKey types are assignable to this, so it must be consulted last
registerPublicKeyEntryDecoder(OpenSSHCertificateDecoder.INSTANCE);
registerPublicKeyEntryDecoder(RSAPublicKeyDecoder.INSTANCE);
registerPublicKeyEntryDecoder(DSSPublicKeyEntryDecoder.INSTANCE);
if (SecurityUtils.isECCSupported()) {
registerPublicKeyEntryDecoder(ECDSAPublicKeyEntryDecoder.INSTANCE);
}
if (SecurityUtils.isEDDSACurveSupported()) {
registerPublicKeyEntryDecoder(SecurityUtils.getEDDSAPublicKeyEntryDecoder());
}
// order matters, these must be last since they register their PrivateKey type as java.security.PrivateKey
// there is logical code which discovers a decoder type by instance assignability to this registered type
if (SecurityUtils.isECCSupported()) {
registerPublicKeyEntryDecoder(SkECDSAPublicKeyEntryDecoder.INSTANCE);
}
if (SecurityUtils.isEDDSACurveSupported()) {
registerPublicKeyEntryDecoder(SkED25519PublicKeyEntryDecoder.INSTANCE);
}
}
private KeyUtils() {
throw new UnsupportedOperationException("No instance");
}
/**
*
* Checks if a path has strict permissions
*
*
* -
*
* The path may not have {@link PosixFilePermission#OTHERS_EXECUTE} permission
*
*
*
* -
*
* (For {@code Unix}) The path may not have group or others permissions
*
*
*
* -
*
* (For {@code Unix}) If the path is a file, then its folder may not have group or others permissions
*
*
*
* -
*
* The path must be owned by current user.
*
*
*
* -
*
* (For {@code Unix}) The path may be owned by root.
*
*
*
* -
*
* (For {@code Unix}) If the path is a file, then its folder must also have valid owner.
*
*
*
*
*
* @param path The {@link Path} to be checked - ignored if {@code null} or does not exist
* @param options The {@link LinkOption}s to use to query the file's permissions
* @return The violated permission as {@link SimpleImmutableEntry} where key is a message and value is
* the offending object {@link PosixFilePermission} or {@link String} for owner - {@code null}
* if no violations detected
* @throws IOException If failed to retrieve the permissions
* @see #STRICTLY_PROHIBITED_FILE_PERMISSION
*/
public static SimpleImmutableEntry validateStrictKeyFilePermissions(Path path, LinkOption... options)
throws IOException {
if ((path == null) || (!Files.exists(path, options))) {
return null;
}
/*
* Android permission are not really consistent with standard Linux ones since the device is
* a "single" user O/S but with each application being a different "user". We therefore assume
* that if the application has access to a file, then it is good enough since there is really
* only one user, and we don't have to worry about multi-user access. Furthermore, the SE Linux
* security available on Android seems to be enough of a safeguard against inadvertent or even
* malicious access.
*/
if (OsUtils.isAndroid()) {
return null;
}
Collection perms = IoUtils.getPermissions(path, options);
if (GenericUtils.isEmpty(perms)) {
return null;
}
if (perms.contains(PosixFilePermission.OTHERS_EXECUTE)) {
PosixFilePermission p = PosixFilePermission.OTHERS_EXECUTE;
return new SimpleImmutableEntry<>(String.format("Permissions violation (%s)", p), p);
}
if (OsUtils.isUNIX()) {
PosixFilePermission p = IoUtils.validateExcludedPermissions(perms, STRICTLY_PROHIBITED_FILE_PERMISSION);
if (p != null) {
return new SimpleImmutableEntry<>(String.format("Permissions violation (%s)", p), p);
}
if (Files.isRegularFile(path, options)) {
Path parent = path.getParent();
p = IoUtils.validateExcludedPermissions(IoUtils.getPermissions(parent, options),
STRICTLY_PROHIBITED_FILE_PERMISSION);
if (p != null) {
return new SimpleImmutableEntry<>(String.format("Parent permissions violation (%s)", p), p);
}
}
}
String owner = IoUtils.getFileOwner(path, options);
if (GenericUtils.isEmpty(owner)) {
// we cannot get owner
// general issue: jvm does not support permissions
// security issue: specific filesystem does not support permissions
return null;
}
String current = OsUtils.getCurrentUser();
Set expected = new HashSet<>();
expected.add(current);
if (OsUtils.isUNIX()) {
// Windows "Administrator" was considered however in Windows most likely a group is used.
expected.add(OsUtils.ROOT_USER);
}
if (!expected.contains(owner)) {
return new SimpleImmutableEntry<>(String.format("Owner violation (%s)", owner), owner);
}
if (OsUtils.isUNIX()) {
if (Files.isRegularFile(path, options)) {
String parentOwner = IoUtils.getFileOwner(path.getParent(), options);
if ((!GenericUtils.isEmpty(parentOwner)) && (!expected.contains(parentOwner))) {
return new SimpleImmutableEntry<>(String.format("Parent owner violation (%s)", parentOwner), parentOwner);
}
}
}
return null;
}
/**
* Reads a single {@link PublicKey} from a public key file.
*
* @param path {@link Path} of the file to read; must not be {@code null}
* @return the {@link PublicKey}, may be {@code null} if the file is empty
* @throws IOException if the file cannot be read or parsed
* @throws GeneralSecurityException if the file contents cannot be read as a single {@link PublicKey}
*/
public static PublicKey loadPublicKey(Path path) throws IOException, GeneralSecurityException {
List keys = AuthorizedKeyEntry.readAuthorizedKeys(Objects.requireNonNull(path));
if (GenericUtils.isEmpty(keys)) {
return null;
}
if (keys.size() > 1) {
throw new InvalidKeySpecException("Public key file contains multiple entries: " + path);
}
return keys.get(0).resolvePublicKey(null, PublicKeyEntryResolver.IGNORING);
}
/**
* @param keyType The key type - {@code OpenSSH} name - e.g., {@code ssh-rsa, ssh-dss}
* @param keySize The key size (in bits)
* @return A {@link KeyPair} of the specified type and size
* @throws GeneralSecurityException If failed to generate the key pair
* @see #getPublicKeyEntryDecoder(String)
* @see PublicKeyEntryDecoder#generateKeyPair(int)
*/
public static KeyPair generateKeyPair(String keyType, int keySize) throws GeneralSecurityException {
PublicKeyEntryDecoder, ?> decoder = getPublicKeyEntryDecoder(keyType);
if (decoder == null) {
throw new InvalidKeySpecException("No decoder for key type=" + keyType);
}
return decoder.generateKeyPair(keySize);
}
/**
* Performs a deep-clone of the original {@link KeyPair} - i.e., creates new public/private keys that are
* clones of the original one
*
* @param keyType The key type - {@code OpenSSH} name - e.g., {@code ssh-rsa, ssh-dss}
* @param kp The {@link KeyPair} to clone - ignored if {@code null}
* @return The cloned instance
* @throws GeneralSecurityException If failed to clone the pair
*/
public static KeyPair cloneKeyPair(String keyType, KeyPair kp) throws GeneralSecurityException {
PublicKeyEntryDecoder, ?> decoder = getPublicKeyEntryDecoder(keyType);
if (decoder == null) {
throw new InvalidKeySpecException("No decoder for key type=" + keyType);
}
return decoder.cloneKeyPair(kp);
}
/**
* @param decoder The decoder to register
* @throws IllegalArgumentException if no decoder or not key type or no supported names for the decoder
* @see PublicKeyEntryDecoder#getPublicKeyType()
* @see PublicKeyEntryDecoder#getSupportedKeyTypes()
*/
public static void registerPublicKeyEntryDecoder(PublicKeyEntryDecoder, ?> decoder) {
Objects.requireNonNull(decoder, "No decoder specified");
Class> pubType = Objects.requireNonNull(decoder.getPublicKeyType(), "No public key type declared");
Class> prvType = Objects.requireNonNull(decoder.getPrivateKeyType(), "No private key type declared");
synchronized (BY_KEY_CLASS_DECODERS_MAP) {
BY_KEY_CLASS_DECODERS_MAP.put(pubType, decoder);
BY_KEY_CLASS_DECODERS_MAP.put(prvType, decoder);
}
registerPublicKeyEntryDecoderKeyTypes(decoder);
}
/**
* Registers the specified decoder for all the types it {@link PublicKeyEntryDecoder#getSupportedKeyTypes()
* supports}
*
* @param decoder The (never {@code null}) {@link PublicKeyEntryDecoder decoder} to register
* @see #registerPublicKeyEntryDecoderForKeyType(String, PublicKeyEntryDecoder)
*/
public static void registerPublicKeyEntryDecoderKeyTypes(PublicKeyEntryDecoder, ?> decoder) {
Objects.requireNonNull(decoder, "No decoder specified");
Collection names
= ValidateUtils.checkNotNullAndNotEmpty(decoder.getSupportedKeyTypes(), "No supported key types");
for (String n : names) {
PublicKeyEntryDecoder, ?> prev = registerPublicKeyEntryDecoderForKeyType(n, decoder);
if (prev != null) {
// noinspection UnnecessaryContinue
continue; // debug breakpoint
}
}
}
/**
* @param keyType The key (never {@code null}/empty) key type
* @param decoder The (never {@code null}) {@link PublicKeyEntryDecoder decoder} to register
* @return The previously registered decoder for this key type - {@code null} if none
*/
public static PublicKeyEntryDecoder, ?> registerPublicKeyEntryDecoderForKeyType(
String keyType, PublicKeyEntryDecoder, ?> decoder) {
keyType = ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type specified");
Objects.requireNonNull(decoder, "No decoder specified");
synchronized (BY_KEY_TYPE_DECODERS_MAP) {
return BY_KEY_TYPE_DECODERS_MAP.put(keyType, decoder);
}
}
/**
* @param decoder The (never {@code null}) {@link PublicKeyEntryDecoder decoder} to unregister
* @return The case insensitive {@link NavigableSet} of all the effectively un-registered key types
* out of all the {@link PublicKeyEntryDecoder#getSupportedKeyTypes() supported} ones.
* @see #unregisterPublicKeyEntryDecoderKeyTypes(PublicKeyEntryDecoder)
*/
public static NavigableSet unregisterPublicKeyEntryDecoder(PublicKeyEntryDecoder, ?> decoder) {
Objects.requireNonNull(decoder, "No decoder specified");
Class> pubType = Objects.requireNonNull(decoder.getPublicKeyType(), "No public key type declared");
Class> prvType = Objects.requireNonNull(decoder.getPrivateKeyType(), "No private key type declared");
synchronized (BY_KEY_CLASS_DECODERS_MAP) {
BY_KEY_CLASS_DECODERS_MAP.remove(pubType);
BY_KEY_CLASS_DECODERS_MAP.remove(prvType);
}
return unregisterPublicKeyEntryDecoderKeyTypes(decoder);
}
/**
* Unregisters the specified decoder for all the types it supports
*
* @param decoder The (never {@code null}) {@link PublicKeyEntryDecoder decoder} to unregister
* @return The case insensitive {@link NavigableSet} of all the effectively un-registered key types
* out of all the {@link PublicKeyEntryDecoder#getSupportedKeyTypes() supported} ones.
* @see #unregisterPublicKeyEntryDecoderForKeyType(String)
*/
public static NavigableSet unregisterPublicKeyEntryDecoderKeyTypes(PublicKeyEntryDecoder, ?> decoder) {
Objects.requireNonNull(decoder, "No decoder specified");
Collection names = ValidateUtils.checkNotNullAndNotEmpty(
decoder.getSupportedKeyTypes(), "No supported key types");
NavigableSet removed = Collections.emptyNavigableSet();
for (String n : names) {
PublicKeyEntryDecoder, ?> prev = unregisterPublicKeyEntryDecoderForKeyType(n);
if (prev == null) {
continue;
}
if (removed.isEmpty()) {
removed = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
}
if (!removed.add(n)) {
// noinspection UnnecessaryContinue
continue; // debug breakpoint
}
}
return removed;
}
/**
* Unregister the decoder registered for the specified key type
*
* @param keyType The key (never {@code null}/empty) key type
* @return The unregistered {@link PublicKeyEntryDecoder} - {@code null} if none registered for this key
* type
*/
public static PublicKeyEntryDecoder, ?> unregisterPublicKeyEntryDecoderForKeyType(String keyType) {
keyType = ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type specified");
synchronized (BY_KEY_TYPE_DECODERS_MAP) {
return BY_KEY_TYPE_DECODERS_MAP.remove(keyType);
}
}
/**
* @param keyType The {@code OpenSSH} key type string - e.g., {@code ssh-rsa, ssh-dss} - ignored if
* {@code null}/empty
* @return The registered {@link PublicKeyEntryDecoder} or {code null} if not found
*/
public static PublicKeyEntryDecoder, ?> getPublicKeyEntryDecoder(String keyType) {
if (GenericUtils.isEmpty(keyType)) {
return null;
}
synchronized (BY_KEY_TYPE_DECODERS_MAP) {
return BY_KEY_TYPE_DECODERS_MAP.get(keyType);
}
}
/**
* @param kp The {@link KeyPair} to examine - ignored if {@code null}
* @return The matching {@link PublicKeyEntryDecoder} provided both the public and private keys have the
* same decoder - {@code null} if no match found
* @see #getPublicKeyEntryDecoder(Key)
*/
public static PublicKeyEntryDecoder, ?> getPublicKeyEntryDecoder(KeyPair kp) {
if (kp == null) {
return null;
}
PublicKeyEntryDecoder, ?> d1 = getPublicKeyEntryDecoder(kp.getPublic());
PublicKeyEntryDecoder, ?> d2 = getPublicKeyEntryDecoder(kp.getPrivate());
if (d1 == d2) {
return d1;
} else {
return null; // some kind of mixed keys...
}
}
/**
* @param key The {@link Key} (public or private) - ignored if {@code null}
* @return The registered {@link PublicKeyEntryDecoder} for this key or {code null} if no match found
* @see #getPublicKeyEntryDecoder(Class)
*/
public static PublicKeyEntryDecoder, ?> getPublicKeyEntryDecoder(Key key) {
if (key == null) {
return null;
} else {
return getPublicKeyEntryDecoder(key.getClass());
}
}
/**
* @param keyType The key {@link Class} - ignored if {@code null} or not a {@link Key} compatible type
* @return The registered {@link PublicKeyEntryDecoder} or {code null} if no match found
*/
public static PublicKeyEntryDecoder, ?> getPublicKeyEntryDecoder(Class> keyType) {
if ((keyType == null) || (!Key.class.isAssignableFrom(keyType))) {
return null;
}
synchronized (BY_KEY_TYPE_DECODERS_MAP) {
PublicKeyEntryDecoder, ?> decoder = BY_KEY_CLASS_DECODERS_MAP.get(keyType);
if (decoder != null) {
return decoder;
}
// in case it is a derived class
for (PublicKeyEntryDecoder, ?> dec : BY_KEY_CLASS_DECODERS_MAP.values()) {
Class> pubType = dec.getPublicKeyType();
Class> prvType = dec.getPrivateKeyType();
if (pubType.isAssignableFrom(keyType) || prvType.isAssignableFrom(keyType)) {
return dec;
}
}
}
return null;
}
/**
* @return The default {@link DigestFactory} by the {@link #getFingerPrint(PublicKey)} and
* {@link #getFingerPrint(String)} methods
* @see #KEY_FINGERPRINT_FACTORY_PROP
* @see #setDefaultFingerPrintFactory(DigestFactory)
*/
public static DigestFactory getDefaultFingerPrintFactory() {
DigestFactory factory = null;
synchronized (DEFAULT_DIGEST_HOLDER) {
factory = DEFAULT_DIGEST_HOLDER.get();
if (factory != null) {
return factory;
}
String propVal = System.getProperty(KEY_FINGERPRINT_FACTORY_PROP);
if (GenericUtils.isEmpty(propVal)) {
factory = DEFAULT_FINGERPRINT_DIGEST_FACTORY;
} else {
factory = ValidateUtils.checkNotNull(BuiltinDigests.fromFactoryName(propVal), "Unknown digest factory: %s",
propVal);
}
ValidateUtils.checkTrue(factory.isSupported(), "Selected fingerprint digest not supported: %s", factory.getName());
DEFAULT_DIGEST_HOLDER.set(factory);
}
return factory;
}
/**
* @param f The {@link DigestFactory} of {@link Digest}s to be used - may not be {@code null}
*/
public static void setDefaultFingerPrintFactory(DigestFactory f) {
synchronized (DEFAULT_DIGEST_HOLDER) {
DEFAULT_DIGEST_HOLDER.set(Objects.requireNonNull(f, "No digest factory"));
}
}
/**
* @param key the public key - ignored if {@code null}
* @return the fingerprint or {@code null} if no key. Note: if exception encountered then returns the
* exception's simple class name
* @see #getFingerPrint(Factory, PublicKey)
*/
public static String getFingerPrint(PublicKey key) {
return getFingerPrint(getDefaultFingerPrintFactory(), key);
}
/**
* @param password The {@link String} to digest - ignored if {@code null}/empty, otherwise its UTF-8 representation
* is used as input for the fingerprint
* @return The fingerprint - {@code null} if {@code null}/empty input. Note: if exception
* encountered then returns the exception's simple class name
* @see #getFingerPrint(String, Charset)
*/
public static String getFingerPrint(String password) {
return getFingerPrint(password, StandardCharsets.UTF_8);
}
/**
* @param password The {@link String} to digest - ignored if {@code null}/empty
* @param charset The {@link Charset} to use in order to convert the string to its byte representation to use as
* input for the fingerprint
* @return The fingerprint - {@code null} if {@code null}/empty input. Note: if exception
* encountered then returns the exception's simple class name
* @see #getFingerPrint(Factory, String, Charset)
* @see #getDefaultFingerPrintFactory()
*/
public static String getFingerPrint(String password, Charset charset) {
return getFingerPrint(getDefaultFingerPrintFactory(), password, charset);
}
/**
* @param f The {@link Factory} to create the {@link Digest} to use
* @param key the public key - ignored if {@code null}
* @return the fingerprint or {@code null} if no key. Note: if exception encountered then returns the
* exception's simple class name
* @see #getFingerPrint(Digest, PublicKey)
*/
public static String getFingerPrint(Factory extends Digest> f, PublicKey key) {
return (key == null) ? null : getFingerPrint(Objects.requireNonNull(f, "No digest factory").create(), key);
}
/**
* @param d The {@link Digest} to use
* @param key the public key - ignored if {@code null}
* @return the fingerprint or {@code null} if no key. Note: if exception encountered then returns the
* exception's simple class name
* @see DigestUtils#getFingerPrint(Digest, byte[], int, int)
*/
public static String getFingerPrint(Digest d, PublicKey key) {
if (key == null) {
return null;
}
try {
Buffer buffer = new ByteArrayBuffer();
buffer.putRawPublicKey(key);
return DigestUtils.getFingerPrint(d, buffer.array(), 0, buffer.wpos());
} catch (Exception e) {
return e.toString();
}
}
public static byte[] getRawFingerprint(PublicKey key) throws Exception {
return getRawFingerprint(getDefaultFingerPrintFactory(), key);
}
public static byte[] getRawFingerprint(Factory extends Digest> f, PublicKey key) throws Exception {
return (key == null) ? null : getRawFingerprint(Objects.requireNonNull(f, "No digest factory").create(), key);
}
public static byte[] getRawFingerprint(Digest d, PublicKey key) throws Exception {
if (key == null) {
return null;
}
Buffer buffer = new ByteArrayBuffer();
buffer.putRawPublicKey(key);
return DigestUtils.getRawFingerprint(d, buffer.array(), 0, buffer.wpos());
}
/**
* @param f The {@link Factory} to create the {@link Digest} to use
* @param s The {@link String} to digest - ignored if {@code null}/empty, otherwise its UTF-8 representation is
* used as input for the fingerprint
* @return The fingerprint - {@code null} if {@code null}/empty input. Note: if exception encountered then
* returns the exception's simple class name
* @see #getFingerPrint(Digest, String, Charset)
*/
public static String getFingerPrint(Factory extends Digest> f, String s) {
return getFingerPrint(f, s, StandardCharsets.UTF_8);
}
/**
* @param f The {@link Factory} to create the {@link Digest} to use
* @param s The {@link String} to digest - ignored if {@code null}/empty
* @param charset The {@link Charset} to use in order to convert the string to its byte representation to use as
* input for the fingerprint
* @return The fingerprint - {@code null} if {@code null}/empty input Note: if exception encountered
* then returns the exception's simple class name
* @see DigestUtils#getFingerPrint(Digest, String, Charset)
*/
public static String getFingerPrint(Factory extends Digest> f, String s, Charset charset) {
return getFingerPrint(f.create(), s, charset);
}
/**
* @param d The {@link Digest} to use
* @param s The {@link String} to digest - ignored if {@code null}/empty, otherwise its UTF-8 representation is
* used as input for the fingerprint
* @return The fingerprint - {@code null} if {@code null}/empty input. Note: if exception encountered then
* returns the exception's simple class name
* @see DigestUtils#getFingerPrint(Digest, String, Charset)
*/
public static String getFingerPrint(Digest d, String s) {
return getFingerPrint(d, s, StandardCharsets.UTF_8);
}
/**
* @param d The {@link Digest} to use to calculate the fingerprint
* @param s The string to digest - ignored if {@code null}/empty
* @param charset The {@link Charset} to use in order to convert the string to its byte representation to use as
* input for the fingerprint
* @return The fingerprint - {@code null} if {@code null}/empty input. Note: if exception encountered
* then returns the exception's simple class name
* @see DigestUtils#getFingerPrint(Digest, String, Charset)
*/
public static String getFingerPrint(Digest d, String s, Charset charset) {
if (GenericUtils.isEmpty(s)) {
return null;
}
try {
return DigestUtils.getFingerPrint(d, s, charset);
} catch (Exception e) {
return e.getClass().getSimpleName();
}
}
/**
* @param expected The expected fingerprint if {@code null} or empty then returns a failure with the default
* fingerprint.
* @param key the {@link PublicKey} - if {@code null} then returns null.
* @return SimpleImmutableEntry - key is success indicator, value is actual fingerprint,
* {@code null} if no key.
* @see #getDefaultFingerPrintFactory()
* @see #checkFingerPrint(String, Factory, PublicKey)
*/
public static SimpleImmutableEntry checkFingerPrint(String expected, PublicKey key) {
return checkFingerPrint(expected, getDefaultFingerPrintFactory(), key);
}
/**
* @param expected The expected fingerprint if {@code null} or empty then returns a failure with the default
* fingerprint.
* @param f The {@link Factory} to be used to generate the default {@link Digest} for the key
* @param key the {@link PublicKey} - if {@code null} then returns null.
* @return SimpleImmutableEntry - key is success indicator, value is actual fingerprint,
* {@code null} if no key.
*/
public static SimpleImmutableEntry checkFingerPrint(
String expected, Factory extends Digest> f, PublicKey key) {
return checkFingerPrint(expected, Objects.requireNonNull(f, "No digest factory").create(), key);
}
/**
* @param expected The expected fingerprint if {@code null} or empty then returns a failure with the default
* fingerprint.
* @param d The {@link Digest} to be used to generate the default fingerprint for the key
* @param key the {@link PublicKey} - if {@code null} then returns null.
* @return SimpleImmutableEntry - key is success indicator, value is actual fingerprint,
* {@code null} if no key.
*/
public static SimpleImmutableEntry checkFingerPrint(String expected, Digest d, PublicKey key) {
if (key == null) {
return null;
}
if (GenericUtils.isEmpty(expected)) {
return new SimpleImmutableEntry<>(false, getFingerPrint(d, key));
}
// de-construct fingerprint
int pos = expected.indexOf(':');
if ((pos < 0) || (pos >= (expected.length() - 1))) {
return new SimpleImmutableEntry<>(false, getFingerPrint(d, key));
}
String name = expected.substring(0, pos);
String value = expected.substring(pos + 1);
DigestFactory expectedFactory;
// We know that all digest names have a length > 2 - if 2 (or less) then assume a pure HEX value
if (name.length() > 2) {
expectedFactory = BuiltinDigests.fromFactoryName(name);
if (expectedFactory == null) {
return new SimpleImmutableEntry<>(false, getFingerPrint(d, key));
}
expected = name.toUpperCase() + ":" + value;
} else {
expectedFactory = BuiltinDigests.md5;
expected = expectedFactory.getName().toUpperCase() + ":" + expected;
}
String fingerprint = getFingerPrint(expectedFactory, key);
boolean matches = BuiltinDigests.md5.getName().equals(expectedFactory.getName())
? expected.equalsIgnoreCase(fingerprint) // HEX is case insensitive
: expected.equals(fingerprint);
return new SimpleImmutableEntry<>(matches, fingerprint);
}
/**
* @param kp a key pair - ignored if {@code null}. If the private key is non-{@code null} then it is used to
* determine the type, otherwise the public one is used.
* @return the key type or {@code null} if cannot determine it
* @see #getKeyType(Key)
*/
public static String getKeyType(KeyPair kp) {
if (kp == null) {
return null;
}
PrivateKey key = kp.getPrivate();
if (key != null) {
return getKeyType(key);
} else {
return getKeyType(kp.getPublic());
}
}
/**
* @param key a public or private key
* @return the key type or {@code null} if cannot determine it
*/
public static String getKeyType(Key key) {
if (key == null) {
return null;
} else if (key instanceof DSAKey) {
return KeyPairProvider.SSH_DSS;
} else if (key instanceof RSAKey) {
return KeyPairProvider.SSH_RSA;
} else if (key instanceof ECKey) {
ECKey ecKey = (ECKey) key;
ECParameterSpec ecSpec = ecKey.getParams();
ECCurves curve = ECCurves.fromCurveParameters(ecSpec);
if (curve == null) {
return null; // debug breakpoint
} else {
return curve.getKeyType();
}
} else if (key instanceof SkEcdsaPublicKey) {
return SkECDSAPublicKeyEntryDecoder.KEY_TYPE;
} else if (SecurityUtils.EDDSA.equalsIgnoreCase(key.getAlgorithm())) {
return KeyPairProvider.SSH_ED25519;
} else if (key instanceof SkED25519PublicKey) {
return SkED25519PublicKeyEntryDecoder.KEY_TYPE;
} else if (key instanceof OpenSshCertificate) {
return ((OpenSshCertificate) key).getKeyType();
}
return null;
}
/**
* @param keyType A key type name - ignored if {@code null}/empty
* @return A {@link List} of they canonical key name and all its aliases
* @see #getCanonicalKeyType(String)
*/
public static List getAllEquivalentKeyTypes(String keyType) {
if (GenericUtils.isEmpty(keyType)) {
return Collections.emptyList();
}
String canonicalName = getCanonicalKeyType(keyType);
List equivalents = new ArrayList<>();
equivalents.add(canonicalName);
synchronized (KEY_TYPE_ALIASES) {
for (Map.Entry ae : KEY_TYPE_ALIASES.entrySet()) {
String alias = ae.getKey();
String name = ae.getValue();
if (canonicalName.equalsIgnoreCase(name)) {
equivalents.add(alias);
}
}
}
return equivalents;
}
/**
* @param keyType The available key-type - ignored if {@code null}/empty
* @return The canonical key type - same as input if no alias registered for the provided key type
* @see #RSA_SHA256_KEY_TYPE_ALIAS
* @see #RSA_SHA512_KEY_TYPE_ALIAS
*/
public static String getCanonicalKeyType(String keyType) {
if (GenericUtils.isEmpty(keyType)) {
return keyType;
}
String canonicalName;
synchronized (KEY_TYPE_ALIASES) {
canonicalName = KEY_TYPE_ALIASES.get(keyType);
}
if (GenericUtils.isEmpty(canonicalName)) {
return keyType;
}
return canonicalName;
}
/**
* @return A case insensitive {@link NavigableSet} of the currently registered key type "aliases".
* @see #getCanonicalKeyType(String)
*/
public static NavigableSet getRegisteredKeyTypeAliases() {
synchronized (KEY_TYPE_ALIASES) {
return KEY_TYPE_ALIASES.isEmpty()
? Collections.emptyNavigableSet()
: GenericUtils.asSortedSet(String.CASE_INSENSITIVE_ORDER, KEY_TYPE_ALIASES.keySet());
}
}
/**
* Registers a collection of aliases to a canonical key type
*
* @param keyType The (never {@code null}/empty) canonical name
* @param aliases The (never {@code null}/empty) aliases
* @return A {@link List} of the replaced aliases - empty if no previous aliases for the canonical name
*/
public static List registerCanonicalKeyTypes(String keyType, Collection aliases) {
ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type value");
ValidateUtils.checkNotNullAndNotEmpty(aliases, "No aliases provided");
List replaced = Collections.emptyList();
synchronized (KEY_TYPE_ALIASES) {
for (String a : aliases) {
ValidateUtils.checkNotNullAndNotEmpty(a, "Null/empty alias registration for %s", keyType);
String prev = KEY_TYPE_ALIASES.put(a, keyType);
if (GenericUtils.isEmpty(prev)) {
continue;
}
if (replaced.isEmpty()) {
replaced = new ArrayList<>();
}
replaced.add(prev);
}
}
return replaced;
}
/**
* @param alias The alias to unregister (ignored if {@code null}/empty)
* @return The associated canonical key type - {@code null} if alias not registered
*/
public static String unregisterCanonicalKeyTypeAlias(String alias) {
if (GenericUtils.isEmpty(alias)) {
return alias;
}
synchronized (KEY_TYPE_ALIASES) {
return KEY_TYPE_ALIASES.remove(alias);
}
}
/**
* Determines the key size in bits
*
* @param key The {@link Key} to examine - ignored if {@code null}
* @return The key size - non-positive value if cannot determine it
*/
public static int getKeySize(Key key) {
if (key == null) {
return -1;
} else if (key instanceof RSAKey) {
BigInteger n = ((RSAKey) key).getModulus();
return n.bitLength();
} else if (key instanceof DSAKey) {
DSAParams params = ((DSAKey) key).getParams();
BigInteger p = params.getP();
return p.bitLength();
} else if (key instanceof ECKey) {
ECParameterSpec ecSpec = ((ECKey) key).getParams();
ECCurves curve = ECCurves.fromCurveParameters(ecSpec);
if (curve != null) {
return curve.getKeySize();
}
} else if (SecurityUtils.EDDSA.equalsIgnoreCase(key.getAlgorithm())) {
return SecurityUtils.getEDDSAKeySize(key);
}
return -1;
}
/**
* @param key The {@link PublicKey} to be checked - ignored if {@code null}
* @param keySet The keys to be searched - ignored if {@code null}/empty
* @return The matching {@link PublicKey} from the keys or {@code null} if no match found
* @see #compareKeys(PublicKey, PublicKey)
*/
public static PublicKey findMatchingKey(PublicKey key, PublicKey... keySet) {
if (key == null || GenericUtils.isEmpty(keySet)) {
return null;
} else {
return findMatchingKey(key, Arrays.asList(keySet));
}
}
/**
* @param key The {@link PublicKey} to be checked - ignored if {@code null}
* @param keySet The keys to be searched - ignored if {@code null}/empty
* @return The matching {@link PublicKey} from the keys or {@code null} if no match found
* @see #compareKeys(PublicKey, PublicKey)
*/
public static PublicKey findMatchingKey(PublicKey key, Collection extends PublicKey> keySet) {
if (key == null || GenericUtils.isEmpty(keySet)) {
return null;
}
for (PublicKey k : keySet) {
if (compareKeys(key, k)) {
return k;
}
}
return null;
}
public static boolean compareKeyPairs(KeyPair k1, KeyPair k2) {
if (Objects.equals(k1, k2)) {
return true;
} else if ((k1 == null) || (k2 == null)) {
return false; // both null is covered by Objects#equals
} else {
return compareKeys(k1.getPublic(), k2.getPublic())
&& compareKeys(k1.getPrivate(), k2.getPrivate());
}
}
public static boolean compareKeys(PublicKey k1, PublicKey k2) {
if ((k1 instanceof RSAPublicKey) && (k2 instanceof RSAPublicKey)) {
return compareRSAKeys(RSAPublicKey.class.cast(k1), RSAPublicKey.class.cast(k2));
} else if ((k1 instanceof DSAPublicKey) && (k2 instanceof DSAPublicKey)) {
return compareDSAKeys(DSAPublicKey.class.cast(k1), DSAPublicKey.class.cast(k2));
} else if ((k1 instanceof ECPublicKey) && (k2 instanceof ECPublicKey)) {
return compareECKeys(ECPublicKey.class.cast(k1), ECPublicKey.class.cast(k2));
} else if ((k1 instanceof SkEcdsaPublicKey) && (k2 instanceof SkEcdsaPublicKey)) {
return compareSkEcdsaKeys(SkEcdsaPublicKey.class.cast(k1), SkEcdsaPublicKey.class.cast(k2));
} else if ((k1 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k1.getAlgorithm())
&& (k2 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k2.getAlgorithm())) {
return SecurityUtils.compareEDDSAPPublicKeys(k1, k2);
} else if ((k1 instanceof SkED25519PublicKey) && (k2 instanceof SkED25519PublicKey)) {
return compareSkEd25519Keys(SkED25519PublicKey.class.cast(k1), SkED25519PublicKey.class.cast(k2));
} else if ((k1 instanceof OpenSshCertificate) && (k2 instanceof OpenSshCertificate)) {
return compareOpenSSHCertificateKeys(OpenSshCertificate.class.cast(k1), OpenSshCertificate.class.cast(k2));
} else {
return false; // either key is null or not of same class
}
}
public static PublicKey recoverPublicKey(PrivateKey key) throws GeneralSecurityException {
if (key instanceof RSAPrivateKey) {
return recoverRSAPublicKey((RSAPrivateKey) key);
} else if (key instanceof DSAPrivateKey) {
return recoverDSAPublicKey((DSAPrivateKey) key);
} else if ((key != null) && SecurityUtils.EDDSA.equalsIgnoreCase(key.getAlgorithm())) {
return SecurityUtils.recoverEDDSAPublicKey(key);
} else {
return null;
}
}
public static boolean compareKeys(PrivateKey k1, PrivateKey k2) {
if ((k1 instanceof RSAPrivateKey) && (k2 instanceof RSAPrivateKey)) {
return compareRSAKeys(RSAPrivateKey.class.cast(k1), RSAPrivateKey.class.cast(k2));
} else if ((k1 instanceof DSAPrivateKey) && (k2 instanceof DSAPrivateKey)) {
return compareDSAKeys(DSAPrivateKey.class.cast(k1), DSAPrivateKey.class.cast(k2));
} else if ((k1 instanceof ECPrivateKey) && (k2 instanceof ECPrivateKey)) {
return compareECKeys(ECPrivateKey.class.cast(k1), ECPrivateKey.class.cast(k2));
} else if ((k1 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k1.getAlgorithm())
&& (k2 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k2.getAlgorithm())) {
return SecurityUtils.compareEDDSAPrivateKeys(k1, k2);
} else {
return false; // either key is null or not of same class
}
}
public static boolean compareRSAKeys(RSAPublicKey k1, RSAPublicKey k2) {
if (Objects.equals(k1, k2)) {
return true;
} else if (k1 == null || k2 == null) {
return false; // both null is covered by Objects#equals
} else {
return Objects.equals(k1.getPublicExponent(), k2.getPublicExponent())
&& Objects.equals(k1.getModulus(), k2.getModulus());
}
}
public static boolean compareRSAKeys(RSAPrivateKey k1, RSAPrivateKey k2) {
if (Objects.equals(k1, k2)) {
return true;
} else if (k1 == null || k2 == null) {
return false; // both null is covered by Objects#equals
} else {
return Objects.equals(k1.getModulus(), k2.getModulus())
&& Objects.equals(k1.getPrivateExponent(), k2.getPrivateExponent());
}
}
public static boolean compareOpenSSHCertificateKeys(OpenSshCertificate k1, OpenSshCertificate k2) {
if (k1 == k2) {
return true;
} else if (k1 == null || k2 == null) {
return false; // both null is covered above
} else {
return Objects.equals(k1.getSerial(), k2.getSerial())
&& Arrays.equals(k1.getSignature(), k2.getSignature())
&& compareKeys(k1.getCertPubKey(), k2.getCertPubKey());
}
}
public static RSAPublicKey recoverRSAPublicKey(RSAPrivateKey privateKey) throws GeneralSecurityException {
if (privateKey instanceof RSAPrivateCrtKey) {
return recoverFromRSAPrivateCrtKey((RSAPrivateCrtKey) privateKey);
} else {
// Not ideal, but best we can do under the circumstances
return recoverRSAPublicKey(privateKey.getModulus(), DEFAULT_RSA_PUBLIC_EXPONENT);
}
}
public static RSAPublicKey recoverFromRSAPrivateCrtKey(RSAPrivateCrtKey rsaKey) throws GeneralSecurityException {
return recoverRSAPublicKey(rsaKey.getPrimeP(), rsaKey.getPrimeQ(), rsaKey.getPublicExponent());
}
public static RSAPublicKey recoverRSAPublicKey(BigInteger p, BigInteger q, BigInteger publicExponent)
throws GeneralSecurityException {
return recoverRSAPublicKey(p.multiply(q), publicExponent);
}
public static RSAPublicKey recoverRSAPublicKey(BigInteger modulus, BigInteger publicExponent)
throws GeneralSecurityException {
KeyFactory kf = SecurityUtils.getKeyFactory(RSA_ALGORITHM);
return (RSAPublicKey) kf.generatePublic(new RSAPublicKeySpec(modulus, publicExponent));
}
public static boolean compareDSAKeys(DSAPublicKey k1, DSAPublicKey k2) {
if (Objects.equals(k1, k2)) {
return true;
} else if (k1 == null || k2 == null) {
return false; // both null is covered by Objects#equals
} else {
return Objects.equals(k1.getY(), k2.getY())
&& compareDSAParams(k1.getParams(), k2.getParams());
}
}
public static boolean compareDSAKeys(DSAPrivateKey k1, DSAPrivateKey k2) {
if (Objects.equals(k1, k2)) {
return true;
} else if (k1 == null || k2 == null) {
return false; // both null is covered by Objects#equals
} else {
return Objects.equals(k1.getX(), k2.getX())
&& compareDSAParams(k1.getParams(), k2.getParams());
}
}
public static boolean compareDSAParams(DSAParams p1, DSAParams p2) {
if (Objects.equals(p1, p2)) {
return true;
} else if (p1 == null || p2 == null) {
return false; // both null is covered by Objects#equals
} else {
return Objects.equals(p1.getG(), p2.getG())
&& Objects.equals(p1.getP(), p2.getP())
&& Objects.equals(p1.getQ(), p2.getQ());
}
}
// based on code from
// https://github.com/alexo/SAML-2.0/blob/master/java-opensaml/opensaml-security-api/src/main/java/org/opensaml/xml/security/SecurityHelper.java
public static DSAPublicKey recoverDSAPublicKey(DSAPrivateKey privateKey) throws GeneralSecurityException {
DSAParams keyParams = privateKey.getParams();
BigInteger p = keyParams.getP();
BigInteger x = privateKey.getX();
BigInteger q = keyParams.getQ();
BigInteger g = keyParams.getG();
BigInteger y = g.modPow(x, p);
KeyFactory kf = SecurityUtils.getKeyFactory(DSS_ALGORITHM);
return (DSAPublicKey) kf.generatePublic(new DSAPublicKeySpec(y, p, q, g));
}
public static boolean compareECKeys(ECPrivateKey k1, ECPrivateKey k2) {
if (Objects.equals(k1, k2)) {
return true;
} else if (k1 == null || k2 == null) {
return false; // both null is covered by Objects#equals
} else {
return Objects.equals(k1.getS(), k2.getS())
&& compareECParams(k1.getParams(), k2.getParams());
}
}
public static boolean compareECKeys(ECPublicKey k1, ECPublicKey k2) {
if (Objects.equals(k1, k2)) {
return true;
} else if (k1 == null || k2 == null) {
return false; // both null is covered by Objects#equals
} else {
return Objects.equals(k1.getW(), k2.getW())
&& compareECParams(k1.getParams(), k2.getParams());
}
}
public static boolean compareECParams(ECParameterSpec s1, ECParameterSpec s2) {
if (Objects.equals(s1, s2)) {
return true;
} else if (s1 == null || s2 == null) {
return false; // both null is covered by Objects#equals
} else {
return Objects.equals(s1.getOrder(), s2.getOrder())
&& (s1.getCofactor() == s2.getCofactor())
&& Objects.equals(s1.getGenerator(), s2.getGenerator())
&& Objects.equals(s1.getCurve(), s2.getCurve());
}
}
public static boolean compareSkEcdsaKeys(SkEcdsaPublicKey k1, SkEcdsaPublicKey k2) {
if (Objects.equals(k1, k2)) {
return true;
} else if (k1 == null || k2 == null) {
return false; // both null is covered by Objects#equals
} else {
return Objects.equals(k1.getAppName(), k2.getAppName())
&& Objects.equals(k1.isNoTouchRequired(), k2.isNoTouchRequired())
&& compareECKeys(k1.getDelegatePublicKey(), k2.getDelegatePublicKey());
}
}
public static boolean compareSkEd25519Keys(SkED25519PublicKey k1, SkED25519PublicKey k2) {
if (Objects.equals(k1, k2)) {
return true;
} else if (k1 == null || k2 == null) {
return false; // both null is covered by Objects#equals
} else {
return Objects.equals(k1.getAppName(), k2.getAppName())
&& Objects.equals(k1.isNoTouchRequired(), k2.isNoTouchRequired())
&& SecurityUtils.compareEDDSAPPublicKeys(k1.getDelegatePublicKey(), k2.getDelegatePublicKey());
}
}
public static String getSignatureAlgorithm(String chosenAlgorithm, PublicKey key) {
// check key as we know only certificates require a mapped signature algorithm currently
if (key instanceof OpenSshCertificate) {
synchronized (SIGNATURE_ALGORITHM_MAP) {
return SIGNATURE_ALGORITHM_MAP.get(chosenAlgorithm);
}
} else {
return chosenAlgorithm;
}
}
}