Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RPK API for bootstrap server #553

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
package org.eclipse.leshan.server.bootstrap.demo;

import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
Expand All @@ -25,12 +27,17 @@
import org.eclipse.leshan.server.bootstrap.BootstrapConfig.ServerSecurity;
import org.eclipse.leshan.server.security.BootstrapSecurityStore;
import org.eclipse.leshan.server.security.SecurityInfo;
import org.eclipse.leshan.util.SecurityUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* A DTLS security store using the provisioned bootstrap information for finding the DTLS/PSK credentials.
*/
public class BootstrapSecurityStoreImpl implements BootstrapSecurityStore {

private static final Logger LOG = LoggerFactory.getLogger(BootstrapSecurityStoreImpl.class);

private final BootstrapStoreImpl bsStore;

public BootstrapSecurityStoreImpl(BootstrapStoreImpl bsStore) {
Expand Down Expand Up @@ -63,14 +70,26 @@ public List<SecurityInfo> getAllByEndpoint(String endpoint) {
if (bsConfig == null || bsConfig.security == null)
return null;

for (Map.Entry<Integer, BootstrapConfig.ServerSecurity> e : bsConfig.security.entrySet()) {
ServerSecurity value = e.getValue();
for (Map.Entry<Integer, BootstrapConfig.ServerSecurity> bsEntry : bsConfig.security.entrySet()) {
ServerSecurity value = bsEntry.getValue();

// Extract PSK identity
if (value.bootstrapServer && value.securityMode == SecurityMode.PSK) {
// got it!
SecurityInfo securityInfo = SecurityInfo.newPreSharedKeyInfo(endpoint,
new String(value.publicKeyOrId, StandardCharsets.UTF_8), value.secretKey);
return Arrays.asList(securityInfo);
}
// Extract RPK identity
else if (value.bootstrapServer && value.securityMode == SecurityMode.RPK) {
try {
SecurityInfo securityInfo = SecurityInfo.newRawPublicKeyInfo(endpoint,
SecurityUtil.extractPublicKey(value.publicKeyOrId));
return Arrays.asList(securityInfo);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
LOG.error("Unable to decode Client public key for {}", endpoint, e);
return null;
}
}
}
return null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,25 @@
package org.eclipse.leshan.server.bootstrap.demo;

import java.io.File;
import java.math.BigInteger;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.security.AlgorithmParameters;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.KeySpec;
import java.util.List;

import org.apache.commons.cli.CommandLine;
Expand All @@ -41,6 +58,7 @@
import org.eclipse.leshan.server.californium.LeshanBootstrapServerBuilder;
import org.eclipse.leshan.server.californium.LeshanServerBuilder;
import org.eclipse.leshan.server.californium.impl.LeshanBootstrapServer;
import org.eclipse.leshan.util.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -167,6 +185,38 @@ public static void createAndStartServer(String webAddress, int webPort, String l
builder.setLocalSecureAddress(secureLocalAddress, secureLocalPort);
builder.setModel(new LwM2mModel(models));

// Create RPK credentials;
PublicKey publicKey = null;
try {
// Get point values
byte[] publicX = Hex
.decodeHex("fb136894878a9696d45fdb04506b9eb49ddcfba71e4e1b4ce23d5c3ac382d6b4".toCharArray());
byte[] publicY = Hex
.decodeHex("3deed825e808f8ed6a9a74ff6bd24e3d34b1c0c5fc253422f7febadbdc9cb9e6".toCharArray());
byte[] privateS = Hex
.decodeHex("35a8303e67a7e99d06552a0f8f6c8f1bf91a174396f4fad6211ae227e890da11".toCharArray());

// Get Elliptic Curve Parameter spec for secp256r1
AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC");
algoParameters.init(new ECGenParameterSpec("secp256r1"));
ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class);

// Create key specs
KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)),
parameterSpec);
KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec);

// 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);
} catch (InvalidKeySpecException | NoSuchAlgorithmException | InvalidParameterSpecException e) {
LOG.error("Unable to initialize RPK.", e);
System.exit(-1);
}

