Migrate netconf-topology to new transport
[netconf.git] / netconf / callhome-protocol / src / main / java / org / opendaylight / netconf / callhome / protocol / tls / CallHomeTlsSessionContext.java
1 /*
2  * Copyright (c) 2020 Pantheon Technologies, s.r.o. and others. All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.netconf.callhome.protocol.tls;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.util.concurrent.Futures;
13 import com.google.common.util.concurrent.ListenableFuture;
14 import com.google.common.util.concurrent.SettableFuture;
15 import io.netty.channel.Channel;
16 import io.netty.handler.ssl.SslHandler;
17 import io.netty.util.HashedWheelTimer;
18 import io.netty.util.concurrent.GlobalEventExecutor;
19 import io.netty.util.concurrent.Promise;
20 import java.net.SocketAddress;
21 import java.security.PublicKey;
22 import java.security.cert.Certificate;
23 import java.util.Optional;
24 import java.util.concurrent.TimeUnit;
25 import java.util.concurrent.atomic.AtomicBoolean;
26 import javax.net.ssl.SSLPeerUnverifiedException;
27 import org.opendaylight.netconf.callhome.protocol.CallHomeNetconfSubsystemListener;
28 import org.opendaylight.netconf.callhome.protocol.CallHomeProtocolSessionContext;
29 import org.opendaylight.netconf.client.NetconfClientSession;
30 import org.opendaylight.netconf.client.NetconfClientSessionListener;
31 import org.opendaylight.netconf.client.NetconfClientSessionNegotiatorFactory;
32 import org.opendaylight.netconf.client.SslHandlerFactory;
33 import org.opendaylight.netconf.client.TlsClientChannelInitializer;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231025.connection.parameters.Protocol.Name;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 final class CallHomeTlsSessionContext implements CallHomeProtocolSessionContext {
39     private static final Logger LOG = LoggerFactory.getLogger(CallHomeTlsSessionContext.class);
40
41     private final AtomicBoolean activated = new AtomicBoolean();
42     private final SslHandlerFactory sslHandlerFactory;
43     private final CallHomeNetconfSubsystemListener subsystemListener;
44     private final String deviceId;
45     private final Channel channel;
46     private final PublicKey publicKey;
47     private final SocketAddress socketAddress;
48
49     CallHomeTlsSessionContext(final String deviceId, final Channel channel, final SslHandlerFactory sslHandlerFactory,
50                               final CallHomeNetconfSubsystemListener subsystemListener) {
51         this.channel = requireNonNull(channel, "channel");
52         this.deviceId = deviceId;
53         socketAddress = channel.remoteAddress();
54         publicKey = createPublicKey(channel);
55         this.sslHandlerFactory = requireNonNull(sslHandlerFactory, "sslHandlerFactory");
56         this.subsystemListener = subsystemListener;
57     }
58
59     private static Promise<NetconfClientSession> newSessionPromise() {
60         return GlobalEventExecutor.INSTANCE.newPromise();
61     }
62
63     void openNetconfChannel(final Channel ch) {
64         LOG.debug("Opening NETCONF Subsystem on TLS connection {}", deviceId);
65         subsystemListener.onNetconfSubsystemOpened(this, listener -> doActivate(ch, listener));
66     }
67
68     @Override
69     public void terminate() {
70         channel.close();
71     }
72
73     private ListenableFuture<NetconfClientSession> doActivate(final Channel ch,
74             final NetconfClientSessionListener listener) {
75         final Promise<NetconfClientSession> activationPromise = newSessionPromise();
76         if (activated.compareAndExchange(false, true)) {
77             return Futures.immediateFailedFuture(new IllegalStateException("Session (channel) already activated."));
78         }
79
80         LOG.info("Activating Netconf channel for {} with {}", getRemoteAddress(), listener);
81         final NetconfClientSessionNegotiatorFactory negotiatorFactory = new NetconfClientSessionNegotiatorFactory(
82             new HashedWheelTimer(), Optional.empty(), TimeUnit.SECONDS.toMillis(5));
83         final TlsClientChannelInitializer tlsClientChannelInitializer = new TlsClientChannelInitializer(
84             sslHandlerFactory, negotiatorFactory, listener);
85         tlsClientChannelInitializer.initialize(ch, activationPromise);
86         final SettableFuture<NetconfClientSession> future = SettableFuture.create();
87         activationPromise.addListener(ignored -> {
88             final var cause = activationPromise.cause();
89             if (cause != null) {
90                 future.setException(cause);
91             } else {
92                 future.set(activationPromise.getNow());
93             }
94         });
95         return future;
96     }
97
98     @Override
99     public PublicKey getRemoteServerKey() {
100         return publicKey;
101     }
102
103     @Override
104     public SocketAddress getRemoteAddress() {
105         return socketAddress;
106     }
107
108     @Override
109     public String getSessionId() {
110         return deviceId;
111     }
112
113     @Override
114     public Name getTransportType() {
115         return Name.TLS;
116     }
117
118     private static PublicKey createPublicKey(final Channel ch) {
119         final SslHandler sslHandler = ch.pipeline().get(SslHandler.class);
120         final Certificate[] certificates;
121         try {
122             certificates = sslHandler.engine().getSession().getPeerCertificates();
123         } catch (SSLPeerUnverifiedException e) {
124             LOG.error("Peer certificate was not established during the handshake", e);
125             throw new IllegalStateException("No peer certificate present", e);
126         }
127         return certificates[0].getPublicKey();
128     }
129 }