diff --git a/kura/org.eclipse.kura.core.keystore/META-INF/MANIFEST.MF b/kura/org.eclipse.kura.core.keystore/META-INF/MANIFEST.MF index 62e91e480f5..e77bd64b166 100644 --- a/kura/org.eclipse.kura.core.keystore/META-INF/MANIFEST.MF +++ b/kura/org.eclipse.kura.core.keystore/META-INF/MANIFEST.MF @@ -40,6 +40,7 @@ Import-Package: com.eclipsesource.json;version="0.9.5", org.eclipse.kura.crypto;version="[1.1,2.0)", org.eclipse.kura.marshalling;version="[1.0,2.0)", org.eclipse.kura.message;version="[1.4,2.0)", + org.eclipse.kura.request.handler.jaxrs;version="[1.0,2.0)", org.eclipse.kura.rest.utils;version="[1.0,2.0)", org.eclipse.kura.security.keystore;version="[1.1,1.2)", org.eclipse.kura.system;version="[1.5,2.0)", diff --git a/kura/org.eclipse.kura.core.keystore/OSGI-INF/org.eclipse.kura.core.keystore.request.handler.keystoreRequestHandler.xml b/kura/org.eclipse.kura.core.keystore/OSGI-INF/org.eclipse.kura.core.keystore.request.handler.keystoreRequestHandlerV1.xml similarity index 91% rename from kura/org.eclipse.kura.core.keystore/OSGI-INF/org.eclipse.kura.core.keystore.request.handler.keystoreRequestHandler.xml rename to kura/org.eclipse.kura.core.keystore/OSGI-INF/org.eclipse.kura.core.keystore.request.handler.keystoreRequestHandlerV1.xml index beda8f8fbf8..831fe1c26b4 100644 --- a/kura/org.eclipse.kura.core.keystore/OSGI-INF/org.eclipse.kura.core.keystore.request.handler.keystoreRequestHandler.xml +++ b/kura/org.eclipse.kura.core.keystore/OSGI-INF/org.eclipse.kura.core.keystore.request.handler.keystoreRequestHandlerV1.xml @@ -1,7 +1,7 @@ - + diff --git a/kura/org.eclipse.kura.core.keystore/OSGI-INF/org.eclipse.kura.core.keystore.request.handler.keystoreRequestHandlerV2.xml b/kura/org.eclipse.kura.core.keystore/OSGI-INF/org.eclipse.kura.core.keystore.request.handler.keystoreRequestHandlerV2.xml new file mode 100644 index 00000000000..f50a96a3a92 --- /dev/null +++ b/kura/org.eclipse.kura.core.keystore/OSGI-INF/org.eclipse.kura.core.keystore.request.handler.keystoreRequestHandlerV2.xml @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/kura/org.eclipse.kura.core.keystore/OSGI-INF/org.eclipse.kura.core.keystore.rest.provider.keystoreRestServiceV1.xml b/kura/org.eclipse.kura.core.keystore/OSGI-INF/org.eclipse.kura.core.keystore.rest.provider.keystoreRestServiceV1.xml new file mode 100644 index 00000000000..33cb3f3b38a --- /dev/null +++ b/kura/org.eclipse.kura.core.keystore/OSGI-INF/org.eclipse.kura.core.keystore.rest.provider.keystoreRestServiceV1.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + diff --git a/kura/org.eclipse.kura.core.keystore/OSGI-INF/org.eclipse.kura.core.keystore.rest.provider.keystoreRestService.xml b/kura/org.eclipse.kura.core.keystore/OSGI-INF/org.eclipse.kura.core.keystore.rest.provider.keystoreRestServiceV2.xml similarity index 84% rename from kura/org.eclipse.kura.core.keystore/OSGI-INF/org.eclipse.kura.core.keystore.rest.provider.keystoreRestService.xml rename to kura/org.eclipse.kura.core.keystore/OSGI-INF/org.eclipse.kura.core.keystore.rest.provider.keystoreRestServiceV2.xml index e990b706c79..98667bc62be 100644 --- a/kura/org.eclipse.kura.core.keystore/OSGI-INF/org.eclipse.kura.core.keystore.rest.provider.keystoreRestService.xml +++ b/kura/org.eclipse.kura.core.keystore/OSGI-INF/org.eclipse.kura.core.keystore.rest.provider.keystoreRestServiceV2.xml @@ -1,7 +1,7 @@ - - + + - + diff --git a/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/KeystoreInstance.java b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/KeystoreInstance.java index 5f5d0fa24be..c785c155e62 100644 --- a/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/KeystoreInstance.java +++ b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/KeystoreInstance.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022 Eurotech and/or its affiliates and others + * Copyright (c) 2022, 2023 Eurotech and/or its affiliates and others * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -14,7 +14,7 @@ import java.security.KeyStore; -interface KeystoreInstance { +public interface KeystoreInstance { public KeyStore getKeystore(); diff --git a/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/request/CsrReadRequest.java b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/request/CsrReadRequest.java new file mode 100644 index 00000000000..93fb824ce97 --- /dev/null +++ b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/request/CsrReadRequest.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2021, 2023 Eurotech and/or its affiliates and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Eurotech + *******************************************************************************/ +package org.eclipse.kura.core.keystore.request; + +import org.eclipse.kura.core.keystore.util.CsrInfo; +import org.eclipse.kura.rest.utils.Validable; + +public class CsrReadRequest extends CsrInfo implements Validable { + + public CsrReadRequest(String keystoreServicePid, String alias) { + super(keystoreServicePid, alias); + } + + public CsrReadRequest(final CsrInfo csrInfo) { + super(csrInfo.getKeystoreServicePid(), csrInfo.getAlias()); + this.setSignatureAlgorithm(csrInfo.getSignatureAlgorithm()); + this.setAttributes(csrInfo.getAttributes()); + } + + @Override + public String toString() { + return "ReadRequest [keystoreServicePid=" + this.getKeystoreServicePid() + ", alias=" + this.getAlias() + + ", algorithm=" + + this.getSignatureAlgorithm() + ", attributes=" + this.getAttributes() + "]"; + } + + @Override + public boolean isValid() { + return this.getKeystoreServicePid() != null && this.getAlias() != null && this.getSignatureAlgorithm() != null + && this.getAttributes() != null; + } + +} diff --git a/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/request/EntryRequest.java b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/request/EntryRequest.java new file mode 100644 index 00000000000..7cbbb89e1c5 --- /dev/null +++ b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/request/EntryRequest.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2021, 2023 Eurotech and/or its affiliates and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Eurotech + *******************************************************************************/ +package org.eclipse.kura.core.keystore.request; + +import org.eclipse.kura.core.keystore.util.EntryInfo; +import org.eclipse.kura.rest.utils.Validable; + +public class EntryRequest extends EntryInfo implements Validable { + + public EntryRequest(String keystoreServicePid, String alias) { + super(keystoreServicePid, alias); + } + + public EntryRequest(final EntryInfo entryInfo) { + super(entryInfo.getKeystoreServicePid(), entryInfo.getAlias()); + } + + @Override + public String toString() { + return "DeleteRequest [keystoreServicePid=" + this.getKeystoreServicePid() + ", alias=" + this.getAlias() + "]"; + } + + @Override + public boolean isValid() { + return this.getKeystoreServicePid() != null && this.getAlias() != null; + } + +} diff --git a/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/rest/provider/KeyPairWriteRequest.java b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/request/KeyPairWriteRequest.java similarity index 67% rename from kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/rest/provider/KeyPairWriteRequest.java rename to kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/request/KeyPairWriteRequest.java index 4ac435adc22..34dae838f54 100644 --- a/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/rest/provider/KeyPairWriteRequest.java +++ b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/request/KeyPairWriteRequest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2021 Eurotech and/or its affiliates and others + * Copyright (c) 2021, 2023 Eurotech and/or its affiliates and others * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -11,7 +11,7 @@ * Eurotech * *******************************************************************************/ -package org.eclipse.kura.core.keystore.rest.provider; +package org.eclipse.kura.core.keystore.request; import org.eclipse.kura.core.keystore.util.KeyPairInfo; import org.eclipse.kura.rest.utils.Validable; @@ -22,6 +22,14 @@ public KeyPairWriteRequest(String keystoreName, String alias) { super(keystoreName, alias); } + public KeyPairWriteRequest(final KeyPairInfo keyPairInfo) { + super(keyPairInfo.getKeystoreServicePid(), keyPairInfo.getAlias()); + this.setAlgorithm(keyPairInfo.getAlgorithm()); + this.setAttributes(keyPairInfo.getAttributes()); + this.setSignatureAlgorithm(keyPairInfo.getSignatureAlgorithm()); + this.setSize(keyPairInfo.getSize()); + } + @Override public boolean isValid() { if (getKeystoreServicePid() == null || getAlias() == null) { diff --git a/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/request/PrivateKeyWriteRequest.java b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/request/PrivateKeyWriteRequest.java new file mode 100644 index 00000000000..31dea47721e --- /dev/null +++ b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/request/PrivateKeyWriteRequest.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2023 Eurotech and/or its affiliates and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Eurotech + *******************************************************************************/ +package org.eclipse.kura.core.keystore.request; + +import org.eclipse.kura.core.keystore.util.PrivateKeyInfo; +import org.eclipse.kura.rest.utils.Validable; + +public class PrivateKeyWriteRequest extends PrivateKeyInfo implements Validable { + + public PrivateKeyWriteRequest(String keystoreServicePid, String alias) { + super(keystoreServicePid, alias); + } + + public PrivateKeyWriteRequest(final PrivateKeyInfo other) { + super(other.getAlias(), other.getKeystoreServicePid()); + this.setAlgorithm(other.getAlgorithm()); + this.setSize(other.getSize()); + this.setPrivateKey(other.getPrivateKey()); + this.setCertificateChain(other.getCertificateChain()); + } + + @Override + public boolean isValid() { + return getKeystoreServicePid() != null && getAlias() != null && getCertificateChain() != null + && getCertificateChain().length > 0; + } +} diff --git a/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/rest/provider/TrustedCertificateWriteRequest.java b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/request/TrustedCertificateWriteRequest.java similarity index 57% rename from kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/rest/provider/TrustedCertificateWriteRequest.java rename to kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/request/TrustedCertificateWriteRequest.java index ed802466119..0402662ecfb 100644 --- a/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/rest/provider/TrustedCertificateWriteRequest.java +++ b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/request/TrustedCertificateWriteRequest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2021 Eurotech and/or its affiliates and others + * Copyright (c) 2021, 2023 Eurotech and/or its affiliates and others * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -10,15 +10,27 @@ * Contributors: * Eurotech *******************************************************************************/ -package org.eclipse.kura.core.keystore.rest.provider; +package org.eclipse.kura.core.keystore.request; import org.eclipse.kura.core.keystore.util.CertificateInfo; import org.eclipse.kura.rest.utils.Validable; public class TrustedCertificateWriteRequest extends CertificateInfo implements Validable { - public TrustedCertificateWriteRequest(String alias, String keystoreName) { - super(alias, keystoreName); + public TrustedCertificateWriteRequest(String keystoreServicePid, String alias) { + super(keystoreServicePid, alias); + } + + public TrustedCertificateWriteRequest(final CertificateInfo other) { + super(other.getKeystoreServicePid(), other.getAlias()); + this.setSubjectDN(other.getSubjectDN()); + this.setSubjectAN(other.getSubjectAN()); + this.setIssuer(other.getIssuer()); + this.setStartDate(other.getStartDate()); + this.setExpirationDate(other.getExpirationDate()); + this.setAlgorithm(other.getAlgorithm()); + this.setSize(other.getSize()); + this.setCertificate(other.getCertificate()); } @Override diff --git a/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/request/handler/KeystoreServiceRequestHandler.java b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/request/handler/KeystoreServiceRequestHandler.java new file mode 100644 index 00000000000..fb11da7bfa8 --- /dev/null +++ b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/request/handler/KeystoreServiceRequestHandler.java @@ -0,0 +1,258 @@ +/******************************************************************************* + * Copyright (c) 2021 Eurotech and/or its affiliates and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Eurotech + * Red Hat Inc + *******************************************************************************/ +package org.eclipse.kura.core.keystore.request.handler; + +import static java.util.Objects.isNull; +import static org.eclipse.kura.cloudconnection.request.RequestHandlerMessageConstants.ARGS_KEY; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.function.Function; + +import org.eclipse.kura.KuraErrorCode; +import org.eclipse.kura.KuraException; +import org.eclipse.kura.cloudconnection.message.KuraMessage; +import org.eclipse.kura.cloudconnection.request.RequestHandler; +import org.eclipse.kura.cloudconnection.request.RequestHandlerContext; +import org.eclipse.kura.cloudconnection.request.RequestHandlerRegistry; +import org.eclipse.kura.core.keystore.request.EntryRequest; +import org.eclipse.kura.core.keystore.request.KeyPairWriteRequest; +import org.eclipse.kura.core.keystore.request.TrustedCertificateWriteRequest; +import org.eclipse.kura.core.keystore.util.CertificateInfo; +import org.eclipse.kura.core.keystore.util.CsrInfo; +import org.eclipse.kura.core.keystore.util.EntryInfo; +import org.eclipse.kura.core.keystore.util.KeyPairInfo; +import org.eclipse.kura.core.keystore.util.KeystoreRemoteService; +import org.eclipse.kura.marshalling.Unmarshaller; +import org.eclipse.kura.message.KuraPayload; +import org.eclipse.kura.message.KuraResponsePayload; +import org.eclipse.kura.rest.utils.Validable; +import org.eclipse.kura.util.service.ServiceUtil; +import org.osgi.framework.ServiceReference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +public class KeystoreServiceRequestHandler extends KeystoreRemoteService implements RequestHandler { + + private static final Logger logger = LoggerFactory.getLogger(KeystoreServiceRequestHandler.class); + + protected static final String NONE_RESOURCE_FOUND_MESSAGE = "Resource not found"; + protected static final String KEYSTORES = "keystores"; + protected static final String ENTRIES = "entries"; + protected static final String ENTRY = "entry"; + protected static final String CSR = "csr"; + protected static final String CERTIFICATE = "certificate"; + protected static final String KEYPAIR = "keypair"; + + private final String appId; + + // ---------------------------------------------------------------- + // + // Dependencies + // + // ---------------------------------------------------------------- + + protected KeystoreServiceRequestHandler(final String appId) { + this.appId = appId; + } + + public void setRequestHandlerRegistry(RequestHandlerRegistry requestHandlerRegistry) { + try { + requestHandlerRegistry.registerRequestHandler(this.appId, this); + } catch (KuraException e) { + logger.info("Unable to register cloudlet {} in {}", this.appId, + requestHandlerRegistry.getClass().getName()); + } + } + + public void unsetRequestHandlerRegistry(RequestHandlerRegistry requestHandlerRegistry) { + try { + requestHandlerRegistry.unregister(this.appId); + } catch (KuraException e) { + logger.info("Unable to register cloudlet {} in {}", this.appId, + requestHandlerRegistry.getClass().getName()); + } + } + + // ---------------------------------------------------------------- + // + // Public methods + // + // ---------------------------------------------------------------- + + @Override + public KuraMessage doGet(final RequestHandlerContext context, final KuraMessage reqMessage) throws KuraException { + final List resourcePath = extractResourcePath(reqMessage); + KuraPayload reqPayload = reqMessage.getPayload(); + + if (resourcePath.isEmpty()) { + logger.error(NONE_RESOURCE_FOUND_MESSAGE); + throw new KuraException(KuraErrorCode.BAD_REQUEST); + } + + if (resourcePath.size() == 1 && resourcePath.get(0).equals(KEYSTORES)) { + return jsonResponse(listKeystoresInternal()); + } else if (resourcePath.size() == 2 && resourcePath.get(0).equals(KEYSTORES) + && resourcePath.get(1).equals(ENTRIES)) { + return doGetEntries(reqPayload); + } else if (resourcePath.size() == 3 && resourcePath.get(0).equals(KEYSTORES) + && resourcePath.get(1).equals(ENTRIES) && resourcePath.get(2).equals(ENTRY)) { + EntryInfo request = unmarshal(new String(reqPayload.getBody(), StandardCharsets.UTF_8), EntryInfo.class); + String keystoreServicePid = request.getKeystoreServicePid(); + String keyAlias = request.getAlias(); + if (!isNull(keystoreServicePid) && !isNull(keyAlias)) { + return jsonResponse(getKeyInternal(keystoreServicePid, keyAlias)); + } else { + throw new KuraException(KuraErrorCode.BAD_REQUEST); + } + } else { + throw new KuraException(KuraErrorCode.BAD_REQUEST); + } + } + + private KuraMessage doGetEntries(KuraPayload reqPayload) { + byte[] body = reqPayload.getBody(); + if (isNull(body) || body.length == 0) { + return jsonResponse(getKeysInternal()); + } else { + EntryInfo request = unmarshal(new String(reqPayload.getBody(), StandardCharsets.UTF_8), EntryInfo.class); + String keystoreServicePid = request.getKeystoreServicePid(); + String keyAlias = request.getAlias(); + if (isNull(keystoreServicePid) && !isNull(keyAlias)) { + return jsonResponse(getKeysByAliasInternal(keyAlias)); + } else { + return jsonResponse(getKeysByPidInternal(keystoreServicePid)); + } + } + } + + @Override + public KuraMessage doPost(RequestHandlerContext context, KuraMessage reqMessage) throws KuraException { + final List resourcePath = extractResourcePath(reqMessage); + KuraPayload reqPayload = reqMessage.getPayload(); + + if (resourcePath.size() != 3 || reqPayload.getBody() == null || reqPayload.getBody().length == 0 + || !resourcePath.get(0).equals(KEYSTORES) || !resourcePath.get(1).equals(ENTRIES)) { + logger.error(NONE_RESOURCE_FOUND_MESSAGE); + throw new KuraException(KuraErrorCode.BAD_REQUEST); + } + + if (resourcePath.get(2).equals(CSR)) { + return doPostCsr(reqPayload); + } else if (resourcePath.get(2).equals(CERTIFICATE)) { + String body = new String(reqPayload.getBody(), StandardCharsets.UTF_8); + CertificateInfo certificateInfo = unmarshal(body, CertificateInfo.class); + validateAs(certificateInfo, TrustedCertificateWriteRequest::new); + storeTrustedCertificateEntryInternal(certificateInfo); + return new KuraMessage(new KuraResponsePayload(200)); + } else if (resourcePath.get(2).equals(KEYPAIR)) { + String body = new String(reqPayload.getBody(), StandardCharsets.UTF_8); + KeyPairInfo keyPairInfo = unmarshal(body, KeyPairInfo.class); + validateAs(keyPairInfo, KeyPairWriteRequest::new); + storeKeyPairEntryInternal(keyPairInfo); + return new KuraMessage(new KuraResponsePayload(200)); + } else { + throw new KuraException(KuraErrorCode.BAD_REQUEST); + } + } + + private KuraMessage doPostCsr(KuraPayload reqPayload) { + String body = new String(reqPayload.getBody(), StandardCharsets.UTF_8); + CsrInfo csrInfo = unmarshal(body, CsrInfo.class); + return jsonResponse(getCSRInternal(csrInfo)); + } + + @Override + public KuraMessage doDel(RequestHandlerContext context, KuraMessage reqMessage) throws KuraException { + final List resourcePath = extractResourcePath(reqMessage); + KuraPayload reqPayload = reqMessage.getPayload(); + + if (resourcePath.size() != 2 || reqPayload.getBody() == null || reqPayload.getBody().length == 0 + || !resourcePath.get(0).equals(KEYSTORES)) { + logger.error(NONE_RESOURCE_FOUND_MESSAGE); + throw new KuraException(KuraErrorCode.BAD_REQUEST); + } + + if (resourcePath.get(1).equals(ENTRIES)) { + String body = new String(reqPayload.getBody(), StandardCharsets.UTF_8); + EntryInfo request = unmarshal(body, EntryInfo.class); + if (request != null) { + validateAs(request, EntryRequest::new); + deleteKeyEntryInternal(request.getKeystoreServicePid(), request.getAlias()); + return new KuraMessage(new KuraResponsePayload(200)); + } else { + throw new KuraException(KuraErrorCode.BAD_REQUEST); + } + } else { + throw new KuraException(KuraErrorCode.BAD_REQUEST); + } + } + + @SuppressWarnings("unchecked") + protected static final List extractResourcePath(final KuraMessage message) throws KuraException { + Object requestObject = message.getProperties().get(ARGS_KEY.value()); + if (requestObject instanceof List) { + return (List) requestObject; + } else { + throw new KuraException(KuraErrorCode.BAD_REQUEST); + } + } + + private static final KuraMessage jsonResponse(final Object response) { + final KuraResponsePayload responsePayload = new KuraResponsePayload(200); + final Gson gson = new GsonBuilder().disableHtmlEscaping().create(); + responsePayload.setBody(gson.toJson(response).getBytes(StandardCharsets.UTF_8)); + return new KuraMessage(responsePayload); + } + + private ServiceReference[] getJsonUnmarshallers() { + String filterString = String.format("(&(kura.service.pid=%s))", + "org.eclipse.kura.json.marshaller.unmarshaller.provider"); + return ServiceUtil.getServiceReferences(this.bundleContext, Unmarshaller.class, filterString); + } + + private void ungetServiceReferences(final ServiceReference[] refs) { + ServiceUtil.ungetServiceReferences(this.bundleContext, refs); + } + + protected T unmarshal(String jsonString, Class clazz) { + T result = null; + ServiceReference[] unmarshallerSRs = getJsonUnmarshallers(); + try { + for (final ServiceReference unmarshallerSR : unmarshallerSRs) { + Unmarshaller unmarshaller = this.bundleContext.getService(unmarshallerSR); + result = unmarshaller.unmarshal(jsonString, clazz); + if (result != null) { + break; + } + } + } catch (Exception e) { + logger.warn("Failed to marshal configuration."); + } finally { + ungetServiceReferences(unmarshallerSRs); + } + return result; + } + + protected void validateAs(final T arg, final Function ctor) throws KuraException { + final Validable validable = ctor.apply(arg); + + if (!validable.isValid()) { + throw new KuraException(KuraErrorCode.BAD_REQUEST); + } + } +} diff --git a/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/request/handler/KeystoreServiceRequestHandlerV1.java b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/request/handler/KeystoreServiceRequestHandlerV1.java index 5365bb5749e..73ae959c5a4 100644 --- a/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/request/handler/KeystoreServiceRequestHandlerV1.java +++ b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/request/handler/KeystoreServiceRequestHandlerV1.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2021 Eurotech and/or its affiliates and others + * Copyright (c) 2023 Eurotech and/or its affiliates and others * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -13,223 +13,10 @@ *******************************************************************************/ package org.eclipse.kura.core.keystore.request.handler; -import static java.util.Objects.isNull; -import static org.eclipse.kura.cloudconnection.request.RequestHandlerMessageConstants.ARGS_KEY; +public class KeystoreServiceRequestHandlerV1 extends KeystoreServiceRequestHandler { -import java.nio.charset.StandardCharsets; -import java.util.List; - -import org.eclipse.kura.KuraErrorCode; -import org.eclipse.kura.KuraException; -import org.eclipse.kura.cloudconnection.message.KuraMessage; -import org.eclipse.kura.cloudconnection.request.RequestHandler; -import org.eclipse.kura.cloudconnection.request.RequestHandlerContext; -import org.eclipse.kura.cloudconnection.request.RequestHandlerRegistry; -import org.eclipse.kura.core.keystore.util.CertificateInfo; -import org.eclipse.kura.core.keystore.util.CsrInfo; -import org.eclipse.kura.core.keystore.util.EntryInfo; -import org.eclipse.kura.core.keystore.util.KeyPairInfo; -import org.eclipse.kura.core.keystore.util.KeystoreRemoteService; -import org.eclipse.kura.marshalling.Unmarshaller; -import org.eclipse.kura.message.KuraPayload; -import org.eclipse.kura.message.KuraResponsePayload; -import org.eclipse.kura.util.service.ServiceUtil; -import org.osgi.framework.ServiceReference; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - -public class KeystoreServiceRequestHandlerV1 extends KeystoreRemoteService implements RequestHandler { - - private static final Logger logger = LoggerFactory.getLogger(KeystoreServiceRequestHandlerV1.class); - public static final String APP_ID = "KEYS-V1"; - - private static final String NONE_RESOURCE_FOUND_MESSAGE = "Resource not found"; - private static final String KEYSTORES = "keystores"; - private static final String ENTRIES = "entries"; - private static final String ENTRY = "entry"; - private static final String CSR = "csr"; - private static final String CERTIFICATE = "certificate"; - private static final String KEYPAIR = "keypair"; - - // ---------------------------------------------------------------- - // - // Dependencies - // - // ---------------------------------------------------------------- - - public void setRequestHandlerRegistry(RequestHandlerRegistry requestHandlerRegistry) { - try { - requestHandlerRegistry.registerRequestHandler(APP_ID, this); - } catch (KuraException e) { - logger.info("Unable to register cloudlet {} in {}", APP_ID, requestHandlerRegistry.getClass().getName()); - } - } - - public void unsetRequestHandlerRegistry(RequestHandlerRegistry requestHandlerRegistry) { - try { - requestHandlerRegistry.unregister(APP_ID); - } catch (KuraException e) { - logger.info("Unable to register cloudlet {} in {}", APP_ID, requestHandlerRegistry.getClass().getName()); - } + public KeystoreServiceRequestHandlerV1() { + super("KEYS-V1"); } - // ---------------------------------------------------------------- - // - // Public methods - // - // ---------------------------------------------------------------- - - @Override - public KuraMessage doGet(final RequestHandlerContext context, final KuraMessage reqMessage) throws KuraException { - final List resourcePath = extractResourcePath(reqMessage); - KuraPayload reqPayload = reqMessage.getPayload(); - - if (resourcePath.isEmpty()) { - logger.error(NONE_RESOURCE_FOUND_MESSAGE); - throw new KuraException(KuraErrorCode.BAD_REQUEST); - } - - if (resourcePath.size() == 1 && resourcePath.get(0).equals(KEYSTORES)) { - return jsonResponse(listKeystoresInternal()); - } else if (resourcePath.size() == 2 && resourcePath.get(0).equals(KEYSTORES) - && resourcePath.get(1).equals(ENTRIES)) { - return doGetEntries(reqPayload); - } else if (resourcePath.size() == 3 && resourcePath.get(0).equals(KEYSTORES) - && resourcePath.get(1).equals(ENTRIES) && resourcePath.get(2).equals(ENTRY)) { - EntryInfo request = unmarshal(new String(reqPayload.getBody(), StandardCharsets.UTF_8), EntryInfo.class); - String keystoreServicePid = request.getKeystoreServicePid(); - String keyAlias = request.getAlias(); - if (!isNull(keystoreServicePid) && !isNull(keyAlias)) { - return jsonResponse(getKeyInternal(keystoreServicePid, keyAlias)); - } else { - throw new KuraException(KuraErrorCode.BAD_REQUEST); - } - } else { - throw new KuraException(KuraErrorCode.BAD_REQUEST); - } - } - - private KuraMessage doGetEntries(KuraPayload reqPayload) { - byte[] body = reqPayload.getBody(); - if (isNull(body) || body.length == 0) { - return jsonResponse(getKeysInternal()); - } else { - EntryInfo request = unmarshal(new String(reqPayload.getBody(), StandardCharsets.UTF_8), EntryInfo.class); - String keystoreServicePid = request.getKeystoreServicePid(); - String keyAlias = request.getAlias(); - if (isNull(keystoreServicePid) && !isNull(keyAlias)) { - return jsonResponse(getKeysByAliasInternal(keyAlias)); - } else { - return jsonResponse(getKeysByPidInternal(keystoreServicePid)); - } - } - } - - @Override - public KuraMessage doPost(RequestHandlerContext context, KuraMessage reqMessage) throws KuraException { - final List resourcePath = extractResourcePath(reqMessage); - KuraPayload reqPayload = reqMessage.getPayload(); - - if (resourcePath.size() != 3 || reqPayload.getBody() == null || reqPayload.getBody().length == 0 - || !resourcePath.get(0).equals(KEYSTORES) || !resourcePath.get(1).equals(ENTRIES)) { - logger.error(NONE_RESOURCE_FOUND_MESSAGE); - throw new KuraException(KuraErrorCode.BAD_REQUEST); - } - - if (resourcePath.get(2).equals(CSR)) { - return doPostCsr(reqPayload); - } else if (resourcePath.get(2).equals(CERTIFICATE)) { - String body = new String(reqPayload.getBody(), StandardCharsets.UTF_8); - CertificateInfo certificateInfo = unmarshal(body, CertificateInfo.class); - storeTrustedCertificateEntryInternal(certificateInfo); - return new KuraMessage(new KuraResponsePayload(200)); - } else if (resourcePath.get(2).equals(KEYPAIR)) { - String body = new String(reqPayload.getBody(), StandardCharsets.UTF_8); - KeyPairInfo keyPairInfo = unmarshal(body, KeyPairInfo.class); - storeKeyPairEntryInternal(keyPairInfo); - return new KuraMessage(new KuraResponsePayload(200)); - } else { - throw new KuraException(KuraErrorCode.BAD_REQUEST); - } - } - - private KuraMessage doPostCsr(KuraPayload reqPayload) { - String body = new String(reqPayload.getBody(), StandardCharsets.UTF_8); - CsrInfo csrInfo = unmarshal(body, CsrInfo.class); - return jsonResponse(getCSRInternal(csrInfo)); - } - - @Override - public KuraMessage doDel(RequestHandlerContext context, KuraMessage reqMessage) throws KuraException { - final List resourcePath = extractResourcePath(reqMessage); - KuraPayload reqPayload = reqMessage.getPayload(); - - if (resourcePath.size() != 2 || reqPayload.getBody() == null || reqPayload.getBody().length == 0 - || !resourcePath.get(0).equals(KEYSTORES)) { - logger.error(NONE_RESOURCE_FOUND_MESSAGE); - throw new KuraException(KuraErrorCode.BAD_REQUEST); - } - - if (resourcePath.get(1).equals(ENTRIES)) { - String body = new String(reqPayload.getBody(), StandardCharsets.UTF_8); - EntryInfo request = unmarshal(body, EntryInfo.class); - if (request != null) { - deleteKeyEntryInternal(request.getKeystoreServicePid(), request.getAlias()); - return new KuraMessage(new KuraResponsePayload(200)); - } else { - throw new KuraException(KuraErrorCode.BAD_REQUEST); - } - } else { - throw new KuraException(KuraErrorCode.BAD_REQUEST); - } - } - - @SuppressWarnings("unchecked") - private static final List extractResourcePath(final KuraMessage message) throws KuraException { - Object requestObject = message.getProperties().get(ARGS_KEY.value()); - if (requestObject instanceof List) { - return (List) requestObject; - } else { - throw new KuraException(KuraErrorCode.BAD_REQUEST); - } - } - - private static final KuraMessage jsonResponse(final Object response) { - final KuraResponsePayload responsePayload = new KuraResponsePayload(200); - final Gson gson = new GsonBuilder().disableHtmlEscaping().create(); - responsePayload.setBody(gson.toJson(response).getBytes(StandardCharsets.UTF_8)); - return new KuraMessage(responsePayload); - } - - private ServiceReference[] getJsonUnmarshallers() { - String filterString = String.format("(&(kura.service.pid=%s))", - "org.eclipse.kura.json.marshaller.unmarshaller.provider"); - return ServiceUtil.getServiceReferences(this.bundleContext, Unmarshaller.class, filterString); - } - - private void ungetServiceReferences(final ServiceReference[] refs) { - ServiceUtil.ungetServiceReferences(this.bundleContext, refs); - } - - protected T unmarshal(String jsonString, Class clazz) { - T result = null; - ServiceReference[] unmarshallerSRs = getJsonUnmarshallers(); - try { - for (final ServiceReference unmarshallerSR : unmarshallerSRs) { - Unmarshaller unmarshaller = this.bundleContext.getService(unmarshallerSR); - result = unmarshaller.unmarshal(jsonString, clazz); - if (result != null) { - break; - } - } - } catch (Exception e) { - logger.warn("Failed to marshal configuration."); - } finally { - ungetServiceReferences(unmarshallerSRs); - } - return result; - } } diff --git a/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/request/handler/KeystoreServiceRequestHandlerV2.java b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/request/handler/KeystoreServiceRequestHandlerV2.java new file mode 100644 index 00000000000..66202a7096c --- /dev/null +++ b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/request/handler/KeystoreServiceRequestHandlerV2.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2023 Eurotech and/or its affiliates and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Eurotech + *******************************************************************************/ +package org.eclipse.kura.core.keystore.request.handler; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.kura.KuraException; +import org.eclipse.kura.cloudconnection.message.KuraMessage; +import org.eclipse.kura.cloudconnection.request.RequestHandlerContext; +import org.eclipse.kura.core.keystore.request.PrivateKeyWriteRequest; +import org.eclipse.kura.core.keystore.util.PrivateKeyInfo; +import org.eclipse.kura.message.KuraPayload; +import org.eclipse.kura.message.KuraResponsePayload; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class KeystoreServiceRequestHandlerV2 extends KeystoreServiceRequestHandler { + + private static final Logger logger = LoggerFactory.getLogger(KeystoreServiceRequestHandlerV2.class); + + protected static final String PRIVATEKEY = "privatekey"; + + private static final List PRIVATE_KEY_RESOURCE = Arrays.asList(KEYSTORES, ENTRIES, PRIVATEKEY); + + public KeystoreServiceRequestHandlerV2() { + super("KEYS-V2"); + } + + @Override + public KuraMessage doPost(RequestHandlerContext context, KuraMessage reqMessage) throws KuraException { + final List resourcePath = extractResourcePath(reqMessage); + + if (!PRIVATE_KEY_RESOURCE.equals(resourcePath)) { + return super.doPost(context, reqMessage); + } + + final KuraPayload reqPayload = reqMessage.getPayload(); + + final String body = new String(reqPayload.getBody(), StandardCharsets.UTF_8); + final PrivateKeyInfo privateKeyInfo = unmarshal(body, PrivateKeyInfo.class); + validateAs(privateKeyInfo, PrivateKeyWriteRequest::new); + try { + storePrivateKeyEntryInternal(privateKeyInfo); + } catch (final Exception e) { + logger.warn("Failed to store private key entry", e); + return new KuraMessage(new KuraResponsePayload(500)); + } + return new KuraMessage(new KuraResponsePayload(200)); + + } + +} diff --git a/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/rest/provider/CsrReadRequest.java b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/rest/provider/CsrReadRequest.java deleted file mode 100644 index f45fffd18e4..00000000000 --- a/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/rest/provider/CsrReadRequest.java +++ /dev/null @@ -1,52 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2021 Eurotech and/or its affiliates and others - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Eurotech - *******************************************************************************/ -package org.eclipse.kura.core.keystore.rest.provider; - -import org.eclipse.kura.rest.utils.Validable; - -public class CsrReadRequest implements Validable { - - private String keystoreServicePid; - private String alias; - private String signatureAlgorithm; - private String attributes; - - public String getKeystoreServicePid() { - return this.keystoreServicePid; - } - - public String getAlias() { - return this.alias; - } - - public String getSignatureAlgorithm() { - return this.signatureAlgorithm; - } - - public String getAttributes() { - return this.attributes; - } - - @Override - public String toString() { - return "ReadRequest [keystoreServicePid=" + this.keystoreServicePid + ", alias=" + this.alias + ", algorithm=" - + this.signatureAlgorithm + ", attributes=" + this.attributes + "]"; - } - - @Override - public boolean isValid() { - return this.keystoreServicePid != null && this.alias != null && this.signatureAlgorithm != null - && this.attributes != null; - } - -} diff --git a/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/rest/provider/DeleteRequest.java b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/rest/provider/DeleteRequest.java deleted file mode 100644 index ba0cecebfd5..00000000000 --- a/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/rest/provider/DeleteRequest.java +++ /dev/null @@ -1,40 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2021 Eurotech and/or its affiliates and others - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Eurotech - *******************************************************************************/ -package org.eclipse.kura.core.keystore.rest.provider; - -import org.eclipse.kura.rest.utils.Validable; - -public class DeleteRequest implements Validable { - - private String keystoreServicePid; - private String alias; - - public String getKeystoreServicePid() { - return this.keystoreServicePid; - } - - public String getAlias() { - return this.alias; - } - - @Override - public String toString() { - return "DeleteRequest [keystoreServicePid=" + this.keystoreServicePid + ", alias=" + this.alias + "]"; - } - - @Override - public boolean isValid() { - return this.keystoreServicePid != null && this.alias != null; - } - -} diff --git a/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/rest/provider/KeystoreRestService.java b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/rest/provider/KeystoreRestService.java index b32939a488a..94df9dd9f97 100644 --- a/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/rest/provider/KeystoreRestService.java +++ b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/rest/provider/KeystoreRestService.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2021 Eurotech and/or its affiliates and others + * Copyright (c) 2021, 2023 Eurotech and/or its affiliates and others * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -27,16 +27,19 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; +import org.eclipse.kura.core.keystore.request.CsrReadRequest; +import org.eclipse.kura.core.keystore.request.EntryRequest; +import org.eclipse.kura.core.keystore.request.KeyPairWriteRequest; +import org.eclipse.kura.core.keystore.request.TrustedCertificateWriteRequest; import org.eclipse.kura.core.keystore.util.EntryInfo; import org.eclipse.kura.core.keystore.util.KeystoreRemoteService; import org.eclipse.kura.security.keystore.KeystoreInfo; import org.osgi.service.useradmin.Role; import org.osgi.service.useradmin.UserAdmin; -@Path("/keystores/v1") public class KeystoreRestService extends KeystoreRemoteService { - private static final String BAD_REQUEST_MESSAGE = "Bad request, "; + protected static final String BAD_REQUEST_MESSAGE = "Bad request, "; private static final String BAD_WRITE_REQUEST_ERROR_MESSAGE = BAD_REQUEST_MESSAGE + "expected request format: {\"keystoreServicePid\": \"MyKeystoreName\", \"alias\": " @@ -114,7 +117,7 @@ public void storeKeypairEntry(KeyPairWriteRequest writeRequest) { @Path("/entries") @RolesAllowed("keystores") @Consumes(MediaType.APPLICATION_JSON) - public void deleteKeyEntry(DeleteRequest deleteRequest) { + public void deleteKeyEntry(EntryRequest deleteRequest) { validate(deleteRequest, BAD_DELETE_REQUEST_ERROR_MESSAGE); deleteKeyEntryInternal(deleteRequest.getKeystoreServicePid(), deleteRequest.getAlias()); } diff --git a/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/rest/provider/KeystoreRestServiceV1.java b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/rest/provider/KeystoreRestServiceV1.java new file mode 100644 index 00000000000..d9fa6deb257 --- /dev/null +++ b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/rest/provider/KeystoreRestServiceV1.java @@ -0,0 +1,20 @@ +/******************************************************************************* + * Copyright (c) 2023 Eurotech and/or its affiliates and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Eurotech + *******************************************************************************/ +package org.eclipse.kura.core.keystore.rest.provider; + +import javax.ws.rs.Path; + +@Path("/keystores/v1") +public class KeystoreRestServiceV1 extends KeystoreRestService { + +} diff --git a/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/rest/provider/KeystoreRestServiceV2.java b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/rest/provider/KeystoreRestServiceV2.java new file mode 100644 index 00000000000..ca7aca231cb --- /dev/null +++ b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/rest/provider/KeystoreRestServiceV2.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2023 Eurotech and/or its affiliates and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Eurotech + *******************************************************************************/ +package org.eclipse.kura.core.keystore.rest.provider; + +import static org.eclipse.kura.rest.utils.Validable.validate; + +import javax.annotation.security.RolesAllowed; +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.core.MediaType; + +import org.eclipse.kura.core.keystore.request.PrivateKeyWriteRequest; +import org.eclipse.kura.request.handler.jaxrs.DefaultExceptionHandler; + +@Path("/keystores/v2") +public class KeystoreRestServiceV2 extends KeystoreRestService { + + private static final String BAD_WRITE_REQUEST_ERROR_MESSAGE = BAD_REQUEST_MESSAGE + + "expected request format: {\"keystoreServicePid\": \"MyKeystoreName\", \"alias\": " + + "\"MyAlias\", \"certificateChain\": \"...\", \"privateKey\": \"...\"}"; + + @POST + @Path("/entries/privatekey") + @RolesAllowed("keystores") + @Consumes(MediaType.APPLICATION_JSON) + public void storeKeypairEntry(PrivateKeyWriteRequest writeRequest) { + validate(writeRequest, BAD_WRITE_REQUEST_ERROR_MESSAGE); + try { + storePrivateKeyEntryInternal(writeRequest); + } catch (final Exception e) { + throw DefaultExceptionHandler.toWebApplicationException(e); + } + } +} diff --git a/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/util/KeystoreRemoteService.java b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/util/KeystoreRemoteService.java index fe5bc95d405..f705635a026 100644 --- a/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/util/KeystoreRemoteService.java +++ b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/util/KeystoreRemoteService.java @@ -36,11 +36,14 @@ import java.security.interfaces.RSAPublicKey; import java.security.spec.ECParameterSpec; import java.util.ArrayList; +import java.util.Arrays; import java.util.Base64; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; import javax.security.auth.x500.X500Principal; import javax.ws.rs.WebApplicationException; @@ -48,8 +51,9 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.eclipse.kura.KuraErrorCode; import org.eclipse.kura.KuraException; -import org.eclipse.kura.core.keystore.rest.provider.CsrReadRequest; +import org.eclipse.kura.core.keystore.request.CsrReadRequest; import org.eclipse.kura.security.keystore.KeystoreInfo; import org.eclipse.kura.security.keystore.KeystoreService; import org.osgi.framework.BundleContext; @@ -285,6 +289,48 @@ protected void storeKeyPairEntryInternal(final KeyPairInfo writeRequest) { } } + protected void storePrivateKeyEntryInternal(final PrivateKeyInfo writeRequest) + throws KuraException, IOException, GeneralSecurityException { + + final KeystoreService targetKeystore = Optional + .ofNullable(this.keystoreServices.get(writeRequest.getKeystoreServicePid())) + .orElseThrow(() -> new KuraException(KuraErrorCode.NOT_FOUND, "KeystoreService not found")); + + if (writeRequest.getPrivateKey() == null) { + updatePrivateKeyEntryCertificateChain(targetKeystore, writeRequest); + } else { + createPrivateKeyEntry(targetKeystore, writeRequest); + } + } + + private void updatePrivateKeyEntryCertificateChain(final KeystoreService targetKeystore, + final PrivateKeyInfo writeRequest) throws KuraException, CertificateException { + final Entry targetEntry = Optional.ofNullable(targetKeystore.getEntry(writeRequest.getAlias())) + .orElseThrow(() -> new KuraException(KuraErrorCode.NOT_FOUND, "Entry not found")); + + if (!(targetEntry instanceof PrivateKeyEntry)) { + throw new KuraException(KuraErrorCode.BAD_REQUEST, "Target entry is not a PrivateKeyEntry"); + } + + final PrivateKeyEntry existingPrivateKeyEntry = (PrivateKeyEntry) targetEntry; + + final Certificate[] certificateChain = parsePublicCertificates( + Arrays.stream(writeRequest.getCertificateChain()).collect(Collectors.joining("\n"))); + + final PrivateKeyEntry result = new PrivateKeyEntry(existingPrivateKeyEntry.getPrivateKey(), certificateChain); + + targetKeystore.setEntry(writeRequest.getAlias(), result); + } + + private void createPrivateKeyEntry(final KeystoreService targetKeystore, final PrivateKeyInfo writeRequest) + throws IOException, GeneralSecurityException, KuraException { + final PrivateKeyEntry privateKeyEntry = createPrivateKey(writeRequest.getPrivateKey(), + Arrays.stream(writeRequest.getCertificateChain()).collect(Collectors.joining("\n"))); + + targetKeystore.setEntry(writeRequest.getAlias(), + privateKeyEntry); + } + protected void deleteKeyEntryInternal(String keystoreServicePid, String alias) { try { this.keystoreServices.get(keystoreServicePid).deleteEntry(alias); diff --git a/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/util/PrivateKeyInfo.java b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/util/PrivateKeyInfo.java index 7b5ceebd59f..431432df225 100644 --- a/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/util/PrivateKeyInfo.java +++ b/kura/org.eclipse.kura.core.keystore/src/main/java/org/eclipse/kura/core/keystore/util/PrivateKeyInfo.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2021 Eurotech and/or its affiliates and others + * Copyright (c) 2021, 2023 Eurotech and/or its affiliates and others * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -21,8 +21,8 @@ public class PrivateKeyInfo extends EntryInfo { private String[] certificateChain; private EntryType type = EntryType.PRIVATE_KEY; - public PrivateKeyInfo(String alias, String keystoreName) { - super(alias, keystoreName); + public PrivateKeyInfo(String keystoreServicePid, String alias) { + super(keystoreServicePid, alias); } public String getAlgorithm() { @@ -60,4 +60,5 @@ public void setCertificateChain(String[] certificateChain) { public EntryType getType() { return this.type; } + } diff --git a/kura/test/org.eclipse.kura.core.keystore.test/META-INF/MANIFEST.MF b/kura/test/org.eclipse.kura.core.keystore.test/META-INF/MANIFEST.MF index 1ecdf994637..c2aee8d6e93 100644 --- a/kura/test/org.eclipse.kura.core.keystore.test/META-INF/MANIFEST.MF +++ b/kura/test/org.eclipse.kura.core.keystore.test/META-INF/MANIFEST.MF @@ -14,7 +14,9 @@ Import-Package: javax.servlet;version="3.1.0", org.eclipse.kura.core.keystore.util;version="1.0.0", org.eclipse.kura.core.testutil;version="[1.0,2.0)", org.eclipse.kura.core.testutil.http;version="[1.0,2.0)", - org.eclipse.kura.core.testutil.pki;version="[1.0,2.0)", + org.eclipse.kura.core.testutil.pki;version="[1.1.0,2.0.0)", + org.eclipse.kura.core.testutil.requesthandler;version="1.2.0", + org.eclipse.kura.core.testutil.service;version="1.0.0", org.eclipse.kura.security.keystore;version="[1.0,2.0)", org.junit;version="[4.12.0,5.0.0)", org.junit.runners;version="[4.12.0,5.0.0)", @@ -23,3 +25,7 @@ Import-Package: javax.servlet;version="3.1.0", org.mockito.stubbing;version="[4.0.0,5.0.0)", org.slf4j;version="1.6.4" Fragment-Host: org.eclipse.kura.core.keystore;bundle-version="1.1.0" +Require-Bundle: org.eclipse.kura.http.server.manager;bundle-version="1.4.0", + org.junit, + org.eclipse.kura.broker.artemis.core;bundle-version="1.5.0", + org.eclipse.kura.broker.artemis.simple.mqtt;bundle-version="1.4.0" diff --git a/kura/test/org.eclipse.kura.core.keystore.test/src/main/java/org/eclipse/kura/core/keystore/test/KeystoreRequestHandlerV2Test.java b/kura/test/org.eclipse.kura.core.keystore.test/src/main/java/org/eclipse/kura/core/keystore/test/KeystoreRequestHandlerV2Test.java new file mode 100644 index 00000000000..871f1d25ed1 --- /dev/null +++ b/kura/test/org.eclipse.kura.core.keystore.test/src/main/java/org/eclipse/kura/core/keystore/test/KeystoreRequestHandlerV2Test.java @@ -0,0 +1,319 @@ +/******************************************************************************* + * Copyright (c) 2023 Eurotech and/or its affiliates and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Eurotech + *******************************************************************************/ +package org.eclipse.kura.core.keystore.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyPair; +import java.security.KeyStore.PrivateKeyEntry; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +import org.bouncycastle.asn1.x500.X500Name; +import org.eclipse.kura.KuraException; +import org.eclipse.kura.configuration.ConfigurationService; +import org.eclipse.kura.core.testutil.pki.TestCA; +import org.eclipse.kura.core.testutil.pki.TestCA.CertificateCreationOptions; +import org.eclipse.kura.core.testutil.requesthandler.AbstractRequestHandlerTest; +import org.eclipse.kura.core.testutil.requesthandler.MqttTransport; +import org.eclipse.kura.core.testutil.requesthandler.RestTransport; +import org.eclipse.kura.core.testutil.requesthandler.Transport; +import org.eclipse.kura.core.testutil.requesthandler.Transport.MethodSpec; +import org.eclipse.kura.core.testutil.service.ServiceUtil; +import org.eclipse.kura.security.keystore.KeystoreService; +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import com.eclipsesource.json.Json; +import com.eclipsesource.json.JsonArray; +import com.eclipsesource.json.JsonObject; + +@RunWith(Parameterized.class) +public class KeystoreRequestHandlerV2Test extends AbstractRequestHandlerTest { + + @Test + public void shouldUploadSimplePrivateKeyEntry() { + givenKeystoreService("bar"); + givenKeyPair("leaf"); + + whenKeyPairIsUploaded("bar", "testalias", true); + thenRequestSucceeds(); + thenKeystoreEntryEqualsCurrentKeyPair("bar", "testalias"); + } + + @Test + public void shouldUploadPrivateKeyEntryWithMultipleCertificatesInChain() { + givenKeystoreService("foo"); + givenKeyPair("ca", "leaf"); + + whenKeyPairIsUploaded("foo", "testalias", true); + thenRequestSucceeds(); + thenKeystoreEntryEqualsCurrentKeyPair("foo", "testalias"); + } + + @Test + public void shouldUpdateSimplePrivateKeyEntry() { + givenKeystoreService("bar"); + givenKeyPairInKeystore("bar", "testalias", "leaf"); + givenNewLeafCert("otherleaf"); + + whenKeyPairIsUploaded("bar", "testalias", false); + thenRequestSucceeds(); + thenKeystoreEntryEqualsCurrentKeyPair("bar", "testalias"); + } + + @Test + public void shouldUpdatePrivateKeyEntryWithMultipleCertificatesInChain() { + givenKeystoreService("foo"); + givenKeyPairInKeystore("foo", "testalias", "ca", "leaf"); + givenNewLeafCert("otherleaf"); + + whenKeyPairIsUploaded("foo", "testalias", false); + thenRequestSucceeds(); + thenKeystoreEntryEqualsCurrentKeyPair("foo", "testalias"); + } + + @Test + public void shouldRejectEmpryRequestObject() { + + whenRequestIsPerformed(new MethodSpec("POST"), privateKeyRespurceURI, "{}"); + thenResponseCodeIs(400); + } + + @Test + public void shouldRejectRequestObjectWithoutPid() { + + whenRequestIsPerformed(new MethodSpec("POST"), privateKeyRespurceURI, + "{\"alias\":\"foo\",\"privateKey\":\"bar\",\"certificateChain\":[\"foo\"]}"); + thenResponseCodeIs(400); + } + + @Test + public void shouldRejectRequestObjectWithoutAlias() { + + whenRequestIsPerformed(new MethodSpec("POST"), privateKeyRespurceURI, + "{\"keystoreServicePid\":\"foo\",\"privateKey\":\"bar\",\"certificateChain\":[\"foo\"]}"); + thenResponseCodeIs(400); + } + + @Test + public void shouldRejectRequestObjectWithoutCertificateChain() { + + whenRequestIsPerformed(new MethodSpec("POST"), privateKeyRespurceURI, + "{\"keystoreServicePid\":\"foo\",\"alias\":\"bar\",\"privateKey\":\"bar\"}"); + thenResponseCodeIs(400); + } + + @Parameters + public static Collection parameters() { + return Arrays.asList( + new Object[] { new RestTransport("keystores/v2"), "/entries/privatekey" }, + new Object[] { new MqttTransport("KEYS-V2"), "/keystores/entries/privatekey" }); + } + + public KeystoreRequestHandlerV2Test(final Transport transport, final String privateKeyRespurceURI) { + super(transport); + this.privateKeyRespurceURI = privateKeyRespurceURI; + } + + private final String privateKeyRespurceURI; + private final Map createdKeystoreServices = new HashMap<>(); + + private KeyPair leafKeyPair; + private String leafKeyPem; + private List certificateChainPem = new ArrayList<>(); + + private PrivateKey leafKey; + private List certificateChain = new ArrayList<>(); + + private TestCA testCA; + + private void givenKeystoreService(final String pid) { + try { + final Path dir = Files.createTempDirectory(null); + final String keystorePath = dir.toFile().getAbsolutePath() + "/" + System.currentTimeMillis() + ".ks"; + + final ConfigurationService configurationService = ServiceUtil + .trackService(ConfigurationService.class, Optional.empty()).get(30, TimeUnit.SECONDS); + + final KeystoreService keystoreService = ServiceUtil + .createFactoryConfiguration(configurationService, KeystoreService.class, pid, + "org.eclipse.kura.core.keystore.FilesystemKeystoreServiceImpl", + Collections.singletonMap("keystore.path", keystorePath)) + .get(30, TimeUnit.SECONDS); + + this.createdKeystoreServices.put(pid, keystoreService); + + } catch (final Exception e) { + fail("failed to create test keystore"); + } + } + + private void givenKeyPair(final String leafCN) { + givenKeyPair("foo", leafCN, false); + } + + private void givenKeyPairInKeystore(final String keystorePid, final String alias, final String leafCN) { + givenKeyPair(leafCN); + + storeCurrentKeyPair(keystorePid, alias); + } + + private void givenKeyPair(final String caCN, final String leafCN) { + givenKeyPair(caCN, leafCN, true); + } + + private void givenKeyPairInKeystore(final String keystorePid, final String alias, final String caCN, + final String leafCN) { + givenKeyPair(caCN, leafCN); + + storeCurrentKeyPair(keystorePid, alias); + } + + private void givenKeyPair(final String caCN, final String leafCN, final boolean includeCA) { + try { + this.testCA = new TestCA( + CertificateCreationOptions.builder(new X500Name("cn=" + caCN + ", dc=bar.com")).build()); + + this.leafKeyPair = TestCA.generateKeyPair(); + + this.leafKey = this.leafKeyPair.getPrivate(); + this.leafKeyPem = privateKeyToPEMString(this.leafKeyPair.getPrivate()); + + final X509Certificate leafCert = testCA.createAndSignCertificate( + CertificateCreationOptions.builder(new X500Name("cn=" + leafCN + ", dc=bar.com")).build(), + this.leafKeyPair); + + this.certificateChain.clear(); + this.certificateChainPem.clear(); + this.certificateChain.add(leafCert); + this.certificateChainPem.add(certificateToPEMString(leafCert)); + + if (includeCA) { + this.certificateChain.add(testCA.getCertificate()); + this.certificateChainPem.add(certificateToPEMString(testCA.getCertificate())); + } + + } catch (Exception e) { + fail("cannot cerate test certificate chain"); + } + } + + private void givenNewLeafCert(final String leafCN) { + try { + final X509Certificate leafCert = this.testCA.createAndSignCertificate( + CertificateCreationOptions.builder(new X500Name("cn=" + leafCN + ", dc=bar.com")).build(), + this.leafKeyPair); + + this.certificateChain.set(0, leafCert); + this.certificateChainPem.set(0, certificateToPEMString(leafCert)); + } catch (Exception e) { + fail("cannot cerate new certificate"); + } + } + + private void storeCurrentKeyPair(final String keystorePid, final String alias) { + try { + this.createdKeystoreServices.get(keystorePid).setEntry(alias, + new PrivateKeyEntry(this.leafKey, this.certificateChain.toArray(new X509Certificate[0]))); + } catch (final Exception e) { + fail("cannot store certificate chain"); + } + } + + private void whenKeyPairIsUploaded(final String keystorePid, final String alias, + final boolean includePrivateKey) { + final JsonObject object = Json.object(); + if (includePrivateKey) { + object.add("privateKey", this.leafKeyPem); + } + + final JsonArray chain = Json.array(); + for (final String cert : certificateChainPem) { + chain.add(cert); + } + + object.add("certificateChain", chain); + object.add("keystoreServicePid", keystorePid); + object.add("alias", alias); + + whenRequestIsPerformed(new MethodSpec("POST"), privateKeyRespurceURI, + object.toString()); + } + + private void thenKeystoreEntryEqualsCurrentKeyPair(final String pid, final String alias) { + try { + final PrivateKeyEntry privateKeyEntry = (PrivateKeyEntry) this.createdKeystoreServices.get(pid) + .getEntry(alias); + + assertEquals(this.leafKeyPem, privateKeyToPEMString(privateKeyEntry.getPrivateKey())); + + final List entryCertificatesAsPem = new ArrayList<>(); + + for (final Certificate cert : privateKeyEntry.getCertificateChain()) { + entryCertificatesAsPem.add(certificateToPEMString((X509Certificate) cert)); + } + + assertEquals(this.certificateChainPem, entryCertificatesAsPem); + } catch (KuraException | IOException e) { + fail("Unable to retrieve keystore entry"); + } + } + + @After + public void cleanupKeystoreServices() { + try { + final ConfigurationService configurationService = ServiceUtil + .trackService(ConfigurationService.class, Optional.empty()).get(30, TimeUnit.SECONDS); + + for (final String pid : createdKeystoreServices.keySet()) { + ServiceUtil.deleteFactoryConfiguration(configurationService, pid).get(30, TimeUnit.SECONDS); + } + } catch (Exception e) { + fail("failed to delete test keystore"); + } + } + + private String certificateToPEMString(final X509Certificate cert) throws IOException { + try (final ByteArrayOutputStream out = new ByteArrayOutputStream()) { + TestCA.encodeToPEM(cert, out); + return new String(out.toByteArray(), StandardCharsets.UTF_8); + } + } + + private String privateKeyToPEMString(final PrivateKey key) throws IOException { + try (final ByteArrayOutputStream out = new ByteArrayOutputStream()) { + TestCA.encodeToPEM(key, out); + return new String(out.toByteArray(), StandardCharsets.UTF_8); + } + } +} diff --git a/kura/test/org.eclipse.kura.core.keystore.test/src/test/java/org/eclipse/kura/core/keystore/rest/provider/test/KeystoreRestServiceTest.java b/kura/test/org.eclipse.kura.core.keystore.test/src/test/java/org/eclipse/kura/core/keystore/rest/provider/test/KeystoreRestServiceTest.java index aec25a9f22f..276488c801e 100644 --- a/kura/test/org.eclipse.kura.core.keystore.test/src/test/java/org/eclipse/kura/core/keystore/rest/provider/test/KeystoreRestServiceTest.java +++ b/kura/test/org.eclipse.kura.core.keystore.test/src/test/java/org/eclipse/kura/core/keystore/rest/provider/test/KeystoreRestServiceTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2021, 2022 Eurotech and/or its affiliates and others + * Copyright (c) 2021, 2023 Eurotech and/or its affiliates and others * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -16,7 +16,6 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -37,12 +36,12 @@ import java.util.Map; import org.eclipse.kura.KuraException; -import org.eclipse.kura.core.keystore.rest.provider.CsrReadRequest; +import org.eclipse.kura.core.keystore.request.CsrReadRequest; +import org.eclipse.kura.core.keystore.request.EntryRequest; +import org.eclipse.kura.core.keystore.request.KeyPairWriteRequest; +import org.eclipse.kura.core.keystore.request.TrustedCertificateWriteRequest; import org.eclipse.kura.core.keystore.rest.provider.CsrResponse; -import org.eclipse.kura.core.keystore.rest.provider.DeleteRequest; -import org.eclipse.kura.core.keystore.rest.provider.KeyPairWriteRequest; import org.eclipse.kura.core.keystore.rest.provider.KeystoreRestService; -import org.eclipse.kura.core.keystore.rest.provider.TrustedCertificateWriteRequest; import org.eclipse.kura.core.keystore.util.CertificateInfo; import org.eclipse.kura.core.keystore.util.EntryInfo; import org.eclipse.kura.core.keystore.util.EntryType; @@ -279,9 +278,7 @@ public void activate(ComponentContext componentContext) { }; krs.activate(null); - DeleteRequest deleteRequest = new DeleteRequest(); - TestUtil.setFieldValue(deleteRequest, "keystoreServicePid", "MyKeystore"); - TestUtil.setFieldValue(deleteRequest, "alias", "MyAlias"); + EntryRequest deleteRequest = new EntryRequest("MyKeystore", "MyAlias"); krs.deleteKeyEntry(deleteRequest); verify(ksMock).deleteEntry("MyAlias"); @@ -338,18 +335,16 @@ public void activate(ComponentContext componentContext) { krs.activate(null); KeyPairWriteRequest writeRequest = new KeyPairWriteRequest("MyKeystore", "MyAlias"); - TestUtil.setFieldValue(writeRequest, "algorithm", "RSA"); - TestUtil.setFieldValue(writeRequest, "signatureAlgorithm", "SHA256WithRSA"); - TestUtil.setFieldValue(writeRequest, "size", 1024); - TestUtil.setFieldValue(writeRequest, "attributes", "CN=Kura, OU=IoT, O=Eclipse, C=US"); + writeRequest.setAlgorithm("RSA"); + writeRequest.setSignatureAlgorithm("SHA256WithRSA"); + writeRequest.setSize(1024); + writeRequest.setAttributes("CN=Kura, OU=IoT, O=Eclipse, C=US"); krs.storeKeypairEntry(writeRequest); - CsrReadRequest readRequest = new CsrReadRequest(); - TestUtil.setFieldValue(readRequest, "keystoreServicePid", "MyKeystore"); - TestUtil.setFieldValue(readRequest, "alias", "MyAlias"); - TestUtil.setFieldValue(readRequest, "signatureAlgorithm", "SHA256WithRSA"); - TestUtil.setFieldValue(readRequest, "attributes", "CN=Kura, OU=IoT, O=Eclipse, C=US"); + CsrReadRequest readRequest = new CsrReadRequest("MyKeystore", "MyAlias"); + readRequest.setSignatureAlgorithm("SHA256WithRSA"); + readRequest.setAttributes("CN=Kura, OU=IoT, O=Eclipse, C=US"); CsrResponse csr = krs.getCSR(readRequest); assertNotNull(csr); diff --git a/kura/test/org.eclipse.kura.core.testutil/META-INF/MANIFEST.MF b/kura/test/org.eclipse.kura.core.testutil/META-INF/MANIFEST.MF index 6988fea1502..52086cd438c 100644 --- a/kura/test/org.eclipse.kura.core.testutil/META-INF/MANIFEST.MF +++ b/kura/test/org.eclipse.kura.core.testutil/META-INF/MANIFEST.MF @@ -49,6 +49,6 @@ Export-Package: org.eclipse.kura.core.testutil;version="1.0.0", org.eclipse.kura.core.testutil.event;version="1.0.0", org.eclipse.kura.core.testutil.http;version="1.0.0", org.eclipse.kura.core.testutil.json;version="1.0.0", - org.eclipse.kura.core.testutil.pki;version="1.0.0", + org.eclipse.kura.core.testutil.pki;version="1.1.0", org.eclipse.kura.core.testutil.requesthandler;version="1.2.0", org.eclipse.kura.core.testutil.service;version="1.0.0" diff --git a/kura/test/org.eclipse.kura.core.testutil/src/main/java/org/eclipse/kura/core/testutil/pki/TestCA.java b/kura/test/org.eclipse.kura.core.testutil/src/main/java/org/eclipse/kura/core/testutil/pki/TestCA.java index 416943c1bb9..f6e4338c2ac 100644 --- a/kura/test/org.eclipse.kura.core.testutil/src/main/java/org/eclipse/kura/core/testutil/pki/TestCA.java +++ b/kura/test/org.eclipse.kura.core.testutil/src/main/java/org/eclipse/kura/core/testutil/pki/TestCA.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2021 Eurotech and/or its affiliates and others + * Copyright (c) 2021, 2023 Eurotech and/or its affiliates and others * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -27,6 +27,7 @@ import java.security.KeyStore.PasswordProtection; import java.security.KeyStore.PrivateKeyEntry; import java.security.KeyStore.ProtectionParameter; +import java.security.PrivateKey; import java.security.Security; import java.security.cert.X509CRL; import java.security.cert.X509Certificate; @@ -231,6 +232,12 @@ public static void encodeToPEM(final X509CRL crl, final OutputStream out) throws } } + public static void encodeToPEM(final PrivateKey privateKey, final OutputStream out) throws IOException { + try (final JcaPEMWriter pw = new JcaPEMWriter(new OutputStreamWriter(out))) { + pw.writeObject(privateKey); + } + } + public static void writeKeystore(final File file, final KeyStore.Entry... entries) throws TestCAException, IOException { try {