Skip to content

Commit

Permalink
#404: Add ACL to bootstrap server
Browse files Browse the repository at this point in the history
  • Loading branch information
sbernard31 committed Mar 15, 2019
1 parent 366f044 commit d9d2e4c
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 43 deletions.
Expand Up @@ -32,6 +32,7 @@
import java.security.spec.ECPublicKeySpec;
import java.security.spec.KeySpec;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

import org.eclipse.leshan.LwM2mId;
Expand All @@ -42,8 +43,10 @@
import org.eclipse.leshan.client.resource.DummyInstanceEnabler;
import org.eclipse.leshan.client.resource.LwM2mObjectEnabler;
import org.eclipse.leshan.client.resource.ObjectsInitializer;
import org.eclipse.leshan.client.resource.SimpleInstanceEnabler;
import org.eclipse.leshan.core.request.Identity;
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;
Expand Down Expand Up @@ -97,36 +100,7 @@ public BootstrapIntegrationTestHelper() {

public void createBootstrapServer(BootstrapSecurityStore securityStore, BootstrapStore bootstrapStore) {
if (bootstrapStore == null) {
bootstrapStore = new BootstrapStore() {
@Override
public BootstrapConfig getBootstrap(String endpoint, Identity deviceIdentity) {
BootstrapConfig bsConfig = new BootstrapConfig();

// security for BS server
ServerSecurity bsSecurity = new ServerSecurity();
bsSecurity.serverId = 1111;
bsSecurity.bootstrapServer = true;
bsSecurity.uri = "coap://" + bootstrapServer.getUnsecuredAddress().getHostString() + ":"
+ bootstrapServer.getUnsecuredAddress().getPort();
bsSecurity.securityMode = SecurityMode.NO_SEC;
bsConfig.security.put(0, bsSecurity);

// security for DM server
ServerSecurity dmSecurity = new ServerSecurity();
dmSecurity.uri = "coap://" + server.getUnsecuredAddress().getHostString() + ":"
+ server.getUnsecuredAddress().getPort();
dmSecurity.serverId = 2222;
dmSecurity.securityMode = SecurityMode.NO_SEC;
bsConfig.security.put(1, dmSecurity);

// DM server
ServerConfig dmConfig = new ServerConfig();
dmConfig.shortId = 2222;
bsConfig.servers.put(0, dmConfig);

return bsConfig;
}
};
bootstrapStore = unsecuredBootstrapStore();
}

if (securityStore == null) {
Expand Down Expand Up @@ -186,7 +160,7 @@ private void createClient(Security security) {
initializer.setInstancesForObject(LwM2mId.SECURITY, security);
initializer.setInstancesForObject(LwM2mId.DEVICE,
new Device("Eclipse Leshan", IntegrationTestHelper.MODEL_NUMBER, "12345", "U"));
initializer.setClassForObject(LwM2mId.ACCESS_CONTROL, DummyInstanceEnabler.class);
initializer.setClassForObject(LwM2mId.ACCESS_CONTROL, SimpleInstanceEnabler.class);
initializer.setClassForObject(LwM2mId.SERVER, DummyInstanceEnabler.class);
List<LwM2mObjectEnabler> objects = initializer.createAll();

Expand Down Expand Up @@ -253,6 +227,92 @@ public List<SecurityInfo> getAllByEndpoint(String endpoint) {
};
}

public BootstrapStore unsecuredBootstrapStore() {
return new BootstrapStore() {

@Override
public BootstrapConfig getBootstrap(String endpoint, Identity deviceIdentity) {

BootstrapConfig bsConfig = new BootstrapConfig();

// security for BS server
ServerSecurity bsSecurity = new ServerSecurity();
bsSecurity.serverId = 1111;
bsSecurity.bootstrapServer = true;
bsSecurity.uri = "coap://" + bootstrapServer.getUnsecuredAddress().getHostString() + ":"
+ bootstrapServer.getUnsecuredAddress().getPort();
bsSecurity.securityMode = SecurityMode.NO_SEC;
bsConfig.security.put(0, bsSecurity);

// security for DM server
ServerSecurity dmSecurity = new ServerSecurity();
dmSecurity.uri = "coap://" + server.getUnsecuredAddress().getHostString() + ":"
+ server.getUnsecuredAddress().getPort();
dmSecurity.serverId = 2222;
dmSecurity.securityMode = SecurityMode.NO_SEC;
bsConfig.security.put(1, dmSecurity);

// DM server
ServerConfig dmConfig = new ServerConfig();
dmConfig.shortId = 2222;
bsConfig.servers.put(0, dmConfig);

return bsConfig;
}
};
}

public BootstrapStore unsecuredWithAclBootstrapStore() {
return new BootstrapStore() {

@Override
public BootstrapConfig getBootstrap(String endpoint, Identity deviceIdentity) {

BootstrapConfig bsConfig = new BootstrapConfig();

// security for BS server
ServerSecurity bsSecurity = new ServerSecurity();
bsSecurity.serverId = 1111;
bsSecurity.bootstrapServer = true;
bsSecurity.uri = "coap://" + bootstrapServer.getUnsecuredAddress().getHostString() + ":"
+ bootstrapServer.getUnsecuredAddress().getPort();
bsSecurity.securityMode = SecurityMode.NO_SEC;
bsConfig.security.put(0, bsSecurity);

// security for DM server
ServerSecurity dmSecurity = new ServerSecurity();
dmSecurity.uri = "coap://" + server.getUnsecuredAddress().getHostString() + ":"
+ server.getUnsecuredAddress().getPort();
dmSecurity.serverId = 2222;
dmSecurity.securityMode = SecurityMode.NO_SEC;
bsConfig.security.put(1, dmSecurity);

// DM server
ServerConfig dmConfig = new ServerConfig();
dmConfig.shortId = 2222;
bsConfig.servers.put(0, dmConfig);

// ACL
ACLConfig aclConfig = new ACLConfig();
aclConfig.objectId = 3;
aclConfig.objectInstanceId = 0;
HashMap<Integer, Long> acl = new HashMap<Integer, Long>();
acl.put(3333, 1l); // server with short id 3333 has just read(1) right on device object (3/0)
aclConfig.acls = acl;
aclConfig.AccessControlOwner = 2222;
bsConfig.acls.put(0, aclConfig);

aclConfig = new ACLConfig();
aclConfig.objectId = 4;
aclConfig.objectInstanceId = 0;
aclConfig.AccessControlOwner = 2222;
bsConfig.acls.put(1, aclConfig);

return bsConfig;
}
};
}

public BootstrapStore pskBootstrapStore() {
return new BootstrapStore() {

Expand Down
Expand Up @@ -16,8 +16,15 @@
package org.eclipse.leshan.integration.tests;

import static org.eclipse.leshan.integration.tests.SecureIntegrationTestHelper.*;
import static org.hamcrest.CoreMatchers.hasItems;
import static org.junit.Assert.*;

import org.eclipse.leshan.SecurityMode;
import org.eclipse.leshan.client.request.ServerIdentity;
import org.eclipse.leshan.core.node.LwM2mObject;
import org.eclipse.leshan.core.node.LwM2mObjectInstance;
import org.eclipse.leshan.core.request.ReadRequest;
import org.eclipse.leshan.core.response.ReadResponse;
import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException;
import org.eclipse.leshan.server.security.SecurityInfo;
import org.junit.After;
Expand Down Expand Up @@ -63,6 +70,43 @@ public void bootstrap() {
helper.assertClientRegisterered();
}

@Test
public void bootstrapWithAcl() {
// Create DM Server without security & start it
helper.createServer();
helper.server.start();

// Create and start bootstrap server
helper.createBootstrapServer(null, helper.unsecuredWithAclBootstrapStore());
helper.bootstrapServer.start();

// Create Client and check it is not already registered
helper.createClient();
helper.assertClientNotRegisterered();

// Start it and wait for registration
helper.client.start();
helper.waitForRegistrationAtServerSide(1);

// check the client is registered
helper.assertClientRegisterered();

// ensure ACL is correctly set
ReadResponse response = helper.client.getObjectEnablers().get(2).read(ServerIdentity.SYSTEM,
new ReadRequest(2));
LwM2mObject acl = (LwM2mObject) response.getContent();
assertThat(acl.getInstances().keySet(), hasItems(0, 1));
LwM2mObjectInstance instance = acl.getInstance(0);
assertEquals(3l, instance.getResource(0).getValue());
assertEquals(0l, instance.getResource(1).getValue());
assertEquals(1l, instance.getResource(2).getValues().get(3333));
assertEquals(2222l, instance.getResource(3).getValue());
instance = acl.getInstance(1);
assertEquals(4l, instance.getResource(0).getValue());
assertEquals(0l, instance.getResource(1).getValue());
assertEquals(2222l, instance.getResource(3).getValue());
}

@Test
public void bootstrapSecureWithPSK() {
// Create DM Server without security & start it
Expand Down
Expand Up @@ -33,6 +33,8 @@ public class BootstrapConfig implements Serializable {

public Map<Integer, ServerSecurity> security = new HashMap<>();

public Map<Integer, ACLConfig> acls = new HashMap<>();

/** server configuration (object 1) */
static public class ServerConfig implements Serializable {
public int shortId;
Expand All @@ -45,10 +47,9 @@ static public class ServerConfig implements Serializable {

@Override
public String toString() {
return String
.format("ServerConfig [shortId=%s, lifetime=%s, defaultMinPeriod=%s, defaultMaxPeriod=%s, disableTimeout=%s, notifIfDisabled=%s, binding=%s]",
shortId, lifetime, defaultMinPeriod, defaultMaxPeriod, disableTimeout, notifIfDisabled,
binding);
return String.format(
"ServerConfig [shortId=%s, lifetime=%s, defaultMinPeriod=%s, defaultMaxPeriod=%s, disableTimeout=%s, notifIfDisabled=%s, binding=%s]",
shortId, lifetime, defaultMinPeriod, defaultMaxPeriod, disableTimeout, notifIfDisabled, binding);
}
}

Expand All @@ -71,18 +72,30 @@ static public class ServerSecurity implements Serializable {
@Override
public String toString() {
// Note : secretKey and smsBindingKeySecret are explicitly excluded from the display for security purposes
return String
.format("ServerSecurity [uri=%s, bootstrapServer=%s, securityMode=%s, publicKeyOrId=%s, serverPublicKey=%s, smsSecurityMode=%s, smsBindingKeySecret=%s, serverSmsNumber=%s, serverId=%s, clientOldOffTime=%s, bootstrapServerAccountTimeout=%s]",
uri, bootstrapServer, securityMode, Arrays.toString(publicKeyOrId),
Arrays.toString(serverPublicKey), smsSecurityMode,
Arrays.toString(smsBindingKeyParam), serverSmsNumber,
serverId, clientOldOffTime, bootstrapServerAccountTimeout);
return String.format(
"ServerSecurity [uri=%s, bootstrapServer=%s, securityMode=%s, publicKeyOrId=%s, serverPublicKey=%s, smsSecurityMode=%s, smsBindingKeySecret=%s, serverSmsNumber=%s, serverId=%s, clientOldOffTime=%s, bootstrapServerAccountTimeout=%s]",
uri, bootstrapServer, securityMode, Arrays.toString(publicKeyOrId),
Arrays.toString(serverPublicKey), smsSecurityMode, Arrays.toString(smsBindingKeyParam),
serverSmsNumber, serverId, clientOldOffTime, bootstrapServerAccountTimeout);
}
}

/** server configuration (object 1) */
static public class ACLConfig implements Serializable {
public int objectId;
public int objectInstanceId;
public Map<Integer, Long> acls;
public Integer AccessControlOwner;

@Override
public String toString() {
return String.format("ACLConfig [objectId=%s, objectInstanceId=%s, ACLs=%s, AccessControlOwner=%s]",
objectId, objectInstanceId, acls, AccessControlOwner);
}
}

@Override
public String toString() {
return String.format("BootstrapConfig [servers=%s, security=%s]", servers, security);
return String.format("BootstrapConfig [servers=%s, security=%s, acls=%s]", servers, security, acls);
}

}
Expand Up @@ -31,6 +31,10 @@ public enum BootstrapFailureCause {
* The "/" object could not be deleted on the device
*/
DELETE_FAILED,
/**
* Object 2 (ACL) could not be written on the device
*/
WRITE_ACL_FAILED,
/**
* Object 1 (Server) could not be written on the device
*/
Expand Down
Expand Up @@ -23,6 +23,7 @@
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import org.eclipse.leshan.core.node.LwM2mMultipleResource;
import org.eclipse.leshan.core.node.LwM2mNode;
import org.eclipse.leshan.core.node.LwM2mObjectInstance;
import org.eclipse.leshan.core.node.LwM2mPath;
Expand All @@ -41,6 +42,7 @@
import org.eclipse.leshan.core.response.ErrorCallback;
import org.eclipse.leshan.core.response.LwM2mResponse;
import org.eclipse.leshan.core.response.ResponseCallback;
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.slf4j.Logger;
Expand Down Expand Up @@ -191,6 +193,40 @@ public void onError(Exception e) {
sessionManager.failed(session, WRITE_SERVER_FAILED, writeServerRequest);
}
});
} else {
// we are done, send the ACL
List<Integer> aclsToSend = new ArrayList<>(cfg.acls.keySet());
sendAcls(session, cfg, aclsToSend);
}
}

private void sendAcls(final BootstrapSession session, final BootstrapConfig cfg, final List<Integer> toSend) {
if (!toSend.isEmpty()) {
// get next config
Integer key = toSend.remove(0);
ACLConfig aclConfig = cfg.acls.get(key);

// extract write request parameters
LwM2mPath path = new LwM2mPath(2, key);
final LwM2mNode aclInstance = convertToAclInstance(key, aclConfig);

final BootstrapWriteRequest writeACLRequest = new BootstrapWriteRequest(path, aclInstance,
session.getContentFormat());
send(session, writeACLRequest, new ResponseCallback<BootstrapWriteResponse>() {
@Override
public void onResponse(BootstrapWriteResponse response) {
LOG.trace("Bootstrap write {} return code {}", session.getEndpoint(), response.getCode());
// recursive call until toSend is empty
sendAcls(session, cfg, toSend);
}
}, new ErrorCallback() {
@Override
public void onError(Exception e) {
LOG.warn(String.format("Error during bootstrap write of acl instance %s on %s", aclInstance,
session.getEndpoint()), e);
sessionManager.failed(session, WRITE_ACL_FAILED, writeACLRequest);
}
});
} else {
final BootstrapFinishRequest finishBootstrapRequest = new BootstrapFinishRequest();
send(session, finishBootstrapRequest, new ResponseCallback<BootstrapFinishResponse>() {
Expand Down Expand Up @@ -268,4 +304,17 @@ private LwM2mObjectInstance convertToServerInstance(int instanceId, ServerConfig

return new LwM2mObjectInstance(instanceId, resources);
}

private LwM2mObjectInstance convertToAclInstance(int instanceId, ACLConfig aclConfig) {
Collection<LwM2mResource> resources = new ArrayList<>();

resources.add(LwM2mSingleResource.newIntegerResource(0, aclConfig.objectId));
resources.add(LwM2mSingleResource.newIntegerResource(1, aclConfig.objectInstanceId));
if (aclConfig.acls != null)
resources.add(LwM2mMultipleResource.newIntegerResource(2, aclConfig.acls));
if (aclConfig.AccessControlOwner != null)
resources.add(LwM2mSingleResource.newIntegerResource(3, aclConfig.AccessControlOwner));

return new LwM2mObjectInstance(instanceId, resources);
}
}

0 comments on commit d9d2e4c

Please sign in to comment.