From 8f0db2449248bb4d20d4237d09ddb095a83f5969 Mon Sep 17 00:00:00 2001 From: Bartosz Stolarczyk Date: Fri, 24 Feb 2023 16:19:38 +0100 Subject: [PATCH] GH-1402:Fix Invalid datatype of Ciphersuite resource in BootstrapConfig Co-authored-by: Simon Bernard --- leshan-server-core/pom.xml | 5 + .../server/bootstrap/BootstrapConfig.java | 59 +++++++++++- .../server/bootstrap/BootstrapUtil.java | 13 ++- .../server/bootstrap/BootstrapConfigTest.java | 92 +++++++++++++++++++ 4 files changed, 165 insertions(+), 4 deletions(-) create mode 100644 leshan-server-core/src/test/java/org/eclipse/leshan/server/bootstrap/BootstrapConfigTest.java diff --git a/leshan-server-core/pom.xml b/leshan-server-core/pom.xml index 6783488474..32a4681710 100644 --- a/leshan-server-core/pom.xml +++ b/leshan-server-core/pom.xml @@ -42,6 +42,11 @@ Contributors: junit-jupiter-engine test + + org.junit.jupiter + junit-jupiter-params + test + ch.qos.logback logback-classic diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfig.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfig.java index 1297373432..af70cf478b 100644 --- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfig.java +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapConfig.java @@ -291,7 +291,6 @@ public static class ServerSecurity { /** * The Object ID of the OSCORE Object Instance that holds the OSCORE configuration to be used by the LWM2M * Client to the LWM2M Server associated with this Security object. - * */ public Integer oscoreSecurityMode; @@ -341,7 +340,7 @@ public static class ServerSecurity { *

* Since Security v1.1 */ - public ULong cipherSuite = null; + public List cipherSuite = null; @Override public String toString() { @@ -465,6 +464,62 @@ public String toString() { } } + public static class CipherSuiteId { + + private final byte firstByte; + private final byte secondByte; + + /** + * Create {@link CipherSuiteId} from Cipher Suite value defined at IANA. + *

+ * Possible values are described in the + * registry. + *

+ * This doesn't mean that Leshan supports all IANA registered Cipher Suites. + * + * @param firstByte of Cipher Suite (e.g. {@code 0xC0} for {@code TLS_PSK_WITH_AES_128_CCM_8}) + * @param secondByte of Cipher Suite (e.g. {@code 0xA8} for {@code TLS_PSK_WITH_AES_128_CCM_8}) + */ + public CipherSuiteId(byte firstByte, byte secondByte) { + this.firstByte = firstByte; + this.secondByte = secondByte; + } + + /** + * Create {@link CipherSuiteId} from ULong value of "DTLS/TLS Ciphersuite" resource (Id:16) from "Security" + * Object (Id:0). + *

+ * As an example, the {@code TLS_PSK_WITH_AES_128_CCM_8} Cipher Suite is represented with the following string + * "{@code 0xC0,0xA8}". To form an integer value the two values are concatenated. In this example, the value is + * {@code 0xc0a8} for {@code 49320}. + */ + public CipherSuiteId(ULong valueFromSecurityObject) { + if (valueFromSecurityObject.intValue() < 0 || valueFromSecurityObject.intValue() > 65535) + throw new IllegalArgumentException("ULong value MUST be a 16-bit unsigned integer (max value : 65535)"); + this.firstByte = (byte) ((valueFromSecurityObject.intValue() >> 8) & 0xFF); + this.secondByte = (byte) ((valueFromSecurityObject.intValue()) & 0xFF); + } + + /** + * @return Value used in "DTLS/TLS Ciphersuite" resource (Id:16) from "Security" Object (Id:0). + *

+ * The two bytes from Cipher Suite id are concatenated into integer value. As an example bytes + * "{@code 0xC0,0xA8}" will be concatenated into {@code 0xc0a8} which in decimal notation is + * {@code 49320}. + */ + public ULong getValueForSecurityObject() { + return ULong.valueOf((Byte.toUnsignedInt(firstByte) << 8) | Byte.toUnsignedInt(secondByte)); + } + + /** + * @return String representing IANA Cipher Suite Value. + */ + @Override + public String toString() { + return String.format("0x%02X,0x%02X", firstByte, secondByte); + } + } + @Override public String toString() { return String.format("BootstrapConfig [servers=%s, security=%s, acls=%s, oscore=%s]", servers, security, acls, diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapUtil.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapUtil.java index 8848ab65a1..ce8b7354af 100644 --- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapUtil.java +++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapUtil.java @@ -18,7 +18,9 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; @@ -36,6 +38,7 @@ import org.eclipse.leshan.core.request.BootstrapWriteRequest; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.response.LwM2mResponse; +import org.eclipse.leshan.core.util.datatype.ULong; import org.eclipse.leshan.server.bootstrap.BootstrapConfig.ACLConfig; import org.eclipse.leshan.server.bootstrap.BootstrapConfig.OscoreObject; import org.eclipse.leshan.server.bootstrap.BootstrapConfig.ServerConfig; @@ -79,8 +82,14 @@ public static LwM2mObjectInstance toSecurityInstance(int instanceId, ServerSecur resources.add(LwM2mSingleResource.newStringResource(14, securityConfig.sni)); if (securityConfig.certificateUsage != null) resources.add(LwM2mSingleResource.newUnsignedIntegerResource(15, securityConfig.certificateUsage.code)); - if (securityConfig.cipherSuite != null) - resources.add(LwM2mSingleResource.newUnsignedIntegerResource(16, securityConfig.cipherSuite)); + if (securityConfig.cipherSuite != null) { + Map ciperSuiteULong = new HashMap<>(); + int i = 0; + for (BootstrapConfig.CipherSuiteId cipherSuiteId : securityConfig.cipherSuite) { + ciperSuiteULong.put(i++, cipherSuiteId.getValueForSecurityObject()); + } + resources.add(LwM2mMultipleResource.newUnsignedIntegerResource(16, ciperSuiteULong)); + } if (securityConfig.oscoreSecurityMode != null) { resources.add(LwM2mSingleResource.newObjectLinkResource(17, new ObjectLink(21, securityConfig.oscoreSecurityMode))); diff --git a/leshan-server-core/src/test/java/org/eclipse/leshan/server/bootstrap/BootstrapConfigTest.java b/leshan-server-core/src/test/java/org/eclipse/leshan/server/bootstrap/BootstrapConfigTest.java new file mode 100644 index 0000000000..6169ba5362 --- /dev/null +++ b/leshan-server-core/src/test/java/org/eclipse/leshan/server/bootstrap/BootstrapConfigTest.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2016 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: + * Bartosz Stolarczyk + * Orange Polska S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.server.bootstrap; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; + +import org.eclipse.leshan.core.util.Hex; +import org.eclipse.leshan.core.util.datatype.ULong; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +class BootstrapConfigTest { + + @ParameterizedTest + @CsvSource(value = { // given 2 bytes, expected ToString() + "C0A8 | 0xC0,0xA8", // TLS_PSK_WITH_AES_128_CCM_8 + "0003 | 0x00,0x03", // TLS_RSA_EXPORT_WITH_RC4_40_MD5 + }, delimiter = '|') + public void test_toString(String input, String expectedResult) { + // Given 2 bytes + byte[] decoded = Hex.decodeHex(input.toCharArray()); + + // Create CipherSuiteId + BootstrapConfig.CipherSuiteId cipherSuiteId = new BootstrapConfig.CipherSuiteId(decoded[0], decoded[1]); + + // Assert if bytes were correctly phrased + assertEquals(expectedResult, cipherSuiteId.toString()); + } + + @ParameterizedTest + @CsvSource(value = { // given ulong, expected ToString() + "49320 | 0xC0,0xA8", // TLS_PSK_WITH_AES_128_CCM_8 + " 0 | 0x00,0x00", // test to lower limit + "65535 | 0xFF,0xFF", // test to upper limit + + }, delimiter = '|') + + public void test_create_new_CipherSuiteId_from_ULong(String input, String expectedResult) { + // Create CipherSuiteId with ULong + BootstrapConfig.CipherSuiteId cipherSuiteId = new BootstrapConfig.CipherSuiteId(ULong.valueOf(input)); + + // Assert if ULong was correctly phrased + assertEquals(expectedResult, cipherSuiteId.toString()); + } + + @ParameterizedTest + @ValueSource(strings = { // + "49320", // TLS_PSK_WITH_AES_128_CCM_8 + "0", // test to lower limit + "65535", // test to upper limit + }) + public void test_getValueForSecurityObject(String input) { + // Create example ULong + ULong testValue = ULong.valueOf(input); + + // Create cipherSuiteId from ULong + BootstrapConfig.CipherSuiteId cipherSuiteId = new BootstrapConfig.CipherSuiteId(testValue); + + // Check if getValueForSecurityObject() returns input ULong + assertEquals(testValue, cipherSuiteId.getValueForSecurityObject()); + } + + @ParameterizedTest + @ValueSource(strings = { // + "2147483647", // max 32-bit signed Integer + "2147483648", // max 32-bit signed Integer + 1 + "65536"// max 16-bit signed Integer +1 + }) + public void test_error_thrown_for_invalid_ulong(String invalidULong) { + // Create ULong from String + ULong testValue = ULong.valueOf(invalidULong); + + // Try to create CipherSuiteId with ULong outside of range + assertThrowsExactly(IllegalArgumentException.class, () -> new BootstrapConfig.CipherSuiteId(testValue)); + } +}