http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/security/AbstractSecurityProviderRegistrar.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/AbstractSecurityProviderRegistrar.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/AbstractSecurityProviderRegistrar.java new file mode 100644 index 0000000..b665166 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/AbstractSecurityProviderRegistrar.java @@ -0,0 +1,129 @@ +/* + * 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.security.Provider; +import java.security.Security; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.sshd.common.util.ValidateUtils; +import org.apache.sshd.common.util.logging.AbstractLoggingBean; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public abstract class AbstractSecurityProviderRegistrar + extends AbstractLoggingBean + implements SecurityProviderRegistrar { + protected final Map<String, Object> props = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + protected final Map<Class<?>, Map<String, Boolean>> supportedEntities = new HashMap<>(); + protected final AtomicReference<Provider> providerHolder = new AtomicReference<>(null); + + private final String name; + + protected AbstractSecurityProviderRegistrar(String name) { + this.name = ValidateUtils.checkNotNullAndNotEmpty(name, "No name provided"); + } + + @Override + public final String getName() { + return name; + } + + @Override + public Map<String, Object> getProperties() { + return props; + } + + @Override + public boolean isSecurityEntitySupported(Class<?> entityType, String name) { + Map<String, Boolean> supportMap; + synchronized (supportedEntities) { + supportMap = supportedEntities.computeIfAbsent( + entityType, k -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER)); + } + + Boolean supportFlag; + synchronized (supportMap) { + supportFlag = supportMap.computeIfAbsent( + name, k -> SecurityProviderRegistrar.super.isSecurityEntitySupported(entityType, name)); + } + + return supportFlag; + } + + /** + * Attempts to see if a provider with this name already registered. If not, + * then uses reflection API in order to load and instantiate the specified + * <tt>providerClassName</tt> + * + * @param providerClassName The fully-qualified class name to instantiate + * if a provider not already registered + * @return The resolved {@link Provider} instance - <B>Note:</B> the result + * is <U>cached</U> - i.e., successful resolution result will not cause + * the code to re-resolve the provider + * @throws ReflectiveOperationException If failed to instantiate the provider + * @throws UnsupportedOperationException If registrar not supported + * @see #isSupported() + * @see Security#getProvider(String) + * @see #createProviderInstance(String) + */ + protected Provider getOrCreateProvider(String providerClassName) throws ReflectiveOperationException { + if (!isSupported()) { + throw new UnsupportedOperationException("Provider not supported"); + } + + Provider provider; + boolean created = false; + synchronized (providerHolder) { + provider = providerHolder.get(); + if (provider != null) { + return provider; + } + + provider = Security.getProvider(getName()); + if (provider == null) { + provider = createProviderInstance(providerClassName); + created = true; + } + providerHolder.set(provider); + } + + if (created) { + log.info("getOrCreateProvider({}) created instance of {}", getName(), providerClassName); + } else { + log.info("getOrCreateProvider({}) resolved instance of {}", getName(), provider.getClass().getName()); + } + + return provider; + } + + protected Provider createProviderInstance(String providerClassName) throws ReflectiveOperationException { + return SecurityProviderChoice.createProviderInstance(getClass(), providerClassName); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[" + getName() + "]"; + } +}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityEntityFactory.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityEntityFactory.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityEntityFactory.java new file mode 100644 index 0000000..94b9454 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityEntityFactory.java @@ -0,0 +1,194 @@ +/* + * 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.lang.reflect.Method; +import java.security.GeneralSecurityException; +import java.security.Provider; +import java.util.Objects; + +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.ValidateUtils; + +/** + * @param <T> Type of security entity being generated by this factory + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface SecurityEntityFactory<T> { + Class<T> getEntityType(); + + T getInstance(String algorithm) throws GeneralSecurityException; + + /** + * Uses reflection in order to wrap the {@code getInstance} method(s) + * as a security entity factory. + * + * @param <F> Type of entity being generated by the factor + * @param entityType The entity type class + * @param registrar The {@code SecurityProviderRegistrar} to use - if + * {@code null} then default provider is used (if specified). + * @param defaultProvider Default provider choice to use if no registrar + * provided. If {@code null}/empty then JCE default is used + * @return The {@link SecurityEntityFactory} for the entity + * @throws ReflectiveOperationException If failed to create the factory + * @see #toDefaultFactory(Class) + * @see #toNamedProviderFactory(Class, String) + * @see #toProviderInstanceFactory(Class, Provider) + * @see SecurityProviderChoice#isNamedProviderUsed() + * @see SecurityProviderChoice#getSecurityProvider() + */ + static <F> SecurityEntityFactory<F> toFactory( + Class<F> entityType, SecurityProviderChoice registrar, SecurityProviderChoice defaultProvider) + throws ReflectiveOperationException { + if (registrar == null) { + if ((defaultProvider == null) || (defaultProvider == SecurityProviderChoice.EMPTY)) { + return toDefaultFactory(entityType); + } else if (defaultProvider.isNamedProviderUsed()) { + return toNamedProviderFactory(entityType, defaultProvider.getName()); + } else { + return toProviderInstanceFactory(entityType, defaultProvider.getSecurityProvider()); + } + } else if (registrar.isNamedProviderUsed()) { + return toNamedProviderFactory(entityType, registrar.getName()); + } else { + return toProviderInstanceFactory(entityType, registrar.getSecurityProvider()); + } + } + + static <F> SecurityEntityFactory<F> toDefaultFactory(Class<F> entityType) + throws ReflectiveOperationException { + Method m = entityType.getDeclaredMethod("getInstance", String.class); + return new SecurityEntityFactory<F>() { + private final String s = SecurityEntityFactory.class.getSimpleName() + + "[" + entityType.getSimpleName() + "]" + + "[default]"; + + @Override + public Class<F> getEntityType() { + return entityType; + } + + @Override + public F getInstance(String algorithm) throws GeneralSecurityException { + try { + Object value = m.invoke(null, algorithm); + return entityType.cast(value); + } catch (ReflectiveOperationException t) { + Throwable e = GenericUtils.peelException(t); + if (e instanceof GeneralSecurityException) { + throw (GeneralSecurityException) e; + } else if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } else if (e instanceof Error) { + throw (Error) e; + } else { + throw new GeneralSecurityException(e); + } + } + } + + @Override + public String toString() { + return s; + } + }; + } + + static <F> SecurityEntityFactory<F> toNamedProviderFactory(Class<F> entityType, String name) + throws ReflectiveOperationException { + ValidateUtils.checkNotNullAndNotEmpty(name, "No provider name specified"); + Method m = entityType.getDeclaredMethod("getInstance", String.class, String.class); + return new SecurityEntityFactory<F>() { + private final String s = SecurityEntityFactory.class.getSimpleName() + + "[" + entityType.getSimpleName() + "]" + + "[" + name + "]"; + + @Override + public Class<F> getEntityType() { + return entityType; + } + + @Override + public F getInstance(String algorithm) throws GeneralSecurityException { + try { + Object value = m.invoke(null, algorithm, name); + return entityType.cast(value); + } catch (ReflectiveOperationException t) { + Throwable e = GenericUtils.peelException(t); + if (e instanceof GeneralSecurityException) { + throw (GeneralSecurityException) e; + } else if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } else if (e instanceof Error) { + throw (Error) e; + } else { + throw new GeneralSecurityException(e); + } + } + } + + @Override + public String toString() { + return s; + } + }; + } + + static <F> SecurityEntityFactory<F> toProviderInstanceFactory(Class<F> entityType, Provider provider) + throws ReflectiveOperationException { + Objects.requireNonNull(provider, "No provider instance"); + Method m = entityType.getDeclaredMethod("getInstance", String.class, Provider.class); + return new SecurityEntityFactory<F>() { + private final String s = SecurityEntityFactory.class.getSimpleName() + + "[" + entityType.getSimpleName() + "]" + + "[" + Provider.class.getSimpleName() + "]" + + "[" + provider.getName() + "]"; + + @Override + public Class<F> getEntityType() { + return entityType; + } + + @Override + public F getInstance(String algorithm) throws GeneralSecurityException { + try { + Object value = m.invoke(null, algorithm, provider); + return entityType.cast(value); + } catch (ReflectiveOperationException t) { + Throwable e = GenericUtils.peelException(t); + if (e instanceof GeneralSecurityException) { + throw (GeneralSecurityException) e; + } else if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } else if (e instanceof Error) { + throw (Error) e; + } else { + throw new GeneralSecurityException(e); + } + } + } + + @Override + public String toString() { + return s; + } + }; + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderChoice.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderChoice.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderChoice.java new file mode 100644 index 0000000..c12e747 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderChoice.java @@ -0,0 +1,130 @@ +/* + * 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.security.Provider; +import java.util.Objects; + +import org.apache.sshd.common.NamedResource; +import org.apache.sshd.common.util.ValidateUtils; +import org.apache.sshd.common.util.threads.ThreadUtils; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface SecurityProviderChoice extends NamedResource { + SecurityProviderChoice EMPTY = new SecurityProviderChoice() { + @Override + public String getName() { + return null; + } + + @Override + public boolean isNamedProviderUsed() { + return false; + } + + @Override + public Provider getSecurityProvider() { + return null; + } + + @Override + public String toString() { + return "EMPTY"; + } + }; + + /** + * @return {@code true} if to use the provider's name rather than its + * {@link Provider} instance - default={@code true}. + */ + default boolean isNamedProviderUsed() { + return true; + } + + /** + * @return The security {@link Provider} to use in case {@link #isNamedProviderUsed()} + * is {@code false}. Can be {@code null} if {@link #isNamedProviderUsed()} is {@code true}, + * but not recommended. + */ + Provider getSecurityProvider(); + + static SecurityProviderChoice toSecurityProviderChoice(String name) { + ValidateUtils.checkNotNullAndNotEmpty(name, "No name provided"); + return new SecurityProviderChoice() { + private final String s = SecurityProviderChoice.class.getSimpleName() + "[" + name + "]"; + + @Override + public String getName() { + return name; + } + + @Override + public boolean isNamedProviderUsed() { + return true; + } + + @Override + public Provider getSecurityProvider() { + return null; + } + + @Override + public String toString() { + return s; + } + }; + } + + static SecurityProviderChoice toSecurityProviderChoice(Provider provider) { + Objects.requireNonNull(provider, "No provider instance"); + return new SecurityProviderChoice() { + private final String s = SecurityProviderChoice.class.getSimpleName() + + "[" + Provider.class.getSimpleName() + "]" + + "[" + provider.getName() + "]"; + + @Override + public String getName() { + return provider.getName(); + } + + @Override + public boolean isNamedProviderUsed() { + return false; + } + + @Override + public Provider getSecurityProvider() { + return provider; + } + + @Override + public String toString() { + return s; + } + }; + } + + static Provider createProviderInstance(Class<?> anchor, String providerClassName) + throws ReflectiveOperationException { + return ThreadUtils.createDefaultInstance(anchor, Provider.class, providerClassName); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderRegistrar.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderRegistrar.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderRegistrar.java new file mode 100644 index 0000000..31e428b --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderRegistrar.java @@ -0,0 +1,337 @@ +/* + * 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.security.KeyFactory; +import java.security.KeyPairGenerator; +import java.security.MessageDigest; +import java.security.Provider; +import java.security.Security; +import java.security.Signature; +import java.security.cert.CertificateFactory; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Predicate; + +import javax.crypto.Cipher; +import javax.crypto.KeyAgreement; +import javax.crypto.Mac; + +import org.apache.sshd.common.OptionalFeature; +import org.apache.sshd.common.PropertyResolver; +import org.apache.sshd.common.PropertyResolverUtils; +import org.apache.sshd.common.SyspropsMapWrapper; +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.IgnoringEmptyMap; +import org.apache.sshd.common.util.ValidateUtils; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface SecurityProviderRegistrar extends SecurityProviderChoice, OptionalFeature, PropertyResolver { + /** + * Base name for configuration properties related to security providers + */ + String CONFIG_PROP_BASE = "org.apache.sshd.security.provider"; + + /** + * Property used to configure whether the provider is enabled regardless of + * whether it is supported. + * + * @see #isEnabled() + */ + String ENABLED_PROPERTY = "enabled"; + + /** + * Property used to configure whether to use the provider's name rather than its + * {@link Provider} instance + * + * @see #isNamedProviderUsed() + */ + String NAMED_PROVIDER_PROPERTY = "useNamed"; + + String ALL_OPTIONS_VALUE = "all"; + String ALL_OPTIONS_WILDCARD = "*"; + + String NO_OPTIONS_VALUE = "none"; + + /** + * All the entities that are used in calls to {@link #isSecurityEntitySupported(Class, String)} + */ + List<Class<?>> SECURITY_ENTITIES = + Collections.unmodifiableList( + Arrays.asList( + Cipher.class, KeyFactory.class, MessageDigest.class, + KeyPairGenerator.class, KeyAgreement.class, Mac.class, + Signature.class, CertificateFactory.class)); + + default String getBasePropertyName() { + return CONFIG_PROP_BASE + "." + getName(); + } + + default String getConfigurationPropertyName(String name) { + return getBasePropertyName() + "." + name; + } + + /** + * @return {@code true} if the provider is enabled regardless of + * whether it is supported - default={@code true}. <B>Note:</B> + * checks if the provider has been <U>programmatically</U> disabled + * via {@link SecurityUtils#setAPrioriDisabledProvider(String, boolean)} + * @see #ENABLED_PROPERTY + */ + default boolean isEnabled() { + if (SecurityUtils.isAPrioriDisabledProvider(getName())) { + return false; + } + + return this.getBooleanProperty(getConfigurationPropertyName(ENABLED_PROPERTY), true); + } + + @Override + default PropertyResolver getParentPropertyResolver() { + return SyspropsMapWrapper.SYSPROPS_RESOLVER; + } + + @Override + default Map<String, Object> getProperties() { + return IgnoringEmptyMap.getInstance(); + } + + /** + * @param transformation The requested {@link Cipher} transformation + * @return {@code true} if this security provider supports the transformation + * @see #isSecurityEntitySupported(Class, String) + */ + default boolean isCipherSupported(String transformation) { + return isSecurityEntitySupported(Cipher.class, transformation); + } + + /** + * @param algorithm The {@link KeyFactory} algorithm + * @return {@code true} if this security provider supports the algorithm + * @see #isSecurityEntitySupported(Class, String) + */ + default boolean isKeyFactorySupported(String algorithm) { + return isSecurityEntitySupported(KeyFactory.class, algorithm); + } + + /** + * @param algorithm The {@link MessageDigest} algorithm + * @return {@code true} if this security provider supports the algorithm + * @see #isSecurityEntitySupported(Class, String) + */ + default boolean isMessageDigestSupported(String algorithm) { + return isSecurityEntitySupported(MessageDigest.class, algorithm); + } + + /** + * @param algorithm The {@link KeyPairGenerator} algorithm + * @return {@code true} if this security provider supports the algorithm + * @see #isSecurityEntitySupported(Class, String) + */ + default boolean isKeyPairGeneratorSupported(String algorithm) { + return isSecurityEntitySupported(KeyPairGenerator.class, algorithm); + } + + /** + * @param algorithm The {@link KeyAgreement} algorithm + * @return {@code true} if this security provider supports the algorithm + * @see #isSecurityEntitySupported(Class, String) + */ + default boolean isKeyAgreementSupported(String algorithm) { + return isSecurityEntitySupported(KeyAgreement.class, algorithm); + } + + /** + * @param algorithm The {@link Mac} algorithm + * @return {@code true} if this security provider supports the algorithm + * @see #isSecurityEntitySupported(Class, String) + */ + default boolean isMacSupported(String algorithm) { + return isSecurityEntitySupported(Mac.class, algorithm); + } + + /** + * @param algorithm The {@link Signature} algorithm + * @return {@code true} if this security provider supports the algorithm + * @see #isSecurityEntitySupported(Class, String) + */ + default boolean isSignatureSupported(String algorithm) { + return isSecurityEntitySupported(Signature.class, algorithm); + } + + /** + * @param type The {@link CertificateFactory} type + * @return {@code true} if this security provider supports the algorithm + * @see #isSecurityEntitySupported(Class, String) + */ + default boolean isCertificateFactorySupported(String type) { + return isSecurityEntitySupported(CertificateFactory.class, type); + } + + /** + * @param entityType The requested entity type - its simple name serves to + * build the configuration property name. + * @return Configuration value to use if no specific configuration provided + * - default=empty + * @see #isSecurityEntitySupported(Class, String) + */ + default String getDefaultSecurityEntitySupportValue(Class<?> entityType) { + return ""; + } + + default boolean isSecurityEntitySupported(Class<?> entityType, String name) { + String defaultValue = getDefaultSecurityEntitySupportValue(entityType); + return isSecurityEntitySupported(this, entityType, name, defaultValue); + } + + /** + * @return {@code true} if to use the provider's name rather than its + * {@link Provider} instance - default={@code true} + * @see #NAMED_PROVIDER_PROPERTY + * @see #getSecurityProvider() + * @see #registerSecurityProvider(SecurityProviderRegistrar) + */ + @Override + default boolean isNamedProviderUsed() { + return PropertyResolverUtils.getBooleanProperty(this, + getConfigurationPropertyName(NAMED_PROVIDER_PROPERTY), + SecurityProviderChoice.super.isNamedProviderUsed()); + } + + /** + * @param v Value to be examined + * @return {@code true} if the value equals (case insensitive) to + * either {@link #ALL_OPTIONS_VALUE} or {@link #ALL_OPTIONS_WILDCARD} + */ + static boolean isAllOptionsValue(String v) { + return ALL_OPTIONS_VALUE.equalsIgnoreCase(v) + || ALL_OPTIONS_WILDCARD.equalsIgnoreCase(v); + } + + /** + * Checks whether the requested entity type algorithm/name is listed + * as supported by the registrar's configuration + * + * @param registrar The {@link SecurityProviderRegistrar} + * @param entityType The requested entity type - its simple name serves to + * build the configuration property name. + * @param name The requested algorithm/name - <B>Note:</B> if the requested + * entity is a {@link Cipher} then the argument is assumed to be a possible + * "/" separated transformation and parsed as such in order to + * retrieve the pure cipher name + * @param defaultValue Configuration value to use if no specific configuration provided + * @return {@code true} registrar is supported and the value is listed + * (case <U>insensitive</U>) or * the property is one of the "all" markers + * @see SecurityProviderRegistrar#isSupported() + * @see #isAllOptionsValue(String) + */ + static boolean isSecurityEntitySupported(SecurityProviderRegistrar registrar, Class<?> entityType, String name, String defaultValue) { + return Objects.requireNonNull(registrar, "No registrar instance").isSupported() + && isSecurityEntitySupported(registrar, registrar.getConfigurationPropertyName(entityType.getSimpleName()), entityType, name, defaultValue); + } + + static boolean isSecurityEntitySupported(PropertyResolver resolver, String propName, Class<?> entityType, String name, String defaultValue) { + if (GenericUtils.isEmpty(name)) { + return false; + } + + String propValue = resolver.getString(propName); + if (GenericUtils.isEmpty(propValue)) { + propValue = defaultValue; + } + + if (NO_OPTIONS_VALUE.equalsIgnoreCase(propValue)) { + return false; + } + + String[] values = GenericUtils.split(propValue, ','); + if (GenericUtils.isEmpty(values)) { + return false; + } + + if ((values.length == 1) && isAllOptionsValue(values[0])) { + return true; + } + + String effectiveName = getEffectiveSecurityEntityName(entityType, name); + int index = Arrays.binarySearch(values, effectiveName, String.CASE_INSENSITIVE_ORDER); + return index >= 0; + } + + /** + * Determines the "pure" security entity name - e.g., for {@link Cipher}s + * it strips the trailing transformation specification in order to extract the + * base cipher name - e.g., "AES/CBC/NoPadding" => "AES" + * + * @param entityType The security entity type - ignored if {@code null} + * @param name The effective name - ignored if {@code null}/empty + * @return The resolved name + */ + static String getEffectiveSecurityEntityName(Class<?> entityType, String name) { + if ((entityType == null) || GenericUtils.isEmpty(name) || (!Cipher.class.isAssignableFrom(entityType))) { + return name; + } + + int pos = name.indexOf('/'); + return (pos > 0) ? name.substring(0, pos) : name; + } + + /** + * Attempts to register the security provider represented by the registrar + * if not already registered. <B>Note:</B> if {@link SecurityProviderRegistrar#isNamedProviderUsed()} + * is {@code true} then the generated provider will be added to the system's + * list of known providers. + * + * @param registrar The {@link SecurityProviderRegistrar} + * @return {@code true} if no provider was previously registered + * @see Security#getProvider(String) + * @see SecurityProviderRegistrar#getSecurityProvider() + * @see Security#addProvider(Provider) + */ + static boolean registerSecurityProvider(SecurityProviderRegistrar registrar) { + String name = ValidateUtils.checkNotNullAndNotEmpty( + (registrar == null) ? null : registrar.getName(), "No name for registrar=%s", registrar); + Provider p = Security.getProvider(name); + if (p != null) { + return false; + } + + p = ValidateUtils.checkNotNull( + registrar.getSecurityProvider(), "No provider created for registrar of %s", name); + if (registrar.isNamedProviderUsed()) { + Security.addProvider(p); + } + + return true; + } + + static SecurityProviderRegistrar findSecurityProviderRegistrarBySecurityEntity( + Predicate<? super SecurityProviderRegistrar> entitySelector, + Collection<? extends SecurityProviderRegistrar> registrars) { + return GenericUtils.findFirstMatchingMember( + r -> r.isEnabled() && r.isSupported() && entitySelector.test(r), registrars); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java new file mode 100644 index 0000000..ee755e6 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java @@ -0,0 +1,759 @@ +/* + * 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.security.spec.InvalidKeySpecException; +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.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.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.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 <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public final class SecurityUtils { + /** + * Bouncycastle JCE provider 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 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 <A HREF="https://tools.ietf.org/html/rfc4419">RFC 4419</A> + * section 3: "Servers and clients SHOULD support groups with a modulus length of k + * bits, where 1024 <= k <= 8192". + * </code> + */ + public static final int MIN_DHGEX_KEY_SIZE = 1024; + // Keys of size > 1024 are not support by default with JCE + public static final int DEFAULT_DHGEX_KEY_SIZE = MIN_DHGEX_KEY_SIZE; + 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<String> DEFAULT_SECURITY_PROVIDER_REGISTRARS = + Collections.unmodifiableList( + Arrays.asList( + "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. <B>Note:</B> 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"; + + 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<String, SecurityProviderRegistrar> REGISTERED_PROVIDERS = new LinkedHashMap<>(); + private static final AtomicReference<KeyPairResourceParser> KEYPAIRS_PARSER_HODLER = new AtomicReference<>(); + // If an entry already exists for the named provider, then it overrides its SecurityProviderRegistrar#isEnabled() + private static final Set<String> APRIORI_DISABLED_PROVIDERS = new TreeSet<>(); + private static final AtomicBoolean REGISTRATION_STATE_HOLDER = new AtomicBoolean(false); + private static final Map<Class<?>, Map<String, SecurityEntityFactory<?>>> SECURITY_ENTITY_FACTORIES = new HashMap<>(); + + private static final AtomicReference<SecurityProviderChoice> DEFAULT_PROVIDER_HOLDER = new AtomicReference<>(); + + private static Boolean hasEcc; + + private SecurityUtils() { + throw new UnsupportedOperationException("No instance"); + } + + /** + * @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" <U>programatically</U> + * 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. <B>Note:</B> 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 <U>copy</U> if the current a-priori disabled providers names + */ + public static Set<String> 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 #getMaxDHGroupExchangeKeySize() + */ + public static boolean isDHGroupExchangeSupported() { + return getMaxDHGroupExchangeKeySize() > 0; + } + + /** + * @param keySize The expected key size + * @return {@code true} if Oakely Diffie-Hellman Group Exchange is supported + * for the specified key size + * @see #getMaxDHGroupExchangeKeySize() + */ + public static boolean isDHOakelyGroupSupported(int keySize) { + return getMaxDHGroupExchangeKeySize() >= keySize; + } + + /** + * @return The maximum supported Diffie-Hellman Group Exchange key size, + * or non-positive if not supported + */ + public static int getMaxDHGroupExchangeKeySize() { + int maxSupportedKeySize; + synchronized (MAX_DHG_KEY_SIZE_HOLDER) { + maxSupportedKeySize = MAX_DHG_KEY_SIZE_HOLDER.get(); + if (maxSupportedKeySize != 0) { // 1st time we are called ? + return maxSupportedKeySize; + } + + String propValue = System.getProperty(MAX_DHGEX_KEY_SIZE_PROP); + if (GenericUtils.isEmpty(propValue)) { + maxSupportedKeySize = -1; + // Go down from max. to min. to ensure we stop at 1st maximum value success + for (int testKeySize = MAX_DHGEX_KEY_SIZE; testKeySize >= MIN_DHGEX_KEY_SIZE; testKeySize -= 1024) { + if (isDHGroupExchangeSupported(testKeySize)) { + maxSupportedKeySize = testKeySize; + break; + } + } + } else { + Logger logger = LoggerFactory.getLogger(SecurityUtils.class); + logger.info("Override max. DH group exchange key size: " + propValue); + maxSupportedKeySize = Integer.parseInt(propValue); + // negative is OK - means user wants to disable DH group exchange + ValidateUtils.checkTrue(maxSupportedKeySize != 0, + "Configured " + MAX_DHGEX_KEY_SIZE_PROP + " value must be non-zero: %d", maxSupportedKeySize); + } + + MAX_DHG_KEY_SIZE_HOLDER.set(maxSupportedKeySize); + } + + return maxSupportedKeySize; + } + + /** + * 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); + } + } + + public static boolean isDHGroupExchangeSupported(int maxKeySize) { + ValidateUtils.checkTrue(maxKeySize > Byte.SIZE, "Invalid max. key size: %d", maxKeySize); + + try { + BigInteger r = new BigInteger("0").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) || "none".equalsIgnoreCase(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 <U>copy</U> of the currently registered security providers + */ + public static Set<String> 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) && (!"none".equalsIgnoreCase(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 = GenericUtils.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 RuntimeException(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 RuntimeException("Failed to register " + name + " as a JCE provider", t); + } + } + + return registeredInstance; + } + + ///////////////// Bouncycastle specific implementations ////////////////// + + /* -------------------------------------------------------------------- */ + + /** + * @param resourceKey An identifier of the key being loaded - used as + * argument to the {@link FilePasswordProvider#getPassword(String)} + * invocation + * @param inputStream The {@link InputStream} for the <U>private</U> key + * @param provider A {@link FilePasswordProvider} - may be {@code null} + * if the loaded key is <U>guaranteed</U> not to be encrypted + * @return The loaded {@link KeyPair} + * @throws IOException If failed to read/parse the input stream + * @throws GeneralSecurityException If failed to generate the keys + */ + public static KeyPair loadKeyPairIdentity(String resourceKey, InputStream inputStream, FilePasswordProvider provider) + throws IOException, GeneralSecurityException { + KeyPairResourceParser parser = getKeyPairResourceParser(); + if (parser == null) { + throw new NoSuchProviderException("No registered key-pair resource parser"); + } + + Collection<KeyPair> ids = parser.loadKeyPairs(resourceKey, provider, inputStream); + int numLoaded = GenericUtils.size(ids); + if (numLoaded <= 0) { + throw new InvalidKeyException("Unsupported private key file format: " + resourceKey); + } + if (numLoaded != 1) { + throw new InvalidKeySpecException("Multiple private key pairs N/A: " + resourceKey); + } + + return ids.iterator().next(); + } + + /* -------------------------------------------------------------------- */ + + 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; + } + + /** + * @return If {@link #isBouncyCastleRegistered()} then a {@link BouncyCastleRandomFactory} + * instance, otherwise a {@link JceRandomFactory} one + */ + public static RandomFactory getRandomFactory() { + if (isBouncyCastleRegistered()) { + 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() { + register(); + + SecurityProviderRegistrar r = getRegisteredProvider(EDDSA); + return (r != null) && r.isEnabled() && r.isSupported(); + } + + /* -------------------------------------------------------------------- */ + + public static PublicKeyEntryDecoder<? extends PublicKey, ? extends PrivateKey> getEDDSAPublicKeyEntryDecoder() { + if (!isEDDSACurveSupported()) { + throw new UnsupportedOperationException(EDDSA + " provider N/A"); + } + + return EdDSASecurityProviderUtils.getEDDSAPublicKeyEntryDecoder(); + } + + public static PrivateKeyEntryDecoder<? extends PublicKey, ? extends PrivateKey> 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<? extends PublicKey> getEDDSAPublicKeyType() { + return isEDDSACurveSupported() ? EdDSASecurityProviderUtils.getEDDSAPublicKeyType() : PublicKey.class; + } + + public static Class<? extends PrivateKey> 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 extends Buffer> B putRawEDDSAPublicKey(B buffer, PublicKey key) { + if (!isEDDSACurveSupported()) { + throw new UnsupportedOperationException(EDDSA + " provider not supported"); + } + + return EdDSASecurityProviderUtils.putRawEDDSAPublicKey(buffer, key); + } + + public static <B extends Buffer> B putEDDSAKeyPair(B buffer, KeyPair kp) { + return putEDDSAKeyPair(buffer, Objects.requireNonNull(kp, "No key pair").getPublic(), kp.getPrivate()); + } + + public static <B extends Buffer> 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 <T> SecurityEntityFactory<T> resolveSecurityEntityFactory( + Class<T> entityType, String algorithm, Predicate<? super SecurityProviderRegistrar> entitySelector) { + Map<String, SecurityEntityFactory<?>> 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<T>) factoryEntry; + } + + public static <T> SecurityEntityFactory<T> createSecurityEntityFactory( + Class<T> entityType, Predicate<? super SecurityProviderRegistrar> 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 = GenericUtils.peelException(t); + if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } else if (e instanceof Error) { + throw (Error) e; + } else { + throw new RuntimeException(e); + } + } + } + + public static KeyFactory getKeyFactory(String algorithm) throws GeneralSecurityException { + SecurityEntityFactory<KeyFactory> factory = + resolveSecurityEntityFactory(KeyFactory.class, algorithm, r -> r.isKeyFactorySupported(algorithm)); + return factory.getInstance(algorithm); + } + + public static Cipher getCipher(String transformation) throws GeneralSecurityException { + SecurityEntityFactory<Cipher> factory = + resolveSecurityEntityFactory(Cipher.class, transformation, r -> r.isCipherSupported(transformation)); + return factory.getInstance(transformation); + } + + public static MessageDigest getMessageDigest(String algorithm) throws GeneralSecurityException { + SecurityEntityFactory<MessageDigest> factory = + resolveSecurityEntityFactory(MessageDigest.class, algorithm, r -> r.isMessageDigestSupported(algorithm)); + return factory.getInstance(algorithm); + } + + public static KeyPairGenerator getKeyPairGenerator(String algorithm) throws GeneralSecurityException { + SecurityEntityFactory<KeyPairGenerator> factory = + resolveSecurityEntityFactory(KeyPairGenerator.class, algorithm, r -> r.isKeyPairGeneratorSupported(algorithm)); + return factory.getInstance(algorithm); + } + + public static KeyAgreement getKeyAgreement(String algorithm) throws GeneralSecurityException { + SecurityEntityFactory<KeyAgreement> factory = + resolveSecurityEntityFactory(KeyAgreement.class, algorithm, r -> r.isKeyAgreementSupported(algorithm)); + return factory.getInstance(algorithm); + } + + public static Mac getMac(String algorithm) throws GeneralSecurityException { + SecurityEntityFactory<Mac> factory = + resolveSecurityEntityFactory(Mac.class, algorithm, r -> r.isMacSupported(algorithm)); + return factory.getInstance(algorithm); + } + + public static Signature getSignature(String algorithm) throws GeneralSecurityException { + SecurityEntityFactory<Signature> factory = + resolveSecurityEntityFactory(Signature.class, algorithm, r -> r.isSignatureSupported(algorithm)); + return factory.getInstance(algorithm); + } + + public static CertificateFactory getCertificateFactory(String type) throws GeneralSecurityException { + SecurityEntityFactory<CertificateFactory> factory = + resolveSecurityEntityFactory(CertificateFactory.class, type, r -> r.isCertificateFactorySupported(type)); + return factory.getInstance(type); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleGeneratorHostKeyProvider.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleGeneratorHostKeyProvider.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleGeneratorHostKeyProvider.java new file mode 100644 index 0000000..3716719 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleGeneratorHostKeyProvider.java @@ -0,0 +1,48 @@ +/* + * 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.bouncycastle; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.security.KeyPair; + +import org.apache.sshd.server.keyprovider.AbstractGeneratorHostKeyProvider; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class BouncyCastleGeneratorHostKeyProvider extends AbstractGeneratorHostKeyProvider { + public BouncyCastleGeneratorHostKeyProvider(Path path) { + setPath(path); + } + + @SuppressWarnings("deprecation") + @Override + protected void doWriteKeyPair(String resourceKey, KeyPair kp, OutputStream outputStream) throws IOException, GeneralSecurityException { + try (org.bouncycastle.openssl.PEMWriter w = + new org.bouncycastle.openssl.PEMWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8))) { + w.writeObject(kp); + w.flush(); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKeyPairResourceParser.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKeyPairResourceParser.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKeyPairResourceParser.java new file mode 100644 index 0000000..4c8722a --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKeyPairResourceParser.java @@ -0,0 +1,130 @@ +/* + * 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.bouncycastle; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.NoSuchProviderException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.apache.sshd.common.config.keys.FilePasswordProvider; +import org.apache.sshd.common.config.keys.loader.AbstractKeyPairResourceParser; +import org.apache.sshd.common.util.ValidateUtils; +import org.apache.sshd.common.util.io.IoUtils; +import org.apache.sshd.common.util.security.SecurityProviderRegistrar; +import org.apache.sshd.common.util.security.SecurityUtils; +import org.bouncycastle.openssl.PEMDecryptorProvider; +import org.bouncycastle.openssl.PEMEncryptedKeyPair; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class BouncyCastleKeyPairResourceParser extends AbstractKeyPairResourceParser { + public static final List<String> BEGINNERS = + Collections.unmodifiableList( + Arrays.asList( + "BEGIN RSA PRIVATE KEY", + "BEGIN DSA PRIVATE KEY", + "BEGIN EC PRIVATE KEY")); + + public static final List<String> ENDERS = + Collections.unmodifiableList( + Arrays.asList( + "END RSA PRIVATE KEY", + "END DSA PRIVATE KEY", + "END EC PRIVATE KEY")); + + public static final BouncyCastleKeyPairResourceParser INSTANCE = new BouncyCastleKeyPairResourceParser(); + + public BouncyCastleKeyPairResourceParser() { + super(BEGINNERS, ENDERS); + } + + @Override + public Collection<KeyPair> extractKeyPairs( + String resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, List<String> lines) + throws IOException, GeneralSecurityException { + StringBuilder writer = new StringBuilder(beginMarker.length() + endMarker.length() + lines.size() * 80); + writer.append(beginMarker).append(IoUtils.EOL); + lines.forEach(l -> writer.append(l).append(IoUtils.EOL)); + writer.append(endMarker).append(IoUtils.EOL); + + String data = writer.toString(); + byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8); + try (InputStream bais = new ByteArrayInputStream(dataBytes)) { + return extractKeyPairs(resourceKey, beginMarker, endMarker, passwordProvider, bais); + } + } + + @Override + public Collection<KeyPair> extractKeyPairs( + String resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, InputStream stream) + throws IOException, GeneralSecurityException { + KeyPair kp = loadKeyPair(resourceKey, stream, passwordProvider); + return (kp == null) ? Collections.emptyList() : Collections.singletonList(kp); + } + + public static KeyPair loadKeyPair(String resourceKey, InputStream inputStream, FilePasswordProvider provider) + throws IOException, GeneralSecurityException { + try (PEMParser r = new PEMParser(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + Object o = r.readObject(); + + SecurityProviderRegistrar registrar = SecurityUtils.getRegisteredProvider(SecurityUtils.BOUNCY_CASTLE); + if (registrar == null) { + throw new NoSuchProviderException(SecurityUtils.BOUNCY_CASTLE + " registrar not available"); + } + + JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter(); + if (registrar.isNamedProviderUsed()) { + pemConverter.setProvider(registrar.getName()); + } else { + pemConverter.setProvider(registrar.getSecurityProvider()); + } + if (o instanceof PEMEncryptedKeyPair) { + ValidateUtils.checkNotNull(provider, "No password provider for resource=%s", resourceKey); + + String password = ValidateUtils.checkNotNullAndNotEmpty(provider.getPassword(resourceKey), "No password provided for resource=%s", resourceKey); + JcePEMDecryptorProviderBuilder decryptorBuilder = new JcePEMDecryptorProviderBuilder(); + PEMDecryptorProvider pemDecryptor = decryptorBuilder.build(password.toCharArray()); + o = ((PEMEncryptedKeyPair) o).decryptKeyPair(pemDecryptor); + } + + if (o instanceof PEMKeyPair) { + return pemConverter.getKeyPair((PEMKeyPair) o); + } else if (o instanceof KeyPair) { + return (KeyPair) o; + } else { + throw new IOException("Failed to read " + resourceKey + " - unknown result object: " + o); + } + } + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleRandom.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleRandom.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleRandom.java new file mode 100644 index 0000000..36c23e7 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleRandom.java @@ -0,0 +1,88 @@ +/* + * 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.bouncycastle; + +import java.security.SecureRandom; + +import org.apache.sshd.common.random.AbstractRandom; +import org.apache.sshd.common.util.ValidateUtils; +import org.apache.sshd.common.util.security.SecurityUtils; +import org.bouncycastle.crypto.prng.RandomGenerator; +import org.bouncycastle.crypto.prng.VMPCRandomGenerator; + +/** + * BouncyCastle <code>Random</code>. + * This pseudo random number generator uses the a very fast PRNG from BouncyCastle. + * The JRE random will be used when creating a new generator to add some random + * data to the seed. + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public final class BouncyCastleRandom extends AbstractRandom { + public static final String NAME = SecurityUtils.BOUNCY_CASTLE; + private final RandomGenerator random; + + public BouncyCastleRandom() { + ValidateUtils.checkTrue(SecurityUtils.isBouncyCastleRegistered(), "BouncyCastle not registered"); + this.random = new VMPCRandomGenerator(); + byte[] seed = new SecureRandom().generateSeed(8); + this.random.addSeedMaterial(seed); + } + + @Override + public String getName() { + return NAME; + } + + @Override + public void fill(byte[] bytes, int start, int len) { + this.random.nextBytes(bytes, start, len); + } + + /** + * Returns a pseudo-random uniformly distributed {@code int} + * in the half-open range [0, n). + */ + @Override + public int random(int n) { + ValidateUtils.checkTrue(n > 0, "Limit must be positive: %d", n); + if ((n & -n) == n) { + return (int) ((n * (long) next(31)) >> 31); + } + + int bits; + int val; + do { + bits = next(31); + val = bits % n; + } while (bits - val + (n - 1) < 0); + return val; + } + + private int next(int numBits) { + int bytes = (numBits + 7) / 8; + byte next[] = new byte[bytes]; + int ret = 0; + random.nextBytes(next); + for (int i = 0; i < bytes; i++) { + ret = (next[i] & 0xFF) | (ret << 8); + } + return ret >>> (bytes * 8 - numBits); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleRandomFactory.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleRandomFactory.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleRandomFactory.java new file mode 100644 index 0000000..720c7a5 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleRandomFactory.java @@ -0,0 +1,45 @@ +/* + * 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.bouncycastle; + +import org.apache.sshd.common.random.AbstractRandomFactory; +import org.apache.sshd.common.random.Random; +import org.apache.sshd.common.util.security.SecurityUtils; + +/** + * Named factory for the BouncyCastle <code>Random</code> + */ +public final class BouncyCastleRandomFactory extends AbstractRandomFactory { + public static final String NAME = "bouncycastle"; + public static final BouncyCastleRandomFactory INSTANCE = new BouncyCastleRandomFactory(); + + public BouncyCastleRandomFactory() { + super(NAME); + } + + @Override + public boolean isSupported() { + return SecurityUtils.isBouncyCastleRegistered(); + } + + @Override + public Random create() { + return new BouncyCastleRandom(); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java new file mode 100644 index 0000000..b47ca80 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java @@ -0,0 +1,128 @@ +/* + * 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.bouncycastle; + +import java.security.KeyFactory; +import java.security.KeyPairGenerator; +import java.security.Provider; +import java.security.Signature; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.ReflectionUtils; +import org.apache.sshd.common.util.security.AbstractSecurityProviderRegistrar; +import org.apache.sshd.common.util.security.SecurityUtils; +import org.apache.sshd.common.util.threads.ThreadUtils; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class BouncyCastleSecurityProviderRegistrar extends AbstractSecurityProviderRegistrar { + // We want to use reflection API so as not to require BouncyCastle to be present in the classpath + public static final String PROVIDER_CLASS = "org.bouncycastle.jce.provider.BouncyCastleProvider"; + // Do not define a static registrar instance to minimize class loading issues + private final AtomicReference<Boolean> supportHolder = new AtomicReference<>(null); + private final AtomicReference<String> allSupportHolder = new AtomicReference<>(); + + public BouncyCastleSecurityProviderRegistrar() { + super(SecurityUtils.BOUNCY_CASTLE); + } + + @Override + public boolean isEnabled() { + if (!super.isEnabled()) { + return false; + } + + // For backward compatibility + return this.getBooleanProperty(SecurityUtils.REGISTER_BOUNCY_CASTLE_PROP, true); + } + + @Override + public Provider getSecurityProvider() { + try { + return getOrCreateProvider(PROVIDER_CLASS); + } catch (ReflectiveOperationException t) { + Throwable e = GenericUtils.peelException(t); + log.error("getSecurityProvider({}) failed ({}) to instantiate {}: {}", + getName(), e.getClass().getSimpleName(), PROVIDER_CLASS, e.getMessage()); + if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } + + throw new RuntimeException(e); + } + } + + @Override + public String getDefaultSecurityEntitySupportValue(Class<?> entityType) { + String allValue = allSupportHolder.get(); + if (GenericUtils.length(allValue) > 0) { + return allValue; + } + + String propName = getConfigurationPropertyName("supportAll"); + allValue = this.getStringProperty(propName, ALL_OPTIONS_VALUE); + if (GenericUtils.isEmpty(allValue)) { + allValue = NO_OPTIONS_VALUE; + } + + allSupportHolder.set(allValue); + return allValue; + } + + @Override + public boolean isSecurityEntitySupported(Class<?> entityType, String name) { + if (!isSupported()) { + return false; + } + + // Some known values it does not support + if (KeyPairGenerator.class.isAssignableFrom(entityType) + || KeyFactory.class.isAssignableFrom(entityType)) { + if (Objects.compare(name, SecurityUtils.EDDSA, String.CASE_INSENSITIVE_ORDER) == 0) { + return false; + } + } else if (Signature.class.isAssignableFrom(entityType)) { + if (Objects.compare(name, SecurityUtils.CURVE_ED25519_SHA512, String.CASE_INSENSITIVE_ORDER) == 0) { + return false; + } + } + + return super.isSecurityEntitySupported(entityType, name); + } + + @Override + public boolean isSupported() { + Boolean supported; + synchronized (supportHolder) { + supported = supportHolder.get(); + if (supported != null) { + return supported.booleanValue(); + } + + ClassLoader cl = ThreadUtils.resolveDefaultClassLoader(getClass()); + supported = ReflectionUtils.isClassAvailable(cl, PROVIDER_CLASS); + supportHolder.set(supported); + } + + return supported.booleanValue(); + } +}
