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