diff --git a/docs/dependencies.md b/docs/dependencies.md index 39aa4f5e5..04331b6b4 100644 --- a/docs/dependencies.md +++ b/docs/dependencies.md @@ -93,10 +93,10 @@ implementation. This is also an **optional** dependency and must be add explicit ## [ed25519-java](https://github.com/str4d/ed25519-java) -Required for supporting [ssh-ed25519](https://tools.ietf.org/html/draft-bjh21-ssh-ed25519-02) keys -and [ed25519-sha-512](https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-02) signatures. **Note:** -the required Maven module(s) are defined as `optional` so must be added as an **explicit** dependency in -order to be included in the classpath: +Can optionally be provided to support [ssh-ed25519](https://tools.ietf.org/html/draft-bjh21-ssh-ed25519-02) keys +and [ed25519-sha-512](https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-02) signatures where [Bouncy Castle](#bouncy-castle) is not suitable. **Note:** +use of this dependency is not recommended, but it can be added as an **explicit** dependency in +order to provide Ed25519 support as follows: ```xml @@ -108,5 +108,3 @@ order to be included in the classpath: ``` - -The code contains support for reading _ed25519_ [OpenSSH formatted private keys](https://issues.apache.org/jira/browse/SSHD-703). diff --git a/docs/files-parsing.md b/docs/files-parsing.md index c8c8368e4..a26b6eea1 100644 --- a/docs/files-parsing.md +++ b/docs/files-parsing.md @@ -18,8 +18,8 @@ and `HostConfigEntry#readHostConfigEntries`. ### PEM/OpenSSH The common code contains built-in support for parsing PEM and/or _OpenSSH_ formatted key files and using them for authentication purposes. -As mentioned previously, it can leverage _Bouncycastle_ if available, but can do most of the work without it as well. For _ed25519_ support, -one must provide the _eddsa_ artifact dependency. +As mentioned previously, it can leverage _Bouncy Castle_ if available, but can do most of the work without it as well. For _ed25519_ support, +one must provide either `net.i2p.crypto.eddsa` or _Bouncy Castle_ as a dependency; if both are present `net.i2p.crypto.eddsa` is used. ### [PUTTY](https://www.putty.org/) diff --git a/docs/standards.md b/docs/standards.md index 58314f57e..34cf56a56 100644 --- a/docs/standards.md +++ b/docs/standards.md @@ -110,7 +110,8 @@ mlkem1024nistp384-sha384. ### Signatures/Keys * ssh-dss, ssh-rsa, rsa-sha2-256, rsa-sha2-512, nistp256, nistp384, nistp521 -, ssh-ed25519 (requires `eddsa` optional module), sk-ecdsa-sha2-nistp256@openssh.com, sk-ssh-ed25519@openssh.com +, ssh-ed25519 (requires Bouncy Castle or `net.i2p.crypto.eddsa` as an optional dependency - if both are present, `net.i2p.crypto.eddsa` is used) +, sk-ecdsa-sha2-nistp256@openssh.com, sk-ssh-ed25519@openssh.com , ssh-rsa-cert-v01@openssh.com, ssh-dss-cert-v01@openssh.com, ssh-ed25519-cert-v01@openssh.com , ecdsa-sha2-nistp256-cert-v01@openssh.com, ecdsa-sha2-nistp384-cert-v01@openssh.com , ecdsa-sha2-nistp521-cert-v01@openssh.com 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 9f58f0046..28883cb8d 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 @@ -874,7 +874,8 @@ public static String getKeyType(Key key) { } else { return curve.getKeyType(); } - } else if (SecurityUtils.EDDSA.equalsIgnoreCase(key.getAlgorithm())) { + } else if (SecurityUtils.EDDSA.equalsIgnoreCase(key.getAlgorithm()) + || SecurityUtils.ED25519.equalsIgnoreCase(key.getAlgorithm())) { return KeyPairProvider.SSH_ED25519; } @@ -1073,6 +1074,9 @@ public static boolean compareKeys(PublicKey k1, PublicKey k2) { } else if ((k1 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k1.getAlgorithm()) && (k2 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k2.getAlgorithm())) { return SecurityUtils.compareEDDSAPPublicKeys(k1, k2); + } else if ((k1 != null) && SecurityUtils.ED25519.equalsIgnoreCase(k1.getAlgorithm()) + && (k2 != null) && SecurityUtils.ED25519.equalsIgnoreCase(k2.getAlgorithm())) { + return SecurityUtils.compareEDDSAPPublicKeys(k1, k2); } else if ((k1 instanceof SkED25519PublicKey) && (k2 instanceof SkED25519PublicKey)) { return compareSkEd25519Keys(SkED25519PublicKey.class.cast(k1), SkED25519PublicKey.class.cast(k2)); } else if ((k1 instanceof OpenSshCertificate) && (k2 instanceof OpenSshCertificate)) { 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 fbb5d233f..8d86aa0bc 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,7 @@ 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.Ed25519PEMResourceKeyParser; +import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; /** * @author Apache MINA SSHD Project @@ -147,9 +147,9 @@ public Collection extractKeyPairs(byte[] encBytes, PKCS8PrivateKeyInfo kp = ECDSAPEMResourceKeyPairParser.parseECKeyPair(curve, parser); } } else if (SecurityUtils.isEDDSACurveSupported() - && Ed25519PEMResourceKeyParser.ED25519_OID.endsWith(oid)) { + && EdDSASupport.ED25519_OID.endsWith(oid)) { ASN1Object privateKeyBytes = pkcs8Info.getPrivateKeyBytes(); - kp = Ed25519PEMResourceKeyParser.decodeEd25519KeyPair(privateKeyBytes.getPureValueBytes()); + kp = EdDSASupport.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/util/buffer/Buffer.java b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java index 275280880..bb7bfc2d8 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java @@ -1042,7 +1042,7 @@ public void putRawPublicKeyBytes(PublicKey key) { byte[] ecPoint = ECCurves.encodeECPoint(ecKey.getW(), ecParams); putString(curve.getName()); putBytes(ecPoint); - } else if (SecurityUtils.EDDSA.equals(key.getAlgorithm())) { + } else if (SecurityUtils.EDDSA.equals(key.getAlgorithm()) || SecurityUtils.ED25519.equals(key.getAlgorithm())) { SecurityUtils.putRawEDDSAPublicKey(this, key); } else if (key instanceof SecurityKeyPublicKey) { putRawPublicKeyBytes(((SecurityKeyPublicKey) key).getDelegatePublicKey()); 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 a62c27e1f..a9cf72ab2 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 @@ -32,6 +32,7 @@ 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; @@ -45,6 +46,7 @@ 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 Apache MINA SSHD Project @@ -188,6 +190,13 @@ default boolean isCertificateFactorySupported(String type) { return isSecurityEntitySupported(CertificateFactory.class, type); } + /** + * @return the EdDSA support implementation associated with the security provider (if applicable) + */ + default Optional> getEdDSASupport() { + return Optional.empty(); + } + /** * @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 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 c45510c7d..97fabfdb5 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 @@ -43,6 +43,7 @@ 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; @@ -77,7 +78,7 @@ 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.security.eddsa.generic.EdDSASupport; import org.apache.sshd.common.util.threads.ThreadUtils; import org.apache.sshd.server.keyprovider.AbstractGeneratorHostKeyProvider; import org.slf4j.Logger; @@ -103,6 +104,8 @@ public final class SecurityUtils { // See EdDSAEngine.SIGNATURE_ALGORITHM public static final String CURVE_ED25519_SHA512 = "NONEwithEdDSA"; + public static final String ED25519 = "Ed25519"; + /** * System property used to configure the value for the minimum supported Diffie-Hellman Group Exchange key size. If * not set, then an internal auto-discovery mechanism is employed. If set to negative value then Diffie-Hellman @@ -606,67 +609,107 @@ public static RandomFactory getRandomFactory() { * @return {@code true} if EDDSA curves (e.g., {@code ed25519}) are supported */ public static boolean isEDDSACurveSupported() { + return getEdDSASupport().isPresent(); + } + + public static boolean isNetI2pCryptoEdDSARegistered() { + register(); + return isProviderRegistered(EDDSA); + } + + public static Optional> getEdDSASupport() { if (isFipsMode()) { - return false; + return Optional.empty(); } register(); - SecurityProviderRegistrar r = getRegisteredProvider(EDDSA); - return (r != null) && r.isEnabled() && r.isSupported(); + 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> support = netI2pCryptoProvider.getEdDSASupport(); + if (support.isPresent()) { + return support; + } + } + + for (Map.Entry entry : REGISTERED_PROVIDERS.entrySet()) { + Optional> support = entry.getValue().getEdDSASupport(); + if (support.isPresent()) { + return support; + } + } + } + return Optional.empty(); } /* -------------------------------------------------------------------- */ public static PublicKeyEntryDecoder getEDDSAPublicKeyEntryDecoder() { - if (!isEDDSACurveSupported()) { + Optional> support = getEdDSASupport(); + if (!support.isPresent()) { throw new UnsupportedOperationException(EDDSA + " provider N/A"); } - return EdDSASecurityProviderUtils.getEDDSAPublicKeyEntryDecoder(); + return support.get().getEDDSAPublicKeyEntryDecoder(); } public static PrivateKeyEntryDecoder getOpenSSHEDDSAPrivateKeyEntryDecoder() { - if (!isEDDSACurveSupported()) { + Optional> support = getEdDSASupport(); + if (!support.isPresent()) { throw new UnsupportedOperationException(EDDSA + " provider N/A"); } - return EdDSASecurityProviderUtils.getOpenSSHEDDSAPrivateKeyEntryDecoder(); + return support.get().getOpenSSHEDDSAPrivateKeyEntryDecoder(); } public static org.apache.sshd.common.signature.Signature getEDDSASigner() { - if (isEDDSACurveSupported()) { - return EdDSASecurityProviderUtils.getEDDSASignature(); + Optional> support = getEdDSASupport(); + if (support.isPresent()) { + return support.get().getEDDSASigner(); } throw new UnsupportedOperationException(EDDSA + " Signer not available"); } public static int getEDDSAKeySize(Key key) { - return EdDSASecurityProviderUtils.getEDDSAKeySize(key); + Optional> support = getEdDSASupport(); + return support.map(edDSASupport -> edDSASupport.getEDDSAKeySize(key)).orElse(-1); } public static Class getEDDSAPublicKeyType() { - return isEDDSACurveSupported() ? EdDSASecurityProviderUtils.getEDDSAPublicKeyType() : PublicKey.class; + Optional> support = getEdDSASupport(); + if (!support.isPresent()) { + return PublicKey.class; + } + return support.get().getEDDSAPublicKeyType(); } public static Class getEDDSAPrivateKeyType() { - return isEDDSACurveSupported() ? EdDSASecurityProviderUtils.getEDDSAPrivateKeyType() : PrivateKey.class; + Optional> support = getEdDSASupport(); + if (!support.isPresent()) { + return PrivateKey.class; + } + return support.get().getEDDSAPrivateKeyType(); } public static boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2) { - return isEDDSACurveSupported() ? EdDSASecurityProviderUtils.compareEDDSAPPublicKeys(k1, k2) : false; + Optional> support = getEdDSASupport(); + return support.map(edDSASupport -> edDSASupport.compareEDDSAPPublicKeys(k1, k2)).orElse(false); } public static boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2) { - return isEDDSACurveSupported() ? EdDSASecurityProviderUtils.compareEDDSAPrivateKeys(k1, k2) : false; + Optional> support = getEdDSASupport(); + return support.map(edDSASupport -> edDSASupport.compareEDDSAPrivateKeys(k1, k2)).orElse(false); } public static PublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException { - if (!isEDDSACurveSupported()) { + Optional> support = getEdDSASupport(); + if (!support.isPresent()) { throw new NoSuchAlgorithmException(EDDSA + " provider not supported"); } - return EdDSASecurityProviderUtils.recoverEDDSAPublicKey(key); + return support.get().recoverEDDSAPublicKey(key); } public static PublicKey generateEDDSAPublicKey(String keyType, byte[] seed) throws GeneralSecurityException { @@ -674,19 +717,34 @@ public static PublicKey generateEDDSAPublicKey(String keyType, byte[] seed) thro throw new InvalidKeyException("Unsupported key type: " + keyType); } - if (!isEDDSACurveSupported()) { + Optional> support = getEdDSASupport(); + if (!support.isPresent()) { throw new NoSuchAlgorithmException(EDDSA + " provider not supported"); } - return EdDSASecurityProviderUtils.generateEDDSAPublicKey(seed); + return support.get().generateEDDSAPublicKey(seed); + } + + public static PrivateKey generateEDDSAPrivateKey(String keyType, byte[] seed) throws GeneralSecurityException, IOException { + if (!KeyPairProvider.SSH_ED25519.equals(keyType)) { + throw new InvalidKeyException("Unsupported key type: " + keyType); + } + + Optional> support = getEdDSASupport(); + if (!support.isPresent()) { + throw new NoSuchAlgorithmException(EDDSA + " provider not supported"); + } + + return support.get().generateEDDSAPrivateKey(seed); } public static B putRawEDDSAPublicKey(B buffer, PublicKey key) { - if (!isEDDSACurveSupported()) { + Optional> support = getEdDSASupport(); + if (!support.isPresent()) { throw new UnsupportedOperationException(EDDSA + " provider not supported"); } - return EdDSASecurityProviderUtils.putRawEDDSAPublicKey(buffer, key); + return support.get().putRawEDDSAPublicKey(buffer, key); } public static B putEDDSAKeyPair(B buffer, KeyPair kp) { @@ -694,11 +752,12 @@ public static B putEDDSAKeyPair(B buffer, KeyPair kp) { } public static B putEDDSAKeyPair(B buffer, PublicKey pubKey, PrivateKey prvKey) { - if (!isEDDSACurveSupported()) { + Optional> support = getEdDSASupport(); + if (!support.isPresent()) { throw new UnsupportedOperationException(EDDSA + " provider not supported"); } - return EdDSASecurityProviderUtils.putEDDSAKeyPair(buffer, pubKey, prvKey); + return support.get().putEDDSAKeyPair(buffer, pubKey, prvKey); } 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/BouncyCastleSecurityProviderRegistrar.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java index f5cf4c549..8a3830bcb 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 @@ -25,12 +25,14 @@ import java.security.Security; import java.security.Signature; import java.util.Objects; +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; /** @@ -43,10 +45,12 @@ public class BouncyCastleSecurityProviderRegistrar extends AbstractSecurityProvi private static final String BCFIPS_PROVIDER_NAME = "BCFIPS"; private static final String BC_PROVIDER_NAME = "BC"; private static final String NAME_FIELD = "PROVIDER_NAME"; + private static final String EDDSA_KEY_CLASS_NAME = "org.bouncycastle.jcajce.interfaces.EdDSAKey"; // Do not define a static registrar instance to minimize class loading issues private final AtomicReference supportHolder = new AtomicReference<>(null); private final AtomicReference allSupportHolder = new AtomicReference<>(); + private final AtomicReference edDSASupportHolder = new AtomicReference<>(null); private String providerClass; private String providerName; @@ -109,14 +113,15 @@ public boolean isSecurityEntitySupported(Class entityType, String name) { 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) { + if (Objects.compare(name, SecurityUtils.EDDSA, String.CASE_INSENSITIVE_ORDER) == 0 + && SecurityUtils.isNetI2pCryptoEdDSARegistered()) { return false; } } else if (Signature.class.isAssignableFrom(entityType)) { - if (Objects.compare(name, SecurityUtils.CURVE_ED25519_SHA512, String.CASE_INSENSITIVE_ORDER) == 0) { + if (Objects.compare(name, SecurityUtils.CURVE_ED25519_SHA512, String.CASE_INSENSITIVE_ORDER) == 0 + && SecurityUtils.isNetI2pCryptoEdDSARegistered()) { return false; } } @@ -176,4 +181,30 @@ public boolean isSupported() { return supported.booleanValue(); } + + @Override + public Optional> getEdDSASupport() { + if (!isEdDSASupported()) { + return Optional.empty(); + } + return Optional.of(new org.apache.sshd.common.util.security.eddsa.bouncycastle.BouncyCastleEdDSASupport()); + } + + private boolean isEdDSASupported() { + if (!isSupported()) { + return false; + } + + Boolean edDSASupported; + synchronized (edDSASupportHolder) { + edDSASupported = edDSASupportHolder.get(); + if (edDSASupported != null) { + return edDSASupported.booleanValue(); + } + Class clazz = ThreadUtils.resolveDefaultClass(getClass(), EDDSA_KEY_CLASS_NAME); + edDSASupported = Boolean.valueOf(clazz != null); + edDSASupportHolder.set(edDSASupported); + return edDSASupported; + } + } } 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 index 846757413..ab64ea720 100644 --- 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 @@ -20,161 +20,34 @@ package org.apache.sshd.common.util.security.eddsa; import java.io.IOException; -import java.io.InputStream; -import java.io.StreamCorruptedException; -import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.KeyFactory; -import java.security.KeyPair; import java.security.NoSuchAlgorithmException; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import net.i2p.crypto.eddsa.EdDSAKey; import net.i2p.crypto.eddsa.EdDSAPrivateKey; -import net.i2p.crypto.eddsa.EdDSAPublicKey; 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.NamedResource; -import org.apache.sshd.common.config.keys.FilePasswordProvider; -import org.apache.sshd.common.config.keys.loader.pem.AbstractPEMResourceKeyPairParser; -import org.apache.sshd.common.session.SessionContext; -import org.apache.sshd.common.util.GenericUtils; -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.io.input.NoCloseInputStream; 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 Apache MINA SSHD Project */ -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". - public static final String BEGIN_MARKER = "BEGIN EDDSA PRIVATE KEY"; - - // Some (older) OpenSSL versions used this if "traditional" (RFC 1421) PEM format was chosen: - public static final String BEGIN_ED25519_MARKER = "BEGIN ED25519 PRIVATE KEY"; - - public static final List BEGINNERS = GenericUtils.unmodifiableList(BEGIN_MARKER, BEGIN_ED25519_MARKER); - - public static final String END_MARKER = "END EDDSA PRIVATE KEY"; - public static final String END_ED25519_MARKER = "END ED25519 PRIVATE KEY"; - public static final List ENDERS = GenericUtils.unmodifiableList(END_MARKER, END_ED25519_MARKER); - - /** - * @see RFC8412 section 3 - */ - public static final String ED25519_OID = "1.3.101.112"; +public class Ed25519PEMResourceKeyParser extends GenericEd25519PEMResourceKeyParser { public static final Ed25519PEMResourceKeyParser INSTANCE = new Ed25519PEMResourceKeyParser(); public Ed25519PEMResourceKeyParser() { - super(EdDSAKey.KEY_ALGORITHM, ED25519_OID, BEGINNERS, ENDERS); - } - - @Override - public Collection extractKeyPairs( - SessionContext session, NamedResource resourceKey, String beginMarker, - String endMarker, FilePasswordProvider passwordProvider, - InputStream stream, Map headers) - throws IOException, GeneralSecurityException { - KeyPair kp = parseEd25519KeyPair(stream, false); - return Collections.singletonList(kp); - } - - public static KeyPair parseEd25519KeyPair( - InputStream inputStream, boolean okToClose) - throws IOException, GeneralSecurityException { - try (DERParser parser = new DERParser(NoCloseInputStream.resolveInputStream(inputStream, okToClose))) { - return parseEd25519KeyPair(parser); - } - } - - /* - * See https://tools.ietf.org/html/rfc8410#section-7 - * - * SEQUENCE { - * INTEGER 0x00 (0 decimal) - * SEQUENCE { - * OBJECTIDENTIFIER 1.3.101.112 - * } - * OCTETSTRING keyData - * } - * - * NOTE: there is another variant that also has some extra parameters - * but it has the same "prefix" structure so we don't care - */ - public static KeyPair parseEd25519KeyPair(DERParser parser) throws IOException, GeneralSecurityException { - ASN1Object obj = parser.readObject(); - if (obj == null) { - throw new StreamCorruptedException("Missing version value"); - } - - BigInteger version = obj.asInteger(); - if (!BigInteger.ZERO.equals(version)) { - throw new StreamCorruptedException("Invalid version: " + version); - } - - obj = parser.readObject(); - if (obj == null) { - throw new StreamCorruptedException("Missing OID container"); - } - - ASN1Type objType = obj.getObjType(); - if (objType != ASN1Type.SEQUENCE) { - throw new StreamCorruptedException("Unexpected OID object type: " + objType); - } - - List curveOid; - try (DERParser oidParser = obj.createParser()) { - obj = oidParser.readObject(); - if (obj == null) { - throw new StreamCorruptedException("Missing OID value"); - } - - curveOid = obj.asOID(); - } - - String oid = GenericUtils.join(curveOid, '.'); - // TODO modify if more curves supported - if (!ED25519_OID.equals(oid)) { - throw new StreamCorruptedException("Unsupported curve OID: " + oid); - } - - obj = parser.readObject(); - if (obj == null) { - throw new StreamCorruptedException("Missing key data"); - } - - return decodeEd25519KeyPair(obj.getValue()); - } - - public static KeyPair decodeEd25519KeyPair(byte[] keyData) throws IOException, GeneralSecurityException { - EdDSAPrivateKey privateKey = decodeEdDSAPrivateKey(keyData); - EdDSAPublicKey publicKey = EdDSASecurityProviderUtils.recoverEDDSAPublicKey(privateKey); - return new KeyPair(publicKey, privateKey); + super(); } public static EdDSAPrivateKey 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 generateEdDSAPrivateKey(obj.getValue()); - } + return EdDSAPrivateKey.class.cast(EdDSASupport.decodeEdDSAPrivateKey(keyData)); } public static EdDSAPrivateKey generateEdDSAPrivateKey(byte[] seed) throws GeneralSecurityException { 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 index 9decb2b31..7c5ac4d5a 100644 --- 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 @@ -18,84 +18,22 @@ */ package org.apache.sshd.common.util.security.eddsa; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.security.GeneralSecurityException; -import java.security.KeyFactory; -import java.security.KeyPairGenerator; -import java.util.Collections; -import java.util.Map; -import java.util.Objects; - import net.i2p.crypto.eddsa.EdDSAPrivateKey; import net.i2p.crypto.eddsa.EdDSAPublicKey; -import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; -import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; -import org.apache.sshd.common.config.keys.KeyEntryResolver; -import org.apache.sshd.common.config.keys.impl.AbstractPublicKeyEntryDecoder; -import org.apache.sshd.common.keyprovider.KeyPairProvider; -import org.apache.sshd.common.session.SessionContext; -import org.apache.sshd.common.util.security.SecurityUtils; +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 Apache MINA SSHD Project */ -public final class Ed25519PublicKeyDecoder extends AbstractPublicKeyEntryDecoder { - public static final int MAX_ALLOWED_SEED_LEN = 1024; // in reality it is much less than this +public final class Ed25519PublicKeyDecoder extends GenericEd25519PublicKeyDecoder { public static final Ed25519PublicKeyDecoder INSTANCE = new Ed25519PublicKeyDecoder(); private Ed25519PublicKeyDecoder() { - super(EdDSAPublicKey.class, EdDSAPrivateKey.class, - Collections.unmodifiableList( - Collections.singletonList( - KeyPairProvider.SSH_ED25519))); - } - - @Override - public EdDSAPublicKey clonePublicKey(EdDSAPublicKey key) throws GeneralSecurityException { - if (key == null) { - return null; - } else { - return generatePublicKey(new EdDSAPublicKeySpec(key.getA(), key.getParams())); - } - } - - @Override - public EdDSAPrivateKey clonePrivateKey(EdDSAPrivateKey key) throws GeneralSecurityException { - if (key == null) { - return null; - } else { - return generatePrivateKey(new EdDSAPrivateKeySpec(key.getSeed(), key.getParams())); - } - } - - @Override - public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException { - return SecurityUtils.getKeyPairGenerator(SecurityUtils.EDDSA); - } - - @Override - public String encodePublicKey(OutputStream s, EdDSAPublicKey key) throws IOException { - Objects.requireNonNull(key, "No public key provided"); - KeyEntryResolver.encodeString(s, KeyPairProvider.SSH_ED25519); - byte[] seed = getSeedValue(key); - KeyEntryResolver.writeRLEBytes(s, seed); - return KeyPairProvider.SSH_ED25519; - } - - @Override - public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException { - return SecurityUtils.getKeyFactory(SecurityUtils.EDDSA); - } - - @Override - public EdDSAPublicKey decodePublicKey( - SessionContext session, String keyType, InputStream keyData, Map headers) - throws IOException, GeneralSecurityException { - byte[] seed = KeyEntryResolver.readRLEBytes(keyData, MAX_ALLOWED_SEED_LEN); - return EdDSAPublicKey.class.cast(SecurityUtils.generateEDDSAPublicKey(keyType, seed)); + super(EdDSAPublicKey.class, EdDSAPrivateKey.class, new NetI2pCryptoEdDSASupport()); } public static byte[] getSeedValue(EdDSAPublicKey key) { 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 8fbd28c7d..edcb55bf0 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 @@ -23,11 +23,13 @@ import java.security.Provider; import java.security.Signature; import java.util.Objects; +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.SecurityUtils; +import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; import org.apache.sshd.common.util.threads.ThreadUtils; /** @@ -100,4 +102,12 @@ public boolean isSupported() { return supported.booleanValue(); } + + @Override + public Optional> getEdDSASupport() { + if (!isSupported()) { + return Optional.empty(); + } + return Optional.of(new NetI2pCryptoEdDSASupport()); + } } 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 058472cfd..4e6cfb3b2 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 @@ -41,6 +41,7 @@ import org.apache.sshd.common.util.ValidateUtils; import org.apache.sshd.common.util.buffer.Buffer; import org.apache.sshd.common.util.security.SecurityUtils; +import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; /** * @author Apache MINA SSHD Project @@ -48,7 +49,7 @@ public final class EdDSASecurityProviderUtils { // See EdDSANamedCurveTable public static final String CURVE_ED25519_SHA512 = "Ed25519"; - public static final int KEY_SIZE = 256; + public static final int KEY_SIZE = EdDSASupport.KEY_SIZE; private EdDSASecurityProviderUtils() { throw new UnsupportedOperationException("No instance"); 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 new file mode 100644 index 000000000..e88f5bdc0 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/NetI2pCryptoEdDSASupport.java @@ -0,0 +1,135 @@ +/* + * 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.Key; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.KeySpec; + +import net.i2p.crypto.eddsa.EdDSAPrivateKey; +import net.i2p.crypto.eddsa.EdDSAPublicKey; +import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; +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.signature.Signature; +import org.apache.sshd.common.util.buffer.Buffer; +import org.apache.sshd.common.util.security.SecurityUtils; +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 Signature getEDDSASigner() { + return EdDSASecurityProviderUtils.getEDDSASignature(); + } + + @Override + public int getEDDSAKeySize(Key key) { + return EdDSASecurityProviderUtils.getEDDSAKeySize(key); + } + + @Override + public Class getEDDSAPublicKeyType() { + return EdDSASecurityProviderUtils.getEDDSAPublicKeyType(); + } + + @Override + public Class getEDDSAPrivateKeyType() { + return EdDSASecurityProviderUtils.getEDDSAPrivateKeyType(); + } + + @Override + public boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2) { + return EdDSASecurityProviderUtils.compareEDDSAPPublicKeys(k1, k2); + } + + @Override + public boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2) { + return EdDSASecurityProviderUtils.compareEDDSAPrivateKeys(k1, k2); + } + + @Override + public EdDSAPublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException { + return EdDSASecurityProviderUtils.recoverEDDSAPublicKey(key); + } + + @Override + public EdDSAPublicKey generateEDDSAPublicKey(byte[] seed) throws GeneralSecurityException { + return (EdDSAPublicKey) EdDSASecurityProviderUtils.generateEDDSAPublicKey(seed); + } + + @Override + public EdDSAPrivateKey generateEDDSAPrivateKey(byte[] seed) throws GeneralSecurityException { + return Ed25519PEMResourceKeyParser.generateEdDSAPrivateKey(seed); + } + + @Override + public B putRawEDDSAPublicKey(B buffer, PublicKey key) { + return EdDSASecurityProviderUtils.putRawEDDSAPublicKey(buffer, key); + } + + @Override + public B putEDDSAKeyPair(B buffer, PublicKey pubKey, PrivateKey prvKey) { + return EdDSASecurityProviderUtils.putEDDSAKeyPair(buffer, pubKey, prvKey); + } + + @Override + public KeySpec createPublicKeySpec(EdDSAPublicKey publicKey) { + return new EdDSAPublicKeySpec(publicKey.getA(), publicKey.getParams()); + } + + @Override + public KeySpec createPrivateKeySpec(EdDSAPrivateKey privateKey) { + return new EdDSAPrivateKeySpec(privateKey.getSeed(), privateKey.getParams()); + } + + @Override + public byte[] getPublicKeyData(EdDSAPublicKey publicKey) { + return publicKey == null ? null : publicKey.getAbyte(); + } + + @Override + public byte[] getPrivateKeyData(EdDSAPrivateKey privateKey) throws IOException { + return privateKey.getSeed(); + } + + @Override + public String getKeyFactoryAlgorithm() { + return SecurityUtils.EDDSA; + } +} 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/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java index 478f97dd1..b4012a90f 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/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java @@ -19,168 +19,22 @@ package org.apache.sshd.common.util.security.eddsa; -import java.io.IOException; -import java.io.InputStream; -import java.security.GeneralSecurityException; -import java.security.InvalidKeyException; -import java.security.KeyFactory; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; -import java.util.Collections; -import java.util.Locale; -import java.util.Objects; - import net.i2p.crypto.eddsa.EdDSAPrivateKey; import net.i2p.crypto.eddsa.EdDSAPublicKey; -import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; -import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec; -import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; -import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; -import org.apache.sshd.common.config.keys.FilePasswordProvider; -import org.apache.sshd.common.config.keys.KeyEntryResolver; -import org.apache.sshd.common.config.keys.impl.AbstractPrivateKeyEntryDecoder; -import org.apache.sshd.common.keyprovider.KeyPairProvider; -import org.apache.sshd.common.session.SessionContext; -import org.apache.sshd.common.util.GenericUtils; -import org.apache.sshd.common.util.io.output.SecureByteArrayOutputStream; -import org.apache.sshd.common.util.security.SecurityUtils; +import org.apache.sshd.common.util.security.eddsa.generic.GenericOpenSSHEd25519PrivateKeyEntryDecoder; /** + * An implementation of {@link GenericOpenSSHEd25519PrivateKeyEntryDecoder} tied to the {@code net.i2p.crypto} EdDSA + * security provider + * * @author Apache MINA SSHD Project */ -public class OpenSSHEd25519PrivateKeyEntryDecoder extends AbstractPrivateKeyEntryDecoder { +public class OpenSSHEd25519PrivateKeyEntryDecoder + extends GenericOpenSSHEd25519PrivateKeyEntryDecoder { 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; public OpenSSHEd25519PrivateKeyEntryDecoder() { - super(EdDSAPublicKey.class, EdDSAPrivateKey.class, - Collections.unmodifiableList( - Collections.singletonList( - KeyPairProvider.SSH_ED25519))); - } - - @Override - public EdDSAPrivateKey decodePrivateKey( - SessionContext session, String keyType, FilePasswordProvider passwordProvider, InputStream keyData) - throws IOException, GeneralSecurityException { - if (!KeyPairProvider.SSH_ED25519.equals(keyType)) { - throw new InvalidKeyException("Unsupported key type: " + keyType); - } - - if (!SecurityUtils.isEDDSACurveSupported()) { - throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " provider not supported"); - } - - // ed25519 bernstein naming: pk .. public key, sk .. secret key - // we expect to find two byte arrays with the following structure (type:size): - // [pk:32], [sk:32,pk:32] - - byte[] pk = GenericUtils.EMPTY_BYTE_ARRAY; - byte[] keypair = GenericUtils.EMPTY_BYTE_ARRAY; - try { - pk = KeyEntryResolver.readRLEBytes(keyData, PK_SIZE * 2); - keypair = KeyEntryResolver.readRLEBytes(keyData, KEYPAIR_SIZE * 2); - if (pk.length != PK_SIZE) { - throw new InvalidKeyException( - String.format(Locale.ENGLISH, "Unexpected pk size: %s (expected %s)", pk.length, PK_SIZE)); - } - - if (keypair.length != KEYPAIR_SIZE) { - throw new InvalidKeyException( - String.format(Locale.ENGLISH, "Unexpected keypair size: %s (expected %s)", keypair.length, - KEYPAIR_SIZE)); - } - - // verify that the keypair contains the expected pk - // yes, it's stored redundant, this seems to mimic the output structure of the keypair generation interface - if (!Arrays.equals(pk, Arrays.copyOfRange(keypair, SK_SIZE, KEYPAIR_SIZE))) { - throw new InvalidKeyException("Keypair did not contain the public key."); - } - - byte[] sk = Arrays.copyOf(keypair, SK_SIZE); - EdDSAParameterSpec params = EdDSANamedCurveTable.getByName(EdDSASecurityProviderUtils.CURVE_ED25519_SHA512); - EdDSAPrivateKey privateKey = generatePrivateKey(new EdDSAPrivateKeySpec(sk, params)); - - // the private key class contains the calculated public key (Abyte) - // pointers to the corresponding code: - // EdDSAPrivateKeySpec.EdDSAPrivateKeySpec(byte[], EdDSAParameterSpec): A = spec.getB().scalarMultiply(a); - // EdDSAPrivateKey.EdDSAPrivateKey(EdDSAPrivateKeySpec): this.Abyte = this.A.toByteArray(); - - // we can now verify the generated pk matches the one we read - if (!Arrays.equals(privateKey.getAbyte(), pk)) { - throw new InvalidKeyException("The provided pk does NOT match the computed pk for the given sk."); - } - - return privateKey; - } finally { - // get rid of sensitive data a.s.a.p - Arrays.fill(pk, (byte) 0); - Arrays.fill(keypair, (byte) 0); - } - } - - @Override - public String encodePrivateKey(SecureByteArrayOutputStream s, EdDSAPrivateKey key, EdDSAPublicKey pubKey) - throws IOException { - Objects.requireNonNull(key, "No private key provided"); - - // ed25519 bernstein naming: pk .. public key, sk .. secret key - // we are expected to write the following arrays (type:size): - // [pk:32], [sk:32,pk:32] - - byte[] sk = key.getSeed(); - byte[] pk = key.getAbyte(); - - Objects.requireNonNull(sk, "No seed"); - - byte[] keypair = new byte[KEYPAIR_SIZE]; - System.arraycopy(sk, 0, keypair, 0, SK_SIZE); - System.arraycopy(pk, 0, keypair, SK_SIZE, PK_SIZE); - - KeyEntryResolver.writeRLEBytes(s, pk); - KeyEntryResolver.writeRLEBytes(s, keypair); - - return KeyPairProvider.SSH_ED25519; - } - - @Override - public boolean isPublicKeyRecoverySupported() { - return true; - } - - @Override - public EdDSAPublicKey recoverPublicKey(EdDSAPrivateKey prvKey) throws GeneralSecurityException { - return EdDSASecurityProviderUtils.recoverEDDSAPublicKey(prvKey); - } - - @Override - public EdDSAPublicKey clonePublicKey(EdDSAPublicKey key) throws GeneralSecurityException { - if (key == null) { - return null; - } else { - return generatePublicKey(new EdDSAPublicKeySpec(key.getA(), key.getParams())); - } - } - - @Override - public EdDSAPrivateKey clonePrivateKey(EdDSAPrivateKey key) throws GeneralSecurityException { - if (key == null) { - return null; - } else { - return generatePrivateKey(new EdDSAPrivateKeySpec(key.getSeed(), key.getParams())); - } - } - - @Override - public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException { - return SecurityUtils.getKeyPairGenerator(SecurityUtils.EDDSA); + super(EdDSAPublicKey.class, EdDSAPrivateKey.class, new NetI2pCryptoEdDSASupport()); } - @Override - public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException { - return SecurityUtils.getKeyFactory(SecurityUtils.EDDSA); - } } 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/SignatureEd25519.java index bba78751d..1c49809ea 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/SignatureEd25519.java @@ -18,34 +18,16 @@ */ package org.apache.sshd.common.util.security.eddsa; -import java.util.Map; - import net.i2p.crypto.eddsa.EdDSAEngine; -import org.apache.sshd.common.keyprovider.KeyPairProvider; -import org.apache.sshd.common.session.SessionContext; -import org.apache.sshd.common.signature.AbstractSignature; -import org.apache.sshd.common.util.ValidateUtils; +import org.apache.sshd.common.util.security.eddsa.generic.GenericSignatureEd25519; /** + * An implementation of {@link GenericSignatureEd25519} tied to the {@code net.i2p.crypto} EdDSA security provider. + * * @author Apache MINA SSHD Project */ -public class SignatureEd25519 extends AbstractSignature { +public class SignatureEd25519 extends GenericSignatureEd25519 { public SignatureEd25519() { super(EdDSAEngine.SIGNATURE_ALGORITHM); } - - @Override - public boolean verify(SessionContext session, byte[] sig) throws Exception { - byte[] data = sig; - Map.Entry encoding - = extractEncodedSignature(data, KeyPairProvider.SSH_ED25519::equalsIgnoreCase); - if (encoding != null) { - String keyType = encoding.getKey(); - ValidateUtils.checkTrue( - KeyPairProvider.SSH_ED25519.equals(keyType), "Mismatched key type: %s", keyType); - data = encoding.getValue(); - } - - return doVerify(data); - } } 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 new file mode 100644 index 000000000..6663c2e45 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java @@ -0,0 +1,169 @@ +/* + * 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.io.IOException; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; + +import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder; +import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder; +import org.apache.sshd.common.signature.Signature; +import org.apache.sshd.common.util.ValidateUtils; +import org.apache.sshd.common.util.buffer.Buffer; +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.apache.sshd.common.util.security.eddsa.generic.GenericSignatureEd25519; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters; +import org.bouncycastle.crypto.util.PrivateKeyFactory; +import org.bouncycastle.crypto.util.PrivateKeyInfoFactory; +import org.bouncycastle.jcajce.interfaces.EdDSAKey; +import org.bouncycastle.jcajce.interfaces.EdDSAPrivateKey; +import org.bouncycastle.jcajce.interfaces.EdDSAPublicKey; +import org.bouncycastle.jcajce.spec.RawEncodedKeySpec; + +public class BouncyCastleEdDSASupport implements EdDSASupport { + + public BouncyCastleEdDSASupport() { + super(); + } + + @Override + public PublicKeyEntryDecoder getEDDSAPublicKeyEntryDecoder() { + return new GenericEd25519PublicKeyDecoder<>(EdDSAPublicKey.class, EdDSAPrivateKey.class, this); + } + + @Override + public PrivateKeyEntryDecoder getOpenSSHEDDSAPrivateKeyEntryDecoder() { + return new GenericOpenSSHEd25519PrivateKeyEntryDecoder<>(EdDSAPublicKey.class, EdDSAPrivateKey.class, this); + } + + @Override + public Signature getEDDSASigner() { + return new GenericSignatureEd25519(SecurityUtils.EDDSA); + } + + @Override + public int getEDDSAKeySize(Key key) { + return key instanceof EdDSAKey ? KEY_SIZE : -1; + } + + @Override + public Class getEDDSAPublicKeyType() { + return EdDSAPublicKey.class; + } + + @Override + public Class getEDDSAPrivateKeyType() { + return EdDSAPrivateKey.class; + } + + @Override + public boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2) { + if (!(k1 instanceof EdDSAPublicKey) || !(k2 instanceof EdDSAPublicKey)) { + return false; + } + + return k1.equals(k2); + } + + @Override + public boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2) { + if (!(k1 instanceof EdDSAPrivateKey) || !(k2 instanceof EdDSAPrivateKey)) { + return false; + } + + return k1.equals(k2); + } + + @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(); + } + + @Override + public EdDSAPublicKey generateEDDSAPublicKey(byte[] seed) throws GeneralSecurityException { + RawEncodedKeySpec keySpec = new RawEncodedKeySpec(seed); + KeyFactory factory = SecurityUtils.getKeyFactory(getKeyFactoryAlgorithm()); + return (EdDSAPublicKey) factory.generatePublic(keySpec); + } + + @Override + public EdDSAPrivateKey generateEDDSAPrivateKey(byte[] seed) throws GeneralSecurityException, IOException { + Ed25519PrivateKeyParameters parameters = new Ed25519PrivateKeyParameters(seed); + PrivateKeyInfo info = PrivateKeyInfoFactory.createPrivateKeyInfo(parameters); + KeyFactory factory = SecurityUtils.getKeyFactory(getKeyFactoryAlgorithm()); + return (EdDSAPrivateKey) factory.generatePrivate(new PKCS8EncodedKeySpec(info.getEncoded())); + } + + @Override + public B putRawEDDSAPublicKey(B buffer, PublicKey key) { + EdDSAPublicKey edKey = ValidateUtils.checkInstanceOf(key, EdDSAPublicKey.class, "Not an EDDSA public key: %s", key); + buffer.putBytes(edKey.getPointEncoding()); + return buffer; + } + + @Override + public B putEDDSAKeyPair(B buffer, PublicKey pubKey, PrivateKey prvKey) { + ValidateUtils.checkInstanceOf(pubKey, EdDSAPublicKey.class, "Not an EDDSA public key: %s", pubKey); + ValidateUtils.checkInstanceOf(prvKey, EdDSAPrivateKey.class, "Not an EDDSA private key: %s", prvKey); + throw new UnsupportedOperationException("Full SSHD-440 implementation N/A"); + } + + @Override + public KeySpec createPublicKeySpec(EdDSAPublicKey publicKey) { + return new RawEncodedKeySpec(publicKey.getPointEncoding()); + } + + @Override + public KeySpec createPrivateKeySpec(EdDSAPrivateKey privateKey) { + return new PKCS8EncodedKeySpec(privateKey.getEncoded()); + } + + @Override + public byte[] getPublicKeyData(EdDSAPublicKey publicKey) { + return publicKey == null ? null : publicKey.getPointEncoding(); + } + + @Override + public byte[] getPrivateKeyData(EdDSAPrivateKey privateKey) throws IOException { + Ed25519PrivateKeyParameters parameters + = (Ed25519PrivateKeyParameters) PrivateKeyFactory.createKey(privateKey.getEncoded()); + return parameters.getEncoded(); + } + + @Override + public String getKeyFactoryAlgorithm() { + return SecurityUtils.ED25519; + } +} 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 new file mode 100644 index 000000000..a8ed9ac54 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java @@ -0,0 +1,198 @@ +/* + * 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.Key; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.KeySpec; + +import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder; +import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder; +import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.apache.sshd.common.signature.Signature; +import org.apache.sshd.common.util.buffer.Buffer; +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. + * + * @param type of the public key supported by the security provider + * @param type of the private key supported by the security provider + * @author Apache MINA SSHD Project + */ +public interface EdDSASupport { + + int KEY_SIZE = 256; + + /** + * @see RFC8412 section 3 + */ + 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 SecurityUtils.generateEDDSAPrivateKey(KeyPairProvider.SSH_ED25519, 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(); + + /** + * @return the signature implementation associated with the security provider. + */ + Signature getEDDSASigner(); + + /** + * @param key the key to get the size of. + * @return the size of the key if it is an EdDSA key, -1 otherwise. + */ + int getEDDSAKeySize(Key key); + + /** + * @return the public key class type associated with the security provider. + */ + Class getEDDSAPublicKeyType(); + + /** + * @return the private key class type associated with the security provider. + */ + Class getEDDSAPrivateKeyType(); + + /** + * @param k1 the first key + * @param k2 the second key + * @return {@code true} if both keys are instances of the public key type associated with the security provider + * and they are equal. + */ + boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2); + + /** + * @param k1 the first key + * @param k2 the second key + * @return {@code true} if both keys are instances of the private key type associated with the security provider + * and they are equal. + */ + boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2); + + /** + * @param key the private key + * @return the public key associated with the private key. + */ + PUB recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException; + + /** + * @param seed the raw public key bytes + * @return the associated public key + */ + PUB generateEDDSAPublicKey(byte[] seed) throws GeneralSecurityException; + + /** + * @param seed the raw private key bytes + * @return the associated private key + */ + PRV generateEDDSAPrivateKey(byte[] seed) throws GeneralSecurityException, IOException; + + /** + * @param buffer the buffer to insert the public key into + * @param key the public key to be inserted into the buffer + * @return the buffer that was passed in + * @param type of the buffer + */ + B putRawEDDSAPublicKey(B buffer, PublicKey key); + + /** + * @param buffer the buffer to insert the keys into + * @param pubKey the public key to be inserted into the buffer + * @param prvKey the private key to be inserted into the buffer + * @return the buffer that was passed in + * @param type of the buffer + */ + B putEDDSAKeyPair(B buffer, PublicKey pubKey, PrivateKey prvKey); + + /** + * @param publicKey the public key + * @return a key spec from the public key + */ + KeySpec createPublicKeySpec(PUB publicKey); + + /** + * @param privateKey the private key + * @return a key spec from the private key + */ + KeySpec createPrivateKeySpec(PRV privateKey); + + /** + * @param publicKey the public key + * @return the raw public key bytes associated with the key + */ + byte[] getPublicKeyData(PUB publicKey); + + /** + * @param privateKey the private key + * @return the raw private key bytes associated with the key + */ + byte[] getPrivateKeyData(PRV privateKey) throws IOException; + + /** + * @return the algorithm name used by the provider's {@link java.security.KeyFactory}. + */ + String getKeyFactoryAlgorithm(); + +} 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/GenericEd25519PEMResourceKeyParser.java new file mode 100644 index 000000000..9e4c64511 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PEMResourceKeyParser.java @@ -0,0 +1,151 @@ +/* + * 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.InputStream; +import java.io.StreamCorruptedException; +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.sshd.common.NamedResource; +import org.apache.sshd.common.config.keys.FilePasswordProvider; +import org.apache.sshd.common.config.keys.loader.pem.AbstractPEMResourceKeyPairParser; +import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.util.GenericUtils; +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.io.input.NoCloseInputStream; +import org.apache.sshd.common.util.security.SecurityUtils; + +/** + * @author Apache MINA SSHD Project + */ +public class GenericEd25519PEMResourceKeyParser 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". + public static final String BEGIN_MARKER = "BEGIN EDDSA PRIVATE KEY"; + + // Some (older) OpenSSL versions used this if "traditional" (RFC 1421) PEM format was chosen: + public static final String BEGIN_ED25519_MARKER = "BEGIN ED25519 PRIVATE KEY"; + + public static final List BEGINNERS = GenericUtils.unmodifiableList(BEGIN_MARKER, BEGIN_ED25519_MARKER); + + public static final String END_MARKER = "END EDDSA PRIVATE KEY"; + public static final String END_ED25519_MARKER = "END ED25519 PRIVATE KEY"; + public static final List ENDERS = GenericUtils.unmodifiableList(END_MARKER, END_ED25519_MARKER); + + public static final String ED25519_OID = EdDSASupport.ED25519_OID; + + public static final GenericEd25519PEMResourceKeyParser INSTANCE = new GenericEd25519PEMResourceKeyParser(); + + public GenericEd25519PEMResourceKeyParser() { + super(SecurityUtils.EDDSA, ED25519_OID, BEGINNERS, ENDERS); + } + + @Override + public Collection extractKeyPairs( + SessionContext session, NamedResource resourceKey, String beginMarker, + String endMarker, FilePasswordProvider passwordProvider, + InputStream stream, Map headers) + throws IOException, GeneralSecurityException { + KeyPair kp = parseEd25519KeyPair(stream, false); + return Collections.singletonList(kp); + } + + public static KeyPair parseEd25519KeyPair( + InputStream inputStream, boolean okToClose) + throws IOException, GeneralSecurityException { + try (DERParser parser = new DERParser(NoCloseInputStream.resolveInputStream(inputStream, okToClose))) { + return parseEd25519KeyPair(parser); + } + } + + /* + * See https://tools.ietf.org/html/rfc8410#section-7 + * + * SEQUENCE { + * INTEGER 0x00 (0 decimal) + * SEQUENCE { + * OBJECTIDENTIFIER 1.3.101.112 + * } + * OCTETSTRING keyData + * } + * + * NOTE: there is another variant that also has some extra parameters + * but it has the same "prefix" structure so we don't care + */ + public static KeyPair parseEd25519KeyPair(DERParser parser) throws IOException, GeneralSecurityException { + ASN1Object obj = parser.readObject(); + if (obj == null) { + throw new StreamCorruptedException("Missing version value"); + } + + BigInteger version = obj.asInteger(); + if (!BigInteger.ZERO.equals(version)) { + throw new StreamCorruptedException("Invalid version: " + version); + } + + obj = parser.readObject(); + if (obj == null) { + throw new StreamCorruptedException("Missing OID container"); + } + + ASN1Type objType = obj.getObjType(); + if (objType != ASN1Type.SEQUENCE) { + throw new StreamCorruptedException("Unexpected OID object type: " + objType); + } + + List curveOid; + try (DERParser oidParser = obj.createParser()) { + obj = oidParser.readObject(); + if (obj == null) { + throw new StreamCorruptedException("Missing OID value"); + } + + curveOid = obj.asOID(); + } + + String oid = GenericUtils.join(curveOid, '.'); + // TODO modify if more curves supported + if (!ED25519_OID.equals(oid)) { + throw new StreamCorruptedException("Unsupported curve OID: " + oid); + } + + obj = parser.readObject(); + if (obj == null) { + throw new StreamCorruptedException("Missing key data"); + } + + return decodeEd25519KeyPair(obj.getValue()); + } + + public static KeyPair decodeEd25519KeyPair(byte[] keyData) throws IOException, GeneralSecurityException { + return EdDSASupport.decodeEd25519KeyPair(keyData); + } + +} 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/GenericEd25519PublicKeyDecoder.java new file mode 100644 index 000000000..99622264b --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PublicKeyDecoder.java @@ -0,0 +1,98 @@ +/* + * 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.InputStream; +import java.io.OutputStream; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; + +import org.apache.sshd.common.config.keys.KeyEntryResolver; +import org.apache.sshd.common.config.keys.impl.AbstractPublicKeyEntryDecoder; +import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.util.security.SecurityUtils; + +/** + * @author Apache MINA SSHD Project + */ +public class GenericEd25519PublicKeyDecoder + extends AbstractPublicKeyEntryDecoder { + public static final int MAX_ALLOWED_SEED_LEN = 1024; // in reality it is much less than this + + protected final EdDSASupport edDSASupport; + + public GenericEd25519PublicKeyDecoder(Class pubType, Class prvType, EdDSASupport edDSASupport) { + super(pubType, prvType, Collections.singletonList(KeyPairProvider.SSH_ED25519)); + this.edDSASupport = edDSASupport; + } + + @Override + public PUB clonePublicKey(PUB key) throws GeneralSecurityException { + if (key == null) { + return null; + } else { + return generatePublicKey(edDSASupport.createPublicKeySpec(key)); + } + } + + @Override + public PRV clonePrivateKey(PRV key) throws GeneralSecurityException { + if (key == null) { + return null; + } else { + return generatePrivateKey(edDSASupport.createPrivateKeySpec(key)); + } + } + + @Override + public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException { + return SecurityUtils.getKeyPairGenerator(SecurityUtils.EDDSA); + } + + @Override + public String encodePublicKey(OutputStream s, PUB key) throws IOException { + Objects.requireNonNull(key, "No public key provided"); + KeyEntryResolver.encodeString(s, KeyPairProvider.SSH_ED25519); + byte[] seed = edDSASupport.getPublicKeyData(key); + KeyEntryResolver.writeRLEBytes(s, seed); + return KeyPairProvider.SSH_ED25519; + } + + @Override + public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException { + return SecurityUtils.getKeyFactory(SecurityUtils.EDDSA); + } + + @Override + public PUB decodePublicKey( + SessionContext session, String keyType, InputStream keyData, Map headers) + throws IOException, GeneralSecurityException { + byte[] seed = KeyEntryResolver.readRLEBytes(keyData, MAX_ALLOWED_SEED_LEN); + return edDSASupport.generateEDDSAPublicKey(seed); + } + +} 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/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java new file mode 100644 index 000000000..078a6572b --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java @@ -0,0 +1,177 @@ +/* + * 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.InputStream; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Arrays; +import java.util.Collections; +import java.util.Locale; +import java.util.Objects; + +import org.apache.sshd.common.config.keys.FilePasswordProvider; +import org.apache.sshd.common.config.keys.KeyEntryResolver; +import org.apache.sshd.common.config.keys.impl.AbstractPrivateKeyEntryDecoder; +import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.io.output.SecureByteArrayOutputStream; +import org.apache.sshd.common.util.security.SecurityUtils; + +/** + * @author Apache MINA SSHD Project + */ +public class GenericOpenSSHEd25519PrivateKeyEntryDecoder + extends AbstractPrivateKeyEntryDecoder { + 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(Class pubType, Class prvType, + EdDSASupport edDSASupport) { + super(pubType, prvType, Collections.singletonList(KeyPairProvider.SSH_ED25519)); + this.edDSASupport = edDSASupport; + } + + @Override + public PRV decodePrivateKey( + SessionContext session, String keyType, FilePasswordProvider passwordProvider, InputStream keyData) + throws IOException, GeneralSecurityException { + if (!KeyPairProvider.SSH_ED25519.equals(keyType)) { + throw new InvalidKeyException("Unsupported key type: " + keyType); + } + + if (!SecurityUtils.isEDDSACurveSupported()) { + throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " provider not supported"); + } + + // ed25519 bernstein naming: pk .. public key, sk .. secret key + // we expect to find two byte arrays with the following structure (type:size): + // [pk:32], [sk:32,pk:32] + + byte[] pk = GenericUtils.EMPTY_BYTE_ARRAY; + byte[] keypair = GenericUtils.EMPTY_BYTE_ARRAY; + try { + pk = KeyEntryResolver.readRLEBytes(keyData, PK_SIZE * 2); + keypair = KeyEntryResolver.readRLEBytes(keyData, KEYPAIR_SIZE * 2); + if (pk.length != PK_SIZE) { + throw new InvalidKeyException( + String.format(Locale.ENGLISH, "Unexpected pk size: %s (expected %s)", pk.length, PK_SIZE)); + } + + if (keypair.length != KEYPAIR_SIZE) { + throw new InvalidKeyException( + String.format(Locale.ENGLISH, "Unexpected keypair size: %s (expected %s)", keypair.length, + KEYPAIR_SIZE)); + } + + // verify that the keypair contains the expected pk + // yes, it's stored redundant, this seems to mimic the output structure of the keypair generation interface + if (!Arrays.equals(pk, Arrays.copyOfRange(keypair, SK_SIZE, KEYPAIR_SIZE))) { + throw new InvalidKeyException("Keypair did not contain the public key."); + } + + byte[] sk = Arrays.copyOf(keypair, SK_SIZE); + PRV privateKey = edDSASupport.generateEDDSAPrivateKey(sk); + + // we can now verify the generated pk matches the one we read + if (!Arrays.equals(edDSASupport.getPublicKeyData(recoverPublicKey(privateKey)), pk)) { + throw new InvalidKeyException("The provided pk does NOT match the computed pk for the given sk."); + } + + return privateKey; + } finally { + // get rid of sensitive data a.s.a.p + Arrays.fill(pk, (byte) 0); + Arrays.fill(keypair, (byte) 0); + } + } + + @Override + public String encodePrivateKey(SecureByteArrayOutputStream s, PRV key, PUB pubKey) + throws IOException { + Objects.requireNonNull(key, "No private key provided"); + + // ed25519 bernstein naming: pk .. public key, sk .. secret key + // we are expected to write the following arrays (type:size): + // [pk:32], [sk:32,pk:32] + + byte[] sk = edDSASupport.getPrivateKeyData(key); + byte[] pk = edDSASupport.getPublicKeyData(pubKey); + + Objects.requireNonNull(sk, "No seed"); + + byte[] keypair = new byte[KEYPAIR_SIZE]; + System.arraycopy(sk, 0, keypair, 0, SK_SIZE); + System.arraycopy(pk, 0, keypair, SK_SIZE, PK_SIZE); + + KeyEntryResolver.writeRLEBytes(s, pk); + KeyEntryResolver.writeRLEBytes(s, keypair); + + return KeyPairProvider.SSH_ED25519; + } + + @Override + public boolean isPublicKeyRecoverySupported() { + return true; + } + + @Override + public PUB recoverPublicKey(PRV prvKey) throws GeneralSecurityException { + return edDSASupport.recoverEDDSAPublicKey(prvKey); + } + + @Override + public PUB clonePublicKey(PUB key) throws GeneralSecurityException { + if (key == null) { + return null; + } else { + return generatePublicKey(edDSASupport.createPublicKeySpec(key)); + } + } + + @Override + public PRV clonePrivateKey(PRV key) throws GeneralSecurityException { + if (key == null) { + return null; + } else { + return generatePrivateKey(edDSASupport.createPrivateKeySpec(key)); + } + } + + @Override + public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException { + return SecurityUtils.getKeyPairGenerator(SecurityUtils.EDDSA); + } + + @Override + public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException { + return SecurityUtils.getKeyFactory(SecurityUtils.EDDSA); + } +} diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericSignatureEd25519.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericSignatureEd25519.java new file mode 100644 index 000000000..99c54fa79 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericSignatureEd25519.java @@ -0,0 +1,50 @@ +/* + * 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.util.Map; + +import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.signature.AbstractSignature; +import org.apache.sshd.common.util.ValidateUtils; + +/** + * @author Apache MINA SSHD Project + */ +public class GenericSignatureEd25519 extends AbstractSignature { + public GenericSignatureEd25519(String algorithm) { + super(algorithm); + } + + @Override + public boolean verify(SessionContext session, byte[] sig) throws Exception { + byte[] data = sig; + Map.Entry encoding + = extractEncodedSignature(data, KeyPairProvider.SSH_ED25519::equalsIgnoreCase); + if (encoding != null) { + String keyType = encoding.getKey(); + ValidateUtils.checkTrue( + KeyPairProvider.SSH_ED25519.equals(keyType), "Mismatched key type: %s", keyType); + data = encoding.getValue(); + } + + return doVerify(data); + } +} diff --git a/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java b/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java index 5bb209a47..69d3254fc 100644 --- a/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java +++ b/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java @@ -253,11 +253,9 @@ protected Iterable loadFromFile(SessionContext session, String alg, Pat String keyAlgorithm = key.getAlgorithm(); if (BuiltinIdentities.Constants.ECDSA.equalsIgnoreCase(keyAlgorithm)) { keyAlgorithm = KeyUtils.EC_ALGORITHM; - } else if (BuiltinIdentities.Constants.ED25519.equalsIgnoreCase(keyAlgorithm)) { - keyAlgorithm = SecurityUtils.EDDSA; } - if (Objects.equals(alg, keyAlgorithm)) { + if (Objects.equals(alg, keyAlgorithm) || edDSAAlgorithmsMatch(alg, keyAlgorithm)) { if (log.isDebugEnabled()) { log.debug("resolveKeyPair({}) loaded key={}-{}", keyPath, KeyUtils.getKeyType(key), KeyUtils.getFingerPrint(key)); @@ -396,4 +394,10 @@ protected KeyPair generateKeyPair(String algorithm) throws GeneralSecurityExcept return generator.generateKeyPair(); } + + boolean edDSAAlgorithmsMatch(String expectedAlgorithm, String keyAlgorithm) { + return (expectedAlgorithm.equalsIgnoreCase(SecurityUtils.EDDSA) + || expectedAlgorithm.equalsIgnoreCase(SecurityUtils.ED25519)) && + (keyAlgorithm.equalsIgnoreCase(SecurityUtils.EDDSA) || keyAlgorithm.equalsIgnoreCase(SecurityUtils.ED25519)); + } } 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 8b42895ce..4847e2b81 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 @@ -19,19 +19,23 @@ package org.apache.sshd.common.util.security.eddsa; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; +import java.security.KeyFactory; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.spec.KeySpec; import java.util.ArrayList; -import java.util.Arrays; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; -import net.i2p.crypto.eddsa.EdDSAPrivateKey; -import net.i2p.crypto.eddsa.EdDSAPublicKey; import org.apache.sshd.common.signature.Signature; import org.apache.sshd.common.util.buffer.BufferUtils; import org.apache.sshd.common.util.security.SecurityUtils; +import org.apache.sshd.common.util.security.eddsa.bouncycastle.BouncyCastleEdDSASupport; +import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; import org.apache.sshd.util.test.JUnitTestSupport; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeAll; @@ -41,9 +45,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - /** * @author Apache MINA SSHD Project * @see EdDSA and Ed25519 @@ -51,131 +52,145 @@ */ @TestMethodOrder(MethodName.class) // see https://github.com/junit-team/junit/wiki/Parameterized-tests @Tag("NoIoTestCase") -public class Ed25519VectorsTest extends JUnitTestSupport { +public class Ed25519VectorsTest extends JUnitTestSupport { private byte[] prvBytes; - private PrivateKey privateKey; + private PRV privateKey; private byte[] pubBytes; - private PublicKey publicKey; + private PUB publicKey; private byte[] msgBytes; private byte[] expSignature; - public void initEd25519VectorsTest(String name, String prvKey, String pubKey, String msg, String signature) - throws GeneralSecurityException { + public void initEd25519VectorsTest( + String name, EdDSASupport support, String prvKey, String pubKey, String msg, String signature) + throws GeneralSecurityException, IOException { prvBytes = BufferUtils.decodeHex(BufferUtils.EMPTY_HEX_SEPARATOR, prvKey); - privateKey = EdDSASecurityProviderUtils.generateEDDSAPrivateKey(prvBytes.clone()); + privateKey = support.generateEDDSAPrivateKey(prvBytes.clone()); pubBytes = BufferUtils.decodeHex(BufferUtils.EMPTY_HEX_SEPARATOR, pubKey); - publicKey = EdDSASecurityProviderUtils.generateEDDSAPublicKey(pubBytes.clone()); + publicKey = support.generateEDDSAPublicKey(pubBytes.clone()); msgBytes = BufferUtils.decodeHex(BufferUtils.EMPTY_HEX_SEPARATOR, msg); expSignature = BufferUtils.decodeHex(BufferUtils.EMPTY_HEX_SEPARATOR, signature); } - @SuppressWarnings("checkstyle:anoninnerlength") public static List parameters() { - return new ArrayList<>( - Arrays.asList( - new Object[] { - "TEST1 - empty message", - "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", - "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a", - "", - "e5564300c360ac729086e2cc806e828a" - + "84877f1eb8e5d974d873e06522490155" - + "5fb8821590a33bacc61e39701cf9b46b" - + "d25bf5f0595bbe24655141438e7a100b" - }, - new Object[] { - "TEST2 - one byte", - "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb", - "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c", - "72", - "92a009a9f0d4cab8720e820b5f642540" - + "a2b27b5416503f8fb3762223ebdb69da" - + "085ac1e43e15996e458f3613d0f11d8c" - + "387b2eaeb4302aeeb00d291612bb0c00" - }, - new Object[] { - "TEST3 - 2 bytes", - "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7", - "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025", - "af82", - "6291d657deec24024827e69c3abe01a3" - + "0ce548a284743a445e3680d7db5ac3ac" - + "18ff9b538d16f290ae67f760984dc659" - + "4a7c15e9716ed28dc027beceea1ec40a" - }, - new Object[] { - "TEST1024 - large message", - "f5e5767cf153319517630f226876b86c8160cc583bc013744c6bf255f5cc0ee5", - "278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e", - "08b8b2b733424243760fe426a4b54908" - + "632110a66c2f6591eabd3345e3e4eb98" - + "fa6e264bf09efe12ee50f8f54e9f77b1" - + "e355f6c50544e23fb1433ddf73be84d8" - + "79de7c0046dc4996d9e773f4bc9efe57" - + "38829adb26c81b37c93a1b270b20329d" - + "658675fc6ea534e0810a4432826bf58c" - + "941efb65d57a338bbd2e26640f89ffbc" - + "1a858efcb8550ee3a5e1998bd177e93a" - + "7363c344fe6b199ee5d02e82d522c4fe" - + "ba15452f80288a821a579116ec6dad2b" - + "3b310da903401aa62100ab5d1a36553e" - + "06203b33890cc9b832f79ef80560ccb9" - + "a39ce767967ed628c6ad573cb116dbef" - + "efd75499da96bd68a8a97b928a8bbc10" - + "3b6621fcde2beca1231d206be6cd9ec7" - + "aff6f6c94fcd7204ed3455c68c83f4a4" - + "1da4af2b74ef5c53f1d8ac70bdcb7ed1" - + "85ce81bd84359d44254d95629e9855a9" - + "4a7c1958d1f8ada5d0532ed8a5aa3fb2" - + "d17ba70eb6248e594e1a2297acbbb39d" - + "502f1a8c6eb6f1ce22b3de1a1f40cc24" - + "554119a831a9aad6079cad88425de6bd" - + "e1a9187ebb6092cf67bf2b13fd65f270" - + "88d78b7e883c8759d2c4f5c65adb7553" - + "878ad575f9fad878e80a0c9ba63bcbcc" - + "2732e69485bbc9c90bfbd62481d9089b" - + "eccf80cfe2df16a2cf65bd92dd597b07" - + "07e0917af48bbb75fed413d238f5555a" - + "7a569d80c3414a8d0859dc65a46128ba" - + "b27af87a71314f318c782b23ebfe808b" - + "82b0ce26401d2e22f04d83d1255dc51a" - + "ddd3b75a2b1ae0784504df543af8969b" - + "e3ea7082ff7fc9888c144da2af58429e" - + "c96031dbcad3dad9af0dcbaaaf268cb8" - + "fcffead94f3c7ca495e056a9b47acdb7" - + "51fb73e666c6c655ade8297297d07ad1" - + "ba5e43f1bca32301651339e22904cc8c" - + "42f58c30c04aafdb038dda0847dd988d" - + "cda6f3bfd15c4b4c4525004aa06eeff8" - + "ca61783aacec57fb3d1f92b0fe2fd1a8" - + "5f6724517b65e614ad6808d6f6ee34df" - + "f7310fdc82aebfd904b01e1dc54b2927" - + "094b2db68d6f903b68401adebf5a7e08" - + "d78ff4ef5d63653a65040cf9bfd4aca7" - + "984a74d37145986780fc0b16ac451649" - + "de6188a7dbdf191f64b5fc5e2ab47b57" - + "f7f7276cd419c17a3ca8e1b939ae49e4" - + "88acba6b965610b5480109c8b17b80e1" - + "b7b750dfc7598d5d5011fd2dcc5600a3" - + "2ef5b52a1ecc820e308aa342721aac09" - + "43bf6686b64b2579376504ccc493d97e" - + "6aed3fb0f9cd71a43dd497f01f17c0e2" - + "cb3797aa2a2f256656168e6c496afc5f" - + "b93246f6b1116398a346f1a641f3b041" - + "e989f7914f90cc2c7fff357876e506b5" - + "0d334ba77c225bc307ba537152f3f161" - + "0e4eafe595f6d9d90d11faa933a15ef1" - + "369546868a7f3a45a96768d40fd9d034" - + "12c091c6315cf4fde7cb68606937380d" - + "b2eaaa707b4c4185c32eddcdd306705e" - + "4dc1ffc872eeee475a64dfac86aba41c" - + "0618983f8741c5ef68d3a101e8a3b8ca" - + "c60c905c15fc910840b94c00a0b9d0", - "0aab4c900501b3e24d7cdf4663326a3a" - + "87df5e4843b2cbdb67cbf6e460fec350" - + "aa5371b1508f9f4528ecea23c436d94b" - + "5e8fcd4f681e30a6ac00a9704a188a03" - })); + List parameters = new ArrayList<>(); + Map> supportedSecurityProviders = new LinkedHashMap<>(); + supportedSecurityProviders.put(SecurityUtils.EDDSA, new NetI2pCryptoEdDSASupport()); + supportedSecurityProviders.put(SecurityUtils.BOUNCY_CASTLE, new BouncyCastleEdDSASupport()); + for (Map.Entry> entry : supportedSecurityProviders.entrySet()) { + String supportClassName = entry.getValue().getClass().getSimpleName(); + parameters.add(new Object[] { + supportClassName + " TEST1 - empty message", + entry.getValue(), + entry.getKey(), + "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", + "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a", + "", + "e5564300c360ac729086e2cc806e828a" + + "84877f1eb8e5d974d873e06522490155" + + "5fb8821590a33bacc61e39701cf9b46b" + + "d25bf5f0595bbe24655141438e7a100b" + }); + parameters.add(new Object[] { + supportClassName + " TEST2 - one byte", + entry.getValue(), + entry.getKey(), + "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb", + "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c", + "72", + "92a009a9f0d4cab8720e820b5f642540" + + "a2b27b5416503f8fb3762223ebdb69da" + + "085ac1e43e15996e458f3613d0f11d8c" + + "387b2eaeb4302aeeb00d291612bb0c00" + }); + parameters.add(new Object[] { + supportClassName + " TEST3 - 2 bytes", + entry.getValue(), + entry.getKey(), + "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7", + "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025", + "af82", + "6291d657deec24024827e69c3abe01a3" + + "0ce548a284743a445e3680d7db5ac3ac" + + "18ff9b538d16f290ae67f760984dc659" + + "4a7c15e9716ed28dc027beceea1ec40a" + }); + parameters.add(new Object[] { + supportClassName + " TEST1024 - large message", + entry.getValue(), + entry.getKey(), + "f5e5767cf153319517630f226876b86c8160cc583bc013744c6bf255f5cc0ee5", + "278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e", + "08b8b2b733424243760fe426a4b54908" + + "632110a66c2f6591eabd3345e3e4eb98" + + "fa6e264bf09efe12ee50f8f54e9f77b1" + + "e355f6c50544e23fb1433ddf73be84d8" + + "79de7c0046dc4996d9e773f4bc9efe57" + + "38829adb26c81b37c93a1b270b20329d" + + "658675fc6ea534e0810a4432826bf58c" + + "941efb65d57a338bbd2e26640f89ffbc" + + "1a858efcb8550ee3a5e1998bd177e93a" + + "7363c344fe6b199ee5d02e82d522c4fe" + + "ba15452f80288a821a579116ec6dad2b" + + "3b310da903401aa62100ab5d1a36553e" + + "06203b33890cc9b832f79ef80560ccb9" + + "a39ce767967ed628c6ad573cb116dbef" + + "efd75499da96bd68a8a97b928a8bbc10" + + "3b6621fcde2beca1231d206be6cd9ec7" + + "aff6f6c94fcd7204ed3455c68c83f4a4" + + "1da4af2b74ef5c53f1d8ac70bdcb7ed1" + + "85ce81bd84359d44254d95629e9855a9" + + "4a7c1958d1f8ada5d0532ed8a5aa3fb2" + + "d17ba70eb6248e594e1a2297acbbb39d" + + "502f1a8c6eb6f1ce22b3de1a1f40cc24" + + "554119a831a9aad6079cad88425de6bd" + + "e1a9187ebb6092cf67bf2b13fd65f270" + + "88d78b7e883c8759d2c4f5c65adb7553" + + "878ad575f9fad878e80a0c9ba63bcbcc" + + "2732e69485bbc9c90bfbd62481d9089b" + + "eccf80cfe2df16a2cf65bd92dd597b07" + + "07e0917af48bbb75fed413d238f5555a" + + "7a569d80c3414a8d0859dc65a46128ba" + + "b27af87a71314f318c782b23ebfe808b" + + "82b0ce26401d2e22f04d83d1255dc51a" + + "ddd3b75a2b1ae0784504df543af8969b" + + "e3ea7082ff7fc9888c144da2af58429e" + + "c96031dbcad3dad9af0dcbaaaf268cb8" + + "fcffead94f3c7ca495e056a9b47acdb7" + + "51fb73e666c6c655ade8297297d07ad1" + + "ba5e43f1bca32301651339e22904cc8c" + + "42f58c30c04aafdb038dda0847dd988d" + + "cda6f3bfd15c4b4c4525004aa06eeff8" + + "ca61783aacec57fb3d1f92b0fe2fd1a8" + + "5f6724517b65e614ad6808d6f6ee34df" + + "f7310fdc82aebfd904b01e1dc54b2927" + + "094b2db68d6f903b68401adebf5a7e08" + + "d78ff4ef5d63653a65040cf9bfd4aca7" + + "984a74d37145986780fc0b16ac451649" + + "de6188a7dbdf191f64b5fc5e2ab47b57" + + "f7f7276cd419c17a3ca8e1b939ae49e4" + + "88acba6b965610b5480109c8b17b80e1" + + "b7b750dfc7598d5d5011fd2dcc5600a3" + + "2ef5b52a1ecc820e308aa342721aac09" + + "43bf6686b64b2579376504ccc493d97e" + + "6aed3fb0f9cd71a43dd497f01f17c0e2" + + "cb3797aa2a2f256656168e6c496afc5f" + + "b93246f6b1116398a346f1a641f3b041" + + "e989f7914f90cc2c7fff357876e506b5" + + "0d334ba77c225bc307ba537152f3f161" + + "0e4eafe595f6d9d90d11faa933a15ef1" + + "369546868a7f3a45a96768d40fd9d034" + + "12c091c6315cf4fde7cb68606937380d" + + "b2eaaa707b4c4185c32eddcdd306705e" + + "4dc1ffc872eeee475a64dfac86aba41c" + + "0618983f8741c5ef68d3a101e8a3b8ca" + + "c60c905c15fc910840b94c00a0b9d0", + "0aab4c900501b3e24d7cdf4663326a3a" + + "87df5e4843b2cbdb67cbf6e460fec350" + + "aa5371b1508f9f4528ecea23c436d94b" + + "5e8fcd4f681e30a6ac00a9704a188a03" + }); + } + return parameters; } @BeforeAll @@ -185,31 +200,38 @@ static void checkEDDSASupported() { @MethodSource("parameters") @ParameterizedTest(name = "{0}") - public void publicKeyBytes(String name, String prvKey, String pubKey, String msg, String signature) throws Exception { - initEd25519VectorsTest(name, prvKey, pubKey, msg, signature); - byte[] publicSeed = Ed25519PublicKeyDecoder.getSeedValue((EdDSAPublicKey) publicKey); + public void publicKeyBytes( + String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg, + String signature) throws Exception { + initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature); + byte[] publicSeed = support.getPublicKeyData(publicKey); assertArrayEquals(pubBytes, publicSeed, "Mismatched public seed value"); } @MethodSource("parameters") @ParameterizedTest(name = "{0}") - public void privateKeyBytes(String name, String prvKey, String pubKey, String msg, String signature) throws Exception { - initEd25519VectorsTest(name, prvKey, pubKey, msg, signature); - assertArrayEquals(prvBytes, ((EdDSAPrivateKey) privateKey).getSeed(), "Mismatched private seed value"); + public void privateKeyBytes( + String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg, + String signature) throws Exception { + initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature); + byte[] privateSeed = support.getPrivateKeyData(privateKey); + assertArrayEquals(prvBytes, privateSeed, "Mismatched private seed value"); } @MethodSource("parameters") @ParameterizedTest(name = "{0}") - public void signature(String name, String prvKey, String pubKey, String msg, String signature) throws Exception { - initEd25519VectorsTest(name, prvKey, pubKey, msg, signature); - Signature signer = EdDSASecurityProviderUtils.getEDDSASignature(); + public void signature( + String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg, + String signature) throws Exception { + initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature); + Signature signer = support.getEDDSASigner(); signer.initSigner(null, privateKey); signer.update(null, msgBytes.clone()); byte[] actSignature = signer.sign(null); assertArrayEquals(expSignature, actSignature, "Mismatched signature"); - Signature verifier = EdDSASecurityProviderUtils.getEDDSASignature(); + Signature verifier = support.getEDDSASigner(); verifier.initVerifier(null, publicKey); verifier.update(null, msgBytes.clone()); assertTrue(verifier.verify(null, expSignature), "Verification failed"); @@ -217,9 +239,11 @@ public void signature(String name, String prvKey, String pubKey, String msg, Str @MethodSource("parameters") @ParameterizedTest(name = "{0}") - public void partialBufferSignature(String name, String prvKey, String pubKey, String msg, String signature) + public void partialBufferSignature( + String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg, + String signature) throws Exception { - initEd25519VectorsTest(name, prvKey, pubKey, msg, signature); + initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature); byte[] extraData = getCurrentTestName().getBytes(StandardCharsets.UTF_8); byte[] dataBuf = new byte[msgBytes.length + extraData.length]; int offset = extraData.length / 2; @@ -227,16 +251,57 @@ public void partialBufferSignature(String name, String prvKey, String pubKey, St System.arraycopy(msgBytes, 0, dataBuf, offset, msgBytes.length); System.arraycopy(extraData, offset, dataBuf, offset + msgBytes.length, extraData.length - offset); - Signature signer = EdDSASecurityProviderUtils.getEDDSASignature(); + Signature signer = support.getEDDSASigner(); signer.initSigner(null, privateKey); signer.update(null, dataBuf.clone(), offset, msgBytes.length); byte[] actSignature = signer.sign(null); assertArrayEquals(expSignature, actSignature, "Mismatched signature"); - Signature verifier = EdDSASecurityProviderUtils.getEDDSASignature(); + Signature verifier = support.getEDDSASigner(); verifier.initVerifier(null, publicKey); verifier.update(null, dataBuf.clone(), offset, msgBytes.length); assertTrue(verifier.verify(null, expSignature), "Verification failed"); } + + @MethodSource("parameters") + @ParameterizedTest(name = "{0}") + public void recoverEDDSAPublicKey( + String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg, + String signature) throws Exception { + initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature); + PUB recoveredKey = support.recoverEDDSAPublicKey(privateKey); + assertTrue(support.compareEDDSAPPublicKeys(publicKey, recoveredKey), "Recovered key is not equal"); + byte[] recoveredBytes = support.getPublicKeyData(recoveredKey); + assertArrayEquals(pubBytes, recoveredBytes, "Mismatched public seed value"); + } + + @MethodSource("parameters") + @ParameterizedTest(name = "{0}") + public void createPublicKeySpec( + String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg, + String signature) throws Exception { + initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature); + KeySpec keySpec = support.createPublicKeySpec(publicKey); + KeyFactory keyFactory = KeyFactory.getInstance(support.getKeyFactoryAlgorithm(), provider); + PUB generatedKey = (PUB) keyFactory.generatePublic(keySpec); + assertTrue(support.compareEDDSAPPublicKeys(publicKey, generatedKey), "Generated key is not equal"); + byte[] generatedBytes = support.getPublicKeyData(generatedKey); + assertArrayEquals(pubBytes, generatedBytes, "Mismatched public seed value"); + } + + @MethodSource("parameters") + @ParameterizedTest(name = "{0}") + public void createPrivateKeySpec( + String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg, + String signature) throws Exception { + initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature); + KeySpec keySpec = support.createPrivateKeySpec(privateKey); + KeyFactory keyFactory = KeyFactory.getInstance(support.getKeyFactoryAlgorithm(), provider); + PRV generatedKey = (PRV) keyFactory.generatePrivate(keySpec); + assertTrue(support.compareEDDSAPrivateKeys(privateKey, generatedKey), "Generated key is not equal"); + byte[] generatedBytes = support.getPrivateKeyData(generatedKey); + assertArrayEquals(prvBytes, generatedBytes, "Mismatched private seed value"); + } + }