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