Skip to content

Commit

Permalink
#725: Add support of OSCORE.
Browse files Browse the repository at this point in the history
Signed-off-by: Rikard Höglund <rikard.hoglund@ri.se>
Also-by: Simon Bernard <sbernard@sierrawireless.com>
Also-by: Michał Wadowski <Michal.Wadowski@orange.com>
  • Loading branch information
rikard-sics authored and sbernard31 committed Mar 23, 2022
1 parent 2adb128 commit c73ee4d
Show file tree
Hide file tree
Showing 40 changed files with 1,370 additions and 61 deletions.
5 changes: 5 additions & 0 deletions leshan-client-cf/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ Contributors:
<groupId>org.eclipse.californium</groupId>
<artifactId>scandium</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.californium</groupId>
<artifactId>cf-oscore</artifactId>
<version>${californium.version}</version>
</dependency>

<!-- test dependencies -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -26,13 +29,20 @@
import java.util.concurrent.TimeUnit;

import org.eclipse.californium.core.CoapServer;
import org.eclipse.californium.core.Utils;
import org.eclipse.californium.core.config.CoapConfig;
import org.eclipse.californium.core.network.CoapEndpoint;
import org.eclipse.californium.core.network.Endpoint;
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.elements.config.Configuration;
import org.eclipse.californium.elements.util.CertPathUtil;
import org.eclipse.californium.oscore.ContextRederivation.PHASE;
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.DtlsConfig;
import org.eclipse.californium.scandium.config.DtlsConfig.DtlsRole;
Expand All @@ -54,6 +64,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.
Expand Down Expand Up @@ -228,9 +240,58 @@ public synchronized ServerIdentity createEndpoint(ServerInfo serverInfo, boolean
}
}

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 = OscoreClientHandler.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 {
byte[] idContext = null;
OSCoreCtx ctx = new OSCoreCtx(serverInfo.masterSecret, true, aeadAlg, serverInfo.senderId,
serverInfo.recipientId, hkdfAlg, 32, serverInfo.masterSalt, idContext, 1000);
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();
// Support Appendix B.2 functionality
ctx.setContextRederivationEnabled(true);
// Set to initiate Appendix B.2 procedure on first sent request
// To either server or bs server
ctx.setContextRederivationPhase(PHASE.CLIENT_INITIATE);

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);

// Build server identity for OSCORE
String sidString = Utils.toHexString(serverInfo.senderId).replace("[", "").replace("]", "").toLowerCase();
String ridString = Utils.toHexString(serverInfo.recipientId).replace("[", "").replace("]", "")
.toLowerCase();
String oscoreIdentity = "sid=" + sidString + ",rid=" + ridString;
serverIdentity = Identity.oscoreOnly(serverInfo.getAddress(), oscoreIdentity);
} else {
currentEndpoint = endpointFactory.createUnsecuredEndpoint(localAddress, coapConfig, null);
currentEndpoint = endpointFactory.createUnsecuredEndpoint(localAddress, coapConfig, null, null);
serverIdentity = Identity.unsecure(serverInfo.getAddress());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,8 @@ public void destroy(boolean deregister) {
requestSender.destroy();
objectTree.destroy();

OscoreClientHandler.purge();

LOG.info("Leshan client destroyed.");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*******************************************************************************
* 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 OscoreClientHandler {

private static HashMapCtxDB db;

public static HashMapCtxDB getContextDB() {
if (db == null) {
db = new HashMapCtxDB();
}
return db;
}

public static void purge() {
db = null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* Contributors:
* Zebra Technologies - initial API and implementation
* Michał Wadowski (Orange) - Improved compliance with rfc6690
* Rikard Höglund (RISE SICS) - Additions to support OSCORE
*******************************************************************************/
package org.eclipse.leshan.client.californium.request;

Expand All @@ -23,7 +24,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.OscoreClientHandler;
import org.eclipse.leshan.client.request.LwM2mRequestSender;
import org.eclipse.leshan.client.servers.ServerIdentity;
import org.eclipse.leshan.core.californium.AsyncRequestObserver;
Expand Down Expand Up @@ -78,6 +83,18 @@ public <T extends LwM2mResponse> 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 = OscoreClientHandler.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<T> syncMessageObserver = new SyncRequestObserver<T>(coapRequest, timeout) {
@Override
Expand Down Expand Up @@ -106,6 +123,18 @@ public <T extends LwM2mResponse> 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 = OscoreClientHandler.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<T>(coapRequest, responseCallback, errorCallback, timeout,
executor) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*******************************************************************************
* 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) - 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;
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 final static List<Integer> supportedResources = Arrays.asList(OSCORE_Master_Secret, OSCORE_Sender_ID,
OSCORE_Recipient_ID, OSCORE_AEAD_Algorithm, OSCORE_HMAC_Algorithm, OSCORE_Master_Salt);

private String masterSecret;
private String senderId;
private String recipientId;
private int aeadAlgorithm;
private int hkdfAlgorithm;
private String masterSalt;

public Oscore() {

}

/**
* 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.hkdfAlgorithm = hkdfAlgorithm;
this.masterSalt = masterSalt;
}

/**
* Constructor providing some default values.
*
* aeadAlgorithm = 10; //AES_CCM_16_64_128m hmacAlgorithm = -10; //HKDF_HMAC_SHA_256, masterSalt = "";
*
*/
public Oscore(int instanceId, String masterSecret, String senderId, String recipientId) {
this(instanceId, masterSecret, senderId, recipientId, 10, -10, "");
}

@Override
public WriteResponse write(ServerIdentity identity, boolean replace, int resourceId, LwM2mResource value) {
LOG.debug("Write on resource {}: {}", resourceId, value);

// 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, replace, resourceId, value);
}

}

@Override
public ReadResponse read(ServerIdentity identity, int resourceid) {
LOG.debug("Read on resource {}", resourceid);
// only accessible for internal read?

switch (resourceid) {

case OSCORE_Master_Secret:
return ReadResponse.success(resourceid, masterSecret);

case OSCORE_Sender_ID:
return ReadResponse.success(resourceid, senderId);

case OSCORE_Recipient_ID:
return ReadResponse.success(resourceid, recipientId);

case OSCORE_AEAD_Algorithm:
return ReadResponse.success(resourceid, aeadAlgorithm);

case OSCORE_HMAC_Algorithm:
return ReadResponse.success(resourceid, hkdfAlgorithm);

case OSCORE_Master_Salt:
return ReadResponse.success(resourceid, masterSalt);

default:
return super.read(identity, resourceid);
}
}

@Override
public List<Integer> getAvailableResourceIds(ObjectModel model) {
return supportedResources;
}

}
Loading

0 comments on commit c73ee4d

Please sign in to comment.