From 2cf53ccd6e4748a1c3545de93440faa233385652 Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Wed, 8 Aug 2018 17:59:39 +0200 Subject: [PATCH 1/5] #537: Add RPK API for bootstrap server --- .../tests/BootstrapIntegrationTestHelper.java | 89 ++++++++++++++++--- .../integration/tests/BootstrapTest.java | 33 +++++-- .../LeshanBootstrapServerBuilder.java | 43 +++++++++ 3 files changed, 150 insertions(+), 15 deletions(-) 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 5c65a8c909..c2d2eab504 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 @@ -16,9 +16,21 @@ package org.eclipse.leshan.integration.tests; +import java.math.BigInteger; import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +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.KeySpec; import java.util.Arrays; import java.util.List; @@ -39,6 +51,7 @@ import org.eclipse.leshan.server.security.BootstrapSecurityStore; import org.eclipse.leshan.server.security.EditableSecurityStore; import org.eclipse.leshan.server.security.SecurityInfo; +import org.eclipse.leshan.util.Hex; /** * Helper for running a server and executing a client against it. @@ -46,7 +59,40 @@ */ public class BootstrapIntegrationTestHelper extends SecureIntegrationTestHelper { - LeshanBootstrapServer bootstrapServer; + public LeshanBootstrapServer bootstrapServer; + public final PublicKey bootstrapServerPublicKey; + public final PrivateKey bootstrapServerPrivateKey; + + public BootstrapIntegrationTestHelper() { + super(); + + // create bootstrap server credentials + 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 + bootstrapServerPublicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); + bootstrapServerPrivateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } + } public void createBootstrapServer(BootstrapSecurityStore securityStore, BootstrapStore bootstrapStore) { if (bootstrapStore == null) { @@ -91,6 +137,8 @@ public BootstrapConfig getBootstrap(String endpoint, Identity deviceIdentity) { builder.setSecurityStore(securityStore); builder.setLocalAddress(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); builder.setLocalSecureAddress(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); + builder.setPrivateKey(bootstrapServerPrivateKey); + builder.setPublicKey(bootstrapServerPublicKey); bootstrapServer = builder.build(); } @@ -120,6 +168,16 @@ public void createPSKClient(String pskIdentity, byte[] pskKey) { createClient(security); } + @Override + public void createRPKClient() { + String bsUrl = "coaps://" + bootstrapServer.getSecuredAddress().getHostString() + ":" + + bootstrapServer.getSecuredAddress().getPort(); + Security security = Security.rpkBootstrap(bsUrl, clientPublicKey.getEncoded(), clientPrivateKey.getEncoded(), + bootstrapServerPublicKey.getEncoded()); + + createClient(security); + } + private void createClient(Security security) { ObjectsInitializer initializer = new ObjectsInitializer(); @@ -137,26 +195,32 @@ private void createClient(Security security) { setupClientMonitoring(); } - public BootstrapSecurityStore bsSecurityStore() { + public BootstrapSecurityStore bsSecurityStore(final SecurityMode mode) { return new BootstrapSecurityStore() { @Override public SecurityInfo getByIdentity(String identity) { - if (BootstrapIntegrationTestHelper.GOOD_PSK_ID.equals(identity)) { - return pskSecurityInfo(); - } else { - return null; + if (mode == SecurityMode.PSK) { + if (BootstrapIntegrationTestHelper.GOOD_PSK_ID.equals(identity)) { + return pskSecurityInfo(); + } } + return null; } @Override public List getAllByEndpoint(String endpoint) { if (getCurrentEndpoint().equals(endpoint)) { - SecurityInfo info = pskSecurityInfo(); - return Arrays.asList(info); - } else { - return Arrays.asList(); + SecurityInfo info; + if (mode == SecurityMode.PSK) { + info = pskSecurityInfo(); + return Arrays.asList(info); + } else if (mode == SecurityMode.RPK) { + info = rpkSecurityInfo(); + return Arrays.asList(info); + } } + return Arrays.asList(); } }; } @@ -167,6 +231,11 @@ public SecurityInfo pskSecurityInfo() { return info; } + public SecurityInfo rpkSecurityInfo() { + SecurityInfo info = SecurityInfo.newRawPublicKeyInfo(getCurrentEndpoint(), clientPublicKey); + return info; + } + private BootstrapSecurityStore dummyBsSecurityStore() { return new BootstrapSecurityStore() { diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapTest.java index aebff50e14..a1d1e33ef7 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapTest.java @@ -17,6 +17,7 @@ import static org.eclipse.leshan.integration.tests.SecureIntegrationTestHelper.*; +import org.eclipse.leshan.SecurityMode; import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; import org.eclipse.leshan.server.security.SecurityInfo; import org.junit.After; @@ -63,13 +64,13 @@ public void bootstrap() { } @Test - public void bootstrapSecure() { + public void bootstrapSecureWithPSK() { // Create DM Server without security & start it helper.createServer(); helper.server.start(); // Create and start bootstrap server - helper.createBootstrapServer(helper.bsSecurityStore()); + helper.createBootstrapServer(helper.bsSecurityStore(SecurityMode.PSK)); helper.bootstrapServer.start(); // Create PSK Client and check it is not already registered @@ -85,17 +86,17 @@ public void bootstrapSecure() { } @Test - public void bootstrapSecureWithBadCredentials() { + public void bootstrapSecureWithBadPSKKey() { // Create DM Server without security & start it helper.createServer(); helper.server.start(); // Create and start bootstrap server - helper.createBootstrapServer(helper.bsSecurityStore()); + helper.createBootstrapServer(helper.bsSecurityStore(SecurityMode.PSK)); helper.bootstrapServer.start(); // Create PSK Client with bad credentials and check it is not already registered - helper.createPSKClient(GOOD_PSK_ID, BAD_PSK_KEY); + helper.createRPKClient(); helper.assertClientNotRegisterered(); // Start it and wait for registration @@ -106,6 +107,28 @@ public void bootstrapSecureWithBadCredentials() { helper.assertClientNotRegisterered(); } + @Test + public void bootstrapSecureWithRPK() { + // Create DM Server without security & start it + helper.createServer(); + helper.server.start(); + + // Create and start bootstrap server + helper.createBootstrapServer(helper.bsSecurityStore(SecurityMode.RPK)); + helper.bootstrapServer.start(); + + // Create RPK Client and check it is not already registered + helper.createRPKClient(); + helper.assertClientNotRegisterered(); + + // Start it and wait for registration + helper.client.start(); + helper.waitForRegistrationAtServerSide(5000); + + // check the client is registered + helper.assertClientRegisterered(); + } + @Test public void bootstrapToPSKServer() throws NonUniqueSecurityInfoException { // Create DM Server & start it diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanBootstrapServerBuilder.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanBootstrapServerBuilder.java index 192616e716..e2b908a19c 100644 --- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanBootstrapServerBuilder.java +++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/LeshanBootstrapServerBuilder.java @@ -17,6 +17,8 @@ package org.eclipse.leshan.server.californium; import java.net.InetSocketAddress; +import java.security.PrivateKey; +import java.security.PublicKey; import org.eclipse.californium.core.network.CoapEndpoint; import org.eclipse.californium.core.network.config.NetworkConfig; @@ -57,6 +59,9 @@ public class LeshanBootstrapServerBuilder { private NetworkConfig coapConfig; private Builder dtlsConfigBuilder; + private PublicKey publicKey; + private PrivateKey privateKey; + private EndpointFactory endpointFactory; private boolean noSecuredEndpoint; @@ -115,6 +120,25 @@ public LeshanBootstrapServerBuilder setLocalSecureAddress(String hostname, int p return this; } + /** + *

