001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.tools;
003
004import static java.awt.event.InputEvent.ALT_DOWN_MASK;
005import static java.awt.event.InputEvent.CTRL_DOWN_MASK;
006import static java.awt.event.InputEvent.SHIFT_DOWN_MASK;
007import static java.awt.event.KeyEvent.VK_A;
008import static java.awt.event.KeyEvent.VK_C;
009import static java.awt.event.KeyEvent.VK_D;
010import static java.awt.event.KeyEvent.VK_DELETE;
011import static java.awt.event.KeyEvent.VK_DOWN;
012import static java.awt.event.KeyEvent.VK_ENTER;
013import static java.awt.event.KeyEvent.VK_ESCAPE;
014import static java.awt.event.KeyEvent.VK_F10;
015import static java.awt.event.KeyEvent.VK_F4;
016import static java.awt.event.KeyEvent.VK_LEFT;
017import static java.awt.event.KeyEvent.VK_NUM_LOCK;
018import static java.awt.event.KeyEvent.VK_PRINTSCREEN;
019import static java.awt.event.KeyEvent.VK_RIGHT;
020import static java.awt.event.KeyEvent.VK_SHIFT;
021import static java.awt.event.KeyEvent.VK_SPACE;
022import static java.awt.event.KeyEvent.VK_TAB;
023import static java.awt.event.KeyEvent.VK_UP;
024import static java.awt.event.KeyEvent.VK_V;
025import static java.awt.event.KeyEvent.VK_X;
026import static java.awt.event.KeyEvent.VK_Y;
027import static java.awt.event.KeyEvent.VK_Z;
028import static org.openstreetmap.josm.tools.I18n.tr;
029
030import java.io.File;
031import java.io.IOException;
032import java.security.InvalidKeyException;
033import java.security.KeyFactory;
034import java.security.KeyStore;
035import java.security.KeyStoreException;
036import java.security.NoSuchAlgorithmException;
037import java.security.NoSuchProviderException;
038import java.security.PublicKey;
039import java.security.SignatureException;
040import java.security.cert.CertificateException;
041import java.security.spec.InvalidKeySpecException;
042import java.security.spec.X509EncodedKeySpec;
043import java.util.ArrayList;
044import java.util.Collection;
045import java.util.Enumeration;
046
047import javax.swing.JOptionPane;
048
049import org.openstreetmap.josm.Main;
050
051/**
052  * {@code PlatformHook} implementation for Microsoft Windows systems.
053  * @since 1023
054  */
055public class PlatformHookWindows extends PlatformHookUnixoid implements PlatformHook {
056
057    private static final byte[] INSECURE_PUBLIC_KEY = new byte[] {
058        0x30, (byte) 0x82, 0x1, 0x22, 0x30, 0xd, 0x6, 0x9, 0x2a, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xf7, 0xd, 0x1, 0x1, 0x1, 0x5, 0x0, 0x3, (byte) 0x82, 0x1, 0xf, 0x0,
059        0x30, (byte) 0x82, 0x01, 0x0a, 0x02, (byte) 0x82, 0x01, 0x01, 0x00, (byte) 0x95, (byte) 0x95, (byte) 0x88,
060        (byte) 0x84, (byte) 0xc8, (byte) 0xd9, 0x6b, (byte) 0xc5, (byte) 0xda, 0x0b, 0x69, (byte) 0xbf, (byte) 0xfc, 0x7e, (byte) 0xb9, (byte) 0x96, 0x2c, (byte) 0xeb, (byte) 0x8f,
061        (byte) 0xbc, 0x6e, 0x40, (byte) 0xe6, (byte) 0xe2, (byte) 0xfc, (byte) 0xf1, 0x7f, 0x73, (byte) 0xa7, (byte) 0x9d, (byte) 0xde, (byte) 0xc7, (byte) 0x88,
062        0x57, 0x51, (byte) 0x84, (byte) 0xed, (byte) 0x96, (byte) 0xfb, (byte) 0xe1, 0x38, (byte) 0xef, 0x08, 0x2b, (byte) 0xf3, (byte) 0xc7, (byte) 0xc3,
063        0x5d, (byte) 0xfe, (byte) 0xf9, 0x51, (byte) 0xe6, 0x29, (byte) 0xfc, (byte) 0xe5, 0x0d, (byte) 0xa1, 0x0d, (byte) 0xa8, (byte) 0xb4, (byte) 0xae,
064        0x26, 0x18, 0x19, 0x4d, 0x6c, 0x0c, 0x3b, 0x12, (byte) 0xba, (byte) 0xbc, 0x5f, 0x32, (byte) 0xb3, (byte) 0xbe,
065        (byte) 0x9d, 0x17, 0x0d, 0x4d, 0x2f, 0x1a, 0x48, (byte) 0xb7, (byte) 0xac, (byte) 0xf7, 0x1a, 0x43, 0x01, (byte) 0x97,
066        (byte) 0xf4, (byte) 0xf8, 0x4c, (byte) 0xbb, 0x6a, (byte) 0xbc, 0x33, (byte) 0xe1, 0x73, 0x1e, (byte) 0x86, (byte) 0xfb, 0x2e, (byte) 0xb1,
067        0x63, 0x75, (byte) 0x85, (byte) 0xdc, (byte) 0x82, 0x6c, 0x28, (byte) 0xf1, (byte) 0xe3, (byte) 0x90, 0x63, (byte) 0x9d, 0x3d, 0x48,
068        (byte) 0x8a, (byte) 0x8c, 0x47, (byte) 0xe2, 0x10, 0x0b, (byte) 0xef, (byte) 0x91, (byte) 0x94, (byte) 0xb0, 0x6c, 0x4c, (byte) 0x80, 0x76,
069        0x03, (byte) 0xe1, (byte) 0xb6, (byte) 0x90, (byte) 0x87, (byte) 0xd9, (byte) 0xae, (byte) 0xf4, (byte) 0x8e, (byte) 0xe0, (byte) 0x9f, (byte) 0xe7, 0x3a, 0x2c,
070        0x2f, 0x21, (byte) 0xd4, 0x46, (byte) 0xba, (byte) 0x95, 0x70, (byte) 0xa9, 0x5b, 0x20, 0x2a, (byte) 0xfa, 0x52, 0x3e,
071        (byte) 0x9d, (byte) 0xd9, (byte) 0xef, 0x28, (byte) 0xc5, (byte) 0xd1, 0x60, (byte) 0x89, 0x68, 0x6e, 0x7f, (byte) 0xd7, (byte) 0x9e, (byte) 0x89,
072        0x4c, (byte) 0xeb, 0x4d, (byte) 0xd2, (byte) 0xc6, (byte) 0xf4, 0x2d, 0x02, 0x5d, (byte) 0xda, (byte) 0xde, 0x33, (byte) 0xfe, (byte) 0xc1,
073        0x7e, (byte) 0xde, 0x4f, 0x1f, (byte) 0x9b, 0x6e, 0x6f, 0x0f, 0x66, 0x71, 0x19, (byte) 0xe9, 0x43, 0x3c,
074        (byte) 0x83, 0x0a, 0x0f, 0x28, 0x21, (byte) 0xc8, 0x38, (byte) 0xd3, 0x4e, 0x48, (byte) 0xdf, (byte) 0xd4, (byte) 0x99, (byte) 0xb5,
075        (byte) 0xc6, (byte) 0x8d, (byte) 0xd4, (byte) 0xc1, 0x69, 0x58, 0x79, (byte) 0x82, 0x32, (byte) 0x82, (byte) 0xd4, (byte) 0x86, (byte) 0xe2, 0x04,
076        0x08, 0x63, (byte) 0x87, (byte) 0xf0, 0x2a, (byte) 0xf6, (byte) 0xec, 0x3e, 0x51, 0x0f, (byte) 0xda, (byte) 0xb4, 0x67, 0x19,
077        0x5e, 0x16, 0x02, (byte) 0x9f, (byte) 0xf1, 0x19, 0x0c, 0x3e, (byte) 0xb8, 0x04, 0x49, 0x07, 0x53, 0x02,
078        0x03, 0x01, 0x00, 0x01
079    };
080
081    private static final String WINDOWS_ROOT = "Windows-ROOT";
082
083    @Override
084    public void openUrl(String url) throws IOException {
085        Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url);
086    }
087
088    @Override
089    public void initSystemShortcuts() {
090        //Shortcut.registerSystemCut("system:menuexit", tr("reserved"), VK_Q, CTRL_DOWN_MASK);
091        Shortcut.registerSystemShortcut("system:duplicate", tr("reserved"), VK_D, CTRL_DOWN_MASK); // not really system, but to avoid odd results
092
093        // Windows 7 shortcuts: http://windows.microsoft.com/en-US/windows7/Keyboard-shortcuts
094
095        // Shortcuts with setAutomatic(): items with automatic shortcuts will not be added to the menu bar at all
096
097        // Don't know why Ctrl-Alt-Del isn't even listed on official Microsoft support page
098        Shortcut.registerSystemShortcut("system:reset", tr("reserved"), VK_DELETE, CTRL_DOWN_MASK | ALT_DOWN_MASK).setAutomatic();
099
100        // Ease of Access keyboard shortcuts
101        Shortcut.registerSystemShortcut("microsoft-reserved-01", tr("reserved"), VK_PRINTSCREEN, ALT_DOWN_MASK | SHIFT_DOWN_MASK).setAutomatic(); // Turn High Contrast on or off
102        Shortcut.registerSystemShortcut("microsoft-reserved-02", tr("reserved"), VK_NUM_LOCK, ALT_DOWN_MASK | SHIFT_DOWN_MASK).setAutomatic(); // Turn Mouse Keys on or off
103        //Shortcut.registerSystemCut("microsoft-reserved-03", tr("reserved"), VK_U, );// Open the Ease of Access Center (TODO: Windows-U, how to handle it in Java ?)
104
105        // General keyboard shortcuts
106        //Shortcut.registerSystemShortcut("system:help", tr("reserved"), VK_F1, 0);                            // Display Help
107        Shortcut.registerSystemShortcut("system:copy", tr("reserved"), VK_C, CTRL_DOWN_MASK);                // Copy the selected item
108        Shortcut.registerSystemShortcut("system:cut", tr("reserved"), VK_X, CTRL_DOWN_MASK);                 // Cut the selected item
109        Shortcut.registerSystemShortcut("system:paste", tr("reserved"), VK_V, CTRL_DOWN_MASK);               // Paste the selected item
110        Shortcut.registerSystemShortcut("system:undo", tr("reserved"), VK_Z, CTRL_DOWN_MASK);                // Undo an action
111        Shortcut.registerSystemShortcut("system:redo", tr("reserved"), VK_Y, CTRL_DOWN_MASK);                // Redo an action
112        //Shortcut.registerSystemCut("microsoft-reserved-10", tr("reserved"), VK_DELETE, 0);                  // Delete the selected item and move it to the Recycle Bin
113        //Shortcut.registerSystemCut("microsoft-reserved-11", tr("reserved"), VK_DELETE, SHIFT_DOWN_MASK);    // Delete the selected item without moving it to the Recycle Bin first
114        //Shortcut.registerSystemCut("system:rename", tr("reserved"), VK_F2, 0);                          // Rename the selected item
115        Shortcut.registerSystemShortcut("system:movefocusright", tr("reserved"), VK_RIGHT, CTRL_DOWN_MASK);  // Move the cursor to the beginning of the next word
116        Shortcut.registerSystemShortcut("system:movefocusleft", tr("reserved"), VK_LEFT, CTRL_DOWN_MASK);    // Move the cursor to the beginning of the previous word
117        Shortcut.registerSystemShortcut("system:movefocusdown", tr("reserved"), VK_DOWN, CTRL_DOWN_MASK);    // Move the cursor to the beginning of the next paragraph
118        Shortcut.registerSystemShortcut("system:movefocusup", tr("reserved"), VK_UP, CTRL_DOWN_MASK);        // Move the cursor to the beginning of the previous paragraph
119        //Shortcut.registerSystemCut("microsoft-reserved-17", tr("reserved"), VK_RIGHT, CTRL_DOWN_MASK | SHIFT_DOWN_MASK); // Select a block of text
120        //Shortcut.registerSystemCut("microsoft-reserved-18", tr("reserved"), VK_LEFT, CTRL_DOWN_MASK | SHIFT_DOWN_MASK);  // Select a block of text
121        //Shortcut.registerSystemCut("microsoft-reserved-19", tr("reserved"), VK_DOWN, CTRL_DOWN_MASK | SHIFT_DOWN_MASK);  // Select a block of text
122        //Shortcut.registerSystemCut("microsoft-reserved-20", tr("reserved"), VK_UP, CTRL_DOWN_MASK | SHIFT_DOWN_MASK);    // Select a block of text
123        //Shortcut.registerSystemCut("microsoft-reserved-21", tr("reserved"), VK_RIGHT, SHIFT_DOWN_MASK); // Select more than one item in a window or on the desktop, or select text within a document
124        //Shortcut.registerSystemCut("microsoft-reserved-22", tr("reserved"), VK_LEFT, SHIFT_DOWN_MASK);  // Select more than one item in a window or on the desktop, or select text within a document
125        //Shortcut.registerSystemCut("microsoft-reserved-23", tr("reserved"), VK_DOWN, SHIFT_DOWN_MASK);  // Select more than one item in a window or on the desktop, or select text within a document
126        //Shortcut.registerSystemCut("microsoft-reserved-24", tr("reserved"), VK_UP, SHIFT_DOWN_MASK);    // Select more than one item in a window or on the desktop, or select text within a document
127        //Shortcut.registerSystemCut("microsoft-reserved-25", tr("reserved"), VK_RIGHT+, CTRL_DOWN_MASK); // Select multiple individual items in a window or on the desktop (TODO: ctrl+arrow+spacebar, how to handle it in Java ?)
128        //Shortcut.registerSystemCut("microsoft-reserved-26", tr("reserved"), VK_LEFT+, CTRL_DOWN_MASK);  // Select multiple individual items in a window or on the desktop (TODO: ctrl+arrow+spacebar, how to handle it in Java ?)
129        //Shortcut.registerSystemCut("microsoft-reserved-27", tr("reserved"), VK_DOWN+, CTRL_DOWN_MASK);  // Select multiple individual items in a window or on the desktop (TODO: ctrl+arrow+spacebar, how to handle it in Java ?)
130        //Shortcut.registerSystemCut("microsoft-reserved-28", tr("reserved"), VK_UP+, CTRL_DOWN_MASK);    // Select multiple individual items in a window or on the desktop (TODO: ctrl+arrow+spacebar, how to handle it in Java ?)
131        Shortcut.registerSystemShortcut("system:selectall", tr("reserved"), VK_A, CTRL_DOWN_MASK);           // Select all items in a document or window
132        //Shortcut.registerSystemCut("system:search", tr("reserved"), VK_F3, 0);                          // Search for a file or folder
133        Shortcut.registerSystemShortcut("microsoft-reserved-31", tr("reserved"), VK_ENTER, ALT_DOWN_MASK).setAutomatic();   // Display properties for the selected item
134        Shortcut.registerSystemShortcut("system:exit", tr("reserved"), VK_F4, ALT_DOWN_MASK).setAutomatic(); // Close the active item, or exit the active program
135        Shortcut.registerSystemShortcut("microsoft-reserved-33", tr("reserved"), VK_SPACE, ALT_DOWN_MASK).setAutomatic();   // Open the shortcut menu for the active window
136        //Shortcut.registerSystemCut("microsoft-reserved-34", tr("reserved"), VK_F4, CTRL_DOWN_MASK);     // Close the active document (in programs that allow you to have multiple documents open simultaneously)
137        Shortcut.registerSystemShortcut("microsoft-reserved-35", tr("reserved"), VK_TAB, ALT_DOWN_MASK).setAutomatic();     // Switch between open items
138        Shortcut.registerSystemShortcut("microsoft-reserved-36", tr("reserved"), VK_TAB, CTRL_DOWN_MASK | ALT_DOWN_MASK).setAutomatic(); // Use the arrow keys to switch between open items
139        //Shortcut.registerSystemCut("microsoft-reserved-37", tr("reserved"), VK_TAB, ); // Cycle through programs on the taskbar by using Aero Flip 3-D (TODO: Windows-Tab, how to handle it in Java ?)
140        //Shortcut.registerSystemCut("microsoft-reserved-38", tr("reserved"), VK_TAB, CTRL_DOWN_MASK | ); // Use the arrow keys to cycle through programs on the taskbar by using Aero Flip 3-D (TODO: Ctrl-Windows-Tab, how to handle it in Java ?)
141        Shortcut.registerSystemShortcut("microsoft-reserved-39", tr("reserved"), VK_ESCAPE, ALT_DOWN_MASK).setAutomatic();  // Cycle through items in the order in which they were opened
142        //Shortcut.registerSystemCut("microsoft-reserved-40", tr("reserved"), VK_F6, 0);                  // Cycle through screen elements in a window or on the desktop
143        //Shortcut.registerSystemCut("microsoft-reserved-41", tr("reserved"), VK_F4, 0);                  // Display the address bar list in Windows Explorer
144        Shortcut.registerSystemShortcut("microsoft-reserved-42", tr("reserved"), VK_F10, SHIFT_DOWN_MASK);   // Display the shortcut menu for the selected item
145        Shortcut.registerSystemShortcut("microsoft-reserved-43", tr("reserved"), VK_ESCAPE, CTRL_DOWN_MASK).setAutomatic(); // Open the Start menu
146        //Shortcut.registerSystemShortcut("microsoft-reserved-44", tr("reserved"), VK_F10, 0);                 // Activate the menu bar in the active program
147        //Shortcut.registerSystemCut("microsoft-reserved-45", tr("reserved"), VK_RIGHT, 0);               // Open the next menu to the right, or open a submenu
148        //Shortcut.registerSystemCut("microsoft-reserved-46", tr("reserved"), VK_LEFT, 0);                // Open the next menu to the left, or close a submenu
149        //Shortcut.registerSystemCut("microsoft-reserved-47", tr("reserved"), VK_F5, 0);                  // Refresh the active window
150        //Shortcut.registerSystemCut("microsoft-reserved-48", tr("reserved"), VK_UP, ALT_DOWN_MASK);      // View the folder one level up in Windows Explorer
151        //Shortcut.registerSystemCut("microsoft-reserved-49", tr("reserved"), VK_ESCAPE, 0);              // Cancel the current task
152        Shortcut.registerSystemShortcut("microsoft-reserved-50", tr("reserved"), VK_ESCAPE, CTRL_DOWN_MASK | SHIFT_DOWN_MASK).setAutomatic(); // Open Task Manager
153        Shortcut.registerSystemShortcut("microsoft-reserved-51", tr("reserved"), VK_SHIFT, ALT_DOWN_MASK).setAutomatic();   // Switch the input language when multiple input languages are enabled
154        Shortcut.registerSystemShortcut("microsoft-reserved-52", tr("reserved"), VK_SHIFT, CTRL_DOWN_MASK).setAutomatic();  // Switch the keyboard layout when multiple keyboard layouts are enabled
155        //Shortcut.registerSystemCut("microsoft-reserved-53", tr("reserved"), ); // Change the reading direction of text in right-to-left reading languages (TODO: unclear)
156    }
157
158    @Override
159    public String getDefaultStyle() {
160        return "com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
161    }
162
163    @Override
164    public boolean rename(File from, File to) {
165        if (to.exists())
166            to.delete();
167        return from.renameTo(to);
168    }
169
170    @Override
171    public String getOSDescription() {
172        return Utils.strip(System.getProperty("os.name")) + " " +
173                ((System.getenv("ProgramFiles(x86)") == null) ? "32" : "64") + "-Bit";
174    }
175
176    /**
177     * Loads Windows-ROOT keystore.
178     * @return Windows-ROOT keystore
179     * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found
180     * @throws CertificateException if any of the certificates in the keystore could not be loaded
181     * @throws IOException if there is an I/O or format problem with the keystore data, if a password is required but not given
182     * @throws KeyStoreException if no Provider supports a KeyStore implementation for the type "Windows-ROOT"
183     * @since 7343
184     */
185    public static KeyStore getRootKeystore() throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException {
186        KeyStore ks = KeyStore.getInstance(WINDOWS_ROOT);
187        ks.load(null, null);
188        return ks;
189    }
190
191    /**
192     * Removes potential insecure certificates installed with previous versions of JOSM on Windows.
193     * @throws NoSuchAlgorithmException on unsupported signature algorithms
194     * @throws CertificateException if any of the certificates in the Windows keystore could not be loaded
195     * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the type "Windows-ROOT"
196     * @throws IOException if there is an I/O or format problem with the keystore data, if a password is required but not given
197     * @since 7335
198     */
199    public static void removeInsecureCertificates() throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException {
200        // 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)
201        PublicKey insecurePubKey = null;
202        try {
203            insecurePubKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(INSECURE_PUBLIC_KEY));
204        } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
205            Main.error(e);
206            return;
207        }
208        KeyStore ks = getRootKeystore();
209        Enumeration<String> en = ks.aliases();
210        Collection<String> insecureCertificates = new ArrayList<>();
211        while (en.hasMoreElements()) {
212            String alias = en.nextElement();
213            // Look for certificates associated with a private key
214            if (ks.isKeyEntry(alias)) {
215                try {
216                    ks.getCertificate(alias).verify(insecurePubKey);
217                    // If no exception, this is a certificate signed with the insecure key -> remove it
218                    insecureCertificates.add(alias);
219                } catch (InvalidKeyException | NoSuchProviderException | SignatureException e) {
220                    // If exception this is not a certificate related to JOSM, just trace it
221                    Main.trace(alias + " --> " + e.getClass().getName());
222                }
223            }
224        }
225        // Remove insecure certificates
226        if (!insecureCertificates.isEmpty()) {
227            StringBuilder message = new StringBuilder("<html>");
228            message.append(tr("A previous version of JOSM has installed a custom certificate in order to provide HTTPS support for Remote Control:"));
229            message.append("<br><ul>");
230            for (String alias : insecureCertificates) {
231                message.append("<li>");
232                message.append(alias);
233                message.append("</li>");
234            }
235            message.append("</ul>");
236            message.append(tr("It appears it could be an important <b>security risk</b>.<br><br>"+
237                    "You are now going to be prompted by Windows to remove this insecure certificate.<br>For your own safety, <b>please click Yes</b> in next dialog."));
238            message.append("</html>");
239            JOptionPane.showMessageDialog(Main.parent, message.toString(), tr("Warning"), JOptionPane.WARNING_MESSAGE);
240            for (String alias : insecureCertificates) {
241                Main.warn(tr("Removing insecure certificate from {0} keystore: {1}", WINDOWS_ROOT, alias));
242                try {
243                    ks.deleteEntry(alias);
244                } catch (KeyStoreException e) {
245                    Main.error(tr("Unable to remove insecure certificate from keystore: {0}", e.getMessage()));
246                }
247            }
248        }
249    }
250
251    @Override
252    public boolean setupHttpsCertificate(String entryAlias, KeyStore.TrustedCertificateEntry trustedCert)
253            throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
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;
261        }
262        // JOSM certificate not found, warn user
263        StringBuilder message = new StringBuilder("<html>");
264        message.append(tr("Remote Control is configured to provide HTTPS support.<br>"+
265                "This requires to add a custom certificate generated by JOSM to the Windows Root CA store.<br><br>"+
266                "You are now going to be prompted by Windows to confirm this operation.<br>"+
267                "To enable proper HTTPS support, <b>please click Yes</b> in next dialog.<br><br>"+
268                "If unsure, you can also click No then disable HTTPS support in Remote Control preferences."));
269        message.append("</html>");
270        JOptionPane.showMessageDialog(Main.parent, message.toString(), tr("HTTPS support in Remote Control"), JOptionPane.INFORMATION_MESSAGE);
271        // install it to Windows-ROOT keystore, used by IE, Chrome and Safari, but not by Firefox
272        Main.info(tr("Adding JOSM localhost certificate to {0} keystore", WINDOWS_ROOT));
273        ks.setEntry(entryAlias, trustedCert, null);
274        return true;
275    }
276}