Skip to content

Commit

Permalink
Add support of RPK to Leshan Client Demo
Browse files Browse the repository at this point in the history
  • Loading branch information
sbernard31 committed Aug 16, 2018
1 parent a75f820 commit 208d8f5
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 21 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,6 @@ bin/
# log Files #
**/src/main/**/logback*.xml
**/src/test/**/logback*.xml

# Credentials files #
**/*.der
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@ public static Security pskBootstrap(String serverUri, byte[] pskIdentity, byte[]
privateKey.clone(), 0);
}

/**
* Returns a new security instance (RPK) for a bootstrap server.
*/
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);
}

/**
* Returns a new security instance (NoSec) for a device management server.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
import java.io.File;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.ECPublicKey;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;

Expand All @@ -44,6 +48,7 @@
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.request.BindingMode;
import org.eclipse.leshan.util.Hex;
import org.eclipse.leshan.util.SecurityUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -55,7 +60,7 @@ public class LeshanClientDemo {

private static final int OBJECT_ID_TEMPERATURE_SENSOR = 3303;
private final static String DEFAULT_ENDPOINT = "LeshanClientDemo";
private final static String USAGE = "java -jar leshan-client-demo.jar [OPTION]";
private final static String USAGE = "java -jar leshan-client-demo.jar [OPTION]\n\n";

private static MyLocation locationInstance;

Expand All @@ -64,6 +69,24 @@ public static void main(final String[] args) {
// Define options for command line tools
Options options = new Options();

final StringBuilder PSKChapter = new StringBuilder();
PSKChapter.append("\n .");
PSKChapter.append("\n .");
PSKChapter.append("\n ================================[ PSK ]=================================");
PSKChapter.append("\n | By default Leshan demo use non secure connection. |");
PSKChapter.append("\n | To use PSK, -i and -p options should be used together. |");
PSKChapter.append("\n ------------------------------------------------------------------------");

final StringBuilder RPKChapter = new StringBuilder();
RPKChapter.append("\n .");
RPKChapter.append("\n .");
RPKChapter.append("\n ================================[ RPK ]=================================");
RPKChapter.append("\n | By default Leshan demo use non secure connection. |");
RPKChapter.append("\n | To use RPK, -cpubk -cpribk -spubk options should be used together. |");
RPKChapter.append("\n | To get helps about files format and how to generate it, see : |");
RPKChapter.append("\n | See https://github.com/eclipse/leshan/wiki/Credential-files-format |");
RPKChapter.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));
Expand All @@ -73,14 +96,20 @@ public static void main(final String[] args) {
"Set the local CoAP port of the Client.\n Default: A valid port value is between 0 and 65535.");
options.addOption("u", true, String.format("Set the LWM2M or Bootstrap server URL.\nDefault: localhost:%d.",
LwM2m.DEFAULT_COAP_PORT));
options.addOption("i", true,
"Set the LWM2M or Bootstrap server PSK identity in ascii.\nUse none secure mode if not set.");
options.addOption("p", true,
"Set the LWM2M or Bootstrap server Pre-Shared-Key in hexa.\nUse none secure mode if not set.");
options.addOption("pos", true,
"Set the initial location (latitude, longitude) of the device to be reported by the Location object. Format: lat_float:long_float");
options.addOption("sf", true, "Scale factor to apply when shifting position. Default is 1.0.");
"Set the initial location (latitude, longitude) of the device to be reported by the Location object.\n Format: lat_float:long_float");
options.addOption("sf", true, "Scale factor to apply when shifting position.\n Default is 1.0." + PSKChapter);
options.addOption("i", true, "Set the LWM2M or Bootstrap server PSK identity in ascii.");
options.addOption("p", true, "Set the LWM2M or Bootstrap server Pre-Shared-Key in hexa." + RPKChapter);
options.addOption("cpubk", true,
"The path to your client public key file.\n The public Key should be in SubjectPublicKeyInfo format (DER encoding).");
options.addOption("cprik", true,
"The path to your client private key file.\nThe private key should be in PKCS#8 format (DER encoding).");
options.addOption("spubk", true,
"The path to your server public key file.\n The public Key should be in SubjectPublicKeyInfo format (DER encoding).");

HelpFormatter formatter = new HelpFormatter();
formatter.setWidth(90);
formatter.setOptionComparator(null);

// Parse arguments
Expand All @@ -106,13 +135,23 @@ public static void main(final String[] args) {
return;
}

// Abort if we have not identity and key for psk.
// Abort if PSK config is not complete
if ((cl.hasOption("i") && !cl.hasOption("p")) || !cl.hasOption("i") && cl.hasOption("p")) {
System.err.println("You should precise identity and Pre-Shared-Key if you want to connect in PSK");
System.err
.println("You should precise identity (-i) and Pre-Shared-Key (-p) if you want to connect in PSK");
formatter.printHelp(USAGE, options);
return;
}

// Abort if all RPK config is not complete
if (cl.hasOption("cpubk") || cl.hasOption("cprik") || cl.hasOption("spubk")) {
if (!cl.hasOption("cpubk") || !cl.hasOption("cprik") || !cl.hasOption("spubk")) {
System.err.println("cpubk, cprik and spubk should be used together to connect using RPK");
formatter.printHelp(USAGE, options);
return;
}
}

// Get endpoint name
String endpoint;
if (cl.hasOption("n")) {
Expand All @@ -128,25 +167,42 @@ public static void main(final String[] args) {
// Get server URI
String serverURI;
if (cl.hasOption("u")) {
if (cl.hasOption("i"))
if (cl.hasOption("i") || cl.hasOption("cpubk"))
serverURI = "coaps://" + cl.getOptionValue("u");
else
serverURI = "coap://" + cl.getOptionValue("u");
} else {
if (cl.hasOption("i"))
if (cl.hasOption("i") || cl.hasOption("cpubk"))
serverURI = "coaps://localhost:" + LwM2m.DEFAULT_COAP_SECURE_PORT;
else
serverURI = "coap://localhost:" + LwM2m.DEFAULT_COAP_PORT;
}

// get security info
// get PSK info
byte[] pskIdentity = null;
byte[] pskKey = null;
if (cl.hasOption("i") && cl.hasOption("p")) {
if (cl.hasOption("i")) {
pskIdentity = cl.getOptionValue("i").getBytes();
pskKey = Hex.decodeHex(cl.getOptionValue("p").toCharArray());
}

// get RPK info
PublicKey clientPublicKey = null;
PrivateKey clientPrivateKey = null;
PublicKey serverPublicKey = null;
if (cl.hasOption("cpubk")) {
try {
clientPrivateKey = SecurityUtil.extractPrivateKey(cl.getOptionValue("cprik"));
clientPublicKey = SecurityUtil.extractPublicKey(cl.getOptionValue("cpubk"));
serverPublicKey = SecurityUtil.extractPublicKey(cl.getOptionValue("spubk"));
} catch (Exception e) {
System.err.println("Unable to load RPK files : " + e.getMessage());
e.printStackTrace();
formatter.printHelp(USAGE, options);
return;
}
}

// get local address
String localAddress = null;
int localPort = 0;
Expand Down Expand Up @@ -189,11 +245,12 @@ public static void main(final String[] args) {
}

createAndStartClient(endpoint, localAddress, localPort, cl.hasOption("b"), serverURI, pskIdentity, pskKey,
latitude, longitude, scaleFactor);
clientPublicKey, clientPrivateKey, serverPublicKey, latitude, longitude, scaleFactor);
}

public static void createAndStartClient(String endpoint, String localAddress, int localPort, boolean needBootstrap,
String serverURI, byte[] pskIdentity, byte[] pskKey, Float latitude, Float longitude, float scaleFactor) {
String serverURI, byte[] pskIdentity, byte[] pskKey, PublicKey clientPublicKey, PrivateKey clientPrivateKey,
PublicKey serverPublicKey, Float latitude, Float longitude, float scaleFactor) {

locationInstance = new MyLocation(latitude, longitude, scaleFactor);

Expand All @@ -204,19 +261,27 @@ public static void createAndStartClient(String endpoint, String localAddress, in
// Initialize object list
ObjectsInitializer initializer = new ObjectsInitializer(new LwM2mModel(models));
if (needBootstrap) {
if (pskIdentity == null) {
initializer.setInstancesForObject(SECURITY, noSecBootstap(serverURI));
if (pskIdentity != null) {
initializer.setInstancesForObject(SECURITY, pskBootstrap(serverURI, pskIdentity, pskKey));
initializer.setClassForObject(SERVER, Server.class);
} else if (clientPublicKey != null) {
initializer.setInstancesForObject(SECURITY, rpkBootstrap(serverURI, clientPublicKey.getEncoded(),
clientPrivateKey.getEncoded(), serverPublicKey.getEncoded()));
initializer.setClassForObject(SERVER, Server.class);
} else {
initializer.setInstancesForObject(SECURITY, pskBootstrap(serverURI, pskIdentity, pskKey));
initializer.setInstancesForObject(SECURITY, noSecBootstap(serverURI));
initializer.setClassForObject(SERVER, Server.class);
}
} else {
if (pskIdentity == null) {
initializer.setInstancesForObject(SECURITY, noSec(serverURI, 123));
if (pskIdentity != null) {
initializer.setInstancesForObject(SECURITY, psk(serverURI, 123, pskIdentity, pskKey));
initializer.setInstancesForObject(SERVER, new Server(123, 30, BindingMode.U, false));
} else if (clientPublicKey != null) {
initializer.setInstancesForObject(SECURITY, rpk(serverURI, 123, clientPublicKey.getEncoded(),
clientPrivateKey.getEncoded(), serverPublicKey.getEncoded()));
initializer.setInstancesForObject(SERVER, new Server(123, 30, BindingMode.U, false));
} else {
initializer.setInstancesForObject(SECURITY, psk(serverURI, 123, pskIdentity, pskKey));
initializer.setInstancesForObject(SECURITY, noSec(serverURI, 123));
initializer.setInstancesForObject(SERVER, new Server(123, 30, BindingMode.U, false));
}
}
Expand Down Expand Up @@ -244,6 +309,32 @@ public static void createAndStartClient(String endpoint, String localAddress, in
builder.setCoapConfig(coapConfig);
final LeshanClient client = builder.build();

// Display client public key to easily add it in Leshan Server Demo
if (clientPublicKey != null) {
PublicKey rawPublicKey = clientPublicKey;
if (rawPublicKey instanceof ECPublicKey) {
ECPublicKey ecPublicKey = (ECPublicKey) rawPublicKey;
// Get x coordinate
byte[] x = ecPublicKey.getW().getAffineX().toByteArray();
if (x[0] == 0)
x = Arrays.copyOfRange(x, 1, x.length);

// Get Y coordinate
byte[] y = ecPublicKey.getW().getAffineY().toByteArray();
if (y[0] == 0)
y = Arrays.copyOfRange(y, 1, y.length);

// Get Curves params
String params = ecPublicKey.getParams().toString();

LOG.info(
"Client public Key is : \n Elliptic Curve parameters : {} \n Public x coord : {} \n Public y coord : {}",
params, Hex.encodeHexString(x), Hex.encodeHexString(y));
} else {
throw new IllegalStateException("Unsupported Public Key Format (only ECPublicKey supported).");
}
}

LOG.info("Press 'w','a','s','d' to change reported Location ({},{}).", locationInstance.getLatitude(),
locationInstance.getLongitude());

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*******************************************************************************
* Copyright (c) 2018 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
*******************************************************************************/
package org.eclipse.leshan.util;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

public class SecurityUtil {

/**
* Extract Elliptic Curve private key in PKCS8 format from file (DER encoded).
*/
public static PrivateKey extractPrivateKey(String fileName) throws Exception {
byte[] keyBytes = Files.readAllBytes(Paths.get(fileName));

PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("EC");
return kf.generatePrivate(spec);
}

/**
* Extract Elliptic Curve public key in SubjectPublicKeyInfo format from file (DER encoded).
*/
public static PublicKey extractPublicKey(String fileName) throws Exception {
byte[] keyBytes = Files.readAllBytes(Paths.get(fileName));

X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("EC");
return kf.generatePublic(spec);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.security.AlgorithmParameters;
import java.security.Key;
import java.security.KeyFactory;
Expand Down Expand Up @@ -337,6 +340,7 @@ public static void createAndStartServer(String webAddress, int webPort, String l

// Get keys
publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
Files.write(Paths.get("server_pub.der"), publicKey.getEncoded(), StandardOpenOption.CREATE);
PrivateKey privateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec);
builder.setPublicKey(publicKey);
builder.setPrivateKey(privateKey);
Expand Down

0 comments on commit 208d8f5

Please sign in to comment.