Decrypt key credentials in keystore-legacy 28/110128/4
authorRobert Varga <robert.varga@pantheon.tech>
Fri, 2 Feb 2024 20:54:55 +0000 (21:54 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Sun, 4 Feb 2024 13:49:01 +0000 (14:49 +0100)
This moves the decryption and decoding logic into keystore-legacy, so
that the lifecycle is controlled.

JIRA: NETCONF-1237
Change-Id: Ib39d034f87f98114aff1106490d237eea99d0940
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
15 files changed:
apps/netconf-topology/src/main/java/org/opendaylight/netconf/topology/spi/NetconfClientConfigurationBuilderFactoryImpl.java
keystore/keystore-legacy/pom.xml
keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/NetconfKeystore.java
keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/impl/ConfigListener.java
keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/impl/DefaultNetconfKeystoreService.java
keystore/keystore-legacy/src/main/java/org/opendaylight/netconf/keystore/legacy/impl/SecurityHelper.java
keystore/keystore-legacy/src/test/java/org/opendaylight/netconf/keystore/legacy/impl/SecurityHelperTest.java [moved from apps/netconf-topology/src/test/java/org/opendaylight/netconf/topology/spi/PKIUtilTest.java with 86% similarity]
keystore/keystore-legacy/src/test/resources/pki/dsa [moved from apps/netconf-topology/src/test/resources/pki/dsa with 100% similarity]
keystore/keystore-legacy/src/test/resources/pki/dsa_encrypted [moved from apps/netconf-topology/src/test/resources/pki/dsa_encrypted with 100% similarity]
keystore/keystore-legacy/src/test/resources/pki/ecdsa [moved from apps/netconf-topology/src/test/resources/pki/ecdsa with 100% similarity]
keystore/keystore-legacy/src/test/resources/pki/ecdsa_encrypted [moved from apps/netconf-topology/src/test/resources/pki/ecdsa_encrypted with 100% similarity]
keystore/keystore-legacy/src/test/resources/pki/rsa [moved from apps/netconf-topology/src/test/resources/pki/rsa with 100% similarity]
keystore/keystore-legacy/src/test/resources/pki/rsa_encrypted [moved from apps/netconf-topology/src/test/resources/pki/rsa_encrypted with 100% similarity]
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/CredentialProvider.java
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DefaultCredentialProvider.java

index 1df8b9450d80b0cb50c2dce4f7e134845a691a6d..d9562698c37fa3050aaa6cbb97043ab8ab630bdf 100644 (file)
@@ -9,25 +9,11 @@ package org.opendaylight.netconf.topology.spi;
 
 import static java.util.Objects.requireNonNull;
 
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Strings;
-import java.io.IOException;
-import java.io.StringReader;
 import java.nio.charset.StandardCharsets;
 import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.security.Provider;
-import java.security.Security;
-import java.util.Base64;
 import java.util.List;
 import javax.inject.Inject;
 import javax.inject.Singleton;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.openssl.PEMEncryptedKeyPair;
-import org.bouncycastle.openssl.PEMKeyPair;
-import org.bouncycastle.openssl.PEMParser;
-import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
-import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
 import org.opendaylight.aaa.encrypt.AAAEncryptionService;
 import org.opendaylight.netconf.client.conf.NetconfClientConfiguration.NetconfClientProtocol;
 import org.opendaylight.netconf.client.conf.NetconfClientConfigurationBuilder;
@@ -58,13 +44,6 @@ import org.osgi.service.component.annotations.Reference;
 @Component
 @Singleton
 public final class NetconfClientConfigurationBuilderFactoryImpl implements NetconfClientConfigurationBuilderFactory {
-    private static final Provider BCPROV;
-
-    static {
-        final var prov = Security.getProvider(BouncyCastleProvider.PROVIDER_NAME);
-        BCPROV = prov != null ? prov : new BouncyCastleProvider();
-    }
-
     private final SslHandlerFactoryProvider sslHandlerFactoryProvider;
     private final AAAEncryptionService encryptionService;
     private final CredentialProvider credentialProvider;
@@ -82,7 +61,7 @@ public final class NetconfClientConfigurationBuilderFactoryImpl implements Netco
 
     @Override
     public NetconfClientConfigurationBuilder createClientConfigurationBuilder(final NodeId nodeId,
-        final NetconfNode node) {
+            final NetconfNode node) {
         final var builder = NetconfClientConfigurationBuilder.create();
         final var protocol = node.getProtocol();
         if (node.requireTcpOnly()) {
@@ -135,7 +114,11 @@ public final class NetconfClientConfigurationBuilderFactoryImpl implements Netco
             final var keyBased = keyAuth.getKeyBased();
             sshParamsBuilder.setClientIdentity(new ClientIdentityBuilder().setUsername(keyBased.getUsername()).build());
             confBuilder.withSshConfigurator(factoryMgr -> {
-                final var keyPair = getKeyPair(keyBased.getKeyId());
+                final var keyId = keyBased.getKeyId();
+                final var keyPair = credentialProvider.credentialForId(keyId);
+                if (keyPair == null) {
+                    throw new IllegalArgumentException("No keypair found with keyId=" + keyId);
+                }
                 factoryMgr.setKeyIdentityProvider(KeyIdentityProvider.wrapKeyPairs(keyPair));
                 final var factory = new UserAuthPublicKeyFactory();
                 factory.setSignatureFactories(factoryMgr.getSignatureFactories());
@@ -157,50 +140,4 @@ public final class NetconfClientConfigurationBuilderFactoryImpl implements Netco
                 .build())
             .build();
     }
-
-    private KeyPair getKeyPair(final String keyId) {
-        // public key retrieval logic taken from DatastoreBackedPublicKeyAuth
-        final var dsKeypair = credentialProvider.credentialForId(keyId);
-        if (dsKeypair == null) {
-            throw new IllegalArgumentException("No keypair found with keyId=" + keyId);
-        }
-        final var passPhrase = Strings.isNullOrEmpty(dsKeypair.getPassphrase()) ? "" : dsKeypair.getPassphrase();
-        try {
-            return decodePrivateKey(decryptString(dsKeypair.getPrivateKey()), decryptString(passPhrase));
-        } catch (IOException e) {
-            throw new IllegalStateException("Could not decode private key with keyId=" + keyId, e);
-        }
-    }
-
-    private String decryptString(final String encrypted) {
-        final byte[] cryptobytes = Base64.getDecoder().decode(encrypted);
-        final byte[] clearbytes;
-        try {
-            clearbytes = encryptionService.decrypt(cryptobytes);
-        } catch (GeneralSecurityException e) {
-            throw new IllegalStateException("Failed to decrypt", e);
-        }
-        return new String(clearbytes, StandardCharsets.UTF_8);
-    }
-
-
-    @VisibleForTesting
-    static KeyPair decodePrivateKey(final String privateKey, final String passphrase) throws IOException {
-        try (var keyReader = new PEMParser(new StringReader(privateKey.replace("\\n", "\n")))) {
-            final var obj = keyReader.readObject();
-
-            final PEMKeyPair keyPair;
-            if (obj instanceof PEMEncryptedKeyPair encrypted) {
-                keyPair = encrypted.decryptKeyPair(new JcePEMDecryptorProviderBuilder()
-                    .setProvider(BCPROV)
-                    .build(passphrase.toCharArray()));
-            } else if (obj instanceof PEMKeyPair plain) {
-                keyPair = plain;
-            } else {
-                throw new IllegalArgumentException("Unhandled private key " + obj.getClass());
-            }
-
-            return new JcaPEMKeyConverter().getKeyPair(keyPair);
-        }
-    }
 }
index 209a25ea72e00f61dc349915af983d4e4dd46be9..fdf36ee467e65f96cd56f8293abc82167283acee 100644 (file)
             <scope>provided</scope>
             <optional>true</optional>
         </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcpkix-jdk18on</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.opendaylight.aaa</groupId>
             <artifactId>aaa-encrypt-service</artifactId>
index 0f04c0b6eba0e8134688df3da4063733a92c0bc4..939b3ee9567174b3e33e0ba191ac6317ef54661e 100644 (file)
@@ -7,6 +7,7 @@
  */
 package org.opendaylight.netconf.keystore.legacy;
 
+import java.security.KeyPair;
 import java.security.cert.X509Certificate;
 import java.util.Map;
 import org.eclipse.jdt.annotation.NonNullByDefault;
@@ -15,11 +16,13 @@ import org.opendaylight.yangtools.concepts.Immutable;
 @NonNullByDefault
 public record NetconfKeystore(
         Map<String, CertifiedPrivateKey> privateKeys,
-        Map<String, X509Certificate> trustedCertificates) implements Immutable {
-    public static final NetconfKeystore EMPTY = new NetconfKeystore(Map.of(), Map.of());
+        Map<String, X509Certificate> trustedCertificates,
+        Map<String, KeyPair> credentials) implements Immutable {
+    public static final NetconfKeystore EMPTY = new NetconfKeystore(Map.of(), Map.of(), Map.of());
 
     public NetconfKeystore {
         privateKeys = Map.copyOf(privateKeys);
         trustedCertificates = Map.copyOf(trustedCertificates);
+        credentials = Map.copyOf(credentials);
     }
 }
index cc1f9e1c5b59ce03d08473d94552d37bb34179f1..f6972812671be8444e4b644c2dc4f5536f7c548b 100644 (file)
@@ -17,6 +17,7 @@ import org.opendaylight.mdsal.binding.api.DataTreeModification;
 import org.opendaylight.netconf.keystore.legacy.impl.DefaultNetconfKeystoreService.ConfigStateBuilder;
 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.keystore.entry.KeyCredential;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.trusted.certificates.TrustedCertificate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -75,6 +76,18 @@ record ConfigListener(DefaultNetconfKeystoreService keystore) implements DataTre
                     }
                 }
             }
+            for (var mod : rootNode.getModifiedChildren(KeyCredential.class)) {
+                switch (mod.modificationType()) {
+                    case SUBTREE_MODIFIED, WRITE -> {
+                        final var keyCredential = mod.dataAfter();
+                        builder.credentials().put(keyCredential.requireKeyId(), keyCredential);
+                    }
+                    case DELETE -> builder.credentials().remove(mod.dataBefore().requireKeyId());
+                    default -> {
+                        // no-op
+                    }
+                }
+            }
         }
     }
 }
\ No newline at end of file
index 815280f48ab27b178f3eb0ce442c9a0d14370169..b7632b702b7239a52623abc0101e201339f26216 100644 (file)
@@ -8,10 +8,13 @@
 package org.opendaylight.netconf.keystore.legacy.impl;
 
 import static java.util.Objects.requireNonNull;
+import static java.util.Objects.requireNonNullElse;
 
 import com.google.common.collect.Maps;
+import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.security.GeneralSecurityException;
+import java.security.KeyPair;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Base64;
@@ -38,6 +41,7 @@ import org.opendaylight.netconf.keystore.legacy.NetconfKeystore;
 import org.opendaylight.netconf.keystore.legacy.NetconfKeystoreService;
 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.keystore.entry.KeyCredential;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.trusted.certificates.TrustedCertificate;
 import org.opendaylight.yangtools.concepts.AbstractObjectRegistration;
 import org.opendaylight.yangtools.concepts.Immutable;
@@ -61,22 +65,26 @@ public final class DefaultNetconfKeystoreService implements NetconfKeystoreServi
     @NonNullByDefault
     private record ConfigState(
             Map<String, PrivateKey> privateKeys,
-            Map<String, TrustedCertificate> trustedCertificates) implements Immutable {
-        static final ConfigState EMPTY = new ConfigState(Map.of(), Map.of());
+            Map<String, TrustedCertificate> trustedCertificates,
+            Map<String, KeyCredential> credentials) implements Immutable {
+        static final ConfigState EMPTY = new ConfigState(Map.of(), Map.of(), Map.of());
 
         ConfigState {
             privateKeys = Map.copyOf(privateKeys);
             trustedCertificates = Map.copyOf(trustedCertificates);
+            credentials = Map.copyOf(credentials);
         }
     }
 
     @NonNullByDefault
     record ConfigStateBuilder(
             HashMap<String, PrivateKey> privateKeys,
-            HashMap<String, TrustedCertificate> trustedCertificates) implements Mutable {
+            HashMap<String, TrustedCertificate> trustedCertificates,
+            HashMap<String, KeyCredential> credentials) implements Mutable {
         ConfigStateBuilder {
             requireNonNull(privateKeys);
             requireNonNull(trustedCertificates);
+            requireNonNull(credentials);
         }
     }
 
@@ -86,6 +94,7 @@ public final class DefaultNetconfKeystoreService implements NetconfKeystoreServi
     private final AtomicReference<NetconfKeystore> keystore = new AtomicReference<>(null);
     private final AtomicReference<ConfigState> config = new AtomicReference<>(ConfigState.EMPTY);
     private final SecurityHelper securityHelper = new SecurityHelper();
+    private final AAAEncryptionService encryptionService;
     private final Registration configListener;
     private final Registration rpcSingleton;
 
@@ -95,6 +104,7 @@ public final class DefaultNetconfKeystoreService implements NetconfKeystoreServi
             @Reference final RpcProviderService rpcProvider,
             @Reference final ClusterSingletonServiceProvider cssProvider,
             @Reference final AAAEncryptionService encryptionService) {
+        this.encryptionService = requireNonNull(encryptionService);
         configListener = dataBroker.registerTreeChangeListener(
             DataTreeIdentifier.of(LogicalDatastoreType.CONFIGURATION, InstanceIdentifier.create(Keystore.class)),
             new ConfigListener(this));
@@ -136,9 +146,9 @@ public final class DefaultNetconfKeystoreService implements NetconfKeystoreServi
         final var prevState = config.getAcquire();
 
         final var builder = new ConfigStateBuilder(new HashMap<>(prevState.privateKeys),
-            new HashMap<>(prevState.trustedCertificates));
+            new HashMap<>(prevState.trustedCertificates), new HashMap<>(prevState.credentials));
         task.accept(builder);
-        final var newState = new ConfigState(builder.privateKeys, builder.trustedCertificates);
+        final var newState = new ConfigState(builder.privateKeys, builder.trustedCertificates, builder.credentials);
 
         // Careful application -- check if listener is still up and whether the state was not updated.
         if (configListener == null || config.compareAndExchangeRelease(prevState, newState) != prevState) {
@@ -228,18 +238,55 @@ public final class DefaultNetconfKeystoreService implements NetconfKeystoreServi
             certs.put(certName, x509cert);
         }
 
+        final var creds = Maps.<String, KeyPair>newHashMapWithExpectedSize(newState.credentials.size());
+        for (var cred : newState.credentials.values()) {
+            final var keyId = cred.requireKeyId();
+            final String passPhrase;
+            try {
+                passPhrase = decryptString(requireNonNullElse(cred.getPassphrase(), ""));
+            } catch (GeneralSecurityException e) {
+                LOG.debug("Failed to decrypt pass phrase for {}", keyId, e);
+                failure = updateFailure(failure, e);
+                continue;
+            }
+
+            final String privateKey;
+            try {
+                privateKey = decryptString(cred.getPrivateKey());
+            } catch (GeneralSecurityException e) {
+                LOG.debug("Failed to decrypt private key for {}", keyId, e);
+                failure = updateFailure(failure, e);
+                continue;
+            }
+
+            final KeyPair keyPair;
+            try {
+                keyPair = securityHelper.decodePrivateKey(privateKey, passPhrase);
+            } catch (IOException e) {
+                LOG.debug("Failed to decode key pair for {}", keyId, e);
+                failure = updateFailure(failure, e);
+                continue;
+            }
+
+            creds.put(keyId, keyPair);
+        }
+
         if (failure != null) {
             LOG.warn("New configuration is invalid, not applying it", failure);
             return;
         }
 
