View Javadoc
1   /*
2    * Licensed under the GPL License. You may not use this file except in compliance with the License.
3    * You may obtain a copy of the License at
4    *
5    *   https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
6    *
7    * THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
8    * WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
9    * PURPOSE.
10   */
11  package psiprobe.controllers.certificates;
12  
13  import jakarta.servlet.http.HttpServletRequest;
14  import jakarta.servlet.http.HttpServletResponse;
15  
16  import java.io.File;
17  import java.io.IOException;
18  import java.io.InputStream;
19  import java.lang.reflect.InvocationTargetException;
20  import java.net.URI;
21  import java.net.URL;
22  import java.nio.file.Files;
23  import java.nio.file.Path;
24  import java.security.KeyStore;
25  import java.security.cert.Certificate;
26  import java.security.cert.X509Certificate;
27  import java.util.ArrayList;
28  import java.util.Collection;
29  import java.util.Collections;
30  import java.util.List;
31  
32  import javax.management.ObjectName;
33  
34  import org.apache.catalina.connector.Connector;
35  import org.apache.coyote.ProtocolHandler;
36  import org.apache.coyote.http11.AbstractHttp11Protocol;
37  import org.slf4j.Logger;
38  import org.slf4j.LoggerFactory;
39  import org.springframework.beans.factory.annotation.Value;
40  import org.springframework.stereotype.Controller;
41  import org.springframework.web.bind.annotation.RequestMapping;
42  import org.springframework.web.servlet.ModelAndView;
43  
44  import psiprobe.controllers.AbstractTomcatContainerController;
45  import psiprobe.model.certificates.Cert;
46  import psiprobe.model.certificates.CertificateInfo;
47  import psiprobe.model.certificates.ConnectorInfo;
48  import psiprobe.model.certificates.SslHostConfigInfo;
49  
50  /**
51   * The Class ListCertificatesController.
52   */
53  @Controller
54  public class ListCertificatesController extends AbstractTomcatContainerController {
55  
56    /** The Constant logger. */
57    private static final Logger logger = LoggerFactory.getLogger(ListCertificatesController.class);
58  
59    @RequestMapping(path = "/certificates.htm")
60    @Override
61    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
62        throws Exception {
63      return super.handleRequest(request, response);
64    }
65  
66    @Override
67    protected ModelAndView handleRequestInternal(HttpServletRequest request,
68        HttpServletResponse response) throws Exception {
69  
70      ModelAndView modelAndView = new ModelAndView(getViewName());
71  
72      try {
73        List<Connector> connectors = getContainerWrapper().getTomcatContainer().findConnectors();
74        List<ConnectorInfo> infos = getConnectorInfos(connectors);
75  
76        for (ConnectorInfo info : infos) {
77          List<Cert> certs;
78  
79          List<SslHostConfigInfo> sslHostConfigInfos = info.getSslHostConfigInfos();
80          for (SslHostConfigInfo sslHostConfigInfo : sslHostConfigInfos) {
81            if (sslHostConfigInfo.getTruststoreFile() != null) {
82              certs = getCertificates(sslHostConfigInfo.getTruststoreType(),
83                  sslHostConfigInfo.getTruststoreFile(), sslHostConfigInfo.getTruststorePassword());
84              sslHostConfigInfo.setTrustStoreCerts(certs);
85            }
86  
87            List<CertificateInfo> certificateInfos = sslHostConfigInfo.getCertificateInfos();
88            for (CertificateInfo certificateInfo : certificateInfos) {
89              if (certificateInfo.getCertificateKeystoreFile() != null) {
90                certs = getCertificates(certificateInfo.getCertificateKeystoreType(),
91                    certificateInfo.getCertificateKeystoreFile(),
92                    certificateInfo.getCertificateKeystorePassword());
93                certificateInfo.setKeyStoreCerts(certs);
94              }
95            }
96            sslHostConfigInfo.setCertificateInfos(certificateInfos);
97          }
98          info.setSslHostConfigInfos(sslHostConfigInfos);
99        }
100 
101       modelAndView.addObject("connectors", infos);
102     } catch (Exception e) {
103       logger.error("There was an exception listing certificates", e);
104     }
105 
106     return modelAndView;
107 
108   }
109 
110   /**
111    * Gets the certificates.
112    *
113    * @param storeType the store type
114    * @param storeFile the store file
115    * @param storePassword the store password
116    *
117    * @return the certificates
118    *
119    * @throws Exception the exception
120    */
121   public List<Cert> getCertificates(String storeType, String storeFile, String storePassword)
122       throws Exception {
123     KeyStore keyStore;
124 
125     // Get key store
126     if (storeType != null) {
127       keyStore = KeyStore.getInstance(storeType);
128     } else {
129       keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
130     }
131 
132     // Get password
133     char[] password = null;
134     if (storePassword != null) {
135       password = storePassword.toCharArray();
136     }
137 
138     // Load key store from file
139     try (InputStream storeInput = getStoreInputStream(storeFile)) {
140       keyStore.load(storeInput, password);
141     } catch (IOException e) {
142       logger.error("Error loading store file {}", storeFile, e);
143       return Collections.emptyList();
144     }
145 
146     List<Cert> certs = new ArrayList<>();
147 
148     for (String alias : Collections.list(keyStore.aliases())) {
149 
150       Certificate[] certificateChains = keyStore.getCertificateChain(alias);
151 
152       if (certificateChains != null) {
153         for (Certificate certificateChain : certificateChains) {
154           X509Certificate x509Cert = (X509Certificate) certificateChain;
155           addToStore(certs, alias, x509Cert);
156         }
157       } else {
158         X509Certificate x509Cert = (X509Certificate) keyStore.getCertificate(alias);
159         addToStore(certs, alias, x509Cert);
160       }
161     }
162     return certs;
163   }
164 
165   /**
166    * Gets the connector infos.
167    *
168    * @param connectors the connectors
169    *
170    * @return the connector infos
171    *
172    * @throws IllegalAccessException the illegal access exception
173    * @throws InvocationTargetException the invocation target exception
174    */
175   private List<ConnectorInfo> getConnectorInfos(Collection<Connector> connectors)
176       throws IllegalAccessException, InvocationTargetException {
177     List<ConnectorInfo> infos = new ArrayList<>();
178     for (Connector connector : connectors) {
179       if (!connector.getSecure()) {
180         continue;
181       }
182 
183       ProtocolHandler protocolHandler = connector.getProtocolHandler();
184 
185       if (protocolHandler instanceof AbstractHttp11Protocol protocol) {
186         if (!protocol.getSecure()) {
187           continue;
188         }
189         infos.add(toConnectorInfo(protocol));
190       }
191     }
192     return infos;
193   }
194 
195   /**
196    * Tries to open a InputStream the same way as Tomcat ConfigFileLoader
197    * {@link org.apache.tomcat.util.file.ConfigFileLoader#getSource()
198    * getSource.getResource(String).getInputStream()}.
199    *
200    * @param path the path of a store file (absolute or relative to CATALINA.BASE), or URI to store
201    *        file (absolute or relative to CATALINA.BASE).
202    *
203    * @return the input stream of the path file
204    *
205    * @throws IOException if path can not be resolved
206    */
207   private InputStream getStoreInputStream(String path) throws IOException {
208     File file = Path.of(path).toFile();
209     if (file.exists()) {
210       return Files.newInputStream(file.toPath());
211     }
212 
213     File catalinaBaseFolder = Path.of(System.getProperty("catalina.base")).toFile();
214     file = Path.of(catalinaBaseFolder.getPath(), path).toFile();
215     if (file.exists()) {
216       return Files.newInputStream(file.toPath());
217     }
218 
219     URI uri = catalinaBaseFolder.toURI().resolve(path);
220 
221     URL url = uri.toURL();
222 
223     return url.openConnection().getInputStream();
224   }
225 
226   /**
227    * To connector info.
228    *
229    * @param protocol the protocol
230    *
231    * @return the connector info
232    *
233    * @throws IllegalAccessException the illegal access exception
234    * @throws InvocationTargetException the invocation target exception
235    */
236   private ConnectorInfo toConnectorInfo(AbstractHttp11Protocol<?> protocol)
237       throws IllegalAccessException, InvocationTargetException {
238     ConnectorInfo info = new ConnectorInfo();
239     info.setName(ObjectName.unquote(protocol.getName()));
240 
241     String defaultSslHostConfigName = protocol.getDefaultSSLHostConfigName();
242     if (defaultSslHostConfigName == null) {
243       logger.error("Cannot determine defaultSslHostConfigName");
244       return info;
245     }
246     info.setDefaultSslHostConfigName(defaultSslHostConfigName);
247     new SslHostConfigHelper(protocol, info);
248 
249     return info;
250   }
251 
252   /**
253    * Adds the to store.
254    *
255    * @param certs the certs
256    * @param alias the alias
257    * @param x509Cert the x509 cert
258    */
259   private void addToStore(Collection<Cert> certs, String alias, X509Certificate x509Cert) {
260     Cert cert = new Cert();
261 
262     cert.setAlias(alias);
263     cert.setSubjectDistinguishedName(x509Cert.getSubjectX500Principal().toString());
264     cert.setNotBefore(x509Cert.getNotBefore().toInstant());
265     cert.setNotAfter(x509Cert.getNotAfter().toInstant());
266     cert.setIssuerDistinguishedName(x509Cert.getIssuerX500Principal().toString());
267 
268     certs.add(cert);
269   }
270 
271   @Value("certificates")
272   @Override
273   public void setViewName(String viewName) {
274     super.setViewName(viewName);
275   }
276 
277 }