// Create CoAP Config
NetworkConfig coapConfig;
File configFile = new File(NetworkConfig.DEFAULT_FILE_NAME);
Expand Down Expand Up @@ -199,7 +249,7 @@ public static void createAndStartServer(String webAddress, int webPort, String l
ServletHolder bsServletHolder = new ServletHolder(new BootstrapServlet(bsStore));
root.addServlet(bsServletHolder, "/api/bootstrap/*");

ServletHolder serverServletHolder = new ServletHolder(new ServerServlet(bsServer));
ServletHolder serverServletHolder = new ServletHolder(new ServerServlet(bsServer, publicKey));
root.addServlet(serverServletHolder, "/api/server/*");

server.setHandler(root);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*******************************************************************************
* Copyright (c) 2013-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
*******************************************************************************/
package org.eclipse.leshan.server.bootstrap.demo.json;

import java.lang.reflect.Type;
import java.security.PublicKey;
import java.security.interfaces.ECPublicKey;
import java.util.Arrays;

import org.eclipse.leshan.server.security.SecurityInfo;
import org.eclipse.leshan.util.Base64;
import org.eclipse.leshan.util.Hex;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;

///!\ This class is a COPY of org.eclipse.leshan.server.demo.servlet.json.SecuritySerializer /!\
public class SecuritySerializer implements JsonSerializer<SecurityInfo> {

@Override
public JsonElement serialize(SecurityInfo src, Type typeOfSrc, JsonSerializationContext context) {
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()));
element.add("psk", psk);
}

if (src.getRawPublicKey() != null) {
JsonObject rpk = new JsonObject();
PublicKey rawPublicKey = src.getRawPublicKey();
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);
rpk.addProperty("x", Hex.encodeHexString(x));

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

// Get Curves params
rpk.addProperty("params", ecPublicKey.getParams().toString());

// Get raw public key in format PKCS8 (DER encoding)
rpk.addProperty("pkcs8", Base64.encodeBase64String(ecPublicKey.getEncoded()));
} else {
throw new JsonParseException("Unsupported Public Key Format (only ECPublicKey supported).");
}
element.add("rpk", rpk);
}

if (src.useX509Cert()) {
element.addProperty("x509", true);
}

return element;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,65 @@

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.PublicKey;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.eclipse.leshan.server.bootstrap.demo.json.SecuritySerializer;
import org.eclipse.leshan.server.californium.impl.LeshanBootstrapServer;
import org.eclipse.leshan.server.security.SecurityInfo;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class ServerServlet extends HttpServlet {

private static final long serialVersionUID = 1L;

private LeshanBootstrapServer server;
private Gson gsonSer;
private PublicKey publicKey;

public ServerServlet(LeshanBootstrapServer server) {
public ServerServlet(LeshanBootstrapServer server, PublicKey serverPublicKey) {
this.server = server;
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(SecurityInfo.class, new SecuritySerializer());
this.gsonSer = builder.create();
this.publicKey = serverPublicKey;
}

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setStatus(HttpServletResponse.SC_OK);
resp.setContentType("application/json");
resp.getOutputStream()
.write(String
.format("{ \"securedEndpointPort\":\"%s\", \"unsecuredEndpointPort\":\"%s\"}",
server.getSecuredAddress().getPort(), server.getUnsecuredAddress().getPort())
.getBytes(StandardCharsets.UTF_8));
String[] path = StringUtils.split(req.getPathInfo(), '/');

if (path.length != 1) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}

if ("security".equals(path[0])) {
String json = this.gsonSer.toJson(SecurityInfo.newRawPublicKeyInfo("leshan", publicKey));
resp.setContentType("application/json");
resp.getOutputStream().write(json.getBytes(StandardCharsets.UTF_8));
resp.setStatus(HttpServletResponse.SC_OK);
return;
}

if ("endpoint".equals(path[0])) {
resp.setStatus(HttpServletResponse.SC_OK);
resp.setContentType("application/json");
resp.getOutputStream()
.write(String
.format("{ \"securedEndpointPort\":\"%s\", \"unsecuredEndpointPort\":\"%s\"}",
server.getSecuredAddress().getPort(), server.getUnsecuredAddress().getPort())
.getBytes(StandardCharsets.UTF_8));
return;
}

resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
}
}
4 changes: 4 additions & 0 deletions leshan-bsserver-demo/src/main/resources/webapp/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@
<script src="tag/bootstrap.tag" type="riot/tag"></script>
<script src="tag/securityconfig-input.tag" type="riot/tag"></script>
<script src="tag/psk-input.tag" type="riot/tag"></script>
<script src="tag/rpk-input.tag" type="riot/tag"></script>
<script src="tag/x509-input.tag" type="riot/tag"></script>
<script src="tag/bootstrap-modal.tag" type="riot/tag"></script>

