From 2c342e61e9b21d6bac0ab5aaf9348f413ce91e2f Mon Sep 17 00:00:00 2001 From: "Jan N. Klug" Date: Sun, 3 Apr 2022 18:57:31 +0200 Subject: [PATCH 1/2] Allow adding Jetty server certificates via REST API Signed-off-by: Jan N. Klug --- .../bnd.bnd | 2 +- .../{internal => }/CertificateGenerator.java | 110 +++------- bundles/org.openhab.core.io.net/pom.xml | 10 + bundles/org.openhab.core.io.rest.core/pom.xml | 10 + .../core/internal/CertificateResource.java | 196 ++++++++++++++++++ .../internal/CertificateResourceTest.java | 136 ++++++++++++ .../src/test/resources/cert/chain.pem | 50 +++++ .../src/test/resources/cert/root.key | 28 +++ .../src/test/resources/cert/root.pem | 21 ++ .../src/test/resources/cert/server.key | 28 +++ .../src/test/resources/cert/server.pem | 29 +++ .../openhab-core/src/main/feature/feature.xml | 1 + .../itest.bndrun | 115 ++++++++++ 13 files changed, 657 insertions(+), 79 deletions(-) rename bundles/org.openhab.core.io.jetty.certificate/src/main/java/org/openhab/core/io/jetty/certificate/{internal => }/CertificateGenerator.java (52%) create mode 100644 bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/CertificateResource.java create mode 100644 bundles/org.openhab.core.io.rest.core/src/test/java/org/openhab/core/io/rest/core/internal/CertificateResourceTest.java create mode 100644 bundles/org.openhab.core.io.rest.core/src/test/resources/cert/chain.pem create mode 100644 bundles/org.openhab.core.io.rest.core/src/test/resources/cert/root.key create mode 100644 bundles/org.openhab.core.io.rest.core/src/test/resources/cert/root.pem create mode 100644 bundles/org.openhab.core.io.rest.core/src/test/resources/cert/server.key create mode 100644 bundles/org.openhab.core.io.rest.core/src/test/resources/cert/server.pem create mode 100644 itests/org.openhab.core.model.core.tests/itest.bndrun diff --git a/bundles/org.openhab.core.io.jetty.certificate/bnd.bnd b/bundles/org.openhab.core.io.jetty.certificate/bnd.bnd index a9c6022bf5f..f3813048b50 100644 --- a/bundles/org.openhab.core.io.jetty.certificate/bnd.bnd +++ b/bundles/org.openhab.core.io.jetty.certificate/bnd.bnd @@ -1,3 +1,3 @@ Bundle-SymbolicName: ${project.artifactId} -Bundle-Activator: org.openhab.core.io.jetty.certificate.internal.CertificateGenerator +Bundle-Activator: org.openhab.core.io.jetty.certificate.CertificateGenerator Import-Package: !org.bouncycastle.*,* diff --git a/bundles/org.openhab.core.io.jetty.certificate/src/main/java/org/openhab/core/io/jetty/certificate/internal/CertificateGenerator.java b/bundles/org.openhab.core.io.jetty.certificate/src/main/java/org/openhab/core/io/jetty/certificate/CertificateGenerator.java similarity index 52% rename from bundles/org.openhab.core.io.jetty.certificate/src/main/java/org/openhab/core/io/jetty/certificate/internal/CertificateGenerator.java rename to bundles/org.openhab.core.io.jetty.certificate/src/main/java/org/openhab/core/io/jetty/certificate/CertificateGenerator.java index 7ed9834565e..54c4293c73d 100644 --- a/bundles/org.openhab.core.io.jetty.certificate/src/main/java/org/openhab/core/io/jetty/certificate/internal/CertificateGenerator.java +++ b/bundles/org.openhab.core.io.jetty.certificate/src/main/java/org/openhab/core/io/jetty/certificate/CertificateGenerator.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.io.jetty.certificate.internal; +package org.openhab.core.io.jetty.certificate; import java.io.ByteArrayInputStream; import java.io.File; @@ -20,38 +20,20 @@ import java.io.InputStream; import java.math.BigInteger; import java.nio.ByteBuffer; -import java.security.InvalidAlgorithmParameterException; -import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; import java.security.SecureRandom; import java.security.cert.Certificate; import java.security.cert.CertificateException; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; -import java.security.spec.ECField; -import java.security.spec.ECFieldFp; -import java.security.spec.ECParameterSpec; -import java.security.spec.ECPoint; -import java.security.spec.ECPrivateKeySpec; -import java.security.spec.ECPublicKeySpec; -import java.security.spec.EllipticCurve; -import java.security.spec.InvalidKeySpecException; import java.util.Date; -import java.util.Random; -import org.bouncycastle.asn1.ASN1Primitive; -import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.X509v3CertificateBuilder; -import org.bouncycastle.jce.ECNamedCurveTable; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; @@ -66,35 +48,35 @@ */ public class CertificateGenerator implements BundleActivator { - private static final String JETTY_KEYSTORE_PATH_PROPERTY = "jetty.keystore.path"; - private static final String KEYSTORE_PASSWORD = "openhab"; - private static final String KEYSTORE_ENTRY_ALIAS = "mykey"; - private static final String KEYSTORE_JKS_TYPE = "JKS"; - private static final String CURVE_NAME = "prime256v1"; + public static final String JETTY_KEYSTORE_PATH_PROPERTY = "jetty.keystore.path"; + public static final String KEYSTORE_PASSWORD = "openhab"; + public static final String KEYSTORE_ENTRY_ALIAS = "mykey"; + public static final String KEYSTORE_JKS_TYPE = "JKS"; private static final String KEY_PAIR_GENERATOR_TYPE = "EC"; - private static final String KEY_FACTORY_TYPE = "EC"; private static final String CONTENT_SIGNER_ALGORITHM = "SHA256withECDSA"; private static final String CERTIFICATE_X509_TYPE = "X.509"; - private static final String X500_NAME = "CN=openhab.org, OU=None, O=None, L=None, C=None"; + public static final String X500_NAME = "CN=openhab.org, OU=None, O=None, L=None, C=None"; - private Logger logger; - - private File keystoreFile; + private static final Logger LOGGER = LoggerFactory.getLogger(CertificateGenerator.class); @Override public void start(BundleContext context) throws Exception { - logger = LoggerFactory.getLogger(CertificateGenerator.class); + try { - KeyStore keystore = ensureKeystore(); + String keystorePath = System.getProperty(JETTY_KEYSTORE_PATH_PROPERTY); + File keystoreFile = new File(keystorePath); + KeyStore keystore = ensureKeystore(keystoreFile); if (!isCertificateInKeystore(keystore)) { - logger.debug("{} alias not found. Generating a new certificate.", KEYSTORE_ENTRY_ALIAS); + LOGGER.debug("{} alias not found. Generating a new certificate.", KEYSTORE_ENTRY_ALIAS); generateCertificate(keystore); + LOGGER.debug("Save the keystore into {}.", keystoreFile.getAbsolutePath()); + keystore.store(new FileOutputStream(keystoreFile), KEYSTORE_PASSWORD.toCharArray()); } else { - logger.debug("{} alias found. Do nothing.", KEYSTORE_ENTRY_ALIAS); + LOGGER.debug("{} alias found. Do nothing.", KEYSTORE_ENTRY_ALIAS); } } catch (CertificateException | KeyStoreException e) { - logger.error("Failed to generate a new SSL Certificate.", e); + LOGGER.error("Failed to generate a new SSL Certificate.", e); } } @@ -108,13 +90,11 @@ public void stop(BundleContext context) throws Exception { * * @throws KeyStoreException if the creation of the keystore fails or if it is not readable. */ - private KeyStore ensureKeystore() throws KeyStoreException { - String keystorePath = System.getProperty(JETTY_KEYSTORE_PATH_PROPERTY); - keystoreFile = new File(keystorePath); + private KeyStore ensureKeystore(File keystoreFile) throws KeyStoreException { KeyStore keyStore = KeyStore.getInstance(KEYSTORE_JKS_TYPE); if (!keystoreFile.exists()) { try { - logger.debug("No keystore found. Creation of {}", keystoreFile.getAbsolutePath()); + LOGGER.debug("No keystore found. Creation of {}", keystoreFile.getAbsolutePath()); boolean newFileCreated = keystoreFile.createNewFile(); if (newFileCreated) { keyStore.load(null, null); @@ -126,7 +106,7 @@ private KeyStore ensureKeystore() throws KeyStoreException { } } else { try (InputStream keystoreStream = new FileInputStream(keystoreFile)) { - logger.debug("Keystore found. Trying to load {}", keystoreFile.getAbsolutePath()); + LOGGER.debug("Keystore found. Trying to load {}", keystoreFile.getAbsolutePath()); keyStore.load(keystoreStream, KEYSTORE_PASSWORD.toCharArray()); } catch (NoSuchAlgorithmException | CertificateException | IOException e) { throw new KeyStoreException("Failed to load the keystore " + keystoreFile.getAbsolutePath(), e); @@ -139,7 +119,7 @@ private KeyStore ensureKeystore() throws KeyStoreException { /** * Check if the keystore contains a certificate with the KEYSTORE_ENTRY_ALIAS. * - * @param keystore + * @param keystore the keystore to check * @return true if the alias is already present in the keystore, else false. * @throws KeyStoreException If the keystore cannot be read. */ @@ -150,62 +130,36 @@ private boolean isCertificateInKeystore(KeyStore keystore) throws KeyStoreExcept /** * Generate a new certificate and store it in the given keystore. * - * @param keystore + * @param keystore the keystore to add this certificate to * @throws CertificateException if the certificate generation has failed. * @throws KeyStoreException If save of the keystore has failed. */ - private void generateCertificate(KeyStore keystore) throws CertificateException, KeyStoreException { + public static void generateCertificate(KeyStore keystore) throws CertificateException, KeyStoreException { try { long startTime = System.currentTimeMillis(); - org.bouncycastle.jce.spec.ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec(CURVE_NAME); - ECField field = new ECFieldFp(ecSpec.getCurve().getField().getCharacteristic()); - EllipticCurve curve = new EllipticCurve(field, ecSpec.getCurve().getA().toBigInteger(), - ecSpec.getCurve().getB().toBigInteger()); - ECPoint pointG = new ECPoint(ecSpec.getG().getXCoord().toBigInteger(), - ecSpec.getG().getYCoord().toBigInteger()); - ECParameterSpec spec = new ECParameterSpec(curve, pointG, ecSpec.getN(), ecSpec.getH().intValue()); - KeyPairGenerator g = KeyPairGenerator.getInstance(KEY_PAIR_GENERATOR_TYPE); - g.initialize(spec, new SecureRandom()); - KeyPair keysPair = g.generateKeyPair(); - - ECPrivateKeySpec ecPrivSpec = new ECPrivateKeySpec(((ECPrivateKey) keysPair.getPrivate()).getS(), spec); - ECPublicKeySpec ecPublicSpec = new ECPublicKeySpec(((ECPublicKey) keysPair.getPublic()).getW(), spec); - KeyFactory kf = KeyFactory.getInstance(KEY_FACTORY_TYPE); - PrivateKey privateKey = kf.generatePrivate(ecPrivSpec); - PublicKey publicKey = kf.generatePublic(ecPublicSpec); - - logger.debug("Keys generated in {} ms.", (System.currentTimeMillis() - startTime)); + KeyPair keyPair = KeyPairGenerator.getInstance(KEY_PAIR_GENERATOR_TYPE).generateKeyPair(); + LOGGER.debug("Keys generated in {} ms.", (System.currentTimeMillis() - startTime)); X500Name issuerDN = new X500Name(X500_NAME); - Integer randomNumber = new Random().nextInt(); - BigInteger serialNumber = BigInteger.valueOf(randomNumber >= 0 ? randomNumber : randomNumber * -1); + SubjectPublicKeyInfo pubKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()); Date notBefore = new Date(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30); Date notAfter = new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 365 * 10)); - X500Name subjectDN = new X500Name(X500_NAME); - byte[] publickeyb = publicKey.getEncoded(); - ASN1Sequence sequence = (ASN1Sequence) ASN1Primitive.fromByteArray(publickeyb); - SubjectPublicKeyInfo subPubKeyInfo = new SubjectPublicKeyInfo(sequence); - X509v3CertificateBuilder v3CertGen = new X509v3CertificateBuilder(issuerDN, serialNumber, notBefore, - notAfter, subjectDN, subPubKeyInfo); + X509v3CertificateBuilder certificateBuilder = new X509v3CertificateBuilder(issuerDN, + new BigInteger(10, new SecureRandom()), notBefore, notAfter, issuerDN, pubKeyInfo); ContentSigner contentSigner = new JcaContentSignerBuilder(CONTENT_SIGNER_ALGORITHM) - .build(keysPair.getPrivate()); - X509CertificateHolder certificateHolder = v3CertGen.build(contentSigner); + .build(keyPair.getPrivate()); + X509CertificateHolder certificateHolder = certificateBuilder.build(contentSigner); Certificate certificate = java.security.cert.CertificateFactory.getInstance(CERTIFICATE_X509_TYPE) .generateCertificate(new ByteArrayInputStream( ByteBuffer.wrap(certificateHolder.toASN1Structure().getEncoded()).array())); - logger.debug("Total certificate generation time: {} ms.", (System.currentTimeMillis() - startTime)); + LOGGER.debug("Total certificate generation time: {} ms.", (System.currentTimeMillis() - startTime)); - keystore.setKeyEntry(KEYSTORE_ENTRY_ALIAS, privateKey, KEYSTORE_PASSWORD.toCharArray(), + keystore.setKeyEntry(KEYSTORE_ENTRY_ALIAS, keyPair.getPrivate(), KEYSTORE_PASSWORD.toCharArray(), new java.security.cert.Certificate[] { certificate }); - - logger.debug("Save the keystore into {}.", keystoreFile.getAbsolutePath()); - - keystore.store(new FileOutputStream(keystoreFile), KEYSTORE_PASSWORD.toCharArray()); - } catch (NoSuchAlgorithmException | InvalidKeySpecException | IOException | OperatorCreationException - | InvalidAlgorithmParameterException e) { + } catch (NoSuchAlgorithmException | IOException | OperatorCreationException e) { throw new CertificateException("Failed to generate the new certificate.", e); } } diff --git a/bundles/org.openhab.core.io.net/pom.xml b/bundles/org.openhab.core.io.net/pom.xml index d86d0b89559..5166e136b19 100644 --- a/bundles/org.openhab.core.io.net/pom.xml +++ b/bundles/org.openhab.core.io.net/pom.xml @@ -20,6 +20,16 @@ org.openhab.core ${project.version} + + org.openhab.core.bundles + org.openhab.core.config.core + ${project.version} + + + org.openhab.core.bundles + org.openhab.core.io.jetty.certificate + ${project.version} + diff --git a/bundles/org.openhab.core.io.rest.core/pom.xml b/bundles/org.openhab.core.io.rest.core/pom.xml index 297f9dbe313..3b6fa2bd586 100644 --- a/bundles/org.openhab.core.io.rest.core/pom.xml +++ b/bundles/org.openhab.core.io.rest.core/pom.xml @@ -45,6 +45,16 @@ org.openhab.core.semantics ${project.version} + + org.openhab.core.bundles + org.openhab.core.io.jetty.certificate + ${project.version} + + + org.openhab.core.bundles + org.openhab.core.test + ${project.version} + org.openhab.core.bundles org.openhab.core.test diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/CertificateResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/CertificateResource.java new file mode 100644 index 00000000000..650f34b3b03 --- /dev/null +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/CertificateResource.java @@ -0,0 +1,196 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.rest.core.internal; + +import static org.openhab.core.io.jetty.certificate.CertificateGenerator.KEYSTORE_ENTRY_ALIAS; +import static org.openhab.core.io.jetty.certificate.CertificateGenerator.KEYSTORE_JKS_TYPE; +import static org.openhab.core.io.jetty.certificate.CertificateGenerator.KEYSTORE_PASSWORD; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.security.KeyFactory; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + +import javax.annotation.security.RolesAllowed; +import javax.ws.rs.Consumes; +import javax.ws.rs.PUT; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.auth.Role; +import org.openhab.core.io.jetty.certificate.CertificateGenerator; +import org.openhab.core.io.rest.RESTConstants; +import org.openhab.core.io.rest.RESTResource; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants; +import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired; +import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect; +import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsName; +import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * The {@link CertificateResource} allows changing the Jetty server certificate + * + * @author Jan N. Klug - Initial contribution + */ +@Component +@JaxrsResource +@JaxrsName(CertificateResource.PATH_CERTIFICATE) +@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")") +@JSONRequired +@javax.ws.rs.Path(CertificateResource.PATH_CERTIFICATE) +@RolesAllowed({ Role.ADMIN }) +@SecurityRequirement(name = "oauth2", scopes = { "admin" }) +@Tag(name = CertificateResource.PATH_CERTIFICATE) +@NonNullByDefault +public class CertificateResource implements RESTResource { + static final String PATH_CERTIFICATE = "certificate"; + static final String JETTY_KEYSTORE_PATH_PROPERTY = "jetty.keystore.path"; + + private final Logger logger = LoggerFactory.getLogger(CertificateResource.class); + + @PUT + @Consumes(MediaType.APPLICATION_JSON) + @Operation(operationId = "updateCertificate", summary = "Updates the Jetty server certificate.", responses = { + @ApiResponse(responseCode = "202", description = "Accepted"), + @ApiResponse(responseCode = "400", description = "Bad Request") }) + public Response putCertificate( + @Parameter(description = "a certificate chain with private key", required = true) CertificateDTO value) { + try { + if ((value.certificateChain == null || value.certificateChain.isBlank()) + && (value.privateKey == null || value.privateKey.isBlank())) { + renewKeyStoreSelfSigned(); + return Response.status(Response.Status.ACCEPTED.getStatusCode(), "Self-Signed Certificate set.") + .build(); + } else { + renewKeyStoreFromDTO(value); + return Response.status(Response.Status.ACCEPTED.getStatusCode(), "Sent Certificate set.").build(); + } + } catch (CertificateException | IOException | NoSuchAlgorithmException | InvalidKeySpecException e) { + return Response.status(Response.Status.BAD_REQUEST.getStatusCode(), e.getMessage()).build(); + } + } + + public static class CertificateDTO { + public @Nullable String certificateChain; + public @Nullable String privateKey; + } + + void renewKeyStoreSelfSigned() { + try { + KeyStore keyStore = KeyStore.getInstance(KEYSTORE_JKS_TYPE); + keyStore.load(null, null); + CertificateGenerator.generateCertificate(keyStore); + // store in file + keyStore.store(new FileOutputStream(getKeystoreFile()), KEYSTORE_PASSWORD.toCharArray()); + } catch (Exception e) { + logger.warn("Failed to re-generate self-signed certificate: {}", e.getMessage()); + } + } + + void renewKeyStoreFromDTO(CertificateDTO certificateDTO) + throws IOException, CertificateException, NoSuchAlgorithmException, InvalidKeySpecException { + List certificates = getCertsFromPEM(certificateDTO.certificateChain); + PrivateKey privateKey = getPrivateKeyFromPEM(certificateDTO.privateKey); + + if (certificates.isEmpty() || privateKey == null) { + logger.warn("Could not get necessary information to regenerate keystore. Check input!"); + return; + } + + try { + Certificate[] certArray = certificates.toArray(Certificate[]::new); + + KeyStore keyStore = KeyStore.getInstance(KEYSTORE_JKS_TYPE); + keyStore.load(null, null); + + // store private key with certificate + keyStore.setKeyEntry(KEYSTORE_ENTRY_ALIAS, privateKey, KEYSTORE_PASSWORD.toCharArray(), certArray); + + // add all except own certificate to store as additional certificates + for (int i = 1; i < certArray.length; i++) { + keyStore.setCertificateEntry("chain" + i, certArray[i]); + } + + // store in file + keyStore.store(new FileOutputStream(getKeystoreFile()), KEYSTORE_PASSWORD.toCharArray()); + } catch (Exception e) { + logger.warn("Failed to re-generate keystore: {}", e.getMessage()); + } + } + + private File getKeystoreFile() { + String keystorePath = System.getProperty(JETTY_KEYSTORE_PATH_PROPERTY); + return new File(keystorePath); + } + + List getCertsFromPEM(@Nullable String certString) throws IOException, CertificateException { + if (certString == null) { + throw new CertificateException("Certificates mst not be null"); + } + InputStream certInputStream = new ByteArrayInputStream(certString.getBytes(StandardCharsets.UTF_8)); + List certificates = new ArrayList<>(); + while (certInputStream.available() > 0) { + X509Certificate certificate = (X509Certificate) CertificateFactory.getInstance("X.509") + .generateCertificate(certInputStream); + logger.debug("Imported subject='{}', serial='{}', until='{}'", certificate.getSubjectDN(), + certificate.getSerialNumber(), certificate.getNotAfter()); + certificates.add(certificate); + } + + logger.info("Imported {} certificates for the Jetty server certificate chain.", certificates.size()); + + return certificates; + } + + @Nullable + PrivateKey getPrivateKeyFromPEM(@Nullable String keyString) + throws CertificateException, NoSuchAlgorithmException, InvalidKeySpecException { + if (keyString == null) { + throw new CertificateException("Key must not be null"); + } + String privateKeyPEM = keyString.replace("-----BEGIN PRIVATE KEY-----", "") + .replaceAll(System.lineSeparator(), "").replace("-----END PRIVATE KEY-----", ""); + + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKeyPEM)); + PrivateKey key = keyFactory.generatePrivate(keySpec); + logger.info("Imported private key for the Jetty server certificate chain."); + return key; + } +} diff --git a/bundles/org.openhab.core.io.rest.core/src/test/java/org/openhab/core/io/rest/core/internal/CertificateResourceTest.java b/bundles/org.openhab.core.io.rest.core/src/test/java/org/openhab/core/io/rest/core/internal/CertificateResourceTest.java new file mode 100644 index 00000000000..03483fb8c12 --- /dev/null +++ b/bundles/org.openhab.core.io.rest.core/src/test/java/org/openhab/core/io/rest/core/internal/CertificateResourceTest.java @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.rest.core.internal; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.openhab.core.io.jetty.certificate.CertificateGenerator.KEYSTORE_ENTRY_ALIAS; +import static org.openhab.core.io.jetty.certificate.CertificateGenerator.KEYSTORE_JKS_TYPE; +import static org.openhab.core.io.jetty.certificate.CertificateGenerator.KEYSTORE_PASSWORD; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.Key; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.ws.rs.core.Response; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.openhab.core.io.jetty.certificate.CertificateGenerator; + +/** + * The {@link CertificateResourceTest} contains tests for the {@link CertificateResource} + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class CertificateResourceTest { + private static final String KEY_STORE_PATH_STRING = "target/test_keystore"; + private static final Path KEY_STORE_PATH = Path.of(KEY_STORE_PATH_STRING); + + private @NonNullByDefault({}) CertificateResource certificateResource; + private CertificateResource.CertificateDTO fromPEM = new CertificateResource.CertificateDTO(); + + @BeforeEach + public void setup() throws IOException { + System.setProperty(CertificateGenerator.JETTY_KEYSTORE_PATH_PROPERTY, KEY_STORE_PATH_STRING); + + fromPEM.certificateChain = Files.readString(Path.of("src/test/resources/cert/chain.pem")); + fromPEM.privateKey = Files.readString(Path.of("src/test/resources/cert/server.key")); + + certificateResource = new CertificateResource(); + } + + @AfterEach + public void cleanUp() throws IOException { + Files.deleteIfExists(KEY_STORE_PATH); + } + + @Test + public void certificateFromPEM() throws KeyStoreException, IOException, CertificateException, + NoSuchAlgorithmException, UnrecoverableKeyException { + Response response = certificateResource.putCertificate(fromPEM); + assertThat(response.getStatus(), is(Response.Status.ACCEPTED.getStatusCode())); + + assertThat(Files.exists(KEY_STORE_PATH), is(true)); + + KeyStore keyStore = KeyStore.getInstance(KEYSTORE_JKS_TYPE); + keyStore.load(Files.newInputStream(KEY_STORE_PATH), KEYSTORE_PASSWORD.toCharArray()); + + Certificate certificate = keyStore.getCertificate(KEYSTORE_ENTRY_ALIAS); + assertThat(certificate, notNullValue()); + assertThat(((X509Certificate) certificate).getSubjectDN().getName(), + is("CN=TestCert, ST=North Rhine-Westphalia, C=DE")); + + Key key = keyStore.getKey(KEYSTORE_ENTRY_ALIAS, KEYSTORE_PASSWORD.toCharArray()); + assertThat(key, instanceOf(PrivateKey.class)); + } + + @Test + public void selfSignedCertificate() throws KeyStoreException, IOException, CertificateException, + NoSuchAlgorithmException, UnrecoverableKeyException { + Response response = certificateResource.putCertificate(new CertificateResource.CertificateDTO()); + + assertThat(response.getStatus(), is(Response.Status.ACCEPTED.getStatusCode())); + + assertThat(Files.exists(KEY_STORE_PATH), is(true)); + + KeyStore keyStore = KeyStore.getInstance(KEYSTORE_JKS_TYPE); + keyStore.load(Files.newInputStream(KEY_STORE_PATH), KEYSTORE_PASSWORD.toCharArray()); + + Certificate certificate = keyStore.getCertificate(KEYSTORE_ENTRY_ALIAS); + assertThat(certificate, notNullValue()); + assertThat(((X509Certificate) certificate).getSubjectDN().getName(), + is("C=None, L=None, O=None, OU=None, CN=openhab.org")); + + Key key = keyStore.getKey(KEYSTORE_ENTRY_ALIAS, KEYSTORE_PASSWORD.toCharArray()); + assertThat(key, instanceOf(PrivateKey.class)); + } + + @Test + public void invalidCertificate() { + fromPEM.certificateChain = "invalid"; + Response response = certificateResource.putCertificate(fromPEM); + + assertThat(response.getStatus(), is(Response.Status.BAD_REQUEST.getStatusCode())); + assertThat(Files.exists(KEY_STORE_PATH), is(false)); + } + + @Test + public void invalidPrivateKey() { + fromPEM.certificateChain = "invalid"; + Response response = certificateResource.putCertificate(fromPEM); + + assertThat(response.getStatus(), is(Response.Status.BAD_REQUEST.getStatusCode())); + assertThat(Files.exists(KEY_STORE_PATH), is(false)); + } +} diff --git a/bundles/org.openhab.core.io.rest.core/src/test/resources/cert/chain.pem b/bundles/org.openhab.core.io.rest.core/src/test/resources/cert/chain.pem new file mode 100644 index 00000000000..dd552ac973c --- /dev/null +++ b/bundles/org.openhab.core.io.rest.core/src/test/resources/cert/chain.pem @@ -0,0 +1,50 @@ +-----BEGIN CERTIFICATE----- +MIIE3TCCA8WgAwIBAgIUeY8LPDj11UZZlIlEpwSGXEGTFV8wDQYJKoZIhvcNAQEL +BQAwQTERMA8GA1UEAwwIVGVzdFJvb3QxCzAJBgNVBAYTAkRFMR8wHQYDVQQIDBZO +b3J0aCBSaGluZS1XZXN0cGhhbGlhMB4XDTIyMDQwNDE1NDk0OVoXDTMyMDQwNDE1 +NDkwMFowQTELMAkGA1UEBhMCREUxHzAdBgNVBAgMFk5vcnRoIFJoaW5lLVdlc3Rw +aGFsaWExETAPBgNVBAMMCFRlc3RDZXJ0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAobfZPniTqcrqpW0uKo3Vx5fBuRhS0eeMN1V1V5i1x8ALu/3XRb9R +BDe2Tnh7zz0iHRBdT9UYgWoCy87EK4PltqDGJwPDn6d7EEw+XKn547PZ5pwVg6Xr +wv0xEQVpN5ZyZJtHJIKFlgk8JsbxACZFVt0MdPs4mjoZt+6OpHzTIoYxN17LXukI +CTyukgm0iLBCfZXy54vWmc9CN0IDde345DqzkSLaKoHM2kJJbxe6JWRqzU1o7yuZ +tRCxgttziPGU1r2pDkux/NNXlcOoHP1xoD708JntrdVBSwmPqpLTEwcpKNs8bfgD +/Ap0K6E24mfs62sEOCeO5o9cq0gGrZbCnQIDAQABo4IByzCCAccwHQYDVR0OBBYE +FN6sDq+4xljYlIHDx66Kyp26GqD7MB8GA1UdIwQYMBaAFFEHo4D0e7ENZPDRLhXz +YOf6XiT5MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB +BQUHAwIwDAYDVR0TAQH/BAIwADCB3AYIKwYBBQUHAQEEgc8wgcwwYQYIKwYBBQUH +MAGGVWh0dHA6Ly9wa2kuY2VydGlmaWNhdGV0b29scy5jb20vcHVibGljL29jc3Av +ZmQ5ZWM2MWE5NmU4NDEzMDc5NTBlNWQ0YmJlYjA1NjEvVGVzdFJvb3QwZwYIKwYB +BQUHMAKGW2h0dHA6Ly9wa2kuY2VydGlmaWNhdGV0b29scy5jb20vcHVibGljL2lz +c3Vlci9mZDllYzYxYTk2ZTg0MTMwNzk1MGU1ZDRiYmViMDU2MS9UZXN0Um9vdC5j +cnQwaQYDVR0fBGIwYDBeoFygWoZYaHR0cDovL3BraS5jZXJ0aWZpY2F0ZXRvb2xz +LmNvbS9wdWJsaWMvY3JsL2ZkOWVjNjFhOTZlODQxMzA3OTUwZTVkNGJiZWIwNTYx +L1Rlc3RSb290LmNybDANBgkqhkiG9w0BAQsFAAOCAQEAHZWC9u9Oz3MiFt7mtq9U +Dk4X9JGvyZ58DPHaHrbUPagilGQnqezUmr8gUcgJyKOcl7om14y8lGyybXSUb8Uo +MMPFk+HPhAGFVsPsoronaVydD5dOGiJbSxMHrtWob8S4Zew0s4j2RvpofTAL5xgR +4A0j6o4BeEqaisw3VAVglkQt0kNVxEHRaZYc9inSB9g3tRHZSuMqxuI3GMwUmaS3 +KVoqtYJNXoi2U61ZwTFvTOXKGF31gRzxnQAwT6Mj9tSH4evHlKoUQ8rDzh/WjBoV +CIqAOjyxnJpxtxySrEXa7wwlybpni0M8MS8nNxob4DiRMBiNehQ83CX9nKnVm9Ko +9Q== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDdjCCAl6gAwIBAgIUcPrUWkaMuYtc4OvkLDD/4U4dV24wDQYJKoZIhvcNAQEL +BQAwQTERMA8GA1UEAwwIVGVzdFJvb3QxCzAJBgNVBAYTAkRFMR8wHQYDVQQIDBZO +b3J0aCBSaGluZS1XZXN0cGhhbGlhMB4XDTIyMDQwNDIwNDYwNloXDTMyMDQwMTIw +NDYwNlowQTERMA8GA1UEAwwIVGVzdFJvb3QxCzAJBgNVBAYTAkRFMR8wHQYDVQQI +DBZOb3J0aCBSaGluZS1XZXN0cGhhbGlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAwo1yQBpVO+dMFq5CpZwOKDylDGDhwqHo3Hovqv9j8pjeaxaZA/W1 +dSz/w9w+Hs8t6i/XL7cHda6PnPPBhVvpbsSF6QngnQWFPectQXD+r0SJFkdYZpNY +53LrEkczR0vPFI/z/1X0WmiG4PhJhrfxfUlJ9yXPuUK8J+jfblq5vhL1n5j+3yov +cZBLmk3wfRpq0od3LypBBN9JGv98wMzZ92PaUU/GA6OHf6N5mWagVF+j/XJF6KGd +MacbFl8IQr4sP5k3WapY6zQ2UDP3/kS0vBIA2lkRYy+r92hNulMpfdGADAq2kNct +gRcLUvuIoZD/Fx/2SR7qB3QbTa+loKRGOwIDAQABo2YwZDAdBgNVHQ4EFgQUUQej +gPR7sQ1k8NEuFfNg5/peJPkwHwYDVR0jBBgwFoAUUQejgPR7sQ1k8NEuFfNg5/pe +JPkwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwDQYJKoZIhvcN +AQELBQADggEBAGF2xmjQ4Qz4xQ2oDMU06Ntgif56QNRvhuTSh72gR8uOEu/cCuLk +6WTFwJN1L1juL1tIRNcnkZZxClG+oeIBLagfLC9cKd/EyFLWOOGd9MmP7BTH73wH +GyS8egPLUt5B26mW8nKcvB1MauCBXrZO4tnoltpQFxRhgpciuQJlup+36PZIQOYl +iwWtQW9C4gU5nexdv7yB+G7kIrQ3a2daZyqNXzYohniMl442RaNE4wfo5/Ei8E9r +an9At+pL+VInb5buIKk9Wos5iLwL5XU94Juge1UBeeBMbL5zkS0OLAkSEbJtfgzL +wzMjHSHfgfH9+pKT9psA9Gn0ziy3brSX3ac= +-----END CERTIFICATE----- diff --git a/bundles/org.openhab.core.io.rest.core/src/test/resources/cert/root.key b/bundles/org.openhab.core.io.rest.core/src/test/resources/cert/root.key new file mode 100644 index 00000000000..b3bbeb4e6af --- /dev/null +++ b/bundles/org.openhab.core.io.rest.core/src/test/resources/cert/root.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDCjXJAGlU750wW +rkKlnA4oPKUMYOHCoejcei+q/2PymN5rFpkD9bV1LP/D3D4ezy3qL9cvtwd1ro+c +88GFW+luxIXpCeCdBYU95y1BcP6vRIkWR1hmk1jncusSRzNHS88Uj/P/VfRaaIbg ++EmGt/F9SUn3Jc+5Qrwn6N9uWrm+EvWfmP7fKi9xkEuaTfB9GmrSh3cvKkEE30ka +/3zAzNn3Y9pRT8YDo4d/o3mZZqBUX6P9ckXooZ0xpxsWXwhCviw/mTdZqljrNDZQ +M/f+RLS8EgDaWRFjL6v3aE26Uyl90YAMCraQ1y2BFwtS+4ihkP8XH/ZJHuoHdBtN +r6WgpEY7AgMBAAECggEAOitmjKoS7/1BL8rcnH2jcf5n7uKMvdABOuLBm+QOczun +zOcNyzRNFALnWh1g6X9SoCG7ukWEUnhl8iyjoQcRLZBO5ZWRdgFvjEmxq05op+jb +ADFGyiymOVWP2YA2YcvV17UO5DpD/EAii+NQwJoA90/RcLb6e87rSReEro8r2Bt3 +Douqby4+Adsa7rbTyWHkgyHy2nN386wQrLpRViij3I0ZwNB/pJNxX4HU4uXx4HA5 +Hl9oJwWUw97yRWFkZtdUSDNDTrtfVfYNSZgHzf9JtzgroXeSn9zgYlRdMssGI/08 +hkAkDfLZykNG7zFZ0sf4Zf0oM8So8ti7n+ME794QAQKBgQD1Q/Fhd3dDJ3/uFDyS +tj1NGJmV4G+jaDEs6TDqon3gtdRRESp8YeLpAe2Hi+8PFthTPOqT5nWbBYKRZ1HL +wyqvV38m7k9WuIbHt0yr34lDKRh3zPFbOL1UHl/3pQbXw51BCOlSbaPCMxwhrjnP +RjSKTwt5FmRo8Oyi6Wr5A2p4AQKBgQDLEUt/NwL0OzVDLML8SU3xaTXaWhqfMOW1 +Vwp/PzfqGUUkf3CtXZ6gNALJPhHFcPm6QO9KWUODsESzbxDfAVzqaCdKuf7I9TDK ++zx8cgsuLjWU/4PMl4rPUgovPX6vGX32dVhc4rB2pKZHI/ZtH5tIAoYLL0tazV8v +o98FcQqeOwKBgGHIBKPnDRUo2kgAqWWUYjwqYpDuOZPcJj7ReT8SKT09QLhRL68H +F3/t1NUFZPmPEg+daagtaRW0zKlLl8C0vn/JUlgjCl54ksNswJSFOiQcbjTU8CR+ +Kn0u3vPMRz4eLsOO2faS34a8+aoz27hXNPsvKFPOKZns82q/xHYidoABAoGAVWOS +YY4aWeTDwtmBrGCOu+LH2GdnJmWDUjulmFhXBMNXhb9o/B2P5EG6ojDNfD7DZASG +GrdqYmeum90qNPv3vEHLs+p+Lrabq971UkcKWILp2RuQqmlW31LaIX80VCDeDNLP +M0/HcDxQBz4iE0TlIOSexPlAMJCHZBr+XuVn150CgYEAvWtoQMZpj1dBDZnDCMKs +PhCUb1/xk0EQ69YKje5UvpEvOiH+VlKfxHALBvDw5huhgXgvxLghv1NlGOMKL59q +7SlJV3RX4XAZ6Sku0+R45r5AjE51/ENZPGhPdcVpkkKLu+vchJjagAiwvEWdo67r +mZtuyKLeu5ugCvCYc8wY1GU= +-----END PRIVATE KEY----- diff --git a/bundles/org.openhab.core.io.rest.core/src/test/resources/cert/root.pem b/bundles/org.openhab.core.io.rest.core/src/test/resources/cert/root.pem new file mode 100644 index 00000000000..9ef646c53ad --- /dev/null +++ b/bundles/org.openhab.core.io.rest.core/src/test/resources/cert/root.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDdjCCAl6gAwIBAgIUcPrUWkaMuYtc4OvkLDD/4U4dV24wDQYJKoZIhvcNAQEL +BQAwQTERMA8GA1UEAwwIVGVzdFJvb3QxCzAJBgNVBAYTAkRFMR8wHQYDVQQIDBZO +b3J0aCBSaGluZS1XZXN0cGhhbGlhMB4XDTIyMDQwNDIwNDYwNloXDTMyMDQwMTIw +NDYwNlowQTERMA8GA1UEAwwIVGVzdFJvb3QxCzAJBgNVBAYTAkRFMR8wHQYDVQQI +DBZOb3J0aCBSaGluZS1XZXN0cGhhbGlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAwo1yQBpVO+dMFq5CpZwOKDylDGDhwqHo3Hovqv9j8pjeaxaZA/W1 +dSz/w9w+Hs8t6i/XL7cHda6PnPPBhVvpbsSF6QngnQWFPectQXD+r0SJFkdYZpNY +53LrEkczR0vPFI/z/1X0WmiG4PhJhrfxfUlJ9yXPuUK8J+jfblq5vhL1n5j+3yov +cZBLmk3wfRpq0od3LypBBN9JGv98wMzZ92PaUU/GA6OHf6N5mWagVF+j/XJF6KGd +MacbFl8IQr4sP5k3WapY6zQ2UDP3/kS0vBIA2lkRYy+r92hNulMpfdGADAq2kNct +gRcLUvuIoZD/Fx/2SR7qB3QbTa+loKRGOwIDAQABo2YwZDAdBgNVHQ4EFgQUUQej +gPR7sQ1k8NEuFfNg5/peJPkwHwYDVR0jBBgwFoAUUQejgPR7sQ1k8NEuFfNg5/pe +JPkwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwDQYJKoZIhvcN +AQELBQADggEBAGF2xmjQ4Qz4xQ2oDMU06Ntgif56QNRvhuTSh72gR8uOEu/cCuLk +6WTFwJN1L1juL1tIRNcnkZZxClG+oeIBLagfLC9cKd/EyFLWOOGd9MmP7BTH73wH +GyS8egPLUt5B26mW8nKcvB1MauCBXrZO4tnoltpQFxRhgpciuQJlup+36PZIQOYl +iwWtQW9C4gU5nexdv7yB+G7kIrQ3a2daZyqNXzYohniMl442RaNE4wfo5/Ei8E9r +an9At+pL+VInb5buIKk9Wos5iLwL5XU94Juge1UBeeBMbL5zkS0OLAkSEbJtfgzL +wzMjHSHfgfH9+pKT9psA9Gn0ziy3brSX3ac= +-----END CERTIFICATE----- diff --git a/bundles/org.openhab.core.io.rest.core/src/test/resources/cert/server.key b/bundles/org.openhab.core.io.rest.core/src/test/resources/cert/server.key new file mode 100644 index 00000000000..4c539909428 --- /dev/null +++ b/bundles/org.openhab.core.io.rest.core/src/test/resources/cert/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCht9k+eJOpyuql +bS4qjdXHl8G5GFLR54w3VXVXmLXHwAu7/ddFv1EEN7ZOeHvPPSIdEF1P1RiBagLL +zsQrg+W2oMYnA8Ofp3sQTD5cqfnjs9nmnBWDpevC/TERBWk3lnJkm0ckgoWWCTwm +xvEAJkVW3Qx0+ziaOhm37o6kfNMihjE3Xste6QgJPK6SCbSIsEJ9lfLni9aZz0I3 +QgN17fjkOrORItoqgczaQklvF7olZGrNTWjvK5m1ELGC23OI8ZTWvakOS7H801eV +w6gc/XGgPvTwme2t1UFLCY+qktMTByko2zxt+AP8CnQroTbiZ+zrawQ4J47mj1yr +SAatlsKdAgMBAAECggEBAJ4E7WxTmcYhlyLKCoBeU3RSX2Mj11lv7dzrzlsnxwhG +KbEqGr32cY2zepo2tJQN4lWP9f3Z9bYPBmhrw9fZbA8GDll1HiPVHTURqyYUmS7c +QKFMBuG6sSK5EMbXCLbedSzFZcDRIQM4RuSFJFzHCJ4WJOPxzp81AohtvEgSCKUD ++SqLM8y8d58l7wjVjsQz/QX7Cnqxf5tRFinSNMSW/Hosr+blHIXNMcvh5Ctu2rrP +4RjQlqf7CjBAykalRtFp2qQcJ7DBSg4HJpV/Z7WqzXVAsxfpCD23yILKgWDqSJSl +2uJscjMC7mD9QdBEai0TN3YJe1xzFyYifk/3iLf5FYECgYEA0WB7jQb7PJRSJlGh +n1gbHIdz8GrfitLsQsFVTaKS/OxaGdE/9KeNK7DmGdmNC8q5WagReyos8GVEpV5O +Xrg3DK/WY9psFEA07vnAcNIBPXcySJ/R5AwhX84codN5UPYjz2ZIixJJClBBvOm5 +OgmAm5YnZw9NdbpNDznZEprLebECgYEAxbqVP5tiPoKKtVmIhWdzdZov62v4leO/ +LmWrOAJWJJJPGkhs2VTEpMVnmlbnHxzQhGDU++ksrGJ8bbgW247/OhIKZO6hdTQe +d1WGxeZjCMD/p8u5UViYDnFohN1oxGffstuoJD59AeP1zEE/6u6UxcdmjC2NJUfi +T0nFAvZpZq0CgYEA0BlWEyAaOuasE5vUyJv4Lk3OhOikD8V9vweK8iOgdjOvhbN9 +HnwdioxC1vRsBIlT2XF7FnAfxDrSlI371JbBWCG4MvyXqTT7p32hsEB5rQ64cogu +Q7tuvFsOCCU4kahihTOC7H6bC94iyFGAoNdq9LizpvIFqfoltvlm79/cVrECgYAN +VdwjAhPKK3u3Z2ZxuNxsng2FTy7gzQ9jza+Sg3XT2ZpEJ4ZP0gvBIR8vpZTI4GSf +yPgUVVGR/sJtk/DWtfo4rGbPD9kL0owLeFUym6sqN/oGExNYjSUUNEncTCJ4vJi1 +s2LPrvc3XWyCbhQlgBnAjQY5hgjy7rowpsUGwh+xsQKBgHF1WGNk9vHwrh+OdLxq +Y0R13LD+7eaZHr6bzdWfX0Ge85bLx1DgcNyAIkmzn+Pz4MFhyF7bPDuSSRd+K0Sm +KnzJxQdUqQtZxCTlohhf4c1GKIX1lmHm6SOqjlqvKmFnl8RK/s6yDcRQzOH/y7PW +5dSHrJ6QV/u3485uZhlPHJlp +-----END PRIVATE KEY----- diff --git a/bundles/org.openhab.core.io.rest.core/src/test/resources/cert/server.pem b/bundles/org.openhab.core.io.rest.core/src/test/resources/cert/server.pem new file mode 100644 index 00000000000..3ab69c16e6a --- /dev/null +++ b/bundles/org.openhab.core.io.rest.core/src/test/resources/cert/server.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE3TCCA8WgAwIBAgIUeY8LPDj11UZZlIlEpwSGXEGTFV8wDQYJKoZIhvcNAQEL +BQAwQTERMA8GA1UEAwwIVGVzdFJvb3QxCzAJBgNVBAYTAkRFMR8wHQYDVQQIDBZO +b3J0aCBSaGluZS1XZXN0cGhhbGlhMB4XDTIyMDQwNDE1NDk0OVoXDTMyMDQwNDE1 +NDkwMFowQTELMAkGA1UEBhMCREUxHzAdBgNVBAgMFk5vcnRoIFJoaW5lLVdlc3Rw +aGFsaWExETAPBgNVBAMMCFRlc3RDZXJ0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAobfZPniTqcrqpW0uKo3Vx5fBuRhS0eeMN1V1V5i1x8ALu/3XRb9R +BDe2Tnh7zz0iHRBdT9UYgWoCy87EK4PltqDGJwPDn6d7EEw+XKn547PZ5pwVg6Xr +wv0xEQVpN5ZyZJtHJIKFlgk8JsbxACZFVt0MdPs4mjoZt+6OpHzTIoYxN17LXukI +CTyukgm0iLBCfZXy54vWmc9CN0IDde345DqzkSLaKoHM2kJJbxe6JWRqzU1o7yuZ +tRCxgttziPGU1r2pDkux/NNXlcOoHP1xoD708JntrdVBSwmPqpLTEwcpKNs8bfgD +/Ap0K6E24mfs62sEOCeO5o9cq0gGrZbCnQIDAQABo4IByzCCAccwHQYDVR0OBBYE +FN6sDq+4xljYlIHDx66Kyp26GqD7MB8GA1UdIwQYMBaAFFEHo4D0e7ENZPDRLhXz +YOf6XiT5MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB +BQUHAwIwDAYDVR0TAQH/BAIwADCB3AYIKwYBBQUHAQEEgc8wgcwwYQYIKwYBBQUH +MAGGVWh0dHA6Ly9wa2kuY2VydGlmaWNhdGV0b29scy5jb20vcHVibGljL29jc3Av +ZmQ5ZWM2MWE5NmU4NDEzMDc5NTBlNWQ0YmJlYjA1NjEvVGVzdFJvb3QwZwYIKwYB +BQUHMAKGW2h0dHA6Ly9wa2kuY2VydGlmaWNhdGV0b29scy5jb20vcHVibGljL2lz +c3Vlci9mZDllYzYxYTk2ZTg0MTMwNzk1MGU1ZDRiYmViMDU2MS9UZXN0Um9vdC5j +cnQwaQYDVR0fBGIwYDBeoFygWoZYaHR0cDovL3BraS5jZXJ0aWZpY2F0ZXRvb2xz +LmNvbS9wdWJsaWMvY3JsL2ZkOWVjNjFhOTZlODQxMzA3OTUwZTVkNGJiZWIwNTYx +L1Rlc3RSb290LmNybDANBgkqhkiG9w0BAQsFAAOCAQEAHZWC9u9Oz3MiFt7mtq9U +Dk4X9JGvyZ58DPHaHrbUPagilGQnqezUmr8gUcgJyKOcl7om14y8lGyybXSUb8Uo +MMPFk+HPhAGFVsPsoronaVydD5dOGiJbSxMHrtWob8S4Zew0s4j2RvpofTAL5xgR +4A0j6o4BeEqaisw3VAVglkQt0kNVxEHRaZYc9inSB9g3tRHZSuMqxuI3GMwUmaS3 +KVoqtYJNXoi2U61ZwTFvTOXKGF31gRzxnQAwT6Mj9tSH4evHlKoUQ8rDzh/WjBoV +CIqAOjyxnJpxtxySrEXa7wwlybpni0M8MS8nNxob4DiRMBiNehQ83CX9nKnVm9Ko +9Q== +-----END CERTIFICATE----- diff --git a/features/karaf/openhab-core/src/main/feature/feature.xml b/features/karaf/openhab-core/src/main/feature/feature.xml index 5fe065535e4..b267b554b66 100644 --- a/features/karaf/openhab-core/src/main/feature/feature.xml +++ b/features/karaf/openhab-core/src/main/feature/feature.xml @@ -63,6 +63,7 @@ mvn:org.openhab.core.bundles/org.openhab.core.io.monitor/${project.version} mvn:org.openhab.core.bundles/org.openhab.core.io.net/${project.version} pax-http-whiteboard + mvn:org.openhab.core.bundles/org.openhab.core.io.jetty.certificate/${project.version} mvn:org.openhab.core.bundles/org.openhab.core.io.http/${project.version} mvn:org.openhab.core.bundles/org.openhab.core.io.rest/${project.version} mvn:org.openhab.core.bundles/org.openhab.core.io.rest.core/${project.version} diff --git a/itests/org.openhab.core.model.core.tests/itest.bndrun b/itests/org.openhab.core.model.core.tests/itest.bndrun new file mode 100644 index 00000000000..9c6757a9079 --- /dev/null +++ b/itests/org.openhab.core.model.core.tests/itest.bndrun @@ -0,0 +1,115 @@ +-include: ../itest-common.bndrun + +Bundle-SymbolicName: ${project.artifactId} +Fragment-Host: org.openhab.core.model.core + +-runrequires: bnd.identity;id='org.openhab.core.model.core.tests' + +# +# done +# +-runbundles: \ + org.antlr.runtime;version='[3.2.0,3.2.1)',\ + org.apache.felix.http.servlet-api;version='[1.1.2,1.1.3)',\ + org.glassfish.hk2.external.aopalliance-repackaged;version='[2.4.0,2.4.1)',\ + org.glassfish.hk2.external.javax.inject;version='[2.4.0,2.4.1)',\ + org.osgi.service.event;version='[1.4.0,1.4.1)',\ + org.apache.servicemix.specs.annotation-api-1.3;version='[1.3.0,1.3.1)',\ + org.eclipse.equinox.event;version='[1.4.300,1.4.301)',\ + com.google.guava.failureaccess;version='[1.0.1,1.0.2)',\ + org.hamcrest;version='[2.2.0,2.2.1)',\ + org.opentest4j;version='[1.2.0,1.2.1)',\ + com.sun.xml.bind.jaxb-osgi;version='[2.3.3,2.3.4)',\ + jakarta.xml.bind-api;version='[2.3.3,2.3.4)',\ + org.apache.servicemix.specs.activation-api-1.2.1;version='[1.2.1,1.2.2)',\ + org.eclipse.emf.common;version='[2.17.0,2.17.1)',\ + org.eclipse.emf.ecore;version='[2.20.0,2.20.1)',\ + org.eclipse.emf.ecore.xmi;version='[2.16.0,2.16.1)',\ + org.glassfish.hk2.osgi-resource-locator;version='[1.0.3,1.0.4)',\ + com.google.guava;version='[30.1.0,30.1.1)',\ + jakarta.annotation-api;version='[2.0.0,2.0.1)',\ + jakarta.inject.jakarta.inject-api;version='[2.0.0,2.0.1)',\ + javax.measure.unit-api;version='[2.1.2,2.1.3)',\ + org.osgi.service.cm;version='[1.6.0,1.6.1)',\ + tech.units.indriya;version='[2.1.2,2.1.3)',\ + uom-lib-common;version='[2.1.0,2.1.1)',\ + si-units;version='[2.1.0,2.1.1)',\ + si.uom.si-quantity;version='[2.1.0,2.1.1)',\ + junit-jupiter-api;version='[5.8.1,5.8.2)',\ + junit-jupiter-engine;version='[5.8.1,5.8.2)',\ + junit-platform-commons;version='[1.8.1,1.8.2)',\ + junit-platform-engine;version='[1.8.1,1.8.2)',\ + junit-platform-launcher;version='[1.8.1,1.8.2)',\ + net.bytebuddy.byte-buddy;version='[1.12.1,1.12.2)',\ + net.bytebuddy.byte-buddy-agent;version='[1.12.1,1.12.2)',\ + org.mockito.junit-jupiter;version='[4.1.0,4.1.1)',\ + org.mockito.mockito-core;version='[4.1.0,4.1.1)',\ + org.objenesis;version='[3.2.0,3.2.1)',\ + org.apache.felix.scr;version='[2.1.30,2.1.31)',\ + org.osgi.util.function;version='[1.2.0,1.2.1)',\ + org.osgi.util.promise;version='[1.2.0,1.2.1)',\ + com.google.inject;version='[5.0.1,5.0.2)',\ + jollyday;version='[0.5.10,0.5.11)',\ + org.objectweb.asm.commons;version='[9.0.0,9.0.1)',\ + org.objectweb.asm.tree;version='[9.0.0,9.0.1)',\ + org.threeten.extra;version='[1.5.0,1.5.1)',\ + org.apache.xbean.bundleutils;version='[4.21.0,4.21.1)',\ + org.apache.xbean.finder;version='[4.21.0,4.21.1)',\ + org.eclipse.jetty.client;version='[9.4.46,9.4.47)',\ + org.eclipse.jetty.http;version='[9.4.46,9.4.47)',\ + org.eclipse.jetty.io;version='[9.4.46,9.4.47)',\ + org.eclipse.jetty.security;version='[9.4.46,9.4.47)',\ + org.eclipse.jetty.server;version='[9.4.46,9.4.47)',\ + org.eclipse.jetty.servlet;version='[9.4.46,9.4.47)',\ + org.eclipse.jetty.util;version='[9.4.46,9.4.47)',\ + org.eclipse.jetty.util.ajax;version='[9.4.46,9.4.47)',\ + org.eclipse.jetty.websocket.api;version='[9.4.46,9.4.47)',\ + org.eclipse.jetty.websocket.client;version='[9.4.46,9.4.47)',\ + org.eclipse.jetty.websocket.common;version='[9.4.46,9.4.47)',\ + org.eclipse.jetty.xml;version='[9.4.46,9.4.47)',\ + org.ops4j.pax.logging.pax-logging-api;version='[2.0.16,2.0.17)',\ + org.ops4j.pax.web.pax-web-api;version='[7.3.25,7.3.26)',\ + org.ops4j.pax.web.pax-web-jetty;version='[7.3.25,7.3.26)',\ + org.ops4j.pax.web.pax-web-runtime;version='[7.3.25,7.3.26)',\ + org.ops4j.pax.web.pax-web-spi;version='[7.3.25,7.3.26)',\ + ch.qos.logback.classic;version='[1.2.11,1.2.12)',\ + ch.qos.logback.core;version='[1.2.11,1.2.12)',\ + biz.aQute.tester.junit-platform;version='[6.4.0,6.4.1)',\ + org.openhab.core;version='[4.0.0,4.0.1)',\ + org.openhab.core.audio;version='[4.0.0,4.0.1)',\ + org.openhab.core.automation;version='[4.0.0,4.0.1)',\ + org.openhab.core.automation.module.script;version='[4.0.0,4.0.1)',\ + org.openhab.core.automation.module.script.rulesupport;version='[4.0.0,4.0.1)',\ + org.openhab.core.config.core;version='[4.0.0,4.0.1)',\ + org.openhab.core.ephemeris;version='[4.0.0,4.0.1)',\ + org.openhab.core.io.console;version='[4.0.0,4.0.1)',\ + org.openhab.core.io.http;version='[4.0.0,4.0.1)',\ + org.openhab.core.io.net;version='[4.0.0,4.0.1)',\ + org.openhab.core.model.core;version='[4.0.0,4.0.1)',\ + org.openhab.core.model.core.tests;version='[4.0.0,4.0.1)',\ + org.openhab.core.model.item;version='[4.0.0,4.0.1)',\ + org.openhab.core.model.persistence;version='[4.0.0,4.0.1)',\ + org.openhab.core.model.rule;version='[4.0.0,4.0.1)',\ + org.openhab.core.model.script;version='[4.0.0,4.0.1)',\ + org.openhab.core.model.script.runtime;version='[4.0.0,4.0.1)',\ + org.openhab.core.model.sitemap;version='[4.0.0,4.0.1)',\ + org.openhab.core.model.thing;version='[4.0.0,4.0.1)',\ + org.openhab.core.persistence;version='[4.0.0,4.0.1)',\ + org.openhab.core.semantics;version='[4.0.0,4.0.1)',\ + org.openhab.core.test;version='[4.0.0,4.0.1)',\ + org.openhab.core.thing;version='[4.0.0,4.0.1)',\ + org.openhab.core.transform;version='[4.0.0,4.0.1)',\ + org.openhab.core.voice;version='[4.0.0,4.0.1)',\ + com.google.gson;version='[2.9.1,2.9.2)',\ + io.github.classgraph;version='[4.8.149,4.8.150)',\ + org.eclipse.equinox.common;version='[3.16.200,3.16.201)',\ + org.eclipse.xtend.lib;version='[2.29.0,2.29.1)',\ + org.eclipse.xtend.lib.macro;version='[2.29.0,2.29.1)',\ + org.eclipse.xtext;version='[2.29.0,2.29.1)',\ + org.eclipse.xtext.common.types;version='[2.29.0,2.29.1)',\ + org.eclipse.xtext.util;version='[2.29.0,2.29.1)',\ + org.eclipse.xtext.xbase;version='[2.29.0,2.29.1)',\ + org.eclipse.xtext.xbase.lib;version='[2.29.0,2.29.1)',\ + org.objectweb.asm;version='[9.4.0,9.4.1)',\ + org.apache.log4j;version='[1.2.19,1.2.20)',\ + org.openhab.core.model.item.runtime;version='[4.0.0,4.0.1)' From ed073a36feb7391c53ffc066531edb1c0ca6a65e Mon Sep 17 00:00:00 2001 From: "Jan N. Klug" Date: Sun, 28 May 2023 14:05:32 +0200 Subject: [PATCH 2/2] fixes Signed-off-by: Jan N. Klug --- bom/compile/pom.xml | 22 +++++++++++++++++++ .../pom.xml | 8 ------- .../itest.bndrun | 4 +++- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/bom/compile/pom.xml b/bom/compile/pom.xml index 1823005acc8..d530c272b67 100644 --- a/bom/compile/pom.xml +++ b/bom/compile/pom.xml @@ -19,6 +19,7 @@ 2.7.4 9.4.50.v20221201 + 1.70 8.0.15 2.1.9 @@ -190,6 +191,26 @@ compile + + + org.bouncycastle + bcprov-jdk15on + ${bouncycastle.version} + compile + + + org.bouncycastle + bcpkix-jdk15on + ${bouncycastle.version} + compile + + + org.bouncycastle + bcutil-jdk15on + ${bouncycastle.version} + compile + + org.jmdns @@ -436,6 +457,7 @@ 5.12.1 compile + diff --git a/bundles/org.openhab.core.io.jetty.certificate/pom.xml b/bundles/org.openhab.core.io.jetty.certificate/pom.xml index f129f3719db..6e0f4d10cac 100644 --- a/bundles/org.openhab.core.io.jetty.certificate/pom.xml +++ b/bundles/org.openhab.core.io.jetty.certificate/pom.xml @@ -13,14 +13,6 @@ openHAB Core :: Bundles :: SSL Certificate Generator - - - org.bouncycastle - bcpkix-jdk15on - 1.52 - - - diff --git a/itests/org.openhab.core.io.rest.core.tests/itest.bndrun b/itests/org.openhab.core.io.rest.core.tests/itest.bndrun index f2d2f06a979..45fb824f8fd 100644 --- a/itests/org.openhab.core.io.rest.core.tests/itest.bndrun +++ b/itests/org.openhab.core.io.rest.core.tests/itest.bndrun @@ -106,4 +106,6 @@ Fragment-Host: org.openhab.core.io.rest.core net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\ org.mockito.junit-jupiter;version='[4.11.0,4.11.1)',\ org.mockito.mockito-core;version='[4.11.0,4.11.1)',\ - org.objenesis;version='[3.3.0,3.3.1)' + org.objenesis;version='[3.3.0,3.3.1)',\ + org.openhab.core.io.jetty.certificate;version='[4.0.0,4.0.1)',\ + org.osgi.service.cm;version='[1.6.0,1.6.1)'