Repository: eclipse/leshan
Pull request #704 (2019-06-21 15:25:18)

Add  a default bootstrap config store.
This aims to facilitate "getting started"  for Bootstrap Server.


Patch:
diff --git a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/BootstrapSecurityStoreImpl.java b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/BootstrapConfigSecurityStore.java
similarity index 73%
rename from leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/BootstrapSecurityStoreImpl.java
rename to leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/BootstrapConfigSecurityStore.java
index 4f08abe3..c1f2db6e 100644
--- a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/BootstrapSecurityStoreImpl.java
+++ b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/BootstrapConfigSecurityStore.java
@@ -25,6 +25,8 @@ import java.util.Map;
 import org.eclipse.leshan.SecurityMode;
 import org.eclipse.leshan.server.bootstrap.BootstrapConfig;
 import org.eclipse.leshan.server.bootstrap.BootstrapConfig.ServerSecurity;
+import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore;
+import org.eclipse.leshan.server.bootstrap.EditableBootstrapConfigStore;
 import org.eclipse.leshan.server.security.BootstrapSecurityStore;
 import org.eclipse.leshan.server.security.SecurityInfo;
 import org.eclipse.leshan.util.SecurityUtil;
@@ -32,22 +34,36 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * A DTLS security store using the provisioned bootstrap information for finding the DTLS/PSK credentials.
+ * A {@link BootstrapSecurityStore} which use a {@link BootstrapConfigStore} to device credentials.
+ * <p>
+ * Generally, a {@link BootstrapSecurityStore} contains information about how a device should connect to server (using
+ * psk, rpk or x509 ). And a {@link BootstrapConfigStore} contains configuration which should be written on device
+ * during bootstrap session.
+ * <p>
+ * This {@link BootstrapSecurityStore} will search in {@link BootstrapConfigStore} to find security info.
+ * <p>
+ * 
+ * WARNING : This store is not production ready.
  */
