Skip to content

Commit

Permalink
#1203: Add Oscore Support to leshan-client-demo
Browse files Browse the repository at this point in the history
  • Loading branch information
sbernard31 committed Jun 7, 2022
1 parent ec7eb14 commit 9c7abd8
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.eclipse.leshan.client.demo.cli.interactive.InteractiveCommands;
import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory;
import org.eclipse.leshan.client.object.LwM2mTestObject;
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;
Expand Down Expand Up @@ -139,44 +140,69 @@ public static LeshanClient createClient(LeshanClientDemoCLI cli, LwM2mModelRepos

// Initialize object list
final ObjectsInitializer initializer = new ObjectsInitializer(repository.getLwM2mModel());
// handle OSCORE
Integer oscoreObjectInstanceId;
if (cli.oscore != null) {
oscoreObjectInstanceId = 12345;
Oscore oscoreObject = new Oscore(oscoreObjectInstanceId, cli.oscore.getOscoreSetting());
initializer.setInstancesForObject(OSCORE, oscoreObject);
} else {
oscoreObjectInstanceId = null;
initializer.setClassForObject(OSCORE, Oscore.class);
}
if (cli.main.bootstrap) {
if (cli.identity.isPSK()) {
// TODO OSCORE support OSCORE with DTLS/PSK
initializer.setInstancesForObject(SECURITY, pskBootstrap(cli.main.url,
cli.identity.getPsk().identity.getBytes(), cli.identity.getPsk().sharekey.getBytes()));
initializer.setClassForObject(SERVER, Server.class);
} else if (cli.identity.isRPK()) {
// TODO OSCORE support OSCORE with DTLS/RPK
initializer.setInstancesForObject(SECURITY,
rpkBootstrap(cli.main.url, cli.identity.getRPK().cpubk.getEncoded(),
cli.identity.getRPK().cprik.getEncoded(), cli.identity.getRPK().spubk.getEncoded()));
initializer.setClassForObject(SERVER, Server.class);
} else if (cli.identity.isx509()) {
// TODO OSCORE support OSCORE with DTLS/X509
initializer.setInstancesForObject(SECURITY,
x509Bootstrap(cli.main.url, cli.identity.getX509().ccert.getEncoded(),
cli.identity.getX509().cprik.getEncoded(), cli.identity.getX509().scert.getEncoded(),
cli.identity.getX509().certUsage.code));
initializer.setClassForObject(SERVER, Server.class);
} else {
initializer.setInstancesForObject(SECURITY, noSecBootstap(cli.main.url));
if (oscoreObjectInstanceId != null) {
initializer.setInstancesForObject(SECURITY,
oscoreOnlyBootstrap(cli.main.url, oscoreObjectInstanceId));
} else {
initializer.setInstancesForObject(SECURITY, noSecBootstap(cli.main.url));
}
initializer.setClassForObject(SERVER, Server.class);
}
} else {
if (cli.identity.isPSK()) {
// TODO OSCORE support OSCORE with DTLS/PSK
initializer.setInstancesForObject(SECURITY, psk(cli.main.url, 123,
cli.identity.getPsk().identity.getBytes(), cli.identity.getPsk().sharekey.getBytes()));
initializer.setInstancesForObject(SERVER, new Server(123, cli.main.lifetimeInSec));
} else if (cli.identity.isRPK()) {
// TODO OSCORE support OSCORE with DTLS/RPK
initializer.setInstancesForObject(SECURITY,
rpk(cli.main.url, 123, cli.identity.getRPK().cpubk.getEncoded(),
cli.identity.getRPK().cprik.getEncoded(), cli.identity.getRPK().spubk.getEncoded()));
initializer.setInstancesForObject(SERVER, new Server(123, cli.main.lifetimeInSec));
} else if (cli.identity.isx509()) {
// TODO OSCORE support OSCORE with DTLS/X509
initializer.setInstancesForObject(SECURITY,
x509(cli.main.url, 123, cli.identity.getX509().ccert.getEncoded(),
cli.identity.getX509().cprik.getEncoded(), cli.identity.getX509().scert.getEncoded(),
cli.identity.getX509().certUsage.code));
initializer.setInstancesForObject(SERVER, new Server(123, cli.main.lifetimeInSec));
} else {
initializer.setInstancesForObject(SECURITY, noSec(cli.main.url, 123));
if (oscoreObjectInstanceId != null) {
initializer.setInstancesForObject(SECURITY, oscoreOnly(cli.main.url, 123, oscoreObjectInstanceId));
} else {
initializer.setInstancesForObject(SECURITY, noSec(cli.main.url, 123));
}
initializer.setInstancesForObject(SERVER, new Server(123, cli.main.lifetimeInSec));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,16 @@ public ClientCIDConverter() {
@ArgGroup(exclusive = true)
public IdentitySection identity = new IdentitySection();

/* ********************************** OSCORE Section ******************************** */
@ArgGroup(exclusive = false,
heading = "%n@|bold,underline OSCORE Options|@ %n%n"//
+ "@|italic " //
+ "By default Leshan demo does not use OSCORE.%n"//
+ "|@" + "@|red, OSCORE implementation in Leshan is in an experimental state.|@%n" //
+ "%n")

public OscoreSection oscore;

@Spec
CommandSpec spec;

Expand All @@ -283,6 +293,11 @@ public void run() {
}
}

// check OSCORE
if (oscore != null) {
oscore.validateOscoreSetting(spec.commandLine());
}

normalizedServerUrl();

// validate url.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*******************************************************************************
* Copyright (c) 2022 Sierra Wireless and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v20.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.html.
*
* Contributors:
* Sierra Wireless - initial API and implementation
*******************************************************************************/
package org.eclipse.leshan.client.demo.cli;

import java.util.Arrays;

import org.eclipse.californium.elements.util.Bytes;
import org.eclipse.leshan.core.demo.cli.MultiParameterException;
import org.eclipse.leshan.core.demo.cli.converters.HexadecimalConverter;
import org.eclipse.leshan.core.oscore.AeadAlgorithm;
import org.eclipse.leshan.core.oscore.HkdfAlgorithm;
import org.eclipse.leshan.core.oscore.InvalidOscoreSettingException;
import org.eclipse.leshan.core.oscore.OscoreSetting;
import org.eclipse.leshan.core.oscore.OscoreValidator;
import org.eclipse.leshan.core.util.StringUtils;

import picocli.CommandLine;
import picocli.CommandLine.ITypeConverter;
import picocli.CommandLine.Option;
import picocli.CommandLine.TypeConversionException;

/**
* Command line Section about OSCORE credentials.
*/
public class OscoreSection {

// mandatory parameters
@Option(required = true,
names = { "-sid", "--sender-id" },
description = { "Hexadecimal byte string used to identify the Sender Context, to"
+ " derive AEAD keys and Common IV, and to contribute to the"
+ " uniqueness of AEAD nonces. Maximum length is determined by the AEAD Algorithm." },
converter = HexadecimalConverter.class)
public Bytes senderId;

@Option(required = true,
names = { "-msec", "--master-secret" },
description = { "Variable length, Hexadecimal byte string used to derive AEAD keys and Common IV." },
converter = HexadecimalConverter.class)
public Bytes masterSecret;

@Option(required = true,
names = { "-rid", "--recipient-id" },
description = { "Hexadecimal byte string used to identify the Recipient Context,"
+ " to derive AEAD keys and Common IV, and to contribute to the"
+ " uniqueness of AEAD nonces. Maximum length is determined by the AEAD Algorithm." },
converter = HexadecimalConverter.class)
public Bytes recipientId;

// optional parameters
@Option(required = true,
names = { "-aead", "--aead-algorithm" },
description = { "The COSE AEAD algorithm to use for encryption.", "Default is ${DEFAULT-VALUE}." },
defaultValue = "AES-CCM-16-64-128",
converter = AeadAlgorithmConverter.class)
public Integer aeadAlgorithm;

private static class AeadAlgorithmConverter implements ITypeConverter<Integer> {
@Override
public Integer convert(String s) {
AeadAlgorithm aeadAlgorithm;
if (StringUtils.isNumeric(s)) {
try {
// Indicated as integer
aeadAlgorithm = AeadAlgorithm.fromValue(Integer.parseInt(s));
} catch (NumberFormatException e) {
aeadAlgorithm = null;
}
} else {
// Indicated as string
aeadAlgorithm = AeadAlgorithm.fromName(s);
}
if (aeadAlgorithm == null || !aeadAlgorithm.isKnown()) {
throw new TypeConversionException(
String.format("Unkown AEAD Algorithm for [%s] \nSupported AEAD algorithm are %s.", s,
Arrays.toString(AeadAlgorithm.knownAeadAlgorithms)));
}
return aeadAlgorithm.getValue();
}
};

@Option(required = true,
names = { "-msalt", "--master-salt" },
description = {
"Optional variable-length hexadecimal byte string containing the salt used to derive AEAD keys and Common IV.",
"Default is an empty string." },
defaultValue = "",
converter = HexadecimalConverter.class)
public Bytes masterSalt;

@Option(required = true,
names = { "-hkdf", "--hkdf-algorithm" },
description = {
"An HMAC-based key derivation function used to derive the Sender Key, Recipient Key, and Common IV.",
"Default is ${DEFAULT-VALUE}." },
defaultValue = "HKDF-SHA-256",
converter = hkdfAlgorithmConverter.class)
public Integer hkdfAlgorithm;

private static class hkdfAlgorithmConverter implements ITypeConverter<Integer> {
@Override
public Integer convert(String s) {
HkdfAlgorithm hkdfAlgorithm;
if (s.matches("-?\\d+")) {
try {
// Indicated as integer
hkdfAlgorithm = HkdfAlgorithm.fromValue(Integer.parseInt(s));
} catch (NumberFormatException e) {
hkdfAlgorithm = null;
}
} else {
// Indicated as string
hkdfAlgorithm = HkdfAlgorithm.fromName(s);
}
if (hkdfAlgorithm == null || !hkdfAlgorithm.isKnown()) {
throw new TypeConversionException(
String.format("Unkown HKDF Algorithm for [%s] \nSupported HKDF algorithm are %s.", s,
Arrays.toString(HkdfAlgorithm.knownHkdfAlgorithms)));
}
return hkdfAlgorithm.getValue();
}
};

// ---------------------------------------------------------------------------------------//
// TODO OSCORE I don't know if we need to add an option for anti-replay,
// maybe this will be more an californium config, let's skip it for now.
//
// @Option(required = true, names = { "-rw", "--replay-windows" }, description = { "TBD." })
// public String replayWindows;
// ---------------------------------------------------------------------------------------//

public OscoreSetting getOscoreSetting() {
return new OscoreSetting(senderId.getBytes(), recipientId.getBytes(), masterSecret.getBytes(), aeadAlgorithm,
hkdfAlgorithm, masterSalt.getBytes());
}

public void validateOscoreSetting(CommandLine commanLine) throws MultiParameterException {
try {
OscoreValidator validator = new OscoreValidator();
validator.validateOscoreSetting(getOscoreSetting());
} catch (IllegalArgumentException | InvalidOscoreSettingException e) {
throw new MultiParameterException(commanLine, String.format("Invalid OSCORE setting : %s", e.getMessage()),
"-sid", "-msec", "-rid", "-aead", "-msalt", "-hkdf");
}
}
}

0 comments on commit 9c7abd8

Please sign in to comment.