From 2c871e0115b00bfab777949a0963029e78c4de5a Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Tue, 30 Jan 2024 05:36:15 +0100 Subject: [PATCH] Refactor NetconfSalKeystoreRpcs Move RPC implementations into keystore-legacy and wrap them in a ClusterSingletonService. JIRA: NETCONF-1237 Change-Id: I1fdc81500c90cb46235838c9e77e741d44160807 Signed-off-by: Robert Varga --- keystore/keystore-legacy/pom.xml | 14 + .../legacy/AbstractNetconfKeystore.java | 19 +- .../netconf/keystore/legacy/AbstractRpc.java | 26 ++ .../legacy/DefaultAddKeystoreEntry.java | 84 +++++ .../keystore/legacy/DefaultAddPrivateKey.java | 52 +++ .../legacy/DefaultAddTrustedCertificate.java | 53 ++++ .../legacy/DefaultRemoveKeystoreEntry.java | 55 ++++ .../legacy/DefaultRemovePrivateKey.java | 57 ++++ .../DefaultRemoveTrustedCertificate.java | 56 ++++ .../netconf/keystore/legacy/RpcSingleton.java | 83 +++++ .../legacy/NetconfKeystoreRpcsTest.java | 149 +++++++++ .../src/test/resources/netconf-keystore.xml | 75 +++++ .../DefaultSslHandlerFactoryProvider.java | 10 +- .../mdsal/impl/NetconfSalKeystoreRpcs.java | 299 ------------------ .../DefaultSslHandlerFactoryProviderTest.java | 19 +- .../impl/NetconfSalKeystoreRpcsTest.java | 163 ---------- 16 files changed, 746 insertions(+), 468 deletions(-) create mode 100644 keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/AbstractRpc.java create mode 100644 keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/DefaultAddKeystoreEntry.java create mode 100644 keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/DefaultAddPrivateKey.java create mode 100644 keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/DefaultAddTrustedCertificate.java create mode 100644 keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/DefaultRemoveKeystoreEntry.java create mode 100644 keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/DefaultRemovePrivateKey.java create mode 100644 keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/DefaultRemoveTrustedCertificate.java create mode 100644 keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/RpcSingleton.java create mode 100644 keystore/keystore-legacy/src/test/java/org/opendaylight/netconf/keystore/legacy/NetconfKeystoreRpcsTest.java create mode 100644 keystore/keystore-legacy/src/test/resources/netconf-keystore.xml delete mode 100644 plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/NetconfSalKeystoreRpcs.java delete mode 100644 plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/impl/NetconfSalKeystoreRpcsTest.java diff --git a/keystore/keystore-legacy/pom.xml b/keystore/keystore-legacy/pom.xml index 637f4008fc..94f2b5a5df 100644 --- a/keystore/keystore-legacy/pom.xml +++ b/keystore/keystore-legacy/pom.xml @@ -22,6 +22,10 @@ Legacy NETCONF keystore + + org.opendaylight.aaa + aaa-encrypt-service + org.opendaylight.mdsal mdsal-binding-api @@ -30,9 +34,19 @@ org.opendaylight.mdsal mdsal-common-api + + org.opendaylight.mdsal + mdsal-singleton-api + org.opendaylight.yangtools concepts + + + org.opendaylight.netconf + netconf-api + test + diff --git a/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/AbstractNetconfKeystore.java b/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/AbstractNetconfKeystore.java index 3f98baea06..d06a62a34e 100644 --- a/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/AbstractNetconfKeystore.java +++ b/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/AbstractNetconfKeystore.java @@ -23,9 +23,12 @@ import java.util.function.Consumer; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.opendaylight.aaa.encrypt.AAAEncryptionService; import org.opendaylight.mdsal.binding.api.DataBroker; import org.opendaylight.mdsal.binding.api.DataTreeIdentifier; +import org.opendaylight.mdsal.binding.api.RpcProviderService; import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.mdsal.singleton.api.ClusterSingletonServiceProvider; import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.Keystore; import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017._private.keys.PrivateKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.trusted.certificates.TrustedCertificate; @@ -93,16 +96,30 @@ public abstract class AbstractNetconfKeystore { private final SecurityHelper securityHelper = new SecurityHelper(); private @Nullable Registration configListener; + private @Nullable Registration rpcSingleton; - protected final void start(final DataBroker dataBroker) { + protected final void start(final DataBroker dataBroker, final RpcProviderService rpcProvider, + final ClusterSingletonServiceProvider cssProvider, final AAAEncryptionService encryptionService) { if (configListener == null) { configListener = dataBroker.registerTreeChangeListener( DataTreeIdentifier.of(LogicalDatastoreType.CONFIGURATION, InstanceIdentifier.create(Keystore.class)), new ConfigListener(this)); + LOG.debug("NETCONF keystore configuration listener started"); } + if (rpcSingleton == null) { + rpcSingleton = cssProvider.registerClusterSingletonService( + new RpcSingleton(dataBroker, rpcProvider, encryptionService)); + LOG.debug("NETCONF keystore configuration singleton registered"); + } + LOG.info("NETCONF keystore service started"); } protected final void stop() { + final var singleton = rpcSingleton; + if (singleton != null) { + rpcSingleton = null; + singleton.close(); + } final var listener = configListener; if (listener != null) { configListener = null; diff --git a/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/AbstractRpc.java b/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/AbstractRpc.java new file mode 100644 index 0000000000..b2db630f36 --- /dev/null +++ b/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/AbstractRpc.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.netconf.keystore.legacy; + +import static java.util.Objects.requireNonNull; + +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.mdsal.binding.api.DataBroker; +import org.opendaylight.mdsal.binding.api.WriteTransaction; + +abstract class AbstractRpc { + private final DataBroker dataBroker; + + AbstractRpc(final DataBroker dataBroker) { + this.dataBroker = requireNonNull(dataBroker); + } + + final @NonNull WriteTransaction newTransaction() { + return dataBroker.newWriteOnlyTransaction(); + } +} diff --git a/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/DefaultAddKeystoreEntry.java b/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/DefaultAddKeystoreEntry.java new file mode 100644 index 0000000000..828ebaf246 --- /dev/null +++ b/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/DefaultAddKeystoreEntry.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2024 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.netconf.keystore.legacy; + +import static java.util.Objects.requireNonNull; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.util.ArrayList; +import java.util.Base64; +import org.opendaylight.aaa.encrypt.AAAEncryptionService; +import org.opendaylight.mdsal.binding.api.DataBroker; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddKeystoreEntry; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddKeystoreEntryInput; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddKeystoreEntryOutput; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddKeystoreEntryOutputBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.Keystore; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.keystore.entry.KeyCredential; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.keystore.entry.KeyCredentialBuilder; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.common.ErrorType; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.common.RpcResultBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class DefaultAddKeystoreEntry extends AbstractRpc implements AddKeystoreEntry { + private static final Logger LOG = LoggerFactory.getLogger(DefaultAddKeystoreEntry.class); + + private final AAAEncryptionService encryptionService; + + DefaultAddKeystoreEntry(final DataBroker dataBroker, final AAAEncryptionService encryptionService) { + super(dataBroker); + this.encryptionService = requireNonNull(encryptionService); + } + + @Override + public ListenableFuture> invoke(final AddKeystoreEntryInput input) { + final var plain = input.getKeyCredential(); + if (plain == null || plain.isEmpty()) { + return RpcResultBuilder.success(new AddKeystoreEntryOutputBuilder().build()).buildFuture(); + } + + LOG.debug("Adding keypairs: {}", plain); + final var encrypted = new ArrayList(plain.size()); + for (var credential : plain.values()) { + final var keyId = credential.getKeyId(); + try { + encrypted.add(new KeyCredentialBuilder(credential) + .setPrivateKey(encryptString(credential.getPrivateKey())) + .setPassphrase(encryptString(credential.getPassphrase())) + .build()); + } catch (GeneralSecurityException e) { + LOG.debug("Cannot decrypt key credential {}}", credential, e); + return RpcResultBuilder.failed() + .withError(ErrorType.APPLICATION, "Failed to decrypt key " + keyId, e) + .buildFuture(); + } + } + + final var tx = newTransaction(); + for (var keypair : encrypted) { + tx.put(LogicalDatastoreType.CONFIGURATION, + InstanceIdentifier.create(Keystore.class).child(KeyCredential.class, keypair.key()), keypair); + } + + return tx.commit().transform(commitInfo -> { + LOG.debug("Updated keypairs: {}", plain.keySet()); + return RpcResultBuilder.success(new AddKeystoreEntryOutputBuilder().build()).build(); + }, MoreExecutors.directExecutor()); + } + + private String encryptString(final String plain) throws GeneralSecurityException { + return Base64.getEncoder().encodeToString(encryptionService.encrypt(plain.getBytes(StandardCharsets.UTF_8))); + } +} \ No newline at end of file diff --git a/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/DefaultAddPrivateKey.java b/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/DefaultAddPrivateKey.java new file mode 100644 index 0000000000..a8e4251f8c --- /dev/null +++ b/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/DefaultAddPrivateKey.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.netconf.keystore.legacy; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import org.opendaylight.mdsal.binding.api.DataBroker; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddPrivateKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddPrivateKeyInput; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddPrivateKeyOutput; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddPrivateKeyOutputBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.Keystore; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017._private.keys.PrivateKey; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.common.RpcResultBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class DefaultAddPrivateKey extends AbstractRpc implements AddPrivateKey { + private static final Logger LOG = LoggerFactory.getLogger(DefaultAddPrivateKey.class); + + DefaultAddPrivateKey(final DataBroker dataBroker) { + super(dataBroker); + } + + @Override + public ListenableFuture> invoke(final AddPrivateKeyInput input) { + final var keys = input.getPrivateKey(); + if (keys == null || keys.isEmpty()) { + return RpcResultBuilder.success(new AddPrivateKeyOutputBuilder().build()).buildFuture(); + } + + LOG.debug("Adding private keys: {}", keys); + final var tx = newTransaction(); + for (var key : keys.values()) { + tx.put(LogicalDatastoreType.CONFIGURATION, + InstanceIdentifier.create(Keystore.class).child(PrivateKey.class, key.key()), key); + } + + return tx.commit().transform(commitInfo -> { + LOG.debug("Added private keys: {}", keys.keySet()); + return RpcResultBuilder.success(new AddPrivateKeyOutputBuilder().build()).build(); + }, MoreExecutors.directExecutor()); + } +} \ No newline at end of file diff --git a/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/DefaultAddTrustedCertificate.java b/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/DefaultAddTrustedCertificate.java new file mode 100644 index 0000000000..795aeda223 --- /dev/null +++ b/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/DefaultAddTrustedCertificate.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.netconf.keystore.legacy; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import org.opendaylight.mdsal.binding.api.DataBroker; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddTrustedCertificate; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddTrustedCertificateInput; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddTrustedCertificateOutput; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddTrustedCertificateOutputBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.Keystore; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.trusted.certificates.TrustedCertificate; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.common.RpcResultBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class DefaultAddTrustedCertificate extends AbstractRpc implements AddTrustedCertificate { + private static final Logger LOG = LoggerFactory.getLogger(DefaultAddTrustedCertificate.class); + + DefaultAddTrustedCertificate(final DataBroker dataBroker) { + super(dataBroker); + } + + @Override + public ListenableFuture> invoke(final AddTrustedCertificateInput input) { + final var certs = input.getTrustedCertificate(); + if (certs == null || certs.isEmpty()) { + return RpcResultBuilder.success(new AddTrustedCertificateOutputBuilder().build()).buildFuture(); + } + + LOG.debug("Updating trusted certificates: {}", certs); + final var tx = newTransaction(); + for (var certificate : certs.values()) { + tx.put(LogicalDatastoreType.CONFIGURATION, + InstanceIdentifier.create(Keystore.class).child(TrustedCertificate.class, certificate.key()), + certificate); + } + + return tx.commit().transform(commitInfo -> { + LOG.debug("Updated trusted certificates: {}", certs.keySet()); + return RpcResultBuilder.success(new AddTrustedCertificateOutputBuilder().build()).build(); + }, MoreExecutors.directExecutor()); + } +} \ No newline at end of file diff --git a/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/DefaultRemoveKeystoreEntry.java b/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/DefaultRemoveKeystoreEntry.java new file mode 100644 index 0000000000..d9c53b62f8 --- /dev/null +++ b/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/DefaultRemoveKeystoreEntry.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.netconf.keystore.legacy; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import java.util.stream.Collectors; +import org.opendaylight.mdsal.binding.api.DataBroker; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.Keystore; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemoveKeystoreEntry; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemoveKeystoreEntryInput; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemoveKeystoreEntryOutput; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemoveKeystoreEntryOutputBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.keystore.entry.KeyCredential; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.keystore.entry.KeyCredentialKey; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.common.RpcResultBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class DefaultRemoveKeystoreEntry extends AbstractRpc implements RemoveKeystoreEntry { + private static final Logger LOG = LoggerFactory.getLogger(DefaultRemoveKeystoreEntry.class); + + DefaultRemoveKeystoreEntry(final DataBroker dataBroker) { + super(dataBroker); + } + + @Override + public ListenableFuture> invoke(final RemoveKeystoreEntryInput input) { + final var keyIds = input.getKeyId(); + if (keyIds == null || keyIds.isEmpty()) { + return RpcResultBuilder.success(new RemoveKeystoreEntryOutputBuilder().build()).buildFuture(); + } + + final var keys = keyIds.stream().map(KeyCredentialKey::new).collect(Collectors.toSet()); + LOG.debug("Removing keypairs: {}", keys); + final var tx = newTransaction(); + for (var key : keys) { + tx.delete(LogicalDatastoreType.CONFIGURATION, + InstanceIdentifier.create(Keystore.class).child(KeyCredential.class, key)); + } + + return tx.commit().transform(commitInfo -> { + LOG.debug("Removed keypairs: {}", keys); + return RpcResultBuilder.success(new RemoveKeystoreEntryOutputBuilder().build()).build(); + }, MoreExecutors.directExecutor()); + } +} \ No newline at end of file diff --git a/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/DefaultRemovePrivateKey.java b/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/DefaultRemovePrivateKey.java new file mode 100644 index 0000000000..04a3dbd9f0 --- /dev/null +++ b/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/DefaultRemovePrivateKey.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.netconf.keystore.legacy; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import java.util.stream.Collectors; +import org.opendaylight.mdsal.binding.api.DataBroker; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.Keystore; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemovePrivateKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemovePrivateKeyInput; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemovePrivateKeyOutput; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemovePrivateKeyOutputBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017._private.keys.PrivateKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017._private.keys.PrivateKeyKey; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.common.RpcResultBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class DefaultRemovePrivateKey extends AbstractRpc implements RemovePrivateKey { + private static final Logger LOG = LoggerFactory.getLogger(DefaultRemovePrivateKey.class); + + DefaultRemovePrivateKey(final DataBroker dataBroker) { + super(dataBroker); + } + + @Override + public ListenableFuture> invoke(final RemovePrivateKeyInput input) { + final var names = input.getName(); + if (names == null || names.isEmpty()) { + return RpcResultBuilder.success(new RemovePrivateKeyOutputBuilder().build()).buildFuture(); + } + + final var keys = names.stream().map(PrivateKeyKey::new).collect(Collectors.toSet()); + LOG.debug("Removing private keys: {}", keys); + + + final var tx = newTransaction(); + for (var key : keys) { + tx.delete(LogicalDatastoreType.CONFIGURATION, + InstanceIdentifier.create(Keystore.class).child(PrivateKey.class, key)); + } + + return tx.commit().transform(commitInfo -> { + LOG.debug("Removed trusted keys: {}", keys); + return RpcResultBuilder.success(new RemovePrivateKeyOutputBuilder().build()).build(); + }, MoreExecutors.directExecutor()); + } +} \ No newline at end of file diff --git a/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/DefaultRemoveTrustedCertificate.java b/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/DefaultRemoveTrustedCertificate.java new file mode 100644 index 0000000000..a95d9dee2d --- /dev/null +++ b/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/DefaultRemoveTrustedCertificate.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.netconf.keystore.legacy; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import java.util.stream.Collectors; +import org.opendaylight.mdsal.binding.api.DataBroker; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.Keystore; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemoveTrustedCertificate; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemoveTrustedCertificateInput; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemoveTrustedCertificateOutput; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemoveTrustedCertificateOutputBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.trusted.certificates.TrustedCertificate; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.trusted.certificates.TrustedCertificateKey; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.common.RpcResultBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class DefaultRemoveTrustedCertificate extends AbstractRpc implements RemoveTrustedCertificate { + private static final Logger LOG = LoggerFactory.getLogger(DefaultRemoveTrustedCertificate.class); + + DefaultRemoveTrustedCertificate(final DataBroker dataBroker) { + super(dataBroker); + } + + @Override + public ListenableFuture> invoke( + final RemoveTrustedCertificateInput input) { + final var names = input.getName(); + if (names == null || names.isEmpty()) { + return RpcResultBuilder.success(new RemoveTrustedCertificateOutputBuilder().build()).buildFuture(); + } + + final var keys = names.stream().map(TrustedCertificateKey::new).collect(Collectors.toSet()); + LOG.debug("Removing trusted certificates: {}", keys); + final var tx = newTransaction(); + for (var key : keys) { + tx.delete(LogicalDatastoreType.CONFIGURATION, + InstanceIdentifier.create(Keystore.class).child(TrustedCertificate.class, key)); + } + + return tx.commit().transform(commitInfo -> { + LOG.debug("Removed trusted certificates: {}", keys); + return RpcResultBuilder.success(new RemoveTrustedCertificateOutputBuilder().build()).build(); + }, MoreExecutors.directExecutor()); + } +} \ No newline at end of file diff --git a/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/RpcSingleton.java b/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/RpcSingleton.java new file mode 100644 index 0000000000..e49c714245 --- /dev/null +++ b/keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/RpcSingleton.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2024 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.netconf.keystore.legacy; + +import static java.util.Objects.requireNonNull; + +import com.google.common.collect.ImmutableClassToInstanceMap; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.aaa.encrypt.AAAEncryptionService; +import org.opendaylight.mdsal.binding.api.DataBroker; +import org.opendaylight.mdsal.binding.api.RpcProviderService; +import org.opendaylight.mdsal.singleton.api.ClusterSingletonService; +import org.opendaylight.mdsal.singleton.api.ServiceGroupIdentifier; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddKeystoreEntry; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddPrivateKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddTrustedCertificate; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemoveKeystoreEntry; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemovePrivateKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemoveTrustedCertificate; +import org.opendaylight.yangtools.concepts.Registration; +import org.opendaylight.yangtools.yang.binding.Rpc; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class RpcSingleton implements ClusterSingletonService { + private static final Logger LOG = LoggerFactory.getLogger(RpcSingleton.class); + private static final @NonNull ServiceGroupIdentifier SGI = new ServiceGroupIdentifier("netconf-keystore-rpc"); + + private final AAAEncryptionService encryptionService; + private final RpcProviderService rpcProvider; + private final DataBroker dataBroker; + + private Registration reg; + + RpcSingleton(final DataBroker dataBroker, final RpcProviderService rpcProvider, + final AAAEncryptionService encryptionService) { + this.dataBroker = requireNonNull(dataBroker); + this.rpcProvider = requireNonNull(rpcProvider); + this.encryptionService = requireNonNull(encryptionService); + } + + @Override + public ServiceGroupIdentifier getIdentifier() { + return SGI; + } + + @Override + public void instantiateServiceInstance() { + if (reg != null) { + LOG.warn("Instiatiating while already running, very weird", new Throwable()); + return; + } + + LOG.debug("Registering RPC implementations"); + reg = rpcProvider.registerRpcImplementations(ImmutableClassToInstanceMap.>builder() + .put(AddKeystoreEntry.class, new DefaultAddKeystoreEntry(dataBroker, encryptionService)) + .put(RemoveKeystoreEntry.class, new DefaultRemoveKeystoreEntry(dataBroker)) + .put(AddPrivateKey.class, new DefaultAddPrivateKey(dataBroker)) + .put(RemovePrivateKey.class, new DefaultRemovePrivateKey(dataBroker)) + .put(AddTrustedCertificate.class, new DefaultAddTrustedCertificate(dataBroker)) + .put(RemoveTrustedCertificate.class, new DefaultRemoveTrustedCertificate(dataBroker)) + .build()); + LOG.info("This node is now owning NETCONF keystore configuration"); + } + + @Override + public ListenableFuture closeServiceInstance() { + if (reg != null) { + LOG.debug("Unregistering RPC implementations"); + reg.close(); + reg = null; + LOG.info("This node is no longer owning NETCONF keystore configuration"); + } + return Futures.immediateVoidFuture(); + } +} diff --git a/keystore/keystore-legacy/src/test/java/org/opendaylight/netconf/keystore/legacy/NetconfKeystoreRpcsTest.java b/keystore/keystore-legacy/src/test/java/org/opendaylight/netconf/keystore/legacy/NetconfKeystoreRpcsTest.java new file mode 100644 index 0000000000..5396420339 --- /dev/null +++ b/keystore/keystore-legacy/src/test/java/org/opendaylight/netconf/keystore/legacy/NetconfKeystoreRpcsTest.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2018 ZTE Corporation. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.netconf.keystore.legacy; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.opendaylight.mdsal.common.api.CommitInfo.emptyFluentFuture; + +import java.util.ArrayList; +import java.util.HashMap; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.opendaylight.aaa.encrypt.AAAEncryptionService; +import org.opendaylight.mdsal.binding.api.DataBroker; +import org.opendaylight.mdsal.binding.api.RpcProviderService; +import org.opendaylight.mdsal.binding.api.WriteTransaction; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.netconf.api.xml.XmlUtil; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddPrivateKeyInput; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddPrivateKeyInputBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddTrustedCertificateInput; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddTrustedCertificateInputBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017._private.keys.PrivateKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017._private.keys.PrivateKeyBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017._private.keys.PrivateKeyKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.trusted.certificates.TrustedCertificate; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.trusted.certificates.TrustedCertificateBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.trusted.certificates.TrustedCertificateKey; +import org.opendaylight.yangtools.concepts.ObjectRegistration; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +@ExtendWith(MockitoExtension.class) +class NetconfKeystoreRpcsTest { + private static final String XML_ELEMENT_PRIVATE_KEY = "private-key"; + private static final String XML_ELEMENT_NAME = "name"; + private static final String XML_ELEMENT_DATA = "data"; + private static final String XML_ELEMENT_CERT_CHAIN = "certificate-chain"; + private static final String XML_ELEMENT_TRUSTED_CERT = "trusted-certificate"; + private static final String XML_ELEMENT_CERT = "certificate"; + + private static Document KEYSTORE; + + @Mock + private WriteTransaction writeTx; + @Mock + private DataBroker dataBroker; + @Mock + private AAAEncryptionService encryptionService; + @Mock + private RpcProviderService rpcProvider; + @Mock + private ObjectRegistration rpcReg; + + @BeforeAll + static void beforeAll() throws Exception { + KEYSTORE = XmlUtil.readXmlToDocument( + NetconfKeystoreRpcsTest.class.getResourceAsStream("/netconf-keystore.xml")); + } + + @BeforeEach + void beforeEach() { + doReturn(writeTx).when(dataBroker).newWriteOnlyTransaction(); + } + + @Test + void testAddPrivateKey() throws Exception { + doReturn(emptyFluentFuture()).when(writeTx).commit(); + + final var rpc = new DefaultAddPrivateKey(dataBroker); + final var input = getPrivateKeyInput(); + rpc.invoke(input).get(); + + verify(writeTx, times(input.nonnullPrivateKey().size())) + .put(any(LogicalDatastoreType.class), any(InstanceIdentifier.class), any(DataObject.class)); + } + + private static AddPrivateKeyInput getPrivateKeyInput() throws Exception { + final var privateKeys = new HashMap(); + final var nodeList = KEYSTORE.getElementsByTagName(XML_ELEMENT_PRIVATE_KEY); + for (int i = 0; i < nodeList.getLength(); i++) { + if (nodeList.item(i) instanceof Element element) { + final var keyName = element.getElementsByTagName(XML_ELEMENT_NAME).item(0).getTextContent(); + final var keyData = element.getElementsByTagName(XML_ELEMENT_DATA).item(0).getTextContent(); + final var certNodes = element.getElementsByTagName(XML_ELEMENT_CERT_CHAIN); + final var certChain = new ArrayList(); + for (int j = 0; j < certNodes.getLength(); j++) { + if (certNodes.item(j) instanceof Element certElement) { + certChain.add(certElement.getTextContent()); + } + } + + final var key = new PrivateKeyKey(keyName); + privateKeys.put(key, new PrivateKeyBuilder() + .withKey(key) + .setData(keyData) + .setCertificateChain(certChain) + .build()); + } + } + + return new AddPrivateKeyInputBuilder().setPrivateKey(privateKeys).build(); + } + + @Test + void testAddTrustedCertificate() throws Exception { + doReturn(emptyFluentFuture()).when(writeTx).commit(); + + final var rpc = new DefaultAddTrustedCertificate(dataBroker); + final var input = getTrustedCertificateInput(); + rpc.invoke(input).get(); + + verify(writeTx, times(input.nonnullTrustedCertificate().size())) + .put(any(LogicalDatastoreType.class), any(InstanceIdentifier.class), any(DataObject.class)); + } + + private static AddTrustedCertificateInput getTrustedCertificateInput() throws Exception { + final var trustedCertificates = new HashMap(); + final var nodeList = KEYSTORE.getElementsByTagName(XML_ELEMENT_TRUSTED_CERT); + for (int i = 0; i < nodeList.getLength(); i++) { + if (nodeList.item(i) instanceof Element element) { + final var certName = element.getElementsByTagName(XML_ELEMENT_NAME).item(0).getTextContent(); + final var certData = element.getElementsByTagName(XML_ELEMENT_CERT).item(0).getTextContent(); + + final var key = new TrustedCertificateKey(certName); + trustedCertificates.put(key, new TrustedCertificateBuilder() + .withKey(key) + .setName(certName) + .setCertificate(certData) + .build()); + } + } + + return new AddTrustedCertificateInputBuilder().setTrustedCertificate(trustedCertificates).build(); + } +} diff --git a/keystore/keystore-legacy/src/test/resources/netconf-keystore.xml b/keystore/keystore-legacy/src/test/resources/netconf-keystore.xml new file mode 100644 index 0000000000..250d9f813d --- /dev/null +++ b/keystore/keystore-legacy/src/test/resources/netconf-keystore.xml @@ -0,0 +1,75 @@ + + + test_private_key + MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCxrZcfyb8hPfiA +Sa1FNqboboPfgOv6E6KJpqpsgbowbEGnw6fOJnx9otfrVmAP86YUT6i+nuPwNG/b +I5YHqxd+OMllmvt0DX1tbtHMirx0wFcVXfk4hruDEMWzsUTohrpiQFTrq/kb9Dr4 +FxO+/Ddt+YMmUhu1WGPAhOun2VErwGF6HuYfBMji3QvSzS0Hn9Fez1/nY88CzQrz +lPUH7FMVYIP/rzovKJT/5Bnj+YiiNMKYuPoyvBmyPylFeCvAhF5uyGDjLGYDkscQ +MzFDu44ebtcnVK9BZUxgWUDVwm5FjDSDjdcWKla7N46ykITvyBelm96oY38KkNaV +6x/e/4KZAgMBAAECggEBAJdNO2PWaOvl2bdlBifqYjeL5MBvCIPsNH0DcBz2W5bg +mQhDlKH4JArYoQXGiAcNdF/Xddrdcz0ZaicyJpBhIaBauyXK1FX/JtAJjp6fhdvl +7kJDw/ZexU7W+YQLcKKSGCWSor4NtBQZ5h1diXMZVBpSX1xCj1Xd7xQCHKrSZEzc +Geqx7ew1jkkHL+K6MunyQrY2fb6snWkyGEJf3VyjRjzzNubmF7dR7p+fo6NpeTD4 +ywEAenkTtw55gwtz2q5pTV3I11ZJHcFjfbLY3SONz86x5FwNODVzaTrLN0JAvkXz +1LqeZbGRnKQ2/emlX1Gt9z8AzlissqJS8JgvQNm+AAECgYEA5bIEJfFDDvBWFPbh +gp5QmX9v9n2jRRX6mEdRPMIbfLfh9NAg3dc5l+yxQaAzSJHrOxe2eciwUGNhE5yb +Spub9o66ZSjAB58GzMI3LIrMUWufp5Ua7ztl3SuMhoTPlAMuqe69q4RMC0KUW9aE +LF4fs2fAm62dv15Z0pOsnR0CdSkCgYEAxgaVD055iAutKXSugI63hvP0T6DAHcnQ +rgdPUT4uCQGibnqUPRbcnNkBNxORRmap12K39B0QQ0eZTpdKNYmWOSnvNSBwiJ6r +ZRCF7UCqArub3nrONSYXXfd+szVjlYgB0paiOUtzEtt7s+JYV2+KswIAx0QwjynX +OPLVAF4tX/ECgYEAtPirwgUzU2rSN9RH2vTHBhlc6nUUlVL6zM2r2NYKeBoc4hi1 +PHPdQbDP+6evoCavkjBdqdgP6lZSXvRNedveZsUPYLJZkeeeoOcIN4Tn8+J6uLuG +rCQ9XqN4JWgwcCqNsn+SWrdyfpCneTArlRVXnq9JFp8UoXlCBeIp5uO7UvkCgYEA +kE7Vq4zlldXkf/RvAnJ+nhMDtE+SEWMz9s6O58anZ5rQUzBy/L2/UXH2p7tTv/kq +xjJDmdrgMhdoSlSIGNHGLqw3jQCx4W23u3O6FXZtLoanhQ77XNIAb1Lf+xrqEltF +8MAjQhuQpWpbEHDfLgC0E9Ve2dgAhyPXmsGjpZv79xECgYAjX+cUFoexgkzhilSc +xyI2GI46IZ1NIFBCMuTMSTkEpBotO1ooUT4ZrR2mdIsoc3O5X5y63ZI1CWMwfd4u +buB0qy91s1i4AMW3JlB/jOX++y7YVwUX2aHHHl9fljFJBYqSxUatPDSuo0r42SXq +sD2PXwX43X/g2QxQhP2l9nfSmg== + MIIDgTCCAmmgAwIBAgIEb3Rj1DANBgkqhkiG9w0BAQsFADBxMQswCQYDVQQGEwJD +TjEQMA4GA1UECBMHSklBTkdTVTEQMA4GA1UEBxMHTkFOSklORzEVMBMGA1UEChMM +b3BlbmRheWxpZ2h0MRAwDgYDVQQLEwduZXRjb25mMRUwEwYDVQQDEwxvcGVuZGF5 +bGlnaHQwHhcNMTcxMTIwMDkyNDM3WhcNMjcxMTE4MDkyNDM3WjBxMQswCQYDVQQG +EwJDTjEQMA4GA1UECBMHSklBTkdTVTEQMA4GA1UEBxMHTkFOSklORzEVMBMGA1UE +ChMMb3BlbmRheWxpZ2h0MRAwDgYDVQQLEwduZXRjb25mMRUwEwYDVQQDEwxvcGVu +ZGF5bGlnaHQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCxrZcfyb8h +PfiASa1FNqboboPfgOv6E6KJpqpsgbowbEGnw6fOJnx9otfrVmAP86YUT6i+nuPw +NG/bI5YHqxd+OMllmvt0DX1tbtHMirx0wFcVXfk4hruDEMWzsUTohrpiQFTrq/kb +9Dr4FxO+/Ddt+YMmUhu1WGPAhOun2VErwGF6HuYfBMji3QvSzS0Hn9Fez1/nY88C +zQrzlPUH7FMVYIP/rzovKJT/5Bnj+YiiNMKYuPoyvBmyPylFeCvAhF5uyGDjLGYD +kscQMzFDu44ebtcnVK9BZUxgWUDVwm5FjDSDjdcWKla7N46ykITvyBelm96oY38K +kNaV6x/e/4KZAgMBAAGjITAfMB0GA1UdDgQWBBTjjrH57CtARfDWvpmkmv7lGL/2 +rDANBgkqhkiG9w0BAQsFAAOCAQEApzyNLC/jh5UhWh9VvQW19Fbn9zTcSiCRtgwf +eUXpQPukLfL0eDOK0kBLIvN8lQtu7nH/3aYTaZwHMOpahDfSe2Q0eZPxr9hX7Hlo +kVOtQ88iW3c/KUF7TVjC16eBeNqvMsBCZ45j0al3QxP40iFPvO576HxRLuYKaB3k ++CNI6bn1m5SKoToQgbQbqrALFE3zud7iaUSiLAPSCLBqvEg50pLbbxc6nHXmHjqp +alTeRo89Ph0k0jnHzsW+GlefH4MGS3E2MZG0i1jh/+JoTC6DV2g/vNI4Yry7YFa0 +WCCnuFcJwlvd12rkxiNI78buwMzEGcOU16wXeDRJxOM+UbF9xA== + + + test_trusted_cert + MIIECTCCAvGgAwIBAgIBCDANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UEBhMCQ1ox +FjAUBgNVBAgMDVNvdXRoIE1vcmF2aWExDTALBgNVBAcMBEJybm8xDzANBgNVBAoM +BkNFU05FVDEMMAoGA1UECwwDVE1DMRMwEQYDVQQDDApleGFtcGxlIENBMSIwIAYJ +KoZIhvcNAQkBFhNleGFtcGxlY2FAbG9jYWxob3N0MB4XDTE1MDczMDA3MjU1MFoX +DTM1MDcyNTA3MjU1MFowgYUxCzAJBgNVBAYTAkNaMRYwFAYDVQQIDA1Tb3V0aCBN +b3JhdmlhMQ8wDQYDVQQKDAZDRVNORVQxDDAKBgNVBAsMA1RNQzEXMBUGA1UEAwwO +ZXhhbXBsZSBzZXJ2ZXIxJjAkBgkqhkiG9w0BCQEWF2V4YW1wbGVzZXJ2ZXJAbG9j +YWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsdI1TBjzX1Pg +QXFuPCw5/kQwU7qkrhirMcFAXhI8EoXepPa9fKAVuMjHW32P6nNzDpnhFe0YGdNl +oIEN3hJJ87cVOqj4o7zZMbq3zVG2L8As7MTA8tYXm2fSC/0rIxxRRemcGUXM0q+4 +LEACjZj2pOKonaivF5VbhgNjPCO1Jj/TamUc0aViE577C9L9EiObGM+bGbabWk/K +WKLsvxUc+sKZXaJ7psTVgpggJAkUszlmwOQgFiMSR53E9/CAkQYhzGVCmH44Vs6H +zs3RZjOTbce4wr4ongiA5LbPeSNSCFjy9loKpaE1rtOjkNBVdiNPCQTmLuODXUTK +gkeL+9v/OwIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVu +U1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU83qEtQDFzDvLoaII +vqiU6k7j1uswHwYDVR0jBBgwFoAUc1YQIqjZsHVwlea0AB4N+ilNI2gwDQYJKoZI +hvcNAQELBQADggEBAJ+QOLi4gPWGofMkLTqSsbv5xRvTw0xa/sJnEeiejtygAu3o +McAsyevSH9EYVPCANxzISPzd9SFaO56HxWgcxLn9vi8ZNvo2wIp9zucNu285ced1 +K/2nDZfBmvBxXnj/n7spwqOyuoIc8sR7P7YyI806Qsfhk3ybNZE5UHJFZKDRQKvR +J1t4nk9saeo87kIuNEDfYNdwYZzRfXoGJ5qIJQK+uJJv9noaIhfFowDW/G14Ji5p +Vh/YtvnOPh7aBjOj8jmzk8MqzK+TZgT7GWu48Nd/NaV8g/DNg9hlN047LaNsJly3 +NX3+VBlpMnA4rKwl1OnmYSirIVh9RJqNwqe6k/k= + + diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DefaultSslHandlerFactoryProvider.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DefaultSslHandlerFactoryProvider.java index f26655eb2e..106b8c8b0c 100644 --- a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DefaultSslHandlerFactoryProvider.java +++ b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DefaultSslHandlerFactoryProvider.java @@ -19,7 +19,10 @@ import javax.annotation.PreDestroy; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.aaa.encrypt.AAAEncryptionService; import org.opendaylight.mdsal.binding.api.DataBroker; +import org.opendaylight.mdsal.binding.api.RpcProviderService; +import org.opendaylight.mdsal.singleton.api.ClusterSingletonServiceProvider; import org.opendaylight.netconf.client.SslHandlerFactory; import org.opendaylight.netconf.client.mdsal.api.SslHandlerFactoryProvider; import org.opendaylight.netconf.keystore.legacy.AbstractNetconfKeystore; @@ -43,8 +46,11 @@ public final class DefaultSslHandlerFactoryProvider extends AbstractNetconfKeyst @Inject @Activate - public DefaultSslHandlerFactoryProvider(@Reference final DataBroker dataBroker) { - start(dataBroker); + public DefaultSslHandlerFactoryProvider(@Reference final DataBroker dataBroker, + @Reference final RpcProviderService rpcProvider, + @Reference final ClusterSingletonServiceProvider cssProvider, + @Reference final AAAEncryptionService encryptionService) { + start(dataBroker, rpcProvider, cssProvider, encryptionService); } @Deactivate diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/NetconfSalKeystoreRpcs.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/NetconfSalKeystoreRpcs.java deleted file mode 100644 index 691a319ec2..0000000000 --- a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/NetconfSalKeystoreRpcs.java +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Copyright (c) 2017 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.netconf.client.mdsal.impl; - -import static java.util.Objects.requireNonNull; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableClassToInstanceMap; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; -import com.google.common.util.concurrent.SettableFuture; -import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; -import java.util.ArrayList; -import java.util.Base64; -import javax.annotation.PreDestroy; -import javax.inject.Inject; -import javax.inject.Singleton; -import org.opendaylight.aaa.encrypt.AAAEncryptionService; -import org.opendaylight.mdsal.binding.api.DataBroker; -import org.opendaylight.mdsal.binding.api.RpcProviderService; -import org.opendaylight.mdsal.binding.api.WriteTransaction; -import org.opendaylight.mdsal.common.api.CommitInfo; -import org.opendaylight.mdsal.common.api.LogicalDatastoreType; -import org.opendaylight.mdsal.dom.api.DefaultDOMRpcException; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddKeystoreEntry; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddKeystoreEntryInput; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddKeystoreEntryOutput; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddKeystoreEntryOutputBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddPrivateKey; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddPrivateKeyInput; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddPrivateKeyOutput; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddPrivateKeyOutputBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddTrustedCertificate; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddTrustedCertificateInput; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddTrustedCertificateOutput; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddTrustedCertificateOutputBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.Keystore; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemoveKeystoreEntry; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemoveKeystoreEntryInput; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemoveKeystoreEntryOutput; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemoveKeystoreEntryOutputBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemovePrivateKey; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemovePrivateKeyInput; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemovePrivateKeyOutput; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemovePrivateKeyOutputBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemoveTrustedCertificate; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemoveTrustedCertificateInput; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemoveTrustedCertificateOutput; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemoveTrustedCertificateOutputBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017._private.keys.PrivateKey; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017._private.keys.PrivateKeyKey; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.keystore.entry.KeyCredential; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.keystore.entry.KeyCredentialBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.keystore.entry.KeyCredentialKey; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.trusted.certificates.TrustedCertificate; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.trusted.certificates.TrustedCertificateKey; -import org.opendaylight.yangtools.concepts.Registration; -import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; -import org.opendaylight.yangtools.yang.binding.Rpc; -import org.opendaylight.yangtools.yang.common.RpcResult; -import org.opendaylight.yangtools.yang.common.RpcResultBuilder; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Deactivate; -import org.osgi.service.component.annotations.Reference; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@Singleton -@Component(service = { }) -public final class NetconfSalKeystoreRpcs implements AutoCloseable { - private static final Logger LOG = LoggerFactory.getLogger(NetconfSalKeystoreRpcs.class); - private static final InstanceIdentifier KEYSTORE_IID = InstanceIdentifier.create(Keystore.class); - - // FIXME: we are populating config datastore, but there may be risks with concurrent access. We really should be - // using cluster singleton service here. - private final DataBroker dataBroker; - private final AAAEncryptionService encryptionService; - private final Registration reg; - - @Inject - @Activate - public NetconfSalKeystoreRpcs(@Reference final DataBroker dataBroker, - @Reference final AAAEncryptionService encryptionService, @Reference final RpcProviderService rpcProvider) { - this.dataBroker = requireNonNull(dataBroker); - this.encryptionService = requireNonNull(encryptionService); - - reg = rpcProvider.registerRpcImplementations(ImmutableClassToInstanceMap.>builder() - .put(RemoveKeystoreEntry.class, this::removeKeystoreEntry) - .put(AddKeystoreEntry.class, this::addKeystoreEntry) - .put(AddTrustedCertificate.class, this::addTrustedCertificate) - .put(RemoveTrustedCertificate.class, this::removeTrustedCertificate) - .put(AddPrivateKey.class, this::addPrivateKey) - .put(RemovePrivateKey.class, this::removePrivateKey) - .build()); - LOG.info("NETCONF keystore service started"); - } - - @PreDestroy - @Deactivate - @Override - public void close() { - reg.close(); - LOG.info("NETCONF keystore service stopped"); - } - - private ListenableFuture> removeKeystoreEntry( - final RemoveKeystoreEntryInput input) { - LOG.debug("Removing keypairs: {}", input); - - final WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction(); - - for (final String id : input.getKeyId()) { - writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, - KEYSTORE_IID.child(KeyCredential.class, new KeyCredentialKey(id))); - } - - final SettableFuture> rpcResult = SettableFuture.create(); - - writeTransaction.commit().addCallback(new FutureCallback() { - @Override - public void onSuccess(final CommitInfo result) { - LOG.debug("remove-key-pair success. Input: {}", input); - rpcResult.set(RpcResultBuilder.success(new RemoveKeystoreEntryOutputBuilder().build()).build()); - } - - @Override - public void onFailure(final Throwable throwable) { - LOG.warn("remove-key-pair failed. Input: {}", input, throwable); - rpcResult.setException(throwable); - } - }, MoreExecutors.directExecutor()); - - return rpcResult; - } - - private ListenableFuture> addKeystoreEntry(final AddKeystoreEntryInput input) { - LOG.debug("Adding keypairs: {}", input); - - final var plain = input.nonnullKeyCredential(); - final var encrypted = new ArrayList(plain.size()); - for (var credential : plain.values()) { - try { - encrypted.add(new KeyCredentialBuilder(credential) - .setPrivateKey(encryptString(credential.getPrivateKey())) - .setPassphrase(encryptString(credential.getPassphrase())) - .build()); - } catch (GeneralSecurityException e) { - return Futures.immediateFailedFuture(new DefaultDOMRpcException("Failed to decrypt " + credential, e)); - } - } - - final var writeTransaction = dataBroker.newWriteOnlyTransaction(); - for (var keypair : encrypted) { - writeTransaction.merge(LogicalDatastoreType.CONFIGURATION, - KEYSTORE_IID.child(KeyCredential.class, keypair.key()), keypair); - } - - final var rpcResult = SettableFuture.>create(); - writeTransaction.commit().addCallback(new FutureCallback() { - @Override - public void onSuccess(final CommitInfo result) { - LOG.debug("add-key-pair success. Input: {}", input); - rpcResult.set(RpcResultBuilder.success(new AddKeystoreEntryOutputBuilder().build()).build()); - } - - @Override - public void onFailure(final Throwable throwable) { - LOG.warn("add-key-pair failed. Input: {}", input, throwable); - rpcResult.setException(throwable); - } - }, MoreExecutors.directExecutor()); - return rpcResult; - } - - private String encryptString(final String plain) throws GeneralSecurityException { - return Base64.getEncoder().encodeToString(encryptionService.encrypt(plain.getBytes(StandardCharsets.UTF_8))); - } - - @VisibleForTesting - ListenableFuture> addTrustedCertificate( - final AddTrustedCertificateInput input) { - final WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction(); - - for (TrustedCertificate certificate : input.nonnullTrustedCertificate().values()) { - writeTransaction.merge(LogicalDatastoreType.CONFIGURATION, - KEYSTORE_IID.child(TrustedCertificate.class, certificate.key()), certificate); - } - - final SettableFuture> rpcResult = SettableFuture.create(); - - writeTransaction.commit().addCallback(new FutureCallback() { - @Override - public void onSuccess(final CommitInfo result) { - LOG.debug("add-trusted-certificate success. Input: {}", input); - rpcResult.set(RpcResultBuilder.success(new AddTrustedCertificateOutputBuilder().build()).build()); - } - - @Override - public void onFailure(final Throwable throwable) { - LOG.warn("add-trusted-certificate failed. Input: {}", input, throwable); - rpcResult.setException(throwable); - } - }, MoreExecutors.directExecutor()); - - return rpcResult; - } - - private ListenableFuture> removeTrustedCertificate( - final RemoveTrustedCertificateInput input) { - final WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction(); - - for (final String name : input.getName()) { - writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, - KEYSTORE_IID.child(TrustedCertificate.class, new TrustedCertificateKey(name))); - } - - final SettableFuture> rpcResult = SettableFuture.create(); - - writeTransaction.commit().addCallback(new FutureCallback() { - @Override - public void onSuccess(final CommitInfo result) { - LOG.debug("remove-trusted-certificate success. Input: {}", input); - rpcResult.set(RpcResultBuilder.success(new RemoveTrustedCertificateOutputBuilder().build()).build()); - } - - @Override - public void onFailure(final Throwable throwable) { - LOG.warn("remove-trusted-certificate failed. Input: {}", input, throwable); - rpcResult.setException(throwable); - } - }, MoreExecutors.directExecutor()); - - return rpcResult; - } - - @VisibleForTesting - ListenableFuture> addPrivateKey(final AddPrivateKeyInput input) { - final WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction(); - - for (PrivateKey key: input.nonnullPrivateKey().values()) { - writeTransaction.merge(LogicalDatastoreType.CONFIGURATION, - KEYSTORE_IID.child(PrivateKey.class, key.key()), key); - } - - final SettableFuture> rpcResult = SettableFuture.create(); - - writeTransaction.commit().addCallback(new FutureCallback() { - @Override - public void onSuccess(final CommitInfo result) { - LOG.debug("add-private-key success. Input: {}", input); - rpcResult.set(RpcResultBuilder.success(new AddPrivateKeyOutputBuilder().build()).build()); - } - - @Override - public void onFailure(final Throwable throwable) { - LOG.warn("add-private-key failed. Input: {}", input, throwable); - rpcResult.setException(throwable); - } - }, MoreExecutors.directExecutor()); - - return rpcResult; - } - - private ListenableFuture> removePrivateKey(final RemovePrivateKeyInput input) { - final WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction(); - - for (final String name : input.getName()) { - writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, - KEYSTORE_IID.child(PrivateKey.class, new PrivateKeyKey(name))); - } - - final SettableFuture> rpcResult = SettableFuture.create(); - - writeTransaction.commit().addCallback(new FutureCallback() { - @Override - public void onSuccess(final CommitInfo result) { - LOG.debug("remove-private-key success. Input: {}", input); - rpcResult.set(RpcResultBuilder.success(new RemovePrivateKeyOutputBuilder().build()).build()); - } - - @Override - public void onFailure(final Throwable throwable) { - LOG.warn("remove-private-key failed. Input: {}", input, throwable); - rpcResult.setException(throwable); - } - }, MoreExecutors.directExecutor()); - - return rpcResult; - } -} diff --git a/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/impl/DefaultSslHandlerFactoryProviderTest.java b/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/impl/DefaultSslHandlerFactoryProviderTest.java index 08f41b46db..c20414ec3b 100644 --- a/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/impl/DefaultSslHandlerFactoryProviderTest.java +++ b/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/impl/DefaultSslHandlerFactoryProviderTest.java @@ -24,10 +24,13 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.opendaylight.aaa.encrypt.AAAEncryptionService; import org.opendaylight.mdsal.binding.api.DataBroker; import org.opendaylight.mdsal.binding.api.DataObjectModification; import org.opendaylight.mdsal.binding.api.DataTreeChangeListener; import org.opendaylight.mdsal.binding.api.DataTreeModification; +import org.opendaylight.mdsal.binding.api.RpcProviderService; +import org.opendaylight.mdsal.singleton.api.ClusterSingletonServiceProvider; import org.opendaylight.netconf.api.xml.XmlUtil; import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.Keystore; import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017._private.keys.PrivateKey; @@ -52,6 +55,12 @@ class DefaultSslHandlerFactoryProviderTest { @Mock private DataBroker dataBroker; @Mock + private RpcProviderService rpcProvider; + @Mock + private ClusterSingletonServiceProvider cssProvider; + @Mock + private AAAEncryptionService encryptionService; + @Mock private Registration listenerRegistration; @Mock private DataTreeModification dataTreeModification1; @@ -76,9 +85,13 @@ class DefaultSslHandlerFactoryProviderTest { }).when(dataBroker).registerTreeChangeListener(any(), any()); } + private DefaultSslHandlerFactoryProvider newProvider() { + return new DefaultSslHandlerFactoryProvider(dataBroker, rpcProvider, cssProvider, encryptionService); + } + @Test void testKeystoreAdapterInit() throws Exception { - try (var keystoreAdapter = new DefaultSslHandlerFactoryProvider(dataBroker)) { + try (var keystoreAdapter = newProvider()) { final var ex = assertThrows(KeyStoreException.class, () -> keystoreAdapter.getJavaKeyStore(Set.of())); assertThat(ex.getMessage(), startsWith("No keystore private key found")); } @@ -94,7 +107,7 @@ class DefaultSslHandlerFactoryProviderTest { final var privateKey = getPrivateKey(); doReturn(privateKey).when(privateKeyModification).dataAfter(); - try (var keystoreAdapter = new DefaultSslHandlerFactoryProvider(dataBroker)) { + try (var keystoreAdapter = newProvider()) { listener.onDataTreeChanged(List.of(dataTreeModification1)); final var keyStore = keystoreAdapter.getJavaKeyStore(Set.of()); @@ -125,7 +138,7 @@ class DefaultSslHandlerFactoryProviderTest { final var trustedCertificate = getTrustedCertificate(); doReturn(trustedCertificate).when(trustedCertificateModification).dataAfter(); - try (var keystoreAdapter = new DefaultSslHandlerFactoryProvider(dataBroker)) { + try (var keystoreAdapter = newProvider()) { // Apply configurations listener.onDataTreeChanged(List.of(dataTreeModification1, dataTreeModification2)); diff --git a/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/impl/NetconfSalKeystoreRpcsTest.java b/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/impl/NetconfSalKeystoreRpcsTest.java deleted file mode 100644 index 18e357e4c9..0000000000 --- a/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/impl/NetconfSalKeystoreRpcsTest.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (c) 2018 ZTE Corporation. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.netconf.client.mdsal.impl; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.opendaylight.mdsal.common.api.CommitInfo.emptyFluentFuture; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import org.opendaylight.aaa.encrypt.AAAEncryptionService; -import org.opendaylight.mdsal.binding.api.DataBroker; -import org.opendaylight.mdsal.binding.api.RpcProviderService; -import org.opendaylight.mdsal.binding.api.WriteTransaction; -import org.opendaylight.mdsal.common.api.LogicalDatastoreType; -import org.opendaylight.netconf.api.xml.XmlUtil; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddPrivateKeyInput; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddPrivateKeyInputBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddTrustedCertificateInput; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddTrustedCertificateInputBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017._private.keys.PrivateKey; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017._private.keys.PrivateKeyBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017._private.keys.PrivateKeyKey; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.trusted.certificates.TrustedCertificate; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.trusted.certificates.TrustedCertificateBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.trusted.certificates.TrustedCertificateKey; -import org.opendaylight.yangtools.concepts.ObjectRegistration; -import org.opendaylight.yangtools.yang.binding.DataObject; -import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -@RunWith(MockitoJUnitRunner.StrictStubs.class) -public class NetconfSalKeystoreRpcsTest { - private static final String XML_ELEMENT_PRIVATE_KEY = "private-key"; - private static final String XML_ELEMENT_NAME = "name"; - private static final String XML_ELEMENT_DATA = "data"; - private static final String XML_ELEMENT_CERT_CHAIN = "certificate-chain"; - private static final String XML_ELEMENT_TRUSTED_CERT = "trusted-certificate"; - private static final String XML_ELEMENT_CERT = "certificate"; - - @Mock - private WriteTransaction writeTx; - @Mock - private DataBroker dataBroker; - @Mock - private AAAEncryptionService encryptionService; - @Mock - private RpcProviderService rpcProvider; - @Mock - private ObjectRegistration rpcReg; - - @Before - public void setUp() { - doReturn(writeTx).when(dataBroker).newWriteOnlyTransaction(); - doNothing().when(writeTx) - .merge(any(LogicalDatastoreType.class), any(InstanceIdentifier.class), any(DataObject.class)); - doReturn(rpcReg).when(rpcProvider).registerRpcImplementations(any()); - doNothing().when(rpcReg).close(); - } - - @Test - public void testAddPrivateKey() throws Exception { - doReturn(emptyFluentFuture()).when(writeTx).commit(); - try (var keystoreService = new NetconfSalKeystoreRpcs(dataBroker, encryptionService, rpcProvider)) { - final AddPrivateKeyInput input = getPrivateKeyInput(); - keystoreService.addPrivateKey(input).get(); - - verify(writeTx, times(input.nonnullPrivateKey().size())) - .merge(any(LogicalDatastoreType.class), any(InstanceIdentifier.class), any(DataObject.class)); - } - } - - @Test - public void testAddTrustedCertificate() throws Exception { - doReturn(emptyFluentFuture()).when(writeTx).commit(); - try (var keystoreService = new NetconfSalKeystoreRpcs(dataBroker, encryptionService, rpcProvider)) { - final var input = getTrustedCertificateInput(); - keystoreService.addTrustedCertificate(input).get(); - - verify(writeTx, times(input.nonnullTrustedCertificate().size())) - .merge(any(LogicalDatastoreType.class), any(InstanceIdentifier.class), any(DataObject.class)); - } - } - - private AddPrivateKeyInput getPrivateKeyInput() throws Exception { - final Map privateKeys = new HashMap<>(); - final Document document = readKeystoreXML(); - final NodeList nodeList = document.getElementsByTagName(XML_ELEMENT_PRIVATE_KEY); - for (int i = 0; i < nodeList.getLength(); i++) { - final Node node = nodeList.item(i); - if (node.getNodeType() != Node.ELEMENT_NODE) { - continue; - } - final Element element = (Element)node; - final String keyName = element.getElementsByTagName(XML_ELEMENT_NAME).item(0).getTextContent(); - final String keyData = element.getElementsByTagName(XML_ELEMENT_DATA).item(0).getTextContent(); - final NodeList certNodes = element.getElementsByTagName(XML_ELEMENT_CERT_CHAIN); - final List certChain = new ArrayList<>(); - for (int j = 0; j < certNodes.getLength(); j++) { - final Node certNode = certNodes.item(j); - if (certNode.getNodeType() != Node.ELEMENT_NODE) { - continue; - } - certChain.add(certNode.getTextContent()); - } - - final PrivateKeyKey key = new PrivateKeyKey(keyName); - privateKeys.put(key, new PrivateKeyBuilder() - .withKey(key) - .setData(keyData) - .setCertificateChain(certChain) - .build()); - } - - return new AddPrivateKeyInputBuilder().setPrivateKey(privateKeys).build(); - } - - private AddTrustedCertificateInput getTrustedCertificateInput() throws Exception { - final Map trustedCertificates = new HashMap<>(); - final Document document = readKeystoreXML(); - final NodeList nodeList = document.getElementsByTagName(XML_ELEMENT_TRUSTED_CERT); - for (int i = 0; i < nodeList.getLength(); i++) { - final Node node = nodeList.item(i); - if (node.getNodeType() != Node.ELEMENT_NODE) { - continue; - } - final Element element = (Element)node; - final String certName = element.getElementsByTagName(XML_ELEMENT_NAME).item(0).getTextContent(); - final String certData = element.getElementsByTagName(XML_ELEMENT_CERT).item(0).getTextContent(); - - final TrustedCertificateKey key = new TrustedCertificateKey(certName); - trustedCertificates.put(key, new TrustedCertificateBuilder() - .withKey(key) - .setName(certName) - .setCertificate(certData) - .build()); - } - - return new AddTrustedCertificateInputBuilder().setTrustedCertificate(trustedCertificates).build(); - } - - private Document readKeystoreXML() throws Exception { - return XmlUtil.readXmlToDocument(getClass().getResourceAsStream("/netconf-keystore.xml")); - } -} -- 2.36.6