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 protocol) {
186 if (!protocol.getSecure()) {
187 continue;
188 }
189 infos.add(toConnectorInfo(protocol));
190 }
191 }
192 return infos;
193 }
194
195
196
197
198
199
200
201
202
203
204
205
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
228
229
230
231
232
233
234
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
254
255
256
257
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 }