Skip to content

Commit

Permalink
REST API about SecurityInfo to prepare to add OSCORE support.
Browse files Browse the repository at this point in the history
  • Loading branch information
sbernard31 committed Jul 6, 2022
1 parent 650c01d commit 69a6a73
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 205 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,9 @@ export default {
}
}

// apply endpoint to security
res.security.endpoint = res.endpoint

return res;
},
close() {
Expand Down
27 changes: 16 additions & 11 deletions leshan-bsserver-demo/webapp/src/components/wizard/SecurityStep.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,21 @@
a demo.
</p>
</v-card-text>
<v-form ref="form" :value="valid" @input="$emit('update:valid', !useDTLS || $event)">
<v-switch class="pl-5"
<v-form
ref="form"
:value="valid"
@input="$emit('update:valid', !useDTLS || $event)"
>
<v-switch
class="pl-5"
v-model="useDTLS"
@change="updateUseDTLS($event)"
label="Using (D)TLS"
></v-switch>
<security-info-input
v-show="useDTLS"
:mode="internalSecurityInfo.mode"
:details="internalSecurityInfo.details"
:mode="internalSecurityInfo.tls.mode"
:details="internalSecurityInfo.tls.details"
@update:mode="updateMode($event)"
@update:details="updateDetails($event)"
/>
Expand All @@ -49,14 +54,14 @@ export default {
data() {
return {
useDTLS: false,
internalSecurityInfo: { mode: "psk", details: {} },
internalSecurityInfo: { tls: { mode: "psk", details: {} } },
};
},
watch: {
value(v) {
if (!v) {
this.useDTLS = false;
this.internalSecurityInfo = { mode: "psk", details: {} };
this.internalSecurityInfo = { tls: { mode: "psk", details: {} } };
} else {
this.useDTLS = true;
this.internalSecurityInfo = v;
Expand All @@ -68,18 +73,18 @@ export default {
if (useDTLS) {
this.$emit("input", this.internalSecurityInfo);
this.resetValidation();
this.$emit('update:valid', false);
this.$emit("update:valid", false);
} else {
this.$emit("input", null);
this.$emit('update:valid', true);
this.$emit("update:valid", true);
}
},
updateMode(mode) {
this.internalSecurityInfo.mode = mode;
this.internalSecurityInfo.tls.mode = mode;
this.$emit("input", this.internalSecurityInfo);
},
updateDetails(mode) {
this.internalSecurityInfo.details = mode;
updateDetails(details) {
this.internalSecurityInfo.tls.details = details;
this.$emit("input", this.internalSecurityInfo);
},
resetValidation() {
Expand Down
19 changes: 5 additions & 14 deletions leshan-bsserver-demo/webapp/src/views/Bootstrap.vue
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,6 @@
import { configsFromRestToUI, configFromUIToRest } from "../js/bsconfigutil.js";
import { fromHex, fromAscii } from "@leshan-server-core-demo/js/byteutils.js";
import SecurityInfoChip from "@leshan-server-core-demo/components/security/SecurityInfoChip.vue";
import {
adaptToUI,
adaptToAPI,
} from "@leshan-server-core-demo/js/securityutils.js";
import ClientConfigDialog from "../components/wizard/ClientConfigDialog.vue";
import { getModeIcon } from "@leshan-server-core-demo/js/securityutils.js";

Expand Down Expand Up @@ -164,11 +160,11 @@ export default {
(c) => c.endpoint === sec.endpoint
);
if (existingConfig) {
existingConfig.security = adaptToUI(sec);
existingConfig.security = sec;
} else {
newConfigs.push({
endpoint: sec.endpoint,
security: adaptToUI(sec),
security: sec,
});
}
})
Expand Down Expand Up @@ -215,14 +211,9 @@ export default {
onAdd(config) {
if (config.security) {
// if we have security we try to add security first
this.axios
.put(
"api/security/clients/",
adaptToAPI(config.security, config.endpoint)
)
.then(() => {
this.addConfig(config);
});
this.axios.put("api/security/clients/", config.security).then(() => {
this.addConfig(config);
});
} else {
// if we don't have security, we remove existing one first
this.axios
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,8 @@
package org.eclipse.leshan.server.core.demo.json;

import java.io.IOException;
import java.math.BigInteger;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.KeySpec;

import org.eclipse.leshan.core.util.Hex;
import org.eclipse.leshan.core.util.SecurityUtil;
Expand All @@ -37,77 +29,80 @@
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;

public class JacksonSecurityDeserializer extends JsonDeserializer<SecurityInfo> {

@Override
public SecurityInfo deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {

JsonNode node = p.getCodec().readTree(p);
if (!node.isObject()) {
throw new JsonParseException(p, "Security info should be a json object");
}

SecurityInfo info = null;

if (node.isObject()) {
// Get endpoint
String endpoint;
if (node.has("endpoint")) {
endpoint = node.get("endpoint").asText();
} else {
throw new JsonParseException(p, "Missing endpoint");
}

String endpoint;
if (node.has("endpoint")) {
endpoint = node.get("endpoint").asText();
} else {
throw new JsonParseException(p, "Missing endpoint");
// handle dtls
if (node.has("tls")) {
JsonNode oTls = node.get("tls");
if (oTls.getNodeType() != JsonNodeType.OBJECT) {
throw new JsonParseException(p, "tls field should be a json object");
}
String mode = oTls.get("mode").asText();
if (mode.equals("psk")) {
// handle PSK
JsonNode oPsk = oTls.get("details");
if (oPsk.getNodeType() != JsonNodeType.OBJECT) {
throw new JsonParseException(p, "details field should be a json object");
}

JsonNode psk = node.get("psk");
JsonNode rpk = node.get("rpk");
JsonNode x509 = node.get("x509");
if (psk != null) {
// PSK Deserialization
// get identity
String identity;
if (psk.has("identity")) {
identity = psk.get("identity").asText();
if (oPsk.has("identity")) {
identity = oPsk.get("identity").asText();
} else {
throw new JsonParseException(p, "Missing PSK identity");
}

// get key
byte[] key;
try {
key = Hex.decodeHex(psk.get("key").asText().toCharArray());
key = Hex.decodeHex(oPsk.get("key").asText().toCharArray());
} catch (IllegalArgumentException e) {
throw new JsonParseException(p, "key parameter must be a valid hex string", e);
}
return SecurityInfo.newPreSharedKeyInfo(endpoint, identity, key);
} else if (mode.equals("rpk")) {
// handle RPK
JsonNode oRpk = oTls.get("details");
if (oRpk.getNodeType() != JsonNodeType.OBJECT) {
throw new JsonParseException(p, "details field should be a json object");
}

info = SecurityInfo.newPreSharedKeyInfo(endpoint, identity, key);
} else if (rpk != null) {
// get public key
PublicKey key;
try {
if (rpk.has("key")) {
byte[] bytekey = Hex.decodeHex(rpk.get("key").asText().toCharArray());
if (oRpk.has("key")) {
byte[] bytekey = Hex.decodeHex(oRpk.get("key").asText().toCharArray());
key = SecurityUtil.publicKey.decode(bytekey);
} else {
// This is just needed to keep API backward compatibility.
// TODO as this is not used anymore by the UI, we should maybe remove it.
byte[] x = Hex.decodeHex(rpk.get("x").asText().toCharArray());
byte[] y = Hex.decodeHex(rpk.get("y").asText().toCharArray());
String params = rpk.get("params").asText();

AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC");
algoParameters.init(new ECGenParameterSpec(params));
ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class);

KeySpec keySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(x), new BigInteger(y)),
parameterSpec);

key = KeyFactory.getInstance("EC").generatePublic(keySpec);
return SecurityInfo.newRawPublicKeyInfo(endpoint, key);
}
} catch (IllegalArgumentException | IOException | GeneralSecurityException e) {
} catch (IllegalArgumentException | GeneralSecurityException e) {
throw new JsonParseException(p, "Invalid security info content", e);
}
info = SecurityInfo.newRawPublicKeyInfo(endpoint, key);
} else if (x509 != null && x509.asBoolean()) {
info = SecurityInfo.newX509CertInfo(endpoint);
} else if (mode.equals("x509")) {
// handle x509
return SecurityInfo.newX509CertInfo(endpoint);
} else {
throw new JsonParseException(p, "Invalid security info content");
}
}

return info;
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,14 @@

import java.io.IOException;
import java.security.PublicKey;
import java.security.interfaces.ECPublicKey;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

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

import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;

public class JacksonSecuritySerializer extends StdSerializer<SecurityInfo> {
Expand All @@ -45,53 +41,38 @@ public JacksonSecuritySerializer() {
}

@Override
public void serialize(SecurityInfo src, JsonGenerator gen, SerializerProvider provider) throws IOException {
Map<String, Object> element = new HashMap<>();

element.put("endpoint", src.getEndpoint());

if (src.getPskIdentity() != null) {
Map<String, Object> psk = new HashMap<>();
psk.put("identity", src.getPskIdentity());
psk.put("key", Hex.encodeHexString(src.getPreSharedKey()));
element.put("psk", psk);
}

if (src.getRawPublicKey() != null) {
Map<String, Object> rpk = new HashMap<>();
PublicKey rawPublicKey = src.getRawPublicKey();
if (rawPublicKey instanceof ECPublicKey) {
rpk.put("key", Hex.encodeHexString(rawPublicKey.getEncoded()));

// TODO all the fields above is no more used should be removed it ?
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.put("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.put("y", Hex.encodeHexString(y));

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

// Get raw public key in format PKCS8 (DER encoding)
rpk.put("pkcs8", Base64.encodeBase64String(ecPublicKey.getEncoded()));
} else {
throw new JsonGenerationException("Unsupported Public Key Format (only ECPublicKey supported).", gen);
public void serialize(SecurityInfo securityInfo, JsonGenerator gen, SerializerProvider provider)
throws IOException {
ObjectNode oSecInfo = JsonNodeFactory.instance.objectNode();
oSecInfo.put("endpoint", securityInfo.getEndpoint());

if (securityInfo.useSecureTransportLayer()) {
// handle (D)TLS case :
ObjectNode oTls = JsonNodeFactory.instance.objectNode();
oSecInfo.set("tls", oTls);

if (securityInfo.usePSK()) {
// set detail for PSK
ObjectNode oPsk = JsonNodeFactory.instance.objectNode();
oPsk.put("identity", securityInfo.getPskIdentity());
oPsk.put("key", Hex.encodeHexString(securityInfo.getPreSharedKey()));

// set PSK field
oTls.put("mode", "psk");
oTls.set("details", oPsk);
} else if (securityInfo.useRPK()) {
// set details
ObjectNode oRpk = JsonNodeFactory.instance.objectNode();
PublicKey rawPublicKey = securityInfo.getRawPublicKey();
oRpk.put("key", Hex.encodeHexString(rawPublicKey.getEncoded()));

// set RPK field
oTls.put("mode", "rpk");
oTls.set("details", oRpk);
} else if (securityInfo.useX509Cert()) {
oTls.put("mode", "x509");
}
element.put("rpk", rpk);
}

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

gen.writeObject(element);
gen.writeTree(oSecInfo);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
* http://www.eclipse.org/org/documents/edl-v10.html.
----------------------------------------------------------------------------->
<template>
<div v-if="securityInfo">
<div v-if="securityInfo && securityInfo.tls">
<v-chip small>
<v-icon left small>
{{ modeIcon }}
</v-icon>
{{ securityInfo.mode }}
{{ securityInfo.tls.mode }}
</v-chip>
</div>
<div v-else>
Expand All @@ -33,7 +33,7 @@ export default {
props: { securityInfo: Object /*securityInfo to display*/ },
computed: {
modeIcon() {
return getModeIcon(this.securityInfo.mode);
return getModeIcon(this.securityInfo.tls.mode);
},
},
};
Expand Down
Loading

0 comments on commit 69a6a73

Please sign in to comment.