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 extends PublicKey, ? extends PrivateKey> 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 extends PublicKey, ? extends PrivateKey> 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 extends PublicKey> getEDDSAPublicKeyType() {
- return isEDDSACurveSupported() ? EdDSASecurityProviderUtils.getEDDSAPublicKeyType() : PublicKey.class;
+ Optional> support = getEdDSASupport();
+ if (!support.isPresent()) {
+ return PublicKey.class;
+ }
+ return support.get().getEDDSAPublicKeyType();
}
public static Class extends PrivateKey> 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 extends PublicKey> getEDDSAPublicKeyType() {
+ return EdDSASecurityProviderUtils.getEDDSAPublicKeyType();
+ }
+
+ @Override
+ public Class extends PrivateKey> 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 extends PublicKey> getEDDSAPublicKeyType() {
+ return EdDSAPublicKey.class;
+ }
+
+ @Override
+ public Class extends PrivateKey> 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 extends PublicKey> getEDDSAPublicKeyType();
+
+ /**
+ * @return the private key class type associated with the security provider.
+ */
+ Class extends PrivateKey> 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