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

commit 0ab624e3a064904b904769b90990de6fb2f5b811
Author: Thomas Wolf <tw...@apache.org>
AuthorDate: Mon Feb 3 21:59:28 2025 +0100

    [SSHD-1161] Pubkey auth: check certificate signature in server user auth
    
    The certificate must be validated. The code did not check the
    certificate signature at all.
    
    Add the missing signature verification, and add a test.
---
 .../sshd/server/auth/pubkey/UserAuthPublicKey.java |  39 ++++++--
 .../common/auth/PublicKeyAuthenticationTest.java   | 110 ++++++++++++++++-----
 2 files changed, 112 insertions(+), 37 deletions(-)

diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/UserAuthPublicKey.java
 
b/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/UserAuthPublicKey.java
index 8e1e57173..bc2ed93bb 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/UserAuthPublicKey.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/UserAuthPublicKey.java
@@ -91,6 +91,7 @@ public class UserAuthPublicKey extends AbstractUserAuth 
implements SignatureFact
         buffer.wpos(buffer.rpos() + len);
 
         PublicKey key = buffer.getRawPublicKey();
+        PublicKey verifyKey = key;
 
         if (key instanceof OpenSshCertificate) {
             OpenSshCertificate cert = (OpenSshCertificate) key;
@@ -101,18 +102,19 @@ public class UserAuthPublicKey extends AbstractUserAuth 
implements SignatureFact
                 if (!OpenSshCertificate.isValidNow(cert)) {
                     throw new CertificateException("expired");
                 }
+                verifyCertificateSignature(session, cert);
+                // TODO: move this into the authenticator
                 Collection<String> principals = cert.getPrincipals();
                 if (!GenericUtils.isEmpty(principals) && 
!principals.contains(username)) {
                     throw new CertificateException("not valid for the given 
username");
                 }
-                // TODO: cert.getCaKey() must be either in authorized_keys, 
marked as a CA key
-                // and not revoked, or in TrustedUserCAKeys and then also match
-                // AuthorizedPricipalsFile, if present.
-            } catch (CertificateException e) {
+            } catch (Exception e) {
                 warn("doAuth({}@{}): public key certificate (id={}) is not 
valid: {}", username, session, cert.getId(),
                         e.getMessage(), e);
-                throw e;
+                return Boolean.FALSE;
             }
+            // Need to use the certified public key for signature 
verification, not the certificate itself
+            verifyKey = cert.getCertPubKey();
         }
 
         Collection<NamedFactory<Signature>> factories = 
ValidateUtils.checkNotNullAndNotEmpty(
@@ -124,11 +126,6 @@ public class UserAuthPublicKey extends AbstractUserAuth 
implements SignatureFact
             log.debug("doAuth({}@{}) verify key type={}, factories={}, 
fingerprint={}",
                     username, session, alg, NamedResource.getNames(factories), 
KeyUtils.getFingerPrint(key));
         }
