From 815300b516e561baa82858efaa95d73f92aa747e Mon Sep 17 00:00:00 2001 From: rikard-sics Date: Thu, 4 Apr 2019 10:11:47 +0200 Subject: [PATCH 01/15] #674: Initial support for OSCORE security object MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rikard Höglund --- .../eclipse/leshan/client/object/Oscore.java | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Oscore.java 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 new file mode 100644 index 0000000000..806266afd9 --- /dev/null +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Oscore.java @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright (c) 2015 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + * Rikard Höglund (RISE SICS) + * + *******************************************************************************/ +package org.eclipse.leshan.client.object; + +import org.eclipse.leshan.client.resource.BaseInstanceEnabler; +import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler; +import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.core.node.LwM2mResource; +import org.eclipse.leshan.core.response.ReadResponse; +import org.eclipse.leshan.core.response.WriteResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A simple {@link LwM2mInstanceEnabler} for the OSCORE Security (21) object. + */ +public class Oscore extends BaseInstanceEnabler { + + private static final Logger LOG = LoggerFactory.getLogger(Security.class); + + private byte[] masterSecret; + private String senderId; + private String recipientId; + private int aeadAlgorithm; + private int hmacAlgorithm; + + public Oscore() { + + } + + public Oscore(byte[] masterSecret, String senderId, String recipientId, int aeadAlgorithm, int hmacAlgorithm) { + super(); + this.masterSecret = masterSecret.clone(); + this.senderId = senderId; + this.recipientId = recipientId; + this.aeadAlgorithm = aeadAlgorithm; + this.hmacAlgorithm = hmacAlgorithm; + } + + @Override + public WriteResponse write(ServerIdentity identity, int resourceId, LwM2mResource value) { + LOG.debug("Write on resource {}: {}", resourceId, value); + // extend + return WriteResponse.notFound(); + } + + @Override + public ReadResponse read(ServerIdentity identity, int resourceid) { + LOG.debug("Read on resource {}", resourceid); + // extend + return ReadResponse.notFound(); + } + + public byte[] getMasterSecret() { + return masterSecret; + } + + public void setMasterSecret(byte[] masterSecret) { + this.masterSecret = masterSecret; + } + + public String getSenderId() { + return senderId; + } + + public void setSenderId(String senderId) { + this.senderId = senderId; + } + + public String getRecipientId() { + return recipientId; + } + + public void setRecipientId(String recipientId) { + this.recipientId = recipientId; + } + + public int getAeadAlgorithm() { + return aeadAlgorithm; + } + + public void setAeadAlgorithm(int aeadAlgorithm) { + this.aeadAlgorithm = aeadAlgorithm; + } + + public int getHmacAlgorithm() { + return hmacAlgorithm; + } + + public void setHmacAlgorithm(int hmacAlgorithm) { + this.hmacAlgorithm = hmacAlgorithm; + } + +} From 09a7a848d5cad7ea304fe359d4accf30534978c9 Mon Sep 17 00:00:00 2001 From: rikard-sics Date: Thu, 11 Jul 2019 09:50:36 +0200 Subject: [PATCH 02/15] Initial support for OSCORE communication between client and server MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds early support for using OSCORE to communicate between a Leshan client and server. The server can register and perform its request towards the server using OSCORE. Likewise the server can perform requests to the client using OSCORE. The web interface of the server has been extended to allow adding clients that use OSCORE for security including their OSCORE security context information. On the client side further work is needed to allow setting OSCORE security context information on it. Signed-off-by: Rikard Höglund --- leshan-client-cf/pom.xml | 5 ++ .../client/californium/OscoreHandler.java | 31 +++++++ .../CaliforniumLwM2mRequestSender.java | 29 ++++++ leshan-client-demo/pom.xml | 5 ++ .../leshan/client/demo/LeshanClientDemo.java | 59 +++++++++++- .../registration/RegisterResource.java | 23 +++++ .../californium/request/RequestSender.java | 33 +++++++ leshan-server-core/pom.xml | 5 ++ .../eclipse/leshan/server/OscoreHandler.java | 31 +++++++ .../leshan/server/security/SecurityInfo.java | 33 ++++++- .../leshan/server/demo/LeshanServerDemo.java | 25 ++++-- .../servlet/json/SecurityDeserializer.java | 77 ++++++++++++++++ .../demo/servlet/json/SecuritySerializer.java | 8 +- .../webapp/js/security-controllers.js | 9 +- .../webapp/partials/security-list.html | 90 +++++++++++++++++++ 15 files changed, 446 insertions(+), 17 deletions(-) create mode 100644 leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/OscoreHandler.java create mode 100644 leshan-server-core/src/main/java/org/eclipse/leshan/server/OscoreHandler.java diff --git a/leshan-client-cf/pom.xml b/leshan-client-cf/pom.xml index d2bf2d6617..9b5e23e8a5 100644 --- a/leshan-client-cf/pom.xml +++ b/leshan-client-cf/pom.xml @@ -46,6 +46,11 @@ Contributors: org.eclipse.californium scandium + + org.eclipse.californium + cf-oscore + ${californium.version} + diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/OscoreHandler.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/OscoreHandler.java new file mode 100644 index 0000000000..95579e9e31 --- /dev/null +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/OscoreHandler.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2015 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Rikard Höglund (RISE SICS) - Additions to support OSCORE + *******************************************************************************/ +package org.eclipse.leshan.client.californium; + +import org.eclipse.californium.oscore.HashMapCtxDB; + +//TODO OSCORE : remove this class and static access. +public class OscoreHandler { + + private static HashMapCtxDB db; + + public static HashMapCtxDB getContextDB() { + if (db == null) { + db = new HashMapCtxDB(); + } + return db; + } +} \ No newline at end of file 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 a568806d20..d448f1e8f8 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 @@ -12,6 +12,7 @@ * * Contributors: * Zebra Technologies - initial API and implementation + * Rikard Höglund (RISE SICS) - Additions to support OSCORE *******************************************************************************/ package org.eclipse.leshan.client.californium.request; @@ -22,7 +23,11 @@ import org.eclipse.californium.core.coap.MessageObserver; import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.coap.Response; +import org.eclipse.californium.elements.util.Bytes; +import org.eclipse.californium.oscore.HashMapCtxDB; +import org.eclipse.californium.oscore.OSException; import org.eclipse.leshan.client.californium.CaliforniumEndpointsManager; +import org.eclipse.leshan.client.californium.OscoreHandler; import org.eclipse.leshan.client.request.LwM2mRequestSender; import org.eclipse.leshan.client.servers.ServerIdentity; import org.eclipse.leshan.core.californium.AsyncRequestObserver; @@ -66,6 +71,18 @@ public T send(ServerIdentity server, final UplinkReque request.accept(coapClientRequestBuilder); Request coapRequest = coapClientRequestBuilder.getRequest(); + // TODO OSCORE : this should be added in CoapRequestBuilder + // Toggle OSCORE use in the request if the target URI of the request has an OSCORE context registered + HashMapCtxDB db = OscoreHandler.getContextDB(); + try { + if (db.getContext(coapRequest.getURI()) != null) { + coapRequest.getOptions().setOscore(Bytes.EMPTY); + } + } catch (OSException e) { + System.err.println("Failed to retrieve OSCORE Context for request"); + e.printStackTrace(); + } + // Send CoAP request synchronously SyncRequestObserver syncMessageObserver = new SyncRequestObserver(coapRequest, timeout) { @Override @@ -93,6 +110,18 @@ public void send(ServerIdentity server, final UplinkRe request.accept(coapClientRequestBuilder); Request coapRequest = coapClientRequestBuilder.getRequest(); + // TODO OSCORE : this should be added in CoapRequestBuilder + // Toggle OSCORE use in the request if the target URI of the request has an OSCORE context registered + HashMapCtxDB db = OscoreHandler.getContextDB(); + try { + if (db.getContext(coapRequest.getURI()) != null) { + coapRequest.getOptions().setOscore(Bytes.EMPTY); + } + } catch (OSException e) { + System.err.println("Failed to retrieve OSCORE Context for request"); + e.printStackTrace(); + } + // Add CoAP request callback MessageObserver obs = new AsyncRequestObserver(coapRequest, responseCallback, errorCallback, timeout, executor) { diff --git a/leshan-client-demo/pom.xml b/leshan-client-demo/pom.xml index a41b588dc8..a920585c5e 100644 --- a/leshan-client-demo/pom.xml +++ b/leshan-client-demo/pom.xml @@ -35,6 +35,11 @@ Contributors: commons-cli commons-cli + + org.eclipse.californium + cf-oscore + ${californium.version} + diff --git a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java index 1521a5f3cc..a56b51011a 100644 --- a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java +++ b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java @@ -23,13 +23,21 @@ import java.io.File; import java.net.InetAddress; +import java.net.URI; +import java.net.URISyntaxException; import java.net.UnknownHostException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.security.interfaces.ECPublicKey; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Scanner; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -41,8 +49,13 @@ import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.eclipse.californium.core.network.config.NetworkConfig; +import org.eclipse.californium.cose.AlgorithmID; import org.eclipse.californium.elements.Connector; import org.eclipse.californium.elements.util.SslContextUtil; +import org.eclipse.californium.oscore.HashMapCtxDB; +import org.eclipse.californium.oscore.OSCoreCoapStackFactory; +import org.eclipse.californium.oscore.OSCoreCtx; +import org.eclipse.californium.oscore.OSException; import org.eclipse.californium.scandium.DTLSConnector; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.californium.scandium.dtls.ClientHandshaker; @@ -57,6 +70,7 @@ import org.eclipse.californium.scandium.dtls.cipher.CipherSuite; import org.eclipse.leshan.client.californium.LeshanClient; import org.eclipse.leshan.client.californium.LeshanClientBuilder; +import org.eclipse.leshan.client.californium.OscoreHandler; import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory; import org.eclipse.leshan.client.object.Server; import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; @@ -219,15 +233,18 @@ public static void main(final String[] args) { "The path to your client certificate file.\n The certificate Common Name (CN) should generaly be equal to the client endpoint name (see -n option).\nThe certificate should be in X509v3 format (DER encoding)."); options.addOption("scert", true, "The path to your server certificate file.\n The certificate should be in X509v3 format (DER encoding)."); + options.addOption("oscore", false, "Use OSCORE for communication between client and server."); final StringBuilder trustStoreChapter = new StringBuilder(); trustStoreChapter.append("\n ."); - trustStoreChapter.append("\n URI format: file://##"); + trustStoreChapter + .append("\n URI format: file://##"); trustStoreChapter.append("\n ."); trustStoreChapter.append("\n Where:"); trustStoreChapter.append("\n - path-to-trust-store-file is path to pkcs12 trust store file"); trustStoreChapter.append("\n - hex-store-password is HEX formatted password for store"); - trustStoreChapter.append("\n - alias-pattern can be used to filter trusted certificates and can also be empty to get all"); + trustStoreChapter.append( + "\n - alias-pattern can be used to filter trusted certificates and can also be empty to get all"); trustStoreChapter.append("\n ."); trustStoreChapter.append("\n Default: All certificates are trusted which is only OK for a demo."); @@ -491,7 +508,8 @@ public static void main(final String[] args) { // check input exists if (!input.exists()) { - System.err.println("Failed to load trust store - file or directory does not exist : " + input.toString()); + System.err.println( + "Failed to load trust store - file or directory does not exist : " + input.toString()); formatter.printHelp(USAGE, options); return; } @@ -557,6 +575,39 @@ public static void main(final String[] args) { // Get models folder String modelsFolderPath = cl.getOptionValue("m"); + // Set up OSCORE Context when using OSCORE + if (cl.hasOption("oscore")) { + System.out.println("Using OSCORE"); + + HashMapCtxDB db = OscoreHandler.getContextDB(); + AlgorithmID alg = AlgorithmID.AES_CCM_16_64_128; + AlgorithmID kdf = AlgorithmID.HKDF_HMAC_SHA_256; + + byte[] master_secret = { 0x11, 0x22, 0x33, 0x44 }; + byte[] sid = new byte[] { (byte) 0xAA }; + byte[] rid = new byte[] { (byte) 0xBB }; + + // Add the OSCORE context associated to the URI of the server + OSCoreCtx ctx = null; + try { + ctx = new OSCoreCtx(master_secret, true, alg, sid, rid, kdf, 32, null, null); + db.addContext(serverURI, ctx); + } catch (OSException e) { + System.err.println("Failed to generate OSCORE Context"); + e.printStackTrace(); + } + + // Also add the context by the IP of the server since requests may use that + String serverIP = null; + try { + serverIP = InetAddress.getByName(new URI(serverURI).getHost()).getHostAddress(); + db.addContext("coap://" + serverIP, ctx); + } catch (UnknownHostException | URISyntaxException | OSException e) { + System.err.println("Failed to find Server IP"); + e.printStackTrace(); + } + OSCoreCoapStackFactory.useAsDefault(db); + } try { createAndStartClient(endpoint, localAddress, localPort, cl.hasOption("b"), additionalAttributes, bsAdditionalAttributes, lifetime, communicationPeriod, serverURI, pskIdentity, pskKey, 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 818c2b87fb..230462c1cf 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 @@ -12,6 +12,7 @@ * * Contributors: * Sierra Wireless - initial API and implementation + * Rikard Höglund (RISE SICS) - Additions to support OSCORE *******************************************************************************/ package org.eclipse.leshan.server.californium.registration; @@ -26,6 +27,9 @@ import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.server.resources.CoapExchange; import org.eclipse.californium.core.server.resources.Resource; +import org.eclipse.californium.oscore.HashMapCtxDB; +import org.eclipse.californium.oscore.OSCoreCtx; +import org.eclipse.californium.oscore.OSException; import org.eclipse.leshan.core.Link; import org.eclipse.leshan.core.californium.LwM2mCoapResource; import org.eclipse.leshan.core.request.BindingMode; @@ -37,6 +41,7 @@ import org.eclipse.leshan.core.response.RegisterResponse; import org.eclipse.leshan.core.response.SendableResponse; import org.eclipse.leshan.core.response.UpdateResponse; +import org.eclipse.leshan.server.OscoreHandler; import org.eclipse.leshan.server.registration.RegistrationHandler; import org.eclipse.leshan.server.registration.RegistrationService; import org.slf4j.Logger; @@ -117,6 +122,24 @@ public void handleDELETE(CoapExchange exchange) { } protected void handleRegister(CoapExchange exchange, Request request) { + // TODO OSCORE : should we really need to do this ? + // Check if this incoming request is using OSCORE + if (exchange.advanced().getRequest().getOptions().getOscore() != null) { + LOG.trace("Client registered using OSCORE"); + + // Update the URI of the associated OSCORE Context with the client's URI + // So the server can send requests to the client + HashMapCtxDB db = OscoreHandler.getContextDB(); + OSCoreCtx clientCtx = db.getContext(exchange.advanced().getCryptographicContextID()); + + try { + db.addContext(request.getScheme() + "://" + + request.getSourceContext().getPeerAddress().getHostString().toString(), clientCtx); + } catch (OSException e) { + LOG.error("Failed to update OSCORE Context for registering client.", request, e); + } + } + // Get identity // -------------------------------- Identity sender = extractIdentity(request.getSourceContext()); diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/RequestSender.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/RequestSender.java index 6abd4d3b82..05ea10c029 100644 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/RequestSender.java +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/RequestSender.java @@ -12,6 +12,7 @@ * * Contributors: * Sierra Wireless - initial API and implementation + * Rikard Höglund (RISE SICS) - Additions to support OSCORE *******************************************************************************/ package org.eclipse.leshan.server.californium.request; @@ -29,6 +30,9 @@ import org.eclipse.californium.core.coap.Response; import org.eclipse.californium.core.network.Endpoint; import org.eclipse.californium.elements.EndpointContext; +import org.eclipse.californium.elements.util.Bytes; +import org.eclipse.californium.oscore.HashMapCtxDB; +import org.eclipse.californium.oscore.OSException; import org.eclipse.leshan.core.californium.AsyncRequestObserver; import org.eclipse.leshan.core.californium.CoapAsyncRequestObserver; import org.eclipse.leshan.core.californium.CoapResponseCallback; @@ -54,6 +58,7 @@ import org.eclipse.leshan.core.util.NamedThreadFactory; import org.eclipse.leshan.core.util.Validate; import org.eclipse.leshan.server.Destroyable; +import org.eclipse.leshan.server.OscoreHandler; import org.eclipse.leshan.server.request.LowerLayerConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -134,6 +139,18 @@ public T sendLwm2mRequest(final String endpointName, I request.accept(coapClientRequestBuilder); final Request coapRequest = coapClientRequestBuilder.getRequest(); + // Toggle OSCORE use in the request if the target URI of the request has an OSCORE context registered + // TODO OSCORE : this should be added in CoapRequestBuilder + HashMapCtxDB db = OscoreHandler.getContextDB(); + try { + if (db.getContext(coapRequest.getURI()) != null) { + coapRequest.getOptions().setOscore(Bytes.EMPTY); + } + } catch (OSException e) { + System.err.println("Failed to retrieve OSCORE Context for request"); + e.printStackTrace(); + } + // Send CoAP request synchronously SyncRequestObserver syncMessageObserver = new SyncRequestObserver(coapRequest, timeoutInMs) { @Override @@ -210,6 +227,18 @@ public void sendLwm2mRequest(final String endpointName request.accept(coapClientRequestBuilder); final Request coapRequest = coapClientRequestBuilder.getRequest(); + // Toggle OSCORE use in the request if the target URI of the request has an OSCORE context registered + // TODO OSCORE : this should be added in CoapRequestBuilder + HashMapCtxDB db = OscoreHandler.getContextDB(); + try { + if (db.getContext(coapRequest.getURI()) != null) { + coapRequest.getOptions().setOscore(Bytes.EMPTY); + } + } catch (OSException e) { + System.err.println("Failed to retrieve OSCORE Context for request"); + e.printStackTrace(); + } + // Add CoAP request callback MessageObserver obs = new AsyncRequestObserver(coapRequest, responseCallback, errorCallback, timeoutInMs, executor) { @@ -264,6 +293,8 @@ public Response sendCoapRequest(Identity destination, String sessionId, Request EndpointContext context = EndpointContextUtil.extractContext(destination, allowConnectionInitiation); coapRequest.setDestinationContext(context); + // TODO OSCORE : should we add the OSCORE option automatically here too ? + // Send CoAP request synchronously CoapSyncRequestObserver syncMessageObserver = new CoapSyncRequestObserver(coapRequest, timeoutInMs); coapRequest.addMessageObserver(syncMessageObserver); @@ -319,6 +350,8 @@ public void sendCoapRequest(Identity destination, String sessionId, Request coap EndpointContext context = EndpointContextUtil.extractContext(destination, allowConnectionInitiation); coapRequest.setDestinationContext(context); + // TODO OSCORE : should we add the OSCORE option automatically here too ? + // Add CoAP request callback MessageObserver obs = new CoapAsyncRequestObserver(coapRequest, responseCallback, errorCallback, timeoutInMs, executor); diff --git a/leshan-server-core/pom.xml b/leshan-server-core/pom.xml index 7337bddb73..504273dd73 100644 --- a/leshan-server-core/pom.xml +++ b/leshan-server-core/pom.xml @@ -33,6 +33,11 @@ Contributors: org.eclipse.leshan leshan-core + + org.eclipse.californium + cf-oscore + ${californium.version} + diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/OscoreHandler.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/OscoreHandler.java new file mode 100644 index 0000000000..22bc88c965 --- /dev/null +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/OscoreHandler.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2015 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Rikard Höglund (RISE SICS) - Additions to support OSCORE + *******************************************************************************/ +package org.eclipse.leshan.server; + +import org.eclipse.californium.oscore.HashMapCtxDB; + +// TODO OSCORE : remove this class and static access. +public class OscoreHandler { + + private static HashMapCtxDB db; + + public static HashMapCtxDB getContextDB() { + if (db == null) { + db = new HashMapCtxDB(); + } + return db; + } +} \ No newline at end of file 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 060f00f06c..ada6ddb3a2 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 @@ -12,6 +12,7 @@ * * Contributors: * Sierra Wireless - initial API and implementation + * Rikard Höglund (RISE SICS) - Additions to support OSCORE *******************************************************************************/ package org.eclipse.leshan.server.security; @@ -19,7 +20,10 @@ import java.security.PublicKey; import java.util.Arrays; +import org.eclipse.californium.oscore.HashMapCtxDB; +import org.eclipse.californium.oscore.OSCoreCtx; import org.eclipse.leshan.core.util.Validate; +import org.eclipse.leshan.server.OscoreHandler; /** * The security info for a client. @@ -31,6 +35,7 @@ *
  • Pre-Shared Key: the given identity and a key are needed
  • *
  • Raw Public Key Certificate: the given public key is needed
  • *
  • X509 Certificate: any trusted X509 certificate is needed
  • + *
  • OSCORE: an OSCORE security context is needed
  • * */ public class SecurityInfo implements Serializable { @@ -50,14 +55,18 @@ public class SecurityInfo implements Serializable { // X.509 private final boolean useX509Cert; + // TODO OSCORE : Save content properly information here. Must be serializable. + boolean useOSCore; + private SecurityInfo(String endpoint, String identity, byte[] preSharedKey, PublicKey rawPublicKey, - boolean useX509Cert) { + boolean useX509Cert, OSCoreCtx oscoreCtx) { Validate.notEmpty(endpoint); this.endpoint = endpoint; this.identity = identity; this.preSharedKey = preSharedKey; this.rawPublicKey = rawPublicKey; this.useX509Cert = useX509Cert; + this.useOSCore = oscoreCtx != null; } /** @@ -72,7 +81,7 @@ private SecurityInfo(String endpoint, String identity, byte[] preSharedKey, Publ public static SecurityInfo newPreSharedKeyInfo(String endpoint, String identity, byte[] preSharedKey) { Validate.notEmpty(identity); Validate.notNull(preSharedKey); - return new SecurityInfo(endpoint, identity, preSharedKey, null, false); + return new SecurityInfo(endpoint, identity, preSharedKey, null, false, null); } /** @@ -85,7 +94,7 @@ public static SecurityInfo newPreSharedKeyInfo(String endpoint, String identity, */ public static SecurityInfo newRawPublicKeyInfo(String endpoint, PublicKey rawPublicKey) { Validate.notNull(rawPublicKey); - return new SecurityInfo(endpoint, null, null, rawPublicKey, false); + return new SecurityInfo(endpoint, null, null, rawPublicKey, false, null); } /** @@ -98,7 +107,23 @@ public static SecurityInfo newRawPublicKeyInfo(String endpoint, PublicKey rawPub * @return a X.509 Security Info. */ public static SecurityInfo newX509CertInfo(String endpoint) { - return new SecurityInfo(endpoint, null, null, null, true); + return new SecurityInfo(endpoint, null, null, null, true, null); + } + + /** + * Construct a {@link SecurityInfo} when using OSCORE. + */ + public static SecurityInfo newOSCoreInfo(String endpoint, String identity, OSCoreCtx oscoreCtx) { + Validate.notEmpty(identity); + Validate.notNull(identity); + Validate.notNull(oscoreCtx); + + // Add the OSCORE Context to the context database + HashMapCtxDB db = OscoreHandler.getContextDB(); + db.addContext(oscoreCtx); + + // TODO OSCORE : identity is reserved for PSK + return new SecurityInfo(endpoint, identity, null, null, false, oscoreCtx); } /** diff --git a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/LeshanServerDemo.java b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/LeshanServerDemo.java index 61904480f3..f084a97081 100644 --- a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/LeshanServerDemo.java +++ b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/LeshanServerDemo.java @@ -14,6 +14,7 @@ * Sierra Wireless - initial API and implementation * Bosch Software Innovations - added Redis URL support with authentication * Firis SA - added mDNS services registering + * Rikard Höglund (RISE SICS) - Additions to support OSCORE *******************************************************************************/ package org.eclipse.leshan.server.demo; @@ -24,7 +25,11 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URI; -import java.security.*; +import java.security.Key; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.PrivateKey; +import java.security.PublicKey; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.ArrayList; @@ -43,6 +48,7 @@ import org.eclipse.californium.core.network.config.NetworkConfig; import org.eclipse.californium.core.network.config.NetworkConfig.Keys; import org.eclipse.californium.elements.util.SslContextUtil; +import org.eclipse.californium.oscore.OSCoreCoapStackFactory; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletHolder; @@ -54,6 +60,7 @@ import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder; import org.eclipse.leshan.core.node.codec.LwM2mNodeDecoder; import org.eclipse.leshan.core.util.SecurityUtil; +import org.eclipse.leshan.server.OscoreHandler; import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.californium.LeshanServerBuilder; import org.eclipse.leshan.server.demo.servlet.ClientServlet; @@ -163,19 +170,20 @@ public static void main(String[] args) { final StringBuilder trustStoreChapter = new StringBuilder(); trustStoreChapter.append("\n ."); - trustStoreChapter.append("\n URI format: file://##"); + trustStoreChapter + .append("\n URI format: file://##"); trustStoreChapter.append("\n ."); trustStoreChapter.append("\n Where:"); trustStoreChapter.append("\n - path-to-trust-store-file is path to pkcs12 trust store file"); trustStoreChapter.append("\n - hex-store-password is HEX formatted password for store"); - trustStoreChapter.append("\n - alias-pattern can be used to filter trusted certificates and can also be empty to get all"); + trustStoreChapter.append( + "\n - alias-pattern can be used to filter trusted certificates and can also be empty to get all"); trustStoreChapter.append("\n ."); trustStoreChapter.append("\n Default: All certificates are trusted which is only OK for a demo."); options.addOption("truststore", true, "The path to a root certificate file to trust or a folder containing all the trusted certificates in X509v3 format (DER encoding) or trust store URI." - + trustStoreChapter - + X509ChapterDeprecated); + + trustStoreChapter + X509ChapterDeprecated); options.addOption("ks", "keystore", true, "Set the key store file.\nIf set, X.509 mode is enabled, otherwise built-in RPK credentials are used."); options.addOption("ksp", "storepass", true, "Set the key store password."); @@ -329,7 +337,8 @@ public static void main(String[] args) { // check input exists if (!input.exists()) { - System.err.println("Failed to load trust store - file or directory does not exist : " + input.toString()); + System.err.println( + "Failed to load trust store - file or directory does not exist : " + input.toString()); formatter.printHelp(USAGE, options); return; } @@ -386,6 +395,10 @@ public static void createAndStartServer(String webAddress, int webPort, String l LwM2mNodeDecoder decoder = new DefaultLwM2mNodeDecoder(); builder.setDecoder(decoder); + // Enable OSCORE stack (fine to do even when using DTLS or only CoAP) + // TODO OSCORE : OSCoreCoapStack should be created in DefaultEndpointFactory. + OSCoreCoapStackFactory.useAsDefault(OscoreHandler.getContextDB()); + // Create CoAP Config NetworkConfig coapConfig; File configFile = new File(NetworkConfig.DEFAULT_FILE_NAME); diff --git a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/SecurityDeserializer.java b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/SecurityDeserializer.java index babc429335..e4f1621c6a 100644 --- a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/SecurityDeserializer.java +++ b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/SecurityDeserializer.java @@ -12,6 +12,7 @@ * * Contributors: * Sierra Wireless - initial API and implementation + * Rikard Höglund (RISE SICS) - Additions to support OSCORE *******************************************************************************/ package org.eclipse.leshan.server.demo.servlet.json; @@ -28,6 +29,9 @@ import java.security.spec.ECPublicKeySpec; import java.security.spec.KeySpec; +import org.eclipse.californium.cose.AlgorithmID; +import org.eclipse.californium.oscore.OSCoreCtx; +import org.eclipse.californium.oscore.OSException; import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.core.util.SecurityUtil; import org.eclipse.leshan.server.security.SecurityInfo; @@ -63,6 +67,7 @@ public SecurityInfo deserialize(JsonElement json, Type typeOfT, JsonDeserializat JsonObject psk = (JsonObject) object.get("psk"); JsonObject rpk = (JsonObject) object.get("rpk"); + JsonObject oscore = (JsonObject) object.get("oscore"); JsonPrimitive x509 = object.getAsJsonPrimitive("x509"); if (psk != null) { // PSK Deserialization @@ -108,6 +113,78 @@ public SecurityInfo deserialize(JsonElement json, Type typeOfT, JsonDeserializat info = SecurityInfo.newRawPublicKeyInfo(endpoint, key); } else if (x509 != null && x509.getAsBoolean()) { info = SecurityInfo.newX509CertInfo(endpoint); + } else if (oscore != null) { + // OSCORE Deserialization + + // Parse hexadecimal context parameters + byte[] masterSecret = Hex.decodeHex(oscore.get("masterSecret").getAsString().toCharArray()); + byte[] senderId = Hex.decodeHex(oscore.get("senderId").getAsString().toCharArray()); + byte[] recipientId = Hex.decodeHex(oscore.get("recipientId").getAsString().toCharArray()); + + // Check parameters that are allowed to be empty + byte[] masterSalt = null; + if (oscore.get("masterSalt") != null) { + masterSalt = Hex.decodeHex(oscore.get("masterSalt").getAsString().toCharArray()); + + if (masterSalt.length == 0) { + masterSalt = null; + } + } + + byte[] idContext = null; + if (oscore.get("idContext") != null) { + idContext = Hex.decodeHex(oscore.get("idContext").getAsString().toCharArray()); + + if (idContext.length == 0) { + idContext = null; + } + } + + // Parse AEAD Algorithm + AlgorithmID aeadAlgorithm = null; + try { + String aeadAlgorithmStr = oscore.get("aeadAlgorithm").getAsString(); + aeadAlgorithm = AlgorithmID.valueOf(aeadAlgorithmStr); + } catch (IllegalArgumentException e) { + throw new JsonParseException("Invalid AEAD algorithm", e); + } + if (aeadAlgorithm != AlgorithmID.AES_CCM_16_64_128) { + throw new JsonParseException("Unsupported AEAD algorithm"); + } + + // Parse HKDF Algorithm + AlgorithmID hkdfAlgorithm = null; + try { + String hkdfAlgorithmStr = oscore.get("hkdfAlgorithm").getAsString(); + hkdfAlgorithm = AlgorithmID.valueOf(hkdfAlgorithmStr); + } catch (IllegalArgumentException e) { + throw new JsonParseException("Invalid HKDF algorithm", e); + } + if (hkdfAlgorithm != AlgorithmID.HKDF_HMAC_SHA_256) { + throw new JsonParseException("Unsupported HKDF algorithm"); + } + + OSCoreCtx ctx = null; + // Attempt to generate OSCORE Context from parsed parameters + // Note that the sender and recipient IDs are inverted here + try { + ctx = new OSCoreCtx(masterSecret, true, aeadAlgorithm, recipientId, senderId, hkdfAlgorithm, 32, + masterSalt, idContext); + } catch (OSException e) { + throw new JsonParseException("Failed to generate OSCORE context", e); + } + + // Create an identity string from the OSCORE context information + StringBuilder b = new StringBuilder(); + if (ctx.getIdContext() != null) { + b.append(Hex.encodeHex(ctx.getIdContext())); + b.append(":"); + } + b.append(Hex.encodeHex(ctx.getRecipientId())); + String identity = b.toString(); + + // TODO OSCORE : identity is for PSK and should not be used for OSCORE + info = SecurityInfo.newOSCoreInfo(endpoint, identity, ctx); } else { throw new JsonParseException("Invalid security info content"); } diff --git a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/SecuritySerializer.java b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/SecuritySerializer.java index 01f38feece..caa03df3b6 100644 --- a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/SecuritySerializer.java +++ b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/SecuritySerializer.java @@ -38,11 +38,13 @@ public JsonElement serialize(SecurityInfo src, Type typeOfSrc, JsonSerialization JsonObject element = new JsonObject(); element.addProperty("endpoint", src.getEndpoint()); - if (src.getIdentity() != null) { JsonObject psk = new JsonObject(); psk.addProperty("identity", src.getIdentity()); - psk.addProperty("key", Hex.encodeHexString(src.getPreSharedKey())); + // TODO OSCORE : remove this if because oscore should not use identity. + if (src.getPreSharedKey() != null) { + psk.addProperty("key", Hex.encodeHexString(src.getPreSharedKey())); + } element.add("psk", psk); } @@ -81,6 +83,8 @@ public JsonElement serialize(SecurityInfo src, Type typeOfSrc, JsonSerialization element.addProperty("x509", true); } + // TODO OSCORE : implement serialization for OSCORE. + return element; } } diff --git a/leshan-server-demo/src/main/resources/webapp/js/security-controllers.js b/leshan-server-demo/src/main/resources/webapp/js/security-controllers.js index b6bf26209a..16dfd0ac7b 100644 --- a/leshan-server-demo/src/main/resources/webapp/js/security-controllers.js +++ b/leshan-server-demo/src/main/resources/webapp/js/security-controllers.js @@ -102,7 +102,12 @@ angular.module('securityControllers', []) var security = {endpoint: $scope.endpoint, psk : { identity : $scope.pskIdentity , key : $scope.pskValue}}; } else if($scope.securityMode == "rpk") { var security = {endpoint: $scope.endpoint, rpk : { key : $scope.rpkValue }}; - } else { + } else if($scope.securityMode == "oscore") { + // Information for OSCORE + var security = {endpoint: $scope.endpoint, oscore : { masterSecret : $scope.masterSecret, masterSalt : $scope.masterSalt, + idContext : $scope.idContext, senderId : $scope.senderId, recipientId : $scope.recipientId, + aeadAlgorithm : $scope.aeadAlgorithm || $scope.defaultAeadAlgorithm, hkdfAlgorithm : $scope.hkdfAlgorithm || $scope.defaultHkdfAlgorithm }}; + } else { var security = {endpoint: $scope.endpoint, x509 : true}; } if(security) { @@ -129,6 +134,8 @@ angular.module('securityControllers', []) $scope.rpkXValue = ''; $scope.rpkYValue = ''; $scope.defaultParams = 'secp256r1'; + $scope.defaultAeadAlgorithm = 'AES_CCM_16_64_128'; + $scope.defaultHkdfAlgorithm = 'HKDF_HMAC_SHA_256'; }; }]) diff --git a/leshan-server-demo/src/main/resources/webapp/partials/security-list.html b/leshan-server-demo/src/main/resources/webapp/partials/security-list.html index ef1e0c80c0..42efbaeb42 100644 --- a/leshan-server-demo/src/main/resources/webapp/partials/security-list.html +++ b/leshan-server-demo/src/main/resources/webapp/partials/security-list.html @@ -58,6 +58,17 @@

    The Leshan Public Key (SubjectPublicKeyInfo der encoded) Identity : {{ wrap(security.psk.identity) }} Key : {{ wrap(security.psk.key) }} + + OSCORE + + Master Secret : {{ security.oscore.masterSecret }}
    + Master Salt : {{ security.oscore.masterSalt }}
    + ID Context : {{ security.oscore.idContext }}
    + Sender ID : {{ security.oscore.senderId }}
    + Recipient ID : {{ security.oscore.recipientId }}
    + AEAD Algorithm : {{ security.oscore.aeadAlgorithm }}
    + HKDF Algorithm : {{ security.oscore.hkdfAlgorithm }} + Raw Public Key
    (Elliptic Curve) Public Key :
    {{wrap(security.rpk.key)}}
    @@ -98,6 +109,8 @@

    + + @@ -125,6 +138,83 @@ + +
    + +
    + +

    Hexadecimal format

    +

    Master Secret is required

    +

    Hexadecimal format is expected

    +

    Master Secret is too long

    +
    +
    + +
    + +
    + +

    Hexadecimal format

    +

    Hexadecimal format is expected

    +

    Master Salt is too long

    +
    +
    + +
    + +
    + +

    Hexadecimal format

    +

    Hexadecimal format is expected

    +

    ID Context is too long

    +
    +
    + +
    + +
    + +

    Hexadecimal format

    +

    Sender ID is required

    +

    Hexadecimal format is expected

    +

    Sender ID is too long

    +
    +
    + +
    + +
    + +

    Hexadecimal format

    +

    Recipient ID is required

    +

    Hexadecimal format is expected

    +

    Recipient ID is too long

    +
    +
    + +
    + +
    + +

    AEAD Algorithm is too long

    +
    +
    + +
    + +
    + +

    HKDF Algorithm is too long

    +
    +
    +
    From c48d54d5a59a369313067307a04f3cc005477f9e Mon Sep 17 00:00:00 2001 From: rikard-sics Date: Fri, 30 Aug 2019 18:02:54 +0200 Subject: [PATCH 03/15] Workaround about missing Token of observe request for oscore. --- .../request/LwM2mResponseBuilder.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/LwM2mResponseBuilder.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/LwM2mResponseBuilder.java index 315ee9cb45..ec32812de3 100644 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/LwM2mResponseBuilder.java +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/request/LwM2mResponseBuilder.java @@ -12,6 +12,7 @@ * * Contributors: * Sierra Wireless - initial API and implementation + * Rikard Höglund (RISE SICS) - Additions to support OSCORE *******************************************************************************/ package org.eclipse.leshan.server.californium.request; @@ -219,6 +220,21 @@ public void visit(ObserveRequest request) { // handle success response: LwM2mNode content = decodeCoapResponse(request.getPath(), coapResponse, request, clientEndpoint); if (coapResponse.getOptions().hasObserve()) { + + /* + * Note: When using OSCORE and Observe the first coapRequest sent to register an observation can have + * its Token missing here. Is this because OSCORE re-creates the request before sending? When looking in + * Wireshark all messages have a Token as they should. The lines below fixes this by taking the Token + * from the response that came to the request (since the request actually has a Token when going out the + * response will have the same correct Token. + * + * TODO OSCORE : This should probably not be done here. + * should we fix this ? should we check if oscore is used ? + */ + if (coapRequest.getTokenBytes() == null) { + coapRequest.setToken(coapResponse.getTokenBytes()); + } + // observe request successful Observation observation = ObserveUtil.createLwM2mObservation(coapRequest); // add the observation to an ObserveResponse instance From cb60769821b07d2afb0a403a664e91a311acac68 Mon Sep 17 00:00:00 2001 From: rikard-sics Date: Tue, 21 Jul 2020 14:39:53 +0200 Subject: [PATCH 04/15] #726: Generate OSCORE object and use it for endpoint creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Code to implement the points listed here: https://github.com/eclipse/leshan/pull/718#issuecomment-521316754 The OSCORE object is generated and filled in the Leshan client demo. It will then be used to create a ServerInfo object using the ServersInfoExtractor. Next the ServerInfo is used in the CaliforniumEndpointsManager to generate endpoints towards servers that OSCORE should be used with. See also issue regarding OSCORE client integration: https://github.com/eclipse/leshan/issues/726 Signed-off-by: Rikard Höglund --- .../CaliforniumEndpointsManager.java | 51 +++++- .../eclipse/leshan/client/object/Oscore.java | 148 +++++++++++++----- .../leshan/client/object/Security.java | 44 ++++-- .../leshan/client/servers/ServerInfo.java | 11 ++ .../client/servers/ServersInfoExtractor.java | 68 +++++++- .../client/util/LinkFormatHelperTest.java | 1 + leshan-client-demo/pom.xml | 1 + .../leshan/client/demo/LeshanClientDemo.java | 53 ++----- leshan-core-cf/pom.xml | 6 + .../californium/DefaultEndpointFactory.java | 7 +- .../core/californium/EndpointFactory.java | 9 +- .../java/org/eclipse/leshan/core/LwM2mId.java | 12 ++ .../tests/BootstrapIntegrationTestHelper.java | 3 +- .../tests/SecureIntegrationTestHelper.java | 6 +- .../californium/LeshanServerBuilder.java | 5 +- .../LeshanBootstrapServerBuilder.java | 4 +- .../server/security/SecurityChecker.java | 8 +- 17 files changed, 332 insertions(+), 105 deletions(-) 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 8849781769..70d32607e7 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 @@ -12,11 +12,14 @@ * * Contributors: * Sierra Wireless - initial API and implementation + * Rikard Höglund (RISE SICS) - Additions to support OSCORE *******************************************************************************/ package org.eclipse.leshan.client.californium; import java.io.IOException; +import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.UnknownHostException; import java.security.PublicKey; import java.security.cert.Certificate; import java.security.cert.X509Certificate; @@ -28,8 +31,13 @@ import org.eclipse.californium.core.network.CoapEndpoint; import org.eclipse.californium.core.network.Endpoint; import org.eclipse.californium.core.network.config.NetworkConfig; +import org.eclipse.californium.cose.AlgorithmID; +import org.eclipse.californium.cose.CoseException; import org.eclipse.californium.elements.Connector; import org.eclipse.californium.elements.auth.RawPublicKeyIdentity; +import org.eclipse.californium.oscore.HashMapCtxDB; +import org.eclipse.californium.oscore.OSCoreCtx; +import org.eclipse.californium.oscore.OSException; import org.eclipse.californium.scandium.DTLSConnector; import org.eclipse.californium.scandium.config.DtlsConnectorConfig.Builder; import org.eclipse.californium.scandium.dtls.cipher.CipherSuite; @@ -46,6 +54,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.upokecenter.cbor.CBORObject; + /** * An {@link EndpointsManager} based on Californium(CoAP implementation) and Scandium (DTLS implementation) which * supports only 1 server. @@ -145,9 +155,46 @@ public boolean isTrusted(RawPublicKeyIdentity id) { } else { throw new RuntimeException("Unable to create connector : unsupported security mode"); } - currentEndpoint = endpointFactory.createSecuredEndpoint(newBuilder.build(), coapConfig, null); + currentEndpoint = endpointFactory.createSecuredEndpoint(newBuilder.build(), coapConfig, null, null); + } else if (serverInfo.useOscore) { + // oscore only mode + LOG.info("Adding OSCORE context for " + serverInfo.getFullUri().toASCIIString()); + // TODO OSCORE : use OscoreStore instead ? + HashMapCtxDB db = OscoreHandler.getContextDB(); + + AlgorithmID hkdfAlg = null; + try { + hkdfAlg = AlgorithmID.FromCBOR(CBORObject.FromObject(serverInfo.hkdfAlgorithm)); + } catch (CoseException e) { + LOG.error("Failed to decode OSCORE HMAC algorithm"); + } + + AlgorithmID aeadAlg = null; + try { + aeadAlg = AlgorithmID.FromCBOR(CBORObject.FromObject(serverInfo.aeadAlgorithm)); + } catch (CoseException e) { + LOG.error("Failed to decode OSCORE AEAD algorithm"); + } + + try { + OSCoreCtx ctx = new OSCoreCtx(serverInfo.masterSecret, true, aeadAlg, serverInfo.senderId, + serverInfo.recipientId, hkdfAlg, 32, serverInfo.masterSalt, serverInfo.idContext); + db.addContext(serverInfo.getFullUri().toASCIIString(), ctx); + + // Also add the context by the IP of the server since requests may use that + String serverIP = InetAddress.getByName(serverInfo.getFullUri().getHost()).getHostAddress(); + db.addContext("coap://" + serverIP, ctx); + + } catch (OSException | UnknownHostException e) { + LOG.error("Failed to generate OSCORE context information"); + return null; + } + + currentEndpoint = endpointFactory.createUnsecuredEndpoint(localAddress, coapConfig, null, db); + // TODO OSCORE: Should be visible in identity if OSCORE is used + serverIdentity = Identity.unsecure(serverInfo.getAddress()); } else { - currentEndpoint = endpointFactory.createUnsecuredEndpoint(localAddress, coapConfig, null); + currentEndpoint = endpointFactory.createUnsecuredEndpoint(localAddress, coapConfig, null, null); serverIdentity = Identity.unsecure(serverInfo.getAddress()); } 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 806266afd9..220ee2dba0 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 @@ -12,14 +12,21 @@ * * Contributors: * Sierra Wireless - initial API and implementation - * Rikard Höglund (RISE SICS) + * Rikard Höglund (RISE SICS) - Additions to support OSCORE * *******************************************************************************/ package org.eclipse.leshan.client.object; +import static org.eclipse.leshan.core.LwM2mId.*; + +import java.util.Arrays; +import java.util.List; + import org.eclipse.leshan.client.resource.BaseInstanceEnabler; import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler; import org.eclipse.leshan.client.servers.ServerIdentity; +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.response.ReadResponse; import org.eclipse.leshan.core.response.WriteResponse; @@ -33,77 +40,136 @@ public class Oscore extends BaseInstanceEnabler { private static final Logger LOG = LoggerFactory.getLogger(Security.class); - private byte[] masterSecret; + 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 hmacAlgorithm; + private int hkdfAlgorithm; + private String masterSalt; + // TODO OSCORE : never used and not part of OSCORE object + private String idContext; public Oscore() { } - public Oscore(byte[] masterSecret, String senderId, String recipientId, int aeadAlgorithm, int hmacAlgorithm) { - super(); - this.masterSecret = masterSecret.clone(); + /** + * Default constructor. + */ + public Oscore(int instanceId, String masterSecret, String senderId, String recipientId, int aeadAlgorithm, + int hkdfAlgorithm, String masterSalt) { + super(instanceId); + this.masterSecret = masterSecret; this.senderId = senderId; this.recipientId = recipientId; this.aeadAlgorithm = aeadAlgorithm; - this.hmacAlgorithm = hmacAlgorithm; + this.hkdfAlgorithm = hkdfAlgorithm; + this.masterSalt = masterSalt; + this.idContext = ""; + } + + /** + * 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, ""); } @Override public WriteResponse write(ServerIdentity identity, int resourceId, LwM2mResource value) { LOG.debug("Write on resource {}: {}", resourceId, value); - // extend - return WriteResponse.notFound(); + + // restricted to BS server? + + switch (resourceId) { + + case OSCORE_Master_Secret: + if (value.getType() != Type.STRING) { + return WriteResponse.badRequest("invalid type"); + } + masterSecret = (String) value.getValue(); + return WriteResponse.success(); + + case OSCORE_Sender_ID: + if (value.getType() != Type.STRING) { + return WriteResponse.badRequest("invalid type"); + } + senderId = (String) value.getValue(); + return WriteResponse.success(); + + case OSCORE_Recipient_ID: + if (value.getType() != Type.STRING) { + return WriteResponse.badRequest("invalid type"); + } + recipientId = (String) value.getValue(); + return WriteResponse.success(); + + 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: + 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) { + return WriteResponse.badRequest("invalid type"); + } + masterSalt = (String) value.getValue(); + return WriteResponse.success(); + + default: + return super.write(identity, resourceId, value); + } + } @Override public ReadResponse read(ServerIdentity identity, int resourceid) { LOG.debug("Read on resource {}", resourceid); - // extend - return ReadResponse.notFound(); - } + // only accessible for internal read? - public byte[] getMasterSecret() { - return masterSecret; - } + switch (resourceid) { - public void setMasterSecret(byte[] masterSecret) { - this.masterSecret = masterSecret; - } + case OSCORE_Master_Secret: + return ReadResponse.success(resourceid, masterSecret); - public String getSenderId() { - return senderId; - } + case OSCORE_Sender_ID: + return ReadResponse.success(resourceid, senderId); - public void setSenderId(String senderId) { - this.senderId = senderId; - } + case OSCORE_Recipient_ID: + return ReadResponse.success(resourceid, recipientId); - public String getRecipientId() { - return recipientId; - } + case OSCORE_AEAD_Algorithm: + return ReadResponse.success(resourceid, aeadAlgorithm); - public void setRecipientId(String recipientId) { - this.recipientId = recipientId; - } + case OSCORE_HMAC_Algorithm: + return ReadResponse.success(resourceid, hkdfAlgorithm); - public int getAeadAlgorithm() { - return aeadAlgorithm; - } + case OSCORE_Master_Salt: + return ReadResponse.success(resourceid, masterSalt); - public void setAeadAlgorithm(int aeadAlgorithm) { - this.aeadAlgorithm = aeadAlgorithm; + default: + return super.read(identity, resourceid); + } } - public int getHmacAlgorithm() { - return hmacAlgorithm; - } - - public void setHmacAlgorithm(int hmacAlgorithm) { - this.hmacAlgorithm = hmacAlgorithm; + @Override + public List getAvailableResourceIds(ObjectModel model) { + return supportedResources; } } 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 d85bc54a91..1d3bc24742 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 @@ -12,6 +12,7 @@ * * Contributors: * Sierra Wireless - initial API and implementation + * Rikard Höglund (RISE SICS) - Additions to support OSCORE *******************************************************************************/ package org.eclipse.leshan.client.object; @@ -27,6 +28,7 @@ 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.node.ObjectLink; import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.response.WriteResponse; @@ -41,7 +43,8 @@ public class Security extends BaseInstanceEnabler { private static final Logger LOG = LoggerFactory.getLogger(Security.class); private final static List supportedResources = Arrays.asList(SEC_SERVER_URI, SEC_BOOTSTRAP, - SEC_SECURITY_MODE, SEC_PUBKEY_IDENTITY, SEC_SERVER_PUBKEY, SEC_SECRET_KEY, SEC_SERVER_ID); + SEC_SECURITY_MODE, SEC_PUBKEY_IDENTITY, SEC_SERVER_PUBKEY, SEC_SECRET_KEY, SEC_SERVER_ID, + SEC_OSCORE_SECURITY_MODE); private String serverUri; /* coaps://host:port */ private boolean bootstrapServer; @@ -52,13 +55,14 @@ public class Security extends BaseInstanceEnabler { private byte[] secretKey; private Integer shortServerId; + private ObjectLink oscoreSecurityMode; public Security() { // should only be used at bootstrap time } public Security(String serverUri, boolean bootstrapServer, int securityMode, byte[] publicKeyOrIdentity, - byte[] serverPublicKey, byte[] secretKey, Integer shortServerId) { + byte[] serverPublicKey, byte[] secretKey, Integer shortServerId, ObjectLink oscoreSecurityMode) { this.serverUri = serverUri; this.bootstrapServer = bootstrapServer; this.securityMode = securityMode; @@ -66,13 +70,15 @@ public Security(String serverUri, boolean bootstrapServer, int securityMode, byt this.serverPublicKey = serverPublicKey; this.secretKey = secretKey; this.shortServerId = shortServerId; + this.oscoreSecurityMode = oscoreSecurityMode; } /** * Returns a new security instance (NoSec) for a bootstrap server. */ public static Security noSecBootstap(String serverUri) { - return new Security(serverUri, true, SecurityMode.NO_SEC.code, new byte[0], new byte[0], new byte[0], 0); + return new Security(serverUri, true, SecurityMode.NO_SEC.code, new byte[0], new byte[0], new byte[0], 0, + new ObjectLink()); } /** @@ -80,7 +86,7 @@ public static Security noSecBootstap(String serverUri) { */ public static Security pskBootstrap(String serverUri, byte[] pskIdentity, byte[] privateKey) { return new Security(serverUri, true, SecurityMode.PSK.code, pskIdentity.clone(), new byte[0], - privateKey.clone(), 0); + privateKey.clone(), 0, new ObjectLink()); } /** @@ -89,7 +95,7 @@ public static Security pskBootstrap(String serverUri, byte[] pskIdentity, byte[] public static Security rpkBootstrap(String serverUri, byte[] clientPublicKey, byte[] clientPrivateKey, byte[] serverPublicKey) { return new Security(serverUri, true, SecurityMode.RPK.code, clientPublicKey.clone(), serverPublicKey.clone(), - clientPrivateKey.clone(), 0); + clientPrivateKey.clone(), 0, new ObjectLink()); } /** @@ -98,7 +104,7 @@ public static Security rpkBootstrap(String serverUri, byte[] clientPublicKey, by public static Security x509Bootstrap(String serverUri, byte[] clientCertificate, byte[] clientPrivateKey, byte[] serverPublicKey) { return new Security(serverUri, true, SecurityMode.X509.code, clientCertificate.clone(), serverPublicKey.clone(), - clientPrivateKey.clone(), 0); + clientPrivateKey.clone(), 0, new ObjectLink()); } /** @@ -106,7 +112,15 @@ public static Security x509Bootstrap(String serverUri, byte[] clientCertificate, */ public static Security noSec(String serverUri, int shortServerId) { return new Security(serverUri, false, SecurityMode.NO_SEC.code, new byte[0], new byte[0], new byte[0], - shortServerId); + shortServerId, new ObjectLink()); + } + + /** + * Returns a new security instance (OSCORE only) for a device management server. + */ + public static Security oscoreOnly(String serverUri, int shortServerId, int oscoreObjectInstanceId) { + return new Security(serverUri, false, SecurityMode.NO_SEC.code, new byte[0], new byte[0], new byte[0], + shortServerId, new ObjectLink(OSCORE, oscoreObjectInstanceId)); } /** @@ -114,7 +128,7 @@ public static Security noSec(String serverUri, int shortServerId) { */ public static Security psk(String serverUri, int shortServerId, byte[] pskIdentity, byte[] privateKey) { return new Security(serverUri, false, SecurityMode.PSK.code, pskIdentity.clone(), new byte[0], - privateKey.clone(), shortServerId); + privateKey.clone(), shortServerId, new ObjectLink()); } /** @@ -123,7 +137,7 @@ public static Security psk(String serverUri, int shortServerId, byte[] pskIdenti public static Security rpk(String serverUri, int shortServerId, byte[] clientPublicKey, byte[] clientPrivateKey, byte[] serverPublicKey) { return new Security(serverUri, false, SecurityMode.RPK.code, clientPublicKey.clone(), serverPublicKey.clone(), - clientPrivateKey.clone(), shortServerId); + clientPrivateKey.clone(), shortServerId, new ObjectLink()); } /** @@ -132,7 +146,7 @@ public static Security rpk(String serverUri, int shortServerId, byte[] clientPub public static Security x509(String serverUri, int shortServerId, byte[] clientCertificate, byte[] clientPrivateKey, byte[] serverPublicKey) { return new Security(serverUri, false, SecurityMode.X509.code, clientCertificate.clone(), - serverPublicKey.clone(), clientPrivateKey.clone(), shortServerId); + serverPublicKey.clone(), clientPrivateKey.clone(), shortServerId, new ObjectLink()); } @Override @@ -187,6 +201,13 @@ public WriteResponse write(ServerIdentity identity, int resourceId, LwM2mResourc shortServerId = ((Long) value.getValue()).intValue(); return WriteResponse.success(); + case SEC_OSCORE_SECURITY_MODE: // oscore security mode + if (value.getType() != Type.OBJLNK) { + return WriteResponse.badRequest("invalid type"); + } + oscoreSecurityMode = (ObjectLink) value.getValue(); + return WriteResponse.success(); + default: return super.write(identity, resourceId, value); } @@ -219,6 +240,9 @@ public ReadResponse read(ServerIdentity identity, int resourceid) { case SEC_SERVER_ID: // short server id return ReadResponse.success(resourceid, shortServerId); + case SEC_OSCORE_SECURITY_MODE: // oscore security mode + return ReadResponse.success(resourceid, oscoreSecurityMode == null ? new ObjectLink() : oscoreSecurityMode); + default: return super.read(identity, resourceid); } 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 06d2d99204..9ca2ac4c80 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 @@ -12,6 +12,7 @@ * * Contributors: * Sierra Wireless - initial API and implementation + * Rikard Höglund (RISE SICS) - Additions to support OSCORE *******************************************************************************/ package org.eclipse.leshan.client.servers; @@ -52,6 +53,16 @@ public class ServerInfo { public PrivateKey privateKey; + // OSCORE parameters + public boolean useOscore; + public byte[] masterSecret; + public byte[] senderId; + public byte[] recipientId; + public long aeadAlgorithm; + public long hkdfAlgorithm; + public byte[] masterSalt; + public byte[] idContext; + 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 dca257d27a..ab7f31a22c 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 @@ -13,6 +13,7 @@ * Contributors: * Sierra Wireless - initial API and implementation * Achim Kraus (Bosch Software Innovations GmbH) - use ServerIdentity.SYSTEM + * Rikard Höglund (RISE SICS) - Additions to support OSCORE *******************************************************************************/ package org.eclipse.leshan.client.servers; @@ -42,9 +43,11 @@ import org.eclipse.leshan.core.node.LwM2mObject; 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.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.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,6 +61,7 @@ public class ServersInfoExtractor { public static ServersInfo getInfo(Map objectEnablers) { LwM2mObjectEnabler securityEnabler = objectEnablers.get(SECURITY); LwM2mObjectEnabler serverEnabler = objectEnablers.get(SERVER); + LwM2mObjectEnabler oscoreEnabler = objectEnablers.get(OSCORE); if (securityEnabler == null || serverEnabler == null) return null; @@ -66,6 +70,11 @@ public static ServersInfo getInfo(Map objectEnabler LwM2mObject securities = (LwM2mObject) securityEnabler.read(SYSTEM, new ReadRequest(SECURITY)).getContent(); LwM2mObject servers = (LwM2mObject) serverEnabler.read(SYSTEM, new ReadRequest(SERVER)).getContent(); + LwM2mObject oscores = null; + if (oscoreEnabler != null) { + oscores = (LwM2mObject) oscoreEnabler.read(SYSTEM, new ReadRequest(OSCORE)).getContent(); + } + for (LwM2mObjectInstance security : securities.getInstances().values()) { try { if ((boolean) security.getResource(SEC_BOOTSTRAP).getValue()) { @@ -103,7 +112,25 @@ public static ServersInfo getInfo(Map objectEnabler info.serverUri = new URI((String) security.getResource(SEC_SERVER_URI).getValue()); info.serverId = (long) security.getResource(SEC_SERVER_ID).getValue(); info.secureMode = getSecurityMode(security); - if (info.secureMode == SecurityMode.PSK) { + + // find instance id of the associated oscore object (if any) + ObjectLink oscoreObjLink = (ObjectLink) security.getResource(SEC_OSCORE_SECURITY_MODE).getValue(); + int oscoreObjectInstanceId = oscoreObjLink.getObjectInstanceId(); + boolean useOscore = oscoreObjLink.getObjectId() == OSCORE; + + if (useOscore) { + // get corresponding oscore object + LwM2mObjectInstance oscoreInstance = oscores.getInstance(oscoreObjectInstanceId); + + 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); + info.idContext = getIdContext(oscoreInstance); + } else if (info.secureMode == SecurityMode.PSK) { info.pskId = getPskIdentity(security); info.pskKey = getPskKey(security); } else if (info.secureMode == SecurityMode.RPK) { @@ -273,4 +300,43 @@ public static boolean isBootstrapServer(LwM2mInstanceEnabler instance) { LwM2mResource isBootstrap = (LwM2mResource) response.getContent(); return (Boolean) isBootstrap.getValue(); } + + // OSCORE related methods below + + public static byte[] getMasterSecret(LwM2mObjectInstance oscoreInstance) { + String value = (String) oscoreInstance.getResource(OSCORE_Master_Secret).getValue(); + return Hex.decodeHex(value.toCharArray()); + } + + public static byte[] getSenderId(LwM2mObjectInstance oscoreInstance) { + String value = (String) oscoreInstance.getResource(OSCORE_Sender_ID).getValue(); + return Hex.decodeHex(value.toCharArray()); + } + + public static byte[] getRecipientId(LwM2mObjectInstance oscoreInstance) { + String value = (String) oscoreInstance.getResource(OSCORE_Recipient_ID).getValue(); + return Hex.decodeHex(value.toCharArray()); + } + + public static long getAeadAlgorithm(LwM2mObjectInstance oscoreInstance) { + return (long) oscoreInstance.getResource(OSCORE_AEAD_Algorithm).getValue(); + } + + public static long getHkdfAlgorithm(LwM2mObjectInstance oscoreInstance) { + return (long) oscoreInstance.getResource(OSCORE_HMAC_Algorithm).getValue(); + } + + public static byte[] getMasterSalt(LwM2mObjectInstance oscoreInstance) { + String value = (String) oscoreInstance.getResource(OSCORE_Master_Salt).getValue(); + + if (value.equals("")) { + return null; + } else { + return Hex.decodeHex(value.toCharArray()); + } + } + + public static byte[] getIdContext(LwM2mObjectInstance oscoreInstance) { + return null; + } } 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 6d52a1afa2..09f31b69a8 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 @@ -265,6 +265,7 @@ public void encode_bootstrap_root() { Link[] links = LinkFormatHelper.getBootstrapClientDescription(objectEnablers); String strLinks = Link.serialize(links); + // TODO : handle version correctly assertEquals( ";lwm2m=1.0,;ssid=111,,;ssid=222,;ssid=333,;ver=2.0,;ssid=333,;ver=2.0,", strLinks); diff --git a/leshan-client-demo/pom.xml b/leshan-client-demo/pom.xml index a920585c5e..d2beb307e1 100644 --- a/leshan-client-demo/pom.xml +++ b/leshan-client-demo/pom.xml @@ -13,6 +13,7 @@ and the Eclipse Distribution License is available at Contributors: Zebra Technologies - initial API and implementation + Rikard Höglund (RISE SICS) - Additions to support OSCORE --> diff --git a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java index a56b51011a..ba942026e5 100644 --- a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java +++ b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java @@ -14,6 +14,7 @@ * Zebra Technologies - initial API and implementation * Sierra Wireless, - initial API and implementation * Bosch Software Innovations GmbH, - initial API and implementation + * Rikard Höglund (RISE SICS) - Additions to support OSCORE *******************************************************************************/ package org.eclipse.leshan.client.demo; @@ -23,8 +24,6 @@ import java.io.File; import java.net.InetAddress; -import java.net.URI; -import java.net.URISyntaxException; import java.net.UnknownHostException; import java.security.PrivateKey; import java.security.PublicKey; @@ -49,13 +48,9 @@ import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.eclipse.californium.core.network.config.NetworkConfig; -import org.eclipse.californium.cose.AlgorithmID; import org.eclipse.californium.elements.Connector; import org.eclipse.californium.elements.util.SslContextUtil; -import org.eclipse.californium.oscore.HashMapCtxDB; import org.eclipse.californium.oscore.OSCoreCoapStackFactory; -import org.eclipse.californium.oscore.OSCoreCtx; -import org.eclipse.californium.oscore.OSException; import org.eclipse.californium.scandium.DTLSConnector; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.californium.scandium.dtls.ClientHandshaker; @@ -72,6 +67,7 @@ import org.eclipse.leshan.client.californium.LeshanClientBuilder; import org.eclipse.leshan.client.californium.OscoreHandler; import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory; +import org.eclipse.leshan.client.object.Oscore; import org.eclipse.leshan.client.object.Server; import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; import org.eclipse.leshan.client.resource.ObjectsInitializer; @@ -575,45 +571,20 @@ public static void main(final String[] args) { // Get models folder String modelsFolderPath = cl.getOptionValue("m"); - // Set up OSCORE Context when using OSCORE + // Set boolean controlling OSCORE usage + // TODO OSCORE : we should define a wording for OSCORE class : Oscore or OSCore or OSCORE + boolean useOSCore = false; if (cl.hasOption("oscore")) { System.out.println("Using OSCORE"); - - HashMapCtxDB db = OscoreHandler.getContextDB(); - AlgorithmID alg = AlgorithmID.AES_CCM_16_64_128; - AlgorithmID kdf = AlgorithmID.HKDF_HMAC_SHA_256; - - byte[] master_secret = { 0x11, 0x22, 0x33, 0x44 }; - byte[] sid = new byte[] { (byte) 0xAA }; - byte[] rid = new byte[] { (byte) 0xBB }; - - // Add the OSCORE context associated to the URI of the server - OSCoreCtx ctx = null; - try { - ctx = new OSCoreCtx(master_secret, true, alg, sid, rid, kdf, 32, null, null); - db.addContext(serverURI, ctx); - } catch (OSException e) { - System.err.println("Failed to generate OSCORE Context"); - e.printStackTrace(); - } - - // Also add the context by the IP of the server since requests may use that - String serverIP = null; - try { - serverIP = InetAddress.getByName(new URI(serverURI).getHost()).getHostAddress(); - db.addContext("coap://" + serverIP, ctx); - } catch (UnknownHostException | URISyntaxException | OSException e) { - System.err.println("Failed to find Server IP"); - e.printStackTrace(); - } - OSCoreCoapStackFactory.useAsDefault(db); + // TODO OSCORE : this should be done in DefaultEndpointFactory + OSCoreCoapStackFactory.useAsDefault(OscoreHandler.getContextDB()); } try { createAndStartClient(endpoint, localAddress, localPort, cl.hasOption("b"), additionalAttributes, bsAdditionalAttributes, lifetime, communicationPeriod, serverURI, pskIdentity, pskKey, clientPrivateKey, clientPublicKey, serverPublicKey, clientCertificate, serverCertificate, trustStore, latitude, longitude, scaleFactor, cl.hasOption("ocf"), cl.hasOption("oc"), - cl.hasOption("r"), cl.hasOption("f"), modelsFolderPath, ciphers); + cl.hasOption("r"), cl.hasOption("f"), modelsFolderPath, ciphers, useOSCore); } catch (Exception e) { System.err.println("Unable to create and start client ..."); e.printStackTrace(); @@ -628,7 +599,7 @@ public static void createAndStartClient(String endpoint, String localAddress, in X509Certificate clientCertificate, X509Certificate serverCertificate, List trustStore, Float latitude, Float longitude, float scaleFactor, boolean supportOldFormat, boolean supportDeprecatedCiphers, boolean reconnectOnUpdate, boolean forceFullhandshake, - String modelsFolderPath, List ciphers) throws Exception { + String modelsFolderPath, List ciphers, boolean useOSCore) throws Exception { locationInstance = new MyLocation(latitude, longitude, scaleFactor); @@ -670,6 +641,12 @@ public static void createAndStartClient(String endpoint, String localAddress, in initializer.setInstancesForObject(SECURITY, x509(serverURI, 123, clientCertificate.getEncoded(), clientPrivateKey.getEncoded(), serverCertificate.getEncoded())); initializer.setInstancesForObject(SERVER, new Server(123, lifetime, BindingMode.U, false)); + } else if (useOSCore) { + String clientName = Hex.encodeHexString(endpoint.getBytes()); + Oscore oscoreObject = new Oscore(12345, "11223344", clientName, "BB"); // Partially hardcoded values + initializer.setInstancesForObject(SECURITY, oscoreOnly(serverURI, 123, oscoreObject.getId())); + initializer.setInstancesForObject(OSCORE, oscoreObject); + initializer.setInstancesForObject(SERVER, new Server(123, lifetime, BindingMode.U, false)); } else { initializer.setInstancesForObject(SECURITY, noSec(serverURI, 123)); initializer.setInstancesForObject(SERVER, new Server(123, lifetime, BindingMode.U, false)); diff --git a/leshan-core-cf/pom.xml b/leshan-core-cf/pom.xml index 293af6f9cd..6e83d3912c 100644 --- a/leshan-core-cf/pom.xml +++ b/leshan-core-cf/pom.xml @@ -13,6 +13,7 @@ and the Eclipse Distribution License is available at Contributors: Sierra Wireless - initial API and implementation + Rikard Höglund (RISE SICS) - Additions to support OSCORE --> @@ -40,6 +41,11 @@ Contributors: org.eclipse.californium scandium + + org.eclipse.californium + cf-oscore + ${californium.version} + 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 57a06ee0b7..06ade1cd72 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 @@ -27,6 +27,7 @@ import org.eclipse.californium.elements.EndpointContextMatcher; import org.eclipse.californium.elements.PrincipalEndpointContextMatcher; import org.eclipse.californium.elements.UDPConnector; +import org.eclipse.californium.oscore.HashMapCtxDB; import org.eclipse.californium.scandium.DTLSConnector; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; @@ -101,7 +102,8 @@ protected EndpointContextMatcher createUnsecuredContextMatcher() { @Override public CoapEndpoint createUnsecuredEndpoint(InetSocketAddress address, NetworkConfig coapConfig, - ObservationStore store) { + ObservationStore store, HashMapCtxDB db) { + // TODO OSCORE : db should maybe be replaced by OscoreEStore return createUnsecuredEndpointBuilder(address, coapConfig, store).build(); } @@ -148,7 +150,8 @@ protected Connector createUnsecuredConnector(InetSocketAddress address) { @Override public CoapEndpoint createSecuredEndpoint(DtlsConnectorConfig dtlsConfig, NetworkConfig coapConfig, - ObservationStore store) { + ObservationStore store, HashMapCtxDB db) { + // TODO OSCORE : db should maybe be replaced by OscoreEStore return createSecuredEndpointBuilder(dtlsConfig, coapConfig, store).build(); } diff --git a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/EndpointFactory.java b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/EndpointFactory.java index 8032fba84f..cc2a0a15b8 100644 --- a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/EndpointFactory.java +++ b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/EndpointFactory.java @@ -12,6 +12,7 @@ * * Contributors: * Sierra Wireless - initial API and implementation + * Rikard Höglund (RISE SICS) - Additions to support OSCORE *******************************************************************************/ package org.eclipse.leshan.core.californium; @@ -20,6 +21,7 @@ import org.eclipse.californium.core.network.CoapEndpoint; import org.eclipse.californium.core.network.config.NetworkConfig; import org.eclipse.californium.core.observe.ObservationStore; +import org.eclipse.californium.oscore.HashMapCtxDB; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; /** @@ -29,8 +31,9 @@ */ public interface EndpointFactory { - CoapEndpoint createUnsecuredEndpoint(InetSocketAddress address, NetworkConfig coapConfig, ObservationStore store); + CoapEndpoint createUnsecuredEndpoint(InetSocketAddress address, NetworkConfig coapConfig, ObservationStore store, + HashMapCtxDB db); - CoapEndpoint createSecuredEndpoint(DtlsConnectorConfig dtlsConfig, NetworkConfig coapConfig, - ObservationStore store); + CoapEndpoint createSecuredEndpoint(DtlsConnectorConfig dtlsConfig, NetworkConfig coapConfig, ObservationStore store, + HashMapCtxDB db); } 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 24a71737c4..02a03af41d 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 @@ -12,6 +12,7 @@ * * Contributors: * Sierra Wireless - initial API and implementation + * Rikard Höglund (RISE SICS) - Additions to support OSCORE *******************************************************************************/ package org.eclipse.leshan.core; @@ -31,6 +32,7 @@ public interface LwM2mId { public static final int LOCATION = 6; public static final int CONNECTIVITY_STATISTICS = 7; public static final int SOFTWARE_MANAGEMENT = 9; + public static final int OSCORE = 21; /* SECURITY RESOURCES */ @@ -41,6 +43,16 @@ public interface LwM2mId { public static final int SEC_SERVER_PUBKEY = 4; public static final int SEC_SECRET_KEY = 5; public static final int SEC_SERVER_ID = 10; + public static final int SEC_OSCORE_SECURITY_MODE = 17; + + /* 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; /* SERVER RESOURCES */ diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapIntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapIntegrationTestHelper.java index bbe1f17567..2a1a5d1229 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapIntegrationTestHelper.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapIntegrationTestHelper.java @@ -44,6 +44,7 @@ import org.eclipse.leshan.client.resource.ObjectsInitializer; import org.eclipse.leshan.core.LwM2mId; import org.eclipse.leshan.core.SecurityMode; +import org.eclipse.leshan.core.node.ObjectLink; import org.eclipse.leshan.core.request.BootstrapDiscoverRequest; import org.eclipse.leshan.core.request.Identity; import org.eclipse.leshan.core.response.BootstrapDiscoverResponse; @@ -188,7 +189,7 @@ public Security withoutSecurity() { // Create Security Object (with bootstrap server only) String bsUrl = "coap://" + bootstrapServer.getUnsecuredAddress().getHostString() + ":" + bootstrapServer.getUnsecuredAddress().getPort(); - return new Security(bsUrl, true, 3, new byte[0], new byte[0], new byte[0], 0); + return new Security(bsUrl, true, 3, new byte[0], new byte[0], new byte[0], 0, new ObjectLink()); } @Override diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecureIntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecureIntegrationTestHelper.java index 23ef0113e5..ed289eaf18 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecureIntegrationTestHelper.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecureIntegrationTestHelper.java @@ -12,6 +12,7 @@ * * Contributors: * Zebra Technologies - initial API and implementation + * Rikard Höglund (RISE SICS) - Additions to support OSCORE *******************************************************************************/ package org.eclipse.leshan.integration.tests; @@ -45,6 +46,7 @@ import org.eclipse.californium.core.network.CoapEndpoint; import org.eclipse.californium.core.network.config.NetworkConfig; import org.eclipse.californium.core.observe.ObservationStore; +import org.eclipse.californium.oscore.HashMapCtxDB; import org.eclipse.californium.scandium.DTLSConnector; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.californium.scandium.config.DtlsConnectorConfig.Builder; @@ -223,7 +225,7 @@ public void createPSKClient(boolean queueMode) { @Override public CoapEndpoint createUnsecuredEndpoint(InetSocketAddress address, NetworkConfig coapConfig, - ObservationStore store) { + ObservationStore store, HashMapCtxDB db) { CoapEndpoint.Builder builder = new CoapEndpoint.Builder(); builder.setInetSocketAddress(address); builder.setNetworkConfig(coapConfig); @@ -232,7 +234,7 @@ public CoapEndpoint createUnsecuredEndpoint(InetSocketAddress address, NetworkCo @Override public CoapEndpoint createSecuredEndpoint(DtlsConnectorConfig dtlsConfig, NetworkConfig coapConfig, - ObservationStore store) { + ObservationStore store, HashMapCtxDB db) { CoapEndpoint.Builder builder = new CoapEndpoint.Builder(); Builder dtlsConfigBuilder = new Builder(dtlsConfig); 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 81c4ad7878..0189c36f6a 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 @@ -547,12 +547,13 @@ public LeshanServer build() { // create endpoints CoapEndpoint unsecuredEndpoint = null; if (!noUnsecuredEndpoint) { - unsecuredEndpoint = endpointFactory.createUnsecuredEndpoint(localAddress, coapConfig, registrationStore); + unsecuredEndpoint = endpointFactory.createUnsecuredEndpoint(localAddress, coapConfig, registrationStore, + null); } CoapEndpoint securedEndpoint = null; if (!noSecuredEndpoint && dtlsConfig != null) { - securedEndpoint = endpointFactory.createSecuredEndpoint(dtlsConfig, coapConfig, registrationStore); + securedEndpoint = endpointFactory.createSecuredEndpoint(dtlsConfig, coapConfig, registrationStore, null); } if (securedEndpoint == null && unsecuredEndpoint == null) { 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 c63cad238a..a1beb2dcb6 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 @@ -539,12 +539,12 @@ public BootstrapHandler create(BootstrapConfigurationStore store, LwM2mBootstrap CoapEndpoint unsecuredEndpoint = null; if (!noUnsecuredEndpoint) { - unsecuredEndpoint = endpointFactory.createUnsecuredEndpoint(localAddress, coapConfig, null); + unsecuredEndpoint = endpointFactory.createUnsecuredEndpoint(localAddress, coapConfig, null, null); } CoapEndpoint securedEndpoint = null; if (!noSecuredEndpoint && dtlsConfig != null) { - securedEndpoint = endpointFactory.createSecuredEndpoint(dtlsConfig, coapConfig, null); + securedEndpoint = endpointFactory.createSecuredEndpoint(dtlsConfig, coapConfig, null, null); } if (securedEndpoint == null && unsecuredEndpoint == 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 790bcc8e2a..a414aa5855 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 @@ -12,6 +12,7 @@ * * Contributors: * Sierra Wireless - initial API and implementation + * Rikard Höglund (RISE SICS) - Additions to support OSCORE *******************************************************************************/ package org.eclipse.leshan.server.security; @@ -99,7 +100,7 @@ public boolean checkSecurityInfo(String endpoint, Identity clientIdentity, Secur return false; } } else { - if (securityInfo != null) { + if (securityInfo != null && securityInfo.useOSCore == false) { LOG.debug("Client '{}' must connect using DTLS", endpoint); return false; } @@ -184,4 +185,9 @@ protected boolean matchX509Identity(String endpoint, String receivedX509CommonNa } return true; } + + protected boolean checkOscoreIdentity(String endpoint, Identity clientIdentity, SecurityInfo securityInfo) { + // TODO: Add comprehensive checks here + return true; + } } From 3e0c2dbef487ea6fa6b43db94c19d8ec27eb6473 Mon Sep 17 00:00:00 2001 From: rikard-sics Date: Wed, 9 Oct 2019 23:20:48 +0200 Subject: [PATCH 05/15] Allow setting OSCORE security settings in client command line arguments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rikard Höglund --- .../leshan/client/demo/LeshanClientDemo.java | 181 ++++++++++++++++-- 1 file changed, 167 insertions(+), 14 deletions(-) diff --git a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java index ba942026e5..7d0197ec8a 100644 --- a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java +++ b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java @@ -48,8 +48,11 @@ import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.eclipse.californium.core.network.config.NetworkConfig; +import org.eclipse.californium.cose.AlgorithmID; +import org.eclipse.californium.cose.CoseException; import org.eclipse.californium.elements.Connector; import org.eclipse.californium.elements.util.SslContextUtil; +import org.eclipse.californium.oscore.HashMapCtxDB; import org.eclipse.californium.oscore.OSCoreCoapStackFactory; import org.eclipse.californium.scandium.DTLSConnector; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; @@ -86,6 +89,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.upokecenter.cbor.CBORObject; + public class LeshanClientDemo { private static final Logger LOG = LoggerFactory.getLogger(LeshanClientDemo.class); @@ -181,6 +186,14 @@ public static void main(final String[] args) { X509Chapter.append("\n | See https://github.com/eclipse/leshan/wiki/Credential-files-format |"); X509Chapter.append("\n ------------------------------------------------------------------------"); + final StringBuilder OSCOREChapter = new StringBuilder(); + OSCOREChapter.append("\n ."); + OSCOREChapter.append("\n ."); + OSCOREChapter.append("\n ===============================[OSCORE]================================="); + OSCOREChapter.append("\n | By default Leshan demo use non secure connection. |"); + OSCOREChapter.append("\n | To use OSCORE, -msec -sid -rid options should be used together |"); + OSCOREChapter.append("\n ------------------------------------------------------------------------"); + options.addOption("h", "help", false, "Display help information."); options.addOption("n", true, String.format( "Set the endpoint name of the Client.\nDefault: the local hostname or '%s' if any.", DEFAULT_ENDPOINT)); @@ -228,8 +241,22 @@ public static void main(final String[] args) { options.addOption("ccert", true, "The path to your client certificate file.\n The certificate Common Name (CN) should generaly be equal to the client endpoint name (see -n option).\nThe certificate should be in X509v3 format (DER encoding)."); options.addOption("scert", true, - "The path to your server certificate file.\n The certificate should be in X509v3 format (DER encoding)."); - options.addOption("oscore", false, "Use OSCORE for communication between client and server."); + "The path to your server certificate file.\n The certificate should be in X509v3 format (DER encoding)." + + OSCOREChapter); + options.addOption("msec", true, + "The OSCORE pre-shared key used between the Client and LwM2M Server/Bootstrap Server."); + options.addOption("msalt", true, + "The OSCORE master salt used between the Client and LwM2M Server or Bootstrap Server.\nDefault: Empty"); + options.addOption("idctx", true, + "The OSCORE ID Context used between the Client and LwM2M Server or Bootstrap Server.\nDefault: Empty"); + options.addOption("sid", true, + "The OSCORE Sender ID used by the client to the LwM2M Server or Bootstrap Server."); + options.addOption("rid", true, + "The OSCORE Recipient ID used by the client to the LwM2M Server or Bootstrap Server."); + options.addOption("aead", true, + "The OSCORE AEAD algorithm used between the Client and LwM2M Server or Bootstrap Server.\nDefault: AES_CCM_16_64_128"); + options.addOption("hkdf", true, + "The OSCORE HKDF algorithm used between the Client and LwM2M Server or Bootstrap Server.\nDefault: HKDF_HMAC_SHA_256"); final StringBuilder trustStoreChapter = new StringBuilder(); trustStoreChapter.append("\n ."); @@ -317,6 +344,15 @@ public static void main(final String[] args) { } } + // Abort if all OSCORE config is not complete + if (cl.hasOption("msec")) { + if (!cl.hasOption("sid") || !cl.hasOption("rid")) { + System.err.println("msec, sid and rid should be used together to connect using OSCORE"); + formatter.printHelp(USAGE, options); + return; + } + } + // Get endpoint name String endpoint; if (cl.hasOption("n")) { @@ -571,20 +607,101 @@ public static void main(final String[] args) { // Get models folder String modelsFolderPath = cl.getOptionValue("m"); - // Set boolean controlling OSCORE usage - // TODO OSCORE : we should define a wording for OSCORE class : Oscore or OSCore or OSCORE - boolean useOSCore = false; - if (cl.hasOption("oscore")) { - System.out.println("Using OSCORE"); - // TODO OSCORE : this should be done in DefaultEndpointFactory - OSCoreCoapStackFactory.useAsDefault(OscoreHandler.getContextDB()); + // Set parameters controlling OSCORE usage + OSCoreSettings oscoreSettings = null; + if (cl.hasOption("msec")) { + + HashMapCtxDB db = OscoreHandler.getContextDB(); + // TODO OSCORE : OSCoreCoapStack should be create in Default endpoint factory + OSCoreCoapStackFactory.useAsDefault(db); + + // Parse OSCORE related command line parameters + + String mastersecretStr = cl.getOptionValue("msec"); + if (mastersecretStr == null) { + System.err.println("The OSCORE master secret must be indicated"); + formatter.printHelp(USAGE, options); + return; + } + + // Set salt to empty string if not indicated + String mastersaltStr = cl.getOptionValue("msalt"); + if (mastersaltStr == null) { + mastersaltStr = ""; + } + + String idcontextStr = cl.getOptionValue("idctx"); + if (idcontextStr != null) { + System.err.println("The OSCORE ID Context parameter is not yet supported"); + formatter.printHelp(USAGE, options); + return; + } + + String senderidStr = cl.getOptionValue("sid"); + if (senderidStr == null) { + System.err.println("The OSCORE Sender ID must be indicated"); + formatter.printHelp(USAGE, options); + return; + } + + String recipientidStr = cl.getOptionValue("rid"); + if (recipientidStr == null) { + System.err.println("The OSCORE Recipient ID must be indicated"); + formatter.printHelp(USAGE, options); + return; + } + + // Parse AEAD Algorithm (set to default if not indicated) + String aeadStr = cl.getOptionValue("aead"); + String defaultAeadAlgorithm = "AES_CCM_16_64_128"; + if (aeadStr == null) { + aeadStr = defaultAeadAlgorithm; + } + + int aeadInt; + try { + if (aeadStr.matches("-?\\d+")) { // Indicated as integer + aeadInt = AlgorithmID.FromCBOR(CBORObject.FromObject(Integer.parseInt(aeadStr))).AsCBOR().AsInt32(); + } else { // Indicated as string + aeadInt = AlgorithmID.valueOf(aeadStr).AsCBOR().AsInt32(); + } + } catch (IllegalArgumentException | CoseException e) { + System.err.println("The AEAD algorithm is not supported (" + defaultAeadAlgorithm + " recommended)"); + formatter.printHelp(USAGE, options); + return; + } + + // Parse HKDF Algorithm (set to default if not indicated) + String hkdfStr = cl.getOptionValue("hkdf"); + String defaultHkdfAlgorithm = "HKDF_HMAC_SHA_256"; + if (hkdfStr == null) { + hkdfStr = defaultHkdfAlgorithm; + } + + int hkdfInt; + try { + if (hkdfStr.matches("-?\\d+")) { // Indicated as integer + hkdfInt = AlgorithmID.FromCBOR(CBORObject.FromObject(Integer.parseInt(hkdfStr))).AsCBOR().AsInt32(); + } else { // Indicated as string + hkdfInt = AlgorithmID.valueOf(hkdfStr).AsCBOR().AsInt32(); + } + } catch (IllegalArgumentException | CoseException e) { + System.err.println("The HKDF algorithm is not supported (" + defaultHkdfAlgorithm + " recommended)"); + formatter.printHelp(USAGE, options); + return; + } + + // Save the configured OSCORE parameters + oscoreSettings = new OSCoreSettings(mastersecretStr, mastersaltStr, idcontextStr, senderidStr, + recipientidStr, aeadInt, hkdfInt); } + try { createAndStartClient(endpoint, localAddress, localPort, cl.hasOption("b"), additionalAttributes, bsAdditionalAttributes, lifetime, communicationPeriod, serverURI, pskIdentity, pskKey, clientPrivateKey, clientPublicKey, serverPublicKey, clientCertificate, serverCertificate, trustStore, latitude, longitude, scaleFactor, cl.hasOption("ocf"), cl.hasOption("oc"), - cl.hasOption("r"), cl.hasOption("f"), modelsFolderPath, ciphers, useOSCore); + cl.hasOption("r"), cl.hasOption("f"), modelsFolderPath, ciphers, oscoreSettings); } catch (Exception e) { System.err.println("Unable to create and start client ..."); e.printStackTrace(); @@ -599,7 +716,7 @@ public static void createAndStartClient(String endpoint, String localAddress, in X509Certificate clientCertificate, X509Certificate serverCertificate, List trustStore, Float latitude, Float longitude, float scaleFactor, boolean supportOldFormat, boolean supportDeprecatedCiphers, boolean reconnectOnUpdate, boolean forceFullhandshake, - String modelsFolderPath, List ciphers, boolean useOSCore) throws Exception { + String modelsFolderPath, List ciphers, OSCoreSettings oscoreSettings) throws Exception { locationInstance = new MyLocation(latitude, longitude, scaleFactor); @@ -641,9 +758,10 @@ public static void createAndStartClient(String endpoint, String localAddress, in initializer.setInstancesForObject(SECURITY, x509(serverURI, 123, clientCertificate.getEncoded(), clientPrivateKey.getEncoded(), serverCertificate.getEncoded())); initializer.setInstancesForObject(SERVER, new Server(123, lifetime, BindingMode.U, false)); - } else if (useOSCore) { - String clientName = Hex.encodeHexString(endpoint.getBytes()); - Oscore oscoreObject = new Oscore(12345, "11223344", clientName, "BB"); // Partially hardcoded values + } else if (oscoreSettings != null) { + Oscore oscoreObject = new Oscore(12345, oscoreSettings.masterSecret, oscoreSettings.senderId, + oscoreSettings.recipientId, oscoreSettings.aeadAlgorithm, oscoreSettings.hkdfAlgorithm, + oscoreSettings.masterSalt); initializer.setInstancesForObject(SECURITY, oscoreOnly(serverURI, 123, oscoreObject.getId())); initializer.setInstancesForObject(OSCORE, oscoreObject); initializer.setInstancesForObject(SERVER, new Server(123, lifetime, BindingMode.U, false)); @@ -821,6 +939,21 @@ public void objectAdded(LwM2mObjectEnabler object) { Hex.encodeHexString(clientPrivateKey.getEncoded())); } + // Display OSCORE settings + if (oscoreSettings != null) { + try { + LOG.info( + "Client uses OSCORE : \n Master Secret (Hex): {} \n Master Salt (Hex): {} \n Sender ID (Hex): {} \n Recipient ID (Hex) {} \n AEAD Algorithm: {} ({}) \n HKDF Algorithm: {} ({})", + oscoreSettings.masterSecret, oscoreSettings.masterSalt, oscoreSettings.senderId, + oscoreSettings.recipientId, oscoreSettings.aeadAlgorithm, + AlgorithmID.FromCBOR(CBORObject.FromObject(oscoreSettings.aeadAlgorithm)), + oscoreSettings.hkdfAlgorithm, + AlgorithmID.FromCBOR(CBORObject.FromObject(oscoreSettings.hkdfAlgorithm))); + } catch (CoseException e) { + throw new IllegalStateException("Invalid agorithm used for OSCORE."); + } + } + // Print commands help StringBuilder commandsHelp = new StringBuilder("Commands available :"); commandsHelp.append(System.lineSeparator()); @@ -901,4 +1034,24 @@ public void run() { } } } + + // Class for holding OSCORE related information + private static class OSCoreSettings { + public String masterSecret; + public String masterSalt; + public String senderId; + public String recipientId; + public int aeadAlgorithm; + public int hkdfAlgorithm; + + public OSCoreSettings(String masterSecret, String masterSalt, String idContext, String senderId, + String recipientId, int aeadAlgorithm, int hkdfAlgorithm) { + this.masterSecret = masterSecret; + this.masterSalt = masterSalt; + this.senderId = senderId; + this.recipientId = recipientId; + this.aeadAlgorithm = aeadAlgorithm; + this.hkdfAlgorithm = hkdfAlgorithm; + } + } } From 9fd06b0ba5e1682063ba9dea7929c98844022fe3 Mon Sep 17 00:00:00 2001 From: rikard-sics Date: Thu, 31 Oct 2019 09:07:46 +0100 Subject: [PATCH 06/15] Implements an identity and matching of the identity for OSCORE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An identity string based on OSCORE parameters is now set for connected clients that use OSCORE. The identity string contains the OSCORE Sender and Recipient ID the client and server are using. These parameters can be retrieved from the source EndpointContext. The identity for OSCORE is also set as a SecurityInfo based on the OSCORE context configured on the server to use with a specific client. The identity of a connected client and the configured SecurityInfo are then matched in the SecurityChecker. Signed-off-by: Rikard Höglund --- .../core/californium/EndpointContextUtil.java | 14 +++++ .../eclipse/leshan/core/request/Identity.java | 47 +++++++++++++---- .../server/security/SecurityChecker.java | 27 +++++++++- .../leshan/server/security/SecurityInfo.java | 51 +++++++++++++++---- .../servlet/json/SecurityDeserializer.java | 12 +---- 5 files changed, 120 insertions(+), 31 deletions(-) diff --git a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/EndpointContextUtil.java b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/EndpointContextUtil.java index 5258637db7..9ae9a2586a 100644 --- a/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/EndpointContextUtil.java +++ b/leshan-core-cf/src/main/java/org/eclipse/leshan/core/californium/EndpointContextUtil.java @@ -14,6 +14,7 @@ * Sierra Wireless - initial API and implementation * Achim Kraus (Bosch Software Innovations GmbH) - add support for californium * endpoint context + * Rikard Höglund (RISE SICS) - Additions to support OSCORE *******************************************************************************/ package org.eclipse.leshan.core.californium; @@ -32,6 +33,7 @@ import org.eclipse.californium.elements.auth.PreSharedKeyIdentity; import org.eclipse.californium.elements.auth.RawPublicKeyIdentity; import org.eclipse.californium.elements.auth.X509CertPath; +import org.eclipse.californium.oscore.OSCoreEndpointContextInfo; import org.eclipse.leshan.core.request.Identity; /** @@ -65,12 +67,22 @@ public static Identity extractIdentity(EndpointContext context) { throw new IllegalStateException( String.format("Unable to extract sender identity : unexpected type of Principal %s [%s]", senderIdentity.getClass(), senderIdentity.toString())); + } else { + // Build identity for OSCORE if it is used + if (context.get(OSCoreEndpointContextInfo.OSCORE_SENDER_ID) != null) { + String oscoreIdentity = "sid=" + context.get(OSCoreEndpointContextInfo.OSCORE_SENDER_ID) + ",rid=" + + context.get(OSCoreEndpointContextInfo.OSCORE_RECIPIENT_ID); + return Identity.oscoreOnly(peerAddress, oscoreIdentity.toLowerCase()); + } } return Identity.unsecure(peerAddress); } /** * Create Californium {@link EndpointContext} from Leshan {@link Identity}. + *

    + * OSCORE does not use a Principal but automatically sets properties in the endpoint context at message + * transmission/reception. * * @param identity The Leshan {@link Identity} to convert. * @param allowConnectionInitiation This request can initiate a Handshake if there is no DTLS connection. @@ -90,6 +102,8 @@ public static EndpointContext extractContext(Identity identity, boolean allowCon } } + // TODO OSCORE : should we add properties to endpoint context ? + if (peerIdentity != null && allowConnectionInitiation) { return new MapBasedEndpointContext(identity.getPeerAddress(), peerIdentity, DtlsEndpointContext.KEY_HANDSHAKE_MODE, DtlsEndpointContext.HANDSHAKE_MODE_AUTO); diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/Identity.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/Identity.java index 601cc34aff..f6ddd61cb8 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/request/Identity.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/Identity.java @@ -13,6 +13,7 @@ * Contributors: * Sierra Wireless - initial API and implementation * Achim Kraus (Bosch Software Innovations GmbH) - add protected constructor for sub-classing + * Rikard Höglund (RISE SICS) - Additions to support OSCORE *******************************************************************************/ package org.eclipse.leshan.core.request; @@ -35,13 +36,16 @@ public class Identity implements Serializable { private final String pskIdentity; private final PublicKey rawPublicKey; private final String x509CommonName; + private final String oscoreIdentity; - private Identity(InetSocketAddress peerAddress, String pskIdentity, PublicKey rawPublicKey, String x509CommonName) { + private Identity(InetSocketAddress peerAddress, String pskIdentity, PublicKey rawPublicKey, String x509CommonName, + String oscoreIdentity) { Validate.notNull(peerAddress); this.peerAddress = peerAddress; this.pskIdentity = pskIdentity; this.rawPublicKey = rawPublicKey; this.x509CommonName = x509CommonName; + this.oscoreIdentity = oscoreIdentity; } protected Identity(Identity identity) { @@ -49,6 +53,7 @@ protected Identity(Identity identity) { this.pskIdentity = identity.pskIdentity; this.rawPublicKey = identity.rawPublicKey; this.x509CommonName = identity.x509CommonName; + this.oscoreIdentity = identity.oscoreIdentity; } public InetSocketAddress getPeerAddress() { @@ -67,6 +72,10 @@ public String getX509CommonName() { return x509CommonName; } + public String getOscoreIdentity() { + return oscoreIdentity; + } + public boolean isPSK() { return pskIdentity != null && !pskIdentity.isEmpty(); } @@ -79,40 +88,52 @@ public boolean isX509() { return x509CommonName != null && !x509CommonName.isEmpty(); } + public boolean isOSCORE() { + return oscoreIdentity != null && !oscoreIdentity.isEmpty(); + } + public boolean isSecure() { return isPSK() || isRPK() || isX509(); } public static Identity unsecure(InetSocketAddress peerAddress) { - return new Identity(peerAddress, null, null, null); + return new Identity(peerAddress, null, null, null, null); } public static Identity unsecure(InetAddress address, int port) { - return new Identity(new InetSocketAddress(address, port), null, null, null); + return new Identity(new InetSocketAddress(address, port), null, null, null, null); } public static Identity psk(InetSocketAddress peerAddress, String identity) { - return new Identity(peerAddress, identity, null, null); + return new Identity(peerAddress, identity, null, null, null); } public static Identity psk(InetAddress address, int port, String identity) { - return new Identity(new InetSocketAddress(address, port), identity, null, null); + return new Identity(new InetSocketAddress(address, port), identity, null, null, null); } public static Identity rpk(InetSocketAddress peerAddress, PublicKey publicKey) { - return new Identity(peerAddress, null, publicKey, null); + return new Identity(peerAddress, null, publicKey, null, null); } public static Identity rpk(InetAddress address, int port, PublicKey publicKey) { - return new Identity(new InetSocketAddress(address, port), null, publicKey, null); + return new Identity(new InetSocketAddress(address, port), null, publicKey, null, null); } public static Identity x509(InetSocketAddress peerAddress, String commonName) { - return new Identity(peerAddress, null, null, commonName); + return new Identity(peerAddress, null, null, commonName, null); } public static Identity x509(InetAddress address, int port, String commonName) { - return new Identity(new InetSocketAddress(address, port), null, null, commonName); + return new Identity(new InetSocketAddress(address, port), null, null, commonName, null); + } + + public static Identity oscoreOnly(InetSocketAddress peerAddress, String oscoreIdentity) { + return new Identity(peerAddress, null, null, null, oscoreIdentity); + } + + public static Identity oscoreOnly(InetAddress address, int port, String oscoreIdentity) { + return new Identity(new InetSocketAddress(address, port), null, null, null, oscoreIdentity); } @Override @@ -123,6 +144,8 @@ else if (rawPublicKey != null) return String.format("Identity %s[rpk=%s]", peerAddress, rawPublicKey); else if (x509CommonName != null) return String.format("Identity %s[x509=%s]", peerAddress, x509CommonName); + else if (oscoreIdentity != null) + return String.format("Identity %s[oscore=%s]", peerAddress, oscoreIdentity); else return String.format("Identity %s[unsecure]", peerAddress); } @@ -135,6 +158,7 @@ public int hashCode() { result = prime * result + ((pskIdentity == null) ? 0 : pskIdentity.hashCode()); result = prime * result + ((rawPublicKey == null) ? 0 : rawPublicKey.hashCode()); result = prime * result + ((x509CommonName == null) ? 0 : x509CommonName.hashCode()); + result = prime * result + ((oscoreIdentity == null) ? 0 : oscoreIdentity.hashCode()); return result; } @@ -167,6 +191,11 @@ public boolean equals(Object obj) { return false; } else if (!x509CommonName.equals(other.x509CommonName)) return false; + if (oscoreIdentity == null) { + if (other.oscoreIdentity != null) + return false; + } else if (!oscoreIdentity.equals(other.oscoreIdentity)) + return false; return true; } } 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 a414aa5855..e556e985c2 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 @@ -100,7 +100,9 @@ public boolean checkSecurityInfo(String endpoint, Identity clientIdentity, Secur return false; } } else { - if (securityInfo != null && securityInfo.useOSCore == false) { + if (clientIdentity.isOSCORE()) { + return checkOscoreIdentity(endpoint, clientIdentity, securityInfo); + } else if (securityInfo != null) { LOG.debug("Client '{}' must connect using DTLS", endpoint); return false; } @@ -187,7 +189,28 @@ protected boolean matchX509Identity(String endpoint, String receivedX509CommonNa } protected boolean checkOscoreIdentity(String endpoint, Identity clientIdentity, SecurityInfo securityInfo) { - // TODO: Add comprehensive checks here + // Manage OSCORE authentication + // ---------------------------------------------------- + if (!securityInfo.useOSCORE()) { + LOG.debug("Client '{}' is not supposed to use OSCORE to authenticate", endpoint); + return false; + } + + if (!matchOscoreIdentity(endpoint, clientIdentity.getOscoreIdentity(), securityInfo.getOscoreIdentity())) { + return false; + } + + LOG.trace("Authenticated client '{}' using OSCORE", endpoint); + return true; + } + + protected boolean matchOscoreIdentity(String endpoint, String receivedOscoreIdentity, + String expectedOscoreIdentity) { + if (!receivedOscoreIdentity.equals(expectedOscoreIdentity)) { + LOG.debug("Invalid identity for client '{}': expected '{}' but was '{}'", endpoint, expectedOscoreIdentity, + receivedOscoreIdentity); + return false; + } return true; } } 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 ada6ddb3a2..20734cfc1b 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 @@ -22,6 +22,7 @@ import org.eclipse.californium.oscore.HashMapCtxDB; import org.eclipse.californium.oscore.OSCoreCtx; +import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.core.util.Validate; import org.eclipse.leshan.server.OscoreHandler; @@ -56,7 +57,7 @@ public class SecurityInfo implements Serializable { private final boolean useX509Cert; // TODO OSCORE : Save content properly information here. Must be serializable. - boolean useOSCore; + private final String oscoreIdentity; private SecurityInfo(String endpoint, String identity, byte[] preSharedKey, PublicKey rawPublicKey, boolean useX509Cert, OSCoreCtx oscoreCtx) { @@ -66,7 +67,7 @@ private SecurityInfo(String endpoint, String identity, byte[] preSharedKey, Publ this.preSharedKey = preSharedKey; this.rawPublicKey = rawPublicKey; this.useX509Cert = useX509Cert; - this.useOSCore = oscoreCtx != null; + this.oscoreIdentity = generateOscoreIdentity(oscoreCtx); } /** @@ -113,17 +114,27 @@ public static SecurityInfo newX509CertInfo(String endpoint) { /** * Construct a {@link SecurityInfo} when using OSCORE. */ - public static SecurityInfo newOSCoreInfo(String endpoint, String identity, OSCoreCtx oscoreCtx) { - Validate.notEmpty(identity); - Validate.notNull(identity); + public static SecurityInfo newOSCoreInfo(String endpoint, OSCoreCtx oscoreCtx) { Validate.notNull(oscoreCtx); // Add the OSCORE Context to the context database HashMapCtxDB db = OscoreHandler.getContextDB(); db.addContext(oscoreCtx); - // TODO OSCORE : identity is reserved for PSK - return new SecurityInfo(endpoint, identity, null, null, false, oscoreCtx); + return new SecurityInfo(endpoint, null, null, null, false, oscoreCtx); + } + + /** + * Generates an OSCORE identity from an OSCORE context + */ + private static String generateOscoreIdentity(OSCoreCtx oscoreCtx) { + if (oscoreCtx == null) { + return null; + } + + String oscoreIdentity = "sid=" + Hex.encodeHexString(oscoreCtx.getSenderId()) + ",rid=" + + Hex.encodeHexString(oscoreCtx.getRecipientId()); + return oscoreIdentity; } /** @@ -177,6 +188,20 @@ public boolean useX509Cert() { return useX509Cert; } + /** + * @return The OSCORE identity + */ + public String getOscoreIdentity() { + return oscoreIdentity; + } + + /** + * @return true if this client should use OSCORE. + */ + public boolean useOSCORE() { + return oscoreIdentity != null; + } + @Override public int hashCode() { final int prime = 31; @@ -186,6 +211,7 @@ public int hashCode() { result = prime * result + Arrays.hashCode(preSharedKey); result = prime * result + ((rawPublicKey == null) ? 0 : rawPublicKey.hashCode()); result = prime * result + (useX509Cert ? 1231 : 1237); + result = prime * result + ((oscoreIdentity == null) ? 0 : oscoreIdentity.hashCode()); return result; } @@ -217,14 +243,21 @@ public boolean equals(Object obj) { return false; if (useX509Cert != other.useX509Cert) return false; + if (oscoreIdentity == null) { + if (other.oscoreIdentity != null) + return false; + } else if (!oscoreIdentity.equals(other.oscoreIdentity)) + return false; + return true; } @Override 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]", endpoint, - identity, rawPublicKey, useX509Cert); + return String.format( + "SecurityInfo [endpoint=%s, identity=%s, rawPublicKey=%s, useX509Cert=%s, oscoreIdentity=%s]", endpoint, + identity, rawPublicKey, useX509Cert, oscoreIdentity); } } diff --git a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/SecurityDeserializer.java b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/SecurityDeserializer.java index e4f1621c6a..b86985b628 100644 --- a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/SecurityDeserializer.java +++ b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/SecurityDeserializer.java @@ -174,17 +174,7 @@ public SecurityInfo deserialize(JsonElement json, Type typeOfT, JsonDeserializat throw new JsonParseException("Failed to generate OSCORE context", e); } - // Create an identity string from the OSCORE context information - StringBuilder b = new StringBuilder(); - if (ctx.getIdContext() != null) { - b.append(Hex.encodeHex(ctx.getIdContext())); - b.append(":"); - } - b.append(Hex.encodeHex(ctx.getRecipientId())); - String identity = b.toString(); - - // TODO OSCORE : identity is for PSK and should not be used for OSCORE - info = SecurityInfo.newOSCoreInfo(endpoint, identity, ctx); + info = SecurityInfo.newOSCoreInfo(endpoint, ctx); } else { throw new JsonParseException("Invalid security info content"); } From 6149aa1229cd87087167e12f4366310c08bbdb01 Mon Sep 17 00:00:00 2001 From: rikard-sics Date: Thu, 25 Jun 2020 13:24:23 +0200 Subject: [PATCH 07/15] Support for communication to the bootstrap server using OSCORE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds support for the client communicating to the bootstrap server using OSCORE. The bootstrap server web UI has been extended to accept settings for OSCORE. Some modifications were also done on the client to support it bootstrapping using OSCORE. It supports OSCORE settings as command line parameters since before. Signed-off-by: Rikard Höglund --- .../demo/BootstrapConfigSecurityStore.java | 26 ++ .../demo/LeshanBootstrapServerDemo.java | 17 +- .../src/main/resources/webapp/index.html | 1 + .../main/resources/webapp/js/bsconfigstore.js | 10 +- .../resources/webapp/tag/bootstrap-modal.tag | 22 +- .../main/resources/webapp/tag/bootstrap.tag | 2 +- .../resources/webapp/tag/oscore-input.tag | 259 ++++++++++++++++++ .../webapp/tag/securityconfig-input.tag | 25 +- .../leshan/client/object/Security.java | 8 + .../client/servers/ServersInfoExtractor.java | 32 ++- .../leshan/client/demo/LeshanClientDemo.java | 7 + .../server/bootstrap/BootstrapConfig.java | 29 ++ .../bootstrap/ConfigurationChecker.java | 14 + .../InMemoryBootstrapConfigStore.java | 58 ++++ .../server/security/SecurityChecker.java | 10 +- .../webapp/partials/security-list.html | 4 +- 16 files changed, 510 insertions(+), 14 deletions(-) create mode 100644 leshan-bsserver-demo/src/main/resources/webapp/tag/oscore-input.tag diff --git a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/BootstrapConfigSecurityStore.java b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/BootstrapConfigSecurityStore.java index 289d24277f..93aae03286 100644 --- a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/BootstrapConfigSecurityStore.java +++ b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/BootstrapConfigSecurityStore.java @@ -12,6 +12,7 @@ * * Contributors: * Sierra Wireless - initial API and implementation + * Rikard Höglund (RISE) - additions to support OSCORE *******************************************************************************/ package org.eclipse.leshan.server.bootstrap.demo; @@ -22,9 +23,14 @@ import java.util.Iterator; import java.util.Map; +import org.eclipse.californium.oscore.HashMapCtxDB; +import org.eclipse.californium.oscore.OSCoreCtx; import org.eclipse.leshan.core.SecurityMode; +import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.core.util.SecurityUtil; +import org.eclipse.leshan.server.OscoreHandler; import org.eclipse.leshan.server.bootstrap.BootstrapConfig; +import org.eclipse.leshan.server.bootstrap.BootstrapConfig.OscoreObject; import org.eclipse.leshan.server.bootstrap.BootstrapConfig.ServerSecurity; import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore; import org.eclipse.leshan.server.bootstrap.EditableBootstrapConfigStore; @@ -88,6 +94,26 @@ public Iterator getAllByEndpoint(String endpoint) { BootstrapConfig bsConfig = bootstrapConfigStore.get(endpoint, null, null); + // TODO this should be done via OSCORE store ? + // Extract OSCORE security info + if (bsConfig != null && bsConfig.oscore != null && !bsConfig.oscore.isEmpty()) { + LOG.trace("Extracting OSCORE security info for endpoint {}", endpoint); + + // First find the context for this endpoint + for (Map.Entry oscoreEntry : bsConfig.oscore.entrySet()) { + OscoreObject value = oscoreEntry.getValue(); + + HashMapCtxDB db = OscoreHandler.getContextDB(); + byte[] rid = Hex.decodeHex(value.oscoreRecipientId.toCharArray()); + OSCoreCtx ctx = db.getContext(rid); + + // Create the security info (will re-add the context to the db) + SecurityInfo securityInfo = SecurityInfo.newOSCoreInfo(endpoint, ctx); + + return Arrays.asList(securityInfo).iterator(); + } + } + if (bsConfig == null || bsConfig.security == null) return null; diff --git a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/LeshanBootstrapServerDemo.java b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/LeshanBootstrapServerDemo.java index 89f9bdc441..200325b3d1 100755 --- a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/LeshanBootstrapServerDemo.java +++ b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/LeshanBootstrapServerDemo.java @@ -14,6 +14,7 @@ * Sierra Wireless - initial API and implementation * Achim Kraus (Bosch Software Innovations GmbH) - add parameter for * configuration filename + * Rikard Höglund (RISE) - additions to support OSCORE *******************************************************************************/ package org.eclipse.leshan.server.bootstrap.demo; @@ -37,6 +38,7 @@ import org.eclipse.californium.core.network.config.NetworkConfig; import org.eclipse.californium.core.network.config.NetworkConfig.Keys; import org.eclipse.californium.elements.util.SslContextUtil; +import org.eclipse.californium.oscore.OSCoreCoapStackFactory; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletHolder; @@ -46,6 +48,7 @@ import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.StaticModel; import org.eclipse.leshan.core.util.SecurityUtil; +import org.eclipse.leshan.server.OscoreHandler; import org.eclipse.leshan.server.bootstrap.BootstrapConfigurationStoreAdapter; import org.eclipse.leshan.server.bootstrap.demo.servlet.BootstrapServlet; import org.eclipse.leshan.server.bootstrap.demo.servlet.ServerServlet; @@ -124,12 +127,14 @@ public static void main(String[] args) { final StringBuilder trustStoreChapter = new StringBuilder(); trustStoreChapter.append("\n ."); - trustStoreChapter.append("\n URI format: file://##"); + trustStoreChapter + .append("\n URI format: file://##"); trustStoreChapter.append("\n ."); trustStoreChapter.append("\n Where:"); trustStoreChapter.append("\n - path-to-trust-store-file is path to pkcs12 trust store file"); trustStoreChapter.append("\n - hex-store-password is HEX formatted password for store"); - trustStoreChapter.append("\n - alias-pattern can be used to filter trusted certificates and can also be empty to get all"); + trustStoreChapter.append( + "\n - alias-pattern can be used to filter trusted certificates and can also be empty to get all"); trustStoreChapter.append("\n ."); trustStoreChapter.append("\n Default: All certificates are trusted which is only OK for a demo."); @@ -283,7 +288,8 @@ public static void main(String[] args) { // check input exists if (!input.exists()) { - System.err.println("Failed to load trust store - file or directory does not exist : " + input.toString()); + System.err.println( + "Failed to load trust store - file or directory does not exist : " + input.toString()); formatter.printHelp(USAGE, options); return; } @@ -322,6 +328,11 @@ public static void createAndStartServer(String webAddress, int webPort, String l String secureLocalAddress, Integer secureLocalPort, String modelsFolderPath, String configFilename, boolean supportDeprecatedCiphers, PublicKey publicKey, PrivateKey privateKey, X509Certificate certificate, List trustStore) throws Exception { + + // Enable OSCORE stack (fine to do even when using DTLS or only CoAP) + // TODO OSCORE : this should be done in DefaultEndpointFactory ? + OSCoreCoapStackFactory.useAsDefault(OscoreHandler.getContextDB()); + // Create Models List models = ObjectLoader.loadDefault(); if (modelsFolderPath != null) { diff --git a/leshan-bsserver-demo/src/main/resources/webapp/index.html b/leshan-bsserver-demo/src/main/resources/webapp/index.html index ca580de4be..6bdb715187 100644 --- a/leshan-bsserver-demo/src/main/resources/webapp/index.html +++ b/leshan-bsserver-demo/src/main/resources/webapp/index.html @@ -13,6 +13,7 @@ + diff --git a/leshan-bsserver-demo/src/main/resources/webapp/js/bsconfigstore.js b/leshan-bsserver-demo/src/main/resources/webapp/js/bsconfigstore.js index 614cc62d09..5d65e51607 100644 --- a/leshan-bsserver-demo/src/main/resources/webapp/js/bsconfigstore.js +++ b/leshan-bsserver-demo/src/main/resources/webapp/js/bsconfigstore.js @@ -6,6 +6,13 @@ var configFromRestToUI = function(config){ var security = config.security[i]; if (security.bootstrapServer){ newConfig.bs.push({security:security}); + + // add oscore object (if any) to bs + var oscoreObjectInstanceId = security.oscoreSecurityMode; + var oscore = config.oscore[oscoreObjectInstanceId]; + if(oscore){ + newConfig.bs.push({oscore:oscore}); + } }else{ newConfig.dm = []; // search for DM information; @@ -33,10 +40,11 @@ var configsFromRestToUI = function(configs){ //convert config from UI to rest API format: var configFromUIToRest = function(config){ - var newConfig = {servers:{}, security:{}}; + var newConfig = {servers:{}, security:{}, oscore:{}}; for (var i = 0; i < config.bs.length; i++) { var bs = config.bs[i]; newConfig.security[i] = bs.security; + newConfig.oscore[i] = bs.oscore; } for (var j = 0; j < config.dm.length; j++) { var dm = config.dm[j]; diff --git a/leshan-bsserver-demo/src/main/resources/webapp/tag/bootstrap-modal.tag b/leshan-bsserver-demo/src/main/resources/webapp/tag/bootstrap-modal.tag index b2f7572d5b..5b1c1dce3b 100644 --- a/leshan-bsserver-demo/src/main/resources/webapp/tag/bootstrap-modal.tag +++ b/leshan-bsserver-demo/src/main/resources/webapp/tag/bootstrap-modal.tag @@ -36,7 +36,7 @@ serverpubkey= {serversecurity.rpk.hexDer} servercertificate= {serversecurity.certificate.hexDer} disable = { {uri:true, serverpubkey:true, servercertificate:true}} - secmode = { {no_sec:true, psk:true,rpk:true, x509:true}} + secmode = { {no_sec:true, psk:true,rpk:true, x509:true, oscore:true}} >

    @@ -93,6 +93,22 @@ function submit(){ var lwserver = tag.refs.lwserver.get_value() var bsserver = tag.refs.bsserver.get_value() + + if(bsserver.secmode === "OSCORE") { + var bsserverOscore = bsserver.oscore; + var oscore = + { + oscoreMasterSecret : bsserverOscore.masterSecret, + oscoreSenderId : bsserverOscore.senderId, + oscoreRecipientId : bsserverOscore.recipientId, + oscoreAeadAlgorithm : bsserverOscore.aeadAlgorithm, + oscoreHmacAlgorithm : bsserverOscore.hkdfAlgorithm, + oscoreMasterSalt : bsserverOscore.masterSalt, + oscoreIdContext : bsserverOscore.idContext + } + var bsOscoreSecurityMode = 0; // link to bs oscore object + bsserver.secmode = "NO_SEC"; // act as no_sec from here + } // add config to the store bsConfigStore.add(endpoint.value, { @@ -131,7 +147,9 @@ smsBindingKeySecret : [ ], smsSecurityMode : "NO_SEC", uri : bsserver.uri, - } + oscoreSecurityMode : bsOscoreSecurityMode + }, + oscore }] }); $('#bootstrap-modal').modal('hide'); diff --git a/leshan-bsserver-demo/src/main/resources/webapp/tag/bootstrap.tag b/leshan-bsserver-demo/src/main/resources/webapp/tag/bootstrap.tag index e4bf5d69f6..bb4d03b010 100644 --- a/leshan-bsserver-demo/src/main/resources/webapp/tag/bootstrap.tag +++ b/leshan-bsserver-demo/src/main/resources/webapp/tag/bootstrap.tag @@ -54,7 +54,7 @@ { endpoint } -
    +

    { security.uri }
    security mode : {security.securityMode}
    diff --git a/leshan-bsserver-demo/src/main/resources/webapp/tag/oscore-input.tag b/leshan-bsserver-demo/src/main/resources/webapp/tag/oscore-input.tag new file mode 100644 index 0000000000..f66e6e6cfe --- /dev/null +++ b/leshan-bsserver-demo/src/main/resources/webapp/tag/oscore-input.tag @@ -0,0 +1,259 @@ + + +

    + +
    + +

    Hexadecimal format

    +

    The master secret is required

    +

    Hexadecimal format is expected

    +

    The master secret is too long

    +
    +
    + +
    + +
    + +

    Hexadecimal format

    +

    Hexadecimal format is expected

    +

    The master salt is too long

    +
    +
    + +
    + +
    + +

    Not supported

    +

    Hexadecimal format is expected

    +

    The ID context is too long

    +
    +
    + +
    + +
    + +

    Hexadecimal format

    +

    Hexadecimal format is expected

    +

    The sender ID is too long

    +
    +
    + +
    + +
    + +

    Hexadecimal format

    +

    Hexadecimal format is expected

    +

    The recipient ID is too long

    +
    +
    + +
    + +
    + +

    The AEAD algorithm is too long

    +
    +
    + +
    + +
    + +

    The HKDF algorithm is too long

    +
    +
    + + + + diff --git a/leshan-bsserver-demo/src/main/resources/webapp/tag/securityconfig-input.tag b/leshan-bsserver-demo/src/main/resources/webapp/tag/securityconfig-input.tag index 4fea9744c3..9b9198d8d3 100644 --- a/leshan-bsserver-demo/src/main/resources/webapp/tag/securityconfig-input.tag +++ b/leshan-bsserver-demo/src/main/resources/webapp/tag/securityconfig-input.tag @@ -16,6 +16,7 @@ +
    @@ -34,6 +35,11 @@
    + + +
    + +