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