1
2
3
4
5
6
7
8
9
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
52
53 @Controller
54 public class ListCertificatesController extends AbstractTomcatContainerController {
55
56
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
112
113
114
115
116
117
118
119
120
121 public List<Cert> getCertificates(String storeType, String storeFile, String storePassword)
122 throws Exception {
123 KeyStore keyStore;
124
125
126 if (storeType != null) {
127 keyStore = KeyStore.getInstance(storeType);
128 } else {
129 keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
130 }
131
132
133 char[] password = null;
134 if (storePassword != null) {
135 password = storePassword.toCharArray();
136 }
137
138
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
167
168
169
170
171
172
173
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
198
199
200
201
202
203
204
205
206
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
229
230
231
232
233
234
235
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
255
256
257
258
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 }