// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.io;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.cert.CertificateFactory;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.util.Objects;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.tools.Utils;
/**
* Class to add missing root certificates to the list of trusted certificates
* for TLS connections.
*
* The added certificates are deemed trustworthy by the main web browsers and
* operating systems, but not included in some distributions of Java.
*
* The certificates are added in-memory at each start, nothing is written to disk.
* @since 9995
*/
public final class CertificateAmendment {
private static final String[] CERT_AMEND = {
"resource://data/security/DST_Root_CA_X3.pem",
"resource://data/security/StartCom_Certification_Authority.pem"
};
private static final String[] SHA_HASHES = {
"0687260331a72403d909f105e69bcf0d32e1bd2493ffc6d9206d11bcd6770739",
"c766a9bef2d4071c863a31aa4920e813b2d198608cb7b7cfe21143b836df09ea"
};
private CertificateAmendment() {
// Hide default constructor for utility classes
}
/**
* Add missing root certificates to the list of trusted certificates for TLS connections.
* @throws IOException if an I/O error occurs
* @throws GeneralSecurityException if a security error occurs
*/
public static void addMissingCertificates() throws IOException, GeneralSecurityException {
if (!Main.pref.getBoolean("tls.add-missing-certificates", true))
return;
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
Path cacertsPath = Paths.get(System.getProperty("java.home"), "lib", "security", "cacerts");
try (InputStream is = Files.newInputStream(cacertsPath)) {
keyStore.load(is, "changeit".toCharArray());
}
CertificateFactory cf = CertificateFactory.getInstance("X.509");
boolean certificateAdded = false;
for (int i = 0; i < CERT_AMEND.length; i++) {
try (CachedFile certCF = new CachedFile(CERT_AMEND[i])) {
byte[] certBytes = certCF.getByteContent();
ByteArrayInputStream certIS = new ByteArrayInputStream(certBytes);
X509Certificate cert = (X509Certificate) cf.generateCertificate(certIS);
MessageDigest md = MessageDigest.getInstance("SHA-256");
String sha1 = Utils.toHexString(md.digest(cert.getEncoded()));
if (!SHA_HASHES[i].equals(sha1)) {
throw new IllegalStateException(
tr("Error adding certificate {0} - certificate fingerprint mismatch. Expected {1}, was {2}",
CERT_AMEND[i],
SHA_HASHES[i],
sha1
));
}
if (certificateIsMissing(keyStore, cert)) {
if (Main.isDebugEnabled()) {
Main.debug(tr("Adding certificate for TLS connections: {0}", cert.getSubjectX500Principal().getName()));
}
String alias = "josm:" + new File(CERT_AMEND[i]).getName();
keyStore.setCertificateEntry(alias, cert);
certificateAdded = true;
}
}
}
if (certificateAdded) {
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
SSLContext.setDefault(sslContext);
}
}
/**
* Check if the certificate is missing and needs to be added to the keystore.
* @param keyStore the keystore
* @param crt the certificate
* @return true, if the certificate is not contained in the keystore
* @throws InvalidAlgorithmParameterException if the keystore does not contain at least one trusted certificate entry
* @throws KeyStoreException if the keystore has not been initialized
*/
private static boolean certificateIsMissing(KeyStore keyStore, X509Certificate crt)
throws KeyStoreException, InvalidAlgorithmParameterException {
PKIXParameters params = new PKIXParameters(keyStore);
String id = crt.getSubjectX500Principal().getName();
for (TrustAnchor ta : params.getTrustAnchors()) {
X509Certificate cert = ta.getTrustedCert();
if (Objects.equals(id, cert.getSubjectX500Principal().getName()))
return false;
}
return true;
}
}