Ticket #12264: certificate-amendment2.diff

File certificate-amendment2.diff, 10.0 KB (added by bastiK, 10 years ago)
  • src/org/openstreetmap/josm/io/CertificateAmendment.java

     
     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.File;
     8import java.io.IOException;
     9import java.io.InputStream;
     10import java.nio.file.Files;
     11import java.nio.file.Path;
     12import java.nio.file.Paths;
     13import java.security.InvalidAlgorithmParameterException;
     14import java.security.KeyManagementException;
     15import java.security.KeyStore;
     16import java.security.KeyStoreException;
     17import java.security.MessageDigest;
     18import java.security.NoSuchAlgorithmException;
     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.tools.Utils;
     31
     32/**
     33 * Class to add missing root certificates to the list of trusted certificates
     34 * for TLS connections.
     35 *
     36 * The added certificates are deemed trustworthy by the main web browsers and
     37 * operating systems, but not included in some distributions of Java.
     38 *
     39 * The certificates are added in-memory at each start, nothing is written to disk.
     40 */
     41public class CertificateAmendment {
     42
     43    public static final String[] CERT_AMEND = { "resource://data/security/DST_Root_CA_X3.pem" };
     44    public static final String[] SHA_HASHES = { "139a5e4a4e0fa505378c72c5f700934ce8333f4e6b1b508886c4b0eb14f4be99" };
     45
     46    /**
     47     * Add missing root certificates to the list of trusted certificates for TLS connections.
     48     * @throws IOException if an I/O error occurs
     49     */
     50    public static void addMissingCertificates() throws IOException {
     51        KeyStore keyStore;
     52        try {
     53            keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
     54        } catch (KeyStoreException ex) {
     55            throw new IOException(ex);
     56        }
     57        Path cacertsPath = Paths.get(System.getProperty("java.home"), "lib", "security", "cacerts");
     58        try (InputStream is = Files.newInputStream(cacertsPath)) {
     59            keyStore.load(is, "changeit".toCharArray());
     60        } catch (NoSuchAlgorithmException ex) {
     61            throw new RuntimeException(ex);
     62        } catch (CertificateException ex) {
     63            throw new IOException(ex);
     64        }
     65
     66        CertificateFactory cf;
     67        try {
     68            cf = CertificateFactory.getInstance("X.509");
     69        } catch (CertificateException ex) {
     70            throw new RuntimeException(ex);
     71        }
     72        boolean certificateAdded = false;
     73        for (int i = 0; i < CERT_AMEND.length; i++) {
     74            CachedFile certCF = new CachedFile(CERT_AMEND[i]);
     75            byte[] certBytes = certCF.getByteContent();
     76            MessageDigest md;
     77            try {
     78                md = MessageDigest.getInstance("SHA-256");
     79            } catch (NoSuchAlgorithmException ex) {
     80                throw new RuntimeException(ex);
     81            }
     82            byte[] sha = md.digest(certBytes);
     83            if (!SHA_HASHES[i].equals(Utils.toHexString(sha)))
     84                throw new RuntimeException(tr("certificate hash mismatch"));
     85
     86            ByteArrayInputStream certIS = new ByteArrayInputStream(certBytes);
     87            X509Certificate cert;
     88            try {
     89                cert = (X509Certificate) cf.generateCertificate(certIS);
     90            } catch (CertificateException ex) {
     91                throw new IOException(ex);
     92            }
     93            if (certificateIsMissing(keyStore, cert)) {
     94                Main.debug(tr("Adding certificate for TLS connections: {0}", cert.getSubjectX500Principal().getName()));
     95                String alias = "josm:" + new File(CERT_AMEND[i]).getName();
     96                try {
     97                    keyStore.setCertificateEntry(alias, cert);
     98                } catch (KeyStoreException ex) {
     99                    throw new AssertionError(ex);
     100                }
     101                certificateAdded = true;
     102            }
     103        }
     104
     105        if (certificateAdded) {
     106            try {
     107                TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
     108                tmf.init(keyStore);
     109                SSLContext sslContext = SSLContext.getInstance("TLS");
     110                sslContext.init(null, tmf.getTrustManagers(), null);
     111                SSLContext.setDefault(sslContext);
     112            } catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException ex) {
     113                throw new RuntimeException(ex);
     114            }
     115        }
     116    }
     117
     118    /**
     119     * Check if the certificate is missing and needs to be added to the keystore.
     120     * @param keyStore the keystore
     121     * @param crt the certificate
     122     * @return true, if the certificate is not contained in the keystore
     123     */
     124    private static boolean certificateIsMissing(KeyStore keyStore, X509Certificate crt) {
     125        String id = crt.getSubjectX500Principal().getName();
     126        PKIXParameters params;
     127        try {
     128            params = new PKIXParameters(keyStore);
     129        } catch (KeyStoreException ex) {
     130            throw new AssertionError(ex);
     131        } catch (InvalidAlgorithmParameterException ex) {
     132            throw new RuntimeException(ex);
     133        }
     134        for (TrustAnchor ta : params.getTrustAnchors()) {
     135            X509Certificate cert = ta.getTrustedCert();
     136            if (Objects.equals(id, cert.getSubjectX500Principal().getName()))
     137                return false;
     138        }
     139        return true;
     140    }
     141}
  • src/org/openstreetmap/josm/gui/MainApplication.java

    Property changes on: src/org/openstreetmap/josm/io/CertificateAmendment.java
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
    5757import org.openstreetmap.josm.gui.preferences.server.OAuthAccessTokenHolder;
    5858import org.openstreetmap.josm.gui.preferences.server.ProxyPreference;
    5959import org.openstreetmap.josm.gui.util.GuiHelper;
     60import org.openstreetmap.josm.io.CertificateAmendment;
    6061import org.openstreetmap.josm.io.DefaultProxySelector;
    6162import org.openstreetmap.josm.io.MessageNotifier;
    6263import org.openstreetmap.josm.io.OnlineResource;
     
    400401            }
    401402        }
    402403
     404        try {
     405            CertificateAmendment.addMissingCertificates();
     406        } catch (IOException ex) {
     407            ex.printStackTrace();
     408            Main.warn(getErrorMessage(Utils.getRootCause(ex)));
     409        }
    403410        Authenticator.setDefault(DefaultAuthenticator.getInstance());
    404411        DefaultProxySelector proxySelector = new DefaultProxySelector(ProxySelector.getDefault());
    405412        ProxySelector.setDefault(proxySelector);
  • src/org/openstreetmap/josm/io/CachedFile.java

     
    55
    66import java.io.BufferedInputStream;
    77import java.io.BufferedReader;
     8import java.io.ByteArrayOutputStream;
    89import java.io.Closeable;
    910import java.io.File;
    1011import java.io.FileInputStream;
     
    212213    }
    213214
    214215    /**
     216     * Get the full content of the requested resource as a byte array.
     217     * @return the full content of the requested resource as byte array
     218     * @throws IOException in case of an I/O error
     219     */
     220    public byte[] getByteContent() throws IOException {
     221        try (InputStream is = getInputStream()) {
     222            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
     223            int nRead;
     224            byte[] data = new byte[8192];
     225            while ((nRead = is.read(data, 0, data.length)) != -1) {
     226                buffer.write(data, 0, nRead);
     227            }
     228            buffer.flush();
     229            return buffer.toByteArray();
     230        }
     231    }
     232
     233    /**
    215234     * Returns {@link #getInputStream()} wrapped in a buffered reader.
    216235     * <p>
    217236     * Detects Unicode charset in use utilizing {@link UTFInputStreamReader}.
  • data/security/DST_Root_CA_X3.pem

     
     1-----BEGIN CERTIFICATE-----
     2MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/
     3MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
     4DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow
     5PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD
     6Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
     7AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O
     8rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq
     9OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b
     10xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw
     117BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD
     12aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
     13HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG
     14SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69
     15ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr
     16AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz
     17R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
     18JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
     19Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
     20-----END CERTIFICATE-----