diff --git a/leshan-bsserver-demo/webapp/src/components/bsconfig/ServerInput.vue b/leshan-bsserver-demo/webapp/src/components/bsconfig/ServerInput.vue
index 255ad8a595..05e2d7e4b6 100644
--- a/leshan-bsserver-demo/webapp/src/components/bsconfig/ServerInput.vue
+++ b/leshan-bsserver-demo/webapp/src/components/bsconfig/ServerInput.vue
@@ -27,32 +27,46 @@
class="examplePatch"
>
+
+
+
+
diff --git a/leshan-bsserver-demo/webapp/src/components/wizard/BootstrapServerStep.vue b/leshan-bsserver-demo/webapp/src/components/wizard/BootstrapServerStep.vue
index 4a9a85076f..384dad99d1 100644
--- a/leshan-bsserver-demo/webapp/src/components/wizard/BootstrapServerStep.vue
+++ b/leshan-bsserver-demo/webapp/src/components/wizard/BootstrapServerStep.vue
@@ -15,12 +15,10 @@
This information will be used to add a
- LWM2M Bootstrap Server to your LWM2M Client during the bootstrap
- Session by writing 1 instance for object /0
.
-
-
- By default no LWM2M Bootstrap server is added.
+ LWM2M Bootstrap Server to your LWM2M Client during the
+ bootstrap Session by writing 1 instance for object /0
.
+ By default no LWM2M Bootstrap server is added.
Previous
-
- Cancel
-
+ Cancel
@@ -193,9 +191,9 @@ export default {
this.config = {
endpoint: null,
security: null,
- dm: { mode: "no_sec" },
+ dm: { security: { mode: "no_sec" } },
bs: null,
- toDelete: ["/0", "/1"],
+ toDelete: ["/0", "/1", "/21"],
autoIdForSecurityObject: false,
};
this.currentStep = 1;
@@ -217,7 +215,7 @@ export default {
if (res.dm) {
if (!res.dm.url) {
res.dm.url =
- res.dm.mode == "no_sec"
+ res.dm.security.mode == "no_sec"
? this.defval.dm.url.nosec
: this.defval.dm.url.sec;
}
@@ -226,24 +224,24 @@ export default {
if (res.bs) {
if (!res.bs.url) {
res.bs.url =
- res.bs.mode == "no_sec"
+ res.bs.security.mode == "no_sec"
? this.defval.bs.url.nosec
: this.defval.bs.url.sec;
}
// apply default rpk value for bs server
- if (res.bs.mode == "rpk") {
+ if (res.bs.security.mode == "rpk") {
for (const key in this.defaultrpk) {
- if (!res.bs.details[key]) {
- res.bs.details[key] = this.defaultrpk[key];
+ if (!res.bs.security.details[key]) {
+ res.bs.security.details[key] = this.defaultrpk[key];
}
}
}
// apply default x509 value for bs server
- if (res.bs.mode == "x509") {
+ if (res.bs.security.mode == "x509") {
for (const key in this.defaultx509) {
- if (!res.bs.details[key]) {
- res.bs.details[key] = this.defaultx509[key];
+ if (!res.bs.security.details[key]) {
+ res.bs.security.details[key] = this.defaultx509[key];
}
}
}
diff --git a/leshan-bsserver-demo/webapp/src/components/wizard/DeleteStep.vue b/leshan-bsserver-demo/webapp/src/components/wizard/DeleteStep.vue
index edf7760192..d939996d5a 100644
--- a/leshan-bsserver-demo/webapp/src/components/wizard/DeleteStep.vue
+++ b/leshan-bsserver-demo/webapp/src/components/wizard/DeleteStep.vue
@@ -18,7 +18,7 @@
existing configuration on the LWM2M client.
- By default, objects /0
and /1
are deleted,
+ By default, objects /0
, /1
and /21
are deleted,
then you will be able to define LWM2M Server and LWM2M Bootstrap Server
to add.
diff --git a/leshan-bsserver-demo/webapp/src/components/wizard/ServerStep.vue b/leshan-bsserver-demo/webapp/src/components/wizard/ServerStep.vue
index b037aa220e..6b9f197f6a 100644
--- a/leshan-bsserver-demo/webapp/src/components/wizard/ServerStep.vue
+++ b/leshan-bsserver-demo/webapp/src/components/wizard/ServerStep.vue
@@ -58,14 +58,14 @@ export default {
data() {
return {
addServer: true,
- internalServer: { mode: "no_sec" }, // internal server Config
+ internalServer: { security: { mode: "no_sec" } }, // internal server Config
};
},
watch: {
value(v) {
if (!v) {
this.addServer = false;
- this.internalServer = { mode: "no_sec" };
+ this.internalServer = { security: { mode: "no_sec" } };
} else {
this.addServer = true;
this.internalServer = v;
diff --git a/leshan-bsserver-demo/webapp/src/js/bsconfigutil.js b/leshan-bsserver-demo/webapp/src/js/bsconfigutil.js
index 1236ec5f52..fdcaa0b769 100644
--- a/leshan-bsserver-demo/webapp/src/js/bsconfigutil.js
+++ b/leshan-bsserver-demo/webapp/src/js/bsconfigutil.js
@@ -18,16 +18,33 @@ var configFromRestToUI = function (config) {
for (var i in config.security) {
var security = config.security[i];
if (security.bootstrapServer) {
- newConfig.bs.push({ security: security });
+ let bs = { security: security };
+
+ // add oscore object (if any) to bs
+ let oscoreObjectInstanceId = security.oscoreSecurityMode;
+ let oscore = config.oscore[oscoreObjectInstanceId];
+ if (oscore) {
+ bs.oscore = oscore;
+ }
+
+ newConfig.bs.push(bs);
} else {
// search for DM information;
+ var server;
for (var j in config.servers) {
- var server = config.servers[j];
+ server = config.servers[j];
if (server.shortId === security.serverId) {
newConfig.dm.push(server);
server.security = security;
}
}
+
+ // add oscore object (if any) to dm
+ let oscoreObjectInstanceId = security.oscoreSecurityMode;
+ let oscore = config.oscore[oscoreObjectInstanceId];
+ if (oscore) {
+ server.oscore = oscore;
+ }
}
}
newConfig.toDelete = config.toDelete;
@@ -49,10 +66,14 @@ var configFromUIToRest = function (c) {
// do a deep copy
// we should maybe rather use cloneDeep from lodashz
let config = JSON.parse(JSON.stringify(c));
- var newConfig = { servers: {}, security: {} };
+ var newConfig = { servers: {}, security: {}, oscore: {} };
for (var i = 0; i < config.bs.length; i++) {
var bs = config.bs[i];
newConfig.security[i] = bs.security;
+ if (bs.oscore) {
+ newConfig.security[i].oscoreSecurityMode = i;
+ newConfig.oscore[i] = bs.oscore;
+ }
}
if (i == 0) {
// To be sure that we are not using instance ID 0 for a DM server.
@@ -63,6 +84,11 @@ var configFromUIToRest = function (c) {
var dm = config.dm[j];
newConfig.security[i + j] = dm.security;
delete dm.security;
+ if (dm.oscore) {
+ newConfig.security[i + j].oscoreSecurityMode = i + j;
+ newConfig.oscore[i + j] = dm.oscore;
+ delete dm.oscore;
+ }
newConfig.servers[j] = dm;
}
newConfig.toDelete = config.toDelete;
diff --git a/leshan-bsserver-demo/webapp/src/views/Bootstrap.vue b/leshan-bsserver-demo/webapp/src/views/Bootstrap.vue
index 21c32e9c1f..e51c146278 100644
--- a/leshan-bsserver-demo/webapp/src/views/Bootstrap.vue
+++ b/leshan-bsserver-demo/webapp/src/views/Bootstrap.vue
@@ -88,6 +88,15 @@
{{ server.security.securityMode.toLowerCase() }}
+
+ with
+
+
+ {{ oscoreIcon() }}
+
+ OSCORE
+
+
@@ -102,6 +111,15 @@
{{ server.security.securityMode.toLowerCase() }}
+
+ with
+
+
+ {{ oscoreIcon() }}
+
+ OSCORE
+
+
@@ -119,7 +137,10 @@ import { configsFromRestToUI, configFromUIToRest } from "../js/bsconfigutil.js";
import { fromHex, fromAscii } from "@leshan-server-core-demo/js/byteutils.js";
import SecurityInfoChip from "@leshan-server-core-demo/components/security/SecurityInfoChip.vue";
import ClientConfigDialog from "../components/wizard/ClientConfigDialog.vue";
-import { getModeIcon } from "@leshan-server-core-demo/js/securityutils.js";
+import {
+ getModeIcon,
+ getOscoreIcon,
+} from "@leshan-server-core-demo/js/securityutils.js";
export default {
components: { ClientConfigDialog, SecurityInfoChip },
@@ -183,28 +204,37 @@ export default {
modeIcon(securitymode) {
return getModeIcon(securitymode);
},
+ oscoreIcon() {
+ return getOscoreIcon();
+ },
formatData(c) {
let s = {};
- s.securityMode = c.mode.toUpperCase();
+ s.securityMode = c.security.mode.toUpperCase();
s.uri = c.url;
- switch (c.mode) {
+ switch (c.security.mode) {
case "psk":
- s.publicKeyOrId = fromAscii(c.details.identity);
- s.secretKey = fromHex(c.details.key);
+ s.publicKeyOrId = fromAscii(c.security.details.identity);
+ s.secretKey = fromHex(c.security.details.key);
break;
case "rpk":
- s.publicKeyOrId = fromHex(c.details.client_pub_key);
- s.secretKey = fromHex(c.details.client_pri_key);
- s.serverPublicKey = fromHex(c.details.server_pub_key);
+ s.publicKeyOrId = fromHex(c.security.details.client_pub_key);
+ s.secretKey = fromHex(c.security.details.client_pri_key);
+ s.serverPublicKey = fromHex(c.security.details.server_pub_key);
break;
case "x509":
- s.publicKeyOrId = fromHex(c.details.client_certificate);
- s.secretKey = fromHex(c.details.client_pri_key);
- s.serverPublicKey = fromHex(c.details.server_certificate);
- s.certificateUsage = c.details.certificate_usage;
+ s.publicKeyOrId = fromHex(c.security.details.client_certificate);
+ s.secretKey = fromHex(c.security.details.client_pri_key);
+ s.serverPublicKey = fromHex(c.security.details.server_certificate);
+ s.certificateUsage = c.security.details.certificate_usage;
break;
}
+ if (c.oscore) {
+ s.oscore = {};
+ s.oscore.oscoreSenderId = fromHex(c.oscore.sid);
+ s.oscore.oscoreMasterSecret = fromHex(c.oscore.msec);
+ s.oscore.oscoreRecipientId = fromHex(c.oscore.rid);
+ }
return s;
},
@@ -257,6 +287,13 @@ export default {
},
},
];
+ if (dmServer.oscore) {
+ c.dm[0].oscore = {
+ oscoreSenderId: dmServer.oscore.oscoreSenderId,
+ oscoreMasterSecret: dmServer.oscore.oscoreMasterSecret,
+ oscoreRecipientId: dmServer.oscore.oscoreRecipientId,
+ };
+ }
}
if (config.bs) {
let bsServer = this.formatData(config.bs);
@@ -278,6 +315,13 @@ export default {
},
},
];
+ if (bsServer.oscore) {
+ c.bs[0].oscore = {
+ oscoreSenderId: bsServer.oscore.oscoreSenderId,
+ oscoreMasterSecret: bsServer.oscore.oscoreMasterSecret,
+ oscoreRecipientId: bsServer.oscore.oscoreRecipientId,
+ };
+ }
}
if (config.security) {
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Oscore.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Oscore.java
index 2c6af3dcb4..8e24e8bab9 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Oscore.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Oscore.java
@@ -163,12 +163,21 @@ public ReadResponse read(ServerIdentity identity, int resourceid) {
return ReadResponse.success(resourceid, recipientId);
case OSCORE_AEAD_ALGORITHM:
+ if (aeadAlgorithm == null) {
+ return ReadResponse.notFound();
+ }
return ReadResponse.success(resourceid, aeadAlgorithm.getValue());
case OSCORE_HMAC_ALGORITHM:
+ if (hkdfAlgorithm == null) {
+ return ReadResponse.notFound();
+ }
return ReadResponse.success(resourceid, hkdfAlgorithm.getValue());
case OSCORE_MASTER_SALT:
+ if (masterSalt == null) {
+ return ReadResponse.notFound();
+ }
return ReadResponse.success(resourceid, masterSalt);
default:
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/ObjectEnabler.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/ObjectEnabler.java
index 524f07109e..6589d9bcf2 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/ObjectEnabler.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/ObjectEnabler.java
@@ -366,6 +366,8 @@ public BootstrapDeleteResponse doDelete(ServerIdentity identity, BootstrapDelete
if (request.getPath().isRoot() || request.getPath().isObject()) {
if (id == LwM2mId.SECURITY) {
// For security object, we clean everything except bootstrap Server account.
+
+ // Get bootstrap account and store removed instances ids
Entry bootstrapServerAccount = null;
int[] instanceIds = new int[instances.size()];
int i = 0;
@@ -373,30 +375,73 @@ public BootstrapDeleteResponse doDelete(ServerIdentity identity, BootstrapDelete
if (ServersInfoExtractor.isBootstrapServer(instance.getValue())) {
bootstrapServerAccount = instance;
} else {
- // store instance ids
+ // Store instance ids
instanceIds[i] = instance.getKey();
i++;
}
}
+ // Clear everything
instances.clear();
+
+ // Put bootstrap account again
if (bootstrapServerAccount != null) {
instances.put(bootstrapServerAccount.getKey(), bootstrapServerAccount.getValue());
}
+
fireInstancesRemoved(instanceIds);
return BootstrapDeleteResponse.success();
- } else {
- instances.clear();
- // fired instances removed
- int[] instanceIds = new int[instances.size()];
- int i = 0;
- for (Entry instance : instances.entrySet()) {
- instanceIds[i] = instance.getKey();
- i++;
+ } else if (id == LwM2mId.OSCORE) {
+ // For OSCORE object, we clean everything except OSCORE object link to bootstrap Server account.
+
+ // Get bootstrap account
+ LwM2mObjectInstance bootstrapInstance = ServersInfoExtractor.getBootstrapSecurityInstance(
+ getLwm2mClient().getObjectTree().getObjectEnabler(LwM2mId.SECURITY));
+ // Get OSCORE instance ID associated to it
+ Integer bootstrapOscoreInstanceId = bootstrapInstance != null
+ ? ServersInfoExtractor.getOscoreSecurityMode(bootstrapInstance)
+ : null;
+
+ // if bootstrap server use OSCORE,
+ // search the OSCORE instance for this ID and store removed instances ids
+ if (bootstrapOscoreInstanceId != null) {
+ Entry bootstrapServerOscore = null;
+ int[] instanceIds = new int[instances.size()];
+ int i = 0;
+ for (Entry instance : instances.entrySet()) {
+ if (bootstrapOscoreInstanceId.equals(instance.getKey())) {
+ bootstrapServerOscore = instance;
+ } else {
+ // Store instance ids
+ instanceIds[i] = instance.getKey();
+ i++;
+ }
+ }
+
+ // Clear everything
+ instances.clear();
+
+ // Put bootstrap OSCORE instance again
+ if (bootstrapServerOscore != null) {
+ instances.put(bootstrapServerOscore.getKey(), bootstrapServerOscore.getValue());
+ }
+ fireInstancesRemoved(instanceIds);
+ return BootstrapDeleteResponse.success();
}
- fireInstancesRemoved(instanceIds);
+ // else delete everything.
+ }
- return BootstrapDeleteResponse.success();
+ // In all other cases, just delete everything
+ instances.clear();
+ // fired instances removed
+ int[] instanceIds = new int[instances.size()];
+ int i = 0;
+ for (Entry instance : instances.entrySet()) {
+ instanceIds[i] = instance.getKey();
+ i++;
}
+ fireInstancesRemoved(instanceIds);
+
+ return BootstrapDeleteResponse.success();
} else if (request.getPath().isObjectInstance()) {
if (id == LwM2mId.SECURITY) {
// For security object, deleting bootstrap Server account is not allowed
@@ -404,6 +449,22 @@ public BootstrapDeleteResponse doDelete(ServerIdentity identity, BootstrapDelete
if (ServersInfoExtractor.isBootstrapServer(instance)) {
return BootstrapDeleteResponse.badRequest("bootstrap server can not be deleted");
}
+ } else if (id == LwM2mId.OSCORE) {
+ // For OSCORE object, deleting instance linked to Bootstrap account is not allowed
+
+ // Get bootstrap instance
+ LwM2mObjectInstance bootstrapInstance = ServersInfoExtractor.getBootstrapSecurityInstance(
+ getLwm2mClient().getObjectTree().getObjectEnabler(LwM2mId.SECURITY));
+ // Get OSCORE instance ID associated to it
+ Integer bootstrapOscoreInstanceId = bootstrapInstance != null
+ ? ServersInfoExtractor.getOscoreSecurityMode(bootstrapInstance)
+ : null;
+
+ if (bootstrapOscoreInstanceId != null
+ && bootstrapOscoreInstanceId.equals(request.getPath().getObjectInstanceId())) {
+ return BootstrapDeleteResponse
+ .badRequest("OSCORE instance linked to bootstrap server can not be deleted");
+ }
}
if (null != instances.remove(request.getPath().getObjectInstanceId())) {
fireInstancesRemoved(request.getPath().getObjectInstanceId());
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServersInfoExtractor.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServersInfoExtractor.java
index bb02cb440a..f5354c6f77 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServersInfoExtractor.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServersInfoExtractor.java
@@ -214,6 +214,19 @@ public static DmServerInfo getDMServerInfo(Map obje
return info.deviceManagements.get(shortID);
}
+ public static LwM2mObjectInstance getBootstrapSecurityInstance(LwM2mObjectEnabler securityEnabler) {
+ LwM2mObject securities = (LwM2mObject) securityEnabler.read(SYSTEM, new ReadRequest(SECURITY)).getContent();
+ if (securities != null) {
+ for (LwM2mObjectInstance instance : securities.getInstances().values()) {
+ if (isBootstrapServer(instance)) {
+ return instance;
+ }
+ }
+ }
+
+ return null;
+ }
+
public static ServerInfo getBootstrapServerInfo(Map objectEnablers) {
ServersInfo info = getInfo(objectEnablers);
if (info == null)
@@ -368,6 +381,14 @@ public static boolean isBootstrapServer(LwM2mInstanceEnabler instance) {
return (Boolean) isBootstrap.getValue();
}
+ public static boolean isBootstrapServer(LwM2mObjectInstance instance) {
+ LwM2mResource resource = instance.getResource(SEC_BOOTSTRAP);
+ if (resource == null) {
+ return false;
+ }
+ return (Boolean) resource.getValue();
+ }
+
// OSCORE related methods below
public static Integer getOscoreSecurityMode(LwM2mObjectInstance securityInstance) {
@@ -390,16 +411,27 @@ public static byte[] getRecipientId(LwM2mObjectInstance oscoreInstance) {
}
public static long getAeadAlgorithm(LwM2mObjectInstance oscoreInstance) {
- return (long) oscoreInstance.getResource(OSCORE_AEAD_ALGORITHM).getValue();
+ LwM2mResource resource = oscoreInstance.getResource(OSCORE_AEAD_ALGORITHM);
+ if (resource != null)
+ return (long) resource.getValue();
+ // return default one from https://datatracker.ietf.org/doc/html/rfc8613#section-3.2
+ return AeadAlgorithm.AES_CCM_16_64_128.getValue();
}
public static long getHkdfAlgorithm(LwM2mObjectInstance oscoreInstance) {
- return (long) oscoreInstance.getResource(OSCORE_HMAC_ALGORITHM).getValue();
+ LwM2mResource resource = oscoreInstance.getResource(OSCORE_HMAC_ALGORITHM);
+ if (resource != null)
+ return (long) resource.getValue();
+ // return default one from https://datatracker.ietf.org/doc/html/rfc8613#section-3.2
+ return HkdfAlgorithm.HKDF_HMAC_SHA_256.getValue();
}
public static byte[] getMasterSalt(LwM2mObjectInstance oscoreInstance) {
- byte[] value = (byte[]) oscoreInstance.getResource(OSCORE_MASTER_SALT).getValue();
+ LwM2mResource resource = oscoreInstance.getResource(OSCORE_MASTER_SALT);
+ if (resource == null)
+ return null;
+ byte[] value = (byte[]) resource.getValue();
if (value.length == 0) {
return null;
} else {
diff --git a/leshan-server-core-demo/webapp/src/components/security/SecurityInfoChip.vue b/leshan-server-core-demo/webapp/src/components/security/SecurityInfoChip.vue
index 2e80875e3a..6995b984f3 100644
--- a/leshan-server-core-demo/webapp/src/components/security/SecurityInfoChip.vue
+++ b/leshan-server-core-demo/webapp/src/components/security/SecurityInfoChip.vue
@@ -19,19 +19,23 @@
{{ securityInfo.tls.mode }}
- {{$icons.mdiLockOutline}}
+ {{ oscoreIcon }}
oscore
- {{ $icons.mdiLockOpenRemove }}
+ {{ noSecIcon }}
Nothing
diff --git a/leshan-server-core-demo/webapp/src/js/securityutils.js b/leshan-server-core-demo/webapp/src/js/securityutils.js
index cbef4adf09..91fd564d5c 100644
--- a/leshan-server-core-demo/webapp/src/js/securityutils.js
+++ b/leshan-server-core-demo/webapp/src/js/securityutils.js
@@ -13,6 +13,8 @@
import {
mdiCertificate,
mdiLock,
+ mdiLockOpenRemove,
+ mdiLockOutline,
mdiKeyChange,
mdiHelpRhombusOutline,
} from "@mdi/js";
@@ -37,4 +39,12 @@ function getModeIcon(mode) {
}
}
-export { getMode, getModeIcon };
+function getOscoreIcon() {
+ return mdiLockOutline;
+}
+
+function getNoSecIcon() {
+ return mdiLockOpenRemove;
+}
+
+export { getMode, getModeIcon, getOscoreIcon, getNoSecIcon };