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

org.apache.sshd.common.util.security.SecurityUtils 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.util.security;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.nio.file.Path;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.CertificateFactory;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;

import javax.crypto.Cipher;
import javax.crypto.KeyAgreement;
import javax.crypto.Mac;
import javax.crypto.spec.DHParameterSpec;

import org.apache.sshd.common.NamedResource;
import org.apache.sshd.common.PropertyResolverUtils;
import org.apache.sshd.common.config.keys.FilePasswordProvider;
import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder;
import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder;
import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser;
import org.apache.sshd.common.config.keys.loader.openssh.OpenSSHKeyPairResourceParser;
import org.apache.sshd.common.config.keys.loader.pem.PEMResourceParserUtils;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.random.JceRandomFactory;
import org.apache.sshd.common.random.RandomFactory;
import org.apache.sshd.common.session.SessionContext;
import org.apache.sshd.common.util.ExceptionUtils;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.common.util.security.bouncycastle.BouncyCastleEncryptedPrivateKeyInfoDecryptor;
import org.apache.sshd.common.util.security.bouncycastle.BouncyCastleGeneratorHostKeyProvider;
import org.apache.sshd.common.util.security.bouncycastle.BouncyCastleKeyPairResourceParser;
import org.apache.sshd.common.util.security.bouncycastle.BouncyCastleRandomFactory;
import org.apache.sshd.common.util.security.eddsa.EdDSASecurityProviderUtils;
import org.apache.sshd.common.util.threads.ThreadUtils;
import org.apache.sshd.server.keyprovider.AbstractGeneratorHostKeyProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Specific security providers related code
 *
 * @author Apache MINA SSHD Project
 */
public final class SecurityUtils {
    /**
     * Bouncy Castle {@link SecurityProviderRegistrar} name.
     */
    public static final String BOUNCY_CASTLE = "BC";

    /**
     * EDDSA support - should match {@code EdDSAKey.KEY_ALGORITHM}
     */
    public static final String EDDSA = "EdDSA";

    // A copy-paste from the original, but we don't want to drag the classes into the classpath
    // See EdDSAEngine.SIGNATURE_ALGORITHM
    public static final String CURVE_ED25519_SHA512 = "NONEwithEdDSA";

    /**
     * System property used to configure the value for the minimum supported Diffie-Hellman Group Exchange key size. If
     * not set, then an internal auto-discovery mechanism is employed. If set to negative value then Diffie-Hellman
     * Group Exchange is disabled. If set to a negative value then Diffie-Hellman Group Exchange is disabled
     */
    public static final String MIN_DHGEX_KEY_SIZE_PROP = "org.apache.sshd.minDHGexKeySize";

    /**
     * System property used to configure the value for the maximum supported Diffie-Hellman Group Exchange key size. If
     * not set, then an internal auto-discovery mechanism is employed. If set to negative value then Diffie-Hellman
     * Group Exchange is disabled. If set to a negative value then Diffie-Hellman Group Exchange is disabled
     */
    public static final String MAX_DHGEX_KEY_SIZE_PROP = "org.apache.sshd.maxDHGexKeySize";

    /**
     * The min. key size value used for testing whether Diffie-Hellman Group Exchange is supported or not. According to
     * RFC 4419 section 3: "Servers and clients SHOULD support
     * groups with a modulus length of k bits, where 1024 <= k <= 8192". 
     *
     * Note: this has been amended by RFC 8270
     */
    public static final int MIN_DHGEX_KEY_SIZE = 2048;
    public static final int PREFERRED_DHGEX_KEY_SIZE = 4096;
    public static final int MAX_DHGEX_KEY_SIZE = 8192;

    /**
     * Comma separated list of fully qualified {@link SecurityProviderRegistrar}s to automatically register
     */
    public static final String SECURITY_PROVIDER_REGISTRARS = "org.apache.sshd.security.registrars";
    public static final List DEFAULT_SECURITY_PROVIDER_REGISTRARS = Collections.unmodifiableList(
            Arrays.asList(
                    "org.apache.sshd.common.util.security.SunJCESecurityProviderRegistrar",
                    "org.apache.sshd.common.util.security.bouncycastle.BouncyCastleSecurityProviderRegistrar",
                    "org.apache.sshd.common.util.security.eddsa.EdDSASecurityProviderRegistrar"));

