Skip to content

Commit

Permalink
Merge pull request #639 from ianjoneill/f-ed25519-bc
Browse files Browse the repository at this point in the history
Bouncy Castle EdDSA / Ed25519 Support
  • Loading branch information
tomaswolf authored Dec 26, 2024
2 parents 0d65679 + 9c49609 commit fb21e1f
Show file tree
Hide file tree
Showing 24 changed files with 1,362 additions and 555 deletions.
10 changes: 4 additions & 6 deletions docs/dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -108,5 +108,3 @@ order to be included in the classpath:
</dependency>

```

The code contains support for reading _ed25519_ [OpenSSH formatted private keys](https://issues.apache.org/jira/browse/SSHD-703).
4 changes: 2 additions & 2 deletions docs/files-parsing.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/)

Expand Down
3 changes: 2 additions & 1 deletion docs/standards.md
Original file line number Diff line number Diff line change
Expand Up @@ -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), [email protected], 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)
, [email protected], 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a href="mailto:[email protected]">Apache MINA SSHD Project</a>
Expand Down Expand Up @@ -147,9 +147,9 @@ public Collection<KeyPair> 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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 <a href="mailto:[email protected]">Apache MINA SSHD Project</a>
Expand Down Expand Up @@ -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<EdDSASupport<?, ?>> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -606,99 +609,155 @@ 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<EdDSASupport<?, ?>> 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<EdDSASupport<?, ?>> support = netI2pCryptoProvider.getEdDSASupport();
if (support.isPresent()) {
return support;
}
}

for (Map.Entry<String, SecurityProviderRegistrar> entry : REGISTERED_PROVIDERS.entrySet()) {
Optional<EdDSASupport<?, ?>> support = entry.getValue().getEdDSASupport();
if (support.isPresent()) {
return support;
}
}
}
return Optional.empty();
}

/* -------------------------------------------------------------------- */

public static PublicKeyEntryDecoder<? extends PublicKey, ? extends PrivateKey> getEDDSAPublicKeyEntryDecoder() {
if (!isEDDSACurveSupported()) {
Optional<EdDSASupport<?, ?>> 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<EdDSASupport<?, ?>> 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<EdDSASupport<?, ?>> 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<EdDSASupport<?, ?>> support = getEdDSASupport();
return support.map(edDSASupport -> edDSASupport.getEDDSAKeySize(key)).orElse(-1);
}

public static Class<? extends PublicKey> getEDDSAPublicKeyType() {
return isEDDSACurveSupported() ? EdDSASecurityProviderUtils.getEDDSAPublicKeyType() : PublicKey.class;
Optional<EdDSASupport<?, ?>> 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<EdDSASupport<?, ?>> 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<EdDSASupport<?, ?>> 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<EdDSASupport<?, ?>> support = getEdDSASupport();
return support.map(edDSASupport -> edDSASupport.compareEDDSAPrivateKeys(k1, k2)).orElse(false);
}

public static PublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException {
if (!isEDDSACurveSupported()) {
Optional<EdDSASupport<?, ?>> 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 {
if (!KeyPairProvider.SSH_ED25519.equals(keyType)) {
throw new InvalidKeyException("Unsupported key type: " + keyType);
}

if (!isEDDSACurveSupported()) {
Optional<EdDSASupport<?, ?>> 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<EdDSASupport<?, ?>> support = getEdDSASupport();
if (!support.isPresent()) {
throw new NoSuchAlgorithmException(EDDSA + " provider not supported");
}

return support.get().generateEDDSAPrivateKey(seed);
}

public static <B extends Buffer> B putRawEDDSAPublicKey(B buffer, PublicKey key) {
if (!isEDDSACurveSupported()) {
Optional<EdDSASupport<?, ?>> 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 extends Buffer> B putEDDSAKeyPair(B buffer, KeyPair kp) {
return putEDDSAKeyPair(buffer, Objects.requireNonNull(kp, "No key pair").getPublic(), kp.getPrivate());
}

public static <B extends Buffer> B putEDDSAKeyPair(B buffer, PublicKey pubKey, PrivateKey prvKey) {
if (!isEDDSACurveSupported()) {
Optional<EdDSASupport<?, ?>> 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 {
Expand Down
Loading

0 comments on commit fb21e1f

Please sign in to comment.