<script src="js/bsconfigstore.js"></script>
<script src="js/server.js"></script>

<script src="vendor/js/jquery-2.1.1.min.js"></script>
<script src="vendor/js/FileSaver.min.js"></script>
<script src="vendor/js/bootstrap.min.js"></script>
<script src="vendor/js/riot+compiler.min.js"></script>

Expand Down Expand Up @@ -53,6 +56,7 @@

<script>
var bsConfigStore = new BsConfigStore();
var server = new server();
riot.mount('bootstrap');
</script>

Expand Down
18 changes: 18 additions & 0 deletions leshan-bsserver-demo/src/main/resources/webapp/js/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
function server() {
riot.observable(this); // Riot provides our event emitter.

var self = this;

self.security = {};

self.init = function() {
$.get('api/server/security', function(data) {
self.security = data;
self.trigger('initialized', self.security);
}).fail(function(xhr, status, error) {
var err = "Unable to get the server info";
console.error(err, status, error, xhr.responseText);
alert(err + ": " + xhr.responseText);
});
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,16 @@
<securityconfig-input ref="lwserver" onchange={update} show={activetab.lwserver}
securi={ "coaps://" + location.hostname + ":5684" }
unsecuri= { "coap://" + location.hostname + ":5683" }
secmode = { {no_sec:true, psk:true, x509:true} }
secmode = { {no_sec:true, psk:true, rpk:true, x509:true} }
></securityconfig-input>
</div>
<div>
<securityconfig-input ref="bsserver" onchange={update} show={activetab.bsserver}
securi={ "coaps://" + location.hostname + ":" + serverdata.securedEndpointPort }
unsecuri= { "coap://" + location.hostname + ":" + serverdata.unsecuredEndpointPort }
disable = { {uri:true} }
secmode = { {no_sec:true, psk:true} }
serverpubkey= {serversecurity.rpk.hex }
disable = { {uri:true, serverpubkey:true} }
secmode = { {no_sec:true, psk:true,rpk:true} }
></securityconfig-input>
</div>

Expand All @@ -53,8 +54,9 @@
var tag = this;
// Tag Params
tag.serverdata = opts.server || {unsecuredEndpointPort:5683, securedEndpointPort:5684};
tag.serversecurity = opts.security || {rpk:{}};
// Tag Internal state
tag.endpoint = {}
tag.endpoint = {};
tag.has_error = has_error;
tag.validate_endpoint = validate_endpoint;
tag.submit = submit;
Expand All @@ -68,8 +70,8 @@
};

// Initialization
this.on('mount', function() {
$('#bootstrap-modal').modal('show');
tag.on('mount', function() {
$('#bootstrap-modal').modal('show');
});

// Tag functions
Expand Down Expand Up @@ -122,7 +124,7 @@
secretKey : bsserver.key,
securityMode : bsserver.secmode,
serverId : 111,
serverPublicKey : lwserver.serverKey,
serverPublicKey : bsserver.serverKey,
serverSmsNumber : "",
smsBindingKeyParam : [ ],
smsBindingKeySecret : [ ],
Expand Down
Loading