This is an automated email from the ASF dual-hosted git repository. twolf pushed a commit to branch dev_3.0 in repository https://gitbox.apache.org/repos/asf/mina-sshd.git
commit 834a078ad4583ef8937f54eec02fdea9b961a7f0 Author: Thomas Wolf <[email protected]> AuthorDate: Sat Sep 6 00:31:53 2025 +0200 More ed25519 simplifications Introduce a PublicKeyFactory interface for something that can recover a public key from a private key. On the ScurityProviderRegistrars implement this interface, and use that in SecurityUtils to recover ed25519 public keys. With that, the EdDSASupport interface and its per-registrar instances become obsolete. Remove them altogether. Move all EdDSA things that are not specific to a provider (which is actually everything but public key recovery) into the "eddsa.generic" package. --- .../apache/sshd/common/config/keys/KeyUtils.java | 3 +- .../keys/impl/SkED25519PublicKeyEntryDecoder.java | 4 +- .../loader/pem/PKCS8PEMResourceKeyPairParser.java | 7 +- .../sshd/common/signature/BuiltinSignatures.java | 2 +- .../sshd/common/signature/SignatureSkED25519.java | 2 +- ...eKeyEntryDecoder.java => PublicKeyFactory.java} | 25 +++--- .../util/security/SecurityProviderRegistrar.java | 14 ++-- .../sshd/common/util/security/SecurityUtils.java | 92 ++++++++------------- .../bouncycastle/BouncyCastlePublicKeyFactory.java | 49 +++++++++++ .../BouncyCastleSecurityProviderRegistrar.java | 22 +++-- .../eddsa/Ed25519PEMResourceKeyParser.java | 63 -------------- .../security/eddsa/Ed25519PublicKeyDecoder.java | 42 ---------- .../eddsa/EdDSASecurityProviderRegistrar.java | 19 +++-- .../security/eddsa/EdDSASecurityProviderUtils.java | 11 +-- .../security/eddsa/NetI2pCryptoEdDSASupport.java | 51 ------------ .../bouncycastle/BouncyCastleEdDSASupport.java | 60 -------------- ...arser.java => Ed25519PEMResourceKeyParser.java} | 34 ++++++-- ...eyDecoder.java => Ed25519PublicKeyDecoder.java} | 14 ++-- .../util/security/eddsa/generic/EdDSASupport.java | 96 ---------------------- .../util/security/eddsa/generic/EdDSAUtils.java | 34 ++++++-- ...a => OpenSSHEd25519PrivateKeyEntryDecoder.java} | 15 ++-- .../eddsa/{ => generic}/SignatureEd25519.java | 2 +- .../util/security/eddsa/Ed25519VectorsTest.java | 53 +++++------- .../common/signature/SignatureFactoriesTest.java | 3 +- 24 files changed, 233 insertions(+), 484 deletions(-) diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java index 059b3a4c3..ab6f70273 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java @@ -84,6 +84,7 @@ import org.apache.sshd.common.util.buffer.Buffer; import org.apache.sshd.common.util.buffer.ByteArrayBuffer; import org.apache.sshd.common.util.io.IoUtils; import org.apache.sshd.common.util.security.SecurityUtils; +import org.apache.sshd.common.util.security.eddsa.generic.Ed25519PublicKeyDecoder; /** * Utility class for keys @@ -172,7 +173,7 @@ public final class KeyUtils { registerPublicKeyEntryDecoder(ECDSAPublicKeyEntryDecoder.INSTANCE); if (SecurityUtils.isEDDSACurveSupported()) { - registerPublicKeyEntryDecoder(SecurityUtils.getEDDSAPublicKeyEntryDecoder()); + registerPublicKeyEntryDecoder(Ed25519PublicKeyDecoder.INSTANCE); } registerPublicKeyEntryDecoder(SkECDSAPublicKeyEntryDecoder.INSTANCE); diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkED25519PublicKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkED25519PublicKeyEntryDecoder.java index 2a2e90b15..de2656de6 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkED25519PublicKeyEntryDecoder.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkED25519PublicKeyEntryDecoder.java @@ -36,7 +36,7 @@ import org.apache.sshd.common.config.keys.u2f.SkED25519PublicKey; import org.apache.sshd.common.keyprovider.KeyPairProvider; import org.apache.sshd.common.session.SessionContext; import org.apache.sshd.common.util.ValidateUtils; -import org.apache.sshd.common.util.security.SecurityUtils; +import org.apache.sshd.common.util.security.eddsa.generic.Ed25519PublicKeyDecoder; import org.apache.sshd.common.util.security.eddsa.generic.EdDSAUtils; /** @@ -65,7 +65,7 @@ public class SkED25519PublicKeyEntryDecoder extends AbstractPublicKeyEntryDecode boolean noTouchRequired = parseBooleanHeader(headers, NO_TOUCH_REQUIRED_HEADER, false); boolean verifyRequired = parseBooleanHeader(headers, VERIFY_REQUIRED_HEADER, false); - PublicKey pk = SecurityUtils.getEDDSAPublicKeyEntryDecoder().decodePublicKey(session, KeyPairProvider.SSH_ED25519, + PublicKey pk = Ed25519PublicKeyDecoder.INSTANCE.decodePublicKey(session, KeyPairProvider.SSH_ED25519, keyData, headers); String appName = KeyEntryResolver.decodeString(keyData, MAX_APP_NAME_LENGTH); return new SkED25519PublicKey(appName, noTouchRequired, verifyRequired, pk); diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java index 38134fc83..74a5a221b 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java @@ -49,7 +49,8 @@ import org.apache.sshd.common.util.io.der.ASN1Type; import org.apache.sshd.common.util.io.der.DERParser; import org.apache.sshd.common.util.security.Decryptor; import org.apache.sshd.common.util.security.SecurityUtils; -import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; +import org.apache.sshd.common.util.security.eddsa.generic.Ed25519PEMResourceKeyParser; +import org.apache.sshd.common.util.security.eddsa.generic.EdDSAUtils; /** * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> @@ -146,9 +147,9 @@ public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar kp = ECDSAPEMResourceKeyPairParser.parseECKeyPair(curve, parser); } } else if (SecurityUtils.isEDDSACurveSupported() - && EdDSASupport.ED25519_OID.endsWith(oid)) { + && EdDSAUtils.ED25519_OID.equals(oid)) { ASN1Object privateKeyBytes = pkcs8Info.getPrivateKeyBytes(); - kp = EdDSASupport.decodeEd25519KeyPair(privateKeyBytes.getPureValueBytes()); + kp = Ed25519PEMResourceKeyParser.decodeEd25519KeyPair(privateKeyBytes.getPureValueBytes()); } else { PrivateKey prvKey = decodePEMPrivateKeyPKCS8(oidAlgorithm, encBytes); PublicKey pubKey = ValidateUtils.checkNotNull(KeyUtils.recoverPublicKey(prvKey), diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java index bb3444351..b943e9eea 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java @@ -44,7 +44,7 @@ import org.apache.sshd.common.keyprovider.KeyPairProvider; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.ValidateUtils; import org.apache.sshd.common.util.security.SecurityUtils; -import org.apache.sshd.common.util.security.eddsa.SignatureEd25519; +import org.apache.sshd.common.util.security.eddsa.generic.SignatureEd25519; /** * Provides easy access to the currently implemented signatures diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureSkED25519.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureSkED25519.java index 9c43ab64d..12510ce70 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureSkED25519.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureSkED25519.java @@ -20,7 +20,7 @@ package org.apache.sshd.common.signature; import org.apache.sshd.common.config.keys.impl.SkED25519PublicKeyEntryDecoder; import org.apache.sshd.common.keyprovider.KeyPairProvider; -import org.apache.sshd.common.util.security.eddsa.SignatureEd25519; +import org.apache.sshd.common.util.security.eddsa.generic.SignatureEd25519; public class SignatureSkED25519 extends AbstractSecurityKeySignature { diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/PublicKeyFactory.java similarity index 58% rename from sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java rename to sshd-common/src/main/java/org/apache/sshd/common/util/security/PublicKeyFactory.java index 7cfb3555b..a2903d1b1 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/PublicKeyFactory.java @@ -16,23 +16,24 @@ * specific language governing permissions and limitations * under the License. */ +package org.apache.sshd.common.util.security; -package org.apache.sshd.common.util.security.eddsa; - -import org.apache.sshd.common.util.security.eddsa.generic.GenericOpenSSHEd25519PrivateKeyEntryDecoder; +import java.security.PrivateKey; +import java.security.PublicKey; /** - * An implementation of {@link GenericOpenSSHEd25519PrivateKeyEntryDecoder} tied to the {@code net.i2p.crypto} EdDSA - * security provider + * Something that can compute a {@link PublicKey} from a given {@link PrivateKey}. * * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ -public class OpenSSHEd25519PrivateKeyEntryDecoder - extends GenericOpenSSHEd25519PrivateKeyEntryDecoder { - public static final OpenSSHEd25519PrivateKeyEntryDecoder INSTANCE = new OpenSSHEd25519PrivateKeyEntryDecoder(); - - public OpenSSHEd25519PrivateKeyEntryDecoder() { - super(new NetI2pCryptoEdDSASupport()); - } +@FunctionalInterface +public interface PublicKeyFactory { + /** + * Given a {@link PrivateKey} computes the corresponding {@link PublicKey}. + * + * @param key {@link PrivateKey} to get the {@link PublicKey} for + * @return the {@link PublicKey}, or {@code null} if no public key could be computed + */ + PublicKey getPublicKey(PrivateKey key); } 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 index ec39cdf89..68c9756a4 100644 --- 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 @@ -22,7 +22,9 @@ package org.apache.sshd.common.util.security; import java.security.KeyFactory; import java.security.KeyPairGenerator; import java.security.MessageDigest; +import java.security.PrivateKey; import java.security.Provider; +import java.security.PublicKey; import java.security.Security; import java.security.Signature; import java.security.cert.CertificateFactory; @@ -32,7 +34,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.function.Predicate; import javax.crypto.Cipher; @@ -46,12 +47,11 @@ 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; -import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; /** * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ -public interface SecurityProviderRegistrar extends SecurityProviderChoice, OptionalFeature, PropertyResolver { +public interface SecurityProviderRegistrar extends SecurityProviderChoice, OptionalFeature, PublicKeyFactory, PropertyResolver { /** * Base name for configuration properties related to security providers */ @@ -190,11 +190,9 @@ public interface SecurityProviderRegistrar extends SecurityProviderChoice, Optio return isSecurityEntitySupported(CertificateFactory.class, type); } - /** - * @return the EdDSA support implementation associated with the security provider (if applicable) - */ - default Optional<EdDSASupport> getEdDSASupport() { - return Optional.empty(); + @Override + default PublicKey getPublicKey(PrivateKey key) { + return null; } /** 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 index dcfb98101..609cd0b0d 100644 --- 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 @@ -42,7 +42,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; @@ -60,7 +59,6 @@ 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.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; @@ -76,8 +74,8 @@ import org.apache.sshd.common.util.security.bouncycastle.BouncyCastleEncryptedPr 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.generic.EdDSASupport; import org.apache.sshd.common.util.security.eddsa.generic.EdDSAUtils; +import org.apache.sshd.common.util.security.eddsa.generic.OpenSSHEd25519PrivateKeyEntryDecoder; import org.apache.sshd.common.util.threads.ThreadUtils; import org.apache.sshd.server.keyprovider.AbstractGeneratorHostKeyProvider; import org.slf4j.Logger; @@ -168,6 +166,8 @@ public final class SecurityUtils { private static final AtomicReference<Boolean> FIPS_MODE = new AtomicReference<>(); + private static final AtomicReference<Boolean> EDDSA_SUPPORT_PRESENT = new AtomicReference<>(); + private SecurityUtils() { throw new UnsupportedOperationException("No instance"); } @@ -556,7 +556,26 @@ public final class SecurityUtils { * @return {@code true} if EDDSA curves (e.g., {@code ed25519}) are supported */ public static boolean isEDDSACurveSupported() { - return getEdDSASupport().isPresent(); + Boolean isSupported = EDDSA_SUPPORT_PRESENT.get(); + if (isSupported == null) { + register(); + // Currently we can only support ed25519 through libraries and thus we require a registrar. + // TODO: GH-585 (needs a multi-release JAR) + boolean supported = false; + synchronized (REGISTERED_PROVIDERS) { + for (SecurityProviderRegistrar r : REGISTERED_PROVIDERS.values()) { + supported = r.isEnabled() && r.isSupported() && r.isKeyFactorySupported(ED25519); + if (supported) { + break; + } + } + } + isSupported = Boolean.valueOf(supported); + if (!EDDSA_SUPPORT_PRESENT.compareAndSet(null, Boolean.valueOf(supported))) { + isSupported = EDDSA_SUPPORT_PRESENT.get(); + } + } + return isSupported != null && isSupported.booleanValue(); } public static boolean isNetI2pCryptoEdDSARegistered() { @@ -564,73 +583,30 @@ public final class SecurityUtils { return isProviderRegistered(EDDSA); } - public static Optional<EdDSASupport> getEdDSASupport() { - if (isFipsMode()) { - return Optional.empty(); - } - register(); - - synchronized (REGISTERED_PROVIDERS) { - // Prefer the net.i2p.crypto provider if it's available for backwards compatibility - SecurityProviderRegistrar netI2pCryptoProvider = REGISTERED_PROVIDERS.get(EDDSA); - if (netI2pCryptoProvider != null) { - Optional<EdDSASupport> support = netI2pCryptoProvider.getEdDSASupport(); - if (support.isPresent()) { - return support; - } - } - - for (Map.Entry<String, SecurityProviderRegistrar> entry : REGISTERED_PROVIDERS.entrySet()) { - Optional<EdDSASupport> support = entry.getValue().getEdDSASupport(); - if (support.isPresent()) { - return support; - } - } - } - return Optional.empty(); - } - /* -------------------------------------------------------------------- */ - public static PublicKeyEntryDecoder getEDDSAPublicKeyEntryDecoder() { - Optional<EdDSASupport> support = getEdDSASupport(); - if (!support.isPresent()) { - throw new UnsupportedOperationException(EDDSA + " provider N/A"); - } - - return support.get().getEDDSAPublicKeyEntryDecoder(); - } - public static PrivateKeyEntryDecoder getOpenSSHEDDSAPrivateKeyEntryDecoder() { - Optional<EdDSASupport> support = getEdDSASupport(); - if (!support.isPresent()) { - throw new UnsupportedOperationException(EDDSA + " provider N/A"); - } - - return support.get().getOpenSSHEDDSAPrivateKeyEntryDecoder(); + return OpenSSHEd25519PrivateKeyEntryDecoder.INSTANCE; } public static boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2) { - if (k1 == null && k2 == null) { - return true; - } - return k1 != null && k2 != null && EdDSAUtils.equals(k1, k2); + return EdDSAUtils.equals(k1, k2); } public static boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2) { - if (k1 == null && k2 == null) { - return true; - } - return k1 != null && k2 != null && EdDSAUtils.equals(k1, k2); + return EdDSAUtils.equals(k1, k2); } public static PublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException { - Optional<EdDSASupport> support = getEdDSASupport(); - if (!support.isPresent()) { - throw new NoSuchAlgorithmException(EDDSA + " provider not supported"); + synchronized (REGISTERED_PROVIDERS) { + for (SecurityProviderRegistrar registrar : REGISTERED_PROVIDERS.values()) { + PublicKey pk = registrar.getPublicKey(key); + if (pk != null) { + return pk; + } + } } - - return support.get().recoverEDDSAPublicKey(key); + return null; } public static KeyPair extractEDDSAKeyPair(Buffer buffer, String keyType) throws GeneralSecurityException { diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastlePublicKeyFactory.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastlePublicKeyFactory.java new file mode 100644 index 000000000..70a1a0aaa --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastlePublicKeyFactory.java @@ -0,0 +1,49 @@ +/* + * 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.PrivateKey; +import java.security.PublicKey; + +import org.apache.sshd.common.util.security.PublicKeyFactory; +import org.apache.sshd.common.util.security.SecurityUtils; +import org.bouncycastle.jcajce.interfaces.EdDSAPrivateKey; + +public class BouncyCastlePublicKeyFactory implements PublicKeyFactory { + + public BouncyCastlePublicKeyFactory() { + super(); + } + + @Override + public PublicKey getPublicKey(PrivateKey key) { + if (SecurityUtils.ED25519.equals(key.getAlgorithm())) { + return getPublicEdDSAKey(key); + } + return null; + } + + public PublicKey getPublicEdDSAKey(PrivateKey key) { + if (key instanceof EdDSAPrivateKey) { + EdDSAPrivateKey edDSAKey = (EdDSAPrivateKey) key; + return edDSAKey.getPublicKey(); + } + return null; + } +} 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 index 206a1e95a..fbd1de662 100644 --- 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 @@ -21,17 +21,17 @@ package org.apache.sshd.common.util.security.bouncycastle; import java.lang.reflect.Field; import java.security.KeyFactory; import java.security.KeyPairGenerator; +import java.security.PrivateKey; import java.security.Provider; +import java.security.PublicKey; import java.security.Security; import java.security.Signature; -import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; import org.apache.sshd.common.util.ExceptionUtils; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.security.AbstractSecurityProviderRegistrar; import org.apache.sshd.common.util.security.SecurityUtils; -import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; import org.apache.sshd.common.util.threads.ThreadUtils; /** @@ -104,14 +104,12 @@ public class BouncyCastleSecurityProviderRegistrar extends AbstractSecurityProvi if (KeyPairGenerator.class.isAssignableFrom(entityType) || KeyFactory.class.isAssignableFrom(entityType)) { - if (SecurityUtils.ED25519.equalsIgnoreCase(name) - && SecurityUtils.isNetI2pCryptoEdDSARegistered()) { - return false; + if (SecurityUtils.ED25519.equalsIgnoreCase(name)) { + return !SecurityUtils.isNetI2pCryptoEdDSARegistered() && isEdDSASupported(); } } else if (Signature.class.isAssignableFrom(entityType)) { - if (SecurityUtils.ED25519.equalsIgnoreCase(name) - && SecurityUtils.isNetI2pCryptoEdDSARegistered()) { - return false; + if (SecurityUtils.ED25519.equalsIgnoreCase(name)) { + return !SecurityUtils.isNetI2pCryptoEdDSARegistered() && isEdDSASupported(); } } @@ -172,11 +170,11 @@ public class BouncyCastleSecurityProviderRegistrar extends AbstractSecurityProvi } @Override - public Optional<EdDSASupport> getEdDSASupport() { - if (!isEdDSASupported()) { - return Optional.empty(); + public PublicKey getPublicKey(PrivateKey key) { + if (isEnabled() && isEdDSASupported() && key.getClass().getPackage().getName().startsWith("org.bouncycastle.")) { + return new BouncyCastlePublicKeyFactory().getPublicKey(key); } - return Optional.of(new org.apache.sshd.common.util.security.eddsa.bouncycastle.BouncyCastleEdDSASupport()); + return super.getPublicKey(key); } private boolean isEdDSASupported() { diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java deleted file mode 100644 index 63ddddac3..000000000 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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.eddsa; - -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; - -import net.i2p.crypto.eddsa.EdDSAPrivateKey; -import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; -import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec; -import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; -import org.apache.sshd.common.util.security.SecurityUtils; -import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; -import org.apache.sshd.common.util.security.eddsa.generic.GenericEd25519PEMResourceKeyParser; - -/** - * An implementation of {@link GenericEd25519PEMResourceKeyParser} tied to the {@code net.i2p.crypto} EdDSA security - * provider. - * - * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> - */ -public class Ed25519PEMResourceKeyParser extends GenericEd25519PEMResourceKeyParser { - - public static final Ed25519PEMResourceKeyParser INSTANCE = new Ed25519PEMResourceKeyParser(); - - public Ed25519PEMResourceKeyParser() { - super(); - } - - public static EdDSAPrivateKey decodeEdDSAPrivateKey(byte[] keyData) throws IOException, GeneralSecurityException { - return EdDSAPrivateKey.class.cast(EdDSASupport.decodeEdDSAPrivateKey(keyData)); - } - - public static EdDSAPrivateKey generateEdDSAPrivateKey(byte[] seed) throws GeneralSecurityException { - if (!SecurityUtils.isEDDSACurveSupported()) { - throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " provider not supported"); - } - - EdDSAParameterSpec params = EdDSANamedCurveTable.getByName(SecurityUtils.ED25519); - EdDSAPrivateKeySpec keySpec = new EdDSAPrivateKeySpec(seed, params); - KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.ED25519); - return EdDSAPrivateKey.class.cast(factory.generatePrivate(keySpec)); - } -} diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java deleted file mode 100644 index eb1ffcd45..000000000 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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.eddsa; - -import net.i2p.crypto.eddsa.EdDSAPublicKey; -import org.apache.sshd.common.util.security.eddsa.generic.GenericEd25519PublicKeyDecoder; - -/** - * An implementation of {@link GenericEd25519PublicKeyDecoder} tied to the {@code net.i2p.crypto} EdDSA security - * provider. - * - * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> - */ -public final class Ed25519PublicKeyDecoder extends GenericEd25519PublicKeyDecoder { - - public static final Ed25519PublicKeyDecoder INSTANCE = new Ed25519PublicKeyDecoder(); - - private Ed25519PublicKeyDecoder() { - super(new NetI2pCryptoEdDSASupport()); - } - - public static byte[] getSeedValue(EdDSAPublicKey key) { - // a bit of reverse-engineering on the EdDSAPublicKeySpec - return (key == null) ? null : key.getAbyte(); - } -} diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java index 3697bdf24..b351153ca 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java @@ -21,16 +21,16 @@ package org.apache.sshd.common.util.security.eddsa; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.KeyPairGenerator; +import java.security.PrivateKey; import java.security.Provider; +import java.security.PublicKey; import java.security.Signature; -import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; import org.apache.sshd.common.util.ExceptionUtils; import org.apache.sshd.common.util.security.AbstractSecurityProviderRegistrar; import org.apache.sshd.common.util.security.SecurityEntityFactory; import org.apache.sshd.common.util.security.SecurityUtils; -import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; import org.apache.sshd.common.util.threads.ThreadUtils; /** @@ -131,14 +131,19 @@ public class EdDSASecurityProviderRegistrar extends AbstractSecurityProviderRegi } @Override - public Optional<EdDSASupport> getEdDSASupport() { - if (!isSupported()) { - return Optional.empty(); + public PublicKey getPublicKey(PrivateKey key) { + if (isEnabled() && isSupported() && "EdDSA".equals(key.getAlgorithm()) + && key.getClass().getPackage().getName().startsWith("net.i2p.")) { + try { + return EdDSASecurityProviderUtils.recoverEDDSAPublicKey(key); + } catch (GeneralSecurityException e) { + return null; + } } - return Optional.of(new NetI2pCryptoEdDSASupport()); + return super.getPublicKey(key); } - private static abstract class DelegatingSecurityEntityFactory<F> implements SecurityEntityFactory<F> { + private abstract static class DelegatingSecurityEntityFactory<F> implements SecurityEntityFactory<F> { private SecurityEntityFactory<F> delegate; diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java index 2fe0705c6..e6d0c9f9f 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java @@ -34,19 +34,19 @@ import net.i2p.crypto.eddsa.EdDSAPrivateKey; import net.i2p.crypto.eddsa.EdDSAPublicKey; import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec; import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; -import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder; import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder; import org.apache.sshd.common.util.ValidateUtils; import org.apache.sshd.common.util.security.SecurityUtils; -import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; +import org.apache.sshd.common.util.security.eddsa.generic.Ed25519PublicKeyDecoder; import org.apache.sshd.common.util.security.eddsa.generic.EdDSAUtils; +import org.apache.sshd.common.util.security.eddsa.generic.SignatureEd25519; /** * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ public final class EdDSASecurityProviderUtils { // See EdDSANamedCurveTable - public static final int KEY_SIZE = EdDSASupport.KEY_SIZE; + public static final int KEY_SIZE = EdDSAUtils.ED25519_LENGTH * Byte.SIZE; private EdDSASecurityProviderUtils() { throw new UnsupportedOperationException("No instance"); @@ -105,11 +105,6 @@ public final class EdDSASecurityProviderUtils { return Ed25519PublicKeyDecoder.INSTANCE; } - public static PrivateKeyEntryDecoder getOpenSSHEDDSAPrivateKeyEntryDecoder() { - ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported"); - return OpenSSHEd25519PrivateKeyEntryDecoder.INSTANCE; - } - public static boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2) { if (!SecurityUtils.isEDDSACurveSupported()) { return false; diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/NetI2pCryptoEdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/NetI2pCryptoEdDSASupport.java deleted file mode 100644 index da8a64eee..000000000 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/NetI2pCryptoEdDSASupport.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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.eddsa; - -import java.security.GeneralSecurityException; -import java.security.PrivateKey; - -import net.i2p.crypto.eddsa.EdDSAPublicKey; -import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder; -import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder; -import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; - -public class NetI2pCryptoEdDSASupport implements EdDSASupport { - - public NetI2pCryptoEdDSASupport() { - super(); - } - - @Override - public PublicKeyEntryDecoder getEDDSAPublicKeyEntryDecoder() { - return Ed25519PublicKeyDecoder.INSTANCE; - } - - @Override - public PrivateKeyEntryDecoder getOpenSSHEDDSAPrivateKeyEntryDecoder() { - return OpenSSHEd25519PrivateKeyEntryDecoder.INSTANCE; - } - - @Override - public EdDSAPublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException { - return EdDSASecurityProviderUtils.recoverEDDSAPublicKey(key); - } - -} diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java deleted file mode 100644 index ea679e906..000000000 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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.eddsa.bouncycastle; - -import java.security.GeneralSecurityException; -import java.security.InvalidKeyException; -import java.security.PrivateKey; - -import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder; -import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder; -import org.apache.sshd.common.util.security.SecurityUtils; -import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; -import org.apache.sshd.common.util.security.eddsa.generic.GenericEd25519PublicKeyDecoder; -import org.apache.sshd.common.util.security.eddsa.generic.GenericOpenSSHEd25519PrivateKeyEntryDecoder; -import org.bouncycastle.jcajce.interfaces.EdDSAPrivateKey; -import org.bouncycastle.jcajce.interfaces.EdDSAPublicKey; - -public class BouncyCastleEdDSASupport implements EdDSASupport { - - public BouncyCastleEdDSASupport() { - super(); - } - - @Override - public PublicKeyEntryDecoder getEDDSAPublicKeyEntryDecoder() { - return new GenericEd25519PublicKeyDecoder(this); - } - - @Override - public PrivateKeyEntryDecoder getOpenSSHEDDSAPrivateKeyEntryDecoder() { - return new GenericOpenSSHEd25519PrivateKeyEntryDecoder(this); - } - - @Override - public EdDSAPublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException { - if (!(key instanceof EdDSAPrivateKey)) { - throw new InvalidKeyException("Private key is not " + SecurityUtils.EDDSA); - } - EdDSAPrivateKey edDSAKey = (EdDSAPrivateKey) key; - return edDSAKey.getPublicKey(); - } - -} diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PEMResourceKeyParser.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/Ed25519PEMResourceKeyParser.java similarity index 80% rename from sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PEMResourceKeyParser.java rename to sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/Ed25519PEMResourceKeyParser.java index 7ee7c47f2..f32985d77 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PEMResourceKeyParser.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/Ed25519PEMResourceKeyParser.java @@ -25,6 +25,8 @@ import java.io.StreamCorruptedException; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -44,7 +46,7 @@ import org.apache.sshd.common.util.security.SecurityUtils; /** * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ -public class GenericEd25519PEMResourceKeyParser extends AbstractPEMResourceKeyPairParser { +public class Ed25519PEMResourceKeyParser extends AbstractPEMResourceKeyPairParser { // From an early draft of RFC 8410 (https://datatracker.ietf.org/doc/html/draft-ietf-curdle-pkix-eddsa-00). // The final RFC 8410 dropped the "EDDSA" and moved to PKCS#8 using just "BEGIN PRIVATE KEY". @@ -59,11 +61,11 @@ public class GenericEd25519PEMResourceKeyParser extends AbstractPEMResourceKeyPa public static final String END_ED25519_MARKER = "END ED25519 PRIVATE KEY"; public static final List<String> ENDERS = GenericUtils.unmodifiableList(END_MARKER, END_ED25519_MARKER); - public static final String ED25519_OID = EdDSASupport.ED25519_OID; + public static final String ED25519_OID = EdDSAUtils.ED25519_OID; - public static final GenericEd25519PEMResourceKeyParser INSTANCE = new GenericEd25519PEMResourceKeyParser(); + public static final Ed25519PEMResourceKeyParser INSTANCE = new Ed25519PEMResourceKeyParser(); - public GenericEd25519PEMResourceKeyParser() { + public Ed25519PEMResourceKeyParser() { super(SecurityUtils.ED25519, ED25519_OID, BEGINNERS, ENDERS); } @@ -145,7 +147,29 @@ public class GenericEd25519PEMResourceKeyParser extends AbstractPEMResourceKeyPa } public static KeyPair decodeEd25519KeyPair(byte[] keyData) throws IOException, GeneralSecurityException { - return EdDSASupport.decodeEd25519KeyPair(keyData); + PrivateKey privateKey = decodeEdDSAPrivateKey(keyData); + PublicKey publicKey = SecurityUtils.recoverEDDSAPublicKey(privateKey); + return new KeyPair(publicKey, privateKey); + } + + /** + * @param keyData the raw private key bytes. + * @return the associated private key. + */ + private static PrivateKey decodeEdDSAPrivateKey(byte[] keyData) throws IOException, GeneralSecurityException { + try (DERParser parser = new DERParser(keyData)) { + ASN1Object obj = parser.readObject(); + if (obj == null) { + throw new StreamCorruptedException("Missing key data container"); + } + + ASN1Type objType = obj.getObjType(); + if (objType != ASN1Type.OCTET_STRING) { + throw new StreamCorruptedException("Mismatched key data container type: " + objType); + } + + return EdDSAUtils.getPrivateKey(obj.getValue()); + } } } diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PublicKeyDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/Ed25519PublicKeyDecoder.java similarity index 86% rename from sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PublicKeyDecoder.java rename to sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/Ed25519PublicKeyDecoder.java index 084bfe0c9..712defeac 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PublicKeyDecoder.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/Ed25519PublicKeyDecoder.java @@ -36,16 +36,18 @@ import org.apache.sshd.common.session.SessionContext; import org.apache.sshd.common.util.security.SecurityUtils; /** + * An {@link AbstractPublicKeyEntryDecoder} for ed25519 keys. + * * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ -public class GenericEd25519PublicKeyDecoder extends AbstractPublicKeyEntryDecoder { +public final class Ed25519PublicKeyDecoder extends AbstractPublicKeyEntryDecoder { + public static final int MAX_ALLOWED_SEED_LEN = 1024; // in reality it is much less than this - protected final EdDSASupport edDSASupport; + public static final Ed25519PublicKeyDecoder INSTANCE = new Ed25519PublicKeyDecoder(); - public GenericEd25519PublicKeyDecoder(EdDSASupport edDSASupport) { + private Ed25519PublicKeyDecoder() { super(Collections.singletonList(KeyPairProvider.SSH_ED25519)); - this.edDSASupport = edDSASupport; } @Override @@ -67,11 +69,9 @@ public class GenericEd25519PublicKeyDecoder extends AbstractPublicKeyEntryDecode } @Override - public PublicKey decodePublicKey( - SessionContext session, String keyType, InputStream keyData, Map<String, String> headers) + public PublicKey decodePublicKey(SessionContext session, String keyType, InputStream keyData, Map<String, String> headers) throws IOException, GeneralSecurityException { byte[] seed = KeyEntryResolver.readRLEBytes(keyData, MAX_ALLOWED_SEED_LEN); return EdDSAUtils.getPublicKey(seed); } - } diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java deleted file mode 100644 index 02ff2e18c..000000000 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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.eddsa.generic; - -import java.io.IOException; -import java.io.StreamCorruptedException; -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.security.PrivateKey; -import java.security.PublicKey; - -import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder; -import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder; -import org.apache.sshd.common.util.io.der.ASN1Object; -import org.apache.sshd.common.util.io.der.ASN1Type; -import org.apache.sshd.common.util.io.der.DERParser; -import org.apache.sshd.common.util.security.SecurityUtils; - -/** - * Provides generic operations required of a security provider to support EdDSA and Ed25519. - * - * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> - */ -public interface EdDSASupport { - - int KEY_SIZE = 256; - - /** - * @see <A HREF="https://tools.ietf.org/html/rfc8410#section-3">RFC8412 section 3</A> - */ - String ED25519_OID = "1.3.101.112"; - - /** - * @param keyData the raw private key bytes. - * @return a {@link KeyPair} from the given raw private key data. - */ - static KeyPair decodeEd25519KeyPair(byte[] keyData) throws IOException, GeneralSecurityException { - PrivateKey privateKey = decodeEdDSAPrivateKey(keyData); - PublicKey publicKey = SecurityUtils.recoverEDDSAPublicKey(privateKey); - return new KeyPair(publicKey, privateKey); - } - - /** - * @param keyData the raw private key bytes. - * @return the associated private key. - */ - static PrivateKey decodeEdDSAPrivateKey(byte[] keyData) throws IOException, GeneralSecurityException { - try (DERParser parser = new DERParser(keyData)) { - ASN1Object obj = parser.readObject(); - if (obj == null) { - throw new StreamCorruptedException("Missing key data container"); - } - - ASN1Type objType = obj.getObjType(); - if (objType != ASN1Type.OCTET_STRING) { - throw new StreamCorruptedException("Mismatched key data container type: " + objType); - } - - return EdDSAUtils.getPrivateKey(obj.getValue()); - } - } - - /** - * @return the public key entry decoder implementation associated with the security provider. - */ - PublicKeyEntryDecoder getEDDSAPublicKeyEntryDecoder(); - - /** - * @return the private key entry decoder implementation associated with the security provider. - */ - PrivateKeyEntryDecoder getOpenSSHEDDSAPrivateKeyEntryDecoder(); - - /** - * @param key the private key - * @return the public key associated with the private key. - */ - PublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException; - -} diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSAUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSAUtils.java index 61460c46c..838c50e3d 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSAUtils.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSAUtils.java @@ -28,6 +28,7 @@ import java.security.spec.KeySpec; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; +import java.util.Objects; import org.apache.sshd.common.util.io.der.DERParser; import org.apache.sshd.common.util.security.SecurityUtils; @@ -40,9 +41,19 @@ import org.apache.sshd.common.util.security.SecurityUtils; */ public final class EdDSAUtils { - private static final int ED25519_LENGTH = 32; // bytes + /** + * @see <a href="https://tools.ietf.org/html/rfc8410#section-3">RFC8412 section 3</a> + */ + public static final String ED25519_OID = "1.3.101.112"; + + /** + * @see <a href="https://tools.ietf.org/html/rfc8410#section-3">RFC8412 section 3</a> + */ + public static final String ED448_OID = "1.3.101.113"; + + public static final int ED25519_LENGTH = 32; // bytes - private static final int ED448_LENGTH = 57; // bytes + public static final int ED448_LENGTH = 57; // bytes // These are the constant prefixes of X.509 encodings of ed25519 and ed448 keys. Appending the actual 32 // or 57 key bytes yields valid encodings. @@ -69,8 +80,8 @@ public final class EdDSAUtils { 0x04, 0x3b, 0x04, 0x39 }; // The first two numbers of the dotted notation are combined into one byte: (1 * 40 + 3) = 43 = 0x2b - private static final byte[] ED25519_OID = { 0x2b, 0x65, 0x70 }; // 1.3.101.112 - private static final byte[] ED448_OID = { 0x2b, 0x65, 0x71 }; // 1.3.101.113 + private static final byte[] ED25519_OID_BYTES = { 0x2b, 0x65, 0x70 }; // 1.3.101.112 + private static final byte[] ED448_OID_BYTES = { 0x2b, 0x65, 0x71 }; // 1.3.101.113 private EdDSAUtils() { throw new IllegalStateException("No instantiation"); @@ -199,9 +210,9 @@ public final class EdDSAUtils { int n; try (DERParser algorithmIdentifier = oneAsymmetricKey.readObject().createParser()) { byte[] oid = algorithmIdentifier.readObject().getValue(); - if (arrayEq(ED25519_OID, oid)) { + if (arrayEq(ED25519_OID_BYTES, oid)) { n = ED25519_LENGTH; - } else if (arrayEq(ED448_OID, oid)) { + } else if (arrayEq(ED448_OID_BYTES, oid)) { n = ED448_LENGTH; } else { throw new IllegalArgumentException("Private key is neither ed25519 nor ed448"); @@ -325,7 +336,10 @@ public final class EdDSAUtils { * @throws IllegalArgumentException if one of the keys is neither an ed25519 nor an ed448 key */ public static boolean equals(PublicKey k1, PublicKey k2) throws IllegalArgumentException { - return arrayEq(getBytes(k1), getBytes(k2)); + if (Objects.equals(k1, k2)) { + return true; + } + return k1 != null && k2 != null && arrayEq(getBytes(k1), getBytes(k2)); } /** @@ -337,6 +351,12 @@ public final class EdDSAUtils { * @throws IllegalArgumentException if one of the keys is neither an ed25519 nor an ed448 key */ public static boolean equals(PrivateKey k1, PrivateKey k2) throws IllegalArgumentException { + if (Objects.equals(k1, k2)) { + return true; + } + if (k1 == null || k2 == null) { + return false; + } byte[] k1Data = null; byte[] k2Data = null; try { diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/OpenSSHEd25519PrivateKeyEntryDecoder.java similarity index 92% rename from sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java rename to sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/OpenSSHEd25519PrivateKeyEntryDecoder.java index e1285fb55..aafd8a717 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/OpenSSHEd25519PrivateKeyEntryDecoder.java @@ -45,16 +45,16 @@ import org.apache.sshd.common.util.security.SecurityUtils; /** * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ -public class GenericOpenSSHEd25519PrivateKeyEntryDecoder extends AbstractPrivateKeyEntryDecoder { +public class OpenSSHEd25519PrivateKeyEntryDecoder extends AbstractPrivateKeyEntryDecoder { + + public static final OpenSSHEd25519PrivateKeyEntryDecoder INSTANCE = new OpenSSHEd25519PrivateKeyEntryDecoder(); + private static final int PK_SIZE = 32; private static final int SK_SIZE = 32; private static final int KEYPAIR_SIZE = PK_SIZE + SK_SIZE; - protected final EdDSASupport edDSASupport; - - public GenericOpenSSHEd25519PrivateKeyEntryDecoder(EdDSASupport edDSASupport) { + public OpenSSHEd25519PrivateKeyEntryDecoder() { super(Collections.singletonList(KeyPairProvider.SSH_ED25519)); - this.edDSASupport = edDSASupport; } @Override @@ -99,7 +99,8 @@ public class GenericOpenSSHEd25519PrivateKeyEntryDecoder extends AbstractPrivate PrivateKey privateKey = EdDSAUtils.getPrivateKey(sk); // we can now verify the generated pk matches the one we read - if (!Arrays.equals(EdDSAUtils.getBytes(recoverPublicKey(privateKey)), pk)) { + PublicKey recoveredPk = recoverPublicKey(privateKey); + if (recoveredPk != null && !Arrays.equals(EdDSAUtils.getBytes(recoveredPk), pk)) { throw new InvalidKeyException("The provided pk does NOT match the computed pk for the given sk."); } @@ -148,7 +149,7 @@ public class GenericOpenSSHEd25519PrivateKeyEntryDecoder extends AbstractPrivate @Override public PublicKey recoverPublicKey(PrivateKey prvKey) throws GeneralSecurityException { - return edDSASupport.recoverEDDSAPublicKey(prvKey); + return SecurityUtils.recoverEDDSAPublicKey(prvKey); } @Override diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/SignatureEd25519.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/SignatureEd25519.java similarity index 96% rename from sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/SignatureEd25519.java rename to sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/SignatureEd25519.java index 7823acc31..3e7e1eb1d 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/SignatureEd25519.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/SignatureEd25519.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.sshd.common.util.security.eddsa; +package org.apache.sshd.common.util.security.eddsa.generic; import java.util.Map; diff --git a/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/Ed25519VectorsTest.java b/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/Ed25519VectorsTest.java index a7deabcd0..29ad12af8 100644 --- a/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/Ed25519VectorsTest.java +++ b/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/Ed25519VectorsTest.java @@ -33,8 +33,8 @@ import org.apache.sshd.common.signature.Signature; import org.apache.sshd.common.util.buffer.BufferUtils; import org.apache.sshd.common.util.security.SecurityProviderRegistrar; import org.apache.sshd.common.util.security.SecurityUtils; -import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; import org.apache.sshd.common.util.security.eddsa.generic.EdDSAUtils; +import org.apache.sshd.common.util.security.eddsa.generic.SignatureEd25519; import org.apache.sshd.util.test.JUnitTestSupport; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeAll; @@ -61,7 +61,7 @@ class Ed25519VectorsTest extends JUnitTestSupport { private byte[] expSignature; void initEd25519VectorsTest( - String name, EdDSASupport support, String prvKey, String pubKey, String msg, String signature) + String name, String prvKey, String pubKey, String msg, String signature) throws GeneralSecurityException, IOException { prvBytes = BufferUtils.decodeHex(BufferUtils.EMPTY_HEX_SEPARATOR, prvKey); privateKey = EdDSAUtils.getPrivateKey(prvBytes.clone()); @@ -80,28 +80,27 @@ class Ed25519VectorsTest extends JUnitTestSupport { if (registrar == null) { throw new IllegalStateException("Neither net.i2p nor BC registered"); } - EdDSASupport support = registrar.getEdDSASupport().orElseThrow(() -> new IllegalStateException("No EdDSA support")); - String supportClassName = support.getClass().getSimpleName(); + String supportClassName = registrar.getClass().getSimpleName(); parameters.add(new Object[] { - supportClassName + " TEST1 - empty message", support, + supportClassName + " TEST1 - empty message", "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a", "", "e5564300c360ac729086e2cc806e828a" + "84877f1eb8e5d974d873e06522490155" + "5fb8821590a33bacc61e39701cf9b46b" + "d25bf5f0595bbe24655141438e7a100b" }); parameters.add(new Object[] { - supportClassName + " TEST2 - one byte", support, + supportClassName + " TEST2 - one byte", "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb", "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c", "72", "92a009a9f0d4cab8720e820b5f642540" + "a2b27b5416503f8fb3762223ebdb69da" + "085ac1e43e15996e458f3613d0f11d8c" + "387b2eaeb4302aeeb00d291612bb0c00" }); parameters.add(new Object[] { - supportClassName + " TEST3 - 2 bytes", support, + supportClassName + " TEST3 - 2 bytes", "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7", "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025", "af82", "6291d657deec24024827e69c3abe01a3" + "0ce548a284743a445e3680d7db5ac3ac" + "18ff9b538d16f290ae67f760984dc659" + "4a7c15e9716ed28dc027beceea1ec40a" }); parameters.add(new Object[] { - supportClassName + " TEST1024 - large message", support, + supportClassName + " TEST1024 - large message", "f5e5767cf153319517630f226876b86c8160cc583bc013744c6bf255f5cc0ee5", "278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e", "08b8b2b733424243760fe426a4b54908" + "632110a66c2f6591eabd3345e3e4eb98" + "fa6e264bf09efe12ee50f8f54e9f77b1" @@ -180,9 +179,8 @@ class Ed25519VectorsTest extends JUnitTestSupport { @MethodSource("parameters") @ParameterizedTest(name = "{0}") void publicKeyBytes( - String name, EdDSASupport support, String prvKey, String pubKey, String msg, - String signature) throws Exception { - initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature); + String name, String prvKey, String pubKey, String msg, String signature) throws Exception { + initEd25519VectorsTest(name, prvKey, pubKey, msg, signature); byte[] publicSeed = EdDSAUtils.getBytes(publicKey); assertArrayEquals(pubBytes, publicSeed, "Mismatched public seed value"); } @@ -190,9 +188,8 @@ class Ed25519VectorsTest extends JUnitTestSupport { @MethodSource("parameters") @ParameterizedTest(name = "{0}") void privateKeyBytes( - String name, EdDSASupport support, String prvKey, String pubKey, String msg, - String signature) throws Exception { - initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature); + String name, String prvKey, String pubKey, String msg, String signature) throws Exception { + initEd25519VectorsTest(name, prvKey, pubKey, msg, signature); byte[] privateSeed = EdDSAUtils.getBytes(privateKey); assertArrayEquals(prvBytes, privateSeed, "Mismatched private seed value"); } @@ -200,9 +197,8 @@ class Ed25519VectorsTest extends JUnitTestSupport { @MethodSource("parameters") @ParameterizedTest(name = "{0}") void signature( - String name, EdDSASupport support, String prvKey, String pubKey, String msg, - String signature) throws Exception { - initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature); + String name, String prvKey, String pubKey, String msg, String signature) throws Exception { + initEd25519VectorsTest(name, prvKey, pubKey, msg, signature); Signature signer = new SignatureEd25519(); signer.initSigner(null, privateKey); signer.update(null, msgBytes.clone()); @@ -219,10 +215,8 @@ class Ed25519VectorsTest extends JUnitTestSupport { @MethodSource("parameters") @ParameterizedTest(name = "{0}") void partialBufferSignature( - String name, EdDSASupport support, String prvKey, String pubKey, String msg, - String signature) - throws Exception { - initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature); + String name, String prvKey, String pubKey, String msg, String signature) throws Exception { + initEd25519VectorsTest(name, prvKey, pubKey, msg, signature); byte[] extraData = getCurrentTestName().getBytes(StandardCharsets.UTF_8); byte[] dataBuf = new byte[msgBytes.length + extraData.length]; int offset = extraData.length / 2; @@ -246,10 +240,9 @@ class Ed25519VectorsTest extends JUnitTestSupport { @MethodSource("parameters") @ParameterizedTest(name = "{0}") void recoverEDDSAPublicKey( - String name, EdDSASupport support, String prvKey, String pubKey, String msg, - String signature) throws Exception { - initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature); - PublicKey recoveredKey = support.recoverEDDSAPublicKey(privateKey); + String name, String prvKey, String pubKey, String msg, String signature) throws Exception { + initEd25519VectorsTest(name, prvKey, pubKey, msg, signature); + PublicKey recoveredKey = SecurityUtils.recoverEDDSAPublicKey(privateKey); assertTrue(SecurityUtils.compareEDDSAPPublicKeys(publicKey, recoveredKey), "Recovered key is not equal"); byte[] recoveredBytes = EdDSAUtils.getBytes(recoveredKey); assertArrayEquals(pubBytes, recoveredBytes, "Mismatched public seed value"); @@ -258,9 +251,8 @@ class Ed25519VectorsTest extends JUnitTestSupport { @MethodSource("parameters") @ParameterizedTest(name = "{0}") void createPublicKeySpec( - String name, EdDSASupport support, String prvKey, String pubKey, String msg, - String signature) throws Exception { - initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature); + String name, String prvKey, String pubKey, String msg, String signature) throws Exception { + initEd25519VectorsTest(name, prvKey, pubKey, msg, signature); KeySpec keySpec = EdDSAUtils.createKeySpec(publicKey); KeyFactory keyFactory = SecurityUtils.getKeyFactory(SecurityUtils.ED25519); PublicKey generatedKey = keyFactory.generatePublic(keySpec); @@ -272,9 +264,8 @@ class Ed25519VectorsTest extends JUnitTestSupport { @MethodSource("parameters") @ParameterizedTest(name = "{0}") void createPrivateKeySpec( - String name, EdDSASupport support, String prvKey, String pubKey, String msg, - String signature) throws Exception { - initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature); + String name, String prvKey, String pubKey, String msg, String signature) throws Exception { + initEd25519VectorsTest(name, prvKey, pubKey, msg, signature); KeySpec keySpec = EdDSAUtils.createKeySpec(privateKey); KeyFactory keyFactory = SecurityUtils.getKeyFactory(SecurityUtils.ED25519); PrivateKey generatedKey = keyFactory.generatePrivate(keySpec); diff --git a/sshd-core/src/test/java/org/apache/sshd/common/signature/SignatureFactoriesTest.java b/sshd-core/src/test/java/org/apache/sshd/common/signature/SignatureFactoriesTest.java index fc2072918..27fc492dc 100644 --- a/sshd-core/src/test/java/org/apache/sshd/common/signature/SignatureFactoriesTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/common/signature/SignatureFactoriesTest.java @@ -43,6 +43,7 @@ import org.apache.sshd.common.keyprovider.KeyTypeIndicator; import org.apache.sshd.common.session.SessionContext; import org.apache.sshd.common.util.ValidateUtils; import org.apache.sshd.common.util.security.SecurityUtils; +import org.apache.sshd.common.util.security.eddsa.generic.Ed25519PublicKeyDecoder; import org.apache.sshd.server.SshServer; import org.apache.sshd.util.test.BaseTestSupport; import org.apache.sshd.util.test.CoreTestSupportUtils; @@ -94,7 +95,7 @@ class SignatureFactoriesTest extends BaseTestSupport implements KeyTypeIndicator curve.isSupported() ? ECDSAPublicKeyEntryDecoder.INSTANCE : null); } addTests(list, KeyPairProvider.SSH_ED25519, BuiltinSignatures.ed25519, ED25519_SIZES, - SecurityUtils.isEDDSACurveSupported() ? SecurityUtils.getEDDSAPublicKeyEntryDecoder() : null); + SecurityUtils.isEDDSACurveSupported() ? Ed25519PublicKeyDecoder.INSTANCE : null); return Collections.unmodifiableList(list); }
