source: josm/trunk/src/org/openstreetmap/josm/io/CertificateAmendment.java@ 13660

Last change on this file since 13660 was 13647, checked in by Don-vip, 6 years ago

see #16204 - Allow to start and close JOSM in WebStart sandbox mode (where every external access is denied). This was very useful to reproduce some very tricky bugs that occured in real life but were almost impossible to diagnose.

  • Property svn:eol-style set to native
File size: 10.5 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.ByteArrayInputStream;
7import java.io.IOException;
8import java.io.InputStream;
9import java.nio.file.Files;
10import java.nio.file.Path;
11import java.nio.file.Paths;
12import java.security.GeneralSecurityException;
13import java.security.InvalidAlgorithmParameterException;
14import java.security.KeyStore;
15import java.security.KeyStoreException;
16import java.security.MessageDigest;
17import java.security.NoSuchAlgorithmException;
18import java.security.cert.CertificateEncodingException;
19import java.security.cert.CertificateException;
20import java.security.cert.CertificateFactory;
21import java.security.cert.PKIXParameters;
22import java.security.cert.TrustAnchor;
23import java.security.cert.X509Certificate;
24import java.util.Objects;
25
26import javax.net.ssl.SSLContext;
27import javax.net.ssl.TrustManagerFactory;
28
29import org.openstreetmap.josm.Main;
30import org.openstreetmap.josm.spi.preferences.Config;
31import org.openstreetmap.josm.tools.Logging;
32import org.openstreetmap.josm.tools.Utils;
33
34/**
35 * Class to add missing root certificates to the list of trusted certificates
36 * for TLS connections.
37 *
38 * The added certificates are deemed trustworthy by the main web browsers and
39 * operating systems, but not included in some distributions of Java.
40 *
41 * The certificates are added in-memory at each start, nothing is written to disk.
42 * @since 9995
43 */
44public final class CertificateAmendment {
45
46 /**
47 * A certificate amendment.
48 * @since 11943
49 */
50 public static class CertAmend {
51 private final String filename;
52 private final String sha256;
53
54 protected CertAmend(String filename, String sha256) {
55 this.filename = Objects.requireNonNull(filename);
56 this.sha256 = Objects.requireNonNull(sha256);
57 }
58
59 /**
60 * Returns the certificate filename.
61 * @return filename for both JOSM embedded certificate and Unix platform certificate
62 * @since 12241
63 */
64 public final String getFilename() {
65 return filename;
66 }
67
68 /**
69 * Returns the SHA-256 hash.
70 * @return the SHA-256 hash, in hexadecimal
71 */
72 public final String getSha256() {
73 return sha256;
74 }
75 }
76
77 /**
78 * An embedded certificate amendment.
79 * @since 13450
80 */
81 public static class EmbeddedCertAmend extends CertAmend {
82 private final String url;
83
84 EmbeddedCertAmend(String url, String filename, String sha256) {
85 super(filename, sha256);
86 this.url = Objects.requireNonNull(url);
87 }
88
89 /**
90 * Returns the embedded URL in JOSM jar.
91 * @return path for JOSM embedded certificate
92 */
93 public final String getUrl() {
94 return url;
95 }
96
97 @Override
98 public String toString() {
99 return url;
100 }
101 }
102
103 /**
104 * A certificate amendment relying on native platform certificate store.
105 * @since 13450
106 */
107 public static class NativeCertAmend extends CertAmend {
108 private final String winAlias;
109 private final String macAlias;
110 private final String httpsWebSite;
111
112 NativeCertAmend(String winAlias, String macAlias, String filename, String sha256, String httpsWebSite) {
113 super(filename, sha256);
114 this.winAlias = Objects.requireNonNull(winAlias);
115 this.macAlias = Objects.requireNonNull(macAlias);
116 this.httpsWebSite = Objects.requireNonNull(httpsWebSite);
117 }
118
119 /**
120 * Returns the Windows alias in System Root Certificates keystore.
121 * @return the Windows alias in System Root Certificates keystore
122 */
123 public final String getWinAlias() {
124 return winAlias;
125 }
126
127 /**
128 * Returns the macOS alias in System Root Certificates keychain.
129 * @return the macOS alias in System Root Certificates keychain
130 */
131 public final String getMacAlias() {
132 return macAlias;
133 }
134
135 /**
136 * Returns the https website we need to call to notify Windows we need its root certificate.
137 * @return the https website signed with this root CA
138 * @since 13451
139 */
140 public String getWebSite() {
141 return httpsWebSite;
142 }
143
144 @Override
145 public String toString() {
146 String result = winAlias;
147 if (!winAlias.equals(macAlias)) {
148 result += " / " + macAlias;
149 }
150 return result;
151 }
152 }
153
154 /**
155 * Certificates embedded in JOSM
156 */
157 private static final EmbeddedCertAmend[] CERT_AMEND = {
158 };
159
160 /**
161 * Certificates looked into platform native keystore and not embedded in JOSM.
162 * Identifiers must match Windows/macOS keystore aliases and Unix filenames for efficient search.
163 */
164 private static final NativeCertAmend[] PLATFORM_CERT_AMEND = {
165 // Government of Netherlands
166 new NativeCertAmend("Staat der Nederlanden Root CA - G2", "Staat der Nederlanden Root CA - G2",
167 "Staat_der_Nederlanden_Root_CA_-_G2.crt",
168 "668c83947da63b724bece1743c31a0e6aed0db8ec5b31be377bb784f91b6716f",
169 "https://roottest-g2.pkioverheid.nl"),
170 // Government of Netherlands
171 new NativeCertAmend("Government of Netherlands G3", "Staat der Nederlanden Root CA - G3",
172 "Staat_der_Nederlanden_Root_CA_-_G3.crt",
173 "3c4fb0b95ab8b30032f432b86f535fe172c185d0fd39865837cf36187fa6f428",
174 "https://roottest-g3.pkioverheid.nl"),
175 // Trusted and used by French Government - https://www.certigna.fr/autorites/index.xhtml?ac=Racine#lracine
176 new NativeCertAmend("Certigna", "Certigna", "Certigna.crt",
177 "e3b6a2db2ed7ce48842f7ac53241c7b71d54144bfb40c11f3f1d0b42f5eea12d",
178 "https://www.certigna.fr"),
179 };
180
181 private CertificateAmendment() {
182 // Hide default constructor for utility classes
183 }
184
185 /**
186 * Add missing root certificates to the list of trusted certificates for TLS connections.
187 * @throws IOException if an I/O error occurs
188 * @throws GeneralSecurityException if a security error occurs
189 */
190 public static void addMissingCertificates() throws IOException, GeneralSecurityException {
191 if (!Config.getPref().getBoolean("tls.add-missing-certificates", true))
192 return;
193 KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
194 Path cacertsPath = Paths.get(Utils.getSystemProperty("java.home"), "lib", "security", "cacerts");
195 try (InputStream is = Files.newInputStream(cacertsPath)) {
196 keyStore.load(is, "changeit".toCharArray());
197 } catch (SecurityException e) {
198 Logging.log(Logging.LEVEL_ERROR, "Unable to load keystore", e);
199 return;
200 }
201
202 MessageDigest md = MessageDigest.getInstance("SHA-256");
203 CertificateFactory cf = CertificateFactory.getInstance("X.509");
204 boolean certificateAdded = false;
205 // Add embedded certificates. Exit in case of error
206 for (EmbeddedCertAmend certAmend : CERT_AMEND) {
207 try (CachedFile certCF = new CachedFile(certAmend.url)) {
208 X509Certificate cert = (X509Certificate) cf.generateCertificate(
209 new ByteArrayInputStream(certCF.getByteContent()));
210 if (checkAndAddCertificate(md, cert, certAmend, keyStore)) {
211 certificateAdded = true;
212 }
213 }
214 }
215
216 try {
217 // Try to add platform certificates. Do not exit in case of error (embedded certificates may be OK)
218 for (NativeCertAmend certAmend : PLATFORM_CERT_AMEND) {
219 X509Certificate cert = Main.platform.getX509Certificate(certAmend);
220 if (checkAndAddCertificate(md, cert, certAmend, keyStore)) {
221 certificateAdded = true;
222 }
223 }
224 } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException | IllegalStateException e) {
225 Logging.error(e);
226 }
227
228 if (certificateAdded) {
229 TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
230 tmf.init(keyStore);
231 SSLContext sslContext = SSLContext.getInstance("TLS");
232 sslContext.init(null, tmf.getTrustManagers(), null);
233 SSLContext.setDefault(sslContext);
234 }
235 }
236
237 private static boolean checkAndAddCertificate(MessageDigest md, X509Certificate cert, CertAmend certAmend, KeyStore keyStore)
238 throws CertificateEncodingException, KeyStoreException, InvalidAlgorithmParameterException {
239 if (cert != null) {
240 String sha256 = Utils.toHexString(md.digest(cert.getEncoded()));
241 if (!certAmend.sha256.equals(sha256)) {
242 throw new IllegalStateException(
243 tr("Error adding certificate {0} - certificate fingerprint mismatch. Expected {1}, was {2}",
244 certAmend, certAmend.sha256, sha256));
245 }
246 if (certificateIsMissing(keyStore, cert)) {
247 if (Logging.isDebugEnabled()) {
248 Logging.debug(tr("Adding certificate for TLS connections: {0}", cert.getSubjectX500Principal().getName()));
249 }
250 String alias = "josm:" + certAmend.filename;
251 keyStore.setCertificateEntry(alias, cert);
252 return true;
253 }
254 }
255 return false;
256 }
257
258 /**
259 * Check if the certificate is missing and needs to be added to the keystore.
260 * @param keyStore the keystore
261 * @param crt the certificate
262 * @return true, if the certificate is not contained in the keystore
263 * @throws InvalidAlgorithmParameterException if the keystore does not contain at least one trusted certificate entry
264 * @throws KeyStoreException if the keystore has not been initialized
265 */
266 private static boolean certificateIsMissing(KeyStore keyStore, X509Certificate crt)
267 throws KeyStoreException, InvalidAlgorithmParameterException {
268 PKIXParameters params = new PKIXParameters(keyStore);
269 String id = crt.getSubjectX500Principal().getName();
270 for (TrustAnchor ta : params.getTrustAnchors()) {
271 X509Certificate cert = ta.getTrustedCert();
272 if (Objects.equals(id, cert.getSubjectX500Principal().getName()))
273 return false;
274 }
275 return true;
276 }
277}
Note: See TracBrowser for help on using the repository browser.