001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.io; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.io.ByteArrayInputStream; 007import java.io.File; 008import java.io.IOException; 009import java.io.InputStream; 010import java.nio.file.Files; 011import java.nio.file.Path; 012import java.nio.file.Paths; 013import java.security.GeneralSecurityException; 014import java.security.InvalidAlgorithmParameterException; 015import java.security.KeyStore; 016import java.security.KeyStoreException; 017import java.security.MessageDigest; 018import java.security.cert.CertificateFactory; 019import java.security.cert.PKIXParameters; 020import java.security.cert.TrustAnchor; 021import java.security.cert.X509Certificate; 022import java.util.Objects; 023 024import javax.net.ssl.SSLContext; 025import javax.net.ssl.TrustManagerFactory; 026 027import org.openstreetmap.josm.Main; 028import org.openstreetmap.josm.tools.Utils; 029 030/** 031 * Class to add missing root certificates to the list of trusted certificates 032 * for TLS connections. 033 * 034 * The added certificates are deemed trustworthy by the main web browsers and 035 * operating systems, but not included in some distributions of Java. 036 * 037 * The certificates are added in-memory at each start, nothing is written to disk. 038 * @since 9995 039 */ 040public final class CertificateAmendment { 041 042 private static final String[] CERT_AMEND = { 043 "resource://data/security/DST_Root_CA_X3.pem", 044 "resource://data/security/StartCom_Certification_Authority.pem" 045 }; 046 047 private static final String[] SHA_HASHES = { 048 "0687260331a72403d909f105e69bcf0d32e1bd2493ffc6d9206d11bcd6770739", 049 "c766a9bef2d4071c863a31aa4920e813b2d198608cb7b7cfe21143b836df09ea" 050 }; 051 052 private CertificateAmendment() { 053 // Hide default constructor for utility classes 054 } 055 056 /** 057 * Add missing root certificates to the list of trusted certificates for TLS connections. 058 * @throws IOException if an I/O error occurs 059 * @throws GeneralSecurityException if a security error occurs 060 */ 061 public static void addMissingCertificates() throws IOException, GeneralSecurityException { 062 if (!Main.pref.getBoolean("tls.add-missing-certificates", true)) 063 return; 064 KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); 065 Path cacertsPath = Paths.get(System.getProperty("java.home"), "lib", "security", "cacerts"); 066 try (InputStream is = Files.newInputStream(cacertsPath)) { 067 keyStore.load(is, "changeit".toCharArray()); 068 } 069 070 CertificateFactory cf = CertificateFactory.getInstance("X.509"); 071 boolean certificateAdded = false; 072 for (int i = 0; i < CERT_AMEND.length; i++) { 073 try (CachedFile certCF = new CachedFile(CERT_AMEND[i])) { 074 byte[] certBytes = certCF.getByteContent(); 075 ByteArrayInputStream certIS = new ByteArrayInputStream(certBytes); 076 X509Certificate cert = (X509Certificate) cf.generateCertificate(certIS); 077 MessageDigest md = MessageDigest.getInstance("SHA-256"); 078 String sha1 = Utils.toHexString(md.digest(cert.getEncoded())); 079 if (!SHA_HASHES[i].equals(sha1)) { 080 throw new IllegalStateException( 081 tr("Error adding certificate {0} - certificate fingerprint mismatch. Expected {1}, was {2}", 082 CERT_AMEND[i], 083 SHA_HASHES[i], 084 sha1 085 )); 086 } 087 if (certificateIsMissing(keyStore, cert)) { 088 if (Main.isDebugEnabled()) { 089 Main.debug(tr("Adding certificate for TLS connections: {0}", cert.getSubjectX500Principal().getName())); 090 } 091 String alias = "josm:" + new File(CERT_AMEND[i]).getName(); 092 keyStore.setCertificateEntry(alias, cert); 093 certificateAdded = true; 094 } 095 } 096 } 097 098 if (certificateAdded) { 099 TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 100 tmf.init(keyStore); 101 SSLContext sslContext = SSLContext.getInstance("TLS"); 102 sslContext.init(null, tmf.getTrustManagers(), null); 103 SSLContext.setDefault(sslContext); 104 } 105 } 106 107 /** 108 * Check if the certificate is missing and needs to be added to the keystore. 109 * @param keyStore the keystore 110 * @param crt the certificate 111 * @return true, if the certificate is not contained in the keystore 112 * @throws InvalidAlgorithmParameterException if the keystore does not contain at least one trusted certificate entry 113 * @throws KeyStoreException if the keystore has not been initialized 114 */ 115 private static boolean certificateIsMissing(KeyStore keyStore, X509Certificate crt) 116 throws KeyStoreException, InvalidAlgorithmParameterException { 117 PKIXParameters params = new PKIXParameters(keyStore); 118 String id = crt.getSubjectX500Principal().getName(); 119 for (TrustAnchor ta : params.getTrustAnchors()) { 120 X509Certificate cert = ta.getTrustedCert(); 121 if (Objects.equals(id, cert.getSubjectX500Principal().getName())) 122 return false; 123 } 124 return true; 125 } 126}