Repository: eclipse/leshan
Pull request #1157 (2021-11-17 17:23:33)

Change CaConstraint cert usage to not support "trustanchor mode".
This aims to implement: #936.


Patch:
diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaConstraintCertificateVerifier.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaConstraintCertificateVerifier.java
index fccc4dd7..dbb676f1 100644
--- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaConstraintCertificateVerifier.java
+++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaConstraintCertificateVerifier.java
@@ -50,6 +50,11 @@ import org.eclipse.leshan.core.util.Validate;
  * 
  * For details about Certificate Usage please see:
  * <a href="https://tools.ietf.org/html/rfc6698#section-2.1.1">rfc6698#section-2.1.1</a> - The Certificate Usage Field
+ * <p>
+ * The RFC says this certificate usage allows both trust anchors and CA certificates, but this raises some issue like
+ * explained in <a href="https://github.com/eclipse/leshan/issues/936">leshan issue #936</a>. So this implementation
+ * does not support CA certificate as trust anchors. If you need trust anchor usage, you should rather use
+ * {@link TrustAnchorAssertionCertificateVerifier}.
  */
 public class CaConstraintCertificateVerifier extends BaseCertificateVerifier {
 
@@ -71,6 +76,7 @@ public class CaConstraintCertificateVerifier extends BaseCertificateVerifier {
 
         validateCertificateChainNotEmpty(messageChain);
         X509Certificate receivedServerCertificate = validateReceivedCertificateIsSupported(messageChain);
+        validateNotDirectTrust(messageChain);
 
         // - must do PKIX validation with trustStore
         CertPath certPath;
@@ -94,4 +100,13 @@ public class CaConstraintCertificateVerifier extends BaseCertificateVerifier {
         return certPath;
     }
 
+    protected void validateNotDirectTrust(CertPath messageChain) throws HandshakeException {
+        Certificate certificate = messageChain.getCertificates().get(0);
+        if (certificate.equals(caCertificate)) {
+            AlertMessage alert = new AlertMessage(AlertLevel.FATAL, AlertDescription.BAD_CERTIFICATE);
+            throw new HandshakeException(
+                    "Invalid certificate path : direct trust is not allowed with 'CA Constraint' usage. Use 'Service Certificate Constraint' instead.",
+                    alert);
+        }
+    }
 }
diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaliforniumEndpointsManager.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaliforniumEndpointsManager.java
index d77dd9c3..5eacb455 100644
--- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaliforniumEndpointsManager.java
+++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaliforniumEndpointsManager.java
@@ -163,14 +163,10 @@ public class CaliforniumEndpointsManager implements EndpointsManager {
 
                 if (certificateUsage == CertificateUsage.CA_CONSTRAINT) {
                     X509Certificate[] trustedCertificates = null;
-                    // - trustStore is combination of client's configured trust store and provided certificate in server
-                    // info
-                    ArrayList<X509Certificate> newTrustedCertificatesList = new ArrayList<>();
                     if (this.trustStore != null) {
-                        newTrustedCertificatesList.addAll(CertPathUtil.toX509CertificatesList(this.trustStore));
+                        trustedCertificates = CertPathUtil.toX509CertificatesList(this.trustStore)
+                                .toArray(new X509Certificate[this.trustStore.size()]);
                     }
-                    newTrustedCertificatesList.add((X509Certificate) serverInfo.serverCertificate);
-                    trustedCertificates = newTrustedCertificatesList.toArray(new X509Certificate[0]);
                     newBuilder.setAdvancedCertificateVerifier(
                             new CaConstraintCertificateVerifier(serverInfo.serverCertificate, trustedCertificates));
                 } else if (certificateUsage == CertificateUsage.SERVICE_CERTIFICATE_CONSTRAINT) {
@@ -179,7 +175,7 @@ public class CaliforniumEndpointsManager implements EndpointsManager {
                     // - trustStore is client's configured trust store
                     if (this.trustStore != null) {
                         trustedCertificates = CertPathUtil.toX509CertificatesList(this.trustStore)
-                                .toArray(new X509Certificate[0]);
+                                .toArray(new X509Certificate[this.trustStore.size()]);
                     }
 
                     newBuilder.setAdvancedCertificateVerifier(new ServiceCertificateConstraintCertificateVerifier(


Test patch:
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecurityTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecurityTest.java
index bd7243c0..f4cf9a53 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecurityTest.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecurityTest.java
@@ -22,8 +22,11 @@ import static org.junit.Assert.*;
 import java.io.IOException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
+import java.security.cert.Certificate;
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
 
 import org.eclipse.californium.core.coap.CoAP.Type;
 import org.eclipse.californium.core.coap.Request;
@@ -791,6 +794,45 @@ public class SecurityTest {
 
     }
 
+    /**
+     * <pre>
+     * Test scenario:
+     * - Certificate Usage = CA constraint
+     * - Server Certificate = intermediate CA certificate
+     * - Server's TLS Server Certificate = intermediate signed certificate (with SAN DNS entry)
+     * - Server accepts client
+     * - Client Trust Store = does not contain server root CA. 
+     *
+     * Expected outcome:
+     * - Client is not able to connect as our CaConstraintCertificateVerifier does not support trust anchor mode.
+     * </pre>
+     */
+    @Test
+    public void registered_device_with_empty_truststore_with_x509cert_to_server_with_x509cert_rootca_certificate_usage_ca_intca_given()
+            throws NonUniqueSecurityInfoException, CertificateEncodingException, InterruptedException {
+        helper.createServerWithX509Cert(helper.serverIntX509CertChain, helper.serverIntPrivateKeyFromCert,
+                helper.trustedCertificates, true);
+
+        helper.server.start();
+
+        helper.setEndpointNameFromX509(helper.clientX509Cert);
+
+        // create a not empty trustore which does not contains any certificate of server certchain.
+        List<Certificate> truststore = new ArrayList<>();
+        truststore.add(helper.serverIntX509CertSelfSigned); // e.g. we use a selfsigned certificate not used in
+                                                            // certchain of this test.
+
+        helper.createX509CertClient(new X509Certificate[] { helper.clientX509Cert }, helper.clientPrivateKeyFromCert,
+                truststore, helper.serverIntX509CertChain[1], CertificateUsage.CA_CONSTRAINT);
+
+        helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint()));
+
+        helper.assertClientNotRegisterered();
+        helper.client.start();
+        helper.ensureNoRegistration(1);
+
+    }
+
     /**
      * <pre>
      * Test scenario: