From: Robert Varga Date: Fri, 2 Feb 2024 15:28:12 +0000 (+0100) Subject: Use NetconfKeystoreService in tls auth provider X-Git-Tag: v7.0.0~61 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=b41c451825b40605e026b72b2f59f5a479629240;p=netconf.git Use NetconfKeystoreService in tls auth provider Rather than listening to datastore, use the associated service to acquire readily-decoded certificates. While we are at it, we index them to a multimap, so we do not need to iterate through them all the time. JIRA: NETCONF-1243 Change-Id: I93e546684e6f31e309da45d0cf07cc30de31565a Signed-off-by: Robert Varga --- diff --git a/apps/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/tls/CallHomeMountTlsAuthProvider.java b/apps/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/tls/CallHomeMountTlsAuthProvider.java index 0f73840a0c..4d96123f5f 100644 --- a/apps/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/tls/CallHomeMountTlsAuthProvider.java +++ b/apps/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/tls/CallHomeMountTlsAuthProvider.java @@ -7,28 +7,19 @@ */ package org.opendaylight.netconf.callhome.mount.tls; -import static java.nio.charset.StandardCharsets.US_ASCII; - +import com.google.common.collect.ImmutableMultimap; import io.netty.channel.Channel; import io.netty.handler.ssl.SslHandler; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; import java.security.PublicKey; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.util.Base64; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.stream.Collectors; import javax.annotation.PreDestroy; import javax.inject.Inject; import javax.inject.Singleton; 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.DataTreeIdentifier; import org.opendaylight.mdsal.binding.api.DataTreeModification; @@ -36,8 +27,8 @@ import org.opendaylight.mdsal.common.api.LogicalDatastoreType; import org.opendaylight.netconf.callhome.server.tls.CallHomeTlsAuthProvider; import org.opendaylight.netconf.client.SslHandlerFactory; import org.opendaylight.netconf.client.mdsal.api.SslHandlerFactoryProvider; -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.netconf.keystore.legacy.NetconfKeystore; +import org.opendaylight.netconf.keystore.legacy.NetconfKeystoreService; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev240129.NetconfCallhomeServer; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev240129.netconf.callhome.server.AllowedDevices; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev240129.netconf.callhome.server.allowed.devices.Device; @@ -59,24 +50,22 @@ public final class CallHomeMountTlsAuthProvider implements CallHomeTlsAuthProvid private final ConcurrentMap deviceToPrivateKey = new ConcurrentHashMap<>(); private final ConcurrentMap deviceToCertificate = new ConcurrentHashMap<>(); - private final ConcurrentMap certificateToPublicKey = new ConcurrentHashMap<>(); private final Registration allowedDevicesReg; private final Registration certificatesReg; private final SslHandlerFactory sslHandlerFactory; + private volatile ImmutableMultimap publicKeyToName = ImmutableMultimap.of(); + @Inject @Activate - public CallHomeMountTlsAuthProvider( - final @Reference SslHandlerFactoryProvider sslHandlerFactoryProvider, - final @Reference DataBroker dataBroker) { + public CallHomeMountTlsAuthProvider(final @Reference SslHandlerFactoryProvider sslHandlerFactoryProvider, + final @Reference DataBroker dataBroker, final @Reference NetconfKeystoreService keystoreService) { sslHandlerFactory = sslHandlerFactoryProvider.getSslHandlerFactory(null); allowedDevicesReg = dataBroker.registerTreeChangeListener( DataTreeIdentifier.of(LogicalDatastoreType.CONFIGURATION, InstanceIdentifier.create(NetconfCallhomeServer.class).child(AllowedDevices.class).child(Device.class)), new AllowedDevicesMonitor()); - certificatesReg = dataBroker.registerTreeChangeListener( - DataTreeIdentifier.of(LogicalDatastoreType.CONFIGURATION, InstanceIdentifier.create(Keystore.class)), - new CertificatesMonitor()); + certificatesReg = keystoreService.registerKeystoreConsumer(this::updateCertificates); } @PreDestroy @@ -87,17 +76,20 @@ public final class CallHomeMountTlsAuthProvider implements CallHomeTlsAuthProvid certificatesReg.close(); } + private void updateCertificates(final NetconfKeystore keystore) { + final var builder = ImmutableMultimap.builder(); + keystore.trustedCertificates().forEach((name, cert) -> builder.put(cert.getPublicKey(), name)); + publicKeyToName = builder.build(); + } + @Override public String idFor(final PublicKey key) { // Find certificate names by the public key - final var certificates = certificateToPublicKey.entrySet().stream() - .filter(v -> key.equals(v.getValue())) - .map(Map.Entry::getKey) - .collect(Collectors.toSet()); + final var certificateNames = publicKeyToName.get(key); // Find devices names associated with a certificate name final var deviceNames = deviceToCertificate.entrySet().stream() - .filter(v -> certificates.contains(v.getValue())) + .filter(v -> certificateNames.contains(v.getValue())) .map(Map.Entry::getKey) .toList(); @@ -120,58 +112,6 @@ public final class CallHomeMountTlsAuthProvider implements CallHomeTlsAuthProvid return sslHandlerFactory.createSslHandler(Set.copyOf(deviceToPrivateKey.values())); } - private final class CertificatesMonitor implements DataTreeChangeListener { - @Override - public void onDataTreeChanged(final List> changes) { - changes.stream() - .map(DataTreeModification::getRootNode) - .flatMap(v -> v.modifiedChildren().stream()) - .filter(v -> v.dataType().equals(TrustedCertificate.class)) - .map(v -> (DataObjectModification) v) - .forEach(this::updateCertificate); - } - - private void updateCertificate(final DataObjectModification change) { - switch (change.modificationType()) { - case DELETE -> deleteCertificate(change.dataBefore()); - case SUBTREE_MODIFIED, WRITE -> { - deleteCertificate(change.dataBefore()); - writeCertificate(change.dataAfter()); - } - default -> { - // Should never happen - } - } - } - - private void deleteCertificate(final TrustedCertificate dataBefore) { - if (dataBefore != null) { - LOG.debug("Removing public key mapping for certificate {}", dataBefore.getName()); - certificateToPublicKey.remove(dataBefore.getName()); - } - } - - private void writeCertificate(final TrustedCertificate dataAfter) { - if (dataAfter != null) { - LOG.debug("Adding public key mapping for certificate {}", dataAfter.getName()); - certificateToPublicKey.putIfAbsent(dataAfter.getName(), buildPublicKey(dataAfter.getCertificate())); - } - } - - private PublicKey buildPublicKey(final String encoded) { - final byte[] decoded = Base64.getMimeDecoder().decode(encoded.getBytes(US_ASCII)); - try { - final CertificateFactory factory = CertificateFactory.getInstance("X.509"); - try (InputStream in = new ByteArrayInputStream(decoded)) { - return factory.generateCertificate(in).getPublicKey(); - } - } catch (final CertificateException | IOException e) { - LOG.error("Unable to build X.509 certificate from encoded value: {}", e.getLocalizedMessage()); - } - return null; - } - } - private final class AllowedDevicesMonitor implements DataTreeChangeListener { @Override public void onDataTreeChanged(final List> mods) {