-public class BootstrapSecurityStoreImpl implements BootstrapSecurityStore {
+public class BootstrapConfigSecurityStore implements BootstrapSecurityStore {
 
-    private static final Logger LOG = LoggerFactory.getLogger(BootstrapSecurityStoreImpl.class);
+    private static final Logger LOG = LoggerFactory.getLogger(BootstrapConfigSecurityStore.class);
 
-    private final BootstrapStoreImpl bsStore;
+    private final EditableBootstrapConfigStore bootstrapConfigStore;
 
-    public BootstrapSecurityStoreImpl(BootstrapStoreImpl bsStore) {
-        this.bsStore = bsStore;
+    /**
+     * @param bootstrapConfigStore in which we search to find how device should authenticate itself.
+     */
+    public BootstrapConfigSecurityStore(EditableBootstrapConfigStore bootstrapConfigStore) {
+        this.bootstrapConfigStore = bootstrapConfigStore;
     }
 
     @Override
     public SecurityInfo getByIdentity(String identity) {
         byte[] identityBytes = identity.getBytes(StandardCharsets.UTF_8);
-        for (Map.Entry<String, BootstrapConfig> e : bsStore.getBootstrapConfigs().entrySet()) {
+        // Acceptable for a demo but iterate over all the store to get PSK is not really acceptable for a production
+        // server.
+        for (Map.Entry<String, BootstrapConfig> e : bootstrapConfigStore.getAll().entrySet()) {
             BootstrapConfig bsConfig = e.getValue();
             if (bsConfig.security != null) {
                 for (Map.Entry<Integer, BootstrapConfig.ServerSecurity> ec : bsConfig.security.entrySet()) {
@@ -65,7 +81,7 @@ public class BootstrapSecurityStoreImpl implements BootstrapSecurityStore {
     @Override
     public List<SecurityInfo> getAllByEndpoint(String endpoint) {
 
-        BootstrapConfig bsConfig = bsStore.getBootstrap(endpoint, null);
+        BootstrapConfig bsConfig = bootstrapConfigStore.get(endpoint, null);
 
         if (bsConfig == null || bsConfig.security == null)
             return null;
diff --git a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/ConfigurationChecker.java b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/ConfigurationChecker.java
deleted file mode 100644
index e51d83e0..00000000
--- a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/ConfigurationChecker.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2014-2015 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.bootstrap.demo;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.security.KeyFactory;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.security.spec.X509EncodedKeySpec;
-import java.util.Map;
-
-import org.apache.commons.lang.ArrayUtils;
-import org.eclipse.leshan.server.bootstrap.BootstrapConfig;
-import org.eclipse.leshan.util.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Check a BootstrapConfig is correct. This is a complex process, we need to check if the different objects are in
- * coherence with each other.
- */
-public class ConfigurationChecker {
-
-    private static final Logger LOG = LoggerFactory.getLogger(ConfigurationChecker.class);
-
-    private static final String[] KEY_ALGORITHMS = new String[] { "EC", "DiffieHellman", "RSA", "DSA" };
-
-    public static void verify(BootstrapConfig config) throws ConfigurationException {
-        // check security configurations
-        for (Map.Entry<Integer, BootstrapConfig.ServerSecurity> e : config.security.entrySet()) {
-            BootstrapConfig.ServerSecurity sec = e.getValue();
-
-            // checks security config
-            switch (sec.securityMode) {
-            case NO_SEC:
-                assertIf(!ArrayUtils.isEmpty(sec.secretKey), "NO-SEC mode, secret key must be empty");
-                assertIf(!ArrayUtils.isEmpty(sec.publicKeyOrId), "NO-SEC mode, public key or ID must be empty");
-                assertIf(!ArrayUtils.isEmpty(sec.serverPublicKey), "NO-SEC mode, server public key must be empty");
-                break;
-            case PSK:
-                assertIf(ArrayUtils.isEmpty(sec.secretKey), "pre-shared-key mode, secret key must not be empty");
-                assertIf(ArrayUtils.isEmpty(sec.publicKeyOrId),
-                        "pre-shared-key mode, public key or id must not be empty");
-                break;
-            case RPK:
-                assertIf(ArrayUtils.isEmpty(sec.secretKey), "raw-public-key mode, secret key must not be empty");
-                assertIf(decodeRfc5958PrivateKey(sec.secretKey) == null,
-                        "raw-public-key mode, secret key must be RFC5958 encoded private key");
-                assertIf(ArrayUtils.isEmpty(sec.publicKeyOrId),
-                        "raw-public-key mode, public key or id must not be empty");
-                assertIf(decodeRfc7250PublicKey(sec.publicKeyOrId) == null,
-                        "raw-public-key mode, public key or id must be RFC7250 encoded public key");
-                assertIf(ArrayUtils.isEmpty(sec.serverPublicKey),
-                        "raw-public-key mode, server public key must not be empty");
-                assertIf(decodeRfc7250PublicKey(sec.serverPublicKey) == null,
-                        "raw-public-key mode, server public key must be RFC7250 encoded public key");
-                break;
-            case X509:
-                assertIf(ArrayUtils.isEmpty(sec.secretKey), "x509 mode, secret key must not be empty");
-                assertIf(decodeRfc5958PrivateKey(sec.secretKey) == null,
-                        "x509 mode, secret key must be RFC5958 encoded private key");
-                assertIf(ArrayUtils.isEmpty(sec.publicKeyOrId), "x509 mode, public key or id must not be empty");
-                assertIf(decodeCertificate(sec.publicKeyOrId) == null,
-                        "x509 mode, public key or id must be DER encoded X.509 certificate");
-                assertIf(ArrayUtils.isEmpty(sec.serverPublicKey),
-                        "x509 mode, server public key must not be empty");
-                assertIf(decodeCertificate(sec.serverPublicKey) == null,
-                        "x509 mode, server public key must be DER encoded X.509 certificate");
-                break;
-            }
-
-            // checks mandatory fields
-            if (StringUtils.isEmpty(sec.uri))
-                throw new ConfigurationException("LwM2M Server URI is mandatory");
-            if (sec.securityMode == null)
-                throw new ConfigurationException("Security Mode is mandatory");
-        }
-
-        // does each server have a corresponding security entry?
-        for (Map.Entry<Integer, BootstrapConfig.ServerConfig> e : config.servers.entrySet()) {
-            BootstrapConfig.ServerConfig srvCfg = e.getValue();
-
-            // shortId checks
-            if (srvCfg.shortId == 0) {
-                throw new ConfigurationException("short ID must not be 0");
-            }
-
-            // look for security entry
-            BootstrapConfig.ServerSecurity security = getSecurityEntry(config, srvCfg.shortId);
-
-            if (security == null) {
-                throw new ConfigurationException("no security entry for server instance: " + e.getKey());
-            }
-
-            if (security.bootstrapServer) {
-                throw new ConfigurationException("the security entry for server  " + e.getKey()
-                        + " should not be a bootstrap server");
-            }
-        }
-    }
-
-    private static PrivateKey decodeRfc5958PrivateKey(byte[] encodedKey) {
-        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encodedKey);
-        for (String algorithm : KEY_ALGORITHMS) {
-            try {
-                KeyFactory kf = KeyFactory.getInstance(algorithm);
-                return kf.generatePrivate(keySpec);
-            } catch (NoSuchAlgorithmException e) {
-                LOG.debug("Failed to instantiate key factory for algorithm " + algorithm, e);
-                continue;
-            } catch (InvalidKeySpecException e) {
-                LOG.debug("Failed to decode RFC5958 private key with algorithm " + algorithm, e);
-                continue;
-            }
-        }
-        return null;
-    }
-
-    private static PublicKey decodeRfc7250PublicKey(byte[] encodedKey) {
-        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encodedKey);
-        for (String algorithm : KEY_ALGORITHMS) {
-            try {
-                KeyFactory kf = KeyFactory.getInstance(algorithm);
-                return kf.generatePublic(keySpec);
-            } catch (NoSuchAlgorithmException e) {
-                LOG.debug("Failed to instantiate key factory for algorithm " + algorithm, e);
-                continue;
-            } catch (InvalidKeySpecException e) {
-                LOG.debug("Failed to decode RFC7250 public key with algorithm " + algorithm, e);
-                continue;
-            }
-        }
-        return null;
-    }
-
-    private static Certificate decodeCertificate(byte[] encodedCert) {
-        try {
-            CertificateFactory cf = CertificateFactory.getInstance("X.509");
-            try (ByteArrayInputStream in = new ByteArrayInputStream(encodedCert)) {
-                return cf.generateCertificate(in);
-            }
-        } catch (CertificateException | IOException e) {
-            LOG.debug("Failed to decode X.509 certificate", e);
-            return null;
-        }
-    }
-
-    private static void assertIf(boolean condition, String message) throws ConfigurationException {
-        if (condition) {
-            throw new ConfigurationException(message);
-        }
-
-    }
-
-    private static BootstrapConfig.ServerSecurity getSecurityEntry(BootstrapConfig config, int shortId) {
-        for (Map.Entry<Integer, BootstrapConfig.ServerSecurity> es : config.security.entrySet()) {
-            if (es.getValue().serverId == shortId) {
-                return es.getValue();
-            }
-        }
-        return null;
-    }
-
-    public static class ConfigurationException extends Exception {
-
-        private static final long serialVersionUID = 1L;
-
-        public ConfigurationException(String message) {
-            super(message);
-        }
-    }
-}
diff --git a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/BootstrapStoreImpl.java b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/JSONFileBootstrapStore.java
similarity index 57%
rename from leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/BootstrapStoreImpl.java
rename to leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/JSONFileBootstrapStore.java
index 6c121755..74f7b384 100644
--- a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/BootstrapStoreImpl.java
+++ b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/JSONFileBootstrapStore.java
@@ -22,14 +22,15 @@ import java.io.FileOutputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
 import java.lang.reflect.Type;
-import java.util.Collections;
 import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 
-import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.server.bootstrap.BootstrapConfig;
-import org.eclipse.leshan.server.bootstrap.BootstrapStore;
-import org.eclipse.leshan.server.bootstrap.demo.ConfigurationChecker.ConfigurationException;
+import org.eclipse.leshan.server.bootstrap.EditableBootstrapConfigStore;
+import org.eclipse.leshan.server.bootstrap.InMemoryBootstrapConfigStore;
+import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException;
 import org.eclipse.leshan.util.Validate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -39,11 +40,16 @@ import com.google.gson.GsonBuilder;
 import com.google.gson.reflect.TypeToken;
 
 /**
- * Simple bootstrap store implementation storing bootstrap information in memory
+ * A {@link EditableBootstrapConfigStore} which persist configuration in a file using json format.
  */
-public class BootstrapStoreImpl implements BootstrapStore {
+public class JSONFileBootstrapStore extends InMemoryBootstrapConfigStore {
 
-    private static final Logger LOG = LoggerFactory.getLogger(BootstrapStoreImpl.class);
+    private static final Logger LOG = LoggerFactory.getLogger(JSONFileBootstrapStore.class);
+
+    // lock for the two maps
+    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
+    private final Lock readLock = readWriteLock.readLock();
+    private final Lock writeLock = readWriteLock.writeLock();
 
     // default location for persistence
     public static final String DEFAULT_FILE = "data/bootstrap.json";
@@ -52,14 +58,14 @@ public class BootstrapStoreImpl implements BootstrapStore {
     private final Gson gson;
     private final Type gsonType;
 
-    public BootstrapStoreImpl() {
+    public JSONFileBootstrapStore() {
         this(DEFAULT_FILE);
     }
 
     /**
      * @param filename the file path to persist the registry
      */
-    public BootstrapStoreImpl(String filename) {
+    public JSONFileBootstrapStore(String filename) {
         Validate.notEmpty(filename);
         GsonBuilder builder = new GsonBuilder();
         builder.setPrettyPrinting();
@@ -70,38 +76,54 @@ public class BootstrapStoreImpl implements BootstrapStore {
         this.loadFromFile();
     }
 
-    private Map<String, BootstrapConfig> bootstrapByEndpoint = new ConcurrentHashMap<>();
-
     @Override
-    public BootstrapConfig getBootstrap(String endpoint, Identity deviceIdentity) {
-        return bootstrapByEndpoint.get(endpoint);
+    public Map<String, BootstrapConfig> getAll() {
+        readLock.lock();
+        try {
+            return super.getAll();
+        } finally {
+            readLock.unlock();
+        }
     }
 
-    public void addConfig(String endpoint, BootstrapConfig config) throws ConfigurationException {
-        ConfigurationChecker.verify(config);
-        bootstrapByEndpoint.put(endpoint, config);
-        saveToFile();
+    public void addToStore(String endpoint, BootstrapConfig config) throws InvalidConfigurationException {
+        super.add(endpoint, config);
     }
 
-    public Map<String, BootstrapConfig> getBootstrapConfigs() {
-        return Collections.unmodifiableMap(bootstrapByEndpoint);
+    @Override
+    public void add(String endpoint, BootstrapConfig config) throws InvalidConfigurationException {
+        writeLock.lock();
+        try {
+            addToStore(endpoint, config);
+            saveToFile();
+        } finally {
+            writeLock.unlock();
+        }
     }
 
-    public boolean deleteConfig(String enpoint) {
-        BootstrapConfig res = bootstrapByEndpoint.remove(enpoint);
-        saveToFile();
-        return res != null;
+    @Override
+    public BootstrapConfig remove(String enpoint) {
+        writeLock.lock();
+        try {
+            BootstrapConfig res = super.remove(enpoint);
+            saveToFile();
+            return res;
+        } finally {
+            writeLock.unlock();
+        }
     }
 
     // /////// File persistence
-
     private void loadFromFile() {
         try {
             File file = new File(filename);
             if (file.exists()) {
                 try (InputStreamReader in = new InputStreamReader(new FileInputStream(file))) {
-                    Map<String, BootstrapConfig> config = gson.fromJson(in, gsonType);
-                    bootstrapByEndpoint.putAll(config);
+                    Map<String, BootstrapConfig> configs = gson.fromJson(in, gsonType);
+                    for (Map.Entry<String, BootstrapConfig> config : configs.entrySet()) {
+                        addToStore(config.getKey(), config.getValue());
+                    }
+
                 }
             }
         } catch (Exception e) {
@@ -109,7 +131,7 @@ public class BootstrapStoreImpl implements BootstrapStore {
         }
     }
 
-    private synchronized void saveToFile() {
+    private void saveToFile() {
         try {
             // Create file if it does not exists.
             File file = new File(filename);
@@ -123,7 +145,7 @@ public class BootstrapStoreImpl implements BootstrapStore {
 
             // Write file
             try (OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(filename))) {
-                out.write(gson.toJson(getBootstrapConfigs(), gsonType));
+                out.write(gson.toJson(getAll(), gsonType));
             }
         } catch (Exception e) {
             LOG.error("Could not save bootstrap infos to file", e);
diff --git a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/LeshanBootstrapServerDemo.java b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/LeshanBootstrapServerDemo.java
index bea3c55d..ceb1aa34 100755
--- a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/LeshanBootstrapServerDemo.java
+++ b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/LeshanBootstrapServerDemo.java
@@ -82,7 +82,7 @@ public class LeshanBootstrapServerDemo {
         options.addOption("wp", "webport", true, "Set the HTTP port for web server.\nDefault: 8080.");
         options.addOption("m", "modelsfolder", true, "A folder which contains object models in OMA DDF(.xml) format.");
         options.addOption("cfg", "configfile", true,
-                "Set the filename for the configuration.\nDefault: " + BootstrapStoreImpl.DEFAULT_FILE + ".");
+                "Set the filename for the configuration.\nDefault: " + JSONFileBootstrapStore.DEFAULT_FILE + ".");
         HelpFormatter formatter = new HelpFormatter();
         formatter.setOptionComparator(null);
 
@@ -143,7 +143,7 @@ public class LeshanBootstrapServerDemo {
         // Get config file
         String configFilename = cl.getOptionValue("cfg");
         if (configFilename == null) {
-            configFilename = BootstrapStoreImpl.DEFAULT_FILE;
+            configFilename = JSONFileBootstrapStore.DEFAULT_FILE;
         }
 
         try {
@@ -169,9 +169,9 @@ public class LeshanBootstrapServerDemo {
 
         // Prepare and start bootstrap server
         LeshanBootstrapServerBuilder builder = new LeshanBootstrapServerBuilder();
-        BootstrapStoreImpl bsStore = new BootstrapStoreImpl(configFilename);
+        JSONFileBootstrapStore bsStore = new JSONFileBootstrapStore(configFilename);
         builder.setConfigStore(bsStore);
-        builder.setSecurityStore(new BootstrapSecurityStoreImpl(bsStore));
+        builder.setSecurityStore(new BootstrapConfigSecurityStore(bsStore));
         builder.setLocalAddress(localAddress, localPort);
         builder.setLocalSecureAddress(secureLocalAddress, secureLocalPort);
         builder.setModel(new LwM2mModel(models));
diff --git a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/servlet/BootstrapServlet.java b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/servlet/BootstrapServlet.java
index 3c1a3bfb..504b0a65 100644
--- a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/servlet/BootstrapServlet.java
+++ b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/servlet/BootstrapServlet.java
@@ -28,8 +28,8 @@ import javax.servlet.http.HttpServletResponse;
 
 import org.apache.commons.lang.StringUtils;
 import org.eclipse.leshan.server.bootstrap.BootstrapConfig;
-import org.eclipse.leshan.server.bootstrap.demo.BootstrapStoreImpl;
-import org.eclipse.leshan.server.bootstrap.demo.ConfigurationChecker.ConfigurationException;
+import org.eclipse.leshan.server.bootstrap.EditableBootstrapConfigStore;
+import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException;
 
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
@@ -63,11 +63,11 @@ public class BootstrapServlet extends HttpServlet {
         }
     }
 
-    private final BootstrapStoreImpl bsStore;
+    private final EditableBootstrapConfigStore bsStore;
 
     private final Gson gson;
 
-    public BootstrapServlet(BootstrapStoreImpl bsStore) {
+    public BootstrapServlet(EditableBootstrapConfigStore bsStore) {
         this.bsStore = bsStore;
 
         this.gson = new GsonBuilder().registerTypeHierarchyAdapter(Byte.class, new SignedByteUnsignedByteAdapter())
@@ -83,7 +83,7 @@ public class BootstrapServlet extends HttpServlet {
 
         resp.setStatus(HttpServletResponse.SC_OK);
         resp.setContentType("application/json");
-        resp.getOutputStream().write(gson.toJson(bsStore.getBootstrapConfigs()).getBytes(StandardCharsets.UTF_8));
+        resp.getOutputStream().write(gson.toJson(bsStore.getAll()).getBytes(StandardCharsets.UTF_8));
     }
 
     @Override
@@ -111,10 +111,10 @@ public class BootstrapServlet extends HttpServlet {
             if (cfg == null) {
                 sendError(resp, HttpServletResponse.SC_BAD_REQUEST, "no content");
             } else {
-                bsStore.addConfig(endpoint, cfg);
+                bsStore.add(endpoint, cfg);
                 resp.setStatus(HttpServletResponse.SC_OK);
             }
-        } catch (JsonSyntaxException | ConfigurationException e) {
+        } catch (JsonSyntaxException | InvalidConfigurationException e) {
             sendError(resp, HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
         }
     }
@@ -138,7 +138,7 @@ public class BootstrapServlet extends HttpServlet {
 
         String endpoint = path[0];
 
-        if (bsStore.deleteConfig(endpoint)) {
+        if (bsStore.remove(endpoint) == null) {
             resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
         } else {
             sendError(resp, HttpServletResponse.SC_NOT_FOUND, "no config for " + endpoint);
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanBootstrapServerBuilder.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanBootstrapServerBuilder.java
index 0d641dd6..9f6753cb 100644
--- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanBootstrapServerBuilder.java
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanBootstrapServerBuilder.java
@@ -36,11 +36,12 @@ import org.eclipse.leshan.core.californium.Lwm2mEndpointContextMatcher;
 import org.eclipse.leshan.core.model.LwM2mModel;
 import org.eclipse.leshan.core.model.ObjectLoader;
 import org.eclipse.leshan.server.bootstrap.BootstrapConfig;
+import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore;
 import org.eclipse.leshan.server.bootstrap.BootstrapHandler;
 import org.eclipse.leshan.server.bootstrap.BootstrapHandlerFactory;
 import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager;
-import org.eclipse.leshan.server.bootstrap.BootstrapStore;
 import org.eclipse.leshan.server.bootstrap.DefaultBootstrapHandler;
+import org.eclipse.leshan.server.bootstrap.InMemoryBootstrapConfigStore;
 import org.eclipse.leshan.server.bootstrap.LwM2mBootstrapRequestSender;
 import org.eclipse.leshan.server.californium.impl.LeshanBootstrapServer;
 import org.eclipse.leshan.server.californium.impl.LwM2mBootstrapPskStore;
@@ -61,7 +62,7 @@ public class LeshanBootstrapServerBuilder {
 
     private InetSocketAddress localAddress;
     private InetSocketAddress localAddressSecure;
-    private BootstrapStore configStore;
+    private BootstrapConfigStore configStore;
     private BootstrapSecurityStore securityStore;
     private BootstrapSessionManager sessionManager;
     private BootstrapHandlerFactory bootstrapHandlerFactory;
@@ -208,16 +209,16 @@ public class LeshanBootstrapServerBuilder {
     }
 
     /**
-     * Set the {@link BootstrapStore} containing bootstrap configuration to apply to each devices.
+     * Set the {@link BootstrapConfigStore} containing bootstrap configuration to apply to each devices.
      * <p>
-     * WARNING: There is not default implementation and this store is mandatory to create a bootstrap server.
+     * By default an {@link InMemoryBootstrapConfigStore} is used.
      * <p>
      * See {@link BootstrapConfig} to see what is could be done during a bootstrap session.
      * 
      * @param configStore the bootstrap configuration store.
      * @return the builder for fluent Bootstrap Server creation.
      */
-    public LeshanBootstrapServerBuilder setConfigStore(BootstrapStore configStore) {
+    public LeshanBootstrapServerBuilder setConfigStore(BootstrapConfigStore configStore) {
         this.configStore = configStore;
         return this;
     }
@@ -255,9 +256,9 @@ public class LeshanBootstrapServerBuilder {
      * Advanced setter used to customize default bootstrap server behavior.
      * <p>
      * By default Bootstrap Server is only able to write Security, Server and ACL objects, see
-     * {@link #setConfigStore(BootstrapStore)}. If you need more advanced behavior you can create your own
+     * {@link #setConfigStore(BootstrapConfigStore)}. If you need more advanced behavior you can create your own
      * {@link BootstrapHandler} by inspiring yourself from {@link DefaultBootstrapHandler}. You will probably need to
-     * create a custom {@link BootstrapConfig} and {@link BootstrapStore} and/or change LWM2M model to use, see
+     * create a custom {@link BootstrapConfig} and {@link BootstrapConfigStore} and/or change LWM2M model to use, see
      * {@link #setModel(LwM2mModel)}.
      * 
      * @param bootstrapHandlerFactory the factory used to create {@link BootstrapHandler}.
@@ -273,12 +274,12 @@ public class LeshanBootstrapServerBuilder {
      * Bootstrap write request.
      * <p>
      * By default, LWM2M object models defined in LWM2M v1.0.x are used. Out of the box, Bootstrap Server is only able
-     * to write Security, Server and ACL objects, see {@link #setConfigStore(BootstrapStore)}.
+     * to write Security, Server and ACL objects, see {@link #setConfigStore(BootstrapConfigStore)}.
      * <p>
      * Set a different LWM2M model if you want to use a different model version of Security, Server and ACL objects, or
      * if you need to write objects which are not available by default. For the second case, you need to change
      * {@link BootstrapHandler} behavior as well, using {@link #setBootstrapHandlerFactory(BootstrapHandlerFactory)} and
-     * probably create a custom {@link BootstrapConfig} and {@link BootstrapStore}.
+     * probably create a custom {@link BootstrapConfig} and {@link BootstrapConfigStore}.
      * <p>
      * WARNING: Only 1 version by object is supported for now.
      * 
@@ -376,14 +377,14 @@ public class LeshanBootstrapServerBuilder {
 
         // TODO we should have default implementation for BootstrapStore in leshan.server project.
         if (configStore == null)
-            throw new IllegalStateException("BootstrapStore is mandatory");
+            configStore = new InMemoryBootstrapConfigStore();
 
         if (sessionManager == null)
             sessionManager = new DefaultBootstrapSessionManager(securityStore);
         if (bootstrapHandlerFactory == null)
             bootstrapHandlerFactory = new BootstrapHandlerFactory() {
                 @Override
-                public BootstrapHandler create(BootstrapStore store, LwM2mBootstrapRequestSender sender,
+                public BootstrapHandler create(BootstrapConfigStore store, LwM2mBootstrapRequestSender sender,
                         BootstrapSessionManager sessionManager) {
                     return new DefaultBootstrapHandler(store, sender, sessionManager);
                 }
@@ -396,18 +397,19 @@ public class LeshanBootstrapServerBuilder {
 
         // handle dtlsConfig
         DtlsConnectorConfig dtlsConfig = null;
-        if (!noSecuredEndpoint) {
+        if (!noSecuredEndpoint && shouldTryToCreateSecureEndpoint()) {
             if (dtlsConfigBuilder == null) {
                 dtlsConfigBuilder = new DtlsConnectorConfig.Builder();
             }
+            // Set default DTLS setting for Leshan unless user change it.
             DtlsConnectorConfig incompleteConfig = dtlsConfigBuilder.getIncompleteConfig();
 
             // Handle PSK Store
-            if (incompleteConfig.getPskStore() == null && securityStore != null) {
-                dtlsConfigBuilder.setPskStore(new LwM2mBootstrapPskStore(securityStore));
-            } else {
+            if (incompleteConfig.getPskStore() != null) {
                 LOG.warn(
                         "PskStore should be automatically set by Leshan. Using a custom implementation is not advised.");
+            } else if (securityStore != null) {
+                dtlsConfigBuilder.setPskStore(new LwM2mBootstrapPskStore(securityStore));
             }
 
             // Handle secure address
@@ -537,6 +539,14 @@ public class LeshanBootstrapServerBuilder {
                 bootstrapHandlerFactory, model, coapConfig);
     }
 
+    /**
+     * @return true if we should try to create a secure endpoint on {@link #build()}
+     */
+    protected boolean shouldTryToCreateSecureEndpoint() {
+        return dtlsConfigBuilder != null || certificateChain != null || privateKey != null || publicKey != null
+                || securityStore != null || trustedCertificates != null;
+    }
+
     /**
      * Create the <code>LeshanBootstrapServer</code>.
      * <p>
@@ -554,8 +564,9 @@ public class LeshanBootstrapServerBuilder {
      * @return the LWM2M Bootstrap server.
      */
     protected LeshanBootstrapServer createBootstrapServer(CoapEndpoint unsecuredEndpoint, CoapEndpoint securedEndpoint,
-            BootstrapStore bsStore, BootstrapSecurityStore bsSecurityStore, BootstrapSessionManager bsSessionManager,
-            BootstrapHandlerFactory bsHandlerFactory, LwM2mModel model, NetworkConfig coapConfig) {
+            BootstrapConfigStore bsStore, BootstrapSecurityStore bsSecurityStore,
+            BootstrapSessionManager bsSessionManager, BootstrapHandlerFactory bsHandlerFactory, LwM2mModel model,
+            NetworkConfig coapConfig) {
         return new LeshanBootstrapServer(unsecuredEndpoint, securedEndpoint, bsStore, bsSecurityStore, bsSessionManager,
                 bsHandlerFactory, model, coapConfig);
     }
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanServerBuilder.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanServerBuilder.java
index 813ef492..b1771d4c 100644
--- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanServerBuilder.java
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanServerBuilder.java
@@ -379,18 +379,19 @@ public class LeshanServerBuilder {
 
         // handle dtlsConfig
         DtlsConnectorConfig dtlsConfig = null;
-        if (!noSecuredEndpoint) {
+        if (!noSecuredEndpoint && shouldTryToCreateSecureEndpoint()) {
             if (dtlsConfigBuilder == null) {
                 dtlsConfigBuilder = new DtlsConnectorConfig.Builder();
             }
-            // set default DTLS setting for Leshan unless user change it.
+            // Set default DTLS setting for Leshan unless user change it.
             DtlsConnectorConfig incompleteConfig = dtlsConfigBuilder.getIncompleteConfig();
+
             // Handle PSK Store
-            if (incompleteConfig.getPskStore() == null && securityStore != null) {
-                dtlsConfigBuilder.setPskStore(new LwM2mPskStore(this.securityStore, registrationStore));
-            } else {
+            if (incompleteConfig.getPskStore() != null) {
                 LOG.warn(
                         "PskStore should be automatically set by Leshan. Using a custom implementation is not advised.");
+            } else if (securityStore != null) {
+                dtlsConfigBuilder.setPskStore(new LwM2mPskStore(this.securityStore, registrationStore));
             }
 
             // Handle secure address
@@ -516,4 +517,12 @@ public class LeshanServerBuilder {
         return new LeshanServer(unsecuredEndpoint, securedEndpoint, registrationStore, securityStore, authorizer,
                 modelProvider, encoder, decoder, coapConfig, noQueueMode, awakeTimeProvider, registrationIdProvider);
     }
+
+    /**
+     * @return true if we should try to create a secure endpoint on {@link #build()}
+     */
+    protected boolean shouldTryToCreateSecureEndpoint() {
+        return dtlsConfigBuilder != null || certificateChain != null || privateKey != null || publicKey != null
+                || securityStore != null || trustedCertificates != null;
+    }
 }
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/LeshanBootstrapServer.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/LeshanBootstrapServer.java
index 2040af52..e61d4c3e 100644
--- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/LeshanBootstrapServer.java
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/LeshanBootstrapServer.java
@@ -26,7 +26,7 @@ import org.eclipse.leshan.core.model.LwM2mModel;
 import org.eclipse.leshan.server.bootstrap.BootstrapHandler;
 import org.eclipse.leshan.server.bootstrap.BootstrapHandlerFactory;
 import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager;
-import org.eclipse.leshan.server.bootstrap.BootstrapStore;
+import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore;
 import org.eclipse.leshan.server.bootstrap.LwM2mBootstrapRequestSender;
 import org.eclipse.leshan.server.bootstrap.LwM2mBootstrapServer;
 import org.eclipse.leshan.server.security.BootstrapSecurityStore;
@@ -48,10 +48,10 @@ public class LeshanBootstrapServer implements LwM2mBootstrapServer {
     private final CoapEndpoint securedEndpoint;
 
     // LWM2M attributes
-    private final BootstrapStore bsStore;
+    private final BootstrapConfigStore bsStore;
     private final BootstrapSecurityStore bsSecurityStore;
 
-    public LeshanBootstrapServer(CoapEndpoint unsecuredEndpoint, CoapEndpoint securedEndpoint, BootstrapStore bsStore,
+    public LeshanBootstrapServer(CoapEndpoint unsecuredEndpoint, CoapEndpoint securedEndpoint, BootstrapConfigStore bsStore,
             BootstrapSecurityStore bsSecurityStore, BootstrapSessionManager bsSessionManager,
             BootstrapHandlerFactory bsHandlerFactory, LwM2mModel model, NetworkConfig coapConfig) {
 
@@ -104,7 +104,7 @@ public class LeshanBootstrapServer implements LwM2mBootstrapServer {
     }
 
     @Override
-    public BootstrapStore getBoostrapStore() {
+    public BootstrapConfigStore getBoostrapStore() {
         return bsStore;
     }
 
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfig.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfig.java
index b7bc8826..8195a547 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfig.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfig.java
@@ -36,7 +36,7 @@ import org.eclipse.leshan.core.request.BindingMode;
  * sent.
  * <li>a Map from instanceId to Instance value for ACL object. For each entry a Bootstrap Write Request will be sent.
  * 
- * @see BootstrapStore
+ * @see BootstrapConfigStore
  * @see DefaultBootstrapHandler
  */
 public class BootstrapConfig implements Serializable {
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapStore.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfigStore.java
similarity index 91%
rename from leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapStore.java
rename to leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfigStore.java
index 164da196..57a011c6 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapStore.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfigStore.java
@@ -20,7 +20,7 @@ import org.eclipse.leshan.core.request.Identity;
 /**
  * A store containing the bootstrap information to be sent to the devices.
  */
-public interface BootstrapStore {
+public interface BootstrapConfigStore {
 
     /**
      * Get the bootstrap configuration to apply to the device identified by the given parameters.
@@ -29,5 +29,5 @@ public interface BootstrapStore {
      * @param deviceIdentity the {@link Identity} the device.
      * @return the {@link BootstrapConfig} to apply.
      */
-    BootstrapConfig getBootstrap(String endpoint, Identity deviceIdentity);
+    BootstrapConfig get(String endpoint, Identity deviceIdentity);
 }
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerFactory.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerFactory.java
index ed8d6183..67e004ee 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerFactory.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerFactory.java
@@ -30,6 +30,6 @@ public interface BootstrapHandlerFactory {
      * @param sessionManager the manager responsible to handle bootstrap session.
      * @return the new {@link BootstrapHandler}.
      */
-    BootstrapHandler create(BootstrapStore store, LwM2mBootstrapRequestSender sender,
+    BootstrapHandler create(BootstrapConfigStore store, LwM2mBootstrapRequestSender sender,
             BootstrapSessionManager sessionManager);
 }
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/ConfigurationChecker.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/ConfigurationChecker.java
new file mode 100644
index 00000000..fc3c449c
--- /dev/null
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/ConfigurationChecker.java
@@ -0,0 +1,241 @@
+/*******************************************************************************
+ * Copyright (c) 2014-2015 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.bootstrap;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Map;
+
+import org.eclipse.leshan.server.bootstrap.BootstrapConfig.ServerSecurity;
+import org.eclipse.leshan.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Check a BootstrapConfig is correct. This is a complex process, we need to check if the different objects are in
+ * coherence with each other.
+ */
+public class ConfigurationChecker {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ConfigurationChecker.class);
+
+    protected final String[] algorithms;
+
+    /**
+     * Create a new configuration checker supporting "EC", "DiffieHellman", "RSA", "DSA" algorithm for Public and
+     * Private keys
+     */
+    public ConfigurationChecker() {
+        this(new String[] { "EC", "DiffieHellman", "RSA", "DSA" });
+    }
+
+    /**
+     * Create a new configuration checker supporting given algorithms.
+     * 
+     * @param algorithms an array of supported algorithm name. (see {@link KeyFactory#getInstance(String))}
+     */
+    public ConfigurationChecker(String[] algorithms) {
+        this.algorithms = algorithms;
+    }
+
+    /**
+     * Verify if the {@link BootstrapConfig} is valid and consistent.
+     * <p>
+     * Raise a {@link InvalidConfigurationException} if config is not OK.
+     * 
+     * @param config the bootstrap configuration to check.
+     * @throws InvalidConfigurationException if bootstrap configuration is not invalid.
+     */
+    public void verify(BootstrapConfig config) throws InvalidConfigurationException {
+        // check security configurations
+        for (Map.Entry<Integer, BootstrapConfig.ServerSecurity> e : config.security.entrySet()) {
+            BootstrapConfig.ServerSecurity sec = e.getValue();
+
+            // checks security config
+            switch (sec.securityMode) {
+            case NO_SEC:
+                checkNoSec(sec);
+                break;
+            case PSK:
+                checkPSK(sec);
+                break;
+            case RPK:
+                checkRPK(sec);
+                break;
+            case X509:
+                checkX509(sec);
+                break;
+            }
+
+            validateMandatoryField(sec);
+        }
+
+        // does each server have a corresponding security entry?
+        validateOneSecurityByServer(config);
+    }
+
+    protected void checkNoSec(ServerSecurity sec) throws InvalidConfigurationException {
+        assertIf(!isEmpty(sec.secretKey), "NO-SEC mode, secret key must be empty");
+        assertIf(!isEmpty(sec.publicKeyOrId), "NO-SEC mode, public key or ID must be empty");
+        assertIf(!isEmpty(sec.serverPublicKey), "NO-SEC mode, server public key must be empty");
+    }
+
+    protected void checkPSK(ServerSecurity sec) throws InvalidConfigurationException {
+        assertIf(isEmpty(sec.secretKey), "pre-shared-key mode, secret key must not be empty");
+        assertIf(isEmpty(sec.publicKeyOrId), "pre-shared-key mode, public key or id must not be empty");
+    }
+
+    protected void checkRPK(ServerSecurity sec) throws InvalidConfigurationException {
+        assertIf(isEmpty(sec.secretKey), "raw-public-key mode, secret key must not be empty");
+        assertIf(decodeRfc5958PrivateKey(sec.secretKey) == null,
+                "raw-public-key mode, secret key must be RFC5958 encoded private key");
+        assertIf(isEmpty(sec.publicKeyOrId), "raw-public-key mode, public key or id must not be empty");
+        assertIf(decodeRfc7250PublicKey(sec.publicKeyOrId) == null,
+                "raw-public-key mode, public key or id must be RFC7250 encoded public key");
+        assertIf(isEmpty(sec.serverPublicKey), "raw-public-key mode, server public key must not be empty");
+        assertIf(decodeRfc7250PublicKey(sec.serverPublicKey) == null,
+                "raw-public-key mode, server public key must be RFC7250 encoded public key");
+    }
+
+    protected void checkX509(ServerSecurity sec) throws InvalidConfigurationException {
+        assertIf(isEmpty(sec.secretKey), "x509 mode, secret key must not be empty");
+        assertIf(decodeRfc5958PrivateKey(sec.secretKey) == null,
+                "x509 mode, secret key must be RFC5958 encoded private key");
+        assertIf(isEmpty(sec.publicKeyOrId), "x509 mode, public key or id must not be empty");
+        assertIf(decodeCertificate(sec.publicKeyOrId) == null,
+                "x509 mode, public key or id must be DER encoded X.509 certificate");
+        assertIf(isEmpty(sec.serverPublicKey), "x509 mode, server public key must not be empty");
+        assertIf(decodeCertificate(sec.serverPublicKey) == null,
+                "x509 mode, server public key must be DER encoded X.509 certificate");
+    }
+
+    protected void validateMandatoryField(ServerSecurity sec) throws InvalidConfigurationException {
+        // checks mandatory fields
+        if (StringUtils.isEmpty(sec.uri))
+            throw new InvalidConfigurationException("LwM2M Server URI is mandatory");
+        if (sec.securityMode == null)
+            throw new InvalidConfigurationException("Security Mode is mandatory");
+
+    }
+
+    /**
+     * Each server entry must have 1 security entry.
+     * 
+     * @param config the bootstrap configuration to check.
+     * @throws InvalidConfigurationException if bootstrap configuration is not invalid.
+     */
+    protected void validateOneSecurityByServer(BootstrapConfig config) throws InvalidConfigurationException {
+        for (Map.Entry<Integer, BootstrapConfig.ServerConfig> e : config.servers.entrySet()) {
+            BootstrapConfig.ServerConfig srvCfg = e.getValue();
+
+            // shortId checks
+            if (srvCfg.shortId == 0) {
+                throw new InvalidConfigurationException("short ID must not be 0");
+            }
+
+            // look for security entry
+            BootstrapConfig.ServerSecurity security = getSecurityEntry(config, srvCfg.shortId);
+
+            if (security == null) {
+                throw new InvalidConfigurationException("no security entry for server instance: " + e.getKey());
+            }
+
+            if (security.bootstrapServer) {
+                throw new InvalidConfigurationException(
+                        "the security entry for server  " + e.getKey() + " should not be a bootstrap server");
+            }
+        }
+    }
+
+    // TODO should we reuse org.eclipse.leshan.util.SecurityUtil ?
+    protected PrivateKey decodeRfc5958PrivateKey(byte[] encodedKey) {
+        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encodedKey);
+        for (String algorithm : algorithms) {
+            try {
+                KeyFactory kf = KeyFactory.getInstance(algorithm);
+                return kf.generatePrivate(keySpec);
+            } catch (NoSuchAlgorithmException e) {
+                LOG.debug("Failed to instantiate key factory for algorithm " + algorithm, e);
+                continue;
+            } catch (InvalidKeySpecException e) {
+                LOG.debug("Failed to decode RFC5958 private key with algorithm " + algorithm, e);
+                continue;
+            }
+        }
+        return null;
+    }
+
+    // TODO should we reuse org.eclipse.leshan.util.SecurityUtil ?
+    protected PublicKey decodeRfc7250PublicKey(byte[] encodedKey) {
+        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encodedKey);
+        for (String algorithm : algorithms) {
+            try {
+                KeyFactory kf = KeyFactory.getInstance(algorithm);
+                return kf.generatePublic(keySpec);
+            } catch (NoSuchAlgorithmException e) {
+                LOG.debug("Failed to instantiate key factory for algorithm " + algorithm, e);
+                continue;
+            } catch (InvalidKeySpecException e) {
+                LOG.debug("Failed to decode RFC7250 public key with algorithm " + algorithm, e);
+                continue;
+            }
+        }
+        return null;
+    }
+
+    // TODO should we reuse org.eclipse.leshan.util.SecurityUtil ?
+    protected Certificate decodeCertificate(byte[] encodedCert) {
+        try {
+            CertificateFactory cf = CertificateFactory.getInstance("X.509");
+            try (ByteArrayInputStream in = new ByteArrayInputStream(encodedCert)) {
+                return cf.generateCertificate(in);
+            }
+        } catch (CertificateException | IOException e) {
+            LOG.debug("Failed to decode X.509 certificate", e);
+            return null;
+        }
+    }
+
+    protected static void assertIf(boolean condition, String message) throws InvalidConfigurationException {
+        if (condition) {
+            throw new InvalidConfigurationException(message);
+        }
+
+    }
+
+    protected static boolean isEmpty(byte[] array) {
+        return array == null || array.length == 0;
+    }
+
+    protected static BootstrapConfig.ServerSecurity getSecurityEntry(BootstrapConfig config, int shortId) {
+        for (Map.Entry<Integer, BootstrapConfig.ServerSecurity> es : config.security.entrySet()) {
+            if (es.getValue().serverId == shortId) {
+                return es.getValue();
+            }
+        }
+        return null;
+    }
+}
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapHandler.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapHandler.java
index 5e9bb69c..a1761eb3 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapHandler.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapHandler.java
@@ -58,22 +58,22 @@ public class DefaultBootstrapHandler implements BootstrapHandler {
 
     protected final Executor e;
 
-    protected final BootstrapStore store;
+    protected final BootstrapConfigStore store;
     protected final LwM2mBootstrapRequestSender sender;
     protected final BootstrapSessionManager sessionManager;
     protected final long requestTimeout;
 
-    public DefaultBootstrapHandler(BootstrapStore store, LwM2mBootstrapRequestSender sender,
+    public DefaultBootstrapHandler(BootstrapConfigStore store, LwM2mBootstrapRequestSender sender,
             BootstrapSessionManager sessionManager) {
         this(store, sender, sessionManager, Executors.newFixedThreadPool(5), DEFAULT_TIMEOUT);
     }
 
-    public DefaultBootstrapHandler(BootstrapStore store, LwM2mBootstrapRequestSender sender,
+    public DefaultBootstrapHandler(BootstrapConfigStore store, LwM2mBootstrapRequestSender sender,
             BootstrapSessionManager sessionManager, Executor executor) {
         this(store, sender, sessionManager, executor, DEFAULT_TIMEOUT);
     }
 
-    public DefaultBootstrapHandler(BootstrapStore store, LwM2mBootstrapRequestSender sender,
+    public DefaultBootstrapHandler(BootstrapConfigStore store, LwM2mBootstrapRequestSender sender,
             BootstrapSessionManager sessionManager, Executor executor, long requestTimeout) {
         this.store = store;
         this.sender = sender;
@@ -95,7 +95,7 @@ public class DefaultBootstrapHandler implements BootstrapHandler {
         }
 
         // Get the desired bootstrap config for the endpoint
-        final BootstrapConfig cfg = store.getBootstrap(endpoint, sender);
+        final BootstrapConfig cfg = store.get(endpoint, sender);
         if (cfg == null) {
             LOG.debug("No bootstrap config for {}/{}", endpoint, sender);
             this.sessionManager.failed(session, NO_BOOTSTRAP_CONFIG, null);
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/EditableBootstrapConfigStore.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/EditableBootstrapConfigStore.java
new file mode 100644
index 00000000..16456287
--- /dev/null
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/EditableBootstrapConfigStore.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2013-2015 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.bootstrap;
+
+import java.util.Map;
+
+/**
+ * An editable {@link BootstrapConfigStore}.
+ */
+public interface EditableBootstrapConfigStore extends BootstrapConfigStore {
+
+    /**
+     * Returns all {@link BootstrapConfig} for all end-points.
+     * 
+     * @return an unmodifiable Map of {@link BootstrapConfig} indexed by endpoint name.
+     */
+    Map<String, BootstrapConfig> getAll();
+
+    /**
+     * Add a new bootstrap configuration for a client end-point.
+     * 
+     * @param endpoint The client endpoint name to which we want to set the given bootstrap configuration.
+     * @param config The configuration to apply for the given client.
+     * @throws InvalidConfigurationException if {@link BootstrapConfig} is not valid.
+     */
+    void add(String endpoint, BootstrapConfig config) throws InvalidConfigurationException;
+
+    /**
+     * Removes the bootstrap Configuration for a given end-point.
+     * 
+     * @param endpoint The client endpoint to which we want to remove the configuration.
+     * @return the removed {@link BootstrapConfig} or <code>null</code> if no bootstrap configuration for this client.
+     */
+    BootstrapConfig remove(String enpoint);
+}
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/InMemoryBootstrapConfigStore.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/InMemoryBootstrapConfigStore.java
new file mode 100644
index 00000000..6d853391
--- /dev/null
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/InMemoryBootstrapConfigStore.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2013-2015 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.bootstrap;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.leshan.core.request.Identity;
+
+/**
+ * Simple bootstrap store implementation storing bootstrap configuration information in memory.
+ */
+public class InMemoryBootstrapConfigStore implements EditableBootstrapConfigStore {
+
+    protected final ConfigurationChecker configChecker = new ConfigurationChecker();
+    protected final Map<String, BootstrapConfig> bootstrapByEndpoint = new ConcurrentHashMap<>();
+
+    @Override
+    public BootstrapConfig get(String endpoint, Identity deviceIdentity) {
+        return bootstrapByEndpoint.get(endpoint);
+    }
+
+    @Override
+    public void add(String endpoint, BootstrapConfig config) throws InvalidConfigurationException {
+        checkConfig(endpoint, config);
+        bootstrapByEndpoint.put(endpoint, config);
+    }
+
+    protected void checkConfig(String endpoint, BootstrapConfig config) throws InvalidConfigurationException {
+        configChecker.verify(config);
+    }
+
+    @Override
+    public BootstrapConfig remove(String enpoint) {
+        return bootstrapByEndpoint.remove(enpoint);
+
+    }
+
+    @Override
+    public Map<String, BootstrapConfig> getAll() {
+        return Collections.unmodifiableMap(bootstrapByEndpoint);
+    }
+}
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/InvalidConfigurationException.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/InvalidConfigurationException.java
new file mode 100644
index 00000000..db284502
--- /dev/null
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/InvalidConfigurationException.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2019 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.bootstrap;
+
+/**
+ * Exception raised when {@link BootstrapConfig} is invalid.
+ */
+public class InvalidConfigurationException extends Exception {
+
+    private static final long serialVersionUID = 1L;
+
+    public InvalidConfigurationException() {
+    }
+
+    public InvalidConfigurationException(String m) {
+        super(m);
+    }
+
+    public InvalidConfigurationException(String m, Object... args) {
+        super(String.format(m, args));
+    }
+
+    public InvalidConfigurationException(Throwable e) {
+        super(e);
+    }
+
+    public InvalidConfigurationException(String m, Throwable e) {
+        super(m, e);
+    }
+
+    public InvalidConfigurationException(Throwable e, String m, Object... args) {
+        super(String.format(m, args), e);
+    }
+}
\ No newline at end of file
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/LwM2mBootstrapServer.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/LwM2mBootstrapServer.java
index 72336be0..af9c92d8 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/LwM2mBootstrapServer.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/LwM2mBootstrapServer.java
@@ -30,7 +30,7 @@ public interface LwM2mBootstrapServer {
      * 
      * @return the store containing configuration to apply to each devices.
      */
-    BootstrapStore getBoostrapStore();
+    BootstrapConfigStore getBoostrapStore();
 
     /**
      * security store used for DTLS authentication on the bootstrap resource.


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 170808bd..561554b6 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
@@ -47,7 +47,7 @@ import org.eclipse.leshan.server.bootstrap.BootstrapConfig;
 import org.eclipse.leshan.server.bootstrap.BootstrapConfig.ACLConfig;
 import org.eclipse.leshan.server.bootstrap.BootstrapConfig.ServerConfig;
 import org.eclipse.leshan.server.bootstrap.BootstrapConfig.ServerSecurity;
-import org.eclipse.leshan.server.bootstrap.BootstrapStore;
+import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore;
 import org.eclipse.leshan.server.californium.LeshanBootstrapServerBuilder;
 import org.eclipse.leshan.server.californium.impl.LeshanBootstrapServer;
 import org.eclipse.leshan.server.security.BootstrapSecurityStore;
@@ -96,7 +96,7 @@ public class BootstrapIntegrationTestHelper extends SecureIntegrationTestHelper
         }
     }
 
-    public void createBootstrapServer(BootstrapSecurityStore securityStore, BootstrapStore bootstrapStore) {
+    public void createBootstrapServer(BootstrapSecurityStore securityStore, BootstrapConfigStore bootstrapStore) {
         if (bootstrapStore == null) {
             bootstrapStore = unsecuredBootstrapStore();
         }
@@ -229,11 +229,11 @@ public class BootstrapIntegrationTestHelper extends SecureIntegrationTestHelper
         };
     }
 
-    public BootstrapStore unsecuredBootstrapStore() {
-        return new BootstrapStore() {
+    public BootstrapConfigStore unsecuredBootstrapStore() {
+        return new BootstrapConfigStore() {
 
             @Override
-            public BootstrapConfig getBootstrap(String endpoint, Identity deviceIdentity) {
+            public BootstrapConfig get(String endpoint, Identity deviceIdentity) {
 
                 BootstrapConfig bsConfig = new BootstrapConfig();
 
@@ -264,7 +264,7 @@ public class BootstrapIntegrationTestHelper extends SecureIntegrationTestHelper
         };
     }
 
-    public BootstrapStore deleteSecurityStore(Integer... objectToDelete) {
+    public BootstrapConfigStore deleteSecurityStore(Integer... objectToDelete) {
         String[] pathToDelete = new String[objectToDelete.length];
         for (int i = 0; i < pathToDelete.length; i++) {
             pathToDelete[i] = "/" + objectToDelete[i];
@@ -273,11 +273,11 @@ public class BootstrapIntegrationTestHelper extends SecureIntegrationTestHelper
         return deleteSecurityStore(pathToDelete);
     }
 
-    public BootstrapStore deleteSecurityStore(final String... pathToDelete) {
-        return new BootstrapStore() {
+    public BootstrapConfigStore deleteSecurityStore(final String... pathToDelete) {
+        return new BootstrapConfigStore() {
 
             @Override
-            public BootstrapConfig getBootstrap(String endpoint, Identity deviceIdentity) {
+            public BootstrapConfig get(String endpoint, Identity deviceIdentity) {
 
                 BootstrapConfig bsConfig = new BootstrapConfig();
                 bsConfig.toDelete = Arrays.asList(pathToDelete);
@@ -286,11 +286,11 @@ public class BootstrapIntegrationTestHelper extends SecureIntegrationTestHelper
         };
     }
 
-    public BootstrapStore unsecuredWithAclBootstrapStore() {
-        return new BootstrapStore() {
+    public BootstrapConfigStore unsecuredWithAclBootstrapStore() {
+        return new BootstrapConfigStore() {
 
             @Override
-            public BootstrapConfig getBootstrap(String endpoint, Identity deviceIdentity) {
+            public BootstrapConfig get(String endpoint, Identity deviceIdentity) {
 
                 BootstrapConfig bsConfig = new BootstrapConfig();
 
@@ -337,11 +337,11 @@ public class BootstrapIntegrationTestHelper extends SecureIntegrationTestHelper
         };
     }
 
-    public BootstrapStore pskBootstrapStore() {
-        return new BootstrapStore() {
+    public BootstrapConfigStore pskBootstrapStore() {
+        return new BootstrapConfigStore() {
 
             @Override
-            public BootstrapConfig getBootstrap(String endpoint, Identity deviceIdentity) {
+            public BootstrapConfig get(String endpoint, Identity deviceIdentity) {
 
                 BootstrapConfig bsConfig = new BootstrapConfig();
 
@@ -374,11 +374,11 @@ public class BootstrapIntegrationTestHelper extends SecureIntegrationTestHelper
         };
     }
 
-    public BootstrapStore rpkBootstrapStore() {
-        return new BootstrapStore() {
+    public BootstrapConfigStore rpkBootstrapStore() {
+        return new BootstrapConfigStore() {
 
             @Override
-            public BootstrapConfig getBootstrap(String endpoint, Identity deviceIdentity) {
+            public BootstrapConfig get(String endpoint, Identity deviceIdentity) {
 
                 BootstrapConfig bsConfig = new BootstrapConfig();
 
diff --git a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/LeshanBootstrapServerBuilderTest.java b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/LeshanBootstrapServerBuilderTest.java
index b7cf683f..0f03235e 100644
--- a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/LeshanBootstrapServerBuilderTest.java
+++ b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/LeshanBootstrapServerBuilderTest.java
@@ -21,7 +21,7 @@ import java.util.List;
 
 import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.server.bootstrap.BootstrapConfig;
-import org.eclipse.leshan.server.bootstrap.BootstrapStore;
+import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore;
 import org.eclipse.leshan.server.californium.impl.LeshanBootstrapServer;
 import org.eclipse.leshan.server.security.BootstrapSecurityStore;
 import org.eclipse.leshan.server.security.SecurityInfo;
@@ -36,9 +36,9 @@ public class LeshanBootstrapServerBuilderTest {
     @Before
     public void start() {
         builder = new LeshanBootstrapServerBuilder();
-        builder.setConfigStore(new BootstrapStore() {
+        builder.setConfigStore(new BootstrapConfigStore() {
             @Override
-            public BootstrapConfig getBootstrap(String endpoint, Identity deviceIdentity) {
+            public BootstrapConfig get(String endpoint, Identity deviceIdentity) {
                 return null;
             }
         });
diff --git a/leshan-server-core/src/test/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerTest.java b/leshan-server-core/src/test/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerTest.java
index 965220d9..9a418325 100644
--- a/leshan-server-core/src/test/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerTest.java
+++ b/leshan-server-core/src/test/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerTest.java
@@ -54,9 +54,9 @@ public class BootstrapHandlerTest {
         final MockBootstrapSessionManager bsSessionManager = new MockBootstrapSessionManager(true);
         final LwM2mBootstrapRequestSender requestSender = new MockRequestSender(true);
 
-        final BootstrapStore bsStore = new BootstrapStore() {
+        final BootstrapConfigStore bsStore = new BootstrapConfigStore() {
             @Override
-            public BootstrapConfig getBootstrap(String endpoint, Identity deviceIdentity) {
+            public BootstrapConfig get(String endpoint, Identity deviceIdentity) {
                 return new BootstrapConfig();
             }
         };
@@ -79,9 +79,9 @@ public class BootstrapHandlerTest {
         final MockBootstrapSessionManager bsSessionManager = new MockBootstrapSessionManager(true);
         final LwM2mBootstrapRequestSender requestSender = new MockRequestSender(false);
 
-        final BootstrapStore bsStore = new BootstrapStore() {
+        final BootstrapConfigStore bsStore = new BootstrapConfigStore() {
             @Override
-            public BootstrapConfig getBootstrap(String endpoint, Identity deviceIdentity) {
+            public BootstrapConfig get(String endpoint, Identity deviceIdentity) {
                 return new BootstrapConfig();
             }
         };