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}