diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaliforniumEndpointsManager.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaliforniumEndpointsManager.java index 2f6bf2e252..4c8af95fa9 100644 --- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaliforniumEndpointsManager.java +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaliforniumEndpointsManager.java @@ -58,7 +58,9 @@ import org.eclipse.leshan.core.californium.oscore.cf.InMemoryOscoreContextDB; import org.eclipse.leshan.core.californium.oscore.cf.OscoreParameters; import org.eclipse.leshan.core.californium.oscore.cf.StaticOscoreStore; +import org.eclipse.leshan.core.oscore.InvalidOscoreSettingException; import org.eclipse.leshan.core.oscore.OscoreIdentity; +import org.eclipse.leshan.core.oscore.OscoreValidator; import org.eclipse.leshan.core.request.Identity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -242,28 +244,48 @@ public synchronized ServerIdentity createEndpoint(ServerInfo serverInfo, boolean currentEndpoint = endpointFactory.createSecuredEndpoint(newBuilder.build(), coapConfig, null, null); } else if (serverInfo.useOscore) { // oscore only mode - LOG.info("Adding OSCORE context for " + serverInfo.getFullUri().toASCIIString()); + LOG.warn("Experimental OSCORE support is used for {}", serverInfo.getFullUri().toASCIIString()); + + try { + new OscoreValidator().validateOscoreSetting(serverInfo.oscoreSetting); + } catch (InvalidOscoreSettingException e) { + throw new RuntimeException(String.format("Unable to create endpoint for %s using OSCORE : Invalid %s.", + serverInfo, serverInfo.oscoreSetting), e); + } AlgorithmID hkdfAlg = null; try { - hkdfAlg = AlgorithmID.FromCBOR(CBORObject.FromObject(serverInfo.hkdfAlgorithm)); + hkdfAlg = AlgorithmID + .FromCBOR(CBORObject.FromObject(serverInfo.oscoreSetting.getHkdfAlgorithm().getValue())); } catch (CoseException e) { - LOG.error("Failed to decode OSCORE HMAC algorithm"); + throw new RuntimeException(String.format( + "Unable to create endpoint for %s using OSCORE : Unable to decode OSCORE HKDF from %s.", + serverInfo, serverInfo.oscoreSetting), e); } AlgorithmID aeadAlg = null; try { - aeadAlg = AlgorithmID.FromCBOR(CBORObject.FromObject(serverInfo.aeadAlgorithm)); + aeadAlg = AlgorithmID + .FromCBOR(CBORObject.FromObject(serverInfo.oscoreSetting.getAeadAlgorithm().getValue())); } catch (CoseException e) { - LOG.error("Failed to decode OSCORE AEAD algorithm"); + throw new RuntimeException(String.format( + "Unable to create endpoint for %s using OSCORE : Unable to decode OSCORE AEAD from %s.", + serverInfo, serverInfo.oscoreSetting), e); } - OscoreParameters oscoreParameters = new OscoreParameters(serverInfo.senderId, serverInfo.recipientId, - serverInfo.masterSecret, aeadAlg, hkdfAlg, serverInfo.masterSalt); + + // TODO OSCORE kind of hack because californium doesn't support an empty byte[] array for salt ? + byte[] masterSalt = serverInfo.oscoreSetting.getMasterSalt().length == 0 ? null + : serverInfo.oscoreSetting.getMasterSalt(); + + OscoreParameters oscoreParameters = new OscoreParameters(serverInfo.oscoreSetting.getSenderId(), + serverInfo.oscoreSetting.getRecipientId(), serverInfo.oscoreSetting.getMasterSecret(), aeadAlg, + hkdfAlg, masterSalt); currentEndpoint = endpointFactory.createUnsecuredEndpoint(localAddress, coapConfig, null, new InMemoryOscoreContextDB(new StaticOscoreStore(oscoreParameters))); // Build server identity for OSCORE - serverIdentity = Identity.oscoreOnly(serverInfo.getAddress(), new OscoreIdentity(serverInfo.recipientId)); + serverIdentity = Identity.oscoreOnly(serverInfo.getAddress(), + new OscoreIdentity(serverInfo.oscoreSetting.getRecipientId())); } else { currentEndpoint = endpointFactory.createUnsecuredEndpoint(localAddress, coapConfig, null, null); serverIdentity = Identity.unsecure(serverInfo.getAddress()); @@ -283,9 +305,7 @@ public synchronized ServerIdentity createEndpoint(ServerInfo serverInfo, boolean try { currentEndpoint.start(); LOG.info("New endpoint created for server {} at {}", currentServer.getUri(), currentEndpoint.getUri()); - } catch ( - - IOException e) { + } catch (IOException e) { throw new RuntimeException("Unable to start endpoint", e); } } diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LwM2mClientCoapResource.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LwM2mClientCoapResource.java index 0bee0585e8..81d721d3c4 100644 --- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LwM2mClientCoapResource.java +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LwM2mClientCoapResource.java @@ -18,6 +18,7 @@ import org.eclipse.californium.core.CoapResource; import org.eclipse.californium.core.coap.CoAP.ResponseCode; import org.eclipse.californium.core.network.Exchange; +import org.eclipse.californium.core.network.Exchange.Origin; import org.eclipse.californium.core.server.resources.CoapExchange; import org.eclipse.leshan.client.engine.RegistrationEngine; import org.eclipse.leshan.client.servers.ServerIdentity; @@ -74,6 +75,7 @@ protected ServerIdentity getServerOrRejectRequest(CoapExchange exchange) { */ protected ServerIdentity extractIdentity(CoapExchange exchange) { return endpointsManager.getServerIdentity(exchange.advanced().getEndpoint(), exchange.getSourceSocketAddress(), - exchange.advanced().getRequest().getSourceContext()); + exchange.advanced().getOrigin() == Origin.REMOTE ? exchange.advanced().getRequest().getSourceContext() + : exchange.advanced().getRequest().getDestinationContext()); } } diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/bootstrap/DefaultBootstrapConsistencyChecker.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/bootstrap/DefaultBootstrapConsistencyChecker.java index ab497277ea..cbe02cbb95 100644 --- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/bootstrap/DefaultBootstrapConsistencyChecker.java +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/bootstrap/DefaultBootstrapConsistencyChecker.java @@ -24,21 +24,26 @@ import org.eclipse.leshan.client.servers.DmServerInfo; import org.eclipse.leshan.client.servers.ServerInfo; import org.eclipse.leshan.core.SecurityMode; +import org.eclipse.leshan.core.oscore.InvalidOscoreSettingException; +import org.eclipse.leshan.core.oscore.OscoreValidator; /** * A default implementation of {@link BootstrapConsistencyChecker} */ public class DefaultBootstrapConsistencyChecker extends BaseBootstrapConsistencyChecker { + private OscoreValidator oscoreValidator = new OscoreValidator(); + @Override - protected void checkBootstrapServerInfo(ServerInfo BootstrapServerInfo, List errors) { - checkCertificateIsValid(BootstrapServerInfo, errors); + protected void checkBootstrapServerInfo(ServerInfo bootstrapServerInfo, List errors) { + checkCertificateIsValid(bootstrapServerInfo, errors); + checkOscoreIsValid(bootstrapServerInfo, errors); } @Override protected void checkDeviceMangementServerInfo(DmServerInfo serverInfo, List errors) { checkCertificateIsValid(serverInfo, errors); - + checkOscoreIsValid(serverInfo, errors); } private void checkCertificateIsValid(ServerInfo serverInfo, List errors) { @@ -49,4 +54,14 @@ private void checkCertificateIsValid(ServerInfo serverInfo, List errors) } } } + + private void checkOscoreIsValid(ServerInfo serverInfo, List errors) { + if (serverInfo.useOscore) { + try { + oscoreValidator.validateOscoreSetting(serverInfo.oscoreSetting); + } catch (InvalidOscoreSettingException e) { + errors.add(e.getMessage()); + } + } + } } diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CaliforniumLwM2mRequestSender.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CaliforniumLwM2mRequestSender.java index 023306ceea..e8d794b063 100644 --- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CaliforniumLwM2mRequestSender.java +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CaliforniumLwM2mRequestSender.java @@ -13,7 +13,6 @@ * Contributors: * Zebra Technologies - initial API and implementation * Michał Wadowski (Orange) - Improved compliance with rfc6690 - * Rikard Höglund (RISE SICS) - Additions to support OSCORE *******************************************************************************/ package org.eclipse.leshan.client.californium.request; 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 4629040f57..2c6af3dcb4 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 @@ -28,6 +28,11 @@ import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel.Type; import org.eclipse.leshan.core.node.LwM2mResource; +import org.eclipse.leshan.core.oscore.AeadAlgorithm; +import org.eclipse.leshan.core.oscore.HkdfAlgorithm; +import org.eclipse.leshan.core.oscore.InvalidOscoreSettingException; +import org.eclipse.leshan.core.oscore.OscoreSetting; +import org.eclipse.leshan.core.oscore.OscoreValidator; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.response.WriteResponse; import org.slf4j.Logger; @@ -35,20 +40,25 @@ /** * A simple {@link LwM2mInstanceEnabler} for the OSCORE Security (21) object. + *

+ * Note it must be used with a minimum 2.0 version of the object because previously String was used instead of byte[] + * for recipientId, SenderId, MasterKey, MasterSalt. + *

+ * See for more details : https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues/521 */ public class Oscore extends BaseInstanceEnabler { private static final Logger LOG = LoggerFactory.getLogger(Security.class); - private final static List supportedResources = Arrays.asList(OSCORE_Master_Secret, OSCORE_Sender_ID, - OSCORE_Recipient_ID, OSCORE_AEAD_Algorithm, OSCORE_HMAC_Algorithm, OSCORE_Master_Salt); + private final static List supportedResources = Arrays.asList(OSCORE_MASTER_SECRET, OSCORE_SENDER_ID, + OSCORE_RECIPIENT_ID, OSCORE_AEAD_ALGORITHM, OSCORE_HMAC_ALGORITHM, OSCORE_MASTER_SALT); - private String masterSecret; - private String senderId; - private String recipientId; - private int aeadAlgorithm; - private int hkdfAlgorithm; - private String masterSalt; + private byte[] masterSecret; + private byte[] senderId; + private byte[] recipientId; + private AeadAlgorithm aeadAlgorithm; + private HkdfAlgorithm hkdfAlgorithm; + private byte[] masterSalt; public Oscore() { @@ -57,75 +67,77 @@ public Oscore() { /** * Default constructor. */ - public Oscore(int instanceId, String masterSecret, String senderId, String recipientId, int aeadAlgorithm, - int hkdfAlgorithm, String masterSalt) { + public Oscore(int instanceId, OscoreSetting oscoreSetting) { super(instanceId); - this.masterSecret = masterSecret; - this.senderId = senderId; - this.recipientId = recipientId; - this.aeadAlgorithm = aeadAlgorithm; - this.hkdfAlgorithm = hkdfAlgorithm; - this.masterSalt = masterSalt; - } - - /** - * Constructor providing some default values. - * - * aeadAlgorithm = 10; //AES_CCM_16_64_128m hmacAlgorithm = -10; //HKDF_HMAC_SHA_256, masterSalt = ""; - * - */ - public Oscore(int instanceId, String masterSecret, String senderId, String recipientId) { - this(instanceId, masterSecret, senderId, recipientId, 10, -10, ""); + try { + new OscoreValidator().validateOscoreSetting(oscoreSetting); + } catch (InvalidOscoreSettingException e) { + throw new IllegalArgumentException("Invalid " + oscoreSetting, e); + } + this.masterSecret = oscoreSetting.getMasterSecret(); + this.senderId = oscoreSetting.getSenderId(); + this.recipientId = oscoreSetting.getRecipientId(); + this.aeadAlgorithm = oscoreSetting.getAeadAlgorithm(); + this.hkdfAlgorithm = oscoreSetting.getHkdfAlgorithm(); + this.masterSalt = oscoreSetting.getMasterSalt(); } @Override public WriteResponse write(ServerIdentity identity, boolean replace, int resourceId, LwM2mResource value) { - LOG.debug("Write on resource {}: {}", resourceId, value); - - // restricted to BS server? + if (!identity.isSystem()) + LOG.debug("Write on resource {}: {}", resourceId, value); switch (resourceId) { - - case OSCORE_Master_Secret: - if (value.getType() != Type.STRING) { + case OSCORE_MASTER_SECRET: + if (value.getType() != Type.OPAQUE) { return WriteResponse.badRequest("invalid type"); } - masterSecret = (String) value.getValue(); + masterSecret = (byte[]) value.getValue(); return WriteResponse.success(); - case OSCORE_Sender_ID: - if (value.getType() != Type.STRING) { + case OSCORE_SENDER_ID: + if (value.getType() != Type.OPAQUE) { return WriteResponse.badRequest("invalid type"); } - senderId = (String) value.getValue(); + senderId = (byte[]) value.getValue(); return WriteResponse.success(); - case OSCORE_Recipient_ID: - if (value.getType() != Type.STRING) { + case OSCORE_RECIPIENT_ID: + if (value.getType() != Type.OPAQUE) { return WriteResponse.badRequest("invalid type"); } - recipientId = (String) value.getValue(); + recipientId = (byte[]) value.getValue(); return WriteResponse.success(); - case OSCORE_AEAD_Algorithm: + case OSCORE_AEAD_ALGORITHM: { if (value.getType() != Type.INTEGER) { return WriteResponse.badRequest("invalid type"); } - aeadAlgorithm = ((Long) value.getValue()).intValue(); - return WriteResponse.success(); - - case OSCORE_HMAC_Algorithm: + Long longValue = (Long) value.getValue(); + aeadAlgorithm = AeadAlgorithm.fromValue(longValue); + if (aeadAlgorithm != null) { + return WriteResponse.success(); + } else { + return WriteResponse.badRequest("unknown algorithm " + longValue); + } + } + case OSCORE_HMAC_ALGORITHM: { if (value.getType() != Type.INTEGER) { return WriteResponse.badRequest("invalid type"); } - hkdfAlgorithm = ((Long) value.getValue()).intValue(); - return WriteResponse.success(); - - case OSCORE_Master_Salt: - if (value.getType() != Type.STRING) { + Long longValue = (Long) value.getValue(); + hkdfAlgorithm = HkdfAlgorithm.fromValue(longValue); + if (hkdfAlgorithm != null) { + return WriteResponse.success(); + } else { + return WriteResponse.badRequest("unknown algorithm " + longValue); + } + } + case OSCORE_MASTER_SALT: + if (value.getType() != Type.OPAQUE) { return WriteResponse.badRequest("invalid type"); } - masterSalt = (String) value.getValue(); + masterSalt = (byte[]) value.getValue(); return WriteResponse.success(); default: @@ -136,27 +148,27 @@ public WriteResponse write(ServerIdentity identity, boolean replace, int resourc @Override public ReadResponse read(ServerIdentity identity, int resourceid) { - LOG.debug("Read on resource {}", resourceid); - // only accessible for internal read? + if (!identity.isSystem()) + LOG.debug("Read on resource {}", resourceid); switch (resourceid) { - case OSCORE_Master_Secret: + case OSCORE_MASTER_SECRET: return ReadResponse.success(resourceid, masterSecret); - case OSCORE_Sender_ID: + case OSCORE_SENDER_ID: return ReadResponse.success(resourceid, senderId); - case OSCORE_Recipient_ID: + case OSCORE_RECIPIENT_ID: return ReadResponse.success(resourceid, recipientId); - case OSCORE_AEAD_Algorithm: - return ReadResponse.success(resourceid, aeadAlgorithm); + case OSCORE_AEAD_ALGORITHM: + return ReadResponse.success(resourceid, aeadAlgorithm.getValue()); - case OSCORE_HMAC_Algorithm: - return ReadResponse.success(resourceid, hkdfAlgorithm); + case OSCORE_HMAC_ALGORITHM: + return ReadResponse.success(resourceid, hkdfAlgorithm.getValue()); - case OSCORE_Master_Salt: + case OSCORE_MASTER_SALT: return ReadResponse.success(resourceid, masterSalt); default: diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Security.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Security.java index 464129546c..807ec7fd1c 100644 --- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Security.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Security.java @@ -29,8 +29,8 @@ import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel.Type; import org.eclipse.leshan.core.node.LwM2mResource; -import org.eclipse.leshan.core.request.argument.Arguments; import org.eclipse.leshan.core.node.ObjectLink; +import org.eclipse.leshan.core.request.argument.Arguments; import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.response.WriteResponse; @@ -84,10 +84,9 @@ public Security(String serverUri, boolean bootstrapServer, int securityMode, byt /** * Returns a new security instance (OSCORE only) for a bootstrap server. */ - public static Security oscoreOnlyBootstrap(String serverUri, Integer shortServerId, int oscoreObjectInstanceId) { - return new Security(serverUri, true, SecurityMode.NO_SEC.code, new byte[0], new byte[0], new byte[0], - shortServerId, CertificateUsage.DOMAIN_ISSUER_CERTIFICATE.code, - new ObjectLink(OSCORE, oscoreObjectInstanceId)); + public static Security oscoreOnlyBootstrap(String serverUri, int oscoreObjectInstanceId) { + return new Security(serverUri, true, SecurityMode.NO_SEC.code, new byte[0], new byte[0], new byte[0], null, + CertificateUsage.DOMAIN_ISSUER_CERTIFICATE.code, new ObjectLink(OSCORE, oscoreObjectInstanceId)); } /** diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/BaseObjectEnabler.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/BaseObjectEnabler.java index a573f42681..c2eaf5b129 100644 --- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/BaseObjectEnabler.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/BaseObjectEnabler.java @@ -113,7 +113,7 @@ public synchronized CreateResponse create(ServerIdentity identity, CreateRequest beginTransaction(LwM2mPath.OBJECT_DEPTH); if (!identity.isSystem()) { - if (id == LwM2mId.SECURITY) { + if (id == LwM2mId.SECURITY || id == LwM2mId.OSCORE) { return CreateResponse.notFound(); } } else if (identity.isLwm2mBootstrapServer()) { @@ -155,8 +155,8 @@ public synchronized ReadResponse read(ServerIdentity identity, ReadRequest reque } if (!identity.isSystem()) { - // read the security object is forbidden - if (id == LwM2mId.SECURITY) { + // read the security or oscore object is forbidden + if (id == LwM2mId.SECURITY || id == LwM2mId.OSCORE) { return ReadResponse.notFound(); } @@ -218,15 +218,16 @@ public synchronized WriteResponse write(ServerIdentity identity, WriteRequest re return WriteResponse.methodNotAllowed(); } - // write the security object is forbidden - if (LwM2mId.SECURITY == id && !identity.isSystem()) { + // write the security or oscore object is forbidden + if (!identity.isSystem() && (id == LwM2mId.SECURITY || id == LwM2mId.OSCORE)) { return WriteResponse.notFound(); } if (path.isResource() || path.isResourceInstance()) { // resource write: // check if the resource is writable - if (LwM2mId.SECURITY != id) { // security resources are writable by SYSTEM + if (id != LwM2mId.SECURITY && id != LwM2mId.OSCORE) { + // security and oscore resources are writable by SYSTEM ResourceModel resourceModel = objectModel.resources.get(path.getResourceId()); if (resourceModel == null) { return WriteResponse.notFound(); @@ -239,7 +240,8 @@ public synchronized WriteResponse write(ServerIdentity identity, WriteRequest re } else if (path.isObjectInstance()) { // instance write: // check if all resources are writable - if (LwM2mId.SECURITY != id) { // security resources are writable by SYSTEM + if (id != LwM2mId.SECURITY && id != LwM2mId.OSCORE) { + // security and oscore resources are writable by SYSTEM ObjectModel model = getObjectModel(); for (Integer writeResourceId : ((LwM2mObjectInstance) request.getNode()).getResources().keySet()) { ResourceModel resourceModel = model.resources.get(writeResourceId); @@ -292,7 +294,7 @@ public synchronized DeleteResponse delete(ServerIdentity identity, DeleteRequest return DeleteResponse.methodNotAllowed(); // delete the security object is forbidden - if (id == LwM2mId.SECURITY) { + if (id == LwM2mId.SECURITY || id == LwM2mId.OSCORE) { return DeleteResponse.notFound(); } @@ -337,7 +339,7 @@ public synchronized ExecuteResponse execute(ServerIdentity identity, ExecuteRequ } // execute on security object is forbidden - if (id == LwM2mId.SECURITY) { + if (id == LwM2mId.SECURITY || id == LwM2mId.OSCORE) { return ExecuteResponse.notFound(); } @@ -382,7 +384,7 @@ public synchronized DiscoverResponse discover(ServerIdentity identity, DiscoverR return DiscoverResponse.methodNotAllowed(); } - if (id == LwM2mId.SECURITY) { + if (id == LwM2mId.SECURITY || id == LwM2mId.OSCORE) { return DiscoverResponse.notFound(); } return doDiscover(identity, request); @@ -454,8 +456,8 @@ public synchronized ObserveResponse observe(ServerIdentity identity, ObserveRequ return ObserveResponse.methodNotAllowed(); if (!identity.isSystem()) { - // observe or read of the security object is forbidden - if (id == LwM2mId.SECURITY) + // observe or read of the security and oscore object are forbidden + if (id == LwM2mId.SECURITY || id == LwM2mId.OSCORE) return ObserveResponse.notFound(); // check if the resource is readable. @@ -484,7 +486,8 @@ protected boolean missingMandatoryResource(Collection resources) // Collect all mandatory writable resource IDs from the model Set mandatoryResources = new HashSet<>(); for (ResourceModel resourceModel : getObjectModel().resources.values()) { - if (resourceModel.mandatory && (LwM2mId.SECURITY == id || resourceModel.operations.isWritable())) + if (resourceModel.mandatory + && (LwM2mId.SECURITY == id || LwM2mId.OSCORE == id || resourceModel.operations.isWritable())) mandatoryResources.add(resourceModel.id); } // Afterwards remove the provided resource IDs from that set diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServerInfo.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServerInfo.java index 9bb085da6e..3f6b008fd9 100644 --- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServerInfo.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServerInfo.java @@ -26,6 +26,7 @@ import org.eclipse.leshan.core.CertificateUsage; import org.eclipse.leshan.core.LwM2m; import org.eclipse.leshan.core.SecurityMode; +import org.eclipse.leshan.core.oscore.OscoreSetting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,12 +58,7 @@ public class ServerInfo { // OSCORE parameters public boolean useOscore; - public byte[] masterSecret; - public byte[] senderId; - public byte[] recipientId; - public long aeadAlgorithm; - public long hkdfAlgorithm; - public byte[] masterSalt; + public OscoreSetting oscoreSetting; public InetSocketAddress getAddress() { return getAddress(serverUri); 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 18694ee009..bb02cb440a 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 @@ -46,10 +46,12 @@ import org.eclipse.leshan.core.node.LwM2mObjectInstance; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.ObjectLink; +import org.eclipse.leshan.core.oscore.AeadAlgorithm; +import org.eclipse.leshan.core.oscore.HkdfAlgorithm; +import org.eclipse.leshan.core.oscore.OscoreSetting; import org.eclipse.leshan.core.request.BindingMode; import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.response.ReadResponse; -import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.core.util.SecurityUtil; import org.eclipse.leshan.core.util.datatype.ULong; import org.slf4j.Logger; @@ -143,14 +145,12 @@ private static void populateServerInfo(ServerInfo info, LwM2mObjectInstance secu info.serverUri, oscoreObjLink.getObjectId()); } else { if (oscores == null) { - LOG.warn( - "Invalid Security info for LWM2M server {}: OSCORE object enabler is not available.", + LOG.warn("Invalid Security info for LWM2M server {}: OSCORE object enabler is not available.", info.serverUri); } else { oscoreInstance = oscores.getInstance(oscoreObjLink.getObjectInstanceId()); if (oscoreInstance == null) { - LOG.warn( - "Invalid Security info for LWM2M server {} : OSCORE instance {} does not exist.", + LOG.warn("Invalid Security info for LWM2M server {} : OSCORE instance {} does not exist.", info.serverUri, oscoreObjLink.getObjectInstanceId()); } } @@ -159,12 +159,15 @@ private static void populateServerInfo(ServerInfo info, LwM2mObjectInstance secu if (oscoreInstance != null) { info.useOscore = true; - info.masterSecret = getMasterSecret(oscoreInstance); - info.senderId = getSenderId(oscoreInstance); - info.recipientId = getRecipientId(oscoreInstance); - info.aeadAlgorithm = getAeadAlgorithm(oscoreInstance); - info.hkdfAlgorithm = getHkdfAlgorithm(oscoreInstance); - info.masterSalt = getMasterSalt(oscoreInstance); + + byte[] masterSecret = getMasterSecret(oscoreInstance); + byte[] senderId = getSenderId(oscoreInstance); + byte[] recipientId = getRecipientId(oscoreInstance); + AeadAlgorithm aeadAlgorithm = AeadAlgorithm.fromValue(getAeadAlgorithm(oscoreInstance)); + HkdfAlgorithm hkdfAlgorithm = HkdfAlgorithm.fromValue(getHkdfAlgorithm(oscoreInstance)); + byte[] masterSalt = getMasterSalt(oscoreInstance); + info.oscoreSetting = new OscoreSetting(senderId, recipientId, masterSecret, aeadAlgorithm, + hkdfAlgorithm, masterSalt); } else if (info.secureMode == SecurityMode.PSK) { info.pskId = getPskIdentity(security); info.pskKey = getPskKey(security); @@ -367,36 +370,40 @@ public static boolean isBootstrapServer(LwM2mInstanceEnabler instance) { // OSCORE related methods below + public static Integer getOscoreSecurityMode(LwM2mObjectInstance securityInstance) { + LwM2mResource resource = securityInstance.getResource(LwM2mId.SEC_OSCORE_SECURITY_MODE); + if (resource != null) + return ((ObjectLink) resource.getValue()).getObjectInstanceId(); + return null; + } + public static byte[] getMasterSecret(LwM2mObjectInstance oscoreInstance) { - String value = (String) oscoreInstance.getResource(OSCORE_Master_Secret).getValue(); - return Hex.decodeHex(value.toCharArray()); + return (byte[]) oscoreInstance.getResource(OSCORE_MASTER_SECRET).getValue(); } public static byte[] getSenderId(LwM2mObjectInstance oscoreInstance) { - String value = (String) oscoreInstance.getResource(OSCORE_Sender_ID).getValue(); - return Hex.decodeHex(value.toCharArray()); + return (byte[]) oscoreInstance.getResource(OSCORE_SENDER_ID).getValue(); } public static byte[] getRecipientId(LwM2mObjectInstance oscoreInstance) { - String value = (String) oscoreInstance.getResource(OSCORE_Recipient_ID).getValue(); - return Hex.decodeHex(value.toCharArray()); + return (byte[]) oscoreInstance.getResource(OSCORE_RECIPIENT_ID).getValue(); } public static long getAeadAlgorithm(LwM2mObjectInstance oscoreInstance) { - return (long) oscoreInstance.getResource(OSCORE_AEAD_Algorithm).getValue(); + return (long) oscoreInstance.getResource(OSCORE_AEAD_ALGORITHM).getValue(); } public static long getHkdfAlgorithm(LwM2mObjectInstance oscoreInstance) { - return (long) oscoreInstance.getResource(OSCORE_HMAC_Algorithm).getValue(); + return (long) oscoreInstance.getResource(OSCORE_HMAC_ALGORITHM).getValue(); } public static byte[] getMasterSalt(LwM2mObjectInstance oscoreInstance) { - String value = (String) oscoreInstance.getResource(OSCORE_Master_Salt).getValue(); + byte[] value = (byte[]) oscoreInstance.getResource(OSCORE_MASTER_SALT).getValue(); - if (value.equals("")) { + if (value.length == 0) { return null; } else { - return Hex.decodeHex(value.toCharArray()); + return value; } } } diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/util/LinkFormatHelper.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/util/LinkFormatHelper.java index 41af2a5e36..bba5ba201e 100644 --- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/util/LinkFormatHelper.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/util/LinkFormatHelper.java @@ -21,9 +21,12 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; +import org.eclipse.leshan.client.servers.ServerIdentity; import org.eclipse.leshan.client.servers.ServersInfoExtractor; import org.eclipse.leshan.core.LwM2m.LwM2mVersion; import org.eclipse.leshan.core.LwM2m.Version; @@ -38,8 +41,12 @@ import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes; import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.model.ObjectModel; +import org.eclipse.leshan.core.node.LwM2mObject; +import org.eclipse.leshan.core.node.LwM2mObjectInstance; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.request.ContentFormat; +import org.eclipse.leshan.core.request.ReadRequest; +import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.util.StringUtils; /** @@ -74,8 +81,8 @@ public int compare(LwM2mObjectEnabler o1, LwM2mObjectEnabler o2) { } }); for (LwM2mObjectEnabler objectEnabler : objEnablerList) { - // skip the security Object - if (objectEnabler.getId() == LwM2mId.SECURITY) + // skip the security and oscore Object + if (objectEnabler.getId() == LwM2mId.SECURITY || objectEnabler.getId() == LwM2mId.OSCORE) continue; List availableInstance = objectEnabler.getAvailableInstanceIds(); @@ -98,12 +105,48 @@ public static LwM2mLink[] getBootstrapClientDescription(Collection>> oscoreAttributesByInstanceId = extractOscoreAttributes( + objectEnablers); + for (LwM2mObjectEnabler objectEnabler : objectEnablers) { - links.addAll(getBootstrapObjectDescriptionWithoutRoot(objectEnabler)); + links.addAll(getBootstrapObjectDescriptionWithoutRoot(objectEnabler, oscoreAttributesByInstanceId)); } return links.toArray(new LwM2mLink[] {}); } + private static Map>> extractOscoreAttributes( + Collection objectEnablers) { + Map>> oscoreAttributes = new HashMap<>(); + for (LwM2mObjectEnabler objectEnabler : objectEnablers) { + if (objectEnabler.getId() == LwM2mId.SECURITY) { + ReadResponse response = objectEnabler.read(ServerIdentity.SYSTEM, new ReadRequest(LwM2mId.SECURITY)); + if (response.isSuccess()) { + LwM2mObject object = (LwM2mObject) response.getContent(); + for (LwM2mObjectInstance instance : object.getInstances().values()) { + Integer oscoreSecurityMode = ServersInfoExtractor.getOscoreSecurityMode(instance); + if (oscoreSecurityMode != null) { + List> attributes = new ArrayList<>(2); + // extract ssid + Long shortServerId = ServersInfoExtractor.getServerId(objectEnabler, instance.getId()); + if (shortServerId != null) + attributes.add(LwM2mAttributes.create(LwM2mAttributes.SHORT_SERVER_ID, shortServerId)); + // extract uri + String uri = ServersInfoExtractor.getServerURI(objectEnabler, instance.getId()); + if (uri != null) + attributes.add(LwM2mAttributes.create(LwM2mAttributes.SERVER_URI, uri)); + + oscoreAttributes.put(oscoreSecurityMode, attributes); + } + } + } + return oscoreAttributes; + } + + } + return oscoreAttributes; + } + public static LwM2mLink[] getObjectDescription(LwM2mObjectEnabler objectEnabler, String rootPath) { List links = new ArrayList<>(); @@ -125,12 +168,13 @@ public static LwM2mLink[] getBootstrapObjectDescription(LwM2mObjectEnabler objec // TODO should be version 1.1 ? LwM2mAttributes.create(LwM2mAttributes.ENABLER_VERSION, LwM2mVersion.V1_0))); - links.addAll(getBootstrapObjectDescriptionWithoutRoot(objectEnabler)); + links.addAll(getBootstrapObjectDescriptionWithoutRoot(objectEnabler, null)); return links.toArray(new LwM2mLink[] {}); } - private static List getBootstrapObjectDescriptionWithoutRoot(LwM2mObjectEnabler objectEnabler) { + private static List getBootstrapObjectDescriptionWithoutRoot(LwM2mObjectEnabler objectEnabler, + Map>> oscoreAttributesByInstanceId) { List links = new ArrayList<>(); // create link for "object" @@ -153,23 +197,35 @@ private static List getBootstrapObjectDescriptionWithoutRoot(LwM2mObj for (Integer instanceId : objectEnabler.getAvailableInstanceIds()) { List> objectAttributes = new ArrayList<>(); - // get short id - if (objectEnabler.getId() == LwM2mId.SECURITY || objectEnabler.getId() == LwM2mId.SERVER) { - Boolean isBootstrapServer = objectEnabler.getId() == LwM2mId.SECURITY - && ServersInfoExtractor.isBootstrapServer(objectEnabler, instanceId); - if (isBootstrapServer != null && !isBootstrapServer) { - Long shortServerId = ServersInfoExtractor.getServerId(objectEnabler, instanceId); - if (shortServerId != null) - objectAttributes.add(LwM2mAttributes.create(LwM2mAttributes.SHORT_SERVER_ID, shortServerId)); + // handle oscore + if (objectEnabler.getId() == LwM2mId.OSCORE) { + System.out.println(oscoreAttributesByInstanceId); + if (oscoreAttributesByInstanceId != null) { + List> oscoreAttributes = oscoreAttributesByInstanceId.get(instanceId); + if (oscoreAttributes != null && !oscoreAttributes.isEmpty()) { + objectAttributes.addAll(oscoreAttributes); + } } + } else { + // get short id + if (objectEnabler.getId() == LwM2mId.SECURITY || objectEnabler.getId() == LwM2mId.SERVER) { + Boolean isBootstrapServer = objectEnabler.getId() == LwM2mId.SECURITY + && ServersInfoExtractor.isBootstrapServer(objectEnabler, instanceId); + if (isBootstrapServer != null && !isBootstrapServer) { + Long shortServerId = ServersInfoExtractor.getServerId(objectEnabler, instanceId); + if (shortServerId != null) + objectAttributes + .add(LwM2mAttributes.create(LwM2mAttributes.SHORT_SERVER_ID, shortServerId)); + } - } + } - // get uri - if (objectEnabler.getId() == LwM2mId.SECURITY) { - String uri = ServersInfoExtractor.getServerURI(objectEnabler, instanceId); - if (uri != null) - objectAttributes.add(LwM2mAttributes.create(LwM2mAttributes.SERVER_URI, uri)); + // get uri + if (objectEnabler.getId() == LwM2mId.SECURITY) { + String uri = ServersInfoExtractor.getServerURI(objectEnabler, instanceId); + if (uri != null) + objectAttributes.add(LwM2mAttributes.create(LwM2mAttributes.SERVER_URI, uri)); + } } // create link diff --git a/leshan-client-core/src/test/java/org/eclipse/leshan/client/util/LinkFormatHelperTest.java b/leshan-client-core/src/test/java/org/eclipse/leshan/client/util/LinkFormatHelperTest.java index dd3a1f28b8..5713b4f6c9 100644 --- a/leshan-client-core/src/test/java/org/eclipse/leshan/client/util/LinkFormatHelperTest.java +++ b/leshan-client-core/src/test/java/org/eclipse/leshan/client/util/LinkFormatHelperTest.java @@ -24,7 +24,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import org.eclipse.leshan.client.object.Oscore; import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.client.object.Server; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; @@ -42,7 +44,9 @@ import org.eclipse.leshan.core.link.lwm2m.DefaultLwM2mLinkParser; import org.eclipse.leshan.core.model.ObjectLoader; import org.eclipse.leshan.core.model.ObjectModel; +import org.eclipse.leshan.core.oscore.OscoreSetting; import org.eclipse.leshan.core.request.ContentFormat; +import org.eclipse.leshan.core.util.Hex; import org.junit.Test; public class LinkFormatHelperTest { @@ -120,7 +124,7 @@ public void encode_client_description_with_version_1_0() { Map instancesMap = new HashMap<>(); instancesMap.put(0, new BaseInstanceEnabler()); - objectEnablers.add(new ObjectEnabler(6, getObjectModel(6), instancesMap, null, ContentFormat.DEFAULT)); + objectEnablers.add(createObjectEnabler(getObjectModel(6), instancesMap)); Link[] links = LinkFormatHelper.getClientDescription(objectEnablers, null, null); String strLinks = serializer.serializeCoreLinkFormat(links); @@ -135,8 +139,7 @@ public void encode_client_description_with_version_2_0() { Map instancesMap = new HashMap<>(); instancesMap.put(0, new BaseInstanceEnabler()); instancesMap.put(1, new BaseInstanceEnabler()); - objectEnablers.add( - new ObjectEnabler(6, getVersionedObjectModel(6, "2.0"), instancesMap, null, ContentFormat.DEFAULT)); + objectEnablers.add(createObjectEnabler(getVersionedObjectModel(6, "2.0"), instancesMap)); Link[] links = LinkFormatHelper.getClientDescription(objectEnablers, null, null); String strLinks = serializer.serializeCoreLinkFormat(links); @@ -149,8 +152,7 @@ public void encode_client_description_with_version_2_0_no_instances() { List objectEnablers = new ArrayList<>(); Map instancesMap = new HashMap<>(); - objectEnablers.add( - new ObjectEnabler(6, getVersionedObjectModel(6, "2.0"), instancesMap, null, ContentFormat.DEFAULT)); + objectEnablers.add(createObjectEnabler(getVersionedObjectModel(6, "2.0"), instancesMap)); Link[] links = LinkFormatHelper.getClientDescription(objectEnablers, null, null); String strLinks = serializer.serializeCoreLinkFormat(links); @@ -162,8 +164,7 @@ public void encode_client_description_with_version_2_0_no_instances() { public void encode_1_content_format() throws LinkParseException { List objectEnablers = new ArrayList<>(); Map instancesMap = Collections.emptyMap(); - objectEnablers.add( - new ObjectEnabler(6, getVersionedObjectModel(6, "1.0"), instancesMap, null, ContentFormat.DEFAULT)); + objectEnablers.add(createObjectEnabler(getVersionedObjectModel(6, "1.0"), instancesMap)); Link[] links = LinkFormatHelper.getClientDescription(objectEnablers, null, Arrays.asList(ContentFormat.TLV)); @@ -174,21 +175,20 @@ public void encode_1_content_format() throws LinkParseException { public void encode_several_content_format() throws LinkParseException { List objectEnablers = new ArrayList<>(); Map instancesMap = Collections.emptyMap(); - objectEnablers.add( - new ObjectEnabler(6, getVersionedObjectModel(6, "1.0"), instancesMap, null, ContentFormat.DEFAULT)); + objectEnablers.add(createObjectEnabler(getVersionedObjectModel(6, "1.0"), instancesMap)); Link[] links = LinkFormatHelper.getClientDescription(objectEnablers, null, Arrays.asList(ContentFormat.TLV, ContentFormat.JSON, ContentFormat.OPAQUE)); - assertArrayEquals(parser.parseCoreLinkFormat(";rt=\"oma.lwm2m\";ct=\"11542 11543 42\",".getBytes()), links); + assertArrayEquals(parser.parseCoreLinkFormat(";rt=\"oma.lwm2m\";ct=\"11542 11543 42\",".getBytes()), + links); } @Test public void encode_bootstrap_object() { Map instancesMap = new HashMap<>(); instancesMap.put(0, new BaseInstanceEnabler()); - ObjectEnabler objectEnabler = new ObjectEnabler(3, getObjectModel(3), instancesMap, null, - ContentFormat.DEFAULT); + LwM2mObjectEnabler objectEnabler = createObjectEnabler(getObjectModel(3), instancesMap); Link[] links = LinkFormatHelper.getBootstrapObjectDescription(objectEnabler); String strLinks = serializer.serializeCoreLinkFormat(links); @@ -199,8 +199,7 @@ public void encode_bootstrap_object() { @Test public void encode_bootstrap_object_with_version_and_no_instance() { Map instancesMap = new HashMap<>(); - ObjectEnabler objectEnabler = new ObjectEnabler(3, getVersionedObjectModel(3, "2.0"), instancesMap, null, - ContentFormat.DEFAULT); + LwM2mObjectEnabler objectEnabler = createObjectEnabler(getVersionedObjectModel(3, "2.0"), instancesMap); Link[] links = LinkFormatHelper.getBootstrapObjectDescription(objectEnabler); String strLinks = serializer.serializeCoreLinkFormat(links); @@ -212,8 +211,7 @@ public void encode_bootstrap_object_with_version_and_no_instance() { public void encode_bootstrap_object_with_version_and_instance() { Map instancesMap = new HashMap<>(); instancesMap.put(0, new BaseInstanceEnabler()); - ObjectEnabler objectEnabler = new ObjectEnabler(3, getVersionedObjectModel(3, "2.0"), instancesMap, null, - ContentFormat.DEFAULT); + LwM2mObjectEnabler objectEnabler = createObjectEnabler(getVersionedObjectModel(3, "2.0"), instancesMap); Link[] links = LinkFormatHelper.getBootstrapObjectDescription(objectEnabler); String strLinks = serializer.serializeCoreLinkFormat(links); @@ -225,8 +223,7 @@ public void encode_bootstrap_object_with_version_and_instance() { public void encode_bootstrap_server_object() { Map instancesMap = new HashMap<>(); instancesMap.put(0, new Server(333, 120)); - ObjectEnabler objectEnabler = new ObjectEnabler(1, getObjectModel(1), instancesMap, null, - ContentFormat.DEFAULT); + LwM2mObjectEnabler objectEnabler = createObjectEnabler(getObjectModel(1), instancesMap); Link[] links = LinkFormatHelper.getBootstrapObjectDescription(objectEnabler); String strLinks = serializer.serializeCoreLinkFormat(links); @@ -238,8 +235,7 @@ public void encode_bootstrap_server_object() { public void encode_bootstrap_server_object_with_version() { Map instancesMap = new HashMap<>(); instancesMap.put(0, new Server(333, 120)); - ObjectEnabler objectEnabler = new ObjectEnabler(1, getVersionedObjectModel(1, "2.0"), instancesMap, null, - ContentFormat.DEFAULT); + LwM2mObjectEnabler objectEnabler = createObjectEnabler(getVersionedObjectModel(1, "2.0"), instancesMap); Link[] links = LinkFormatHelper.getBootstrapObjectDescription(objectEnabler); String strLinks = serializer.serializeCoreLinkFormat(links); @@ -254,7 +250,7 @@ public void encode_bootstrap_security_object() { instancesMap.put(1, Security.noSecBootstap("coap://localhost:1")); instancesMap.put(2, Security.noSec("coap://localhost:22", 222)); instancesMap.put(3, Security.noSec("coap://localhost:33", 333)); - ObjectEnabler objectEnabler = new ObjectEnabler(0, getObjectModel(0), instancesMap, null, + LwM2mObjectEnabler objectEnabler = new ObjectEnabler(0, getObjectModel(0), instancesMap, null, ContentFormat.DEFAULT); Link[] links = LinkFormatHelper.getBootstrapObjectDescription(objectEnabler); @@ -278,27 +274,25 @@ public void encode_bootstrap_root() { securityInstances.put(1, Security.noSecBootstap("coap://localhost:1")); securityInstances.put(2, Security.noSec("coap://localhost:22", 222)); securityInstances.put(3, Security.noSec("coap://localhost:33", 333)); - ObjectEnabler securityObjectEnabler = new ObjectEnabler(0, getObjectModel(0), securityInstances, null, - ContentFormat.DEFAULT); + LwM2mObjectEnabler securityObjectEnabler = createObjectEnabler(getObjectModel(0), securityInstances); objectEnablers.add(securityObjectEnabler); // object 1 Map serverInstances = new HashMap<>(); serverInstances.put(0, new Server(333, 120)); - ObjectEnabler serverObjectEnabler = new ObjectEnabler(1, getVersionedObjectModel(1, "2.0"), serverInstances, - null, ContentFormat.DEFAULT); + LwM2mObjectEnabler serverObjectEnabler = createObjectEnabler(getVersionedObjectModel(1, "2.0"), + serverInstances); objectEnablers.add(serverObjectEnabler); // object 2 - ObjectEnabler aclObjectEnabler = new ObjectEnabler(2, getVersionedObjectModel(2, "2.0"), - new HashMap(), null, ContentFormat.DEFAULT); + LwM2mObjectEnabler aclObjectEnabler = createObjectEnabler(getVersionedObjectModel(2, "2.0"), + new HashMap()); objectEnablers.add(aclObjectEnabler); // object 3 Map deviceInstances = new HashMap<>(); deviceInstances.put(0, new BaseInstanceEnabler()); - ObjectEnabler deviceObjectEnabler = new ObjectEnabler(3, getObjectModel(3), deviceInstances, null, - ContentFormat.DEFAULT); + LwM2mObjectEnabler deviceObjectEnabler = createObjectEnabler(getObjectModel(3), deviceInstances); objectEnablers.add(deviceObjectEnabler); Link[] links = LinkFormatHelper.getBootstrapClientDescription(objectEnablers); @@ -312,6 +306,54 @@ public void encode_bootstrap_root() { + ";ver=2.0,;ssid=333,;ver=2.0,", strLinks); } + @Test + public void encode_bootstrap_root_with_oscore() { + List objectEnablers = new ArrayList<>(); + + // object 0 + Map securityInstances = new HashMap<>(); + securityInstances.put(0, Security.oscoreOnly("coap://localhost:11", 111, 10)); + securityInstances.put(1, Security.oscoreOnlyBootstrap("coap://localhost:1", 11)); + securityInstances.put(2, Security.noSec("coap://localhost:22", 222)); + LwM2mObjectEnabler securityObjectEnabler = createObjectEnabler(getObjectModel(0, "1.2"), securityInstances); + objectEnablers.add(securityObjectEnabler); + + // object 1 + Map serverInstances = new HashMap<>(); + serverInstances.put(0, new Server(333, 120)); + LwM2mObjectEnabler serverObjectEnabler = createObjectEnabler(getVersionedObjectModel(1, "2.0"), + serverInstances); + objectEnablers.add(serverObjectEnabler); + + // object 3 + Map deviceInstances = new HashMap<>(); + deviceInstances.put(0, new BaseInstanceEnabler()); + LwM2mObjectEnabler deviceObjectEnabler = createObjectEnabler(getObjectModel(3), deviceInstances); + objectEnablers.add(deviceObjectEnabler); + + // object 21 + Map oscoreInstances = new HashMap<>(); + oscoreInstances.put(10, new Oscore(10, new OscoreSetting(Hex.decodeHex("AA".toCharArray()), + Hex.decodeHex("BB".toCharArray()), Hex.decodeHex("CC".toCharArray())))); + oscoreInstances.put(11, new Oscore(11, new OscoreSetting(Hex.decodeHex("11".toCharArray()), + Hex.decodeHex("22".toCharArray()), Hex.decodeHex("33".toCharArray())))); + LwM2mObjectEnabler oscoreObjectEnabler = createObjectEnabler(getObjectModel(21, "2.0"), oscoreInstances); + objectEnablers.add(oscoreObjectEnabler); + + Link[] links = LinkFormatHelper.getBootstrapClientDescription(objectEnablers); + String strLinks = serializer.serializeCoreLinkFormat(links); + + // TODO : handle version correctly + assertEquals(";lwm2m=1.0,;ver=1.2,;ssid=111;uri=\"coap://localhost:11\"," // + + ";uri=\"coap://localhost:1\"," // + + ";ssid=222;uri=\"coap://localhost:22\"," // + + ";ver=2.0,;ssid=333,," // + + ";ver=2.0," // + + ";ssid=111;uri=\"coap://localhost:11\"," // + + ";uri=\"coap://localhost:1\"" // + , strLinks); + } + private ObjectModel getObjectModel(int id) { List objectModels = ObjectLoader.loadDefault(LwM2mVersion.V1_0); for (ObjectModel objectModel : objectModels) { @@ -334,6 +376,39 @@ private ObjectModel getVersionedObjectModel(int id, String version) { return null; } + private ObjectModel getObjectModel(int id, String version) { + List objectModels = ObjectLoader.loadAllDefault(); + for (ObjectModel om : objectModels) { + if (om.id == id && version.equals(om.version)) + return om; + } + return null; + } + + /** + * create a objectEnabler with 1 instance of the given model + */ + private LwM2mObjectEnabler createObjectEnabler(ObjectModel objectModel, + Map instances) { + + // create factory + BaseInstanceEnablerFactory factory = new BaseInstanceEnablerFactory() { + @Override + public LwM2mInstanceEnabler create() { + return new DummyInstanceEnabler(); + } + }; + + // set-up instances + for (Entry instanceEntry : instances.entrySet()) { + instanceEntry.getValue().setId(instanceEntry.getKey()); + instanceEntry.getValue().setModel(objectModel); + } + + // create enabler; + return new ObjectEnabler(objectModel.id, objectModel, instances, factory, ContentFormat.DEFAULT); + } + /** * create a objectEnabler with 1 instance of the given model */ diff --git a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/DefaultEndpointFactory.java b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/DefaultEndpointFactory.java index bbbb12e6da..2ad7436709 100644 --- a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/DefaultEndpointFactory.java +++ b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/DefaultEndpointFactory.java @@ -104,7 +104,6 @@ protected EndpointContextMatcher createUnsecuredContextMatcher() { @Override public CoapEndpoint createUnsecuredEndpoint(InetSocketAddress address, Configuration coapConfig, ObservationStore store, OSCoreCtxDB db) { - // TODO OSCORE : db should maybe be replaced by OscoreEStore Builder builder = createUnsecuredEndpointBuilder(address, coapConfig, store); if (db != null) { builder.setCustomCoapStackArgument(db).setCoapStackFactory(new OSCoreCoapStackFactory()); @@ -157,7 +156,7 @@ protected Connector createUnsecuredConnector(InetSocketAddress address, Configur @Override public CoapEndpoint createSecuredEndpoint(DtlsConnectorConfig dtlsConfig, Configuration coapConfig, ObservationStore store, OSCoreCtxDB db) { - // TODO OSCORE : db should maybe be replaced by OscoreEStore + // TODO OSCORE : add support of OSCORE to secured (DTLS) endpoint. return createSecuredEndpointBuilder(dtlsConfig, coapConfig, store).build(); } diff --git a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/oscore/cf/InMemoryOscoreContextDB.java b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/oscore/cf/InMemoryOscoreContextDB.java index b3697a6924..fb09392d53 100644 --- a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/oscore/cf/InMemoryOscoreContextDB.java +++ b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/oscore/cf/InMemoryOscoreContextDB.java @@ -24,12 +24,11 @@ import org.slf4j.LoggerFactory; /** - * An {@link OSCoreCtxDB} which store context in memory and is able to derive context from data provided in - * {@link OscoreStore} + * An {@link OSCoreCtxDB} which store context in memory and is able to derive context from {@link OscoreParameters} + * provided in {@link OscoreStore} * */ // TODO OSCORE this should be moved in californium. -// TODO OSCORE I don't know if we need to extends HashMapCtxDB or implement OSCoreCtxDB public class InMemoryOscoreContextDB extends HashMapCtxDB { private static final Logger LOG = LoggerFactory.getLogger(InMemoryOscoreContextDB.class); @@ -99,13 +98,14 @@ public synchronized OSCoreCtx getContext(String uri) throws OSException { private static OSCoreCtx deriveContext(OscoreParameters oscoreParameters) { try { - OSCoreCtx osCoreCtx = new OSCoreCtx(oscoreParameters.getMasterSecret(), true, oscoreParameters.getAeadAlgorithm(), - oscoreParameters.getSenderId(), oscoreParameters.getRecipientId(), oscoreParameters.getHmacAlgorithm(), 32, + OSCoreCtx osCoreCtx = new OSCoreCtx(oscoreParameters.getMasterSecret(), true, + oscoreParameters.getAeadAlgorithm(), oscoreParameters.getSenderId(), + oscoreParameters.getRecipientId(), oscoreParameters.getHmacAlgorithm(), 32, oscoreParameters.getMasterSalt(), null, 1000); osCoreCtx.setContextRederivationEnabled(true); return osCoreCtx; } catch (OSException e) { - LOG.error("Unable to derive context from OscoreParameters %s", oscoreParameters, e); + LOG.error("Unable to derive context from {}", oscoreParameters, e); return null; } } diff --git a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/oscore/cf/OscoreParameters.java b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/oscore/cf/OscoreParameters.java index 6fa16536e5..a84fe4824a 100644 --- a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/oscore/cf/OscoreParameters.java +++ b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/oscore/cf/OscoreParameters.java @@ -72,7 +72,7 @@ public byte[] getMasterSalt() { public String toString() { // Note : oscoreMasterSecret and oscoreMasterSalt are explicitly excluded from the display for security // purposes - return String.format("OscoreSetting [senderId=%s, recipientId=%s, aeadAlgorithm=%s, hmacAlgorithm=%s]", + return String.format("OscoreParameters [senderId=%s, recipientId=%s, aeadAlgorithm=%s, hmacAlgorithm=%s]", Hex.encodeHexString(senderId), Hex.encodeHexString(recipientId), aeadAlgorithm, hmacAlgorithm); } diff --git a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/oscore/cf/StaticOscoreStore.java b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/oscore/cf/StaticOscoreStore.java index 955d82d806..9a57790bc2 100644 --- a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/oscore/cf/StaticOscoreStore.java +++ b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/oscore/cf/StaticOscoreStore.java @@ -40,5 +40,4 @@ public OscoreParameters getOscoreParameters(byte[] recipientID) { public byte[] getRecipientId(String uri) { return parameters.getRecipientId(); } - } diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/LwM2mId.java b/leshan-core/src/main/java/org/eclipse/leshan/core/LwM2mId.java index e25bc2b032..402278f072 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/LwM2mId.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/LwM2mId.java @@ -48,12 +48,12 @@ public interface LwM2mId { /* OSCORE RESOURCES */ - public static final int OSCORE_Master_Secret = 0; - public static final int OSCORE_Sender_ID = 1; - public static final int OSCORE_Recipient_ID = 2; - public static final int OSCORE_AEAD_Algorithm = 3; - public static final int OSCORE_HMAC_Algorithm = 4; - public static final int OSCORE_Master_Salt = 5; + public static final int OSCORE_MASTER_SECRET = 0; + public static final int OSCORE_SENDER_ID = 1; + public static final int OSCORE_RECIPIENT_ID = 2; + public static final int OSCORE_AEAD_ALGORITHM = 3; + public static final int OSCORE_HMAC_ALGORITHM = 4; + public static final int OSCORE_MASTER_SALT = 5; /* SERVER RESOURCES */ diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/model/ObjectLoader.java b/leshan-core/src/main/java/org/eclipse/leshan/core/model/ObjectLoader.java index 7871d23264..a4e384ba3c 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/model/ObjectLoader.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/model/ObjectLoader.java @@ -38,7 +38,7 @@ public class ObjectLoader { static final String[] ddfpaths = new String[] { "0-1_0.xml", "0-1_1.xml", "0.xml", "1-1_0.xml", "1-1_1.xml", "2-1_0.xml", "2.xml", "3-1_0.xml", "3-1_1.xml", "3.xml", "4-1_0.xml", "4-1_1.xml", "4-1_2.xml", "4.xml", - "5-1_0.xml", "5.xml", "6.xml", "7.xml", "21-1_0.xml", "21.xml" }; + "5-1_0.xml", "5.xml", "6.xml", "7.xml", "21.xml" }; /** * Load last embedded version of default LWM2M objects. So the list contain only one model by object. diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/oscore/AeadAlgorithm.java b/leshan-core/src/main/java/org/eclipse/leshan/core/oscore/AeadAlgorithm.java new file mode 100644 index 0000000000..ae3a1768d9 --- /dev/null +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/oscore/AeadAlgorithm.java @@ -0,0 +1,131 @@ +package org.eclipse.leshan.core.oscore; + +import java.io.Serializable; +import java.util.Arrays; + +import org.eclipse.leshan.core.util.datatype.NumberUtil; + +/** + * Some utility method about code AEAD Algoritm as defined at https://datatracker.ietf.org/doc/html/rfc8152#section-10 + */ +public class AeadAlgorithm implements Serializable { + private static final long serialVersionUID = 1L; // TODO not sure we will keep SecurityInfo serializable + + public static final AeadAlgorithm AES_GCM_128 = new AeadAlgorithm("A128GCM", 1, 12); // + public static final AeadAlgorithm AES_GCM_192 = new AeadAlgorithm("A192GCM", 2, 12); // + public static final AeadAlgorithm AES_GCM_256 = new AeadAlgorithm("A256GCM", 3, 12); // + public static final AeadAlgorithm AES_CCM_16_64_128 = new AeadAlgorithm("AES-CCM-16-64-128", 10, 13); // + public static final AeadAlgorithm AES_CCM_16_64_256 = new AeadAlgorithm("AES-CCM-16-64-256", 11, 13); // + public static final AeadAlgorithm AES_CCM_64_64_128 = new AeadAlgorithm("AES-CCM-64-64-128", 12, 7); // + public static final AeadAlgorithm AES_CCM_64_64_256 = new AeadAlgorithm("AES-CCM-64-64-256", 13, 7); // + public static final AeadAlgorithm AES_CCM_16_128_128 = new AeadAlgorithm("AES-CCM-16-128-128", 30, 13); // + public static final AeadAlgorithm AES_CCM_16_128_256 = new AeadAlgorithm("AES-CCM-16-128-256", 31, 13); // + public static final AeadAlgorithm AES_CCM_64_128_128 = new AeadAlgorithm("AES-CCM-64-128-128", 32, 7); // + public static final AeadAlgorithm AES_CCM_64_128_256 = new AeadAlgorithm("AES-CCM-64-128-256", 33, 7); // + + public static final AeadAlgorithm knownAeadAlgorithms[] = new AeadAlgorithm[] { // + AES_GCM_128, AES_GCM_192, AES_GCM_256, // + AES_CCM_16_64_128, AES_CCM_16_64_256, AES_CCM_64_64_128, AES_CCM_64_64_256, // + AES_CCM_16_128_128, AES_CCM_16_128_256, AES_CCM_64_128_128, AES_CCM_64_128_256 }; + + private final String name; + private final int value; + private final int nonceSize; // in bytes + + public AeadAlgorithm(String name, int value, int nonceSize) { + this.name = name; + this.value = value; + this.nonceSize = nonceSize; + } + + /** + * @return {@link AeadAlgorithm} with the given value. + */ + public static AeadAlgorithm fromValue(int value) { + for (AeadAlgorithm alg : knownAeadAlgorithms) { + if (alg.value == value) + return alg; + } + return new AeadAlgorithm("UNKNOWN", value, 0); + } + + /** + * @return {@link AeadAlgorithm} with the given value. + */ + public static AeadAlgorithm fromValue(long value) { + try { + int intValue = NumberUtil.longToInt(value); + return fromValue(intValue); + } catch (IllegalArgumentException e) { + if (value >= 0) + return fromValue(Integer.MAX_VALUE); + else + return fromValue(Integer.MIN_VALUE); + } + } + + /** + * @return {@link AeadAlgorithm} with the given name, return null if this {@link AeadAlgorithm} is not known. + */ + public static AeadAlgorithm fromName(String name) { + for (AeadAlgorithm alg : knownAeadAlgorithms) { + if (alg.name.equals(name)) + return alg; + } + return null; + } + + public int getValue() { + return value; + } + + /** + * @return nonce size in bytes. + */ + public int getNonceSize() { + return nonceSize; + } + + /** + * @return true is this is a known {@link AeadAlgorithm} + */ + public boolean isKnown() { + return Arrays.asList(knownAeadAlgorithms).contains(this); + } + + @Override + public String toString() { + return String.format("%s(%d)", name, value); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + nonceSize; + result = prime * result + value; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AeadAlgorithm other = (AeadAlgorithm) obj; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + if (nonceSize != other.nonceSize) + return false; + if (value != other.value) + return false; + return true; + } +} diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/oscore/HkdfAlgorithm.java b/leshan-core/src/main/java/org/eclipse/leshan/core/oscore/HkdfAlgorithm.java new file mode 100644 index 0000000000..43a059aa1f --- /dev/null +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/oscore/HkdfAlgorithm.java @@ -0,0 +1,83 @@ +package org.eclipse.leshan.core.oscore; + +import java.io.Serializable; +import java.util.Arrays; + +import org.eclipse.leshan.core.util.datatype.NumberUtil; + +/** + * Some utility method about code HKDF Algoritm as defined at https://datatracker.ietf.org/doc/html/rfc8152#section-11.1 + * and https://datatracker.ietf.org/doc/html/rfc8152#section-12.1.2 + */ +public class HkdfAlgorithm implements Serializable { + private static final long serialVersionUID = 1L; // TODO not sure we will keep SecurityInfo serializable + + public static final HkdfAlgorithm HKDF_HMAC_SHA_256 = new HkdfAlgorithm("HKDF-SHA-256", -10);// + public static final HkdfAlgorithm HKDF_HMAC_SHA_512 = new HkdfAlgorithm("HKDF-SHA-512", -11); // + public static final HkdfAlgorithm HKDF_HMAC_AES_128 = new HkdfAlgorithm("HKDF-AES-128", -12); // + public static final HkdfAlgorithm HKDF_HMAC_AES_256 = new HkdfAlgorithm("HKDF-AES-256", -13); // + + public static final HkdfAlgorithm knownHkdfAlgorithms[] = new HkdfAlgorithm[] { // + HKDF_HMAC_SHA_256, HKDF_HMAC_SHA_512, HKDF_HMAC_AES_128, HKDF_HMAC_AES_256 }; + + private final String name; + private final int value; + + public HkdfAlgorithm(String name, int value) { + this.name = name; + this.value = value; + } + + /** + * @return {@link HkdfAlgorithm} with the given value. + */ + public static HkdfAlgorithm fromValue(int value) { + for (HkdfAlgorithm alg : knownHkdfAlgorithms) { + if (alg.value == value) + return alg; + } + return new HkdfAlgorithm("UNKNOWN", value); + } + + /** + * @return {@link HkdfAlgorithm} with the given name, return null if this {@link HkdfAlgorithm} is not known. + */ + public static HkdfAlgorithm fromName(String name) { + for (HkdfAlgorithm alg : knownHkdfAlgorithms) { + if (alg.name.equals(name)) + return alg; + } + return null; + } + + /** + * @return {@link HkdfAlgorithm} with the given value. + */ + public static HkdfAlgorithm fromValue(long value) { + try { + int intValue = NumberUtil.longToInt(value); + return fromValue(intValue); + } catch (IllegalArgumentException e) { + if (value >= 0) + return fromValue(Integer.MAX_VALUE); + else + return fromValue(Integer.MIN_VALUE); + } + } + + public int getValue() { + return value; + } + + /** + * @return true is this is a known {@link HkdfAlgorithm} + */ + public boolean isKnown() { + return Arrays.asList(knownHkdfAlgorithms).contains(this); + } + + @Override + public String toString() { + return String.format("%s(%d)", name, value); + } +} diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/oscore/InvalidOscoreSettingException.java b/leshan-core/src/main/java/org/eclipse/leshan/core/oscore/InvalidOscoreSettingException.java new file mode 100644 index 0000000000..fbc5a0adbf --- /dev/null +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/oscore/InvalidOscoreSettingException.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2022 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.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.core.oscore; + +public class InvalidOscoreSettingException extends Exception { + + private static final long serialVersionUID = 1L; + + public InvalidOscoreSettingException(String message) { + super(message); + } + + public InvalidOscoreSettingException(String message, Object... args) { + super(String.format(message, args)); + } + + public InvalidOscoreSettingException(Exception e, String message, Object... args) { + super(String.format(message, args), e); + } + + public InvalidOscoreSettingException(String message, Exception cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/oscore/OscoreIdentity.java b/leshan-core/src/main/java/org/eclipse/leshan/core/oscore/OscoreIdentity.java index b122ed8c39..7e4b691f97 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/oscore/OscoreIdentity.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/oscore/OscoreIdentity.java @@ -16,14 +16,13 @@ package org.eclipse.leshan.core.oscore; import java.io.Serializable; -import java.nio.charset.StandardCharsets; import java.util.Arrays; import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.core.util.Validate; /** - * An OSCORE identifier for a foreign peer. + * An OSCORE Object identifying a foreign peer. * */ public class OscoreIdentity implements Serializable { @@ -39,16 +38,10 @@ public OscoreIdentity(byte[] recipientId) { this.recipientId = recipientId; } - // TODO OSCORE not sure we need this constructor - public OscoreIdentity(String senderId, String recipientId) { - this.recipientId = new Hex().decode(recipientId.getBytes(StandardCharsets.UTF_8)); - } - public byte[] getRecipientId() { return recipientId; } - // TODO OSCORE: Generate toString() in Eclipse. @Override public String toString() { return String.format("OscoreIdentity [%s]", Hex.encodeHexString(recipientId)); diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/oscore/OscoreSetting.java b/leshan-core/src/main/java/org/eclipse/leshan/core/oscore/OscoreSetting.java similarity index 62% rename from leshan-server-core/src/main/java/org/eclipse/leshan/server/security/oscore/OscoreSetting.java rename to leshan-core/src/main/java/org/eclipse/leshan/core/oscore/OscoreSetting.java index a1c3c9921d..2dd6cca361 100644 --- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/oscore/OscoreSetting.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/oscore/OscoreSetting.java @@ -13,32 +13,57 @@ * Contributors: * Sierra Wireless - initial API and implementation *******************************************************************************/ -package org.eclipse.leshan.server.security.oscore; +package org.eclipse.leshan.core.oscore; import java.io.Serializable; import java.util.Arrays; -import org.eclipse.leshan.core.oscore.OscoreIdentity; import org.eclipse.leshan.core.util.Hex; +import org.eclipse.leshan.core.util.Validate; +/** + * OSCORE Settings. + *

+ * See : https://datatracker.ietf.org/doc/html/rfc8613#section-3.2 + */ public class OscoreSetting implements Serializable { private static final long serialVersionUID = 1L; + public static final AeadAlgorithm DEFAULT_AEAD_ALGORITHM = AeadAlgorithm.AES_CCM_16_64_128; + public static final HkdfAlgorithm DEFAULT_HKDF_ALGORITHM = HkdfAlgorithm.HKDF_HMAC_SHA_256; + public static final byte[] DEFAULT_MASTER_SALT = new byte[0]; + private final byte[] senderId; private final byte[] recipientId; private final byte[] masterSecret; - private final Integer aeadAlgorithm; - private final Integer hmacAlgorithm; + private final AeadAlgorithm aeadAlgorithm; + private final HkdfAlgorithm hkdfAlgorithm; private final byte[] masterSalt; + public OscoreSetting(byte[] senderId, byte[] recipientId, byte[] masterSecret) { + this(senderId, recipientId, masterSecret, (AeadAlgorithm) null, null, null); + } + public OscoreSetting(byte[] senderId, byte[] recipientId, byte[] masterSecret, Integer aeadAlgorithm, - Integer hmacAlgorithm, byte[] masterSalt) { + Integer hkdfAlgorithm, byte[] masterSalt) { + this(senderId, recipientId, masterSecret, // + aeadAlgorithm == null ? null : AeadAlgorithm.fromValue(aeadAlgorithm), // + hkdfAlgorithm == null ? null : HkdfAlgorithm.fromValue(hkdfAlgorithm), // + masterSalt); + } + + public OscoreSetting(byte[] senderId, byte[] recipientId, byte[] masterSecret, AeadAlgorithm aeadAlgorithm, + HkdfAlgorithm hkdfAlgorithm, byte[] masterSalt) { + Validate.notNull(senderId); + Validate.notNull(recipientId); + Validate.notNull(masterSecret); + this.senderId = senderId; this.recipientId = recipientId; this.masterSecret = masterSecret; - this.aeadAlgorithm = aeadAlgorithm; - this.hmacAlgorithm = hmacAlgorithm; - this.masterSalt = masterSalt; + this.aeadAlgorithm = aeadAlgorithm == null ? DEFAULT_AEAD_ALGORITHM : aeadAlgorithm; + this.hkdfAlgorithm = hkdfAlgorithm == null ? DEFAULT_HKDF_ALGORITHM : hkdfAlgorithm; + this.masterSalt = masterSalt == null ? DEFAULT_MASTER_SALT : masterSalt; } public byte[] getSenderId() { @@ -53,12 +78,12 @@ public byte[] getMasterSecret() { return masterSecret; } - public Integer getAeadAlgorithm() { + public AeadAlgorithm getAeadAlgorithm() { return aeadAlgorithm; } - public Integer getHmacAlgorithm() { - return hmacAlgorithm; + public HkdfAlgorithm getHkdfAlgorithm() { + return hkdfAlgorithm; } public byte[] getMasterSalt() { @@ -73,8 +98,8 @@ public OscoreIdentity getOscoreIdentity() { public String toString() { // Note : oscoreMasterSecret and oscoreMasterSalt are explicitly excluded from the display for security // purposes - return String.format("OscoreSetting [senderId=%s, recipientId=%s, aeadAlgorithm=%s, hmacAlgorithm=%s]", - Hex.encodeHexString(senderId), Hex.encodeHexString(recipientId), aeadAlgorithm, hmacAlgorithm); + return String.format("OscoreSetting [senderId=%s, recipientId=%s, aeadAlgorithm=%s, hkdfsAlgorithm=%s]", + Hex.encodeHexString(senderId), Hex.encodeHexString(recipientId), aeadAlgorithm, hkdfAlgorithm); } @Override @@ -82,7 +107,7 @@ public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((aeadAlgorithm == null) ? 0 : aeadAlgorithm.hashCode()); - result = prime * result + ((hmacAlgorithm == null) ? 0 : hmacAlgorithm.hashCode()); + result = prime * result + ((hkdfAlgorithm == null) ? 0 : hkdfAlgorithm.hashCode()); result = prime * result + Arrays.hashCode(masterSalt); result = prime * result + Arrays.hashCode(masterSecret); result = prime * result + Arrays.hashCode(recipientId); @@ -104,10 +129,10 @@ public boolean equals(Object obj) { return false; } else if (!aeadAlgorithm.equals(other.aeadAlgorithm)) return false; - if (hmacAlgorithm == null) { - if (other.hmacAlgorithm != null) + if (hkdfAlgorithm == null) { + if (other.hkdfAlgorithm != null) return false; - } else if (!hmacAlgorithm.equals(other.hmacAlgorithm)) + } else if (!hkdfAlgorithm.equals(other.hkdfAlgorithm)) return false; if (!Arrays.equals(masterSalt, other.masterSalt)) return false; diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/oscore/OscoreValidator.java b/leshan-core/src/main/java/org/eclipse/leshan/core/oscore/OscoreValidator.java new file mode 100644 index 0000000000..6b4e9ea5b0 --- /dev/null +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/oscore/OscoreValidator.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2022 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.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.core.oscore; + +import java.util.Arrays; +import java.util.List; + +import org.eclipse.leshan.core.util.Hex; + +public class OscoreValidator { + + public void validateOscoreSetting(OscoreSetting oscoreSetting) throws InvalidOscoreSettingException { + byte[] senderId = oscoreSetting.getSenderId(); + byte[] recipientId = oscoreSetting.getRecipientId(); + byte[] masterSecret = oscoreSetting.getMasterSecret(); + AeadAlgorithm aeadAlgorithm = oscoreSetting.getAeadAlgorithm(); + HkdfAlgorithm hkdfAlgorithm = oscoreSetting.getHkdfAlgorithm(); + + // Validate that Algorithm are known. + if (!aeadAlgorithm.isKnown()) { + throw new InvalidOscoreSettingException("Unkown AEAD Algorithm (%s) : known AEAD Algorithm are %s", + aeadAlgorithm, Arrays.toString(AeadAlgorithm.knownAeadAlgorithms)); + } + if (!hkdfAlgorithm.isKnown()) { + throw new InvalidOscoreSettingException("Unkown HKDF Algorithm (%s) : known HKDF Algorithm are %s", + hkdfAlgorithm, Arrays.toString(HkdfAlgorithm.knownHkdfAlgorithms)); + } + + // Validate senderId and recipient id length + // see : https://datatracker.ietf.org/doc/html/rfc8613#section-3.3 + // The maximum length of Sender ID in bytes equals the length of the AEAD nonce minus 6. + // The Sender IDs can be very short (note that the empty string is a legitimate value). + int nonceSize = aeadAlgorithm.getNonceSize(); + int maxLength = nonceSize - 6; + if (senderId.length > maxLength) { + throw new InvalidOscoreSettingException("Invalid Sender ID (%s) : max length for % algorithm is %s", + Hex.encodeHexString(senderId), aeadAlgorithm, maxLength); + } + if (recipientId.length > maxLength) { + throw new InvalidOscoreSettingException("Invalid Recipient ID (%s) : max length for % algorithm is %s", + Hex.encodeHexString(recipientId), aeadAlgorithm, maxLength); + } + // Validate master key. + if (masterSecret.length == 0) { + throw new InvalidOscoreSettingException("Invalid Master Secret : can not be an empty String"); + } + + // Temporary code check for supported Algorithm + List supportedAeadAlgorithm = Arrays.asList(AeadAlgorithm.AES_CCM_16_64_128, + AeadAlgorithm.AES_CCM_16_128_128, AeadAlgorithm.AES_CCM_64_64_128, AeadAlgorithm.AES_CCM_64_128_128); + if (!supportedAeadAlgorithm.contains(aeadAlgorithm)) { + throw new InvalidOscoreSettingException("Invalid AEAD Algorithm (%s) : currently only %s are supported.", + aeadAlgorithm, supportedAeadAlgorithm); + } + + List supportedHkdfAlgorithm = Arrays.asList(HkdfAlgorithm.HKDF_HMAC_SHA_256, + HkdfAlgorithm.HKDF_HMAC_SHA_512); + if (!supportedHkdfAlgorithm.contains(hkdfAlgorithm)) { + throw new InvalidOscoreSettingException("Invalid HKDF Algorithm (%s) : currently only %s are supported.", + hkdfAlgorithm, supportedHkdfAlgorithm); + } + } +} diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/util/datatype/NumberUtil.java b/leshan-core/src/main/java/org/eclipse/leshan/core/util/datatype/NumberUtil.java index 435020d239..a0bee5cab4 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/util/datatype/NumberUtil.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/util/datatype/NumberUtil.java @@ -194,4 +194,17 @@ public static Double numberToDouble(Number number, boolean permissiveNumberConve throw new IllegalArgumentException(String.format("Floating-point number expected but was %s (%s)", number, number.getClass().getCanonicalName())); } + + /** + * Convert the given long to integer without loss. + * + * @throws IllegalArgumentException if the long can not be store in an integer. + */ + public static int longToInt(long longValue) { + int intValue = (int) longValue; + if (intValue != longValue) { + throw new IllegalArgumentException(String.format("%d cannot be cast to int.", longValue)); + } + return intValue; + } } diff --git a/leshan-core/src/main/resources/models/21-1_0.xml b/leshan-core/src/main/resources/models/21-1_0.xml deleted file mode 100644 index 0f50879a4b..0000000000 --- a/leshan-core/src/main/resources/models/21-1_0.xml +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - LWM2M OSCORE - - 21 - urn:oma:lwm2m:oma:211.1 - 1.0Multiple - Optional - - OSCORE Master Secret - - Single - Mandatory - String - - - - - OSCORE Sender ID - - Single - Mandatory - String - - - - - OSCORE Recipient ID - - Single - Mandatory - String - - - - - OSCORE AEAD Algorithm - - Single - Optional - Integer - - - - - OSCORE HMAC Algorithm - - Single - Optional - Integer - - - - - OSCORE Master Salt - - Single - Optional - String - - - - - - - diff --git a/leshan-core/src/main/resources/models/21.xml b/leshan-core/src/main/resources/models/21.xml index fffe0a8943..5685648bad 100644 --- a/leshan-core/src/main/resources/models/21.xml +++ b/leshan-core/src/main/resources/models/21.xml @@ -4,7 +4,7 @@ FILE INFORMATION OMA Permanent Document - File: OMA-SUP-XML_21-V1_1-20201110-A.xml + File: OMA-SUP-XML_21-V2_0-20211123-A.xml Path: http://www.openmobilealliance.org/release/ObjLwM2M_OSCORE/ OMNA LwM2M Registry @@ -13,17 +13,11 @@ OMNA LwM2M Registry NORMATIVE INFORMATION - Information about this file can be found in the latest revision of - - OMA-TS-LightweightM2M_Core-V1_2 - - This is available at http://www.openmobilealliance.org/release/LightweightM2M/ - Send comments to https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues LEGAL DISCLAIMER - Copyright 2020 Open Mobile Alliance. + Copyright 2021 Open Mobile Alliance. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -63,9 +57,9 @@ LEGAL DISCLAIMER 21 - urn:oma:lwm2m:oma:21:1.1 + urn:oma:lwm2m:oma:21:2.0 1.1 - 1.1 + 2.0 Multiple Optional @@ -73,7 +67,7 @@ Instances of this Object are linked from Instances of Object 0 using the OSCORE Single Mandatory - String + Opaque @@ -82,7 +76,7 @@ Instances of this Object are linked from Instances of Object 0 using the OSCORE Single Mandatory - String + Opaque @@ -91,7 +85,7 @@ Instances of this Object are linked from Instances of Object 0 using the OSCORE Single Mandatory - String + Opaque @@ -119,7 +113,7 @@ Instances of this Object are linked from Instances of Object 0 using the OSCORE Single Optional - String + Opaque @@ -128,7 +122,7 @@ Instances of this Object are linked from Instances of Object 0 using the OSCORE Single Optional - String + Opaque diff --git a/leshan-core/src/test/java/org/eclipse/leshan/core/datatype/NumberUtilTest.java b/leshan-core/src/test/java/org/eclipse/leshan/core/datatype/NumberUtilTest.java index 6a82366034..d12f67c75c 100644 --- a/leshan-core/src/test/java/org/eclipse/leshan/core/datatype/NumberUtilTest.java +++ b/leshan-core/src/test/java/org/eclipse/leshan/core/datatype/NumberUtilTest.java @@ -212,4 +212,9 @@ public void double_too_big_for_ulong() { public void bigdecimal_to_big_for_ulong() { numberToULong(new BigDecimal("18446744073709551616")); } + + @Test(expected = IllegalArgumentException.class) + public void too_long_to_int() { + longToInt(2147483648l); + } } diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapTest.java index 3ee4993997..7284b0243b 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapTest.java @@ -231,11 +231,10 @@ public void bootstrapWithDiscoverOnRoot() { assertTrue(helper.lastCustomResponse instanceof BootstrapDiscoverResponse); BootstrapDiscoverResponse lastDiscoverAnswer = (BootstrapDiscoverResponse) helper.lastCustomResponse; assertEquals(ResponseCode.CONTENT, lastDiscoverAnswer.getCode()); - assertEquals( - String.format( - ";lwm2m=1.0,;ver=1.1,;uri=\"coap://%s:%d\",;ver=1.1,;ver=1.1,,", - helper.bootstrapServer.getUnsecuredAddress().getHostString(), - helper.bootstrapServer.getUnsecuredAddress().getPort()), + assertEquals(String.format( + ";lwm2m=1.0,;ver=1.1,;uri=\"coap://%s:%d\",;ver=1.1,;ver=1.1,,;ver=2.0", + helper.bootstrapServer.getUnsecuredAddress().getHostString(), + helper.bootstrapServer.getUnsecuredAddress().getPort()), linkSerializer.serializeCoreLinkFormat(lastDiscoverAnswer.getObjectLinks())); } @@ -263,11 +262,10 @@ public void bootstrapWithDiscoverOnRootThenRebootstrap() throws InvalidRequestEx assertTrue(helper.lastCustomResponse instanceof BootstrapDiscoverResponse); BootstrapDiscoverResponse lastDiscoverAnswer = (BootstrapDiscoverResponse) helper.lastCustomResponse; assertEquals(ResponseCode.CONTENT, lastDiscoverAnswer.getCode()); - assertEquals( - String.format( - ";lwm2m=1.0,;ver=1.1,;uri=\"coap://%s:%d\",;ver=1.1,;ver=1.1,,", - helper.bootstrapServer.getUnsecuredAddress().getHostString(), - helper.bootstrapServer.getUnsecuredAddress().getPort()), + assertEquals(String.format( + ";lwm2m=1.0,;ver=1.1,;uri=\"coap://%s:%d\",;ver=1.1,;ver=1.1,,;ver=2.0", + helper.bootstrapServer.getUnsecuredAddress().getHostString(), + helper.bootstrapServer.getUnsecuredAddress().getPort()), linkSerializer.serializeCoreLinkFormat(lastDiscoverAnswer.getObjectLinks())); // re-bootstrap @@ -288,7 +286,7 @@ public void bootstrapWithDiscoverOnRootThenRebootstrap() throws InvalidRequestEx lastDiscoverAnswer = (BootstrapDiscoverResponse) helper.lastCustomResponse; assertEquals(ResponseCode.CONTENT, lastDiscoverAnswer.getCode()); assertEquals(String.format( - ";lwm2m=1.0,;ver=1.1,;uri=\"coap://%s:%d\",;ssid=2222;uri=\"coap://%s:%d\",;ver=1.1,;ssid=2222,;ver=1.1,,", + ";lwm2m=1.0,;ver=1.1,;uri=\"coap://%s:%d\",;ssid=2222;uri=\"coap://%s:%d\",;ver=1.1,;ssid=2222,;ver=1.1,,;ver=2.0", helper.bootstrapServer.getUnsecuredAddress().getHostString(), helper.bootstrapServer.getUnsecuredAddress().getPort(), helper.server.getUnsecuredAddress().getHostString(), helper.server.getUnsecuredAddress().getPort()), @@ -657,7 +655,7 @@ public void bootstrapToRPKServer() throws NonUniqueSecurityInfoException { @Test public void bootstrapUnsecuredToServerWithOscore() throws NonUniqueSecurityInfoException { - helper.createServer(); + helper.createOscoreServer(); helper.server.start(); helper.createBootstrapServer(null, helper.unsecuredBootstrapStoreWithOscoreServer()); @@ -667,7 +665,8 @@ public void bootstrapUnsecuredToServerWithOscore() throws NonUniqueSecurityInfoE helper.createClient(); helper.assertClientNotRegisterered(); - helper.getSecurityStore().add(SecurityInfo.newOSCoreInfo(helper.getCurrentEndpoint(), getOscoreSetting())); + helper.getSecurityStore() + .add(SecurityInfo.newOscoreInfo(helper.getCurrentEndpoint(), getServerOscoreSetting())); // Start it and wait for registration helper.client.start(); @@ -680,10 +679,10 @@ public void bootstrapUnsecuredToServerWithOscore() throws NonUniqueSecurityInfoE @Test public void bootstrapViaOscoreToServerWithOscore() throws NonUniqueSecurityInfoException { - helper.createServer(); + helper.createOscoreServer(); helper.server.start(); - helper.createBootstrapServer(helper.bsSecurityStore(SecurityMode.NO_SEC), + helper.createOscoreBootstrapServer(helper.bsOscoreSecurityStore(), helper.oscoreBootstrapStoreWithOscoreServer()); helper.bootstrapServer.start(); @@ -691,7 +690,8 @@ public void bootstrapViaOscoreToServerWithOscore() throws NonUniqueSecurityInfoE helper.createOscoreOnlyBootstrapClient(); helper.assertClientNotRegisterered(); - helper.getSecurityStore().add(SecurityInfo.newOSCoreInfo(helper.getCurrentEndpoint(), getOscoreSetting())); + helper.getSecurityStore() + .add(SecurityInfo.newOscoreInfo(helper.getCurrentEndpoint(), getServerOscoreSetting())); // Start it and wait for registration helper.client.start(); @@ -707,7 +707,7 @@ public void bootstrapViaOscoreToUnsecuredServer() throws OSException { helper.createServer(); helper.server.start(); - helper.createBootstrapServer(helper.bsSecurityStore(SecurityMode.NO_SEC), + helper.createOscoreBootstrapServer(helper.bsOscoreSecurityStore(), helper.oscoreBootstrapStoreWithUnsecuredServer()); helper.bootstrapServer.start(); diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecurityTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecurityTest.java index 9dfe789c4c..c9d6d2014e 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecurityTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecurityTest.java @@ -108,12 +108,13 @@ public void registered_device_with_psk_to_server_with_psk() public void registered_device_with_oscore_to_server_with_oscore() throws NonUniqueSecurityInfoException, InterruptedException { - helper.createServer(); + helper.createOscoreServer(); helper.server.start(); helper.createOscoreClient(); - helper.getSecurityStore().add(SecurityInfo.newOSCoreInfo(helper.getCurrentEndpoint(), getOscoreSetting())); + helper.getSecurityStore() + .add(SecurityInfo.newOscoreInfo(helper.getCurrentEndpoint(), getServerOscoreSetting())); // Check client is not registered helper.assertClientNotRegisterered(); diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/BootstrapIntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/BootstrapIntegrationTestHelper.java index 4f644d225c..10f52b62b8 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/BootstrapIntegrationTestHelper.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/BootstrapIntegrationTestHelper.java @@ -58,6 +58,7 @@ import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder; import org.eclipse.leshan.core.oscore.OscoreIdentity; +import org.eclipse.leshan.core.oscore.OscoreSetting; import org.eclipse.leshan.core.request.BootstrapDownlinkRequest; import org.eclipse.leshan.core.request.BootstrapRequest; import org.eclipse.leshan.core.request.ContentFormat; @@ -82,7 +83,6 @@ import org.eclipse.leshan.server.security.EditableSecurityStore; import org.eclipse.leshan.server.security.SecurityChecker; import org.eclipse.leshan.server.security.SecurityInfo; -import org.eclipse.leshan.server.security.oscore.OscoreSetting; /** * Helper for running a server and executing a client against it. @@ -175,6 +175,22 @@ public void end(BootstrapSession bsSession) { return builder; } + public void createOscoreBootstrapServer(BootstrapSecurityStore securityStore, BootstrapConfigStore bootstrapStore) { + LeshanBootstrapServerBuilder builder = createBootstrapBuilder(securityStore, bootstrapStore); + builder.setEnableOscore(true); + if (bootstrapStore == null) { + bootstrapStore = unsecuredBootstrapStore(); + } + + if (securityStore == null) { + securityStore = dummyBsSecurityStore(); + } + builder.setSecurityStore(securityStore); + builder.setSessionManager(new TestBootstrapSessionManager(securityStore, bootstrapStore)); + bootstrapServer = builder.build(); + setupBootstrapServerMonitoring(); + } + public void createBootstrapServer(BootstrapSecurityStore securityStore, BootstrapConfigStore bootstrapStore) { LeshanBootstrapServerBuilder builder = createBootstrapBuilder(securityStore, bootstrapStore); if (bootstrapStore == null) { @@ -251,10 +267,10 @@ public void createOscoreOnlyBootstrapClient() { String bsServerUri = "coap://" + bootstrapServer.getUnsecuredAddress().getHostString() + ":" + bootstrapServer.getUnsecuredAddress().getPort(); - Oscore oscoreObject = getOscoreBootstrapClientObject(); + Oscore oscoreObject = new Oscore(12345, getBootstrapClientOscoreSetting()); ObjectsInitializer initializer = new TestObjectsInitializer(); initializer.setInstancesForObject(OSCORE, oscoreObject); - createClient(oscoreOnlyBootstrap(bsServerUri, null, oscoreObject.getId()), initializer); + createClient(oscoreOnlyBootstrap(bsServerUri, oscoreObject.getId()), initializer); } @Override @@ -340,6 +356,32 @@ public boolean isSupported(ContentFormat format) { setupClientMonitoring(); } + public BootstrapSecurityStore bsOscoreSecurityStore() { + return new BootstrapSecurityStore() { + + @Override + public Iterator getAllByEndpoint(String endpoint) { + if (getCurrentEndpoint().equals(endpoint)) { + return Arrays.asList(SecurityInfo.newOscoreInfo(endpoint, getServerOscoreSetting())).iterator(); + } + return null; + } + + @Override + public SecurityInfo getByIdentity(String pskIdentity) { + return null; + } + + @Override + public SecurityInfo getByOscoreIdentity(OscoreIdentity oscoreIdentity) { + if (oscoreIdentity.equals(getBootstrapServerOscoreSetting().getOscoreIdentity())) { + return oscoreSecurityInfo(); + } + return null; + } + }; + } + public BootstrapSecurityStore bsSecurityStore(final SecurityMode mode) { return new BootstrapSecurityStore() { @@ -363,10 +405,6 @@ public Iterator getAllByEndpoint(String endpoint) { } else if (mode == SecurityMode.RPK) { info = rpkSecurityInfo(); return Arrays.asList(info).iterator(); - } else if (mode == SecurityMode.NO_SEC) { - // Create the security info (will re-add the context to the db) - info = SecurityInfo.newOSCoreInfo(endpoint, getOscoreSetting()); - return Arrays.asList(info).iterator(); } } return null; @@ -374,9 +412,6 @@ public Iterator getAllByEndpoint(String endpoint) { @Override public SecurityInfo getByOscoreIdentity(OscoreIdentity oscoreIdentity) { - if (oscoreIdentity.equals(getBootstrapOscoreSetting().getOscoreIdentity())) { - return oscoreSecurityInfo(); - } return null; } }; @@ -394,7 +429,7 @@ public SecurityInfo rpkSecurityInfo() { } public SecurityInfo oscoreSecurityInfo() { - SecurityInfo info = SecurityInfo.newOSCoreInfo(getCurrentEndpoint(), getBootstrapOscoreSetting()); + SecurityInfo info = SecurityInfo.newOscoreInfo(getCurrentEndpoint(), getBootstrapServerOscoreSetting()); return info; } @@ -753,32 +788,27 @@ public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSe }; } - public static OscoreSetting getBootstrapOscoreSetting() { + public static OscoreSetting getBootstrapServerOscoreSetting() { return new OscoreSetting(OSCORE_BOOTSTRAP_RECIPIENT_ID, OSCORE_BOOTSTRAP_SENDER_ID, - OSCORE_BOOTSTRAP_MASTER_SECRET, OSCORE_ALGORITHM.AsCBOR().AsInt32(), - OSCORE_KDF_ALGORITHM.AsCBOR().AsInt32(), OSCORE_BOOTSTRAP_MASTER_SALT); + OSCORE_BOOTSTRAP_MASTER_SECRET, OSCORE_AEAD_ALGORITHM, OSCORE_HKDF_ALGORITHM, + OSCORE_BOOTSTRAP_MASTER_SALT); } - protected static Oscore getOscoreBootstrapClientObject() { - return new Oscore(12345, new String(Hex.encodeHex(OSCORE_BOOTSTRAP_MASTER_SECRET)), - new String(Hex.encodeHex(OSCORE_BOOTSTRAP_SENDER_ID)), - new String(Hex.encodeHex(OSCORE_BOOTSTRAP_RECIPIENT_ID)), OSCORE_ALGORITHM.AsCBOR().AsInt32(), - OSCORE_KDF_ALGORITHM.AsCBOR().AsInt32(), new String(Hex.encodeHex(OSCORE_BOOTSTRAP_MASTER_SALT))); + protected static OscoreSetting getBootstrapClientOscoreSetting() { + return new OscoreSetting(OSCORE_BOOTSTRAP_SENDER_ID, OSCORE_BOOTSTRAP_RECIPIENT_ID, + OSCORE_BOOTSTRAP_MASTER_SECRET, OSCORE_AEAD_ALGORITHM, OSCORE_HKDF_ALGORITHM, + OSCORE_BOOTSTRAP_MASTER_SALT); } protected static BootstrapConfig.OscoreObject getOscoreBootstrapObject(boolean bootstrap) { BootstrapConfig.OscoreObject oscoreObject = new BootstrapConfig.OscoreObject(); - oscoreObject.oscoreMasterSecret = new String( - Hex.encodeHex(bootstrap ? OSCORE_BOOTSTRAP_MASTER_SECRET : OSCORE_MASTER_SECRET)); - oscoreObject.oscoreSenderId = new String( - Hex.encodeHex(bootstrap ? OSCORE_BOOTSTRAP_SENDER_ID : OSCORE_SENDER_ID)); - oscoreObject.oscoreRecipientId = new String( - Hex.encodeHex(bootstrap ? OSCORE_BOOTSTRAP_RECIPIENT_ID : OSCORE_RECIPIENT_ID)); - oscoreObject.oscoreAeadAlgorithm = OSCORE_ALGORITHM.AsCBOR().AsInt32(); - oscoreObject.oscoreHmacAlgorithm = OSCORE_KDF_ALGORITHM.AsCBOR().AsInt32(); - oscoreObject.oscoreMasterSalt = new String( - Hex.encodeHex(bootstrap ? OSCORE_BOOTSTRAP_MASTER_SALT : OSCORE_MASTER_SALT)); + oscoreObject.oscoreMasterSecret = bootstrap ? OSCORE_BOOTSTRAP_MASTER_SECRET : OSCORE_MASTER_SECRET; + oscoreObject.oscoreSenderId = bootstrap ? OSCORE_BOOTSTRAP_SENDER_ID : OSCORE_SENDER_ID; + oscoreObject.oscoreRecipientId = bootstrap ? OSCORE_BOOTSTRAP_RECIPIENT_ID : OSCORE_RECIPIENT_ID; + oscoreObject.oscoreAeadAlgorithm = OSCORE_AEAD_ALGORITHM.getValue(); + oscoreObject.oscoreHmacAlgorithm = OSCORE_HKDF_ALGORITHM.getValue(); + oscoreObject.oscoreMasterSalt = bootstrap ? OSCORE_BOOTSTRAP_MASTER_SALT : OSCORE_MASTER_SALT; return oscoreObject; } diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/IntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/IntegrationTestHelper.java index 80ec2151ca..68cb152c86 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/IntegrationTestHelper.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/IntegrationTestHelper.java @@ -165,6 +165,12 @@ public void createServer() { setupServerMonitoring(); } + public void createOscoreServer() { + server = createServerBuilder().setEnableOscore(true).build(); + // monitor client registration + setupServerMonitoring(); + } + protected LeshanServerBuilder createServerBuilder() { LeshanServerBuilder builder = new LeshanServerBuilder(); builder.setDecoder(new DefaultLwM2mDecoder(true)); diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/SecureIntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/SecureIntegrationTestHelper.java index 883c0b8dde..56fb485498 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/SecureIntegrationTestHelper.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/SecureIntegrationTestHelper.java @@ -50,7 +50,6 @@ import org.eclipse.californium.core.network.CoapEndpoint; import org.eclipse.californium.core.observe.ObservationStore; -import org.eclipse.californium.cose.AlgorithmID; import org.eclipse.californium.elements.config.Configuration; import org.eclipse.californium.oscore.OSCoreCtxDB; import org.eclipse.californium.scandium.DTLSConnector; @@ -75,6 +74,9 @@ import org.eclipse.leshan.core.CertificateUsage; import org.eclipse.leshan.core.LwM2mId; import org.eclipse.leshan.core.californium.EndpointFactory; +import org.eclipse.leshan.core.oscore.AeadAlgorithm; +import org.eclipse.leshan.core.oscore.HkdfAlgorithm; +import org.eclipse.leshan.core.oscore.OscoreSetting; import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.core.util.X509CertUtil; import org.eclipse.leshan.server.californium.LeshanServerBuilder; @@ -83,7 +85,6 @@ import org.eclipse.leshan.server.security.InMemorySecurityStore; import org.eclipse.leshan.server.security.SecurityChecker; import org.eclipse.leshan.server.security.SecurityStore; -import org.eclipse.leshan.server.security.oscore.OscoreSetting; public class SecureIntegrationTestHelper extends IntegrationTestHelper { @@ -99,8 +100,8 @@ public class SecureIntegrationTestHelper extends IntegrationTestHelper { public static final byte[] OSCORE_SENDER_ID = Hex.decodeHex("ABCDEF".toCharArray()); public static final byte[] OSCORE_RECIPIENT_ID = Hex.decodeHex("FEDCBA".toCharArray()); - public static final AlgorithmID OSCORE_ALGORITHM = AlgorithmID.AES_CCM_16_64_128; - public static final AlgorithmID OSCORE_KDF_ALGORITHM = AlgorithmID.HKDF_HMAC_SHA_256; + public static final AeadAlgorithm OSCORE_AEAD_ALGORITHM = AeadAlgorithm.AES_CCM_16_64_128; + public static final HkdfAlgorithm OSCORE_HKDF_ALGORITHM = HkdfAlgorithm.HKDF_HMAC_SHA_256; private SinglePSKStore singlePSKStore; protected SecurityStore securityStore; @@ -485,7 +486,7 @@ public void createOscoreClient() { String serverUri = "coap://" + server.getUnsecuredAddress().getHostString() + ":" + server.getUnsecuredAddress().getPort(); - Oscore oscoreObject = getOscoreClientObject(); + Oscore oscoreObject = new Oscore(12345, getClientOscoreSetting()); initializer.setInstancesForObject(SECURITY, oscoreOnly(serverUri, 12345, oscoreObject.getId())); initializer.setInstancesForObject(OSCORE, oscoreObject); @@ -502,16 +503,14 @@ public void createOscoreClient() { setupClientMonitoring(); } - public static OscoreSetting getOscoreSetting() { - return new OscoreSetting(OSCORE_RECIPIENT_ID, OSCORE_SENDER_ID, OSCORE_MASTER_SECRET, - OSCORE_ALGORITHM.AsCBOR().AsInt32(), OSCORE_KDF_ALGORITHM.AsCBOR().AsInt32(), OSCORE_MASTER_SALT); + public static OscoreSetting getServerOscoreSetting() { + return new OscoreSetting(OSCORE_RECIPIENT_ID, OSCORE_SENDER_ID, OSCORE_MASTER_SECRET, OSCORE_AEAD_ALGORITHM, + OSCORE_HKDF_ALGORITHM, OSCORE_MASTER_SALT); } - protected static Oscore getOscoreClientObject() { - return new Oscore(12345, new String(Hex.encodeHex(OSCORE_MASTER_SECRET)), - new String(Hex.encodeHex(OSCORE_SENDER_ID)), new String(Hex.encodeHex(OSCORE_RECIPIENT_ID)), - OSCORE_ALGORITHM.AsCBOR().AsInt32(), OSCORE_KDF_ALGORITHM.AsCBOR().AsInt32(), - new String(Hex.encodeHex(OSCORE_MASTER_SALT))); + protected static OscoreSetting getClientOscoreSetting() { + return new OscoreSetting(OSCORE_SENDER_ID, OSCORE_RECIPIENT_ID, OSCORE_MASTER_SECRET, OSCORE_AEAD_ALGORITHM, + OSCORE_HKDF_ALGORITHM, OSCORE_MASTER_SALT); } public PublicKey getServerPublicKey() { diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/TestObjectLoader.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/TestObjectLoader.java index 76ce716b5c..f2145bf12b 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/TestObjectLoader.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/TestObjectLoader.java @@ -40,7 +40,7 @@ public static List loadDefaultObject() { objectModels.add(repository.getObjectModel(LwM2mId.FIRMWARE, "1.0")); objectModels.add(repository.getObjectModel(LwM2mId.LOCATION, "1.0")); objectModels.add(repository.getObjectModel(LwM2mId.CONNECTIVITY_STATISTICS, "1.0")); - objectModels.add(repository.getObjectModel(LwM2mId.OSCORE, "1.0")); + objectModels.add(repository.getObjectModel(LwM2mId.OSCORE, "2.0")); // Test object 3442 objectModels.add(repository.getObjectModel(3442, "1.0")); diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/ConnectionCleaner.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/ConnectionCleaner.java index 0e81ddc94b..96181a10b4 100644 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/ConnectionCleaner.java +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/ConnectionCleaner.java @@ -49,7 +49,7 @@ public boolean accept(Principal principal) { // PSK if (info.usePSK() && principal instanceof PreSharedKeyIdentity) { String identity = ((PreSharedKeyIdentity) principal).getIdentity(); - if (info.getIdentity().equals(identity)) { + if (info.getPskIdentity().equals(identity)) { return true; } } 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 fa11c2062d..f04d108bbb 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 @@ -115,6 +115,8 @@ public class LeshanServerBuilder { protected boolean updateRegistrationOnNotification; private LwM2mLinkParser linkParser; + private boolean enableOscore = false; + /** *

* Set the address/port for unsecured CoAP Server. @@ -410,6 +412,16 @@ public LeshanServerBuilder setUpdateRegistrationOnNotification(boolean updateReg return this; } + /** + * Enable EXPERIMENTAL OSCORE feature. + *

+ * By default OSCORE is not enabled. + */ + public LeshanServerBuilder setEnableOscore(boolean enableOscore) { + this.enableOscore = enableOscore; + return this; + } + /** * The default Californium/CoAP {@link Configuration} used by the builder. */ @@ -552,10 +564,14 @@ public LeshanServer build() { LOG.warn("Unable to create DTLS config and so secured endpoint.", e); } } - // TODO OSCORE handle OSCORE + + // Handle OSCORE support. OSCoreCtxDB oscoreCtxDB = null; - if (securityStore != null) { - oscoreCtxDB = new InMemoryOscoreContextDB(new LwM2mOscoreStore(securityStore, registrationStore)); + if (enableOscore) { + LOG.warn("Experimental OSCORE feature is enabled."); + if (securityStore != null) { + oscoreCtxDB = new InMemoryOscoreContextDB(new LwM2mOscoreStore(securityStore, registrationStore)); + } } // create endpoints diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LwM2mOscoreStore.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LwM2mOscoreStore.java index b82c471544..6e0881e067 100644 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LwM2mOscoreStore.java +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LwM2mOscoreStore.java @@ -64,9 +64,14 @@ public OscoreParameters getOscoreParameters(byte[] recipientID) { securityInfo.getOscoreSetting().getSenderId(), // securityInfo.getOscoreSetting().getRecipientId(), // securityInfo.getOscoreSetting().getMasterSecret(), // - AlgorithmID.FromCBOR(CBORObject.FromObject(securityInfo.getOscoreSetting().getAeadAlgorithm())), // - AlgorithmID.FromCBOR(CBORObject.FromObject(securityInfo.getOscoreSetting().getHmacAlgorithm())), // - securityInfo.getOscoreSetting().getMasterSalt()); + // TODO OSCORE we maybe need an API without the need to create a CBOR Object + AlgorithmID.FromCBOR( + CBORObject.FromObject(securityInfo.getOscoreSetting().getAeadAlgorithm().getValue())), // + AlgorithmID.FromCBOR( + CBORObject.FromObject(securityInfo.getOscoreSetting().getHkdfAlgorithm().getValue())), // + // TODO OSCORE kind of hack because californium doesn't support an empty byte[] array for salt ? + securityInfo.getOscoreSetting().getMasterSalt().length == 0 ? null + : securityInfo.getOscoreSetting().getMasterSalt()); } catch (CoseException e) { LOG.error("Unable to create OscoreParameters from OoscoreSetting %s", securityInfo.getOscoreSetting(), e); return null; diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LwM2mPskStore.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LwM2mPskStore.java index 3ca64524e7..07339c3791 100644 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LwM2mPskStore.java +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LwM2mPskStore.java @@ -81,7 +81,7 @@ public PskPublicInformation getIdentity(InetSocketAddress peerAddress, ServerNam if (registration != null) { SecurityInfo securityInfo = securityStore.getByEndpoint(registration.getEndpoint()); if (securityInfo != null) { - return new PskPublicInformation(securityInfo.getIdentity()); + return new PskPublicInformation(securityInfo.getPskIdentity()); } return null; } diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/BootstrapResource.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/BootstrapResource.java index f277a489cc..54fc38d433 100644 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/BootstrapResource.java +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/BootstrapResource.java @@ -12,7 +12,6 @@ * * Contributors: * Sierra Wireless - initial API and implementation - * Rikard Höglund (RISE) - additions to support OSCORE *******************************************************************************/ package org.eclipse.leshan.server.californium.bootstrap; diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServer.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServer.java index 877d9434aa..a1cfb76da9 100644 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServer.java +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServer.java @@ -162,7 +162,6 @@ public void destroy() { } else if (requestSender instanceof Stoppable) { ((Stoppable) requestSender).stop(); } - LOG.info("Bootstrap server destroyed."); } diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerBuilder.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerBuilder.java index 5ad71a78f1..064c79fcff 100644 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerBuilder.java +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LeshanBootstrapServerBuilder.java @@ -105,6 +105,8 @@ public class LeshanBootstrapServerBuilder { private LwM2mLinkParser linkParser; + private boolean enableOscore = false; + /** * Set the address/port for unsecured CoAP communication (coap://). *

@@ -399,6 +401,16 @@ public LeshanBootstrapServerBuilder disableSecuredEndpoint() { return this; } + /** + * Enable EXPERIMENTAL OSCORE feature. + *

+ * By default OSCORE is not enabled. + */ + public LeshanBootstrapServerBuilder setEnableOscore(boolean enableOscore) { + this.enableOscore = enableOscore; + return this; + } + /** * Create the default CoAP/Californium {@link Configuration} used by the builder. *

@@ -548,12 +560,15 @@ public BootstrapHandler create(LwM2mBootstrapRequestSender sender, } } - // TODO OSCORE handle OSCORE + // Handle OSCORE support. OSCoreCtxDB oscoreCtxDB = null; OscoreBootstrapListener sessionHolder = null; - if (securityStore != null) { - sessionHolder = new OscoreBootstrapListener(); - oscoreCtxDB = new InMemoryOscoreContextDB(new LwM2mBootstrapOscoreStore(securityStore, sessionHolder)); + if (enableOscore) { + if (securityStore != null) { + sessionHolder = new OscoreBootstrapListener(); + oscoreCtxDB = new InMemoryOscoreContextDB(new LwM2mBootstrapOscoreStore(securityStore, sessionHolder)); + LOG.warn("Experimental OSCORE feature is enabled."); + } } CoapEndpoint unsecuredEndpoint = null; diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LwM2mBootstrapOscoreStore.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LwM2mBootstrapOscoreStore.java index d493b8a2f8..4cae0b88ea 100644 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LwM2mBootstrapOscoreStore.java +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/LwM2mBootstrapOscoreStore.java @@ -64,9 +64,14 @@ public OscoreParameters getOscoreParameters(byte[] recipientID) { securityInfo.getOscoreSetting().getSenderId(), // securityInfo.getOscoreSetting().getRecipientId(), // securityInfo.getOscoreSetting().getMasterSecret(), // - AlgorithmID.FromCBOR(CBORObject.FromObject(securityInfo.getOscoreSetting().getAeadAlgorithm())), // - AlgorithmID.FromCBOR(CBORObject.FromObject(securityInfo.getOscoreSetting().getHmacAlgorithm())), // - securityInfo.getOscoreSetting().getMasterSalt()); + // TODO OSCORE we maybe need an API without the need to create a CBOR Object + AlgorithmID.FromCBOR( + CBORObject.FromObject(securityInfo.getOscoreSetting().getAeadAlgorithm().getValue())), // + AlgorithmID.FromCBOR( + CBORObject.FromObject(securityInfo.getOscoreSetting().getHkdfAlgorithm().getValue())), // + // TODO OSCORE kind of hack because californium doesn't support an empty byte[] array for salt ? + securityInfo.getOscoreSetting().getMasterSalt().length == 0 ? null + : securityInfo.getOscoreSetting().getMasterSalt()); } catch (CoseException e) { LOG.error("Unable to create OscoreParameters from OoscoreSetting %s", securityInfo.getOscoreSetting(), e); return null; diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/registration/RegisterResource.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/registration/RegisterResource.java index f98d69ec1a..18be11cca6 100644 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/registration/RegisterResource.java +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/registration/RegisterResource.java @@ -13,7 +13,6 @@ * Contributors: * Sierra Wireless - initial API and implementation * Michał Wadowski (Orange) - Improved compliance with rfc6690 - * Rikard Höglund (RISE SICS) - Additions to support OSCORE *******************************************************************************/ package org.eclipse.leshan.server.californium.registration; diff --git a/leshan-server-core-demo/src/main/java/org/eclipse/leshan/server/core/demo/json/JacksonSecuritySerializer.java b/leshan-server-core-demo/src/main/java/org/eclipse/leshan/server/core/demo/json/JacksonSecuritySerializer.java index 2f45051e22..62723b7cd1 100644 --- a/leshan-server-core-demo/src/main/java/org/eclipse/leshan/server/core/demo/json/JacksonSecuritySerializer.java +++ b/leshan-server-core-demo/src/main/java/org/eclipse/leshan/server/core/demo/json/JacksonSecuritySerializer.java @@ -50,9 +50,9 @@ public void serialize(SecurityInfo src, JsonGenerator gen, SerializerProvider pr element.put("endpoint", src.getEndpoint()); - if (src.getIdentity() != null) { + if (src.getPskIdentity() != null) { Map psk = new HashMap<>(); - psk.put("identity", src.getIdentity()); + psk.put("identity", src.getPskIdentity()); psk.put("key", Hex.encodeHexString(src.getPreSharedKey())); element.put("psk", psk); } 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 37bd4a5196..d29fab1bb7 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 @@ -30,6 +30,7 @@ import org.eclipse.leshan.core.request.BindingMode; import org.eclipse.leshan.core.request.BootstrapDiscoverRequest; import org.eclipse.leshan.core.request.ContentFormat; +import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.core.util.datatype.ULong; /** @@ -400,17 +401,58 @@ public String toString() { } } - /** oscore configuration (object 17) */ - // TODO OSCORE : add some javadoc + /** + * OSCORE configuration (object 21) as defined in LWM2M 1.1.x TS. + *

+ * WARNING BootstrapConfig support OSCORE object since version 2.0 : + * https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues/521 + *

+ * This LwM2M Object provides the keying material and related information of a LwM2M Client appropriate to access a + * specified LwM2M Server using OSCORE. One Object Instance MAY address a LwM2M Bootstrap-Server. These LwM2M Object + * Resources MUST only be changed by a LwM2M Bootstrap-Server or Bootstrap from Smartcard and MUST NOT be accessible + * by any other LwM2M Server. Instances of this Object are linked from Instances of Object 0 using the OSCORE + * Security Mode Resource of Object 0. Instances of this Object MUST NOT be linked from more than one Instance of + * Object 0. + */ public static class OscoreObject implements Serializable { private static final long serialVersionUID = 1L; - public String oscoreMasterSecret = ""; - public String oscoreSenderId = ""; - public String oscoreRecipientId = ""; + /** + * This resource MUST be used to store the pre-shared key used in LwM2M Client and LwM2M + * Server/Bootstrap-Server, called the Master Secret. + */ + public byte[] oscoreMasterSecret = null; + /** + * This resource MUST store an OSCORE identifier for the LwM2M Client called the Sender ID. + */ + public byte[] oscoreSenderId = null; + /** + * This resource MUST store an OSCORE identifier for the LwM2M Client called the Recipient ID. + */ + public byte[] oscoreRecipientId = null; + /** + * This resource MUST be used to store the encoding of the AEAD Algorithm as defined in Table 10 of RFC 8152. + * The AEAD is used by OSCORE for encryption and integrity protection of CoAP message fields. + */ public Integer oscoreAeadAlgorithm = null; + /** + * This resource MUST be used to store the encoding of the HMAC Algorithm used in the HKDF. The encoding of HMAC + * algorithms are defined in Table 7 of RFC 8152. The HKDF is used to derive the security context used by + * OSCORE. + */ public Integer oscoreHmacAlgorithm = null; - public String oscoreMasterSalt = ""; + /** + * This resource MUST be used to store a non-secret random value called the Master Salt. The Master Salt is used + * to derive the security context used by OSCORE. + */ + public byte[] oscoreMasterSalt = null; + + // TODO OSCORE : not yet implemented + // /** + // * This resource MUST be used to store an OSCORE identifier called ID Context. This identifier is used to + // * identify the Common Context and derive the security context used by OSCORE. + // */ + // public byte[] oscoreContextId = null; @Override public String toString() { @@ -418,14 +460,14 @@ public String toString() { // purposes return String.format( "OscoreObject [oscoreSenderId=%s, oscoreRecipientId=%s, oscoreAeadAlgorithm=%s, oscoreHmacAlgorithm=%s]", - oscoreSenderId, oscoreRecipientId, oscoreAeadAlgorithm, oscoreHmacAlgorithm); + Hex.encodeHexString(oscoreSenderId), Hex.encodeHexString(oscoreRecipientId), oscoreAeadAlgorithm, + oscoreHmacAlgorithm); } } @Override public String toString() { - // TODO OSCORE : should we add OSCORE to toString ? or is it to sensitive data. - // this question remain for other config. - return String.format("BootstrapConfig [servers=%s, security=%s, acls=%s]", servers, security, acls); + return String.format("BootstrapConfig [servers=%s, security=%s, acls=%s, oscore=%s]", servers, security, acls, + oscore); } } diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapUtil.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapUtil.java index ee7a5868df..fdee12d258 100644 --- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapUtil.java +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapUtil.java @@ -161,17 +161,17 @@ public static LwM2mObjectInstance toOscoreInstance(int instanceId, OscoreObject Collection resources = new ArrayList<>(); if (oscoreConfig.oscoreMasterSecret != null) - resources.add(LwM2mSingleResource.newStringResource(0, oscoreConfig.oscoreMasterSecret)); + resources.add(LwM2mSingleResource.newBinaryResource(0, oscoreConfig.oscoreMasterSecret)); if (oscoreConfig.oscoreSenderId != null) - resources.add(LwM2mSingleResource.newStringResource(1, oscoreConfig.oscoreSenderId)); + resources.add(LwM2mSingleResource.newBinaryResource(1, oscoreConfig.oscoreSenderId)); if (oscoreConfig.oscoreRecipientId != null) - resources.add(LwM2mSingleResource.newStringResource(2, oscoreConfig.oscoreRecipientId)); + resources.add(LwM2mSingleResource.newBinaryResource(2, oscoreConfig.oscoreRecipientId)); if (oscoreConfig.oscoreAeadAlgorithm != null) resources.add(LwM2mSingleResource.newIntegerResource(3, oscoreConfig.oscoreAeadAlgorithm)); if (oscoreConfig.oscoreHmacAlgorithm != null) resources.add(LwM2mSingleResource.newIntegerResource(4, oscoreConfig.oscoreHmacAlgorithm)); if (oscoreConfig.oscoreMasterSalt != null) - resources.add(LwM2mSingleResource.newStringResource(5, oscoreConfig.oscoreMasterSalt)); + resources.add(LwM2mSingleResource.newBinaryResource(5, oscoreConfig.oscoreMasterSalt)); return new LwM2mObjectInstance(instanceId, resources); } 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 index a2d746dd30..f112f6270c 100644 --- 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 @@ -28,8 +28,12 @@ import org.eclipse.leshan.core.node.InvalidLwM2mPathException; import org.eclipse.leshan.core.node.LwM2mPath; +import org.eclipse.leshan.core.oscore.InvalidOscoreSettingException; +import org.eclipse.leshan.core.oscore.OscoreSetting; +import org.eclipse.leshan.core.oscore.OscoreValidator; import org.eclipse.leshan.core.util.SecurityUtil; import org.eclipse.leshan.core.util.StringUtils; +import org.eclipse.leshan.server.bootstrap.BootstrapConfig.OscoreObject; import org.eclipse.leshan.server.bootstrap.BootstrapConfig.ServerSecurity; /** @@ -38,6 +42,8 @@ */ public class ConfigurationChecker { + private OscoreValidator oscoreValidator = new OscoreValidator(); + /** * Verify if the {@link BootstrapConfig} is valid and consistent. *

@@ -54,19 +60,6 @@ public void verify(BootstrapConfig config) throws InvalidConfigurationException for (Map.Entry e : config.security.entrySet()) { BootstrapConfig.ServerSecurity sec = e.getValue(); - // Retrieve the OSCORE object for this bootstrap server security object - if (sec.bootstrapServer && sec.oscoreSecurityMode != null) { - BootstrapConfig.OscoreObject osc = config.oscore.get(sec.oscoreSecurityMode); - if (osc != null) { - assertIf(StringUtils.isEmpty(osc.oscoreMasterSecret), "master secret must not be empty"); - assertIf(StringUtils.isEmpty(osc.oscoreSenderId) && StringUtils.isEmpty(osc.oscoreRecipientId), - "either sender ID or recipient ID must be filled"); - } else { - throw new InvalidConfigurationException( - "Bootstrap server is set to use OSCORE, its OSCORE object must not be empty"); - } - } - // checks security config switch (sec.securityMode) { case NO_SEC: @@ -86,6 +79,13 @@ public void verify(BootstrapConfig config) throws InvalidConfigurationException } validateMandatoryField(sec); + + validateOscoreObjectExist(sec, config); + } + + // check oscore configuration + for (OscoreObject oscoreObject : config.oscore.values()) { + checkOscore(oscoreObject); } // does each server have a corresponding security entry? @@ -131,6 +131,26 @@ protected void checkX509(ServerSecurity sec) throws InvalidConfigurationExceptio "x509 mode, server public key must be DER encoded X.509 certificate"); } + protected void checkOscore(OscoreObject oscoreObject) throws InvalidConfigurationException { + OscoreSetting oscoreSetting = new OscoreSetting(oscoreObject.oscoreSenderId, oscoreObject.oscoreRecipientId, + oscoreObject.oscoreMasterSecret, oscoreObject.oscoreAeadAlgorithm, oscoreObject.oscoreHmacAlgorithm, + oscoreObject.oscoreMasterSalt); + + try { + oscoreValidator.validateOscoreSetting(oscoreSetting); + } catch (InvalidOscoreSettingException e) { + throw new InvalidConfigurationException(e, "oscore mode, invalid %s : %s", oscoreSetting, e.getMessage()); + } + } + + protected void validateOscoreObjectExist(ServerSecurity sec, BootstrapConfig config) + throws InvalidConfigurationException { + if (sec.oscoreSecurityMode != null) { + OscoreObject oscoreObject = config.oscore.get(sec.oscoreSecurityMode); + assertIf(isNull(oscoreObject), "oscore mode, no oscore Object with instance " + sec.oscoreSecurityMode); + } + } + protected void validatePath(List pathtoDelete) throws InvalidConfigurationException { for (String path : pathtoDelete) { try { @@ -214,6 +234,10 @@ protected static boolean isEmpty(byte[] array) { return array == null || array.length == 0; } + protected static boolean isNull(Object array) { + return array == null; + } + protected static BootstrapConfig.ServerSecurity getSecurityEntry(BootstrapConfig config, int shortId) { for (Map.Entry es : config.security.entrySet()) { if (!es.getValue().bootstrapServer && es.getValue().serverId == shortId) { 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 index 6a6c57d3cd..e1d4025bd3 100644 --- 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 @@ -59,9 +59,13 @@ public synchronized void add(String endpoint, BootstrapConfig config) throws Inv } } // TODO we should probably also check lwm2m server - // TODO OSCORE we should also check recipient ID uniqueness. + // About this uniqueness check above, + // This was introduced because of : https://github.com/eclipse/leshan/issues/322 + // but with https://github.com/eclipse/leshan/issues/1112 + // I'm not sure this still makes sense... + bootstrapByEndpoint.put(endpoint, config); if (pskToAdd != null) { bootstrapByPskId.put(pskToAdd, config); diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/InMemorySecurityStore.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/InMemorySecurityStore.java index 0f41185d57..5e3d38c98e 100644 --- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/InMemorySecurityStore.java +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/InMemorySecurityStore.java @@ -20,7 +20,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.Map.Entry; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -41,7 +40,10 @@ public class InMemorySecurityStore implements EditableSecurityStore { protected Map securityByEp = new HashMap<>(); // by PSK identity - protected Map securityByIdentity = new HashMap<>(); + protected Map securityByPskIdentity = new HashMap<>(); + + // by PSK oscoreIdentity + protected Map securityByOscoreIdentity = new HashMap<>(); private SecurityStoreListener listener; @@ -68,7 +70,7 @@ public SecurityInfo getByEndpoint(String endpoint) { public SecurityInfo getByIdentity(String identity) { readLock.lock(); try { - return securityByIdentity.get(identity); + return securityByPskIdentity.get(identity); } finally { readLock.unlock(); } @@ -79,15 +81,12 @@ public SecurityInfo getByIdentity(String identity) { */ @Override public SecurityInfo getByOscoreIdentity(OscoreIdentity oscoreIdentity) { - // TODO oscore add an index - for (Entry securityEntry : securityByEp.entrySet()) { - if (securityEntry.getValue().useOSCORE()) { - if (securityEntry.getValue().getOscoreSetting().getOscoreIdentity().equals(oscoreIdentity)) { - return securityEntry.getValue(); - } - } + readLock.lock(); + try { + return securityByOscoreIdentity.get(oscoreIdentity); + } finally { + readLock.unlock(); } - return null; } @Override @@ -104,20 +103,42 @@ public Collection getAll() { public SecurityInfo add(SecurityInfo info) throws NonUniqueSecurityInfoException { writeLock.lock(); try { - String identity = info.getIdentity(); - if (identity != null) { - SecurityInfo infoByIdentity = securityByIdentity.get(info.getIdentity()); - if (infoByIdentity != null && !info.getEndpoint().equals(infoByIdentity.getEndpoint())) { - throw new NonUniqueSecurityInfoException("PSK Identity " + info.getIdentity() + " is already used"); + // For PSK, check if PSK identity is not already used. + String pskIdentity = info.getPskIdentity(); + if (pskIdentity != null) { + SecurityInfo infoByPskIdentity = securityByPskIdentity.get(pskIdentity); + if (infoByPskIdentity != null && !info.getEndpoint().equals(infoByPskIdentity.getEndpoint())) { + throw new NonUniqueSecurityInfoException("PSK Identity " + pskIdentity + " is already used"); } + securityByPskIdentity.put(pskIdentity, info); + } - securityByIdentity.put(info.getIdentity(), info); + // For OSCORE, check if Oscore identity is not already used. + OscoreIdentity oscoreIdentity = info.getOscoreSetting() != null + ? info.getOscoreSetting().getOscoreIdentity() + : null; + if (oscoreIdentity != null) { + SecurityInfo infoByOscoreIdentity = securityByOscoreIdentity.get(oscoreIdentity); + if (infoByOscoreIdentity != null && !info.getEndpoint().equals(infoByOscoreIdentity.getEndpoint())) { + throw new NonUniqueSecurityInfoException("Oscore Identity " + oscoreIdentity + " is already used"); + } + securityByOscoreIdentity.put(oscoreIdentity, info); } + // Add new security info SecurityInfo previous = securityByEp.put(info.getEndpoint(), info); - String previousIdentity = previous == null ? null : previous.getIdentity(); - if (previousIdentity != null && !previousIdentity.equals(identity)) { - securityByIdentity.remove(previousIdentity); + + // For PSK, remove index by PSK Identity if needed + String previousPskIdentity = previous == null ? null : previous.getPskIdentity(); + if (previousPskIdentity != null && !previousPskIdentity.equals(pskIdentity)) { + securityByPskIdentity.remove(previousPskIdentity); + } + + // For OSCORE, remove index by OSCORE Identity if needed + OscoreIdentity previousOscoreIdentity = previous == null || previous.getOscoreSetting() == null ? null + : previous.getOscoreSetting().getOscoreIdentity(); + if (previousOscoreIdentity != null && !previousOscoreIdentity.equals(oscoreIdentity)) { + securityByOscoreIdentity.remove(previousOscoreIdentity); } return previous; @@ -132,8 +153,13 @@ public SecurityInfo remove(String endpoint, boolean infosAreCompromised) { try { SecurityInfo info = securityByEp.get(endpoint); if (info != null) { - if (info.getIdentity() != null) { - securityByIdentity.remove(info.getIdentity()); + // For PSK, remove index by PSK Identity if needed + if (info.getPskIdentity() != null) { + securityByPskIdentity.remove(info.getPskIdentity()); + } + // For OSCORE, remove index by OSCORE Identity if needed + if (info.getOscoreSetting() != null) { + securityByOscoreIdentity.remove(info.getOscoreSetting().getOscoreIdentity()); } securityByEp.remove(endpoint); if (listener != null) { diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/SecurityChecker.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/SecurityChecker.java index 762134c4f2..25b50e7581 100644 --- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/SecurityChecker.java +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/SecurityChecker.java @@ -64,7 +64,6 @@ public boolean checkSecurityInfos(String endpoint, Identity clientIdentity, Iter return false; } else { // check if one expected security info matches OSCORE client identity - LOG.trace("Checking incoming client's OSCORE identity."); do { SecurityInfo securityInfo = securityInfos.next(); if (checkSecurityInfo(endpoint, clientIdentity, securityInfo)) { @@ -115,7 +114,6 @@ public boolean checkSecurityInfo(String endpoint, Identity clientIdentity, Secur } } else { if (clientIdentity.isOSCORE()) { - LOG.trace("Checking incoming client's OSCORE identity."); return checkOscoreIdentity(endpoint, clientIdentity, securityInfo); } else if (securityInfo != null) { LOG.debug("Client '{}' must connect using DTLS", endpoint); @@ -133,7 +131,7 @@ protected boolean checkPskIdentity(String endpoint, Identity clientIdentity, Sec return false; } - if (!matchPskIdentity(endpoint, clientIdentity.getPskIdentity(), securityInfo.getIdentity())) { + if (!matchPskIdentity(endpoint, clientIdentity.getPskIdentity(), securityInfo.getPskIdentity())) { return false; } diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/SecurityInfo.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/SecurityInfo.java index ed75e7f090..d57ad01b24 100644 --- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/SecurityInfo.java +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/security/SecurityInfo.java @@ -20,9 +20,10 @@ import java.security.PublicKey; import java.util.Arrays; -import org.eclipse.leshan.core.util.Hex; +import org.eclipse.leshan.core.oscore.InvalidOscoreSettingException; +import org.eclipse.leshan.core.oscore.OscoreSetting; +import org.eclipse.leshan.core.oscore.OscoreValidator; import org.eclipse.leshan.core.util.Validate; -import org.eclipse.leshan.server.security.oscore.OscoreSetting; /** * The security info for a client. @@ -45,7 +46,7 @@ public class SecurityInfo implements Serializable { private final String endpoint; // PSK - private final String identity; + private final String pskIdentity; private final byte[] preSharedKey; // RPK @@ -54,14 +55,14 @@ public class SecurityInfo implements Serializable { // X.509 private final boolean useX509Cert; - // TODO OSCORE : Save content properly information here. Must be serializable. + // OSCORE private final OscoreSetting oscoreSetting; - private SecurityInfo(String endpoint, String identity, byte[] preSharedKey, PublicKey rawPublicKey, + private SecurityInfo(String endpoint, String pskIdentity, byte[] preSharedKey, PublicKey rawPublicKey, boolean useX509Cert, OscoreSetting oscoreSetting) { Validate.notEmpty(endpoint); this.endpoint = endpoint; - this.identity = identity; + this.pskIdentity = pskIdentity; this.preSharedKey = preSharedKey; this.rawPublicKey = rawPublicKey; this.useX509Cert = useX509Cert; @@ -112,23 +113,14 @@ public static SecurityInfo newX509CertInfo(String endpoint) { /** * Construct a {@link SecurityInfo} when using OSCORE. */ - // TODO OSCORE rename in newOscoreInfo - public static SecurityInfo newOSCoreInfo(String endpoint, OscoreSetting oscoreSetting) { + public static SecurityInfo newOscoreInfo(String endpoint, OscoreSetting oscoreSetting) { Validate.notNull(oscoreSetting); - return new SecurityInfo(endpoint, null, null, null, false, oscoreSetting); - } - - /** - * Generates an OSCORE identity from an OSCORE context - */ - private static String generateOscoreIdentity(OscoreSetting oscoreSetting) { - if (oscoreSetting == null) { - return null; + try { + new OscoreValidator().validateOscoreSetting(oscoreSetting); + } catch (InvalidOscoreSettingException e) { + throw new IllegalArgumentException("Invalid " + oscoreSetting, e); } - - String oscoreIdentity = "sid=" + Hex.encodeHexString(oscoreSetting.getSenderId()) + ",rid=" - + Hex.encodeHexString(oscoreSetting.getRecipientId()); - return oscoreIdentity; + return new SecurityInfo(endpoint, null, null, null, false, oscoreSetting); } /** @@ -142,13 +134,13 @@ public String getEndpoint() { * @return the Pre-Shared-Key identity or null if {@link #usePSK()} return false. * @see #getPreSharedKey() */ - public String getIdentity() { - return identity; + public String getPskIdentity() { + return pskIdentity; } /** * @return the Pre-Shared-Key or null if {@link #usePSK()} return false. - * @see #getIdentity() + * @see #getPskIdentity() */ public byte[] getPreSharedKey() { return preSharedKey; @@ -165,7 +157,7 @@ public OscoreSetting getOscoreSetting() { * @return true if this client should use PSK authentication. */ public boolean usePSK() { - return identity != null && preSharedKey != null; + return pskIdentity != null && preSharedKey != null; } /** @@ -201,7 +193,7 @@ public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((endpoint == null) ? 0 : endpoint.hashCode()); - result = prime * result + ((identity == null) ? 0 : identity.hashCode()); + result = prime * result + ((pskIdentity == null) ? 0 : pskIdentity.hashCode()); result = prime * result + Arrays.hashCode(preSharedKey); result = prime * result + ((rawPublicKey == null) ? 0 : rawPublicKey.hashCode()); result = prime * result + (useX509Cert ? 1231 : 1237); @@ -223,10 +215,10 @@ public boolean equals(Object obj) { return false; } else if (!endpoint.equals(other.endpoint)) return false; - if (identity == null) { - if (other.identity != null) + if (pskIdentity == null) { + if (other.pskIdentity != null) return false; - } else if (!identity.equals(other.identity)) + } else if (!pskIdentity.equals(other.pskIdentity)) return false; if (!Arrays.equals(preSharedKey, other.preSharedKey)) return false; @@ -252,7 +244,7 @@ public String toString() { // Note : preSharedKey is explicitly excluded from display for security purposes return String.format( "SecurityInfo [endpoint=%s, identity=%s, rawPublicKey=%s, useX509Cert=%s, oscoreIdentity=%s]", endpoint, - identity, rawPublicKey, useX509Cert, useOSCORE() ? getOscoreSetting().getOscoreIdentity() : ""); + pskIdentity, rawPublicKey, useX509Cert, useOSCORE() ? getOscoreSetting().getOscoreIdentity() : ""); } } diff --git a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisSecurityStore.java b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisSecurityStore.java index bffc5d7529..bf6a1c9bc1 100644 --- a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisSecurityStore.java +++ b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisSecurityStore.java @@ -106,19 +106,19 @@ public Collection getAll() { public SecurityInfo add(SecurityInfo info) throws NonUniqueSecurityInfoException { byte[] data = serialize(info); try (Jedis j = pool.getResource()) { - if (info.getIdentity() != null) { + if (info.getPskIdentity() != null) { // populate the secondary index (security info by PSK id) - String oldEndpoint = j.hget(PSKID_SEC, info.getIdentity()); + String oldEndpoint = j.hget(PSKID_SEC, info.getPskIdentity()); if (oldEndpoint != null && !oldEndpoint.equals(info.getEndpoint())) { - throw new NonUniqueSecurityInfoException("PSK Identity " + info.getIdentity() + " is already used"); + throw new NonUniqueSecurityInfoException("PSK Identity " + info.getPskIdentity() + " is already used"); } - j.hset(PSKID_SEC.getBytes(), info.getIdentity().getBytes(), info.getEndpoint().getBytes()); + j.hset(PSKID_SEC.getBytes(), info.getPskIdentity().getBytes(), info.getEndpoint().getBytes()); } byte[] previousData = j.getSet((SEC_EP + info.getEndpoint()).getBytes(), data); SecurityInfo previous = previousData == null ? null : deserialize(previousData); - String previousIdentity = previous == null ? null : previous.getIdentity(); - if (previousIdentity != null && !previousIdentity.equals(info.getIdentity())) { + String previousIdentity = previous == null ? null : previous.getPskIdentity(); + if (previousIdentity != null && !previousIdentity.equals(info.getPskIdentity())) { j.hdel(PSKID_SEC, previousIdentity); } @@ -133,8 +133,8 @@ public SecurityInfo remove(String endpoint, boolean infosAreCompromised) { if (data != null) { SecurityInfo info = deserialize(data); - if (info.getIdentity() != null) { - j.hdel(PSKID_SEC.getBytes(), info.getIdentity().getBytes()); + if (info.getPskIdentity() != null) { + j.hdel(PSKID_SEC.getBytes(), info.getPskIdentity().getBytes()); } j.del((SEC_EP + endpoint).getBytes()); if (listener != null) { diff --git a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/SecurityInfoSerDes.java b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/SecurityInfoSerDes.java index f7aac69b87..65f7aab664 100644 --- a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/SecurityInfoSerDes.java +++ b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/SecurityInfoSerDes.java @@ -48,8 +48,8 @@ public class SecurityInfoSerDes { public static byte[] serialize(SecurityInfo s) { ObjectNode o = JsonNodeFactory.instance.objectNode(); o.put("ep", s.getEndpoint()); - if (s.getIdentity() != null) { - o.put("id", s.getIdentity()); + if (s.getPskIdentity() != null) { + o.put("id", s.getPskIdentity()); } if (s.getPreSharedKey() != null) { o.put("psk", Hex.encodeHexString(s.getPreSharedKey()));