    /**
     * System property used to control whether to automatically register the {@code Bouncyastle} JCE provider
     *
     * @deprecated Please use "org.apache.sshd.security.provider.BC.enabled"
     */
    @Deprecated
    public static final String REGISTER_BOUNCY_CASTLE_PROP = "org.apache.sshd.registerBouncyCastle";

    /**
     * System property used to control whether Elliptic Curves are supported or not. If not set then the support is
     * auto-detected. Note: if set to {@code true} it is up to the user to make sure that indeed there is a
     * provider for them
     */
    public static final String ECC_SUPPORTED_PROP = "org.apache.sshd.eccSupport";

    /**
     * System property used to decide whether EDDSA curves are supported or not (in addition or even in spite of
     * {@link #isEDDSACurveSupported()}). If not set or set to {@code true}, then the existence of the optional support
     * classes determines the support.
     *
     * @deprecated Please use "org.apache.sshd.security.provider.EdDSA.enabled&qupt;
     */
    @Deprecated
    public static final String EDDSA_SUPPORTED_PROP = "org.apache.sshd.eddsaSupport";

    public static final String PROP_DEFAULT_SECURITY_PROVIDER = "org.apache.sshd.security.defaultProvider";

    /**
     * A boolean system property that can be set to {@code "true"} to enable FIPS mode. In FIPS mode, crypto-algorithms
     * not approved in FIPS-140 will not be available.
     * 

* Note: if this system property is not {@code "true"}, it can be overridden via {@link #setFipsMode()}. *

*/ public static final String FIPS_ENABLED = "org.apache.sshd.security.fipsEnabled"; private static final AtomicInteger MIN_DHG_KEY_SIZE_HOLDER = new AtomicInteger(0); private static final AtomicInteger MAX_DHG_KEY_SIZE_HOLDER = new AtomicInteger(0); /* * NOTE: we use a LinkedHashMap in order to preserve registration order in case several providers support the same * security entity */ private static final Map REGISTERED_PROVIDERS = new LinkedHashMap<>(); private static final AtomicReference KEYPAIRS_PARSER_HODLER = new AtomicReference<>(); // If an entry already exists for the named provider, then it overrides its SecurityProviderRegistrar#isEnabled() private static final Set APRIORI_DISABLED_PROVIDERS = new TreeSet<>(); private static final AtomicBoolean REGISTRATION_STATE_HOLDER = new AtomicBoolean(false); private static final Map, Map>> SECURITY_ENTITY_FACTORIES = new HashMap<>(); private static final AtomicReference DEFAULT_PROVIDER_HOLDER = new AtomicReference<>(); private static final AtomicReference FIPS_MODE = new AtomicReference<>(); private static Boolean hasEcc; private SecurityUtils() { throw new UnsupportedOperationException("No instance"); } /** * Unconditionally set FIPS mode, overriding the {@link #FIPS_ENABLED} system property. * * @throws IllegalStateException if a call to {@link #isFipsMode()} had already occurred and returned {@code false}. */ public static void setFipsMode() { if (!FIPS_MODE.compareAndSet(null, Boolean.TRUE)) { if (!Boolean.TRUE.equals(FIPS_MODE.get())) { throw new IllegalStateException("FIPS mode was already set to FALSE"); } } } /** * Tells whether FIPS mode is enabled, either through the system property {@link #FIPS_ENABLED} or via * {@link #setFipsMode()}. * * @return {@code true} if FIPS mode is enabled, {@code false} otherwise. */ public static boolean isFipsMode() { Boolean value = FIPS_MODE.get(); if (FIPS_MODE.get() == null) { value = Boolean.getBoolean(FIPS_ENABLED); if (!FIPS_MODE.compareAndSet(null, value)) { value = FIPS_MODE.get(); } } return value; } /** * @param name The provider's name - never {@code null}/empty * @return {@code true} if the provider is marked as disabled a-priori * @see #setAPrioriDisabledProvider(String, boolean) */ public static boolean isAPrioriDisabledProvider(String name) { ValidateUtils.checkNotNullAndNotEmpty(name, "No provider name specified"); synchronized (APRIORI_DISABLED_PROVIDERS) { return APRIORI_DISABLED_PROVIDERS.contains(name); } } /** * Marks a provider's registrar as "a-priori" programatically so that when its * {@link SecurityProviderRegistrar#isEnabled()} is eventually consulted it will return {@code false} regardless of * the configured value for the specific provider registrar instance. Note: has no effect if the provider has * already been registered. * * @param name The provider's name - never {@code null}/empty * @param disabled {@code true} whether to disable it a-priori * @see #isAPrioriDisabledProvider(String) */ public static void setAPrioriDisabledProvider(String name, boolean disabled) { ValidateUtils.checkNotNullAndNotEmpty(name, "No provider name specified"); synchronized (APRIORI_DISABLED_PROVIDERS) { if (disabled) { APRIORI_DISABLED_PROVIDERS.add(name); } else { APRIORI_DISABLED_PROVIDERS.remove(name); } } } /** * @return A copy if the current a-priori disabled providers names */ public static Set getAPrioriDisabledProviders() { synchronized (APRIORI_DISABLED_PROVIDERS) { return new TreeSet<>(APRIORI_DISABLED_PROVIDERS); } } /** * @return {@code true} if Elliptic Curve Cryptography is supported * @see #ECC_SUPPORTED_PROP */ public static boolean isECCSupported() { if (hasEcc == null) { String propValue = System.getProperty(ECC_SUPPORTED_PROP); if (GenericUtils.isEmpty(propValue)) { try { getKeyPairGenerator(KeyUtils.EC_ALGORITHM); hasEcc = Boolean.TRUE; } catch (Throwable t) { hasEcc = Boolean.FALSE; } } else { Logger logger = LoggerFactory.getLogger(SecurityUtils.class); logger.info("Override ECC support value: {}", propValue); hasEcc = Boolean.valueOf(propValue); } } return hasEcc; } /** * @return {@code true} if Diffie-Hellman Group Exchange is supported * @see #getMinDHGroupExchangeKeySize() * @see #getMaxDHGroupExchangeKeySize() */ public static boolean isDHGroupExchangeSupported() { int maxSize = getMaxDHGroupExchangeKeySize(); int minSize = getMinDHGroupExchangeKeySize(); return (minSize > 0) && (maxSize > 0) && (minSize <= maxSize); } /** * @param keySize The expected key size * @return {@code true} if Oakely Diffie-Hellman Group Exchange is supported for the specified key size * @see #isDHGroupExchangeSupported() * @see #getMaxDHGroupExchangeKeySize() */ public static boolean isDHOakelyGroupSupported(int keySize) { return isDHGroupExchangeSupported() && (getMaxDHGroupExchangeKeySize() >= keySize); } /** * @return The minimum supported Diffie-Hellman Group Exchange key size, or non-positive if not supported */ public static int getMinDHGroupExchangeKeySize() { return resolveDHGEXKeySizeValue(MIN_DHG_KEY_SIZE_HOLDER, MIN_DHGEX_KEY_SIZE_PROP, MIN_DHGEX_KEY_SIZE); } /** * Set programmatically the reported value for {@link #getMinDHGroupExchangeKeySize()} * * @param keySize The reported key size - if zero, then it will be auto-detected, if negative then DH group exchange * will be disabled */ public static void setMinDHGroupExchangeKeySize(int keySize) { synchronized (MIN_DHG_KEY_SIZE_HOLDER) { MIN_DHG_KEY_SIZE_HOLDER.set(keySize); } } /** * @return The maximum supported Diffie-Hellman Group Exchange key size, or non-positive if not supported */ public static int getMaxDHGroupExchangeKeySize() { return resolveDHGEXKeySizeValue(MAX_DHG_KEY_SIZE_HOLDER, MAX_DHGEX_KEY_SIZE_PROP, MAX_DHGEX_KEY_SIZE); } /** * Set programmatically the reported value for {@link #getMaxDHGroupExchangeKeySize()} * * @param keySize The reported key size - if zero, then it will be auto-detected, if negative then DH group exchange * will be disabled */ public static void setMaxDHGroupExchangeKeySize(int keySize) { synchronized (MAX_DHG_KEY_SIZE_HOLDER) { MAX_DHG_KEY_SIZE_HOLDER.set(keySize); } } private static int resolveDHGEXKeySizeValue( AtomicInteger holder, String propName, int maxKeySize) { int maxSupportedKeySize; synchronized (holder) { maxSupportedKeySize = holder.get(); if (maxSupportedKeySize != 0) { // 1st time we are called ? return maxSupportedKeySize; } String propValue = System.getProperty(propName); if (GenericUtils.isEmpty(propValue)) { maxSupportedKeySize = -1; // Go down from max. to min. to ensure we stop at 1st maximum value success for (int testKeySize = maxKeySize; testKeySize >= MIN_DHGEX_KEY_SIZE; testKeySize -= 1024) { if (isDHGroupExchangeSupported(testKeySize)) { maxSupportedKeySize = testKeySize; break; } } } else { Logger logger = LoggerFactory.getLogger(SecurityUtils.class); logger.info("Override DH group exchange key size via {}: {}", propName, propValue); maxSupportedKeySize = Integer.parseInt(propValue); // negative is OK - means user wants to disable DH group exchange ValidateUtils.checkTrue(maxSupportedKeySize != 0, "Configured " + propName + " value must be non-zero: %d", maxSupportedKeySize); } holder.set(maxSupportedKeySize); } return maxSupportedKeySize; } public static boolean isDHGroupExchangeSupported(int maxKeySize) { ValidateUtils.checkTrue(maxKeySize > Byte.SIZE, "Invalid max. key size: %d", maxKeySize); try { BigInteger r = BigInteger.ZERO.setBit(maxKeySize - 1); DHParameterSpec dhSkipParamSpec = new DHParameterSpec(r, r); KeyPairGenerator kpg = getKeyPairGenerator("DH"); kpg.initialize(dhSkipParamSpec); return true; } catch (GeneralSecurityException t) { return false; } } public static SecurityProviderChoice getDefaultProviderChoice() { SecurityProviderChoice choice; synchronized (DEFAULT_PROVIDER_HOLDER) { choice = DEFAULT_PROVIDER_HOLDER.get(); if (choice != null) { return choice; } String name = System.getProperty(PROP_DEFAULT_SECURITY_PROVIDER); choice = (GenericUtils.isEmpty(name) || PropertyResolverUtils.isNoneValue(name)) ? SecurityProviderChoice.EMPTY : SecurityProviderChoice.toSecurityProviderChoice(name); DEFAULT_PROVIDER_HOLDER.set(choice); } return choice; } public static void setDefaultProviderChoice(SecurityProviderChoice choice) { DEFAULT_PROVIDER_HOLDER.set(choice); } /** * @return A copy of the currently registered security providers */ public static Set getRegisteredProviders() { // returns a COPY of the providers in order to avoid modifications synchronized (REGISTERED_PROVIDERS) { return new TreeSet<>(REGISTERED_PROVIDERS.keySet()); } } public static boolean isBouncyCastleRegistered() { register(); return isProviderRegistered(BOUNCY_CASTLE); } public static boolean isProviderRegistered(String provider) { return getRegisteredProvider(provider) != null; } public static SecurityProviderRegistrar getRegisteredProvider(String provider) { ValidateUtils.checkNotNullAndNotEmpty(provider, "No provider name specified"); synchronized (REGISTERED_PROVIDERS) { return REGISTERED_PROVIDERS.get(provider); } } public static boolean isRegistrationCompleted() { return REGISTRATION_STATE_HOLDER.get(); } private static void register() { synchronized (REGISTRATION_STATE_HOLDER) { if (REGISTRATION_STATE_HOLDER.get()) { return; } String regsList = System.getProperty(SECURITY_PROVIDER_REGISTRARS, GenericUtils.join(DEFAULT_SECURITY_PROVIDER_REGISTRARS, ',')); boolean bouncyCastleRegistered = false; if ((GenericUtils.length(regsList) > 0) && (!PropertyResolverUtils.isNoneValue(regsList))) { String[] classes = GenericUtils.split(regsList, ','); Logger logger = LoggerFactory.getLogger(SecurityUtils.class); boolean debugEnabled = logger.isDebugEnabled(); for (String registrarClass : classes) { SecurityProviderRegistrar r; try { r = ThreadUtils.createDefaultInstance(SecurityUtils.class, SecurityProviderRegistrar.class, registrarClass); } catch (ReflectiveOperationException t) { Throwable e = ExceptionUtils.peelException(t); logger.error("Failed ({}) to create default {} registrar instance: {}", e.getClass().getSimpleName(), registrarClass, e.getMessage()); if (e instanceof RuntimeException) { throw (RuntimeException) e; } else if (e instanceof Error) { throw (Error) e; } else { throw new IllegalStateException(e); } } String name = r.getName(); SecurityProviderRegistrar registeredInstance = registerSecurityProvider(r); if (registeredInstance == null) { if (debugEnabled) { logger.debug("register({}) not registered - enabled={}, supported={}", name, r.isEnabled(), r.isSupported()); } continue; // provider not registered - e.g., disabled, not supported } if (BOUNCY_CASTLE.equalsIgnoreCase(name)) { bouncyCastleRegistered = true; } } } SecurityProviderChoice choice = getDefaultProviderChoice(); if (((choice == null) || (choice == SecurityProviderChoice.EMPTY)) && bouncyCastleRegistered) { setDefaultProviderChoice(SecurityProviderChoice.toSecurityProviderChoice(BOUNCY_CASTLE)); } REGISTRATION_STATE_HOLDER.set(true); } } /** * @param registrar The registrar instance to register * @return The registered instance - may be different than required if already registered. Returns * {@code null} if not already registered and not enabled or not supported registrar. */ public static SecurityProviderRegistrar registerSecurityProvider(SecurityProviderRegistrar registrar) { Objects.requireNonNull(registrar, "No registrar instance to register"); String name = registrar.getName(); SecurityProviderRegistrar registeredInstance = getRegisteredProvider(name); if ((registeredInstance == null) && registrar.isEnabled() && registrar.isSupported()) { try { SecurityProviderRegistrar.registerSecurityProvider(registrar); synchronized (REGISTERED_PROVIDERS) { REGISTERED_PROVIDERS.put(name, registrar); } return registrar; } catch (Throwable t) { Logger logger = LoggerFactory.getLogger(SecurityUtils.class); logger.error("Failed {} to register {} as a JCE provider: {}", t.getClass().getSimpleName(), name, t.getMessage()); throw new IllegalArgumentException("Failed to register " + name + " as a JCE provider", t); } } return registeredInstance; } ///////////////// Bouncycastle specific implementations ////////////////// /* -------------------------------------------------------------------- */ /** * @param session The {@link SessionContext} for invoking this load command - may be {@code null} * if not invoked within a session context (e.g., offline tool). * @param resourceKey An identifier of the key being loaded - used as argument to the * {@code FilePasswordProvider#getPassword} invocation * @param inputStream The {@link InputStream} for the private key * @param provider A {@link FilePasswordProvider} - may be {@code null} if the loaded key is * guaranteed not to be encrypted * @return The loaded {@link KeyPair}-s - or {@code null} if none loaded * @throws IOException If failed to read/parse the input stream * @throws GeneralSecurityException If failed to generate the keys */ public static Iterable loadKeyPairIdentities( SessionContext session, NamedResource resourceKey, InputStream inputStream, FilePasswordProvider provider) throws IOException, GeneralSecurityException { KeyPairResourceParser parser = getKeyPairResourceParser(); if (parser == null) { throw new NoSuchProviderException("No registered key-pair resource parser"); } Collection ids = parser.loadKeyPairs(session, resourceKey, provider, inputStream); int numLoaded = GenericUtils.size(ids); if (numLoaded <= 0) { return null; } return ids; } /* -------------------------------------------------------------------- */ public static AbstractGeneratorHostKeyProvider createGeneratorHostKeyProvider(Path path) { ValidateUtils.checkTrue(isBouncyCastleRegistered(), "BouncyCastle not registered"); return new BouncyCastleGeneratorHostKeyProvider(path); } public static KeyPairResourceParser getBouncycastleKeyPairResourceParser() { ValidateUtils.checkTrue(isBouncyCastleRegistered(), "BouncyCastle not registered"); return BouncyCastleKeyPairResourceParser.INSTANCE; } public static Decryptor getBouncycastleEncryptedPrivateKeyInfoDecryptor() { ValidateUtils.checkTrue(isBouncyCastleRegistered(), "BouncyCastle not registered"); return BouncyCastleEncryptedPrivateKeyInfoDecryptor.INSTANCE; } /** * @return If {@link #isBouncyCastleRegistered()} then a {@link BouncyCastleRandomFactory} instance, otherwise a * {@link JceRandomFactory} one */ public static RandomFactory getRandomFactory() { if (isBouncyCastleRegistered() && BouncyCastleRandomFactory.INSTANCE.isSupported()) { return BouncyCastleRandomFactory.INSTANCE; } else { return JceRandomFactory.INSTANCE; } } ///////////////////////////// ED25519 support /////////////////////////////// /** * @return {@code true} if EDDSA curves (e.g., {@code ed25519}) are supported */ public static boolean isEDDSACurveSupported() { if (isFipsMode()) { return false; } register(); SecurityProviderRegistrar r = getRegisteredProvider(EDDSA); return (r != null) && r.isEnabled() && r.isSupported(); } /* -------------------------------------------------------------------- */ public static PublicKeyEntryDecoder getEDDSAPublicKeyEntryDecoder() { if (!isEDDSACurveSupported()) { throw new UnsupportedOperationException(EDDSA + " provider N/A"); } return EdDSASecurityProviderUtils.getEDDSAPublicKeyEntryDecoder(); } public static PrivateKeyEntryDecoder getOpenSSHEDDSAPrivateKeyEntryDecoder() { if (!isEDDSACurveSupported()) { throw new UnsupportedOperationException(EDDSA + " provider N/A"); } return EdDSASecurityProviderUtils.getOpenSSHEDDSAPrivateKeyEntryDecoder(); } public static org.apache.sshd.common.signature.Signature getEDDSASigner() { if (isEDDSACurveSupported()) { return EdDSASecurityProviderUtils.getEDDSASignature(); } throw new UnsupportedOperationException(EDDSA + " Signer not available"); } public static int getEDDSAKeySize(Key key) { return EdDSASecurityProviderUtils.getEDDSAKeySize(key); } public static Class getEDDSAPublicKeyType() { return isEDDSACurveSupported() ? EdDSASecurityProviderUtils.getEDDSAPublicKeyType() : PublicKey.class; } public static Class getEDDSAPrivateKeyType() { return isEDDSACurveSupported() ? EdDSASecurityProviderUtils.getEDDSAPrivateKeyType() : PrivateKey.class; } public static boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2) { return isEDDSACurveSupported() ? EdDSASecurityProviderUtils.compareEDDSAPPublicKeys(k1, k2) : false; } public static boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2) { return isEDDSACurveSupported() ? EdDSASecurityProviderUtils.compareEDDSAPrivateKeys(k1, k2) : false; } public static PublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException { if (!isEDDSACurveSupported()) { throw new NoSuchAlgorithmException(EDDSA + " provider not supported"); } return EdDSASecurityProviderUtils.recoverEDDSAPublicKey(key); } public static PublicKey generateEDDSAPublicKey(String keyType, byte[] seed) throws GeneralSecurityException { if (!KeyPairProvider.SSH_ED25519.equals(keyType)) { throw new InvalidKeyException("Unsupported key type: " + keyType); } if (!isEDDSACurveSupported()) { throw new NoSuchAlgorithmException(EDDSA + " provider not supported"); } return EdDSASecurityProviderUtils.generateEDDSAPublicKey(seed); } public static B putRawEDDSAPublicKey(B buffer, PublicKey key) { if (!isEDDSACurveSupported()) { throw new UnsupportedOperationException(EDDSA + " provider not supported"); } return EdDSASecurityProviderUtils.putRawEDDSAPublicKey(buffer, key); } public static B putEDDSAKeyPair(B buffer, KeyPair kp) { return putEDDSAKeyPair(buffer, Objects.requireNonNull(kp, "No key pair").getPublic(), kp.getPrivate()); } public static B putEDDSAKeyPair(B buffer, PublicKey pubKey, PrivateKey prvKey) { if (!isEDDSACurveSupported()) { throw new UnsupportedOperationException(EDDSA + " provider not supported"); } return EdDSASecurityProviderUtils.putEDDSAKeyPair(buffer, pubKey, prvKey); } public static KeyPair extractEDDSAKeyPair(Buffer buffer, String keyType) throws GeneralSecurityException { if (!KeyPairProvider.SSH_ED25519.equals(keyType)) { throw new InvalidKeyException("Unsupported key type: " + keyType); } if (!isEDDSACurveSupported()) { throw new NoSuchAlgorithmException(EDDSA + " provider not supported"); } throw new GeneralSecurityException("Full SSHD-440 implementation N/A"); } ////////////////////////////////////////////////////////////////////////// public static KeyPairResourceParser getKeyPairResourceParser() { KeyPairResourceParser parser; synchronized (KEYPAIRS_PARSER_HODLER) { parser = KEYPAIRS_PARSER_HODLER.get(); if (parser != null) { return parser; } parser = KeyPairResourceParser.aggregate( PEMResourceParserUtils.PROXY, OpenSSHKeyPairResourceParser.INSTANCE); KEYPAIRS_PARSER_HODLER.set(parser); } return parser; } /** * @param parser The system-wide {@code KeyPairResourceParser} to use. If set to {@code null}, then the default * parser will be re-constructed on next call to {@link #getKeyPairResourceParser()} */ public static void setKeyPairResourceParser(KeyPairResourceParser parser) { synchronized (KEYPAIRS_PARSER_HODLER) { KEYPAIRS_PARSER_HODLER.set(parser); } } //////////////////////////// Security entities factories ///////////////////////////// @SuppressWarnings("unchecked") public static SecurityEntityFactory resolveSecurityEntityFactory( Class entityType, String algorithm, Predicate entitySelector) { Map> factoriesMap; synchronized (SECURITY_ENTITY_FACTORIES) { factoriesMap = SECURITY_ENTITY_FACTORIES.computeIfAbsent( entityType, k -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER)); } String effectiveName = SecurityProviderRegistrar.getEffectiveSecurityEntityName(entityType, algorithm); SecurityEntityFactory factoryEntry; synchronized (factoriesMap) { factoryEntry = factoriesMap.computeIfAbsent( effectiveName, k -> createSecurityEntityFactory(entityType, entitySelector)); } return (SecurityEntityFactory) factoryEntry; } public static SecurityEntityFactory createSecurityEntityFactory( Class entityType, Predicate entitySelector) { register(); SecurityProviderRegistrar registrar; synchronized (REGISTERED_PROVIDERS) { registrar = SecurityProviderRegistrar.findSecurityProviderRegistrarBySecurityEntity( entitySelector, REGISTERED_PROVIDERS.values()); } try { return SecurityEntityFactory.toFactory(entityType, registrar, getDefaultProviderChoice()); } catch (ReflectiveOperationException t) { Throwable e = ExceptionUtils.peelException(t); if (e instanceof RuntimeException) { throw (RuntimeException) e; } else if (e instanceof Error) { throw (Error) e; } else { throw new IllegalArgumentException(e); } } } public static KeyFactory getKeyFactory(String algorithm) throws GeneralSecurityException { SecurityEntityFactory factory = resolveSecurityEntityFactory(KeyFactory.class, algorithm, r -> r.isKeyFactorySupported(algorithm)); return factory.getInstance(algorithm); } public static Cipher getCipher(String transformation) throws GeneralSecurityException { SecurityEntityFactory factory = resolveSecurityEntityFactory(Cipher.class, transformation, r -> r.isCipherSupported(transformation)); return factory.getInstance(transformation); } public static MessageDigest getMessageDigest(String algorithm) throws GeneralSecurityException { SecurityEntityFactory factory = resolveSecurityEntityFactory(MessageDigest.class, algorithm, r -> r.isMessageDigestSupported(algorithm)); return factory.getInstance(algorithm); } public static KeyPairGenerator getKeyPairGenerator(String algorithm) throws GeneralSecurityException { SecurityEntityFactory factory = resolveSecurityEntityFactory(KeyPairGenerator.class, algorithm, r -> r.isKeyPairGeneratorSupported(algorithm)); return factory.getInstance(algorithm); } public static KeyAgreement getKeyAgreement(String algorithm) throws GeneralSecurityException { SecurityEntityFactory factory = resolveSecurityEntityFactory(KeyAgreement.class, algorithm, r -> r.isKeyAgreementSupported(algorithm)); return factory.getInstance(algorithm); } public static Mac getMac(String algorithm) throws GeneralSecurityException { SecurityEntityFactory factory = resolveSecurityEntityFactory(Mac.class, algorithm, r -> r.isMacSupported(algorithm)); return factory.getInstance(algorithm); } public static Signature getSignature(String algorithm) throws GeneralSecurityException { SecurityEntityFactory factory = resolveSecurityEntityFactory(Signature.class, algorithm, r -> r.isSignatureSupported(algorithm)); return factory.getInstance(algorithm); } public static CertificateFactory getCertificateFactory(String type) throws GeneralSecurityException { SecurityEntityFactory factory = resolveSecurityEntityFactory(CertificateFactory.class, type, r -> r.isCertificateFactorySupported(type)); return factory.getInstance(type); } }