001/*-
002 * #%L
003 * HAPI FHIR - Client Framework
004 * %%
005 * Copyright (C) 2014 - 2024 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.rest.client.tls;
021
022import ca.uhn.fhir.i18n.Msg;
023import ca.uhn.fhir.tls.BaseStoreInfo;
024import ca.uhn.fhir.tls.KeyStoreInfo;
025import ca.uhn.fhir.tls.PathType;
026import ca.uhn.fhir.tls.TlsAuthentication;
027import ca.uhn.fhir.tls.TrustStoreInfo;
028import jakarta.annotation.Nonnull;
029import org.apache.commons.lang3.Validate;
030import org.apache.http.conn.ssl.DefaultHostnameVerifier;
031import org.apache.http.conn.ssl.NoopHostnameVerifier;
032import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
033import org.apache.http.ssl.PrivateKeyStrategy;
034import org.apache.http.ssl.SSLContextBuilder;
035import org.apache.http.ssl.SSLContexts;
036
037import java.io.FileInputStream;
038import java.io.InputStream;
039import java.security.KeyStore;
040import java.util.Optional;
041import javax.net.ssl.HostnameVerifier;
042import javax.net.ssl.SSLContext;
043import javax.net.ssl.TrustManager;
044import javax.net.ssl.TrustManagerFactory;
045import javax.net.ssl.X509TrustManager;
046
047import static org.apache.commons.lang3.StringUtils.isNotBlank;
048
049public class TlsAuthenticationSvc {
050
051        private TlsAuthenticationSvc() {}
052
053        public static SSLContext createSslContext(@Nonnull TlsAuthentication theTlsAuthentication) {
054                Validate.notNull(theTlsAuthentication, "theTlsAuthentication cannot be null");
055
056                try {
057                        SSLContextBuilder contextBuilder = SSLContexts.custom();
058
059                        if (theTlsAuthentication.getKeyStoreInfo().isPresent()) {
060                                KeyStoreInfo keyStoreInfo =
061                                                theTlsAuthentication.getKeyStoreInfo().get();
062                                PrivateKeyStrategy privateKeyStrategy = null;
063                                if (isNotBlank(keyStoreInfo.getAlias())) {
064                                        privateKeyStrategy = (aliases, socket) -> keyStoreInfo.getAlias();
065                                }
066                                KeyStore keyStore = createKeyStore(keyStoreInfo);
067                                contextBuilder.loadKeyMaterial(keyStore, keyStoreInfo.getKeyPass(), privateKeyStrategy);
068                        }
069
070                        if (theTlsAuthentication.getTrustStoreInfo().isPresent()) {
071                                TrustStoreInfo trustStoreInfo =
072                                                theTlsAuthentication.getTrustStoreInfo().get();
073                                KeyStore trustStore = createKeyStore(trustStoreInfo);
074                                contextBuilder.loadTrustMaterial(trustStore, TrustSelfSignedStrategy.INSTANCE);
075                        }
076
077                        return contextBuilder.build();
078                } catch (Exception e) {
079                        throw new TlsAuthenticationException(Msg.code(2102) + "Failed to create SSLContext", e);
080                }
081        }
082
083        public static KeyStore createKeyStore(BaseStoreInfo theStoreInfo) {
084                try {
085                        KeyStore keyStore = KeyStore.getInstance(theStoreInfo.getType().toString());
086
087                        if (PathType.RESOURCE.equals(theStoreInfo.getPathType())) {
088                                try (InputStream inputStream =
089                                                TlsAuthenticationSvc.class.getResourceAsStream(theStoreInfo.getFilePath())) {
090                                        validateKeyStoreExists(inputStream);
091                                        keyStore.load(inputStream, theStoreInfo.getStorePass());
092                                }
093                        } else if (PathType.FILE.equals(theStoreInfo.getPathType())) {
094                                try (InputStream inputStream = new FileInputStream(theStoreInfo.getFilePath())) {
095                                        validateKeyStoreExists(inputStream);
096                                        keyStore.load(inputStream, theStoreInfo.getStorePass());
097                                }
098                        }
099                        return keyStore;
100                } catch (Exception e) {
101                        throw new TlsAuthenticationException(Msg.code(2103) + "Failed to create KeyStore", e);
102                }
103        }
104
105        public static void validateKeyStoreExists(InputStream theInputStream) {
106                if (theInputStream == null) {
107                        throw new TlsAuthenticationException(Msg.code(2116) + "Keystore does not exists");
108                }
109        }
110
111        public static X509TrustManager createTrustManager(Optional<TrustStoreInfo> theTrustStoreInfo) {
112                try {
113                        TrustManagerFactory trustManagerFactory =
114                                        TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
115                        if (!theTrustStoreInfo.isPresent()) {
116                                trustManagerFactory.init((KeyStore) null); // Load Trust Manager Factory with default Java truststore
117                        } else {
118                                TrustStoreInfo trustStoreInfo = theTrustStoreInfo.get();
119                                KeyStore trustStore = createKeyStore(trustStoreInfo);
120                                trustManagerFactory.init(trustStore);
121                        }
122                        for (TrustManager trustManager : trustManagerFactory.getTrustManagers()) {
123                                if (trustManager instanceof X509TrustManager) {
124                                        return (X509TrustManager) trustManager;
125                                }
126                        }
127                        throw new TlsAuthenticationException(Msg.code(2104) + "Could not find X509TrustManager");
128                } catch (Exception e) {
129                        throw new TlsAuthenticationException(Msg.code(2105) + "Failed to create X509TrustManager");
130                }
131        }
132
133        public static HostnameVerifier createHostnameVerifier(Optional<TrustStoreInfo> theTrustStoreInfo) {
134                return theTrustStoreInfo.isPresent() ? new DefaultHostnameVerifier() : new NoopHostnameVerifier();
135        }
136
137        public static class TlsAuthenticationException extends RuntimeException {
138                private static final long serialVersionUID = 1l;
139
140                public TlsAuthenticationException(String theMessage, Throwable theCause) {
141                        super(theMessage, theCause);
142                }
143
144                public TlsAuthenticationException(String theMessage) {
145                        super(theMessage);
146                }
147        }
148}