Fix NETCONF session(channel) activation for the CallHome(over TLS)
[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 io.netty.channel.Channel;
13 import io.netty.handler.ssl.SslHandler;
14 import io.netty.util.HashedWheelTimer;
15 import io.netty.util.concurrent.GlobalEventExecutor;
16 import io.netty.util.concurrent.Promise;
17 import java.net.SocketAddress;
18 import java.security.PublicKey;
19 import java.security.cert.Certificate;
20 import java.util.Optional;
21 import java.util.concurrent.TimeUnit;
22 import java.util.concurrent.atomic.AtomicBoolean;
23 import javax.net.ssl.SSLPeerUnverifiedException;
24 import org.opendaylight.netconf.callhome.protocol.CallHomeNetconfSubsystemListener;
25 import org.opendaylight.netconf.callhome.protocol.CallHomeProtocolSessionContext;
26 import org.opendaylight.netconf.callhome.protocol.TransportType;
27 import org.opendaylight.netconf.client.NetconfClientSession;
28 import org.opendaylight.netconf.client.NetconfClientSessionListener;
29 import org.opendaylight.netconf.client.NetconfClientSessionNegotiatorFactory;
30 import org.opendaylight.netconf.client.SslHandlerFactory;
31 import org.opendaylight.netconf.client.TlsClientChannelInitializer;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 final class CallHomeTlsSessionContext implements CallHomeProtocolSessionContext {
36     private static final Logger LOG = LoggerFactory.getLogger(CallHomeTlsSessionContext.class);
37
38     private final AtomicBoolean activated = new AtomicBoolean();
39     private final SslHandlerFactory sslHandlerFactory;
40     private final CallHomeNetconfSubsystemListener subsystemListener;
41     private final String deviceId;
42     private final Channel channel;
43     private final PublicKey publicKey;
44     private final SocketAddress socketAddress;
45
46     CallHomeTlsSessionContext(final String deviceId, final Channel channel, final SslHandlerFactory sslHandlerFactory,
47                               final CallHomeNetconfSubsystemListener subsystemListener) {
48         this.channel = requireNonNull(channel, "channel");
49         this.deviceId = deviceId;
50         this.socketAddress = channel.remoteAddress();
51         this.publicKey = createPublicKey(channel);
52         this.sslHandlerFactory = requireNonNull(sslHandlerFactory, "sslHandlerFactory");
53         this.subsystemListener = subsystemListener;
54     }
55
56     private static Promise<NetconfClientSession> newSessionPromise() {
57         return GlobalEventExecutor.INSTANCE.newPromise();
58     }
59
60     void openNetconfChannel(final Channel ch) {
61         LOG.debug("Opening NETCONF Subsystem on TLS connection {}", deviceId);
62         subsystemListener.onNetconfSubsystemOpened(this, listener -> doActivate(ch, listener));
63     }
64
65     @Override
66     public void terminate() {
67         channel.close();
68     }
69
70     private Promise<NetconfClientSession> doActivate(final Channel ch, final NetconfClientSessionListener listener) {
71         final Promise<NetconfClientSession> activationPromise = newSessionPromise();
72         if (activated.compareAndExchange(false, true)) {
73             return activationPromise.setFailure(new IllegalStateException("Session (channel) already activated."));
74         }
75
76         LOG.info("Activating Netconf channel for {} with {}", getRemoteAddress(), listener);
77         final NetconfClientSessionNegotiatorFactory negotiatorFactory = new NetconfClientSessionNegotiatorFactory(
78             new HashedWheelTimer(), Optional.empty(), TimeUnit.SECONDS.toMillis(5));
79         final TlsClientChannelInitializer tlsClientChannelInitializer = new TlsClientChannelInitializer(
80             sslHandlerFactory, negotiatorFactory, listener);
81         tlsClientChannelInitializer.initialize(ch, activationPromise);
82         return activationPromise;
83     }
84
85     @Override
86     public PublicKey getRemoteServerKey() {
87         return publicKey;
88     }
89
90     @Override
91     public SocketAddress getRemoteAddress() {
92         return socketAddress;
93     }
94
95     @Override
96     public String getSessionId() {
97         return deviceId;
98     }
99
100     @Override
101     public TransportType getTransportType() {
102         return TransportType.TLS;
103     }
104
105     private static PublicKey createPublicKey(final Channel ch) {
106         final SslHandler sslHandler = ch.pipeline().get(SslHandler.class);
107         final Certificate[] certificates;
108         try {
109             certificates = sslHandler.engine().getSession().getPeerCertificates();
110         } catch (SSLPeerUnverifiedException e) {
111             LOG.error("Peer certificate was not established during the handshake", e);
112             throw new IllegalStateException("No peer certificate present", e);
113         }
114         return certificates[0].getPublicKey();
115     }
116 }