-        final var newKeystore = new NetconfKeystore(keys, certs);
+        final var newKeystore = new NetconfKeystore(keys, certs, creds);
         keystore.setRelease(newKeystore);
         consumers.forEach(consumer -> consumer.getInstance().accept(newKeystore));
     }
 
     private static byte[] base64Decode(final String base64) {
-        return Base64.getMimeDecoder().decode(base64.getBytes(StandardCharsets.US_ASCII));
+        return Base64.getMimeDecoder().decode(base64.getBytes(StandardCharsets.UTF_8));
+    }
+
+    private String decryptString(final String encrypted) throws GeneralSecurityException {
+        return new String(encryptionService.decrypt(Base64.getDecoder().decode(encrypted)), StandardCharsets.UTF_8);
     }
 
     private static @NonNull Throwable updateFailure(final @Nullable Throwable failure, final @NonNull Exception ex) {
index 679970e99dbbde6ed19ef04e511cc6a5e4bfefbc..0f109a6f6b87bcc99253ec17d0eacc9797a27bda 100644 (file)
@@ -8,19 +8,31 @@
 package org.opendaylight.netconf.keystore.legacy.impl;
 
 import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.StringReader;
 import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
+import java.security.KeyPair;
 import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.Security;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.PKCS8EncodedKeySpec;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openssl.PEMEncryptedKeyPair;
+import org.bouncycastle.openssl.PEMKeyPair;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
+import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
 import org.eclipse.jdt.annotation.NonNull;
 
 final class SecurityHelper {
     private CertificateFactory certFactory;
     private KeyFactory dsaFactory;
     private KeyFactory rsaFactory;
+    private Provider bcProv;
 
     @NonNull PrivateKey generatePrivateKey(final byte[] privateKey) throws GeneralSecurityException {
         final var keySpec = new PKCS8EncodedKeySpec(privateKey);
@@ -48,4 +60,28 @@ final class SecurityHelper {
         }
         return (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(certificate));
     }
+
+    @NonNull KeyPair decodePrivateKey(final String privateKey, final String passphrase) throws IOException {
+        if (bcProv == null) {
+            final var prov = Security.getProvider(BouncyCastleProvider.PROVIDER_NAME);
+            bcProv = prov != null ? prov : new BouncyCastleProvider();
+        }
+
+        try (var keyReader = new PEMParser(new StringReader(privateKey.replace("\\n", "\n")))) {
+            final var obj = keyReader.readObject();
+
+            final PEMKeyPair keyPair;
+            if (obj instanceof PEMEncryptedKeyPair encrypted) {
+                keyPair = encrypted.decryptKeyPair(new JcePEMDecryptorProviderBuilder()
+                    .setProvider(bcProv)
+                    .build(passphrase.toCharArray()));
+            } else if (obj instanceof PEMKeyPair plain) {
+                keyPair = plain;
+            } else {
+                throw new IOException("Unhandled private key " + obj.getClass());
+            }
+
+            return new JcaPEMKeyConverter().getKeyPair(keyPair);
+        }
+    }
 }
\ No newline at end of file
similarity index 86%
rename from apps/netconf-topology/src/test/java/org/opendaylight/netconf/topology/spi/PKIUtilTest.java
rename to keystore/keystore-legacy/src/test/java/org/opendaylight/netconf/keystore/legacy/impl/SecurityHelperTest.java
index ddf2bb2f31346852c28d2efd4d1766334baf1c9c..c3df2542be57a0836881c6aa8c9a3be6d0860ee7 100644 (file)
@@ -6,7 +6,7 @@
  * 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.topology.spi;
+package org.opendaylight.netconf.keystore.legacy.impl;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -17,7 +17,9 @@ import java.security.KeyPair;
 import org.bouncycastle.openssl.EncryptionException;
 import org.junit.jupiter.api.Test;
 
-class PKIUtilTest {
+class SecurityHelperTest {
+    private final SecurityHelper helper = new SecurityHelper();
+
     @Test
     void testRSAKey() throws Exception {
         assertNotNull(decodePrivateKey("rsa", ""));
@@ -69,9 +71,9 @@ class PKIUtilTest {
         assertEquals("exception using cipher - please check password and data.", ex.getMessage());
     }
 
-    private static KeyPair decodePrivateKey(final String resourceName, final String password) throws Exception {
-        return NetconfClientConfigurationBuilderFactoryImpl.decodePrivateKey(
-            new String(PKIUtilTest.class.getResourceAsStream("/pki/" + resourceName).readAllBytes(),
+    private KeyPair decodePrivateKey(final String resourceName, final String password) throws Exception {
+        return helper.decodePrivateKey(
+            new String(SecurityHelperTest.class.getResourceAsStream("/pki/" + resourceName).readAllBytes(),
                 StandardCharsets.UTF_8),
             password);
     }
index 8a69af1b9f4fd7cac959105124143edb2ea62094..e85e10dcd71d11e9f072b2acdca71acc7d8021db 100644 (file)
@@ -7,16 +7,16 @@
  */
 package org.opendaylight.netconf.client.mdsal.api;
 
+import java.security.KeyPair;
 import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.keystore.entry.KeyCredential;
 
 public interface CredentialProvider {
     /**
-     * Get the a {@link KeyCredential} for a particular id.
+     * Get the a {@link KeyPair} for a particular id.
      *
      * @param id Credential id
-     * @return A {@link KeyCredential} object, {@code null} if not found
+     * @return A {@link KeyPair} object, {@code null} if not found
      * @throws NullPointerException if {@code id} is {@code null}
      */
-    @Nullable KeyCredential credentialForId(String id);
+    @Nullable KeyPair credentialForId(String id);
 }
index b188ab6f5237d7eefbad82d69bb294dac68e5ff0..e3951d0a0e7053917bbc5219cbb2bc776ef65920 100644 (file)
@@ -7,42 +7,35 @@
  */
 package org.opendaylight.netconf.client.mdsal.impl;
 
+import static java.util.Objects.requireNonNull;
+
+import java.security.KeyPair;
 import java.util.Map;
 import javax.annotation.PreDestroy;
 import javax.inject.Inject;
 import javax.inject.Singleton;
 import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.mdsal.binding.api.DataBroker;
-import org.opendaylight.mdsal.binding.api.DataListener;
-import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
-import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
 import org.opendaylight.netconf.client.mdsal.api.CredentialProvider;
-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.KeyCredentialKey;
+import org.opendaylight.netconf.keystore.legacy.NetconfKeystoreService;
 import org.opendaylight.yangtools.concepts.Registration;
-import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 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 = CredentialProvider.class)
-public final class DefaultCredentialProvider implements CredentialProvider, DataListener<Keystore>, AutoCloseable {
-    private static final Logger LOG = LoggerFactory.getLogger(DefaultCredentialProvider.class);
-
+public final class DefaultCredentialProvider implements CredentialProvider, AutoCloseable {
     private final @NonNull Registration reg;
 
-    private volatile @NonNull Map<KeyCredentialKey, KeyCredential> credentials = Map.of();
+    private volatile @NonNull Map<String, KeyPair> credentials = Map.of();
 
     @Inject
     @Activate
-    public DefaultCredentialProvider(@Reference final DataBroker dataBroker) {
-        reg = dataBroker.registerDataListener(
-            DataTreeIdentifier.of(LogicalDatastoreType.CONFIGURATION, InstanceIdentifier.create(Keystore.class)), this);
+    public DefaultCredentialProvider(@Reference final NetconfKeystoreService keystoreService) {
+        reg = keystoreService.registerKeystoreConsumer(keystore -> {
+            credentials = keystore.credentials();
+        });
     }
 
     @Deactivate
@@ -53,15 +46,7 @@ public final class DefaultCredentialProvider implements CredentialProvider, Data
     }
 
     @Override
-    public KeyCredential credentialForId(final String id) {
-        return credentials.get(new KeyCredentialKey(id));
-    }
-
-    @Override
-    public void dataChangedTo(final Keystore data) {
-        final var newCredentials = data != null ? data.nonnullKeyCredential()
-            : Map.<KeyCredentialKey, KeyCredential>of();
-        LOG.debug("Updating to {} credentials", newCredentials.size());
-        credentials = newCredentials;
+    public KeyPair credentialForId(final String id) {
+        return credentials.get(requireNonNull(id));
     }
 }