9a6c4f70e6f87a8c63bdaef8984b14fe8f30c01c
[nonrtric.git] / information-coordinator-service / src / main / java / org / oransc / ics / clients / AsyncRestClientFactory.java
1 /*-
2  * ========================LICENSE_START=================================
3  * O-RAN-SC
4  * %%
5  * Copyright (C) 2020 Nordix Foundation
6  * %%
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ========================LICENSE_END===================================
19  */
20
21 package org.oransc.ics.clients;
22
23 import io.netty.handler.ssl.SslContext;
24 import io.netty.handler.ssl.SslContextBuilder;
25 import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
26
27 import java.io.FileInputStream;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.lang.invoke.MethodHandles;
31 import java.security.KeyStore;
32 import java.security.KeyStoreException;
33 import java.security.NoSuchAlgorithmException;
34 import java.security.UnrecoverableKeyException;
35 import java.security.cert.Certificate;
36 import java.security.cert.CertificateException;
37 import java.security.cert.X509Certificate;
38 import java.util.Collections;
39 import java.util.List;
40 import java.util.stream.Collectors;
41 import javax.net.ssl.KeyManagerFactory;
42
43 import org.oransc.ics.configuration.WebClientConfig;
44 import org.oransc.ics.configuration.WebClientConfig.HttpProxyConfig;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47 import org.springframework.util.ResourceUtils;
48
49 /**
50  * Factory for a generic reactive REST client.
51  */
52 public class AsyncRestClientFactory {
53     private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
54
55     private final SslContextFactory sslContextFactory;
56     private final HttpProxyConfig httpProxyConfig;
57     private final SecurityContext securityContext;
58
59     public AsyncRestClientFactory(WebClientConfig clientConfig, SecurityContext securityContext) {
60         if (clientConfig != null) {
61             this.sslContextFactory = new CachingSslContextFactory(clientConfig);
62             this.httpProxyConfig = clientConfig.httpProxyConfig();
63         } else {
64             logger.warn("No configuration for web client defined, HTTPS will not work");
65             this.sslContextFactory = null;
66             this.httpProxyConfig = null;
67         }
68         this.securityContext = securityContext;
69     }
70
71     public AsyncRestClient createRestClientNoHttpProxy(String baseUrl) {
72         return createRestClient(baseUrl, false);
73     }
74
75     public AsyncRestClient createRestClientUseHttpProxy(String baseUrl) {
76         return createRestClient(baseUrl, true);
77     }
78
79     private AsyncRestClient createRestClient(String baseUrl, boolean useHttpProxy) {
80         if (this.sslContextFactory != null) {
81             try {
82                 return new AsyncRestClient(baseUrl, this.sslContextFactory.createSslContext(),
83                     useHttpProxy ? httpProxyConfig : null, this.securityContext);
84             } catch (Exception e) {
85                 String exceptionString = e.toString();
86                 logger.error("Could not init SSL context, reason: {}", exceptionString);
87             }
88         }
89         return new AsyncRestClient(baseUrl, null, httpProxyConfig, this.securityContext);
90     }
91
92     private class SslContextFactory {
93         private final WebClientConfig clientConfig;
94
95         public SslContextFactory(WebClientConfig clientConfig) {
96             this.clientConfig = clientConfig;
97         }
98
99         public SslContext createSslContext() throws UnrecoverableKeyException, NoSuchAlgorithmException,
100             CertificateException, KeyStoreException, IOException {
101             return this.createSslContext(createKeyManager());
102         }
103
104         private SslContext createSslContext(KeyManagerFactory keyManager)
105             throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException {
106             if (this.clientConfig.isTrustStoreUsed()) {
107                 return createSslContextRejectingUntrustedPeers(this.clientConfig.trustStore(),
108                     this.clientConfig.trustStorePassword(), keyManager);
109             } else {
110                 // Trust anyone
111                 return SslContextBuilder.forClient() //
112                     .keyManager(keyManager) //
113                     .trustManager(InsecureTrustManagerFactory.INSTANCE) //
114                     .build();
115             }
116         }
117
118         private SslContext createSslContextRejectingUntrustedPeers(String trustStorePath, String trustStorePass,
119             KeyManagerFactory keyManager)
120             throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException {
121
122             final KeyStore trustStore = getTrustStore(trustStorePath, trustStorePass);
123             List<Certificate> certificateList = Collections.list(trustStore.aliases()).stream() //
124                 .filter(alias -> isCertificateEntry(trustStore, alias)) //
125                 .map(alias -> getCertificate(trustStore, alias)) //
126                 .collect(Collectors.toList());
127             final X509Certificate[] certificates = certificateList.toArray(new X509Certificate[certificateList.size()]);
128
129             return SslContextBuilder.forClient() //
130                 .keyManager(keyManager) //
131                 .trustManager(certificates) //
132                 .build();
133         }
134
135         private boolean isCertificateEntry(KeyStore trustStore, String alias) {
136             try {
137                 return trustStore.isCertificateEntry(alias);
138             } catch (KeyStoreException e) {
139                 logger.error("Error reading truststore {}", e.getMessage());
140                 return false;
141             }
142         }
143
144         private Certificate getCertificate(KeyStore trustStore, String alias) {
145             try {
146                 return trustStore.getCertificate(alias);
147             } catch (KeyStoreException e) {
148                 logger.error("Error reading truststore {}", e.getMessage());
149                 return null;
150             }
151         }
152
153         private KeyManagerFactory createKeyManager() throws NoSuchAlgorithmException, CertificateException, IOException,
154             UnrecoverableKeyException, KeyStoreException {
155             final KeyManagerFactory keyManager = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
156             final KeyStore keyStore = KeyStore.getInstance(this.clientConfig.keyStoreType());
157             final String keyStoreFile = this.clientConfig.keyStore();
158             final String keyStorePassword = this.clientConfig.keyStorePassword();
159             final String keyPassword = this.clientConfig.keyPassword();
160             try (final InputStream inputStream = new FileInputStream(keyStoreFile)) {
161                 keyStore.load(inputStream, keyStorePassword.toCharArray());
162             }
163             keyManager.init(keyStore, keyPassword.toCharArray());
164             return keyManager;
165         }
166
167         private synchronized KeyStore getTrustStore(String trustStorePath, String trustStorePass)
168             throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException {
169
170             KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType());
171             store.load(new FileInputStream(ResourceUtils.getFile(trustStorePath)), trustStorePass.toCharArray());
172             return store;
173         }
174     }
175
176     public class CachingSslContextFactory extends SslContextFactory {
177         private SslContext cachedContext = null;
178
179         public CachingSslContextFactory(WebClientConfig clientConfig) {
180             super(clientConfig);
181         }
182
183         @Override
184         public SslContext createSslContext() throws UnrecoverableKeyException, NoSuchAlgorithmException,
185             CertificateException, KeyStoreException, IOException {
186             if (this.cachedContext == null) {
187                 this.cachedContext = super.createSslContext();
188             }
189             return this.cachedContext;
190
191         }
192     }
193 }