source: josm/trunk/src/org/openstreetmap/josm/tools/PlatformHookWindows.java@ 14253

Last change on this file since 14253 was 14161, checked in by Don-vip, 6 years ago

fix #16646 - catch DirectoryIteratorException when loading Fonts on Windows

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