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;
029import static org.openstreetmap.josm.tools.Utils.getSystemEnv;
030import static org.openstreetmap.josm.tools.Utils.getSystemProperty;
031import static org.openstreetmap.josm.tools.WinRegistry.HKEY_LOCAL_MACHINE;
032
033import java.awt.Desktop;
034import java.awt.GraphicsEnvironment;
035import java.io.BufferedWriter;
036import java.io.File;
037import java.io.IOException;
038import java.io.InputStream;
039import java.io.OutputStream;
040import java.io.OutputStreamWriter;
041import java.io.Writer;
042import java.lang.reflect.InvocationTargetException;
043import java.net.URI;
044import java.net.URISyntaxException;
045import java.nio.charset.StandardCharsets;
046import java.nio.file.DirectoryIteratorException;
047import java.nio.file.DirectoryStream;
048import java.nio.file.FileSystems;
049import java.nio.file.Files;
050import java.nio.file.InvalidPathException;
051import java.nio.file.Path;
052import java.security.InvalidKeyException;
053import java.security.KeyFactory;
054import java.security.KeyStore;
055import java.security.KeyStoreException;
056import java.security.MessageDigest;
057import java.security.NoSuchAlgorithmException;
058import java.security.NoSuchProviderException;
059import java.security.PublicKey;
060import java.security.SignatureException;
061import java.security.cert.Certificate;
062import java.security.cert.CertificateException;
063import java.security.cert.X509Certificate;
064import java.security.spec.InvalidKeySpecException;
065import java.security.spec.X509EncodedKeySpec;
066import java.text.ParseException;
067import java.util.ArrayList;
068import java.util.Arrays;
069import java.util.Collection;
070import java.util.Enumeration;
071import java.util.HashSet;
072import java.util.List;
073import java.util.Locale;
074import java.util.Properties;
075import java.util.Set;
076import java.util.concurrent.ExecutionException;
077import java.util.concurrent.TimeUnit;
078import java.util.regex.Matcher;
079import java.util.regex.Pattern;
080
081import javax.swing.JOptionPane;
082
083import org.openstreetmap.josm.data.Preferences;
084import org.openstreetmap.josm.data.StructUtils;
085import org.openstreetmap.josm.data.StructUtils.StructEntry;
086import org.openstreetmap.josm.data.StructUtils.WriteExplicitly;
087import org.openstreetmap.josm.gui.MainApplication;
088import org.openstreetmap.josm.io.CertificateAmendment.NativeCertAmend;
089import org.openstreetmap.josm.io.NetworkManager;
090import org.openstreetmap.josm.io.OnlineResource;
091import org.openstreetmap.josm.spi.preferences.Config;
092
093/**
094 * {@code PlatformHook} implementation for Microsoft Windows systems.
095 * @since 1023
096 */
097public class PlatformHookWindows implements PlatformHook {
098
099    /**
100     * Pattern of Microsoft .NET and Powershell version numbers in registry.
101     */
102    private static final Pattern MS_VERSION_PATTERN = Pattern.compile("(\\d+)\\.(\\d+)(\\.\\d+.*)?");
103
104    /**
105     * Simple data class to hold information about a font.
106     *
107     * Used for fontconfig.properties files.
108     */
109    public static class FontEntry {
110        /**
111         * The character subset. Basically a free identifier, but should be unique.
112         */
113        @StructEntry
114        public String charset;
115
116        /**
117         * Platform font name.
118         */
119        @StructEntry
120        @WriteExplicitly
121        public String name = "";
122
123        /**
124         * File name.
125         */
126        @StructEntry
127        @WriteExplicitly
128        public String file = "";
129
130        /**
131         * Constructs a new {@code FontEntry}.
132         */
133        public FontEntry() {
134            // Default constructor needed for construction by reflection
135        }
136
137        /**
138         * Constructs a new {@code FontEntry}.
139         * @param charset The character subset. Basically a free identifier, but should be unique
140         * @param name Platform font name
141         * @param file File name
142         */
143        public FontEntry(String charset, String name, String file) {
144            this.charset = charset;
145            this.name = name;
146            this.file = file;
147        }
148    }
149
150    private static final byte[] INSECURE_PUBLIC_KEY = new byte[] {
151        0x30, (byte) 0x82, 0x1, 0x22, 0x30, 0xd, 0x6, 0x9, 0x2a, (byte) 0x86, 0x48,
152        (byte) 0x86, (byte) 0xf7, 0xd, 0x1, 0x1, 0x1, 0x5, 0x0, 0x3, (byte) 0x82, 0x1, 0xf, 0x0,
153        0x30, (byte) 0x82, 0x01, 0x0a, 0x02, (byte) 0x82, 0x01, 0x01, 0x00, (byte) 0x95, (byte) 0x95, (byte) 0x88,
154        (byte) 0x84, (byte) 0xc8, (byte) 0xd9, 0x6b, (byte) 0xc5, (byte) 0xda, 0x0b, 0x69, (byte) 0xbf, (byte) 0xfc,
155        0x7e, (byte) 0xb9, (byte) 0x96, 0x2c, (byte) 0xeb, (byte) 0x8f, (byte) 0xbc, 0x6e, 0x40, (byte) 0xe6, (byte) 0xe2,
156        (byte) 0xfc, (byte) 0xf1, 0x7f, 0x73, (byte) 0xa7, (byte) 0x9d, (byte) 0xde, (byte) 0xc7, (byte) 0x88, 0x57, 0x51,
157        (byte) 0x84, (byte) 0xed, (byte) 0x96, (byte) 0xfb, (byte) 0xe1, 0x38, (byte) 0xef, 0x08, 0x2b, (byte) 0xf3,
158        (byte) 0xc7, (byte) 0xc3, 0x5d, (byte) 0xfe, (byte) 0xf9, 0x51, (byte) 0xe6, 0x29, (byte) 0xfc, (byte) 0xe5, 0x0d,
159        (byte) 0xa1, 0x0d, (byte) 0xa8, (byte) 0xb4, (byte) 0xae, 0x26, 0x18, 0x19, 0x4d, 0x6c, 0x0c, 0x3b, 0x12, (byte) 0xba,
160        (byte) 0xbc, 0x5f, 0x32, (byte) 0xb3, (byte) 0xbe, (byte) 0x9d, 0x17, 0x0d, 0x4d, 0x2f, 0x1a, 0x48, (byte) 0xb7,
161        (byte) 0xac, (byte) 0xf7, 0x1a, 0x43, 0x01, (byte) 0x97, (byte) 0xf4, (byte) 0xf8, 0x4c, (byte) 0xbb, 0x6a, (byte) 0xbc,
162        0x33, (byte) 0xe1, 0x73, 0x1e, (byte) 0x86, (byte) 0xfb, 0x2e, (byte) 0xb1, 0x63, 0x75, (byte) 0x85, (byte) 0xdc,
163        (byte) 0x82, 0x6c, 0x28, (byte) 0xf1, (byte) 0xe3, (byte) 0x90, 0x63, (byte) 0x9d, 0x3d, 0x48, (byte) 0x8a, (byte) 0x8c,
164        0x47, (byte) 0xe2, 0x10, 0x0b, (byte) 0xef, (byte) 0x91, (byte) 0x94, (byte) 0xb0, 0x6c, 0x4c, (byte) 0x80, 0x76, 0x03,
165        (byte) 0xe1, (byte) 0xb6, (byte) 0x90, (byte) 0x87, (byte) 0xd9, (byte) 0xae, (byte) 0xf4, (byte) 0x8e, (byte) 0xe0,
166        (byte) 0x9f, (byte) 0xe7, 0x3a, 0x2c, 0x2f, 0x21, (byte) 0xd4, 0x46, (byte) 0xba, (byte) 0x95, 0x70, (byte) 0xa9, 0x5b,
167        0x20, 0x2a, (byte) 0xfa, 0x52, 0x3e, (byte) 0x9d, (byte) 0xd9, (byte) 0xef, 0x28, (byte) 0xc5, (byte) 0xd1, 0x60,
168        (byte) 0x89, 0x68, 0x6e, 0x7f, (byte) 0xd7, (byte) 0x9e, (byte) 0x89, 0x4c, (byte) 0xeb, 0x4d, (byte) 0xd2, (byte) 0xc6,
169        (byte) 0xf4, 0x2d, 0x02, 0x5d, (byte) 0xda, (byte) 0xde, 0x33, (byte) 0xfe, (byte) 0xc1, 0x7e, (byte) 0xde, 0x4f, 0x1f,
170        (byte) 0x9b, 0x6e, 0x6f, 0x0f, 0x66, 0x71, 0x19, (byte) 0xe9, 0x43, 0x3c, (byte) 0x83, 0x0a, 0x0f, 0x28, 0x21, (byte) 0xc8,
171        0x38, (byte) 0xd3, 0x4e, 0x48, (byte) 0xdf, (byte) 0xd4, (byte) 0x99, (byte) 0xb5, (byte) 0xc6, (byte) 0x8d, (byte) 0xd4,
172        (byte) 0xc1, 0x69, 0x58, 0x79, (byte) 0x82, 0x32, (byte) 0x82, (byte) 0xd4, (byte) 0x86, (byte) 0xe2, 0x04, 0x08, 0x63,
173        (byte) 0x87, (byte) 0xf0, 0x2a, (byte) 0xf6, (byte) 0xec, 0x3e, 0x51, 0x0f, (byte) 0xda, (byte) 0xb4, 0x67, 0x19, 0x5e,
174        0x16, 0x02, (byte) 0x9f, (byte) 0xf1, 0x19, 0x0c, 0x3e, (byte) 0xb8, 0x04, 0x49, 0x07, 0x53, 0x02, 0x03, 0x01, 0x00, 0x01
175    };
176
177    private static final String WINDOWS_ROOT = "Windows-ROOT";
178
179    private static final String CURRENT_VERSION = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";
180
181    private String oSBuildNumber;
182
183    @Override
184    public Platform getPlatform() {
185        return Platform.WINDOWS;
186    }
187
188    @Override
189    public void afterPrefStartupHook() {
190        extendFontconfig("fontconfig.properties.src");
191    }
192
193    @Override
194    public void startupHook(JavaExpirationCallback callback) {
195        checkExpiredJava(callback);
196    }
197
198    @Override
199    public void openUrl(String url) throws IOException {
200        if (!url.startsWith("file:/")) {
201            final String customBrowser = Config.getPref().get("browser.windows", "");
202            if (!customBrowser.isEmpty()) {
203                Runtime.getRuntime().exec(new String[]{customBrowser, url});
204                return;
205            }
206        }
207        try {
208            // Desktop API works fine under Windows
209            Desktop.getDesktop().browse(new URI(url));
210        } catch (IOException | URISyntaxException e) {
211            Logging.log(Logging.LEVEL_WARN, "Desktop class failed. Platform dependent fall back for open url in browser.", e);
212            Runtime.getRuntime().exec(new String[]{"rundll32", "url.dll,FileProtocolHandler", url});
213        }
214    }
215
216    @Override
217    public void initSystemShortcuts() {
218        // CHECKSTYLE.OFF: LineLength
219        //Shortcut.registerSystemCut("system:menuexit", tr("reserved"), VK_Q, CTRL_DOWN_MASK);
220        Shortcut.registerSystemShortcut("system:duplicate", tr("reserved"), VK_D, CTRL_DOWN_MASK); // not really system, but to avoid odd results
221
222        // Windows 7 shortcuts: http://windows.microsoft.com/en-US/windows7/Keyboard-shortcuts
223
224        // Shortcuts with setAutomatic(): items with automatic shortcuts will not be added to the menu bar at all
225
226        // Don't know why Ctrl-Alt-Del isn't even listed on official Microsoft support page
227        Shortcut.registerSystemShortcut("system:reset", tr("reserved"), VK_DELETE, CTRL_DOWN_MASK | ALT_DOWN_MASK).setAutomatic();
228
229        // Ease of Access keyboard shortcuts
230        Shortcut.registerSystemShortcut("microsoft-reserved-01", tr("reserved"), VK_PRINTSCREEN, ALT_DOWN_MASK | SHIFT_DOWN_MASK).setAutomatic(); // Turn High Contrast on or off
231        Shortcut.registerSystemShortcut("microsoft-reserved-02", tr("reserved"), VK_NUM_LOCK, ALT_DOWN_MASK | SHIFT_DOWN_MASK).setAutomatic(); // Turn Mouse Keys on or off
232        //Shortcut.registerSystemCut("microsoft-reserved-03", tr("reserved"), VK_U, );// Open the Ease of Access Center (TODO: Windows-U, how to handle it in Java ?)
233
234        // General keyboard shortcuts
235        //Shortcut.registerSystemShortcut("system:help", tr("reserved"), VK_F1, 0);                            // Display Help
236        Shortcut.registerSystemShortcut("system:copy", tr("reserved"), VK_C, CTRL_DOWN_MASK);                // Copy the selected item
237        Shortcut.registerSystemShortcut("system:cut", tr("reserved"), VK_X, CTRL_DOWN_MASK);                 // Cut the selected item
238        Shortcut.registerSystemShortcut("system:paste", tr("reserved"), VK_V, CTRL_DOWN_MASK);               // Paste the selected item
239        Shortcut.registerSystemShortcut("system:undo", tr("reserved"), VK_Z, CTRL_DOWN_MASK);                // Undo an action
240        Shortcut.registerSystemShortcut("system:redo", tr("reserved"), VK_Y, CTRL_DOWN_MASK);                // Redo an action
241        //Shortcut.registerSystemCut("microsoft-reserved-10", tr("reserved"), VK_DELETE, 0);                  // Delete the selected item and move it to the Recycle Bin
242        //Shortcut.registerSystemCut("microsoft-reserved-11", tr("reserved"), VK_DELETE, SHIFT_DOWN_MASK);    // Delete the selected item without moving it to the Recycle Bin first
243        //Shortcut.registerSystemCut("system:rename", tr("reserved"), VK_F2, 0);                          // Rename the selected item
244        Shortcut.registerSystemShortcut("system:movefocusright", tr("reserved"), VK_RIGHT, CTRL_DOWN_MASK);  // Move the cursor to the beginning of the next word
245        Shortcut.registerSystemShortcut("system:movefocusleft", tr("reserved"), VK_LEFT, CTRL_DOWN_MASK);    // Move the cursor to the beginning of the previous word
246        Shortcut.registerSystemShortcut("system:movefocusdown", tr("reserved"), VK_DOWN, CTRL_DOWN_MASK);    // Move the cursor to the beginning of the next paragraph
247        Shortcut.registerSystemShortcut("system:movefocusup", tr("reserved"), VK_UP, CTRL_DOWN_MASK);        // Move the cursor to the beginning of the previous paragraph
248        //Shortcut.registerSystemCut("microsoft-reserved-17", tr("reserved"), VK_RIGHT, CTRL_DOWN_MASK | SHIFT_DOWN_MASK); // Select a block of text
249        //Shortcut.registerSystemCut("microsoft-reserved-18", tr("reserved"), VK_LEFT, CTRL_DOWN_MASK | SHIFT_DOWN_MASK);  // Select a block of text
250        //Shortcut.registerSystemCut("microsoft-reserved-19", tr("reserved"), VK_DOWN, CTRL_DOWN_MASK | SHIFT_DOWN_MASK);  // Select a block of text
251        //Shortcut.registerSystemCut("microsoft-reserved-20", tr("reserved"), VK_UP, CTRL_DOWN_MASK | SHIFT_DOWN_MASK);    // Select a block of text
252        //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
253        //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
254        //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
255        //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
256        //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 ?)
257        //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 ?)
258        //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 ?)
259        //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 ?)
260        Shortcut.registerSystemShortcut("system:selectall", tr("reserved"), VK_A, CTRL_DOWN_MASK);           // Select all items in a document or window
261        //Shortcut.registerSystemCut("system:search", tr("reserved"), VK_F3, 0);                          // Search for a file or folder
262        Shortcut.registerSystemShortcut("microsoft-reserved-31", tr("reserved"), VK_ENTER, ALT_DOWN_MASK).setAutomatic();   // Display properties for the selected item
263        Shortcut.registerSystemShortcut("system:exit", tr("reserved"), VK_F4, ALT_DOWN_MASK).setAutomatic(); // Close the active item, or exit the active program
264        Shortcut.registerSystemShortcut("microsoft-reserved-33", tr("reserved"), VK_SPACE, ALT_DOWN_MASK).setAutomatic();   // Open the shortcut menu for the active window
265        //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)
266        Shortcut.registerSystemShortcut("microsoft-reserved-35", tr("reserved"), VK_TAB, ALT_DOWN_MASK).setAutomatic();     // Switch between open items
267        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
268        //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 ?)
269        //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 ?)
270        Shortcut.registerSystemShortcut("microsoft-reserved-39", tr("reserved"), VK_ESCAPE, ALT_DOWN_MASK).setAutomatic();  // Cycle through items in the order in which they were opened
271        //Shortcut.registerSystemCut("microsoft-reserved-40", tr("reserved"), VK_F6, 0);                  // Cycle through screen elements in a window or on the desktop
272        //Shortcut.registerSystemCut("microsoft-reserved-41", tr("reserved"), VK_F4, 0);                  // Display the address bar list in Windows Explorer
273        Shortcut.registerSystemShortcut("microsoft-reserved-42", tr("reserved"), VK_F10, SHIFT_DOWN_MASK);   // Display the shortcut menu for the selected item
274        Shortcut.registerSystemShortcut("microsoft-reserved-43", tr("reserved"), VK_ESCAPE, CTRL_DOWN_MASK).setAutomatic(); // Open the Start menu
275        //Shortcut.registerSystemShortcut("microsoft-reserved-44", tr("reserved"), VK_F10, 0);                 // Activate the menu bar in the active program
276        //Shortcut.registerSystemCut("microsoft-reserved-45", tr("reserved"), VK_RIGHT, 0);               // Open the next menu to the right, or open a submenu
277        //Shortcut.registerSystemCut("microsoft-reserved-46", tr("reserved"), VK_LEFT, 0);                // Open the next menu to the left, or close a submenu
278        //Shortcut.registerSystemCut("microsoft-reserved-47", tr("reserved"), VK_F5, 0);                  // Refresh the active window
279        //Shortcut.registerSystemCut("microsoft-reserved-48", tr("reserved"), VK_UP, ALT_DOWN_MASK);      // View the folder one level up in Windows Explorer
280        //Shortcut.registerSystemCut("microsoft-reserved-49", tr("reserved"), VK_ESCAPE, 0);              // Cancel the current task
281        Shortcut.registerSystemShortcut("microsoft-reserved-50", tr("reserved"), VK_ESCAPE, CTRL_DOWN_MASK | SHIFT_DOWN_MASK).setAutomatic(); // Open Task Manager
282        Shortcut.registerSystemShortcut("microsoft-reserved-51", tr("reserved"), VK_SHIFT, ALT_DOWN_MASK).setAutomatic();   // Switch the input language when multiple input languages are enabled
283        Shortcut.registerSystemShortcut("microsoft-reserved-52", tr("reserved"), VK_SHIFT, CTRL_DOWN_MASK).setAutomatic();  // Switch the keyboard layout when multiple keyboard layouts are enabled
284        //Shortcut.registerSystemCut("microsoft-reserved-53", tr("reserved"), ); // Change the reading direction of text in right-to-left reading languages (TODO: unclear)
285        // CHECKSTYLE.ON: LineLength
286    }
287
288    @Override
289    public String getDefaultStyle() {
290        return "com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
291    }
292
293    @Override
294    public boolean rename(File from, File to) {
295        if (to.exists())
296            Utils.deleteFile(to);
297        return from.renameTo(to);
298    }
299
300    @Override
301    public String getOSDescription() {
302        return Utils.strip(getSystemProperty("os.name")) + ' ' +
303                ((getSystemEnv("ProgramFiles(x86)") == null) ? "32" : "64") + "-Bit";
304    }
305
306    /**
307     * Returns the Windows product name from registry (example: "Windows 10 Pro")
308     * @return the Windows product name from registry
309     * @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible
310     * @throws InvocationTargetException if the underlying method throws an exception
311     * @since 12744
312     */
313    public static String getProductName() throws IllegalAccessException, InvocationTargetException {
314        return WinRegistry.readString(HKEY_LOCAL_MACHINE, CURRENT_VERSION, "ProductName");
315    }
316
317    /**
318     * Returns the Windows release identifier from registry (example: "1703")
319     * @return the Windows release identifier from registry
320     * @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible
321     * @throws InvocationTargetException if the underlying method throws an exception
322     * @since 12744
323     */
324    public static String getReleaseId() throws IllegalAccessException, InvocationTargetException {
325        return WinRegistry.readString(HKEY_LOCAL_MACHINE, CURRENT_VERSION, "ReleaseId");
326    }
327
328    /**
329     * Returns the Windows current build number from registry (example: "15063")
330     * @return the Windows current build number from registry
331     * @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible
332     * @throws InvocationTargetException if the underlying method throws an exception
333     * @since 12744
334     */
335    public static String getCurrentBuild() throws IllegalAccessException, InvocationTargetException {
336        return WinRegistry.readString(HKEY_LOCAL_MACHINE, CURRENT_VERSION, "CurrentBuild");
337    }
338
339    private static String buildOSBuildNumber() {
340        StringBuilder sb = new StringBuilder();
341        try {
342            sb.append(getProductName());
343            String releaseId = getReleaseId();
344            if (releaseId != null) {
345                sb.append(' ').append(releaseId);
346            }
347            sb.append(" (").append(getCurrentBuild()).append(')');
348        } catch (ReflectiveOperationException | JosmRuntimeException | NoClassDefFoundError e) {
349            Logging.log(Logging.LEVEL_ERROR, "Unable to get Windows build number", e);
350            Logging.debug(e);
351        }
352        return sb.toString();
353    }
354
355    @Override
356    public String getOSBuildNumber() {
357        if (oSBuildNumber == null) {
358            oSBuildNumber = buildOSBuildNumber();
359        }
360        return oSBuildNumber;
361    }
362
363    /**
364     * Loads Windows-ROOT keystore.
365     * @return Windows-ROOT keystore
366     * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found
367     * @throws CertificateException if any of the certificates in the keystore could not be loaded
368     * @throws IOException if there is an I/O or format problem with the keystore data, if a password is required but not given
369     * @throws KeyStoreException if no Provider supports a KeyStore implementation for the type "Windows-ROOT"
370     * @since 7343
371     */
372    public static KeyStore getRootKeystore() throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException {
373        KeyStore ks = KeyStore.getInstance(WINDOWS_ROOT);
374        ks.load(null, null);
375        return ks;
376    }
377
378    /**
379     * Removes potential insecure certificates installed with previous versions of JOSM on Windows.
380     * @throws NoSuchAlgorithmException on unsupported signature algorithms
381     * @throws CertificateException if any of the certificates in the Windows keystore could not be loaded
382     * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the type "Windows-ROOT"
383     * @throws IOException if there is an I/O or format problem with the keystore data, if a password is required but not given
384     * @since 7335
385     */
386    public static void removeInsecureCertificates() throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException {
387        // 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)
388        PublicKey insecurePubKey = null;
389        try {
390            insecurePubKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(INSECURE_PUBLIC_KEY));
391        } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
392            Logging.error(e);
393            return;
394        }
395        KeyStore ks = getRootKeystore();
396        Enumeration<String> en = ks.aliases();
397        Collection<String> insecureCertificates = new ArrayList<>();
398        while (en.hasMoreElements()) {
399            String alias = en.nextElement();
400            // Look for certificates associated with a private key
401            if (ks.isKeyEntry(alias)) {
402                try {
403                    ks.getCertificate(alias).verify(insecurePubKey);
404                    // If no exception, this is a certificate signed with the insecure key -> remove it
405                    insecureCertificates.add(alias);
406                } catch (InvalidKeyException | NoSuchProviderException | SignatureException e) {
407                    // If exception this is not a certificate related to JOSM, just trace it
408                    Logging.trace(alias + " --> " + e.getClass().getName());
409                    Logging.trace(e);
410                }
411            }
412        }
413        // Remove insecure certificates
414        if (!insecureCertificates.isEmpty()) {
415            StringBuilder message = new StringBuilder("<html>");
416            message.append(tr("A previous version of JOSM has installed a custom certificate "+
417                    "in order to provide HTTPS support for Remote Control:"))
418                   .append("<br><ul>");
419            for (String alias : insecureCertificates) {
420                message.append("<li>")
421                       .append(alias)
422                       .append("</li>");
423            }
424            message.append("</ul>")
425                   .append(tr("It appears it could be an important <b>security risk</b>.<br><br>"+
426                    "You are now going to be prompted by Windows to remove this insecure certificate.<br>"+
427                    "For your own safety, <b>please click Yes</b> in next dialog."))
428                   .append("</html>");
429            JOptionPane.showMessageDialog(MainApplication.getMainFrame(), message.toString(), tr("Warning"), JOptionPane.WARNING_MESSAGE);
430            for (String alias : insecureCertificates) {
431                Logging.warn(tr("Removing insecure certificate from {0} keystore: {1}", WINDOWS_ROOT, alias));
432                try {
433                    ks.deleteEntry(alias);
434                } catch (KeyStoreException e) {
435                    Logging.log(Logging.LEVEL_ERROR, tr("Unable to remove insecure certificate from keystore: {0}", e.getMessage()), e);
436                }
437            }
438        }
439    }
440
441    @Override
442    public boolean setupHttpsCertificate(String entryAlias, KeyStore.TrustedCertificateEntry trustedCert)
443            throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
444        KeyStore ks = getRootKeystore();
445        // Look for certificate to install
446        try {
447            String alias = ks.getCertificateAlias(trustedCert.getTrustedCertificate());
448            if (alias != null) {
449                // JOSM certificate found, return
450                Logging.debug(tr("JOSM localhost certificate found in {0} keystore: {1}", WINDOWS_ROOT, alias));
451                return false;
452            }
453        } catch (ArrayIndexOutOfBoundsException e) {
454            // catch error of JDK-8172244 as bug seems to not be fixed anytime soon
455            Logging.log(Logging.LEVEL_ERROR, "JDK-8172244 occurred. Abort HTTPS setup", e);
456            return false;
457        }
458        if (!GraphicsEnvironment.isHeadless()) {
459            // JOSM certificate not found, warn user
460            StringBuilder message = new StringBuilder("<html>");
461            message.append(tr("Remote Control is configured to provide HTTPS support.<br>"+
462                    "This requires to add a custom certificate generated by JOSM to the Windows Root CA store.<br><br>"+
463                    "You are now going to be prompted by Windows to confirm this operation.<br>"+
464                    "To enable proper HTTPS support, <b>please click Yes</b> in next dialog.<br><br>"+
465                    "If unsure, you can also click No then disable HTTPS support in Remote Control preferences."))
466                   .append("</html>");
467            JOptionPane.showMessageDialog(MainApplication.getMainFrame(), message.toString(),
468                    tr("HTTPS support in Remote Control"), JOptionPane.INFORMATION_MESSAGE);
469        }
470        // install it to Windows-ROOT keystore, used by IE, Chrome and Safari, but not by Firefox
471        Logging.info(tr("Adding JOSM localhost certificate to {0} keystore", WINDOWS_ROOT));
472        ks.setEntry(entryAlias, trustedCert, null);
473        return true;
474    }
475
476    @Override
477    public X509Certificate getX509Certificate(NativeCertAmend certAmend)
478            throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
479        // Get Windows Trust Root Store
480        KeyStore ks = getRootKeystore();
481        // Search by alias (fast)
482        for (String winAlias : certAmend.getNativeAliases()) {
483            Certificate result = ks.getCertificate(winAlias);
484            if (result == null && !NetworkManager.isOffline(OnlineResource.CERTIFICATES)) {
485                // Make a web request to target site to force Windows to update if needed its trust root store from its certificate trust list
486                // A better, but a lot more complex method might be to get certificate list from Windows Registry with PowerShell
487                // using (Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\Microsoft\\SystemCertificates\\AuthRoot\\AutoUpdate').EncodedCtl)
488                // then decode it using CertUtil -dump or calling CertCreateCTLContext API using JNI, and finally find and decode the certificate
489                Logging.trace(webRequest(certAmend.getWebSite()));
490                // Reload Windows Trust Root Store and search again by alias (fast)
491                ks = getRootKeystore();
492                result = ks.getCertificate(winAlias);
493            }
494            if (result instanceof X509Certificate) {
495                return (X509Certificate) result;
496            }
497        }
498        // If not found, search by SHA-256 (slower)
499        MessageDigest md = MessageDigest.getInstance("SHA-256");
500        for (Enumeration<String> aliases = ks.aliases(); aliases.hasMoreElements();) {
501            String alias = aliases.nextElement();
502            Certificate result = ks.getCertificate(alias);
503            if (result instanceof X509Certificate
504                    && certAmend.getSha256().equalsIgnoreCase(Utils.toHexString(md.digest(result.getEncoded())))) {
505                Logging.warn("Certificate not found for alias ''{0}'' but found for alias ''{1}''", certAmend.getNativeAliases(), alias);
506                return (X509Certificate) result;
507            }
508        }
509        // Not found
510        return null;
511    }
512
513    @Override
514    public File getDefaultCacheDirectory() {
515        String p = getSystemEnv("LOCALAPPDATA");
516        if (p == null || p.isEmpty()) {
517            // Fallback for Windows OS earlier than Windows Vista, where the variable is not defined
518            p = getSystemEnv("APPDATA");
519        }
520        return new File(new File(p, Preferences.getJOSMDirectoryBaseName()), "cache");
521    }
522
523    @Override
524    public File getDefaultPrefDirectory() {
525        return new File(getSystemEnv("APPDATA"), Preferences.getJOSMDirectoryBaseName());
526    }
527
528    @Override
529    public File getDefaultUserDataDirectory() {
530        // Use preferences directory by default
531        return Config.getDirs().getPreferencesDirectory(false);
532    }
533
534    /**
535     * <p>Add more fallback fonts to the Java runtime, in order to get
536     * support for more scripts.</p>
537     *
538     * <p>The font configuration in Java doesn't include some Indic scripts,
539     * even though MS Windows ships with fonts that cover these unicode ranges.</p>
540     *
541     * <p>To fix this, the fontconfig.properties template is copied to the JOSM
542     * cache folder. Then, the additional entries are added to the font
543     * configuration. Finally the system property "sun.awt.fontconfig" is set
544     * to the customized fontconfig.properties file.</p>
545     *
546     * <p>This is a crude hack, but better than no font display at all for these languages.
547     * There is no guarantee, that the template file
548     * ($JAVA_HOME/lib/fontconfig.properties.src) matches the default
549     * configuration (which is in a binary format).
550     * Furthermore, the system property "sun.awt.fontconfig" is undocumented and
551     * may no longer work in future versions of Java.</p>
552     *
553     * <p>Related Java bug: <a href="https://bugs.openjdk.java.net/browse/JDK-8008572">JDK-8008572</a></p>
554     *
555     * @param templateFileName file name of the fontconfig.properties template file
556     */
557    protected void extendFontconfig(String templateFileName) {
558        String customFontconfigFile = Config.getPref().get("fontconfig.properties", null);
559        if (customFontconfigFile != null) {
560            Utils.updateSystemProperty("sun.awt.fontconfig", customFontconfigFile);
561            return;
562        }
563        if (!Config.getPref().getBoolean("font.extended-unicode", true))
564            return;
565
566        String javaLibPath = getSystemProperty("java.home") + File.separator + "lib";
567        Path templateFile = FileSystems.getDefault().getPath(javaLibPath, templateFileName);
568        String templatePath = templateFile.toString();
569        if (templatePath.startsWith("null") || !Files.isReadable(templateFile)) {
570            Logging.warn("extended font config - unable to find font config template file {0}", templatePath);
571            return;
572        }
573        try (InputStream fis = Files.newInputStream(templateFile)) {
574            Properties props = new Properties();
575            props.load(fis);
576            byte[] content = Files.readAllBytes(templateFile);
577            File cachePath = Config.getDirs().getCacheDirectory(true);
578            Path fontconfigFile = cachePath.toPath().resolve("fontconfig.properties");
579            OutputStream os = Files.newOutputStream(fontconfigFile);
580            os.write(content);
581            try (Writer w = new BufferedWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8))) {
582                Collection<FontEntry> extrasPref = StructUtils.getListOfStructs(Config.getPref(),
583                        "font.extended-unicode.extra-items", getAdditionalFonts(), FontEntry.class);
584                Collection<FontEntry> extras = new ArrayList<>();
585                w.append("\n\n# Added by JOSM to extend unicode coverage of Java font support:\n\n");
586                List<String> allCharSubsets = new ArrayList<>();
587                for (FontEntry entry: extrasPref) {
588                    Collection<String> fontsAvail = getInstalledFonts();
589                    if (fontsAvail != null && fontsAvail.contains(entry.file.toUpperCase(Locale.ENGLISH))) {
590                        if (!allCharSubsets.contains(entry.charset)) {
591                            allCharSubsets.add(entry.charset);
592                            extras.add(entry);
593                        } else {
594                            Logging.trace("extended font config - already registered font for charset ''{0}'' - skipping ''{1}''",
595                                    entry.charset, entry.name);
596                        }
597                    } else {
598                        Logging.trace("extended font config - Font ''{0}'' not found on system - skipping", entry.name);
599                    }
600                }
601                for (FontEntry entry: extras) {
602                    allCharSubsets.add(entry.charset);
603                    if ("".equals(entry.name)) {
604                        continue;
605                    }
606                    String key = "allfonts." + entry.charset;
607                    String value = entry.name;
608                    String prevValue = props.getProperty(key);
609                    if (prevValue != null && !prevValue.equals(value)) {
610                        Logging.warn("extended font config - overriding ''{0}={1}'' with ''{2}''", key, prevValue, value);
611                    }
612                    w.append(key + '=' + value + '\n');
613                }
614                w.append('\n');
615                for (FontEntry entry: extras) {
616                    if ("".equals(entry.name) || "".equals(entry.file)) {
617                        continue;
618                    }
619                    String key = "filename." + entry.name.replace(' ', '_');
620                    String value = entry.file;
621                    String prevValue = props.getProperty(key);
622                    if (prevValue != null && !prevValue.equals(value)) {
623                        Logging.warn("extended font config - overriding ''{0}={1}'' with ''{2}''", key, prevValue, value);
624                    }
625                    w.append(key + '=' + value + '\n');
626                }
627                w.append('\n');
628                String fallback = props.getProperty("sequence.fallback");
629                if (fallback != null) {
630                    w.append("sequence.fallback=" + fallback + ',' + Utils.join(",", allCharSubsets) + '\n');
631                } else {
632                    w.append("sequence.fallback=" + Utils.join(",", allCharSubsets) + '\n');
633                }
634            }
635            Utils.updateSystemProperty("sun.awt.fontconfig", fontconfigFile.toString());
636        } catch (IOException | InvalidPathException ex) {
637            Logging.error(ex);
638        }
639    }
640
641    /**
642     * Get a list of fonts that are installed on the system.
643     *
644     * Must be done without triggering the Java Font initialization.
645     * (See {@link #extendFontconfig(java.lang.String)}, have to set system
646     * property first, which is then read by sun.awt.FontConfiguration upon initialization.)
647     *
648     * @return list of file names
649     */
650    protected Collection<String> getInstalledFonts() {
651        // Cannot use GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames()
652        // because we have to set the system property before Java initializes its fonts.
653        // Use more low-level method to find the installed fonts.
654        List<String> fontsAvail = new ArrayList<>();
655        Path fontPath = FileSystems.getDefault().getPath(getSystemEnv("SYSTEMROOT"), "Fonts");
656        try (DirectoryStream<Path> ds = Files.newDirectoryStream(fontPath)) {
657            for (Path p : ds) {
658                Path filename = p.getFileName();
659                if (filename != null) {
660                    fontsAvail.add(filename.toString().toUpperCase(Locale.ENGLISH));
661                }
662            }
663            fontsAvail.add(""); // for devanagari
664        } catch (IOException | DirectoryIteratorException ex) {
665            Logging.log(Logging.LEVEL_ERROR, ex);
666            Logging.warn("extended font config - failed to load available Fonts");
667            fontsAvail = null;
668        }
669        return fontsAvail;
670    }
671
672    /**
673     * Get default list of additional fonts to add to the configuration.
674     *
675     * Java will choose thee first font in the list that can render a certain character.
676     *
677     * @return list of FontEntry objects
678     */
679    protected Collection<FontEntry> getAdditionalFonts() {
680        Collection<FontEntry> def = new ArrayList<>(33);
681        def.add(new FontEntry("devanagari", "", "")); // just include in fallback list font already defined in template
682
683        // Windows scripts: https://msdn.microsoft.com/en-us/goglobal/bb688099.aspx
684        // IE default fonts: https://msdn.microsoft.com/en-us/library/ie/dn467844(v=vs.85).aspx
685
686        // Windows 10 and later
687        def.add(new FontEntry("historic", "Segoe UI Historic", "SEGUIHIS.TTF"));       // historic charsets
688
689        // Windows 8/8.1 and later
690        def.add(new FontEntry("javanese", "Javanese Text", "JAVATEXT.TTF"));           // ISO 639: jv
691        def.add(new FontEntry("leelawadee", "Leelawadee", "LEELAWAD.TTF"));            // ISO 639: bug
692        def.add(new FontEntry("malgun", "Malgun Gothic", "MALGUN.TTF"));               // ISO 639: ko
693        def.add(new FontEntry("myanmar", "Myanmar Text", "MMRTEXT.TTF"));              // ISO 639: my
694        def.add(new FontEntry("nirmala", "Nirmala UI", "NIRMALA.TTF"));                // ISO 639: sat,srb
695        def.add(new FontEntry("segoeui", "Segoe UI", "SEGOEUI.TTF"));                  // ISO 639: lis
696        def.add(new FontEntry("emoji", "Segoe UI Emoji", "SEGUIEMJ.TTF"));             // emoji symbol characters
697
698        // Windows 7 and later
699        def.add(new FontEntry("nko_tifinagh_vai_osmanya", "Ebrima", "EBRIMA.TTF"));    // ISO 639: ber. Nko only since Win 8
700        def.add(new FontEntry("khmer1", "Khmer UI", "KHMERUI.TTF"));                   // ISO 639: km
701        def.add(new FontEntry("lao1", "Lao UI", "LAOUI.TTF"));                         // ISO 639: lo
702        def.add(new FontEntry("tai_le", "Microsoft Tai Le", "TAILE.TTF"));             // ISO 639: khb
703        def.add(new FontEntry("new_tai_lue", "Microsoft New Tai Lue", "NTHAILU.TTF")); // ISO 639: khb
704
705        // Windows Vista and later:
706        def.add(new FontEntry("ethiopic", "Nyala", "NYALA.TTF"));                   // ISO 639: am,gez,ti
707        def.add(new FontEntry("tibetan", "Microsoft Himalaya", "HIMALAYA.TTF"));    // ISO 639: bo,dz
708        def.add(new FontEntry("cherokee", "Plantagenet Cherokee", "PLANTC.TTF"));   // ISO 639: chr
709        def.add(new FontEntry("unified_canadian", "Euphemia", "EUPHEMIA.TTF"));     // ISO 639: cr,in
710        def.add(new FontEntry("khmer2", "DaunPenh", "DAUNPENH.TTF"));               // ISO 639: km
711        def.add(new FontEntry("khmer3", "MoolBoran", "MOOLBOR.TTF"));               // ISO 639: km
712        def.add(new FontEntry("lao_thai", "DokChampa", "DOKCHAMP.TTF"));            // ISO 639: lo
713        def.add(new FontEntry("mongolian", "Mongolian Baiti", "MONBAITI.TTF"));     // ISO 639: mn
714        def.add(new FontEntry("oriya", "Kalinga", "KALINGA.TTF"));                  // ISO 639: or
715        def.add(new FontEntry("sinhala", "Iskoola Pota", "ISKPOTA.TTF"));           // ISO 639: si
716        def.add(new FontEntry("yi", "Yi Baiti", "MSYI.TTF"));                       // ISO 639: ii
717
718        // Windows XP and later
719        def.add(new FontEntry("gujarati", "Shruti", "SHRUTI.TTF"));
720        def.add(new FontEntry("kannada", "Tunga", "TUNGA.TTF"));
721        def.add(new FontEntry("gurmukhi", "Raavi", "RAAVI.TTF"));
722        def.add(new FontEntry("telugu", "Gautami", "GAUTAMI.TTF"));
723        def.add(new FontEntry("bengali", "Vrinda", "VRINDA.TTF"));                  // since XP SP2
724        def.add(new FontEntry("syriac", "Estrangelo Edessa", "ESTRE.TTF"));         // ISO 639: arc
725        def.add(new FontEntry("thaana", "MV Boli", "MVBOLI.TTF"));                  // ISO 639: dv
726        def.add(new FontEntry("malayalam", "Kartika", "KARTIKA.TTF"));              // ISO 639: ml; since XP SP2
727
728        // Windows 2000 and later
729        def.add(new FontEntry("tamil", "Latha", "LATHA.TTF"));
730
731        // Comes with MS Office & Outlook 2000. Good unicode coverage, so add if available.
732        def.add(new FontEntry("arialuni", "Arial Unicode MS", "ARIALUNI.TTF"));
733
734        return def;
735    }
736
737    /**
738     * Determines if the .NET framework 4.5 (or later) is installed.
739     * Windows 7 ships by default with an older version.
740     * @return {@code true} if the .NET framework 4.5 (or later) is installed.
741     * @since 13463
742     */
743    public static boolean isDotNet45Installed() {
744        try {
745            // https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed#net_d
746            // "The existence of the Release DWORD indicates that the .NET Framework 4.5 or later has been installed"
747            // Great, but our WinRegistry only handles REG_SZ type, so we have to check the Version key
748            String version = WinRegistry.readString(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full", "Version");
749            if (version != null) {
750                Matcher m = MS_VERSION_PATTERN.matcher(version);
751                if (m.matches()) {
752                    int maj = Integer.parseInt(m.group(1));
753                    int min = Integer.parseInt(m.group(2));
754                    return (maj == 4 && min >= 5) || maj > 4;
755                }
756            }
757        } catch (IllegalAccessException | InvocationTargetException | NumberFormatException e) {
758            Logging.error(e);
759        }
760        return false;
761    }
762
763    /**
764     * Returns the major version number of PowerShell.
765     * @return the major version number of PowerShell. -1 in case of error
766     * @since 13465
767     */
768    public static int getPowerShellVersion() {
769        try {
770            String version = WinRegistry.readString(
771                    HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Powershell\\3\\PowershellEngine", "PowershellVersion");
772            if (version != null) {
773                Matcher m = MS_VERSION_PATTERN.matcher(version);
774                if (m.matches()) {
775                    return Integer.parseInt(m.group(1));
776                }
777            }
778        } catch (NumberFormatException | IllegalAccessException | InvocationTargetException e) {
779            Logging.error(e);
780        }
781        return -1;
782    }
783
784    /**
785     * Performs a web request using Windows CryptoAPI (through PowerShell).
786     * This is useful to ensure Windows trust store will contain a specific root CA.
787     * @param uri the web URI to request
788     * @return HTTP response from the given URI
789     * @throws IOException if any I/O error occurs
790     * @since 13458
791     */
792    public static String webRequest(String uri) throws IOException {
793        // With PS 6.0 (not yet released in Windows) we could simply use:
794        // Invoke-WebRequest -SSlProtocol Tsl12 $uri
795        // .NET framework < 4.5 does not support TLS 1.2 (https://stackoverflow.com/a/43240673/2257172)
796        if (isDotNet45Installed() && getPowerShellVersion() >= 3) {
797            try {
798                // The following works with PS 3.0 (Windows 8+), https://stackoverflow.com/a/41618979/2257172
799                return Utils.execOutput(Arrays.asList("powershell", "-Command",
800                        "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;"+
801                        "[System.Net.WebRequest]::Create('"+uri+"').GetResponse()"
802                        ), 5, TimeUnit.SECONDS);
803            } catch (ExecutionException | InterruptedException e) {
804                Logging.warn("Unable to request certificate of " + uri);
805                Logging.debug(e);
806            }
807        }
808        return null;
809    }
810
811    @Override
812    public File resolveFileLink(File file) {
813        if (file.getName().endsWith(".lnk")) {
814            try {
815                return new File(new WindowsShortcut(file).getRealFilename());
816            } catch (IOException | ParseException e) {
817                Logging.error(e);
818            }
819        }
820        return file;
821    }
822
823    @Override
824    public Collection<String> getPossiblePreferenceDirs() {
825        Set<String> locations = new HashSet<>();
826        String appdata = getSystemEnv("APPDATA");
827        if (appdata != null && getSystemEnv("ALLUSERSPROFILE") != null
828                && appdata.lastIndexOf(File.separator) != -1) {
829            appdata = appdata.substring(appdata.lastIndexOf(File.separator));
830            locations.add(new File(new File(getSystemEnv("ALLUSERSPROFILE"), appdata), "JOSM").getPath());
831        }
832        return locations;
833    }
834}