You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2020/05/22 13:00:39 UTC

[mina-sshd] 03/05: [SSHD-989] Implement ECDSA public key recovery from PKCS#8 encoded data

This is an automated email from the ASF dual-hosted git repository.

lgoldstein pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git

commit 95b1635454515bde44702f928e99696568540ea3
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Fri May 1 18:24:38 2020 +0300

    [SSHD-989] Implement ECDSA public key recovery from PKCS#8 encoded data
---
 CHANGES.md                                         |  6 +-
 .../loader/pem/DSSPEMResourceKeyPairParser.java    |  2 +-
 .../loader/pem/ECDSAPEMResourceKeyPairParser.java  | 75 ++++++++++------------
 .../loader/pem/PKCS8PEMResourceKeyPairParser.java  | 60 ++++++-----------
 .../keys/loader/pem/PKCS8PrivateKeyInfo.java       | 16 ++---
 .../loader/pem/RSAPEMResourceKeyPairParser.java    |  2 +-
 .../org/apache/sshd/common/util/GenericUtils.java  |  2 +-
 .../pem/PKCS8PEMResourceKeyPairParserTest.java     |  6 ++
 .../apache/sshd/util/test/JUnitTestSupport.java    |  1 +
 9 files changed, 75 insertions(+), 95 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 3fb3d27..8e644be 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -53,6 +53,10 @@ as much of the available functionality as possible.
 
 * [SSHD-992](https://issues.apache.org/jira/browse/SSHD-984) - Provide more hooks into the SFTP server subsystem via SftpFileSystemAccessor
 
+<<<<<<< HEAD
 * [SSHD-997](https://issues.apache.org/jira/browse/SSHD-997) - Fixed OpenSSH private key decoders for RSA and Ed25519
 
-* [SSHD-998](https://issues.apache.org/jira/browse/SSHD-998) - Take into account SFTP version preference when establishing initial channel
\ No newline at end of file
+* [SSHD-998](https://issues.apache.org/jira/browse/SSHD-998) - Take into account SFTP version preference when establishing initial channel
+=======
+* [SSHD-989](https://issues.apache.org/jira/browse/SSHD-989) - Implement ECDSA public key recovery for PKCS8 encoded data
+>>>>>>> [SSHD-989] Implement ECDSA public key recovery from PKCS#8 encoded data
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java
index ca4f04a..796bf60 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java
@@ -47,7 +47,7 @@ import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- * @see <a href="https://tools.ietf.org/html/rfc3279#section-2.3.2">RFC-3279 section 2.3.2</a>
+ * @see    <a href="https://tools.ietf.org/html/rfc3279#section-2.3.2">RFC-3279 section 2.3.2</a>
  */
 public class DSSPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser {
     // Not exactly according to standard but good enough
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java
index 12b74f9..44d3d51 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java
@@ -51,7 +51,7 @@ import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- * @see <a href="https://tools.ietf.org/html/rfc5915">RFC 5915</a>
+ * @see    <a href="https://tools.ietf.org/html/rfc5915">RFC 5915</a>
  */
 public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser {
     public static final String BEGIN_MARKER = "BEGIN EC PRIVATE KEY";
@@ -78,7 +78,23 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
             FilePasswordProvider passwordProvider,
             InputStream stream, Map<String, String> headers)
             throws IOException, GeneralSecurityException {
-        Map.Entry<ECPublicKeySpec, ECPrivateKeySpec> spec = decodeECPrivateKeySpec(stream, false);
+
+        KeyPair kp = parseECKeyPair(stream, false);
+        return Collections.singletonList(kp);
+    }
+
+    public static KeyPair parseECKeyPair(
+            InputStream inputStream, boolean okToClose)
+            throws IOException, GeneralSecurityException {
+        try (DERParser parser = new DERParser(NoCloseInputStream.resolveInputStream(inputStream, okToClose))) {
+            return parseECKeyPair(parser);
+        }
+    }
+
+    public static KeyPair parseECKeyPair(DERParser parser)
+            throws IOException, GeneralSecurityException {
+        ASN1Object sequence = parser.readObject();
+        Map.Entry<ECPublicKeySpec, ECPrivateKeySpec> spec = decodeECPrivateKeySpec(sequence);
         if (!SecurityUtils.isECCSupported()) {
             throw new NoSuchProviderException("ECC not supported");
         }
@@ -86,8 +102,7 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
         KeyFactory kf = SecurityUtils.getKeyFactory(KeyUtils.EC_ALGORITHM);
         ECPublicKey pubKey = (ECPublicKey) kf.generatePublic(spec.getKey());
         ECPrivateKey prvKey = (ECPrivateKey) kf.generatePrivate(spec.getValue());
-        KeyPair kp = new KeyPair(pubKey, prvKey);
-        return Collections.singletonList(kp);
+        return new KeyPair(pubKey, prvKey);
     }
 
     /**
@@ -121,23 +136,11 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
      * </CODE>
      * </PRE>
      *
-     * @param  inputStream The {@link InputStream} containing the DER encoded data
-     * @param  okToClose   {@code true} if OK to close the DER stream once parsing complete
+     * @param  sequence    The {@link ASN1Object} sequence containing the DER encoded data
      * @return             The decoded {@link SimpleImmutableEntry} of {@link ECPublicKeySpec} and
      *                     {@link ECPrivateKeySpec}
      * @throws IOException If failed to to decode the DER stream
      */
-    public static SimpleImmutableEntry<ECPublicKeySpec, ECPrivateKeySpec> decodeECPrivateKeySpec(
-            InputStream inputStream, boolean okToClose)
-            throws IOException {
-        ASN1Object sequence;
-        try (DERParser parser = new DERParser(NoCloseInputStream.resolveInputStream(inputStream, okToClose))) {
-            sequence = parser.readObject();
-        }
-
-        return decodeECPrivateKeySpec(sequence);
-    }
-
     public static SimpleImmutableEntry<ECPublicKeySpec, ECPrivateKeySpec> decodeECPrivateKeySpec(ASN1Object sequence)
             throws IOException {
         ASN1Type objType = (sequence == null) ? null : sequence.getObjType();
@@ -155,12 +158,8 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
             /*
              * According to https://tools.ietf.org/html/rfc5915 - section 3
              *
-             * ECPrivateKey ::= SEQUENCE {
-             *      version        INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
-             *      privateKey     OCTET STRING,
-             *      parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
-             *      publicKey  [1] BIT STRING OPTIONAL
-             * }
+             * ECPrivateKey ::= SEQUENCE { version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1), privateKey OCTET
+             * STRING, parameters [0] ECParameters {{ NamedCurve }} OPTIONAL, publicKey [1] BIT STRING OPTIONAL }
              */
             ECPoint w = decodeECPublicKeyValue(curve, parser);
             ECPublicKeySpec pubSpec = new ECPublicKeySpec(w, prvSpec.getParams());
@@ -171,12 +170,8 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
     /*
      * According to https://tools.ietf.org/html/rfc5915 - section 3
      *
-     * ECPrivateKey ::= SEQUENCE {
-     *      version        INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
-     *      privateKey     OCTET STRING,
-     *      parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
-     *      publicKey  [1] BIT STRING OPTIONAL
-     * }
+     * ECPrivateKey ::= SEQUENCE { version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1), privateKey OCTET STRING,
+     * parameters [0] ECParameters {{ NamedCurve }} OPTIONAL, publicKey [1] BIT STRING OPTIONAL }
      */
     public static final ECPrivateKeySpec decodeECPrivateKeySpec(DERParser parser) throws IOException {
         // see openssl asn1parse -inform PEM -in ...file... -dump
@@ -188,8 +183,8 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
         /*
          * According to https://tools.ietf.org/html/rfc5915 - section 3
          *
-         *      For this version of the document, it SHALL be set to ecPrivkeyVer1,
-         *      which is of type INTEGER and whose value is one (1)
+         * For this version of the document, it SHALL be set to ecPrivkeyVer1, which is of type INTEGER and whose value
+         * is one (1)
          */
         BigInteger version = versionObject.asInteger();
         if (!BigInteger.ONE.equals(version)) {
@@ -209,14 +204,11 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
         /*
          * According to https://tools.ietf.org/html/rfc5915 - section 3
          *
-         *      parameters specifies the elliptic curve domain parameters
-         *      associated to the private key.  The type ECParameters is discussed
-         *      in [RFC5480].  As specified in [RFC5480], only the namedCurve
-         *      CHOICE is permitted.  namedCurve is an object identifier that
-         *      fully identifies the required values for a particular set of
-         *      elliptic curve domain parameters.  Though the ASN.1 indicates that
-         *      the parameters field is OPTIONAL, implementations that conform to
-         *      this document MUST always include the parameters field.
+         * parameters specifies the elliptic curve domain parameters associated to the private key. The type
+         * ECParameters is discussed in [RFC5480]. As specified in [RFC5480], only the namedCurve CHOICE is permitted.
+         * namedCurve is an object identifier that fully identifies the required values for a particular set of elliptic
+         * curve domain parameters. Though the ASN.1 indicates that the parameters field is OPTIONAL, implementations
+         * that conform to this document MUST always include the parameters field.
          */
         ASN1Object paramsObject = parser.readObject();
         if (paramsObject == null) {
@@ -271,9 +263,8 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
         /*
          * According to https://tools.ietf.org/html/rfc5915
          *
-         *      Though the ASN.1 indicates publicKey is OPTIONAL,
-         *      implementations that conform to this document SHOULD
-         *      always include the publicKey field
+         * Though the ASN.1 indicates publicKey is OPTIONAL, implementations that conform to this document SHOULD always
+         * include the publicKey field
          */
         try (DERParser dataParser = dataObject.createParser()) {
             ASN1Object pointData = dataParser.readObject();
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 37e6694..f871a3a 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
@@ -27,8 +27,6 @@ import java.security.KeyPair;
 import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.util.Collection;
 import java.util.Collections;
@@ -42,11 +40,13 @@ import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.io.der.ASN1Object;
+import org.apache.sshd.common.util.io.der.DERParser;
 import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- * @see <a href="https://tools.ietf.org/html/rfc5208">RFC 5208</A>
+ * @see    <a href="https://tools.ietf.org/html/rfc5208">RFC 5208</A>
  */
 public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser {
     // Not exactly according to standard but good enough
@@ -75,8 +75,8 @@ public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
         byte[] encBytes = IoUtils.toByteArray(stream);
         PKCS8PrivateKeyInfo pkcs8Info = new PKCS8PrivateKeyInfo(encBytes);
         return extractKeyPairs(
-            session, resourceKey, beginMarker, endMarker,
-            passwordProvider, encBytes, pkcs8Info, headers);
+                session, resourceKey, beginMarker, endMarker,
+                passwordProvider, encBytes, pkcs8Info, headers);
     }
 
     public Collection<KeyPair> extractKeyPairs(
@@ -86,44 +86,22 @@ public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
             PKCS8PrivateKeyInfo pkcs8Info, Map<String, String> headers)
             throws IOException, GeneralSecurityException {
         List<Integer> oidAlgorithm = pkcs8Info.getAlgorithmIdentifier();
-        PrivateKey prvKey = decodePEMPrivateKeyPKCS8(oidAlgorithm, encBytes);
-        PublicKey pubKey = recoverPublicKey(
-            session, resourceKey, beginMarker, endMarker,
-            passwordProvider, headers, encBytes, pkcs8Info, prvKey);
-        ValidateUtils.checkNotNull(pubKey,
-                "Failed to recover public key of OID=%s", oidAlgorithm);
-        KeyPair kp = new KeyPair(pubKey, prvKey);
-        return Collections.singletonList(kp);
-
-    }
-
-    @SuppressWarnings("checkstyle:ParameterNumber")
-    protected PublicKey recoverPublicKey(
-            SessionContext session, NamedResource resourceKey,
-            String beginMarker, String endMarker,
-            FilePasswordProvider passwordProvider,
-            Map<String, String> headers, byte[] encBytes,
-            PKCS8PrivateKeyInfo pkcs8Info, PrivateKey privateKey)
-            throws IOException, GeneralSecurityException {
-        if (privateKey instanceof ECPrivateKey) {
-            return recoverECPublicKey(
-                session, resourceKey, beginMarker, endMarker,
-                passwordProvider, headers, encBytes, pkcs8Info,
-                (ECPrivateKey) privateKey);
+        String oid = GenericUtils.join(oidAlgorithm, '.');
+        KeyPair kp;
+        if (SecurityUtils.isECCSupported()
+                && ECDSAPEMResourceKeyPairParser.ECDSA_OID.equals(oid)) {
+            ASN1Object privateKeyBytes = pkcs8Info.getPrivateKeyBytes();
+            try (DERParser parser = privateKeyBytes.createParser()) {
+                kp = ECDSAPEMResourceKeyPairParser.parseECKeyPair(parser);
+            }
+        } else {
+            PrivateKey prvKey = decodePEMPrivateKeyPKCS8(oidAlgorithm, encBytes);
+            PublicKey pubKey = ValidateUtils.checkNotNull(KeyUtils.recoverPublicKey(prvKey),
+                    "Failed to recover public key of OID=%s", oidAlgorithm);
+            kp = new KeyPair(pubKey, prvKey);
         }
 
-        return KeyUtils.recoverPublicKey(privateKey);
-    }
-
-    @SuppressWarnings("checkstyle:ParameterNumber")
-    protected ECPublicKey recoverECPublicKey(
-            SessionContext session, NamedResource resourceKey,
-            String beginMarker, String endMarker,
-            FilePasswordProvider passwordProvider,
-            Map<String, String> headers, byte[] encBytes,
-            PKCS8PrivateKeyInfo pkcs8Info, ECPrivateKey privateKey)
-            throws IOException, GeneralSecurityException {
-        throw new NoSuchAlgorithmException("TODO: SSHD-976");
+        return Collections.singletonList(kp);
     }
 
     public static PrivateKey decodePEMPrivateKeyPKCS8(List<Integer> oidAlgorithm, byte[] keyBytes)
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PrivateKeyInfo.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PrivateKeyInfo.java
index 873afb2..48917a5 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PrivateKeyInfo.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PrivateKeyInfo.java
@@ -50,7 +50,7 @@ import org.apache.sshd.common.util.io.der.DERParser;
  * </PRE>
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- * @see <a href="https://tools.ietf.org/html/rfc5208#section-5">RFC 5208 - section 5</a>
+ * @see    <a href="https://tools.ietf.org/html/rfc5208#section-5">RFC 5208 - section 5</a>
  */
 public class PKCS8PrivateKeyInfo /* TODO Cloneable */ {
     private BigInteger version;
@@ -108,11 +108,11 @@ public class PKCS8PrivateKeyInfo /* TODO Cloneable */ {
     }
 
     /**
-     * Decodes the current information with the data from the provided encoding.
-     * <B>Note:</B> User should {@link #clear()} the current information before parsing
+     * Decodes the current information with the data from the provided encoding. <B>Note:</B> User should
+     * {@link #clear()} the current information before parsing
      *
-     * @param privateKeyInfo The {@link ASN1Object} encoding
-     * @throws IOException If failed to parse the encoding
+     * @param  privateKeyInfo The {@link ASN1Object} encoding
+     * @throws IOException    If failed to parse the encoding
      */
     public void decode(ASN1Object privateKeyInfo) throws IOException {
         try (DERParser parser = privateKeyInfo.createParser()) {
@@ -158,8 +158,8 @@ public class PKCS8PrivateKeyInfo /* TODO Cloneable */ {
     @Override
     public String toString() {
         return getClass().getSimpleName()
-            + "[version=" + getVersion()
-            + ", algorithmIdentifier=" + getAlgorithmIdentifier()
-            + "]";
+               + "[version=" + getVersion()
+               + ", algorithmIdentifier=" + getAlgorithmIdentifier()
+               + "]";
     }
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java
index d599afe..161a0ac 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java
@@ -48,7 +48,7 @@ import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- * @see <a href="https://tools.ietf.org/html/rfc3279#section-2.3.1">RFC-3279 section 2.3.1</a>
+ * @see    <a href="https://tools.ietf.org/html/rfc3279#section-2.3.1">RFC-3279 section 2.3.1</a>
  */
 public class RSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser {
     // Not exactly according to standard but good enough
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java
index 4ed0514..1bc8bfa 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java
@@ -1015,7 +1015,7 @@ public final class GenericUtils {
     /**
      * The delegate Suppliers get() method is called exactly once and the result is cached.
      *
-     * @param <T> Generic type of supplied value
+     * @param  <T>      Generic type of supplied value
      * @param  delegate The actual Supplier
      * @return          The memoized Supplier
      */
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParserTest.java b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParserTest.java
index 4c6fdd8..f078c23 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParserTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParserTest.java
@@ -32,6 +32,7 @@ import java.util.List;
 import org.apache.commons.ssl.PEMItem;
 import org.apache.commons.ssl.PEMUtil;
 import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.cipher.ECCurves;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.security.SecurityUtils;
@@ -74,6 +75,11 @@ public class PKCS8PEMResourceKeyPairParserTest extends JUnitTestSupport {
         for (Integer ks : DSS_SIZES) {
             params.add(new Object[] { KeyUtils.DSS_ALGORITHM, ks });
         }
+        if (SecurityUtils.isECCSupported()) {
+            for (ECCurves curve : ECCurves.VALUES) {
+                params.add(new Object[] { KeyUtils.EC_ALGORITHM, curve.getKeySize() });
+            }
+        }
         return params;
     }
 
diff --git a/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
index 84b58c8..6db9657 100644
--- a/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
+++ b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
@@ -447,6 +447,7 @@ public abstract class JUnitTestSupport extends Assert {
             return KeyUtils.EC_ALGORITHM;
         } else {
             return algorithm.toUpperCase(Locale.ENGLISH);
+        }
     }
 
     public static void assertRSAPublicKeyEquals(String message, RSAPublicKey expected, RSAPublicKey actual) {