Changeset 11943 in josm


Ignore:
Timestamp:
2017-04-17T14:07:04+02:00 (4 months ago)
Author:
Don-vip
Message:

fix #14649 - load Dutch Government (G2 & G3) certificates from Windows keystore if not found in Java keystore

Location:
trunk
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/io/CertificateAmendment.java

    r11903 r11943  
    1616import java.security.KeyStoreException;
    1717import java.security.MessageDigest;
     18import java.security.NoSuchAlgorithmException;
     19import java.security.cert.CertificateEncodingException;
     20import java.security.cert.CertificateException;
    1821import java.security.cert.CertificateFactory;
    1922import java.security.cert.PKIXParameters;
     
    4043public final class CertificateAmendment {
    4144
    42     private static final String[] CERT_AMEND = {
    43         "resource://data/security/DST_Root_CA_X3.pem"
     45    /**
     46     * A certificate amendment.
     47     * @since 11940
     48     */
     49    public static class CertAmend {
     50        private final String id;
     51        private final String sha256;
     52
     53        CertAmend(String path, String sha256) {
     54            this.id = path;
     55            this.sha256 = sha256;
     56        }
     57
     58        /**
     59         * Returns the certificate identifier.
     60         * @return path for JOSM embedded certificate, alias for platform certificate
     61         */
     62        public final String getId() {
     63            return id;
     64        }
     65
     66        /**
     67         * Returns the SHA-256 hash.
     68         * @return the SHA-256 hash, in hexadecimal
     69         */
     70        public final String getSha256() {
     71            return sha256;
     72        }
     73    }
     74
     75    /**
     76     * Certificates embedded in JOSM
     77     */
     78    private static final CertAmend[] CERT_AMEND = {
     79        new CertAmend("resource://data/security/DST_Root_CA_X3.pem",
     80                "0687260331a72403d909f105e69bcf0d32e1bd2493ffc6d9206d11bcd6770739")
    4481    };
    4582
    46     private static final String[] SHA_HASHES = {
    47         "0687260331a72403d909f105e69bcf0d32e1bd2493ffc6d9206d11bcd6770739"
     83    /**
     84     * Certificates looked into platform native keystore and not embedded in JOSM.
     85     * Identifiers must match Windows keystore aliases for efficient search.
     86     */
     87    private static final CertAmend[] PLATFORM_CERT_AMEND = {
     88        new CertAmend("Staat der Nederlanden Root CA - G2",
     89                "668c83947da63b724bece1743c31a0e6aed0db8ec5b31be377bb784f91b6716f"),
     90        new CertAmend("Government of Netherlands G3",
     91                "3c4fb0b95ab8b30032f432b86f535fe172c185d0fd39865837cf36187fa6f428")
    4892    };
    4993
     
    66110        }
    67111
     112        MessageDigest md = MessageDigest.getInstance("SHA-256");
    68113        CertificateFactory cf = CertificateFactory.getInstance("X.509");
    69114        boolean certificateAdded = false;
    70         for (int i = 0; i < CERT_AMEND.length; i++) {
    71             try (CachedFile certCF = new CachedFile(CERT_AMEND[i])) {
    72                 byte[] certBytes = certCF.getByteContent();
    73                 ByteArrayInputStream certIS = new ByteArrayInputStream(certBytes);
    74                 X509Certificate cert = (X509Certificate) cf.generateCertificate(certIS);
    75                 MessageDigest md = MessageDigest.getInstance("SHA-256");
    76                 String sha1 = Utils.toHexString(md.digest(cert.getEncoded()));
    77                 if (!SHA_HASHES[i].equals(sha1)) {
    78                     throw new IllegalStateException(
    79                             tr("Error adding certificate {0} - certificate fingerprint mismatch. Expected {1}, was {2}",
    80                             CERT_AMEND[i],
    81                             SHA_HASHES[i],
    82                             sha1
    83                             ));
    84                 }
    85                 if (certificateIsMissing(keyStore, cert)) {
    86                     if (Main.isDebugEnabled()) {
    87                         Main.debug(tr("Adding certificate for TLS connections: {0}", cert.getSubjectX500Principal().getName()));
    88                     }
    89                     String alias = "josm:" + new File(CERT_AMEND[i]).getName();
    90                     keyStore.setCertificateEntry(alias, cert);
     115        // Add embedded certificates. Exit in case of error
     116        for (CertAmend certAmend : CERT_AMEND) {
     117            try (CachedFile certCF = new CachedFile(certAmend.id)) {
     118                X509Certificate cert = (X509Certificate) cf.generateCertificate(
     119                        new ByteArrayInputStream(certCF.getByteContent()));
     120                if (checkAndAddCertificate(md, cert, certAmend, keyStore)) {
    91121                    certificateAdded = true;
    92122                }
    93123            }
     124        }
     125
     126        try {
     127            // Try to add platform certificates. Do not exit in case of error (embedded certificates may be OK)
     128            for (CertAmend certAmend : PLATFORM_CERT_AMEND) {
     129                X509Certificate cert = Main.platform.getX509Certificate(certAmend);
     130                if (checkAndAddCertificate(md, cert, certAmend, keyStore)) {
     131                    certificateAdded = true;
     132                }
     133            }
     134        } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException | IllegalStateException e) {
     135            Main.error(e);
    94136        }
    95137
     
    101143            SSLContext.setDefault(sslContext);
    102144        }
     145    }
     146
     147    private static boolean checkAndAddCertificate(MessageDigest md, X509Certificate cert, CertAmend certAmend, KeyStore keyStore)
     148            throws CertificateEncodingException, KeyStoreException, InvalidAlgorithmParameterException {
     149        if (cert != null) {
     150            String sha256 = Utils.toHexString(md.digest(cert.getEncoded()));
     151            if (!certAmend.sha256.equals(sha256)) {
     152                throw new IllegalStateException(
     153                        tr("Error adding certificate {0} - certificate fingerprint mismatch. Expected {1}, was {2}",
     154                            certAmend.id, certAmend.sha256, sha256));
     155            }
     156            if (certificateIsMissing(keyStore, cert)) {
     157                if (Main.isDebugEnabled()) {
     158                    Main.debug(tr("Adding certificate for TLS connections: {0}", cert.getSubjectX500Principal().getName()));
     159                }
     160                String alias = "josm:" + new File(certAmend.id).getName();
     161                keyStore.setCertificateEntry(alias, cert);
     162                return true;
     163            }
     164        }
     165        return false;
    103166    }
    104167
  • trunk/src/org/openstreetmap/josm/tools/PlatformHook.java

    r11642 r11943  
    99import java.security.NoSuchAlgorithmException;
    1010import java.security.cert.CertificateException;
     11import java.security.cert.X509Certificate;
    1112import java.util.List;
     13
     14import org.openstreetmap.josm.io.CertificateAmendment.CertAmend;
    1215
    1316/**
     
    154157
    155158    /**
     159     * Returns the {@code X509Certificate} matching the given certificate amendment information.
     160     * @param certAmend certificate amendment
     161     * @return the {@code X509Certificate} matching the given certificate amendment information, or {@code null}
     162     * @throws KeyStoreException in case of error
     163     * @throws IOException in case of error
     164     * @throws CertificateException in case of error
     165     * @throws NoSuchAlgorithmException in case of error
     166     * @since 11940
     167     */
     168    default X509Certificate getX509Certificate(CertAmend certAmend)
     169            throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
     170        return null;
     171    }
     172
     173    /**
    156174     * Returns the platform-dependent default cache directory.
    157175     * @return the platform-dependent default cache directory
  • trunk/src/org/openstreetmap/josm/tools/PlatformHookWindows.java

    r11939 r11943  
    4545import java.security.KeyStore;
    4646import java.security.KeyStoreException;
     47import java.security.MessageDigest;
    4748import java.security.NoSuchAlgorithmException;
    4849import java.security.NoSuchProviderException;
    4950import java.security.PublicKey;
    5051import java.security.SignatureException;
     52import java.security.cert.Certificate;
    5153import java.security.cert.CertificateException;
     54import java.security.cert.X509Certificate;
    5255import java.security.spec.InvalidKeySpecException;
    5356import java.security.spec.X509EncodedKeySpec;
     
    6467import org.openstreetmap.josm.Main;
    6568import org.openstreetmap.josm.data.Preferences;
     69import org.openstreetmap.josm.io.CertificateAmendment.CertAmend;
    6670
    6771/**
    68   * {@code PlatformHook} implementation for Microsoft Windows systems.
    69   * @since 1023
    70   */
     72 * {@code PlatformHook} implementation for Microsoft Windows systems.
     73 * @since 1023
     74 */
    7175public class PlatformHookWindows implements PlatformHook {
    7276
     
    351355        ks.setEntry(entryAlias, trustedCert, null);
    352356        return true;
     357    }
     358
     359    @Override
     360    public X509Certificate getX509Certificate(CertAmend certAmend)
     361            throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
     362        KeyStore ks = getRootKeystore();
     363        // Search by alias (fast)
     364        Certificate result = ks.getCertificate(certAmend.getId());
     365        if (result instanceof X509Certificate) {
     366            return (X509Certificate) result;
     367        }
     368        // If not found, search by SHA-256 (slower)
     369        MessageDigest md = MessageDigest.getInstance("SHA-256");
     370        for (Enumeration<String> aliases = ks.aliases(); aliases.hasMoreElements();) {
     371            result = ks.getCertificate(aliases.nextElement());
     372            if (result instanceof X509Certificate
     373                    && certAmend.getSha256().equalsIgnoreCase(Utils.toHexString(md.digest(result.getEncoded())))) {
     374                return (X509Certificate) result;
     375            }
     376        }
     377        // Not found
     378        return null;
    353379    }
    354380
  • trunk/test/unit/org/openstreetmap/josm/JOSMFixture.java

    r10899 r11943  
    2222import org.openstreetmap.josm.io.OsmApi;
    2323import org.openstreetmap.josm.tools.I18n;
     24import org.openstreetmap.josm.tools.JosmRuntimeException;
    2425import org.openstreetmap.josm.tools.Logging;
    2526
     
    111112            CertificateAmendment.addMissingCertificates();
    112113        } catch (IOException | GeneralSecurityException ex) {
    113             throw new RuntimeException(ex);
     114            throw new JosmRuntimeException(ex);
    114115        }
    115116
  • trunk/test/unit/org/openstreetmap/josm/io/CertificateAmendmentTest.java

    r11923 r11943  
    2626    @Rule
    2727    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
    28     public JOSMTestRules test = new JOSMTestRules();
     28    public JOSMTestRules test = new JOSMTestRules().platform().https();
    2929
    3030    /**
     
    8282    }
    8383
     84    /**
     85     * Test Dutch government.
     86     * @throws IOException in case of I/O error
     87     */
     88    @Test
     89    public void testDutchGovernment() throws IOException {
     90        connect("https://geodata.nationaalgeoregister.nl", true);
     91    }
     92
    8493    private static void connect(String url, boolean shouldWork) throws IOException {
    8594        URLConnection connection = new URL(url).openConnection();
  • trunk/test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java

    r11777 r11943  
    44import java.io.File;
    55import java.io.IOException;
     6import java.security.GeneralSecurityException;
    67import java.text.MessageFormat;
    78import java.util.TimeZone;
     
    1718import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
    1819import org.openstreetmap.josm.gui.util.GuiHelper;
     20import org.openstreetmap.josm.io.CertificateAmendment;
    1921import org.openstreetmap.josm.io.OsmApi;
    2022import org.openstreetmap.josm.io.OsmApiInitializationException;
    2123import org.openstreetmap.josm.io.OsmTransferCanceledException;
    2224import org.openstreetmap.josm.tools.I18n;
     25import org.openstreetmap.josm.tools.JosmRuntimeException;
    2326import org.openstreetmap.josm.tools.Logging;
    2427import org.openstreetmap.josm.tools.MemoryManagerTest;
     
    4649    private boolean allowMemoryManagerLeaks;
    4750    private boolean useMapStyles;
     51    private boolean useHttps;
    4852
    4953    /**
     
    137141    public JOSMTestRules projection() {
    138142        useProjection = true;
     143        return this;
     144    }
     145
     146    /**
     147     * Set up HTTPS certificates
     148     * @return this instance, for easy chaining
     149     */
     150    public JOSMTestRules https() {
     151        useHttps = true;
    139152        return this;
    140153    }
     
    222235        }
    223236
     237        // Set Platform
     238        if (platform) {
     239            Main.determinePlatformHook();
     240        }
     241
     242        if (useHttps) {
     243            try {
     244                CertificateAmendment.addMissingCertificates();
     245            } catch (IOException | GeneralSecurityException ex) {
     246                throw new JosmRuntimeException(ex);
     247            }
     248        }
     249
    224250        if (useProjection) {
    225251            Main.setProjection(Projections.getProjectionByCode("EPSG:3857")); // Mercator
     
    241267                throw new InitializationError(e);
    242268            }
    243         }
    244 
    245         // Set Platform
    246         if (platform) {
    247             Main.determinePlatformHook();
    248269        }
    249270
Note: See TracChangeset for help on using the changeset viewer.