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) {
186         AbstractHttp11Protocol<?> protocol = (AbstractHttp11Protocol<?>) protocolHandler;
187         if (!protocol.getSecure()) {
188           continue;
189         }
190         infos.add(toConnectorInfo(protocol));
191       }
192     }
193     return infos;
194   }
195 
196   /**
197    * Tries to open a InputStream the same way as Tomcat ConfigFileLoader
198    * {@link org.apache.tomcat.util.file.ConfigFileLoader#getSource()
199    * getSource.getResource(String).getInputStream()}.
200    *
201    * @param path the path of a store file (absolute or relative to CATALINA.BASE), or URI to store
202    *        file (absolute or relative to CATALINA.BASE).
203    *
204    * @return the input stream of the path file
205    *
206    * @throws IOException if path can not be resolved
207    */
208   private InputStream getStoreInputStream(String path) throws IOException {
209     File file = Path.of(path).toFile();
210     if (file.exists()) {
211       return Files.newInputStream(file.toPath());
212     }
213 
214     File catalinaBaseFolder = Path.of(System.getProperty("catalina.base")).toFile();
215     file = Path.of(catalinaBaseFolder.getPath(), path).toFile();
216     if (file.exists()) {
217       return Files.newInputStream(file.toPath());
218     }
219 
220     URI uri = catalinaBaseFolder.toURI().resolve(path);
221 
222     URL url = uri.toURL();
223 
224     return url.openConnection().getInputStream();
225   }
226 
227   /**
228    * To connector info.
229    *
230    * @param protocol the protocol
231    *
232    * @return the connector info
233    *
234    * @throws IllegalAccessException the illegal access exception
235    * @throws InvocationTargetException the invocation target exception
236    */
237   private ConnectorInfo toConnectorInfo(AbstractHttp11Protocol<?> protocol)
238       throws IllegalAccessException, InvocationTargetException {
239     ConnectorInfo info = new ConnectorInfo();
240     info.setName(ObjectName.unquote(protocol.getName()));
241 
242     String defaultSslHostConfigName = protocol.getDefaultSSLHostConfigName();
243     if (defaultSslHostConfigName == null) {
244       logger.error("Cannot determine defaultSslHostConfigName");
245       return info;
246     }
247     info.setDefaultSslHostConfigName(defaultSslHostConfigName);
248     new SslHostConfigHelper(protocol, info);
249 
250     return info;
251   }
252 
253   /**
254    * Adds the to store.
255    *
256    * @param certs the certs
257    * @param alias the alias
258    * @param x509Cert the x509 cert
259    */
260   private void addToStore(Collection<Cert> certs, String alias, X509Certificate x509Cert) {
261     Cert cert = new Cert();
262 
263     cert.setAlias(alias);
264     cert.setSubjectDistinguishedName(x509Cert.getSubjectX500Principal().toString());
265     cert.setNotBefore(x509Cert.getNotBefore().toInstant());
266     cert.setNotAfter(x509Cert.getNotAfter().toInstant());
267     cert.setIssuerDistinguishedName(x509Cert.getIssuerX500Principal().toString());
268 
269     certs.add(cert);
270   }
271 
272   @Value("certificates")
273   @Override
274   public void setViewName(String viewName) {
275     super.setViewName(viewName);
276   }
277 
278 }