Changeset 7343 in josm


Ignore:
Timestamp:
2014-07-28T16:40:19+02:00 (5 years ago)
Author:
Don-vip
Message:

see #10230, see #10033 - add "Install/uninstall certificate" buttons in remote control preferences (Windows only)

Location:
trunk/src/org/openstreetmap/josm
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/gui/MainApplication.java

    r7335 r7343  
    445445                // Check for insecure certificates to remove.
    446446                // This is Windows-dependant code but it can't go to preStartupHook (need i18n) neither startupHook (need to be called before remote control)
    447                 ((PlatformHookWindows)Main.platform).removeInsecureCertificates();
     447                PlatformHookWindows.removeInsecureCertificates();
    448448            } catch (NoSuchAlgorithmException | CertificateException | KeyStoreException | IOException e) {
    449449                error(e);
  • trunk/src/org/openstreetmap/josm/gui/preferences/remotecontrol/RemoteControlPreference.java

    r7335 r7343  
    99import java.awt.event.ActionEvent;
    1010import java.awt.event.ActionListener;
     11import java.io.IOException;
     12import java.security.GeneralSecurityException;
     13import java.security.KeyStore;
     14import java.security.KeyStoreException;
     15import java.security.NoSuchAlgorithmException;
     16import java.security.cert.CertificateException;
    1117import java.util.LinkedHashMap;
    1218import java.util.Map;
     
    1521import javax.swing.BorderFactory;
    1622import javax.swing.Box;
     23import javax.swing.JButton;
    1724import javax.swing.JCheckBox;
    1825import javax.swing.JLabel;
     26import javax.swing.JOptionPane;
    1927import javax.swing.JPanel;
    2028import javax.swing.JSeparator;
     
    3139import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler;
    3240import org.openstreetmap.josm.tools.GBC;
     41import org.openstreetmap.josm.tools.PlatformHookWindows;
    3342
    3443/**
     
    6170    private JCheckBox enableRemoteControl;
    6271    private JCheckBox enableHttpsSupport;
    63     private JCheckBox loadInNewLayer = new JCheckBox(tr("Download objects to new layer"));
    64     private JCheckBox alwaysAskUserConfirm = new JCheckBox(tr("Confirm all Remote Control actions manually"));
     72
     73    private JButton installCertificate;
     74    private JButton uninstallCertificate;
     75
     76    private final JCheckBox loadInNewLayer = new JCheckBox(tr("Download objects to new layer"));
     77    private final JCheckBox alwaysAskUserConfirm = new JCheckBox(tr("Confirm all Remote Control actions manually"));
    6578
    6679    @Override
     
    91104        remote.add(wrapper, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 5, 5, 5));
    92105
    93         enableHttpsSupport = new JCheckBox(tr("Enable HTTPS support"), RemoteControl.PROP_REMOTECONTROL_HTTPS_ENABLED.get());
     106        boolean https = RemoteControl.PROP_REMOTECONTROL_HTTPS_ENABLED.get();
     107
     108        enableHttpsSupport = new JCheckBox(tr("Enable HTTPS support"), https);
    94109        wrapper.add(enableHttpsSupport, GBC.eol().fill(GBC.HORIZONTAL));
     110
     111        // Certificate installation only available on Windows for now, see #10033
     112        if (Main.isPlatformWindows()) {
     113            installCertificate = new JButton(tr("Install..."));
     114            uninstallCertificate = new JButton(tr("Uninstall..."));
     115            installCertificate.setToolTipText(tr("Install JOSM localhost certificate to system/browser root keystores"));
     116            uninstallCertificate.setToolTipText(tr("Uninstall JOSM localhost certificate from system/browser root keystores"));
     117            wrapper.add(new JLabel(tr("Certificate:")), GBC.std().insets(15, 5, 0, 0));
     118            wrapper.add(installCertificate, GBC.std().insets(5, 5, 0, 0));
     119            wrapper.add(uninstallCertificate, GBC.eol().insets(5, 5, 0, 0));
     120            enableHttpsSupport.addActionListener(new ActionListener() {
     121                @Override
     122                public void actionPerformed(ActionEvent e) {
     123                    installCertificate.setEnabled(enableHttpsSupport.isSelected());
     124                }
     125            });
     126            installCertificate.addActionListener(new ActionListener() {
     127                @Override
     128                public void actionPerformed(ActionEvent e) {
     129                    try {
     130                        boolean changed = RemoteControlHttpsServer.setupPlatform(
     131                                RemoteControlHttpsServer.loadJosmKeystore());
     132                        String msg = changed ?
     133                                tr("Certificate has been successfully installed.") :
     134                                tr("Certificate is already installed. Nothing to do.");
     135                        Main.info(msg);
     136                        JOptionPane.showMessageDialog(wrapper, msg);
     137                    } catch (IOException | GeneralSecurityException ex) {
     138                        Main.error(ex);
     139                    }
     140                }
     141            });
     142            uninstallCertificate.addActionListener(new ActionListener() {
     143                @Override
     144                public void actionPerformed(ActionEvent e) {
     145                    try {
     146                        String msg;
     147                        KeyStore ks = PlatformHookWindows.getRootKeystore();
     148                        if (ks.containsAlias(RemoteControlHttpsServer.ENTRY_ALIAS)) {
     149                            Main.info(tr("Removing certificate {0} from root keystore.", RemoteControlHttpsServer.ENTRY_ALIAS));
     150                            ks.deleteEntry(RemoteControlHttpsServer.ENTRY_ALIAS);
     151                            msg = tr("Certificate has been successfully uninstalled.");
     152                        } else {
     153                            msg = tr("Certificate is not installed. Nothing to do.");
     154                        }
     155                        Main.info(msg);
     156                        JOptionPane.showMessageDialog(wrapper, msg);
     157                    } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException ex) {
     158                        Main.error(ex);
     159                    }
     160                }
     161            });
     162            installCertificate.setEnabled(https);
     163        }
     164
    95165        wrapper.add(new JSeparator(), GBC.eop().fill(GBC.HORIZONTAL).insets(15, 5, 15, 5));
    96166
     
    116186                // 'setEnabled(false)' does not work for JLabel with html text, so do it manually
    117187                // FIXME: use QuadStateCheckBox to make checkboxes unset when disabled
     188                if (installCertificate != null && uninstallCertificate != null) {
     189                    // Install certificate button is enabled if HTTPS is also enabled
     190                    installCertificate.setEnabled(enableRemoteControl.isSelected() && enableHttpsSupport.isSelected());
     191                    // Uninstall certificate button is always enabled
     192                    uninstallCertificate.setEnabled(true);
     193                }
    118194            }
    119195        };
  • trunk/src/org/openstreetmap/josm/io/remotecontrol/RemoteControlHttpsServer.java

    r7338 r7343  
    2121import java.security.KeyPairGenerator;
    2222import java.security.KeyStore;
     23import java.security.KeyStoreException;
    2324import java.security.NoSuchAlgorithmException;
    2425import java.security.PrivateKey;
    2526import java.security.SecureRandom;
    2627import java.security.cert.Certificate;
     28import java.security.cert.CertificateException;
    2729import java.security.cert.X509Certificate;
    2830import java.util.Arrays;
     
    9193     * @since 7335
    9294     */
    93     public StringProperty KEYSTORE_PASSWORD = new StringProperty("remotecontrol.https.keystore.password", "");
     95    public static final StringProperty KEYSTORE_PASSWORD = new StringProperty("remotecontrol.https.keystore.password", "");
    9496
    9597    /**
     
    9799     * @since 7335
    98100     */
    99     public StringProperty KEYENTRY_PASSWORD = new StringProperty("remotecontrol.https.keyentry.password", "");
     101    public static final StringProperty KEYENTRY_PASSWORD = new StringProperty("remotecontrol.https.keyentry.password", "");
     102
     103    /**
     104     * Unique alias used to store JOSM localhost entry, both in JOSM keystore and system/browser keystores.
     105     * @since 7343
     106     */
     107    public static final String ENTRY_ALIAS = "josm_localhost";
    100108
    101109    /**
     
    194202    }
    195203
     204    /**
     205     * Setup the JOSM internal keystore, used to store HTTPS certificate and private key.
     206     * @return Path to the (initialized) JOSM keystore
     207     * @throws IOException if an I/O error occurs
     208     * @throws GeneralSecurityException if a security error occurs
     209     * @since 7343
     210     */
     211    public static Path setupJosmKeystore() throws IOException, GeneralSecurityException {
     212
     213        char[] storePassword = KEYSTORE_PASSWORD.get().toCharArray();
     214        char[] entryPassword = KEYENTRY_PASSWORD.get().toCharArray();
     215
     216        Path dir = Paths.get(RemoteControl.getRemoteControlDir());
     217        Path path = dir.resolve(KEYSTORE_FILENAME);
     218        Files.createDirectories(dir);
     219
     220        if (!Files.exists(path)) {
     221            Main.debug("No keystore found, creating a new one");
     222
     223            // Create new keystore like previous one generated with JDK keytool as follows:
     224            // keytool -genkeypair -storepass josm_ssl -keypass josm_ssl -alias josm_localhost -dname "CN=localhost, OU=JOSM, O=OpenStreetMap"
     225            // -ext san=ip:127.0.0.1 -keyalg RSA -validity 1825
     226
     227            KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
     228            generator.initialize(2048);
     229            KeyPair pair = generator.generateKeyPair();
     230
     231            X509Certificate cert = generateCertificate("CN=localhost, OU=JOSM, O=OpenStreetMap", pair, 1825, "SHA256withRSA",
     232                    "dns:localhost,ip:127.0.0.1,ip:::1,uri:https://127.0.0.1:"+HTTPS_PORT+",uri:https://::1:"+HTTPS_PORT);
     233
     234            KeyStore ks = KeyStore.getInstance("JKS");
     235            ks.load(null, null);
     236
     237            // Generate new passwords. See https://stackoverflow.com/a/41156/2257172
     238            SecureRandom random = new SecureRandom();
     239            KEYSTORE_PASSWORD.put(new BigInteger(130, random).toString(32));
     240            KEYENTRY_PASSWORD.put(new BigInteger(130, random).toString(32));
     241
     242            storePassword = KEYSTORE_PASSWORD.get().toCharArray();
     243            entryPassword = KEYENTRY_PASSWORD.get().toCharArray();
     244
     245            ks.setKeyEntry(ENTRY_ALIAS, pair.getPrivate(), entryPassword, new Certificate[]{cert});
     246            ks.store(Files.newOutputStream(path, StandardOpenOption.CREATE), storePassword);
     247        }
     248        return path;
     249    }
     250
     251    /**
     252     * Loads the JOSM keystore.
     253     * @return the (initialized) JOSM keystore
     254     * @throws IOException if an I/O error occurs
     255     * @throws GeneralSecurityException if a security error occurs
     256     * @since 7343
     257     */
     258    public static KeyStore loadJosmKeystore() throws IOException, GeneralSecurityException {
     259        try (InputStream in = Files.newInputStream(setupJosmKeystore())) {
     260            KeyStore ks = KeyStore.getInstance("JKS");
     261            ks.load(in, KEYSTORE_PASSWORD.get().toCharArray());
     262
     263            if (Main.isDebugEnabled()) {
     264                for (Enumeration<String> aliases = ks.aliases(); aliases.hasMoreElements();) {
     265                    Main.debug("Alias in JOSM keystore: "+aliases.nextElement());
     266                }
     267            }
     268            return ks;
     269        }
     270    }
     271
    196272    private void initialize() {
    197273        if (!initOK) {
    198274            try {
    199                 char[] storePassword = KEYSTORE_PASSWORD.get().toCharArray();
    200                 char[] entryPassword = KEYENTRY_PASSWORD.get().toCharArray();
    201 
    202                 Path dir = Paths.get(RemoteControl.getRemoteControlDir());
    203                 Path path = dir.resolve(KEYSTORE_FILENAME);
    204                 Files.createDirectories(dir);
    205 
    206                 if (!Files.exists(path)) {
    207                     Main.debug("No keystore found, creating a new one");
    208 
    209                     // Create new keystore like previous one generated with JDK keytool as follows:
    210                     // keytool -genkeypair -storepass josm_ssl -keypass josm_ssl -alias josm_localhost -dname "CN=localhost, OU=JOSM, O=OpenStreetMap"
    211                     // -ext san=ip:127.0.0.1 -keyalg RSA -validity 1825
    212 
    213                     KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
    214                     generator.initialize(2048);
    215                     KeyPair pair = generator.generateKeyPair();
    216 
    217                     X509Certificate cert = generateCertificate("CN=localhost, OU=JOSM, O=OpenStreetMap", pair, 1825, "SHA256withRSA",
    218                             "dns:localhost,ip:127.0.0.1,ip:::1,uri:https://127.0.0.1:"+HTTPS_PORT+",uri:https://::1:"+HTTPS_PORT);
    219 
    220                     KeyStore ks = KeyStore.getInstance("JKS");
    221                     ks.load(null, null);
    222 
    223                     // Generate new passwords. See https://stackoverflow.com/a/41156/2257172
    224                     SecureRandom random = new SecureRandom();
    225                     KEYSTORE_PASSWORD.put(new BigInteger(130, random).toString(32));
    226                     KEYENTRY_PASSWORD.put(new BigInteger(130, random).toString(32));
    227 
    228                     storePassword = KEYSTORE_PASSWORD.get().toCharArray();
    229                     entryPassword = KEYENTRY_PASSWORD.get().toCharArray();
    230 
    231                     ks.setKeyEntry("josm_localhost", pair.getPrivate(), entryPassword, new Certificate[]{cert});
    232                     ks.store(Files.newOutputStream(path, StandardOpenOption.CREATE), storePassword);
    233                 }
    234 
    235                 try (InputStream in = Files.newInputStream(path)) {
    236                     // Load keystore
    237                     KeyStore ks = KeyStore.getInstance("JKS");
    238                     ks.load(in, storePassword);
    239 
    240                     if (Main.isDebugEnabled()) {
    241                         for (Enumeration<String> aliases = ks.aliases(); aliases.hasMoreElements();) {
    242                             Main.debug("Alias in keystore: "+aliases.nextElement());
    243                         }
    244                     }
    245 
    246                     KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
    247                     kmf.init(ks, entryPassword);
    248 
    249                     TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
    250                     tmf.init(ks);
    251 
    252                     sslContext = SSLContext.getInstance("TLS");
    253                     sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
    254 
    255                     if (Main.isTraceEnabled()) {
    256                         Main.trace("SSL Context protocol: " + sslContext.getProtocol());
    257                         Main.trace("SSL Context provider: " + sslContext.getProvider());
    258                     }
    259 
    260                     Enumeration<String> aliases = ks.aliases();
    261                     if (aliases.hasMoreElements()) {
    262                         Main.platform.setupHttpsCertificate(new KeyStore.TrustedCertificateEntry(ks.getCertificate(aliases.nextElement())));
    263                     }
    264 
    265                     initOK = true;
    266                 }
     275                KeyStore ks = loadJosmKeystore();
     276
     277                KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
     278                kmf.init(ks, KEYENTRY_PASSWORD.get().toCharArray());
     279
     280                TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
     281                tmf.init(ks);
     282
     283                sslContext = SSLContext.getInstance("TLS");
     284                sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
     285
     286                if (Main.isTraceEnabled()) {
     287                    Main.trace("SSL Context protocol: " + sslContext.getProtocol());
     288                    Main.trace("SSL Context provider: " + sslContext.getProvider());
     289                }
     290
     291                setupPlatform(ks);
     292
     293                initOK = true;
    267294            } catch (IOException | GeneralSecurityException e) {
    268295                Main.error(e);
    269296            }
    270297        }
     298    }
     299
     300    /**
     301     * Setup the platform-dependant certificate stuff.
     302     * @param josmKs The JOSM keystore, containing localhost certificate and private key.
     303     * @return {@code true} if something has changed as a result of the call (certificate installation, etc.)
     304     * @throws KeyStoreException if the keystore has not been initialized (loaded)
     305     * @throws NoSuchAlgorithmException in case of error
     306     * @throws CertificateException in case of error
     307     * @throws IOException in case of error
     308     * @since 7343
     309     */
     310    public static boolean setupPlatform(KeyStore josmKs) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
     311        Enumeration<String> aliases = josmKs.aliases();
     312        if (aliases.hasMoreElements()) {
     313            return Main.platform.setupHttpsCertificate(ENTRY_ALIAS,
     314                    new KeyStore.TrustedCertificateEntry(josmKs.getCertificate(aliases.nextElement())));
     315        }
     316        return false;
    271317    }
    272318
  • trunk/src/org/openstreetmap/josm/tools/PlatformHook.java

    r7335 r7343  
    109109    /**
    110110     * Setup system keystore to add JOSM HTTPS certificate (for remote control).
     111     * @param entryAlias The entry alias to use
    111112     * @param trustedCert the JOSM certificate for localhost
     113     * @return {@code true} if something has changed as a result of the call (certificate installation, etc.)
    112114     * @throws KeyStoreException in case of error
    113115     * @throws IOException in case of error
    114116     * @throws CertificateException in case of error
    115117     * @throws NoSuchAlgorithmException in case of error
    116      * @since 7206
     118     * @since 7343
    117119     */
    118     public void setupHttpsCertificate(KeyStore.TrustedCertificateEntry trustedCert)
     120    public boolean setupHttpsCertificate(String entryAlias, KeyStore.TrustedCertificateEntry trustedCert)
    119121            throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException;
    120122}
  • trunk/src/org/openstreetmap/josm/tools/PlatformHookUnixoid.java

    r7335 r7343  
    358358
    359359    @Override
    360     public void setupHttpsCertificate(KeyStore.TrustedCertificateEntry trustedCert)
     360    public boolean setupHttpsCertificate(String entryAlias, KeyStore.TrustedCertificateEntry trustedCert)
    361361            throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
    362362        // TODO setup HTTPS certificate on Unix systems
     363        return false;
    363364    }
    364365}
  • trunk/src/org/openstreetmap/josm/tools/PlatformHookWindows.java

    r7342 r7343  
    181181     * @throws IOException if there is an I/O or format problem with the keystore data, if a password is required but not given
    182182     * @throws KeyStoreException if no Provider supports a KeyStore implementation for the type "Windows-ROOT"
     183     * @since 7343
    183184     */
    184     private KeyStore getWindowsKeystore() throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException {
     185    public static KeyStore getRootKeystore() throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException {
    185186        KeyStore ks = KeyStore.getInstance(WINDOWS_ROOT);
    186187        ks.load(null, null);
     
    196197     * @since 7335
    197198     */
    198     public void removeInsecureCertificates() throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException {
     199    public static void removeInsecureCertificates() throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException {
    199200        // We offered before a public private key we need now to remove from Windows PCs as it might be a huge security risk (see #10230)
    200201        PublicKey insecurePubKey = null;
     
    205206            return;
    206207        }
    207         KeyStore ks = getWindowsKeystore();
     208        KeyStore ks = getRootKeystore();
    208209        Enumeration<String> en = ks.aliases();
    209210        Collection<String> insecureCertificates = new ArrayList<>();
     
    249250
    250251    @Override
    251     public void setupHttpsCertificate(KeyStore.TrustedCertificateEntry trustedCert)
     252    public boolean setupHttpsCertificate(String entryAlias, KeyStore.TrustedCertificateEntry trustedCert)
    252253            throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
    253         KeyStore ks = getWindowsKeystore();
    254         Enumeration<String> en = ks.aliases();
    255 
    256         while (en.hasMoreElements()) {
    257             String alias = en.nextElement();
    258             // Look for certificate to install
    259             if (ks.getCertificate(alias).equals(trustedCert.getTrustedCertificate())) {
    260                 // JOSM certificate found, return
    261                 Main.debug("JOSM certificate found: "+alias);
    262                 return;
    263             }
     254        KeyStore ks = getRootKeystore();
     255        // Look for certificate to install
     256        String alias = ks.getCertificateAlias(trustedCert.getTrustedCertificate());
     257        if (alias != null) {
     258            // JOSM certificate found, return
     259            Main.debug(tr("JOSM localhost certificate found in {0} keystore: {1}", WINDOWS_ROOT, alias));
     260            return false;
    264261        }
    265262        // JOSM certificate not found, install it to Windows-ROOT keystore, used by IE, Chrome and Safari, but not by Firefox
    266263        Main.info(tr("Adding JOSM localhost certificate to {0} keystore", WINDOWS_ROOT));
    267         ks.setEntry("josm_localhost", trustedCert, null);
     264        ks.setEntry(entryAlias, trustedCert, null);
     265        return true;
    268266    }
    269267}
Note: See TracChangeset for help on using the changeset viewer.