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.security.KeyStore;
24  import java.security.cert.Certificate;
25  import java.security.cert.X509Certificate;
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.Collections;
29  import java.util.List;
30  
31  import javax.management.ObjectName;
32  
33  import org.apache.catalina.connector.Connector;
34  import org.apache.coyote.ProtocolHandler;
35  import org.apache.coyote.http11.AbstractHttp11JsseProtocol;
36  import org.slf4j.Logger;
37  import org.slf4j.LoggerFactory;
38  import org.springframework.beans.factory.annotation.Value;
39  import org.springframework.stereotype.Controller;
40  import org.springframework.web.bind.annotation.RequestMapping;
41  import org.springframework.web.servlet.ModelAndView;
42  
43  import psiprobe.controllers.AbstractTomcatContainerController;
44  import psiprobe.model.certificates.Cert;
45  import psiprobe.model.certificates.CertificateInfo;
46  import psiprobe.model.certificates.ConnectorInfo;
47  import psiprobe.model.certificates.SslHostConfigInfo;
48  
49  /**
50   * The Class ListCertificatesController.
51   */
52  @Controller
53  public class ListCertificatesController extends AbstractTomcatContainerController {
54  
55    /** The Constant logger. */
56    private static final Logger logger = LoggerFactory.getLogger(ListCertificatesController.class);
57  
58    @RequestMapping(path = "/certificates.htm")
59    @Override
60    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
61        throws Exception {
62      return super.handleRequest(request, response);
63    }
64  
65    @Override
66    protected ModelAndView handleRequestInternal(HttpServletRequest request,
67        HttpServletResponse response) throws Exception {
68  
69      ModelAndView modelAndView = new ModelAndView(getViewName());
70  
71      try {
72        List<Connector> connectors = getContainerWrapper().getTomcatContainer().findConnectors();
73        List<ConnectorInfo> infos = getConnectorInfos(connectors);
74  
75        for (ConnectorInfo info : infos) {
76          List<Cert> certs;
77  
78          List<SslHostConfigInfo> sslHostConfigInfos = info.getSslHostConfigInfos();
79          for (SslHostConfigInfo sslHostConfigInfo : sslHostConfigInfos) {
80            if (sslHostConfigInfo.getTruststoreFile() != null) {
81              certs = getCertificates(sslHostConfigInfo.getTruststoreType(),
82                  sslHostConfigInfo.getTruststoreFile(), sslHostConfigInfo.getTruststorePassword());
83              sslHostConfigInfo.setTrustStoreCerts(certs);
84            }
85  
86            List<CertificateInfo> certificateInfos = sslHostConfigInfo.getCertificateInfos();
87            for (CertificateInfo certificateInfo : certificateInfos) {
88              if (certificateInfo.getCertificateKeystoreFile() != null) {
89                certs = getCertificates(certificateInfo.getCertificateKeystoreType(),
90                    certificateInfo.getCertificateKeystoreFile(),
91                    certificateInfo.getCertificateKeystorePassword());
92                certificateInfo.setKeyStoreCerts(certs);
93              }
94            }
95            sslHostConfigInfo.setCertificateInfos(certificateInfos);
96          }
97          info.setSslHostConfigInfos(sslHostConfigInfos);
98        }
99  
100       modelAndView.addObject("connectors", infos);
101     } catch (Exception e) {
102       logger.error("There was an exception listing certificates", e);
103     }
104 
105     return modelAndView;
106 
107   }
108 
109   /**
110    * Gets the certificates.
111    *
112    * @param storeType the store type
113    * @param storeFile the store file
114    * @param storePassword the store password
115    *
116    * @return the certificates
117    *
118    * @throws Exception the exception
119    */
120   public List<Cert> getCertificates(String storeType, String storeFile, String storePassword)
121       throws Exception {
122     KeyStore keyStore;
123 
124     // Get key store
125     if (storeType != null) {
126       keyStore = KeyStore.getInstance(storeType);
127     } else {
128       keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
129     }
130 
131     // Get password
132     char[] password = null;
133     if (storePassword != null) {
134       password = storePassword.toCharArray();
135     }
136 
137     // Load key store from file
138     try (InputStream storeInput = getStoreInputStream(storeFile)) {
139       keyStore.load(storeInput, password);
140     } catch (IOException e) {
141       logger.error("Error loading store file {}", storeFile, e);
142       return Collections.emptyList();
143     }
144 
145     List<Cert> certs = new ArrayList<>();
146 
147     for (String alias : Collections.list(keyStore.aliases())) {
148 
149       Certificate[] certificateChains = keyStore.getCertificateChain(alias);
150 
151       if (certificateChains != null) {
152         for (Certificate certificateChain : certificateChains) {
153           X509Certificate x509Cert = (X509Certificate) certificateChain;
154           addToStore(certs, alias, x509Cert);
155         }
156       } else {
157         X509Certificate x509Cert = (X509Certificate) keyStore.getCertificate(alias);
158         addToStore(certs, alias, x509Cert);
159       }
160     }
161     return certs;
162   }
163 
164   /**
165    * Gets the connector infos.
166    *
167    * @param connectors the connectors
168    *
169    * @return the connector infos
170    *
171    * @throws IllegalAccessException the illegal access exception
172    * @throws InvocationTargetException the invocation target exception
173    */
174   private List<ConnectorInfo> getConnectorInfos(Collection<Connector> connectors)
175       throws IllegalAccessException, InvocationTargetException {
176     List<ConnectorInfo> infos = new ArrayList<>();
177     for (Connector connector : connectors) {
178       if (!connector.getSecure()) {
179         continue;
180       }
181 
182       ProtocolHandler protocolHandler = connector.getProtocolHandler();
183 
184       if (protocolHandler instanceof AbstractHttp11JsseProtocol) {
185         AbstractHttp11JsseProtocol<?> protocol = (AbstractHttp11JsseProtocol<?>) protocolHandler;
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 = new File(path);
209     if (file.exists()) {
210       return Files.newInputStream(file.toPath());
211     }
212 
213     File catalinaBaseFolder = new File(System.getProperty("catalina.base"));
214     file = new File(catalinaBaseFolder, path);
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(AbstractHttp11JsseProtocol<?> 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 }