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

Last change on this file was 19160, checked in by taylor.smock, 8 months ago

Fix #23825: UI appears to freeze when a multi-select option is used

There are two problems fixed here:

  1. The ListSelectionListener gets called twice; this is fixed by waiting for the list value to stop changing.
  2. The act of applyChangeTags clones any primitive that might be affected by the tag change of the primitive. This can get pretty expensive if relations are involved. This is fixed by cloning the primitive in a different thread.
  • Property svn:eol-style set to native
File size: 11.0 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.Collection;
25import java.util.Collections;
26import java.util.Objects;
27
28import javax.net.ssl.SSLContext;
29import javax.net.ssl.TrustManagerFactory;
30
31import org.openstreetmap.josm.spi.preferences.Config;
32import org.openstreetmap.josm.tools.Logging;
33import org.openstreetmap.josm.tools.PlatformManager;
34import org.openstreetmap.josm.tools.Utils;
35
36/**
37 * Class to add missing root certificates to the list of trusted certificates
38 * for TLS connections.
39 *
40 * The added certificates are deemed trustworthy by the main web browsers and
41 * operating systems, but not included in some distributions of Java.
42 *
43 * The certificates are added in-memory at each start, nothing is written to disk.
44 * @since 9995
45 */
46public final class CertificateAmendment {
47
48 /**
49 * A certificate amendment.
50 * @since 11943
51 */
52 public static class CertAmend {
53 private final String filename;
54 private final String sha256;
55
56 protected CertAmend(String filename, String sha256) {
57 this.filename = Objects.requireNonNull(filename);
58 this.sha256 = Objects.requireNonNull(sha256);
59 }
60
61 /**
62 * Returns the certificate filename.
63 * @return filename for both JOSM embedded certificate and Unix platform certificate
64 * @since 12241
65 */
66 public final String getFilename() {
67 return filename;
68 }
69
70 /**
71 * Returns the SHA-256 hash.
72 * @return the SHA-256 hash, in hexadecimal
73 */
74 public final String getSha256() {
75 return sha256;
76 }
77 }
78
79 /**
80 * An embedded certificate amendment.
81 * @since 13450
82 */
83 public static class EmbeddedCertAmend extends CertAmend {
84 private final String url;
85
86 EmbeddedCertAmend(String url, String filename, String sha256) {
87 super(filename, sha256);
88 this.url = Objects.requireNonNull(url);
89 }
90
91 /**
92 * Returns the embedded URL in JOSM jar.
93 * @return path for JOSM embedded certificate
94 */
95 public final String getUrl() {
96 return url;
97 }
98
99 @Override
100 public String toString() {
101 return url;
102 }
103 }
104
105 /**
106 * A certificate amendment relying on native platform certificate store.
107 * @since 13450
108 */
109 public static class NativeCertAmend extends CertAmend {
110 private final Collection<String> aliases;
111 private final String httpsWebSite;
112
113 NativeCertAmend(Collection<String> aliases, String filename, String sha256, String httpsWebSite) {
114 super(filename, sha256);
115 this.aliases = Objects.requireNonNull(aliases);
116 this.httpsWebSite = Objects.requireNonNull(httpsWebSite);
117 }
118
119 /**
120 * Returns the native aliases in System Root Certificates keystore/keychain.
121 * @return the native aliases in System Root Certificates keystore/keychain
122 * @since 15006
123 */
124 public final Collection<String> getNativeAliases() {
125 return aliases;
126 }
127
128 /**
129 * Returns the https website we need to call to notify Windows we need its root certificate.
130 * @return the https website signed with this root CA
131 * @since 13451
132 */
133 public String getWebSite() {
134 return httpsWebSite;
135 }
136
137 @Override
138 public String toString() {
139 return String.join(" / ", aliases);
140 }
141 }
142
143 /**
144 * Certificates embedded in JOSM
145 */
146 private static final EmbeddedCertAmend[] CERT_AMEND = {
147 };
148
149 /**
150 * Certificates looked into platform native keystore and not embedded in JOSM.
151 * Identifiers must match Windows/macOS keystore aliases and Unix filenames for efficient search.
152 * To find correct values, see:<ul>
153 * <li><a href="https://ccadb-public.secure.force.com/mozilla/IncludedCACertificateReport">Mozilla List</a></li>
154 * <li><a href="https://ccadb-public.secure.force.com/microsoft/IncludedCACertificateReportForMSFT">Microsoft List</a></li>
155 * <li><a href="https://support.apple.com/en-us/HT210770">Apple List</a></li>
156 * </ul>
157 */
158 private static final NativeCertAmend[] PLATFORM_CERT_AMEND = {
159 // #15178 - Trusted and used by French Government - for cadastre - https://www.certigna.fr/autorites/index.xhtml?ac=Racine#lracine
160 // (expires 2027, should be in Java 21)
161 new NativeCertAmend(Collections.singleton("Certigna"),
162 "Certigna.crt",
163 "e3b6a2db2ed7ce48842f7ac53241c7b71d54144bfb40c11f3f1d0b42f5eea12d",
164 "https://www.certigna.fr"),
165 // #16307 - Trusted and used by Slovakian Government - https://eidas.disig.sk/en/cacert/ (expires 2042)
166 new NativeCertAmend(Collections.singleton("CA Disig Root R2"),
167 "CA_Disig_Root_R2.pem",
168 "e23d4a036d7b70e9f595b1422079d2b91edfbb1fb651a0633eaa8a9dc5f80703",
169 "https://eidas.disig.sk"),
170 // #17668 - used by city of Budapest - for https://terinfo.ujbuda.hu - https://e-szigno.hu/ (expires 2029)
171 new NativeCertAmend(Collections.singleton("MicroSec e-Szigno Root CA 2009"),
172 "Microsec_e-Szigno_Root_CA_2009.pem",
173 "3c5f81fea5fab82c64bfa2eaecafcde8e077fc8620a7cae537163df36edbf378",
174 "https://e-szigno.hu"),
175 // #18920 - Spanish Government - https://www.sede.fnmt.gob.es/descargas/certificados-raiz-de-la-fnmt (expires 2030)
176 new NativeCertAmend(Collections.singleton("AC RAIZ FNMT-RCM"),
177 "AC_RAIZ_FNMT-RCM.pem",
178 "ebc5570c29018c4d67b1aa127baf12f703b4611ebc17b7dab5573894179b93fa",
179 "https://www.sede.fnmt.gob.es"),
180 };
181
182 private CertificateAmendment() {
183 // Hide default constructor for utility classes
184 }
185
186 /**
187 * Add missing root certificates to the list of trusted certificates for TLS connections.
188 * @throws IOException if an I/O error occurs
189 * @throws GeneralSecurityException if a security error occurs
190 */
191 public static void addMissingCertificates() throws IOException, GeneralSecurityException {
192 if (!Config.getPref().getBoolean("tls.add-missing-certificates", true))
193 return;
194 KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
195 Path cacertsPath = Paths.get(Utils.getSystemProperty("java.home"), "lib", "security", "cacerts");
196 try (InputStream is = Files.newInputStream(cacertsPath)) {
197 keyStore.load(is, "changeit".toCharArray());
198 } catch (SecurityException e) {
199 Logging.log(Logging.LEVEL_ERROR, "Unable to load keystore", e);
200 return;
201 }
202
203 MessageDigest md = MessageDigest.getInstance("SHA-256");
204 CertificateFactory cf = CertificateFactory.getInstance("X.509");
205 boolean certificateAdded = false;
206 // Add embedded certificates. Exit in case of error
207 for (EmbeddedCertAmend certAmend : CERT_AMEND) {
208 try (CachedFile certCF = new CachedFile(certAmend.url)) {
209 X509Certificate cert = (X509Certificate) cf.generateCertificate(
210 new ByteArrayInputStream(certCF.getByteContent()));
211 if (checkAndAddCertificate(md, cert, certAmend, keyStore)) {
212 certificateAdded = true;
213 }
214 }
215 }
216
217 try {
218 // Try to add platform certificates. Do not exit in case of error (embedded certificates may be OK)
219 for (NativeCertAmend certAmend : PLATFORM_CERT_AMEND) {
220 X509Certificate cert = PlatformManager.getPlatform().getX509Certificate(certAmend);
221 if (checkAndAddCertificate(md, cert, certAmend, keyStore)) {
222 certificateAdded = true;
223 }
224 }
225 } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException | IllegalStateException e) {
226 Logging.error(e);
227 }
228
229 if (certificateAdded) {
230 TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
231 tmf.init(keyStore);
232 SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
233 sslContext.init(null, tmf.getTrustManagers(), null);
234 SSLContext.setDefault(sslContext);
235 }
236 }
237
238 private static boolean checkAndAddCertificate(MessageDigest md, X509Certificate cert, CertAmend certAmend, KeyStore keyStore)
239 throws CertificateEncodingException, KeyStoreException, InvalidAlgorithmParameterException {
240 if (cert != null) {
241 String sha256 = Utils.toHexString(md.digest(cert.getEncoded()));
242 if (!certAmend.sha256.equals(sha256)) {
243 throw new IllegalStateException(
244 tr("Error adding certificate {0} - certificate fingerprint mismatch. Expected {1}, was {2}",
245 certAmend, certAmend.sha256, sha256));
246 }
247 if (certificateIsMissing(keyStore, cert)) {
248 if (Logging.isDebugEnabled()) {
249 Logging.debug("Adding certificate for TLS connections: " + cert.getSubjectX500Principal().getName());
250 }
251 String alias = "josm:" + certAmend.filename;
252 keyStore.setCertificateEntry(alias, cert);
253 return true;
254 }
255 }
256 return false;
257 }
258
259 /**
260 * Check if the certificate is missing and needs to be added to the keystore.
261 * @param keyStore the keystore
262 * @param crt the certificate
263 * @return true, if the certificate is not contained in the keystore
264 * @throws InvalidAlgorithmParameterException if the keystore does not contain at least one trusted certificate entry
265 * @throws KeyStoreException if the keystore has not been initialized
266 */
267 private static boolean certificateIsMissing(KeyStore keyStore, X509Certificate crt)
268 throws KeyStoreException, InvalidAlgorithmParameterException {
269 PKIXParameters params = new PKIXParameters(keyStore);
270 return params.getTrustAnchors().stream()
271 .map(TrustAnchor::getTrustedCert)
272 .noneMatch(c -> Objects.equals(crt.getSubjectX500Principal().getName(), c.getSubjectX500Principal().getName()));
273 }
274}
Note: See TracBrowser for help on using the repository browser.