All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.sshd.common.config.keys.KeyUtils Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 34.0.0.Final
Show 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 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 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 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 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 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 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; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy