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