-        /*
-         * When users employ cert authentication, need to use the public key 
in the cert for signing
-         * and cannot use the cert itself directly for signing
-         */
-        PublicKey verifyKey = key instanceof OpenSshCertificate ? 
((OpenSshCertificate) key).getCertPubKey() : key;
         Signature verifier = ValidateUtils.checkNotNull(
                 NamedFactory.create(factories, alg),
                 "No verifier located for algorithm=%s",
@@ -149,6 +146,7 @@ public class UserAuthPublicKey extends AbstractUserAuth 
implements SignatureFact
         boolean authed;
         try {
             authed = authenticator.authenticate(username, key, session);
+            // Also checks username against the certificate's principals, if 
key is a certificate.
         } catch (Error e) {
             warn("doAuth({}@{}) failed ({}) to consult delegate for {} key={}: 
{}",
                     username, session, e.getClass().getSimpleName(), alg, 
KeyUtils.getFingerPrint(key), e.getMessage(), e);
@@ -183,6 +181,27 @@ public class UserAuthPublicKey extends AbstractUserAuth 
implements SignatureFact
         return Boolean.TRUE;
     }
 
+    protected void verifyCertificateSignature(ServerSession session, 
OpenSshCertificate cert) throws Exception {
+        PublicKey signatureKey = cert.getCaPubKey();
+        String keyAlg = KeyUtils.getKeyType(signatureKey);
+        String keyId = cert.getId();
+
+        String sigAlg = cert.getSignatureAlgorithm();
+        if (!keyAlg.equals(KeyUtils.getCanonicalKeyType(sigAlg))) {
+            throw new CertificateException(
+                    "Found invalid signature alg " + sigAlg + " for key ID=" + 
keyId + " using a " + keyAlg + " CA key");
+        }
+
+        Signature verif = 
ValidateUtils.checkNotNull(NamedFactory.create(session.getSignatureFactories(), 
sigAlg),
+                "No CA verifier located for algorithm=%s of key ID=%s", 
sigAlg, keyId);
+        verif.initVerifier(session, signatureKey);
+        verif.update(session, cert.getMessage());
+
+        if (!verif.verify(session, cert.getSignature())) {
+            throw new CertificateException("CA signature verification failed 
for key type=" + keyAlg + " of key ID=" + keyId);
+        }
+    }
+
     protected boolean verifySignature(
             ServerSession session, String username, String alg, PublicKey key, 
Buffer buffer, Signature verifier, byte[] sig)
             throws Exception {
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/common/auth/PublicKeyAuthenticationTest.java
 
b/sshd-core/src/test/java/org/apache/sshd/common/auth/PublicKeyAuthenticationTest.java
index 134ab37e6..be0359fe0 100644
--- 
a/sshd-core/src/test/java/org/apache/sshd/common/auth/PublicKeyAuthenticationTest.java
+++ 
b/sshd-core/src/test/java/org/apache/sshd/common/auth/PublicKeyAuthenticationTest.java
@@ -49,6 +49,7 @@ import org.apache.sshd.common.SshException;
 import org.apache.sshd.common.config.keys.FilePasswordProvider;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.config.keys.OpenSshCertificate;
+import org.apache.sshd.common.config.keys.OpenSshCertificateImpl;
 import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.session.SessionContext;
@@ -460,25 +461,23 @@ public class PublicKeyAuthenticationTest extends 
AuthenticationTestSupport {
         }
     }
 
-    @ParameterizedTest(name = "test certificates issued using the {0} 
algorithm")
+    @ParameterizedTest(name = "test certificate {0} signed with {2}")
     @MethodSource("certificateAlgorithms")
-    void testCertificateWithDifferentAlgorithms(String keyAlgorithm, int 
keySize, String signatureAlgorithm) throws Exception {
+    void testCertificateWithDifferentAlgorithms(
+            String keyAlgorithm, int keySize, String sigKeyAlgorithm, int 
sigKeySize,
+            String signatureAlgorithm) throws Exception {
         // 1. Generating a user key pair
         KeyPair userkey = CommonTestSupportUtils.generateKeyPair(keyAlgorithm, 
keySize);
         // 2. Generating CA key pair
-        KeyPair caKeypair = 
CommonTestSupportUtils.generateKeyPair(keyAlgorithm, keySize);
+        KeyPair caKeypair = 
CommonTestSupportUtils.generateKeyPair(sigKeyAlgorithm, sigKeySize);
 
         // 3. Building openSshCertificate
-        OpenSshCertificate signedCert = 
OpenSshCertificateBuilder.userCertificate()
-                .serial(System.currentTimeMillis())
-                .publicKey(userkey.getPublic())
-                .id("test-cert-" + keyAlgorithm)
-                .validBefore(System.currentTimeMillis() + 
TimeUnit.HOURS.toMillis(1))
-                .principals(Collections.singletonList("user01"))
-                .criticalOptions(Collections.emptyList())
-                .extensions(Arrays.asList(
-                        new 
OpenSshCertificate.CertificateOption("permit-X11-forwarding"),
-                        new 
OpenSshCertificate.CertificateOption("permit-agent-forwarding")))
+        OpenSshCertificate signedCert = 
OpenSshCertificateBuilder.userCertificate() //
+                .serial(System.currentTimeMillis()) //
+                .publicKey(userkey.getPublic()) //
+                .id("test-cert-" + keyAlgorithm) //
+                .validBefore(System.currentTimeMillis() + 
TimeUnit.HOURS.toMillis(1)) //
+                .principals(Collections.singletonList("user01")) //
                 .sign(caKeypair, signatureAlgorithm);
 
         // 4. Configuring the ssh server
@@ -486,8 +485,7 @@ public class PublicKeyAuthenticationTest extends 
AuthenticationTestSupport {
         
sshd.setKeyboardInteractiveAuthenticator(KeyboardInteractiveAuthenticator.NONE);
         CoreTestSupportUtils.setupFullSignaturesSupport(sshd);
 
-        sshd.setUserAuthFactories(Collections.singletonList(
-                new 
org.apache.sshd.server.auth.pubkey.UserAuthPublicKeyFactory()));
+        sshd.setUserAuthFactories(Collections.singletonList(new 
org.apache.sshd.server.auth.pubkey.UserAuthPublicKeyFactory()));
 
         AtomicInteger authAttempts = new AtomicInteger(0);
         sshd.setPublickeyAuthenticator((username, key, session) -> {
@@ -502,14 +500,12 @@ public class PublicKeyAuthenticationTest extends 
AuthenticationTestSupport {
         // 5. Testing Client Authentication
         try (SshClient client = setupTestClient()) {
             CoreTestSupportUtils.setupFullSignaturesSupport(client);
-            client.setUserAuthFactories(Collections.singletonList(
-                    new 
org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory()));
+            client.setUserAuthFactories(
+                    Collections.singletonList(new 
org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory()));
 
             client.start();
 
-            try (ClientSession session = client.connect("user01", 
TEST_LOCALHOST, port)
-                    .verify(CONNECT_TIMEOUT)
-                    .getSession()) {
+            try (ClientSession session = client.connect("user01", 
TEST_LOCALHOST, port).verify(CONNECT_TIMEOUT).getSession()) {
 
                 KeyPair certKeyPair = new KeyPair(signedCert, 
userkey.getPrivate());
                 session.addPublicKeyIdentity(certKeyPair);
@@ -524,12 +520,72 @@ public class PublicKeyAuthenticationTest extends 
AuthenticationTestSupport {
     }
 
     private static Stream<Arguments> certificateAlgorithms() {
-        return Stream.of(
-                // key size, signature algorithm, algorithm name
-                Arguments.of(KeyUtils.RSA_ALGORITHM, 2048, "rsa-sha2-512"),
-                Arguments.of(KeyUtils.RSA_ALGORITHM, 2048, "rsa-sha2-256"),
-                Arguments.of(KeyUtils.EC_ALGORITHM, 256, 
"ecdsa-sha2-nistp256"),
-                Arguments.of(KeyUtils.EC_ALGORITHM, 384, 
"ecdsa-sha2-nistp384"),
-                Arguments.of(KeyUtils.EC_ALGORITHM, 521, 
"ecdsa-sha2-nistp521"));
+        return Stream.of( //
+                Arguments.of(KeyUtils.RSA_ALGORITHM, 2048, 
KeyUtils.RSA_ALGORITHM, 2048, "rsa-sha2-512"),
+                Arguments.of(KeyUtils.RSA_ALGORITHM, 2048, 
KeyUtils.RSA_ALGORITHM, 2048, "rsa-sha2-256"),
+                Arguments.of(KeyUtils.EC_ALGORITHM, 256, 
KeyUtils.EC_ALGORITHM, 256, "ecdsa-sha2-nistp256"),
+                Arguments.of(KeyUtils.EC_ALGORITHM, 384, 
KeyUtils.EC_ALGORITHM, 384, "ecdsa-sha2-nistp384"),
+                Arguments.of(KeyUtils.EC_ALGORITHM, 521, 
KeyUtils.EC_ALGORITHM, 521, "ecdsa-sha2-nistp521"),
+                Arguments.of(KeyUtils.RSA_ALGORITHM, 2048, 
KeyUtils.EC_ALGORITHM, 384, "ecdsa-sha2-nistp384"),
+                Arguments.of(KeyUtils.EC_ALGORITHM, 384, 
KeyUtils.RSA_ALGORITHM, 2048, "rsa-sha2-512"));
+    }
+
+    @Test
+    void testCertificateWithBrokenSignature() throws Exception {
+        KeyPair userkey = 
CommonTestSupportUtils.generateKeyPair(KeyUtils.EC_ALGORITHM, 256);
+        KeyPair caKeypair = 
CommonTestSupportUtils.generateKeyPair(KeyUtils.EC_ALGORITHM, 256);
+
+        OpenSshCertificate signedCert = 
OpenSshCertificateBuilder.userCertificate() //
+                .serial(System.currentTimeMillis()) //
+                .publicKey(userkey.getPublic()) //
+                .id("test-cert-ecdsa-sha2-nistp256") //
+                .validBefore(System.currentTimeMillis() + 
TimeUnit.HOURS.toMillis(1)) //
+                .principals(Collections.singletonList("user01")) //
+                .sign(caKeypair, "ecdsa-sha2-nistp256");
+
+        // Break the certificate
+        assertTrue(signedCert instanceof OpenSshCertificateImpl);
+        OpenSshCertificateImpl certImpl = (OpenSshCertificateImpl) signedCert;
+        byte[] certSig = certImpl.getSignature();
+        certSig[certSig.length - 1] ^= 0xAA;
+        certImpl.setSignature(certSig);
+
+        // Configure the ssh server
+        sshd.setPasswordAuthenticator(RejectAllPasswordAuthenticator.INSTANCE);
+        
sshd.setKeyboardInteractiveAuthenticator(KeyboardInteractiveAuthenticator.NONE);
+        CoreTestSupportUtils.setupFullSignaturesSupport(sshd);
+
+        sshd.setUserAuthFactories(Collections.singletonList(new 
org.apache.sshd.server.auth.pubkey.UserAuthPublicKeyFactory()));
+
+        AtomicInteger authAttempts = new AtomicInteger(0);
+        sshd.setPublickeyAuthenticator((username, key, session) -> {
+            authAttempts.incrementAndGet();
+            if (key instanceof OpenSshCertificate) {
+                OpenSshCertificate cert = (OpenSshCertificate) key;
+                return KeyUtils.compareKeys(cert.getCaPubKey(), 
caKeypair.getPublic());
+            }
+            return false;
+        });
+
+        // Client authentication should fail
+        try (SshClient client = setupTestClient()) {
+            CoreTestSupportUtils.setupFullSignaturesSupport(client);
+            client.setUserAuthFactories(
+                    Collections.singletonList(new 
org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory()));
+
+            client.start();
+
+            try (ClientSession session = client.connect("user01", 
TEST_LOCALHOST, port).verify(CONNECT_TIMEOUT).getSession()) {
+
+                KeyPair certKeyPair = new KeyPair(signedCert, 
userkey.getPrivate());
+                session.addPublicKeyIdentity(certKeyPair);
+
+                AuthFuture auth = session.auth();
+                assertThrows(SshException.class, () -> 
auth.verify(AUTH_TIMEOUT));
+            } finally {
+                client.stop();
+            }
+        }
     }
+
 }

Reply via email to