+ * Set the {@link PublicKey} of the server which will be used for RawPublicKey DTLS authentication. + *

+ * This should be used for RPK support only. + */ + public LeshanBootstrapServerBuilder setPublicKey(PublicKey publicKey) { + this.publicKey = publicKey; + return this; + } + + /** + * Set the {@link PrivateKey} of the server which will be used for RawPublicKey(RPK). + */ + public LeshanBootstrapServerBuilder setPrivateKey(PrivateKey privateKey) { + this.privateKey = privateKey; + return this; + } + /** *

* Set the address for secured CoAP Server (Using DTLS). @@ -244,6 +268,25 @@ public LeshanBootstrapServer build() { if (incompleteConfig.getStaleConnectionThreshold() == null) dtlsConfigBuilder.setStaleConnectionThreshold(coapConfig.getLong(Keys.MAX_PEER_INACTIVITY_PERIOD)); + // check conflict for private key + if (privateKey != null) { + if (incompleteConfig.getPrivateKey() != null && !incompleteConfig.getPrivateKey().equals(privateKey)) { + throw new IllegalStateException(String.format( + "Configuration conflict between LeshanBuilder and DtlsConnectorConfig.Builder for private key: %s != %s", + privateKey, incompleteConfig.getPrivateKey())); + } + + if (publicKey != null) { + if (incompleteConfig.getPublicKey() != null && !incompleteConfig.getPublicKey().equals(publicKey)) { + throw new IllegalStateException(String.format( + "Configuration conflict between LeshanBuilder and DtlsConnectorConfig.Builder for public key: %s != %s", + publicKey, incompleteConfig.getPublicKey())); + } + + dtlsConfigBuilder.setIdentity(privateKey, publicKey); + } + } + // Deactivate SNI by default // TODO should we support SNI ? if (incompleteConfig.isSniEnabled() == null) { From dd8fd4bd5ab8a679fe8693fda72e53e05ddb1c75 Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Mon, 13 Aug 2018 17:16:03 +0200 Subject: [PATCH 2/5] Add RPK support to bsserver demo --- .../demo/BootstrapSecurityStoreImpl.java | 25 ++- .../demo/LeshanBootstrapServerDemo.java | 52 ++++- .../demo/json/SecuritySerializer.java | 83 ++++++++ .../bootstrap/demo/servlet/ServerServlet.java | 49 ++++- .../src/main/resources/webapp/index.html | 4 + .../src/main/resources/webapp/js/server.js | 18 ++ .../resources/webapp/tag/bootstrap-modal.tag | 16 +- .../main/resources/webapp/tag/bootstrap.tag | 60 +++++- .../main/resources/webapp/tag/rpk-input.tag | 107 ++++++++++ .../webapp/tag/securityconfig-input.tag | 24 ++- .../resources/webapp/vendor/js/FileSaver.js | 185 ++++++++++++++++++ .../webapp/vendor/js/FileSaver.min.js | 2 + .../org/eclipse/leshan/util/SecurityUtil.java | 18 +- .../demo/servlet/json/SecuritySerializer.java | 1 + 14 files changed, 613 insertions(+), 31 deletions(-) create mode 100644 leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/json/SecuritySerializer.java create mode 100644 leshan-bsserver-demo/src/main/resources/webapp/js/server.js create mode 100644 leshan-bsserver-demo/src/main/resources/webapp/tag/rpk-input.tag create mode 100644 leshan-bsserver-demo/src/main/resources/webapp/vendor/js/FileSaver.js create mode 100644 leshan-bsserver-demo/src/main/resources/webapp/vendor/js/FileSaver.min.js diff --git a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/BootstrapSecurityStoreImpl.java b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/BootstrapSecurityStoreImpl.java index 495ac9b331..8dcbb87175 100644 --- a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/BootstrapSecurityStoreImpl.java +++ b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/BootstrapSecurityStoreImpl.java @@ -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; @@ -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) { @@ -63,14 +70,26 @@ public List getAllByEndpoint(String endpoint) { if (bsConfig == null || bsConfig.security == null) return null; - for (Map.Entry e : bsConfig.security.entrySet()) { - ServerSecurity value = e.getValue(); + for (Map.Entry 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; 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 e1edeea577..818c3a5e0d 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 @@ -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; @@ -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; @@ -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); @@ -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); diff --git a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/json/SecuritySerializer.java b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/json/SecuritySerializer.java new file mode 100644 index 0000000000..44f04cbd0c --- /dev/null +++ b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/json/SecuritySerializer.java @@ -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 { + + @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; + } +} diff --git a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/servlet/ServerServlet.java b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/servlet/ServerServlet.java index faf663312a..2bef64086d 100644 --- a/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/servlet/ServerServlet.java +++ b/leshan-bsserver-demo/src/main/java/org/eclipse/leshan/server/bootstrap/demo/servlet/ServerServlet.java @@ -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); } } diff --git a/leshan-bsserver-demo/src/main/resources/webapp/index.html b/leshan-bsserver-demo/src/main/resources/webapp/index.html index 0edafa4ebd..65ac7ec26d 100644 --- a/leshan-bsserver-demo/src/main/resources/webapp/index.html +++ b/leshan-bsserver-demo/src/main/resources/webapp/index.html @@ -11,12 +11,15 @@ + + + @@ -53,6 +56,7 @@ diff --git a/leshan-bsserver-demo/src/main/resources/webapp/js/server.js b/leshan-bsserver-demo/src/main/resources/webapp/js/server.js new file mode 100644 index 0000000000..e1339b3d26 --- /dev/null +++ b/leshan-bsserver-demo/src/main/resources/webapp/js/server.js @@ -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); + }); + }; +} \ No newline at end of file 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 0b62232e66..50e66aa201 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 @@ -26,15 +26,16 @@

@@ -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; @@ -68,8 +70,8 @@ }; // Initialization - this.on('mount', function() { - $('#bootstrap-modal').modal('show'); + tag.on('mount', function() { + $('#bootstrap-modal').modal('show'); }); // Tag functions @@ -122,7 +124,7 @@ secretKey : bsserver.key, securityMode : bsserver.secmode, serverId : 111, - serverPublicKey : lwserver.serverKey, + serverPublicKey : bsserver.serverKey, serverSmsNumber : "", smsBindingKeyParam : [ ], smsBindingKeySecret : [ ], 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 811d338905..53e2e46484 100644 --- a/leshan-bsserver-demo/src/main/resources/webapp/tag/bootstrap.tag +++ b/leshan-bsserver-demo/src/main/resources/webapp/tag/bootstrap.tag @@ -1,4 +1,22 @@ +
+

The Leshan bootstrap public key (SubjectPublicKeyInfo der encoded) + +

+

+ Elliptic Curve parameters : {serverSecurityInfo.rpk.params}
+ Public x coord : {serverSecurityInfo.rpk.x}
+ Public y coord : {serverSecurityInfo.rpk.y}
+

+ Hex :
{pkcs8pubkey.hex}
+
+
+ Base64 :
{pkcs8pubkey.base64}
+
+

+
- +
- - + + +
+ +
+ +
@@ -36,6 +41,7 @@ // Tag Params tag.secmode = opts.secmode || {no_sec:true}; tag.disable = opts.disable || {}; + tag.serverpubkey = opts.serverpubkey || ""; tag.onchange = opts.onchange; tag.securi = opts.securi || ""; tag.unsecuri = opts.unsecuri || ""; @@ -54,7 +60,8 @@ } function has_error() { - return tag.refs.secMode.value === "psk" && tag.refs.psk.has_error() + return tag.refs.secMode.value === "psk" && tag.refs.psk.has_error() + || tag.refs.secMode.value === "rpk" && tag.refs.rpk.has_error() || tag.refs.secMode.value === "x509" && tag.refs.x509.has_error(); } @@ -79,7 +86,12 @@ var psk = tag.refs.psk.get_value() config.id = fromAscii(psk.id); config.key = fromHex(psk.key); - }else if(config.secmode === "X509"){ + } else if(config.secmode === "RPK"){ + var rpk = tag.refs.rpk.get_value(); + config.id = fromHex(rpk.pubkey); + config.key = fromHex(rpk.privkey); + config.serverKey = fromHex(rpk.servpubkey); + } else if(config.secmode === "X509"){ var x509 = tag.refs.x509.get_value(); config.id = fromHex(x509.cert); config.key = fromHex(x509.key); diff --git a/leshan-bsserver-demo/src/main/resources/webapp/vendor/js/FileSaver.js b/leshan-bsserver-demo/src/main/resources/webapp/vendor/js/FileSaver.js new file mode 100644 index 0000000000..d4ed5395ab --- /dev/null +++ b/leshan-bsserver-demo/src/main/resources/webapp/vendor/js/FileSaver.js @@ -0,0 +1,185 @@ +/* FileSaver.js + * A saveAs() FileSaver implementation. + * 1.3.4 + * 2018-01-12 13:14:0 + * + * By Eli Grey, http://eligrey.com + * License: MIT + * See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md + */ + +/*global self */ +/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */ + +/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ + +var saveAs = saveAs || (function(view) { + "use strict"; + // IE <10 is explicitly unsupported + if (typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) { + return; + } + var + doc = view.document + // only get URL when necessary in case Blob.js hasn't overridden it yet + , get_URL = function() { + return view.URL || view.webkitURL || view; + } + , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a") + , can_use_save_link = "download" in save_link + , click = function(node) { + var event = new MouseEvent("click"); + node.dispatchEvent(event); + } + , is_safari = /constructor/i.test(view.HTMLElement) || view.safari + , is_chrome_ios =/CriOS\/[\d]+/.test(navigator.userAgent) + , throw_outside = function(ex) { + (view.setImmediate || view.setTimeout)(function() { + throw ex; + }, 0); + } + , force_saveable_type = "application/octet-stream" + // the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to + , arbitrary_revoke_timeout = 1000 * 40 // in ms + , revoke = function(file) { + var revoker = function() { + if (typeof file === "string") { // file is an object URL + get_URL().revokeObjectURL(file); + } else { // file is a File + file.remove(); + } + }; + setTimeout(revoker, arbitrary_revoke_timeout); + } + , dispatch = function(filesaver, event_types, event) { + event_types = [].concat(event_types); + var i = event_types.length; + while (i--) { + var listener = filesaver["on" + event_types[i]]; + if (typeof listener === "function") { + try { + listener.call(filesaver, event || filesaver); + } catch (ex) { + throw_outside(ex); + } + } + } + } + , auto_bom = function(blob) { + // prepend BOM for UTF-8 XML and text/* types (including HTML) + // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF + if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { + return new Blob([String.fromCharCode(0xFEFF), blob], {type: blob.type}); + } + return blob; + } + , FileSaver = function(blob, name, no_auto_bom) { + if (!no_auto_bom) { + blob = auto_bom(blob); + } + // First try a.download, then web filesystem, then object URLs + var + filesaver = this + , type = blob.type + , force = type === force_saveable_type + , object_url + , dispatch_all = function() { + dispatch(filesaver, "writestart progress write writeend".split(" ")); + } + // on any filesys errors revert to saving with object URLs + , fs_error = function() { + if ((is_chrome_ios || (force && is_safari)) && view.FileReader) { + // Safari doesn't allow downloading of blob urls + var reader = new FileReader(); + reader.onloadend = function() { + var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;'); + var popup = view.open(url, '_blank'); + if(!popup) view.location.href = url; + url=undefined; // release reference before dispatching + filesaver.readyState = filesaver.DONE; + dispatch_all(); + }; + reader.readAsDataURL(blob); + filesaver.readyState = filesaver.INIT; + return; + } + // don't create more object URLs than needed + if (!object_url) { + object_url = get_URL().createObjectURL(blob); + } + if (force) { + view.location.href = object_url; + } else { + var opened = view.open(object_url, "_blank"); + if (!opened) { + // Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html + view.location.href = object_url; + } + } + filesaver.readyState = filesaver.DONE; + dispatch_all(); + revoke(object_url); + } + ; + filesaver.readyState = filesaver.INIT; + + if (can_use_save_link) { + object_url = get_URL().createObjectURL(blob); + setTimeout(function() { + save_link.href = object_url; + save_link.download = name; + click(save_link); + dispatch_all(); + revoke(object_url); + filesaver.readyState = filesaver.DONE; + }); + return; + } + + fs_error(); + } + , FS_proto = FileSaver.prototype + , saveAs = function(blob, name, no_auto_bom) { + return new FileSaver(blob, name || blob.name || "download", no_auto_bom); + } + ; + // IE 10+ (native saveAs) + if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) { + return function(blob, name, no_auto_bom) { + name = name || blob.name || "download"; + + if (!no_auto_bom) { + blob = auto_bom(blob); + } + return navigator.msSaveOrOpenBlob(blob, name); + }; + } + + FS_proto.abort = function(){}; + FS_proto.readyState = FS_proto.INIT = 0; + FS_proto.WRITING = 1; + FS_proto.DONE = 2; + + FS_proto.error = + FS_proto.onwritestart = + FS_proto.onprogress = + FS_proto.onwrite = + FS_proto.onabort = + FS_proto.onerror = + FS_proto.onwriteend = + null; + + return saveAs; +}( + typeof self !== "undefined" && self + || typeof window !== "undefined" && window + || this +)); + +if (typeof module !== "undefined" && module.exports) { + module.exports.saveAs = saveAs; +} else if ((typeof define !== "undefined" && define !== null) && (define.amd !== null)) { + define("FileSaver.js", function() { + return saveAs; + }); +} diff --git a/leshan-bsserver-demo/src/main/resources/webapp/vendor/js/FileSaver.min.js b/leshan-bsserver-demo/src/main/resources/webapp/vendor/js/FileSaver.min.js new file mode 100644 index 0000000000..0c8992e7b0 --- /dev/null +++ b/leshan-bsserver-demo/src/main/resources/webapp/vendor/js/FileSaver.min.js @@ -0,0 +1,2 @@ +/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ +var saveAs=saveAs||function(e){"use strict";if(!("undefined"==typeof e||"undefined"!=typeof navigator&&/MSIE [1-9]\./.test(navigator.userAgent))){var t=e.document,n=function(){return e.URL||e.webkitURL||e},o=t.createElementNS("http://www.w3.org/1999/xhtml","a"),r="download"in o,a=function(e){var t=new MouseEvent("click");e.dispatchEvent(t)},i=/constructor/i.test(e.HTMLElement)||e.safari,d=/CriOS\/[\d]+/.test(navigator.userAgent),f=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},s="application/octet-stream",u=4e4,c=function(e){var t=function(){"string"==typeof e?n().revokeObjectURL(e):e.remove()};setTimeout(t,u)},l=function(e,t,n){t=[].concat(t);for(var o=t.length;o--;){var r=e["on"+t[o]];if("function"==typeof r)try{r.call(e,n||e)}catch(a){f(a)}}},v=function(e){return/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)?new Blob([String.fromCharCode(65279),e],{type:e.type}):e},p=function(t,f,u){u||(t=v(t));var p,w=this,m=t.type,y=m===s,S=function(){l(w,"writestart progress write writeend".split(" "))},h=function(){if((d||y&&i)&&e.FileReader){var o=new FileReader;return o.onloadend=function(){var t=d?o.result:o.result.replace(/^data:[^;]*;/,"data:attachment/file;"),n=e.open(t,"_blank");n||(e.location.href=t),t=void 0,w.readyState=w.DONE,S()},o.readAsDataURL(t),void(w.readyState=w.INIT)}if(p||(p=n().createObjectURL(t)),y)e.location.href=p;else{var r=e.open(p,"_blank");r||(e.location.href=p)}w.readyState=w.DONE,S(),c(p)};return w.readyState=w.INIT,r?(p=n().createObjectURL(t),void setTimeout(function(){o.href=p,o.download=f,a(o),S(),c(p),w.readyState=w.DONE})):void h()},w=p.prototype,m=function(e,t,n){return new p(e,t||e.name||"download",n)};return"undefined"!=typeof navigator&&navigator.msSaveOrOpenBlob?function(e,t,n){return t=t||e.name||"download",n||(e=v(e)),navigator.msSaveOrOpenBlob(e,t)}:(w.abort=function(){},w.readyState=w.INIT=0,w.WRITING=1,w.DONE=2,w.error=w.onwritestart=w.onprogress=w.onwrite=w.onabort=w.onerror=w.onwriteend=null,m)}}("undefined"!=typeof self&&self||"undefined"!=typeof window&&window||this);"undefined"!=typeof module&&module.exports?module.exports.saveAs=saveAs:"undefined"!=typeof define&&null!==define&&null!==define.amd&&define("FileSaver.js",function(){return saveAs}); \ No newline at end of file diff --git a/leshan-core/src/main/java/org/eclipse/leshan/util/SecurityUtil.java b/leshan-core/src/main/java/org/eclipse/leshan/util/SecurityUtil.java index 8906170d03..ea34752b46 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/util/SecurityUtil.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/util/SecurityUtil.java @@ -15,11 +15,14 @@ *******************************************************************************/ package org.eclipse.leshan.util; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; @@ -28,7 +31,8 @@ public class SecurityUtil { /** * Extract Elliptic Curve private key in PKCS8 format from file (DER encoded). */ - public static PrivateKey extractPrivateKey(String fileName) throws Exception { + public static PrivateKey extractPrivateKey(String fileName) + throws NoSuchAlgorithmException, InvalidKeySpecException, IOException { byte[] keyBytes = Files.readAllBytes(Paths.get(fileName)); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); @@ -39,10 +43,18 @@ public static PrivateKey extractPrivateKey(String fileName) throws Exception { /** * Extract Elliptic Curve public key in SubjectPublicKeyInfo format from file (DER encoded). */ - public static PublicKey extractPublicKey(String fileName) throws Exception { + public static PublicKey extractPublicKey(String fileName) + throws NoSuchAlgorithmException, InvalidKeySpecException, IOException { byte[] keyBytes = Files.readAllBytes(Paths.get(fileName)); + return extractPublicKey(keyBytes); + } - X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes); + /** + * Extract Elliptic Curve public key in SubjectPublicKeyInfo format from byteArray (DER encoded). + */ + public static PublicKey extractPublicKey(byte[] subjectPublicKeyInfo) + throws NoSuchAlgorithmException, InvalidKeySpecException { + X509EncodedKeySpec spec = new X509EncodedKeySpec(subjectPublicKeyInfo); KeyFactory kf = KeyFactory.getInstance("EC"); return kf.generatePublic(spec); } 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 b938490f93..7cb0b89a93 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 @@ -30,6 +30,7 @@ import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; +// /!\ This class is a COPY of org.eclipse.leshan.server.bootstrap.demo.json.SecuritySerializer /!\ public class SecuritySerializer implements JsonSerializer { @Override From 6ecdd5ca6ade2600cb853c2ceb5bd03a7b5e53d9 Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Fri, 17 Aug 2018 17:33:57 +0200 Subject: [PATCH 3/5] Fix X509 bootstrap server demo UI --- .../src/main/resources/webapp/tag/x509-input.tag | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/leshan-bsserver-demo/src/main/resources/webapp/tag/x509-input.tag b/leshan-bsserver-demo/src/main/resources/webapp/tag/x509-input.tag index 46ed1c84f0..0fa62a8494 100644 --- a/leshan-bsserver-demo/src/main/resources/webapp/tag/x509-input.tag +++ b/leshan-bsserver-demo/src/main/resources/webapp/tag/x509-input.tag @@ -57,7 +57,7 @@ tag.x509Cert.error = true; tag.x509Cert.nothexa = true; } - opts.onchange(); + tag.onchange(); } function validate_x509PrivateKey(){ @@ -72,7 +72,7 @@ tag.x509PrivateKey.error = true; tag.x509PrivateKey.nothexa = true; } - opts.onchange(); + tag.onchange(); } function validate_x509ServerCert(){ @@ -87,11 +87,11 @@ tag.x509ServerCert.error = true; tag.x509ServerCert.nothexa = true; } - opts.onchange(); + tag.onchange(); } function has_error(){ - return typeof tag.x509Cert.error === "undefined" || x509Cert.error || typeof x509PrivateKey.error === "undefined" || x509PrivateKey.error || typeof x509ServerCert.error === "undefined" || x509ServerCert.error; + return typeof tag.x509Cert.error === "undefined" || tag.x509Cert.error || typeof tag.x509PrivateKey.error === "undefined" || tag.x509PrivateKey.error || typeof tag.x509ServerCert.error === "undefined" || tag.x509ServerCert.error; } function get_value(){ From e5807fd4e4e7ee4218ca76baff5bb6e783efbf7d Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Fri, 17 Aug 2018 17:34:38 +0200 Subject: [PATCH 4/5] Print hexa public and private key in log of Leshan client demo. --- .../org/eclipse/leshan/client/demo/LeshanClientDemo.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 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 5945b69724..b5363a5aff 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 @@ -328,8 +328,11 @@ public static void createAndStartClient(String endpoint, String localAddress, in 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)); + "Client uses RPK : \n Elliptic Curve parameters : {} \n Public x coord : {} \n Public y coord : {} \n Public Key (Hex): {} \n Private Key (Hex): {}", + params, Hex.encodeHexString(x), Hex.encodeHexString(y), + Hex.encodeHexString(rawPublicKey.getEncoded()), + Hex.encodeHexString(clientPrivateKey.getEncoded())); + } else { throw new IllegalStateException("Unsupported Public Key Format (only ECPublicKey supported)."); } From 588b0611a6f3946e95c18d9e10e2ed13f4b130de Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Fri, 17 Aug 2018 17:35:04 +0200 Subject: [PATCH 5/5] Display more RPK information in leshan-server-demo UI. --- .../src/main/resources/webapp/css/lw-app.css | 5 +++ .../webapp/js/security-controllers.js | 34 ++++++++++++++----- .../webapp/partials/security-list.html | 10 ++++-- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/leshan-server-demo/src/main/resources/webapp/css/lw-app.css b/leshan-server-demo/src/main/resources/webapp/css/lw-app.css index 2facc58a90..0003c5a500 100644 --- a/leshan-server-demo/src/main/resources/webapp/css/lw-app.css +++ b/leshan-server-demo/src/main/resources/webapp/css/lw-app.css @@ -26,6 +26,11 @@ code { color: #b11eb2; } +pre { + word-wrap: break-word; + word-break: break-all; + white-space: pre-wrap; +} .navbar { text-transform: uppercase; 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 ada6ad0be3..aa5439e2f8 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 @@ -22,6 +22,26 @@ angular.module('securityControllers', []) 'dialog', function SecurityCtrl($scope, $http, dialog) { + function toHex(byteArray){ + var hex = []; + for (var i in byteArray){ + hex[i] = byteArray[i].toString(16).toUpperCase(); + if (hex[i].length === 1){ + hex[i] = '0' + hex[i]; + } + } + return hex.join(''); + }; + function base64ToBytes(base64){ + var byteKey = atob(base64); + var byteKeyLength = byteKey.length; + var array = new Uint8Array(new ArrayBuffer(byteKeyLength)); + for(i = 0; i < byteKeyLength; i++) { + array[i] = byteKey.charCodeAt(i); + } + return array; + } + // update navbar angular.element("#navbar").children().removeClass('active'); angular.element("#security-navlink").addClass('active'); @@ -41,7 +61,11 @@ angular.module('securityControllers', []) $scope.error = "Unable to get the server security info list: " + status + " " + data; console.error($scope.error); }).success(function(data, status, headers, config) { - $scope.serverSecurityInfo = data; + $scope.serverSecurityInfo = data; + $scope.pkcs8pubkey = {}; + $scope.pkcs8pubkey.base64 = data.rpk.pkcs8; + $scope.pkcs8pubkey.bytes = base64ToBytes($scope.pkcs8pubkey.base64); + $scope.pkcs8pubkey.hex = toHex($scope.pkcs8pubkey.bytes); }); $scope.remove = function(endpoint) { @@ -56,13 +80,7 @@ angular.module('securityControllers', []) }; $scope.saveServerPubKey = function(serverPubKey) { - var byteKey = atob(serverPubKey); - var byteKeyLength = byteKey.length; - var array = new Uint8Array(new ArrayBuffer(byteKeyLength)); - for(i = 0; i < byteKeyLength; i++) { - array[i] = byteKey.charCodeAt(i); - } - var blob = new Blob([array], {type: "application/octet-stream"}); + var blob = new Blob($scope.pkcs8pubkey.bytes, {type: "application/octet-stream"}); var fileName = "serverPubKey.der"; saveAs(blob, fileName); }; 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 0ff347a62f..8b6ddb7194 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 @@ -1,5 +1,5 @@ -
-

The Leshan public key +
+

The Leshan public key (SubjectPublicKeyInfo der encoded) @@ -8,6 +8,12 @@

The Leshan public key Elliptic Curve parameters : {{serverSecurityInfo.rpk.params}}
Public x coord : {{serverSecurityInfo.rpk.x}}
Public y coord : {{serverSecurityInfo.rpk.y}} +
+ Hex :
{{pkcs8pubkey.hex}}
+
+
+ Base64 :
{{pkcs8pubkey.base64}}
+