This is an automated email from the ASF dual-hosted git repository.
twolf pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git
The following commit(s) were added to refs/heads/master by this push:
new 8f0c296a2 GH-830: Get EC curve parameters from the security provider
8f0c296a2 is described below
commit 8f0c296a2486e447c180c2205716cfd0d8935b2d
Author: Thomas Wolf <[email protected]>
AuthorDate: Wed Oct 8 18:05:25 2025 +0200
GH-830: Get EC curve parameters from the security provider
For EC public keys read from a ByteArrayBuffer, Bouncy Castle would
generate X.509 encodings using as algorithm parameters not the named
curve OID but the raw curve parameters.
This was caused by our using manually constructed ECParameterSpecs with
the values given in [1]. SunEC has code matching raw parameters back to
known curve names, and then using the OID, but Bouncy Castle does not.
So instead of constructing the ECParameterSpecs explicitly let the
security provider provide them. With that approach, Bouncy Castle can
produce a spec that also transports the OID, and then uses the OID in
the X.509 encoding.
---
CHANGES.md | 3 +-
.../org/apache/sshd/common/cipher/ECCurves.java | 122 +++++++--------------
.../sshd/common/util/security/SecurityUtils.java | 8 ++
3 files changed, 52 insertions(+), 81 deletions(-)
diff --git a/CHANGES.md b/CHANGES.md
index 35c21e19c..3815efae9 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -31,7 +31,8 @@
* [GH-807](https://github.com/apache/mina-sshd/issues/807) Handle "verified"
flag for sk-* keys
* [GH-809](https://github.com/apache/mina-sshd/pull/809) Fix server-side
authentication for FIDO/U2F sk-* keys with flags in `authorized_keys`
-* [GH-827](https://github.com/apache/mina-sshd/pull/827) Don't fail on invalid
`known_hosts` lines; log and skip them
+* [GH-827](https://github.com/apache/mina-sshd/issues/827) Don't fail on
invalid `known_hosts` lines; log and skip them
+* [GH-830](https://github.com/apache/mina-sshd/issues/830) EC public keys: let
Bouncy Castle generate X.509 encodings with the curve OID as algorithm parameter
## New Features
diff --git
a/sshd-common/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
b/sshd-common/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
index b3f98bc05..5967b079d 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
@@ -25,9 +25,11 @@ import java.io.OutputStream;
import java.io.StreamCorruptedException;
import java.io.UncheckedIOException;
import java.math.BigInteger;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
import java.security.interfaces.ECKey;
import java.security.spec.ECField;
-import java.security.spec.ECFieldFp;
+import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.EllipticCurve;
@@ -43,6 +45,7 @@ import java.util.stream.Collectors;
import org.apache.sshd.common.NamedResource;
import org.apache.sshd.common.OptionalFeature;
import org.apache.sshd.common.config.keys.KeyEntryResolver;
+import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.digest.BuiltinDigests;
import org.apache.sshd.common.digest.Digest;
import org.apache.sshd.common.digest.DigestFactory;
@@ -59,78 +62,11 @@ import org.apache.sshd.common.util.security.SecurityUtils;
* @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a>
*/
public enum ECCurves implements KeyTypeIndicator, KeySizeIndicator,
NamedResource, OptionalFeature {
- nistp256(Constants.NISTP256, new int[] { 1, 2, 840, 10045, 3, 1, 7 },
- new ECParameterSpec(
- new EllipticCurve(
- new ECFieldFp(
- new
BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF",
16)),
- new
BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC",
16),
- new
BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b",
16)),
- new ECPoint(
- new
BigInteger("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296",
16),
- new
BigInteger("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5",
16)),
- new
BigInteger("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551",
16),
- 1),
- 32,
- BuiltinDigests.sha256),
- nistp384(Constants.NISTP384, new int[] { 1, 3, 132, 0, 34 },
- new ECParameterSpec(
- new EllipticCurve(
- new ECFieldFp(
- new BigInteger(
-
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF",
- 16)),
- new BigInteger(
-
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC",
- 16),
- new BigInteger(
-
"B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF",
- 16)),
- new ECPoint(
- new BigInteger(
-
"AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7",
- 16),
- new BigInteger(
-
"3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F",
- 16)),
- new BigInteger(
-
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973",
- 16),
- 1),
- 48,
- BuiltinDigests.sha384),
- nistp521(Constants.NISTP521, new int[] { 1, 3, 132, 0, 35 },
- new ECParameterSpec(
- new EllipticCurve(
- new ECFieldFp(
- new BigInteger(
-
"01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
- +
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
- 16)),
- new BigInteger(
-
"01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
- +
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC",
- 16),
- new BigInteger(
-
"0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951"
- +
"EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00",
- 16)),
- new ECPoint(
- new BigInteger(
-
"00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77"
- +
"EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66",
- 16),
- new BigInteger(
-
"011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE7299"
- +
"5EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650",
- 16)),
- new BigInteger(
-
"01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B"
- +
"7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409",
- 16),
- 1),
- 66,
- BuiltinDigests.sha512);
+
+ // See RFC 5656: https://datatracker.ietf.org/doc/html/rfc5656#section-10.1
+ nistp256(Constants.NISTP256, "secp256r1", new int[] { 1, 2, 840, 10045, 3,
1, 7 }, 32, BuiltinDigests.sha256),
+ nistp384(Constants.NISTP384, "secp384r1", new int[] { 1, 3, 132, 0, 34 },
48, BuiltinDigests.sha384),
+ nistp521(Constants.NISTP521, "secp521r1", new int[] { 1, 3, 132, 0, 35 },
66, BuiltinDigests.sha512);
/**
* A {@link Set} of all the known curves
@@ -160,25 +96,41 @@ public enum ECCurves implements KeyTypeIndicator,
KeySizeIndicator, NamedResourc
.collect(Collectors.toList()));
private final String name;
+ private final String secName;
private final String keyType;
private final String oidString;
private final List<Integer> oidValue;
- private final ECParameterSpec params;
- private final int keySize;
+ private ECParameterSpec params;
+ private volatile int keySize = -1;
private final int numOctets;
private final DigestFactory digestFactory;
- ECCurves(String name, int[] oid, ECParameterSpec params, int numOctets,
DigestFactory digestFactory) {
+ ECCurves(String name, String secName, int[] oid, int numOctets,
DigestFactory digestFactory) {
this.name = ValidateUtils.checkNotNullAndNotEmpty(name, "No curve
name");
+ this.secName = ValidateUtils.checkNotNullAndNotEmpty(secName, "No SEC
curve name");
this.oidString = NumberUtils.join('.',
ValidateUtils.checkNotNullAndNotEmpty(oid, "No OID"));
this.oidValue = Collections.unmodifiableList(NumberUtils.asList(oid));
this.keyType = Constants.ECDSA_SHA2_PREFIX + name;
- this.params = ValidateUtils.checkNotNull(params, "No EC params for
%s", name);
- this.keySize = getCurveSize(params);
this.numOctets = numOctets;
this.digestFactory = Objects.requireNonNull(digestFactory, "No
digestFactory");
}
+ private ECParameterSpec getParams(String secName) {
+ try {
+ AlgorithmParameters paramsFactory =
SecurityUtils.getAlgorithmParameters(KeyUtils.EC_ALGORITHM);
+ // Although ECGenParameterSpec exists since Java 1.5 the parameter
names were documented only in Java 14
+ // with JDK-8210755. But the names were available all the time and
are also supported in Bouncy Castle.
+ //
+ // Note that the names must not be the NIST names but the SEC
names.
+ //
+ // See also https://www.secg.org/sec2-v2.pdf for the name
definitions and the exact numerical parameters.
+ paramsFactory.init(new ECGenParameterSpec(secName));
+ return paramsFactory.getParameterSpec(ECParameterSpec.class);
+ } catch (GeneralSecurityException e) {
+ return null;
+ }
+ }
+
@Override // The curve's standard name
public final String getName() {
return name;
@@ -203,12 +155,22 @@ public enum ECCurves implements KeyTypeIndicator,
KeySizeIndicator, NamedResourc
}
public final ECParameterSpec getParameters() {
- return params;
+ synchronized (this) {
+ if (params == null) {
+ params = ValidateUtils.checkNotNull(getParams(secName), "No EC
params for %s", name);
+ }
+ return params;
+ }
}
@Override
public final int getKeySize() {
- return keySize;
+ int sz = keySize;
+ if (sz < 0) {
+ sz = getCurveSize(getParameters());
+ keySize = sz;
+ }
+ return sz;
}
/**
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 6138998de..525b66d01 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
@@ -22,6 +22,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.nio.file.Path;
+import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.Key;
@@ -846,6 +847,13 @@ public final class SecurityUtils {
}
}
+ public static AlgorithmParameters getAlgorithmParameters(String algorithm)
throws GeneralSecurityException {
+ SecurityEntityFactory<AlgorithmParameters> factory
+ = resolveSecurityEntityFactory(AlgorithmParameters.class,
algorithm,
+ r ->
r.isSecurityEntitySupported(AlgorithmParameters.class, algorithm));
+ return factory.getInstance(algorithm);
+ }
+
public static KeyFactory getKeyFactory(String algorithm) throws
GeneralSecurityException {
SecurityEntityFactory<KeyFactory> factory
= resolveSecurityEntityFactory(KeyFactory.class, algorithm, r
-> r.isKeyFactorySupported(algorithm));