Repository: eclipse/leshan
Pull request #818 (2020-03-12 17:00:59)

Add a mechanism to clean DTLS Connection when SecurityInfo is removed
This aims to address #162.

With this fix as soon as the `SecurityInfo` is removed from the `SecurityStore`, corresponding DTLS connection is removed and so device is  immediately unable to communicate anymore. If device try to send a packet it will be ignored. (as defined in [DTLS specification](https://tools.ietf.org/html/rfc6347#section-4.1.2.7))

This mechanism is enabled only if the `SecurityStore` implements `EditableSecurityStore`.


Patch:
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/ConnectionCleaner.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/ConnectionCleaner.java
new file mode 100644
index 00000000..b6c36cca
--- /dev/null
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/ConnectionCleaner.java
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Sierra Wireless and others.
+ * 
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ * 
+ * The Eclipse Public License is available at
+ *    http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ *    http://www.eclipse.org/org/documents/edl-v10.html.
+ * 
+ * Contributors:
+ *     Sierra Wireless - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.leshan.server.californium;
+
+import java.security.Principal;
+import java.security.PublicKey;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.eclipse.californium.elements.auth.PreSharedKeyIdentity;
+import org.eclipse.californium.elements.auth.RawPublicKeyIdentity;
+import org.eclipse.californium.elements.auth.X509CertPath;
+import org.eclipse.californium.elements.util.LeastRecentlyUsedCache.Predicate;
+import org.eclipse.californium.scandium.DTLSConnector;
+import org.eclipse.leshan.core.californium.EndpointContextUtil;
+import org.eclipse.leshan.server.security.SecurityInfo;
+
+/**
+ * This class is responsible to remove DTLS connection for a given SecurityInfo.
+ */
+public class ConnectionCleaner {
+
+    private DTLSConnector connector;
+
+    public ConnectionCleaner(DTLSConnector connector) {
+        this.connector = connector;
+    }
+
+    public void cleanConnectionFor(final SecurityInfo... infos) {
+        connector.startTerminateConnectionsForPrincipal(new Predicate<Principal>() {
+            @Override
+            public boolean accept(Principal principal) {
+                if (principal != null) {
+                    for (SecurityInfo info : infos) {
+                        if (info != null) {
+                            // PSK
+                            if (info.usePSK() && principal instanceof PreSharedKeyIdentity) {
+                                String identity = ((PreSharedKeyIdentity) principal).getIdentity();
+                                if (info.getIdentity().equals(identity)) {
+                                    return true;
+                                }
+                            }
+                            // RPK
+                            else if (info.useRPK() && principal instanceof RawPublicKeyIdentity) {
+                                PublicKey publicKey = ((RawPublicKeyIdentity) principal).getKey();
+                                if (info.getRawPublicKey().equals(publicKey)) {
+                                    return true;
+                                }
+                            }
+                            // x509
+                            else if (info.useX509Cert() && principal instanceof X500Principal
+                                    || principal instanceof X509CertPath) {
+                                // Extract common name
+                                String x509CommonName = EndpointContextUtil.extractCN(principal.getName());
+                                if (x509CommonName.equals(info.getEndpoint())) {
+                                    return true;
+                                }
+                            }
+                        }
+                    }
+                }
+                return false;
+            }
+        });
+    }
+}
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanServer.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanServer.java
index 3bba0a40..07fbd430 100644
--- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanServer.java
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanServer.java
@@ -27,6 +27,7 @@ import org.eclipse.californium.core.network.CoapEndpoint;
 import org.eclipse.californium.core.network.Endpoint;
 import org.eclipse.californium.core.network.config.NetworkConfig;
 import org.eclipse.californium.core.server.resources.Resource;
+import org.eclipse.californium.scandium.DTLSConnector;
 import org.eclipse.leshan.core.californium.CoapResponseCallback;
 import org.eclipse.leshan.core.node.codec.CodecException;
 import org.eclipse.leshan.core.node.codec.LwM2mNodeDecoder;
@@ -68,8 +69,10 @@ import org.eclipse.leshan.server.registration.RegistrationStore;
 import org.eclipse.leshan.server.registration.RegistrationUpdate;
 import org.eclipse.leshan.server.request.LwM2mRequestSender;
 import org.eclipse.leshan.server.security.Authorizer;
+import org.eclipse.leshan.server.security.EditableSecurityStore;
 import org.eclipse.leshan.server.security.SecurityInfo;
 import org.eclipse.leshan.server.security.SecurityStore;
+import org.eclipse.leshan.server.security.SecurityStoreListener;
 import org.eclipse.leshan.util.Validate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -175,6 +178,9 @@ public class LeshanServer {
         requestSender = createRequestSender(securedEndpoint, unsecuredEndpoint, registrationService, observationService,
                 this.modelProvider, encoder, decoder, presenceService);
 
+        // connection cleaner
+        createConnectionCleaner(securityStore, securedEndpoint);
+
         coapApi = new CoapAPI();
     }
 
@@ -262,6 +268,24 @@ public class LeshanServer {
         return requestSender;
     }
 
+    protected void createConnectionCleaner(SecurityStore securityStore, CoapEndpoint securedEndpoint) {
+        if (securedEndpoint != null && securedEndpoint.getConnector() instanceof DTLSConnector
+                && securityStore instanceof EditableSecurityStore) {
+
+            final ConnectionCleaner connectionCleaner = new ConnectionCleaner(
+                    (DTLSConnector) securedEndpoint.getConnector());
+
+            ((EditableSecurityStore) securityStore).setListener(new SecurityStoreListener() {
+                @Override
+                public void securityInfoRemoved(boolean infosAreCompromised, SecurityInfo... infos) {
+                    if (infosAreCompromised) {
+                        connectionCleaner.cleanConnectionFor(infos);
+                    }
+                }
+            });
+        }
+    }
+
     /**
      * Starts the server and binds it to the specified port.
      */
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/EditableSecurityStore.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/EditableSecurityStore.java
index c68d967e..8cae40f9 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/EditableSecurityStore.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/EditableSecurityStore.java
@@ -41,7 +41,16 @@ public interface EditableSecurityStore extends SecurityStore {
      * Removes the security information for a given end-point.
      * 
      * @param endpoint the client end-point
+     * @param infosAreCompromised if the {@link SecurityInfo} removed should be considered as compromised and so must
+     *        not be used anymore immediately.
      * @return the removed {@link SecurityInfo} or <code>null</code> if no info for the end-point.
      */
-    SecurityInfo remove(String endpoint);
+    SecurityInfo remove(String endpoint, boolean infosAreCompromised);
+
+    /**
+     * Set a Listener for this store.
+     * 
+     * @param listener the security store listener
+     */
+    void setListener(SecurityStoreListener listener);
 }
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/FileSecurityStore.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/FileSecurityStore.java
index cb8b4640..7ce0ac5a 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/FileSecurityStore.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/FileSecurityStore.java
@@ -70,10 +70,10 @@ public class FileSecurityStore extends InMemorySecurityStore {
     }
 
     @Override
-    public SecurityInfo remove(String endpoint) {
+    public SecurityInfo remove(String endpoint, boolean infosAreCompromised) {
         writeLock.lock();
         try {
-            SecurityInfo info = super.remove(endpoint);
+            SecurityInfo info = super.remove(endpoint, infosAreCompromised);
             if (info != null) {
                 saveToFile();
             }
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/InMemorySecurityStore.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/InMemorySecurityStore.java
index 6bdde277..d9c99462 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/InMemorySecurityStore.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/InMemorySecurityStore.java
@@ -40,6 +40,8 @@ public class InMemorySecurityStore implements EditableSecurityStore {
     // by PSK identity
     protected Map<String, SecurityInfo> securityByIdentity = new HashMap<>();
 
+    private SecurityStoreListener listener;
+
     public InMemorySecurityStore() {
     }
 
@@ -106,7 +108,7 @@ public class InMemorySecurityStore implements EditableSecurityStore {
     }
 
     @Override
-    public SecurityInfo remove(String endpoint) {
+    public SecurityInfo remove(String endpoint, boolean infosAreCompromised) {
         writeLock.lock();
         try {
             SecurityInfo info = securityByEp.get(endpoint);
@@ -115,10 +117,18 @@ public class InMemorySecurityStore implements EditableSecurityStore {
                     securityByIdentity.remove(info.getIdentity());
                 }
                 securityByEp.remove(endpoint);
+                if (listener != null) {
+                    listener.securityInfoRemoved(infosAreCompromised, info);
+                }
             }
             return info;
         } finally {
             writeLock.unlock();
         }
     }
+
+    @Override
+    public void setListener(SecurityStoreListener listener) {
+        this.listener = listener;
+    }
 }
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/SecurityStoreListener.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/SecurityStoreListener.java
new file mode 100644
index 00000000..f7471be0
--- /dev/null
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/SecurityStoreListener.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Sierra Wireless and others.
+ * 
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ * 
+ * The Eclipse Public License is available at
+ *    http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ *    http://www.eclipse.org/org/documents/edl-v10.html.
+ * 
+ * Contributors:
+ *     Sierra Wireless - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.leshan.server.security;
+
+/**
+ * A Listener for {@link SecurityStore}
+ */
+public interface SecurityStoreListener {
+    /**
+     * Called when {@link SecurityInfo} are removed.
+     * 
+     * @param infosAreCompromised True if info are compromised and should not be used immediately
+     * @param infos Array of removed {@link SecurityInfo}
+     */
+    void securityInfoRemoved(boolean infosAreCompromised, SecurityInfo... infos);
+}
diff --git a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/SecurityServlet.java b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/SecurityServlet.java
index d7303ce0..10e2725f 100644
--- a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/SecurityServlet.java
+++ b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/SecurityServlet.java
@@ -171,7 +171,7 @@ public class SecurityServlet extends HttpServlet {
         }
 
         LOG.debug("Removing security info for end-point {}", endpoint);
-        if (this.store.remove(endpoint) != null) {
+        if (this.store.remove(endpoint, true) != null) {
             resp.sendError(HttpServletResponse.SC_OK);
         } else {
             resp.sendError(HttpServletResponse.SC_NOT_FOUND);
diff --git a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisSecurityStore.java b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisSecurityStore.java
index 9eb5678a..136f3c79 100644
--- a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisSecurityStore.java
+++ b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisSecurityStore.java
@@ -23,6 +23,7 @@ import org.eclipse.leshan.server.security.EditableSecurityStore;
 import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException;
 import org.eclipse.leshan.server.security.SecurityInfo;
 import org.eclipse.leshan.server.security.SecurityStore;
+import org.eclipse.leshan.server.security.SecurityStoreListener;
 
 import redis.clients.jedis.Jedis;
 import redis.clients.jedis.ScanParams;
@@ -41,6 +42,7 @@ public class RedisSecurityStore implements EditableSecurityStore {
     private static final String PSKID_SEC = "PSKID#SEC";
 
     private final Pool<Jedis> pool;
+    private SecurityStoreListener listener;
 
     public RedisSecurityStore(Pool<Jedis> pool) {
         this.pool = pool;
@@ -118,7 +120,7 @@ public class RedisSecurityStore implements EditableSecurityStore {
     }
 
     @Override
-    public SecurityInfo remove(String endpoint) {
+    public SecurityInfo remove(String endpoint, boolean infosAreCompromised) {
         try (Jedis j = pool.getResource()) {
             byte[] data = j.get((SEC_EP + endpoint).getBytes());
 
@@ -128,6 +130,9 @@ public class RedisSecurityStore implements EditableSecurityStore {
                     j.hdel(PSKID_SEC.getBytes(), info.getIdentity().getBytes());
                 }
                 j.del((SEC_EP + endpoint).getBytes());
+                if (listener != null) {
+                    listener.securityInfoRemoved(infosAreCompromised, info);
+                }
                 return info;
             }
         }
@@ -141,4 +146,9 @@ public class RedisSecurityStore implements EditableSecurityStore {
     private SecurityInfo deserialize(byte[] data) {
         return SecurityInfoSerDes.deserialize(data);
     }
+
+    @Override
+    public void setListener(SecurityStoreListener listener) {
+        this.listener = listener;
+    }
 }
\ No newline at end of file


Test patch:
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapIntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapIntegrationTestHelper.java
index a3fa6f53..6617be34 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapIntegrationTestHelper.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapIntegrationTestHelper.java
@@ -416,6 +416,6 @@ public class BootstrapIntegrationTestHelper extends SecureIntegrationTestHelper
     @Override
     public void dispose() {
         super.dispose();
-        ((EditableSecurityStore) server.getSecurityStore()).remove(getCurrentEndpoint());
+        ((EditableSecurityStore) server.getSecurityStore()).remove(getCurrentEndpoint(), false);
     }
 }
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/IntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/IntegrationTestHelper.java
index 30441326..8dc76724 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/IntegrationTestHelper.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/IntegrationTestHelper.java
@@ -248,7 +248,7 @@ public class IntegrationTestHelper {
         } catch (InterruptedException e) {
             throw new RuntimeException(e);
         } catch (TimeoutException e) {
-            // timeou means no registration
+            // timeout means no registration
             return;
         }
         fail("No update registration expected");
@@ -276,7 +276,7 @@ public class IntegrationTestHelper {
         } catch (InterruptedException e) {
             throw new RuntimeException(e);
         } catch (TimeoutException e) {
-            // timeou means no registration
+            // timeout means no registration
             return;
         }
         fail("No de-registration expected");
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecureIntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecureIntegrationTestHelper.java
index 6a9c84d6..bbb73f30 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecureIntegrationTestHelper.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecureIntegrationTestHelper.java
@@ -272,6 +272,7 @@ public class SecureIntegrationTestHelper extends IntegrationTestHelper {
         builder.setLocalAddress(clientAddress.getHostString(), clientAddress.getPort());
         builder.setObjects(objects);
         client = builder.build();
+        setupClientMonitoring();
     }
 
     public void createRPKClient() {
@@ -310,6 +311,7 @@ public class SecureIntegrationTestHelper extends IntegrationTestHelper {
         builder.setLocalAddress(clientAddress.getHostString(), clientAddress.getPort());
         builder.setObjects(objects);
         client = builder.build();
+        setupClientMonitoring();
     }
 
     @Override
@@ -366,9 +368,9 @@ public class SecureIntegrationTestHelper extends IntegrationTestHelper {
 
     @Override
     public void dispose() {
-        getSecurityStore().remove(getCurrentEndpoint());
-        getSecurityStore().remove(BAD_ENDPOINT);
-        getSecurityStore().remove(GOOD_ENDPOINT);
+        getSecurityStore().remove(getCurrentEndpoint(), false);
+        getSecurityStore().remove(BAD_ENDPOINT, false);
+        getSecurityStore().remove(GOOD_ENDPOINT, false);
         super.dispose();
     }
 }
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 ed8e47d9..a6159f00 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
@@ -399,6 +399,38 @@ public class SecurityTest {
         helper.ensureNoRegistration(1);
     }
 
+    @Test
+    public void registered_device_with_psk_identity_to_server_with_psk_then_remove_security_info()
+            throws NonUniqueSecurityInfoException, InterruptedException {
+        // Create PSK server & start it
+        helper.createServer(); // default server support PSK
+        helper.server.start();
+
+        // Create PSK Client
+        helper.createPSKClient();
+
+        // Add client credentials to the server
+        helper.getSecurityStore()
+                .add(SecurityInfo.newPreSharedKeyInfo(helper.getCurrentEndpoint(), GOOD_PSK_ID, GOOD_PSK_KEY));
+
+        // Check client is not registered
+        helper.assertClientNotRegisterered();
+
+        // Start it and wait for registration
+        helper.client.start();
+        helper.waitForRegistrationAtClientSide(1);
+
+        // Check client is well registered
+        helper.assertClientRegisterered();
+
+        // remove compromised credentials
+        helper.getSecurityStore().remove(helper.getCurrentEndpoint(), true);
+
+        // try to update
+        helper.client.triggerRegistrationUpdate();
+        helper.ensureNoUpdate(1);
+    }
+
     @Test
     public void nonunique_psk_identity() throws NonUniqueSecurityInfoException {
         helper.createServer();
@@ -450,7 +482,7 @@ public class SecurityTest {
     }
 
     @Test
-    public void registered_device_with_bad_rpk_to_server_with_rpk() throws NonUniqueSecurityInfoException {
+    public void registered_device_with_bad_rpk_to_server_with_rpk_() throws NonUniqueSecurityInfoException {
         helper.createServerWithRPK();
         helper.server.start();
 
@@ -465,6 +497,32 @@ public class SecurityTest {
         helper.ensureNoRegistration(1);
     }
 
+    @Test
+    public void registered_device_with_rpk_to_server_with_rpk_then_remove_security_info()
+            throws NonUniqueSecurityInfoException {
+        helper.createServerWithRPK();
+        helper.server.start();
+
+        helper.createRPKClient();
+
+        helper.getSecurityStore()
+                .add(SecurityInfo.newRawPublicKeyInfo(helper.getCurrentEndpoint(), helper.clientPublicKey));
+
+        helper.assertClientNotRegisterered();
+        helper.client.start();
+        helper.waitForRegistrationAtClientSide(1);
+
+        // Check client is well registered
+        helper.assertClientRegisterered();
+
+        // remove compromised credentials
+        helper.getSecurityStore().remove(helper.getCurrentEndpoint(), true);
+
+        // try to update
+        helper.client.triggerRegistrationUpdate();
+        helper.ensureNoUpdate(1);
+    }
+
     @Test
     public void registered_device_with_rpk_and_bad_endpoint_to_server_with_rpk() throws NonUniqueSecurityInfoException {
         helper.createServerWithRPK();
@@ -478,6 +536,31 @@ public class SecurityTest {
         helper.ensureNoRegistration(1);
     }
 
+    @Test
+    public void registered_device_with_x509cert_to_server_with_x509cert_then_remove_security_info()
+            throws NonUniqueSecurityInfoException, CertificateEncodingException {
+        helper.createServerWithX509Cert();
+        helper.server.start();
+
+        helper.createX509CertClient();
+
+        helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint()));
+
+        helper.assertClientNotRegisterered();
+        helper.client.start();
+        helper.waitForRegistrationAtClientSide(1);
+
+        // Check client is well registered
+        helper.assertClientRegisterered();
+
+        // remove compromised credentials
+        helper.getSecurityStore().remove(helper.getCurrentEndpoint(), true);
+
+        // try to update
+        helper.client.triggerRegistrationUpdate();
+        helper.ensureNoUpdate(1);
+    }
+
     @Test
     public void registered_device_with_x509cert_to_server_with_x509cert()
             throws NonUniqueSecurityInfoException, CertificateEncodingException {
diff --git a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/LeshanServerTest.java b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/LeshanServerTest.java
index da4f1473..6d3b02bd 100644
--- a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/LeshanServerTest.java
+++ b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/LeshanServerTest.java
@@ -33,7 +33,7 @@ public class LeshanServerTest {
 
     @Test
     public void testStartStopStart() throws InterruptedException {
-        LeshanServer server = new LeshanServerBuilder().build();
+        LeshanServer server = new LeshanServerBuilder().setLocalAddress(new InetSocketAddress(0)).build();
 
         server.start();
         Thread.sleep(100);
@@ -47,7 +47,7 @@ public class LeshanServerTest {
         // look at nb active thread before.
         int numberOfThreadbefore = Thread.activeCount();
 
-        LeshanServer server = new LeshanServerBuilder().build();
+        LeshanServer server = new LeshanServerBuilder().setLocalAddress(new InetSocketAddress(0)).build();
         server.start();
         Thread.sleep(100);
         // HACK force creation thread creation.
@@ -65,7 +65,7 @@ public class LeshanServerTest {
         // look at nb active thread before.
         int numberOfThreadbefore = Thread.activeCount();
 
-        LeshanServer server = new LeshanServerBuilder().build();
+        LeshanServer server = new LeshanServerBuilder().setLocalAddress(new InetSocketAddress(0)).build();
         server.start();
         Thread.sleep(100);
         // HACK force creation thread creation.
diff --git a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerTest.java b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerTest.java
index 520913f1..cb974c3e 100644
--- a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerTest.java
+++ b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerTest.java
@@ -38,7 +38,8 @@ public class LeshanBootstrapServerTest {
     private BootstrapHandler bsHandler;
 
     private LeshanBootstrapServer createBootstrapServer() {
-        LeshanBootstrapServerBuilder builder = new LeshanBootstrapServerBuilder();
+        LeshanBootstrapServerBuilder builder = new LeshanBootstrapServerBuilder()
+                .setLocalAddress(new InetSocketAddress(0));
         builder.setBootstrapHandlerFactory(new BootstrapHandlerFactory() {
 
             @Override