Changeset 7335 in josm for trunk/src


Ignore:
Timestamp:
2014-07-26T03:50:31+02:00 (5 years ago)
Author:
Don-vip
Message:

see #10230, see #10033 - big rework of HTTPS support for Remote Control:

  • HTTPS disabled by default, must be enabled in remote control preferences
  • Old certificate and private key removed from jar and Windows keystore if found, even if remote control disabled
  • New certificate generated at runtime with critical X509 extensions BasicConstraints (non-CA certificate), ExtendedKeyUsage (usage restriction for TLS server sessions)
  • New passwords generated at runtime (but stored in clear in user preferences)
  • Private key is no longer stored in Windows keystore (only certificate)
Location:
trunk/src/org/openstreetmap/josm
Files:
13 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/Main.java

    r7294 r7335  
    15141514        return Main.platform instanceof PlatformHookOsx;
    15151515    }
     1516
     1517    /**
     1518     * Determines if we are currently running on Windows.
     1519     * @return {@code true} if we are currently running on Windows
     1520     * @since 7335
     1521     */
     1522    public static boolean isPlatformWindows() {
     1523        return Main.platform instanceof PlatformHookWindows;
     1524    }
    15161525}
  • trunk/src/org/openstreetmap/josm/actions/FullscreenToggleAction.java

    r7005 r7335  
    2121
    2222import org.openstreetmap.josm.Main;
    23 import org.openstreetmap.josm.tools.PlatformHookWindows;
    2423import org.openstreetmap.josm.tools.Shortcut;
    2524
     
    7776            }
    7877        }
    79        
     78
    8079        boolean selected = isSelected();
    8180
     
    9594        // screen by default (it's a simulated mode, but should be ok)
    9695        String exclusive = Main.pref.get("draw.fullscreen.exclusive-mode", "auto");
    97         if ("true".equals(exclusive) || ("auto".equals(exclusive) && !(Main.platform instanceof PlatformHookWindows))) {
     96        if ("true".equals(exclusive) || ("auto".equals(exclusive) && !Main.isPlatformWindows())) {
    9897            gd.setFullScreenWindow(selected ? frame : null);
    9998        }
  • trunk/src/org/openstreetmap/josm/actions/RestartAction.java

    r7005 r7335  
    1818import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
    1919import org.openstreetmap.josm.tools.ImageProvider;
    20 import org.openstreetmap.josm.tools.PlatformHookWindows;
    2120import org.openstreetmap.josm.tools.Shortcut;
    2221
     
    7675            // java binary
    7776            final String java = System.getProperty("java.home") + File.separator + "bin" + File.separator +
    78                     (Main.platform instanceof PlatformHookWindows ? "java.exe" : "java");
     77                    (Main.isPlatformWindows() ? "java.exe" : "java");
    7978            if (!new File(java).isFile()) {
    8079                throw new IOException("Unable to find suitable java runtime at "+java);
  • trunk/src/org/openstreetmap/josm/actions/ShowStatusReportAction.java

    r7318 r7335  
    3131import org.openstreetmap.josm.tools.OpenBrowser;
    3232import org.openstreetmap.josm.tools.PlatformHookUnixoid;
    33 import org.openstreetmap.josm.tools.PlatformHookWindows;
    3433import org.openstreetmap.josm.tools.Shortcut;
    3534import org.openstreetmap.josm.tools.Utils;
     
    105104        try {
    106105            final String envJavaHome = System.getenv("JAVA_HOME");
    107             final String envJavaHomeAlt = Main.platform instanceof PlatformHookWindows ? "%JAVA_HOME%" : "${JAVA_HOME}";
     106            final String envJavaHomeAlt = Main.isPlatformWindows() ? "%JAVA_HOME%" : "${JAVA_HOME}";
    108107            final String propJavaHome = System.getProperty("java.home");
    109108            final String propJavaHomeAlt = "<java.home>";
     
    160159        try {
    161160            Map<String, Setting<?>> settings = Main.pref.getAllSettings();
    162             settings.remove("osm-server.username");
    163             settings.remove("osm-server.password");
    164             settings.remove("oauth.access-token.key");
    165             settings.remove("oauth.access-token.secret");
    166161            Set<String> keys = new HashSet<>(settings.keySet());
    167162            for (String key : keys) {
    168                 if (key.startsWith("marker.show")) {
     163                // Remove sensitive information from status report
     164                if (key.startsWith("marker.show") || key.contains("username") || key.contains("password") || key.contains("access-token")) {
    169165                    settings.remove(key);
    170166                }
  • trunk/src/org/openstreetmap/josm/data/validation/OsmValidator.java

    r7315 r7335  
    145145
    146146    /**
    147      * Returns the plugin's directory of the plugin
     147     * Returns the validator directory.
    148148     *
    149      * @return The directory of the plugin
     149     * @return The validator directory
    150150     */
    151151    public static String getValidatorDir() {
  • trunk/src/org/openstreetmap/josm/gui/MainApplication.java

    r7187 r7335  
    1313import java.awt.event.WindowEvent;
    1414import java.io.File;
     15import java.io.IOException;
    1516import java.io.InputStream;
    1617import java.net.Authenticator;
     
    1920import java.security.AllPermission;
    2021import java.security.CodeSource;
     22import java.security.KeyStoreException;
     23import java.security.NoSuchAlgorithmException;
    2124import java.security.PermissionCollection;
    2225import java.security.Permissions;
    2326import java.security.Policy;
     27import java.security.cert.CertificateException;
    2428import java.util.ArrayList;
    2529import java.util.Collection;
     
    5963import org.openstreetmap.josm.tools.ImageProvider;
    6064import org.openstreetmap.josm.tools.OsmUrlToBounds;
     65import org.openstreetmap.josm.tools.PlatformHookWindows;
    6166import org.openstreetmap.josm.tools.Utils;
    6267
     
    323328            // Enable JOSM debug level
    324329            logLevel = 4;
    325             // Enable debug in OAuth signpost
    326             Preferences.updateSystemProperty("debug", "true");
    327330            Main.info(tr("Printing debugging messages to console"));
    328331        }
     
    331334            // Enable JOSM debug level
    332335            logLevel = 5;
     336            // Enable debug in OAuth signpost via system preference, but only at trace level
     337            Preferences.updateSystemProperty("debug", "true");
    333338            Main.info(tr("Enabled detailed debug level (trace)"));
    334339        }
     
    435440
    436441        SwingUtilities.invokeLater(new GuiFinalizationWorker(args, proxySelector));
     442
     443        if (Main.isPlatformWindows()) {
     444            try {
     445                // Check for insecure certificates to remove.
     446                // This is Windows-dependant code but it can't go to preStartupHook (need i18n) neither startupHook (need to be called before remote control)
     447                ((PlatformHookWindows)Main.platform).removeInsecureCertificates();
     448            } catch (NoSuchAlgorithmException | CertificateException | KeyStoreException | IOException e) {
     449                error(e);
     450            }
     451        }
    437452
    438453        if (RemoteControl.PROP_REMOTECONTROL_ENABLED.get()) {
  • trunk/src/org/openstreetmap/josm/gui/preferences/remotecontrol/RemoteControlPreference.java

    r7005 r7335  
    2828import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
    2929import org.openstreetmap.josm.io.remotecontrol.RemoteControl;
     30import org.openstreetmap.josm.io.remotecontrol.RemoteControlHttpsServer;
    3031import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler;
    3132import org.openstreetmap.josm.tools.GBC;
     
    5960    private final Map<PermissionPrefWithDefault, JCheckBox> prefs = new LinkedHashMap<>();
    6061    private JCheckBox enableRemoteControl;
     62    private JCheckBox enableHttpsSupport;
    6163    private JCheckBox loadInNewLayer = new JCheckBox(tr("Download objects to new layer"));
    6264    private JCheckBox alwaysAskUserConfirm = new JCheckBox(tr("Confirm all Remote Control actions manually"));
     
    8991        remote.add(wrapper, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 5, 5, 5));
    9092
    91         wrapper.add(new JLabel(tr("Permitted actions:")), GBC.eol());
     93        enableHttpsSupport = new JCheckBox(tr("Enable HTTPS support"), RemoteControl.PROP_REMOTECONTROL_HTTPS_ENABLED.get());
     94        wrapper.add(enableHttpsSupport, GBC.eol().fill(GBC.HORIZONTAL));
     95        wrapper.add(new JSeparator(), GBC.eop().fill(GBC.HORIZONTAL).insets(15, 5, 15, 5));
     96
     97        wrapper.add(new JLabel(tr("Permitted actions:")), GBC.eol().insets(5, 0, 0, 0));
    9298        for (JCheckBox p : prefs.values()) {
    9399            wrapper.add(p, GBC.eol().insets(15, 5, 0, 0).fill(GBC.HORIZONTAL));
     
    120126    public boolean ok() {
    121127        boolean enabled = enableRemoteControl.isSelected();
     128        boolean httpsEnabled = enableHttpsSupport.isSelected();
    122129        boolean changed = RemoteControl.PROP_REMOTECONTROL_ENABLED.put(enabled);
     130        boolean httpsChanged = RemoteControl.PROP_REMOTECONTROL_HTTPS_ENABLED.put(httpsEnabled);
    123131        if (enabled) {
    124132            for (Entry<PermissionPrefWithDefault, JCheckBox> p : prefs.entrySet()) {
     
    134142                RemoteControl.stop();
    135143            }
     144        } else if (httpsChanged) {
     145            if (httpsEnabled) {
     146                RemoteControlHttpsServer.restartRemoteControlHttpsServer();
     147            } else {
     148                RemoteControlHttpsServer.stopRemoteControlHttpsServer();
     149            }
    136150        }
    137151        return false;
  • trunk/src/org/openstreetmap/josm/io/remotecontrol/RemoteControl.java

    r6941 r7335  
    22package org.openstreetmap.josm.io.remotecontrol;
    33
     4import org.openstreetmap.josm.Main;
    45import org.openstreetmap.josm.data.preferences.BooleanProperty;
    56import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler;
     
    1112 * and increment the major version and set minor to 0 on incompatible changes.
    1213 */
    13 public class RemoteControl
    14 {
     14public class RemoteControl {
     15
    1516    /**
    1617     * If the remote control feature is enabled or disabled. If disabled,
     
    1819     */
    1920    public static final BooleanProperty PROP_REMOTECONTROL_ENABLED = new BooleanProperty("remotecontrol.enabled", false);
     21
     22    /**
     23     * If the remote control feature is enabled or disabled for HTTPS. If disabled,
     24     * only HTTP access will be available.
     25     * @since 7335
     26     */
     27    public static final BooleanProperty PROP_REMOTECONTROL_HTTPS_ENABLED = new BooleanProperty("remotecontrol.https.enabled", false);
    2028
    2129    /**
     
    5462        RequestProcessor.addRequestHandlerClass(command, handlerClass);
    5563    }
     64
     65    /**
     66     * Returns the remote control directory.
     67     * @return The remote control directory
     68     * @since 7335
     69     */
     70    public static String getRemoteControlDir() {
     71        return Main.pref.getPreferencesDir() + "remotecontrol/";
     72    }
    5673}
  • trunk/src/org/openstreetmap/josm/io/remotecontrol/RemoteControlHttpsServer.java

    r7206 r7335  
    77import java.io.IOException;
    88import java.io.InputStream;
     9import java.math.BigInteger;
    910import java.net.BindException;
    1011import java.net.InetAddress;
     
    1213import java.net.Socket;
    1314import java.net.SocketException;
    14 import java.security.Key;
    15 import java.security.KeyManagementException;
     15import java.nio.file.Files;
     16import java.nio.file.Path;
     17import java.nio.file.Paths;
     18import java.nio.file.StandardOpenOption;
     19import java.security.GeneralSecurityException;
     20import java.security.KeyPair;
     21import java.security.KeyPairGenerator;
    1622import java.security.KeyStore;
    17 import java.security.KeyStoreException;
    1823import java.security.NoSuchAlgorithmException;
    1924import java.security.PrivateKey;
    20 import java.security.UnrecoverableEntryException;
     25import java.security.SecureRandom;
    2126import java.security.cert.Certificate;
    22 import java.security.cert.CertificateException;
     27import java.security.cert.X509Certificate;
    2328import java.util.Arrays;
     29import java.util.Date;
    2430import java.util.Enumeration;
     31import java.util.Vector;
    2532
    2633import javax.net.ssl.KeyManagerFactory;
     
    3239
    3340import org.openstreetmap.josm.Main;
     41import org.openstreetmap.josm.data.preferences.StringProperty;
     42
     43import sun.security.util.ObjectIdentifier;
     44import sun.security.x509.AlgorithmId;
     45import sun.security.x509.BasicConstraintsExtension;
     46import sun.security.x509.CertificateAlgorithmId;
     47import sun.security.x509.CertificateExtensions;
     48import sun.security.x509.CertificateIssuerName;
     49import sun.security.x509.CertificateSerialNumber;
     50import sun.security.x509.CertificateSubjectName;
     51import sun.security.x509.CertificateValidity;
     52import sun.security.x509.CertificateVersion;
     53import sun.security.x509.CertificateX509Key;
     54import sun.security.x509.DNSName;
     55import sun.security.x509.ExtendedKeyUsageExtension;
     56import sun.security.x509.GeneralName;
     57import sun.security.x509.GeneralNameInterface;
     58import sun.security.x509.GeneralNames;
     59import sun.security.x509.IPAddressName;
     60import sun.security.x509.OIDName;
     61import sun.security.x509.SubjectAlternativeNameExtension;
     62import sun.security.x509.URIName;
     63import sun.security.x509.X500Name;
     64import sun.security.x509.X509CertImpl;
     65import sun.security.x509.X509CertInfo;
    3466
    3567/**
     
    4779    private SSLContext sslContext;
    4880
    49     private static final String KEYSTORE_PATH = "/data/josm.keystore";
    50     private static final String KEYSTORE_PASSWORD = "josm_ssl";
     81    private static final String KEYSTORE_FILENAME = "josm.keystore";
     82
     83    /**
     84     * Preference for keystore password (automatically generated by JOSM).
     85     * @since 7335
     86     */
     87    public StringProperty KEYSTORE_PASSWORD = new StringProperty("remotecontrol.https.keystore.password", "");
     88
     89    /**
     90     * Preference for certificate password (automatically generated by JOSM).
     91     * @since 7335
     92     */
     93    public StringProperty KEYENTRY_PASSWORD = new StringProperty("remotecontrol.https.keyentry.password", "");
     94
     95    /**
     96     * Creates a GeneralName object from known types.
     97     * @param t one of 4 known types
     98     * @param v value
     99     * @return which one
     100     * @throws IOException
     101     */
     102    private static GeneralName createGeneralName(String t, String v) throws IOException {
     103        GeneralNameInterface gn;
     104        switch (t.toLowerCase()) {
     105            case "uri": gn = new URIName(v); break;
     106            case "dns": gn = new DNSName(v); break;
     107            case "ip": gn = new IPAddressName(v); break;
     108            default: gn = new OIDName(v);
     109        }
     110        return new GeneralName(gn);
     111    }
     112
     113    /**
     114     * Create a self-signed X.509 Certificate.
     115     * @param dn the X.509 Distinguished Name, eg "CN=localhost, OU=JOSM, O=OpenStreetMap"
     116     * @param pair the KeyPair
     117     * @param days how many days from now the Certificate is valid for
     118     * @param algorithm the signing algorithm, eg "SHA256withRSA"
     119     * @param san SubjectAlternativeName extension (optional)
     120     */
     121    private static X509Certificate generateCertificate(String dn, KeyPair pair, int days, String algorithm, String san) throws GeneralSecurityException, IOException {
     122        PrivateKey privkey = pair.getPrivate();
     123        X509CertInfo info = new X509CertInfo();
     124        Date from = new Date();
     125        Date to = new Date(from.getTime() + days * 86400000l);
     126        CertificateValidity interval = new CertificateValidity(from, to);
     127        BigInteger sn = new BigInteger(64, new SecureRandom());
     128        X500Name owner = new X500Name(dn);
     129
     130        info.set(X509CertInfo.VALIDITY, interval);
     131        info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(sn));
     132        info.set(X509CertInfo.SUBJECT, new CertificateSubjectName(owner));
     133        info.set(X509CertInfo.ISSUER, new CertificateIssuerName(owner));
     134        info.set(X509CertInfo.KEY, new CertificateX509Key(pair.getPublic()));
     135        info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
     136        AlgorithmId algo = new AlgorithmId(AlgorithmId.md5WithRSAEncryption_oid);
     137        info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algo));
     138
     139        CertificateExtensions ext = new CertificateExtensions();
     140        // Critical: Not CA, max path len 0
     141        ext.set(BasicConstraintsExtension.NAME, new BasicConstraintsExtension(true, false, 0));
     142        // Critical: only allow TLS ("serverAuth" = 1.3.6.1.5.5.7.3.1)
     143        ext.set(ExtendedKeyUsageExtension.NAME, new ExtendedKeyUsageExtension(true,
     144                new Vector<ObjectIdentifier>(Arrays.asList(new ObjectIdentifier("1.3.6.1.5.5.7.3.1")))));
     145
     146        if (san != null) {
     147            int colonpos;
     148            String[] ps = san.split(",");
     149            GeneralNames gnames = new GeneralNames();
     150            for(String item: ps) {
     151                colonpos = item.indexOf(':');
     152                if (colonpos < 0) {
     153                    throw new IllegalArgumentException("Illegal item " + item + " in " + san);
     154                }
     155                String t = item.substring(0, colonpos);
     156                String v = item.substring(colonpos+1);
     157                gnames.add(createGeneralName(t, v));
     158            }
     159            // Non critical
     160            ext.set(SubjectAlternativeNameExtension.NAME, new SubjectAlternativeNameExtension(false, gnames));
     161        }
     162
     163        info.set(X509CertInfo.EXTENSIONS, ext);
     164
     165        // Sign the cert to identify the algorithm that's used.
     166        X509CertImpl cert = new X509CertImpl(info);
     167        cert.sign(privkey, algorithm);
     168
     169        // Update the algorithm, and resign.
     170        algo = (AlgorithmId)cert.get(X509CertImpl.SIG_ALG);
     171        info.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, algo);
     172        cert = new X509CertImpl(info);
     173        cert.sign(privkey, algorithm);
     174        return cert;
     175    }
    51176
    52177    private void initialize() {
    53178        if (!initOK) {
    54179            try {
    55                 // Create new keystore
    56                 KeyStore ks = KeyStore.getInstance("JKS");
    57                 char[] password = KEYSTORE_PASSWORD.toCharArray();
    58 
    59                 // Load keystore generated with Java 7 keytool as follows:
    60                 // keytool -genkeypair -storepass josm_ssl -keypass josm_ssl -alias josm_localhost -dname "CN=localhost, OU=JOSM, O=OpenStreetMap"
    61                 // -ext san=ip:127.0.0.1 -keyalg RSA -validity 1825
    62                 try (InputStream in = RemoteControlHttpsServer.class.getResourceAsStream(KEYSTORE_PATH)) {
    63                     if (in == null) {
    64                         Main.error(tr("Unable to find JOSM keystore at {0}. Remote control will not be available on HTTPS.", KEYSTORE_PATH));
    65                     } else {
    66                         ks.load(in, password);
    67 
    68                         if (Main.isDebugEnabled()) {
    69                             for (Enumeration<String> aliases = ks.aliases(); aliases.hasMoreElements();) {
    70                                 Main.debug("Alias in keystore: "+aliases.nextElement());
    71                             }
     180                char[] storePassword = KEYSTORE_PASSWORD.get().toCharArray();
     181                char[] entryPassword = KEYENTRY_PASSWORD.get().toCharArray();
     182
     183                Path dir = Paths.get(RemoteControl.getRemoteControlDir());
     184                Path path = dir.resolve(KEYSTORE_FILENAME);
     185                Files.createDirectories(dir);
     186
     187                if (!Files.exists(path)) {
     188                    Main.debug("No keystore found, creating a new one");
     189
     190                    // Create new keystore like previous one generated with JDK keytool as follows:
     191                    // keytool -genkeypair -storepass josm_ssl -keypass josm_ssl -alias josm_localhost -dname "CN=localhost, OU=JOSM, O=OpenStreetMap"
     192                    // -ext san=ip:127.0.0.1 -keyalg RSA -validity 1825
     193
     194                    KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
     195                    generator.initialize(2048);
     196                    KeyPair pair = generator.generateKeyPair();
     197
     198                    X509Certificate cert = generateCertificate("CN=localhost, OU=JOSM, O=OpenStreetMap", pair, 1825, "SHA256withRSA", "ip:127.0.0.1");
     199
     200                    KeyStore ks = KeyStore.getInstance("JKS");
     201                    ks.load(null, null);
     202
     203                    // Generate new passwords. See https://stackoverflow.com/a/41156/2257172
     204                    SecureRandom random = new SecureRandom();
     205                    KEYSTORE_PASSWORD.put(new BigInteger(130, random).toString(32));
     206                    KEYENTRY_PASSWORD.put(new BigInteger(130, random).toString(32));
     207
     208                    storePassword = KEYSTORE_PASSWORD.get().toCharArray();
     209                    entryPassword = KEYENTRY_PASSWORD.get().toCharArray();
     210
     211                    ks.setKeyEntry("josm_localhost", pair.getPrivate(), entryPassword, new Certificate[]{cert});
     212                    ks.store(Files.newOutputStream(path, StandardOpenOption.CREATE), storePassword);
     213                }
     214
     215                try (InputStream in = Files.newInputStream(path)) {
     216                    // Load keystore
     217                    KeyStore ks = KeyStore.getInstance("JKS");
     218                    ks.load(in, storePassword);
     219
     220                    if (Main.isDebugEnabled()) {
     221                        for (Enumeration<String> aliases = ks.aliases(); aliases.hasMoreElements();) {
     222                            Main.debug("Alias in keystore: "+aliases.nextElement());
    72223                        }
    73 
    74                         KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
    75                         kmf.init(ks, password);
    76 
    77                         TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
    78                         tmf.init(ks);
    79 
    80                         sslContext = SSLContext.getInstance("TLS");
    81                         sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
    82 
    83                         if (Main.isDebugEnabled()) {
    84                             Main.debug("SSL Context protocol: " + sslContext.getProtocol());
    85                             Main.debug("SSL Context provider: " + sslContext.getProvider());
    86                         }
    87 
    88                         Enumeration<String> aliases = ks.aliases();
    89                         if (aliases.hasMoreElements()) {
    90                             String aliasKey = aliases.nextElement();
    91                             Key key = ks.getKey(aliasKey, password);
    92                             Certificate[] chain = ks.getCertificateChain(aliasKey);
    93                             Main.platform.setupHttpsCertificate(new KeyStore.PrivateKeyEntry((PrivateKey) key, chain));
    94                         }
    95 
    96                         initOK = true;
    97224                    }
    98                 }
    99             } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException |
    100                     IOException | KeyManagementException | UnrecoverableEntryException e) {
     225
     226                    KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
     227                    kmf.init(ks, entryPassword);
     228
     229                    TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
     230                    tmf.init(ks);
     231
     232                    sslContext = SSLContext.getInstance("TLS");
     233                    sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
     234
     235                    if (Main.isTraceEnabled()) {
     236                        Main.trace("SSL Context protocol: " + sslContext.getProtocol());
     237                        Main.trace("SSL Context provider: " + sslContext.getProvider());
     238                    }
     239
     240                    Enumeration<String> aliases = ks.aliases();
     241                    if (aliases.hasMoreElements()) {
     242                        Main.platform.setupHttpsCertificate(new KeyStore.TrustedCertificateEntry(ks.getCertificate(aliases.nextElement())));
     243                    }
     244
     245                    initOK = true;
     246                }
     247            } catch (IOException | GeneralSecurityException e) {
    101248                Main.error(e);
    102249            }
     
    112259            stopRemoteControlHttpsServer();
    113260
    114             instance = new RemoteControlHttpsServer(port);
    115             if (instance.initOK) {
    116                 instance.start();
     261            if (RemoteControl.PROP_REMOTECONTROL_HTTPS_ENABLED.get()) {
     262                instance = new RemoteControlHttpsServer(port);
     263                if (instance.initOK) {
     264                    instance.start();
     265                }
    117266            }
    118267        } catch (BindException ex) {
     
    152301        initialize();
    153302
     303        if (!initOK) {
     304            Main.error(tr("Unable to initialize Remote Control HTTPS Server"));
     305            return;
     306        }
     307
    154308        // Create SSL Server factory
    155309        SSLServerSocketFactory factory = sslContext.getServerSocketFactory();
    156         if (Main.isDebugEnabled()) {
    157             Main.debug("SSL factory - Supported Cipher suites: "+Arrays.toString(factory.getSupportedCipherSuites()));
     310        if (Main.isTraceEnabled()) {
     311            Main.trace("SSL factory - Supported Cipher suites: "+Arrays.toString(factory.getSupportedCipherSuites()));
    158312        }
    159313
     
    165319            InetAddress.getByName(Main.pref.get("remote.control.host", "localhost")));
    166320
    167         if (Main.isDebugEnabled() && server instanceof SSLServerSocket) {
     321        if (Main.isTraceEnabled() && server instanceof SSLServerSocket) {
    168322            SSLServerSocket sslServer = (SSLServerSocket) server;
    169             Main.debug("SSL server - Enabled Cipher suites: "+Arrays.toString(sslServer.getEnabledCipherSuites()));
    170             Main.debug("SSL server - Enabled Protocols: "+Arrays.toString(sslServer.getEnabledProtocols()));
    171             Main.debug("SSL server - Enable Session Creation: "+sslServer.getEnableSessionCreation());
    172             Main.debug("SSL server - Need Client Auth: "+sslServer.getNeedClientAuth());
    173             Main.debug("SSL server - Want Client Auth: "+sslServer.getWantClientAuth());
    174             Main.debug("SSL server - Use Client Mode: "+sslServer.getUseClientMode());
     323            Main.trace("SSL server - Enabled Cipher suites: "+Arrays.toString(sslServer.getEnabledCipherSuites()));
     324            Main.trace("SSL server - Enabled Protocols: "+Arrays.toString(sslServer.getEnabledProtocols()));
     325            Main.trace("SSL server - Enable Session Creation: "+sslServer.getEnableSessionCreation());
     326            Main.trace("SSL server - Need Client Auth: "+sslServer.getNeedClientAuth());
     327            Main.trace("SSL server - Want Client Auth: "+sslServer.getWantClientAuth());
     328            Main.trace("SSL server - Use Client Mode: "+sslServer.getUseClientMode());
    175329        }
    176330    }
     
    187341                @SuppressWarnings("resource")
    188342                Socket request = server.accept();
    189                 if (Main.isDebugEnabled() && request instanceof SSLSocket) {
     343                if (Main.isTraceEnabled() && request instanceof SSLSocket) {
    190344                    SSLSocket sslSocket = (SSLSocket) request;
    191                     Main.debug("SSL socket - Enabled Cipher suites: "+Arrays.toString(sslSocket.getEnabledCipherSuites()));
    192                     Main.debug("SSL socket - Enabled Protocols: "+Arrays.toString(sslSocket.getEnabledProtocols()));
    193                     Main.debug("SSL socket - Enable Session Creation: "+sslSocket.getEnableSessionCreation());
    194                     Main.debug("SSL socket - Need Client Auth: "+sslSocket.getNeedClientAuth());
    195                     Main.debug("SSL socket - Want Client Auth: "+sslSocket.getWantClientAuth());
    196                     Main.debug("SSL socket - Use Client Mode: "+sslSocket.getUseClientMode());
    197                     Main.debug("SSL socket - Session: "+sslSocket.getSession());
     345                    Main.trace("SSL socket - Enabled Cipher suites: "+Arrays.toString(sslSocket.getEnabledCipherSuites()));
     346                    Main.trace("SSL socket - Enabled Protocols: "+Arrays.toString(sslSocket.getEnabledProtocols()));
     347                    Main.trace("SSL socket - Enable Session Creation: "+sslSocket.getEnableSessionCreation());
     348                    Main.trace("SSL socket - Need Client Auth: "+sslSocket.getNeedClientAuth());
     349                    Main.trace("SSL socket - Want Client Auth: "+sslSocket.getWantClientAuth());
     350                    Main.trace("SSL socket - Use Client Mode: "+sslSocket.getUseClientMode());
     351                    Main.trace("SSL socket - Session: "+sslSocket.getSession());
    198352                }
    199353                RequestProcessor.processRequest(request);
     
    214368     */
    215369    public void stopServer() throws IOException {
    216         server.close();
    217         Main.info(marktr("RemoteControl::Server (https) stopped."));
     370        if (server != null) {
     371            server.close();
     372            Main.info(marktr("RemoteControl::Server (https) stopped."));
     373        }
    218374    }
    219375}
  • trunk/src/org/openstreetmap/josm/tools/OpenBrowser.java

    r7029 r7335  
    4444        if (Desktop.isDesktopSupported()) {
    4545            try {
    46                 if (Main.platform instanceof PlatformHookWindows) {
     46                if (Main.isPlatformWindows()) {
    4747                    // Desktop API works fine under Windows, so we don't try any fallback in case of I/O exceptions because it's not API's fault
    4848                    Desktop.getDesktop().browse(uri);
  • trunk/src/org/openstreetmap/josm/tools/PlatformHook.java

    r7206 r7335  
    109109    /**
    110110     * Setup system keystore to add JOSM HTTPS certificate (for remote control).
    111      * @param privateKeyEntry the JOSM certificate for localhost and associated private key
     111     * @param trustedCert the JOSM certificate for localhost
    112112     * @throws KeyStoreException in case of error
    113113     * @throws IOException in case of error
     
    116116     * @since 7206
    117117     */
    118     public void setupHttpsCertificate(KeyStore.PrivateKeyEntry privateKeyEntry)
     118    public void setupHttpsCertificate(KeyStore.TrustedCertificateEntry trustedCert)
    119119            throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException;
    120120}
  • trunk/src/org/openstreetmap/josm/tools/PlatformHookUnixoid.java

    r7318 r7335  
    358358
    359359    @Override
    360     public void setupHttpsCertificate(KeyStore.PrivateKeyEntry privateKeyEntry)
     360    public void setupHttpsCertificate(KeyStore.TrustedCertificateEntry trustedCert)
    361361            throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
    362362        // TODO setup HTTPS certificate on Unix systems
  • trunk/src/org/openstreetmap/josm/tools/PlatformHookWindows.java

    r7206 r7335  
    3030import java.io.File;
    3131import java.io.IOException;
     32import java.security.InvalidKeyException;
     33import java.security.KeyFactory;
    3234import java.security.KeyStore;
    3335import java.security.KeyStoreException;
    3436import java.security.NoSuchAlgorithmException;
    35 import java.security.cert.Certificate;
     37import java.security.NoSuchProviderException;
     38import java.security.PublicKey;
     39import java.security.SignatureException;
    3640import java.security.cert.CertificateException;
     41import java.security.spec.InvalidKeySpecException;
     42import java.security.spec.X509EncodedKeySpec;
     43import java.util.ArrayList;
     44import java.util.Collection;
    3745import java.util.Enumeration;
     46
     47import javax.swing.JOptionPane;
    3848
    3949import org.openstreetmap.josm.Main;
     
    4454  */
    4555public class PlatformHookWindows extends PlatformHookUnixoid implements PlatformHook {
     56
     57    private static final byte[] INSECURE_PUBLIC_KEY = new byte[] {
     58        0x30, (byte) 0x82, 0x1, 0x22, 0x30, 0xd, 0x6, 0x9, 0x2a, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xf7, 0xd, 0x1, 0x1, 0x1, 0x5, 0x0, 0x3, (byte) 0x82, 0x1, 0xf, 0x0,
     59        0x30, (byte) 0x82, 0x01, 0x0a, 0x02, (byte) 0x82, 0x01, 0x01, 0x00, (byte) 0x95, (byte) 0x95, (byte) 0x88,
     60        (byte) 0x84, (byte) 0xc8, (byte) 0xd9, 0x6b, (byte) 0xc5, (byte) 0xda, 0x0b, 0x69, (byte) 0xbf, (byte) 0xfc, 0x7e, (byte) 0xb9, (byte) 0x96, 0x2c, (byte) 0xeb, (byte) 0x8f,
     61        (byte) 0xbc, 0x6e, 0x40, (byte) 0xe6, (byte) 0xe2, (byte) 0xfc, (byte) 0xf1, 0x7f, 0x73, (byte) 0xa7, (byte) 0x9d, (byte) 0xde, (byte) 0xc7, (byte) 0x88,
     62        0x57, 0x51, (byte) 0x84, (byte) 0xed, (byte) 0x96, (byte) 0xfb, (byte) 0xe1, 0x38, (byte) 0xef, 0x08, 0x2b, (byte) 0xf3, (byte) 0xc7, (byte) 0xc3,
     63        0x5d, (byte) 0xfe, (byte) 0xf9, 0x51, (byte) 0xe6, 0x29, (byte) 0xfc, (byte) 0xe5, 0x0d, (byte) 0xa1, 0x0d, (byte) 0xa8, (byte) 0xb4, (byte) 0xae,
     64        0x26, 0x18, 0x19, 0x4d, 0x6c, 0x0c, 0x3b, 0x12, (byte) 0xba, (byte) 0xbc, 0x5f, 0x32, (byte) 0xb3, (byte) 0xbe,
     65        (byte) 0x9d, 0x17, 0x0d, 0x4d, 0x2f, 0x1a, 0x48, (byte) 0xb7, (byte) 0xac, (byte) 0xf7, 0x1a, 0x43, 0x01, (byte) 0x97,
     66        (byte) 0xf4, (byte) 0xf8, 0x4c, (byte) 0xbb, 0x6a, (byte) 0xbc, 0x33, (byte) 0xe1, 0x73, 0x1e, (byte) 0x86, (byte) 0xfb, 0x2e, (byte) 0xb1,
     67        0x63, 0x75, (byte) 0x85, (byte) 0xdc, (byte) 0x82, 0x6c, 0x28, (byte) 0xf1, (byte) 0xe3, (byte) 0x90, 0x63, (byte) 0x9d, 0x3d, 0x48,
     68        (byte) 0x8a, (byte) 0x8c, 0x47, (byte) 0xe2, 0x10, 0x0b, (byte) 0xef, (byte) 0x91, (byte) 0x94, (byte) 0xb0, 0x6c, 0x4c, (byte) 0x80, 0x76,
     69        0x03, (byte) 0xe1, (byte) 0xb6, (byte) 0x90, (byte) 0x87, (byte) 0xd9, (byte) 0xae, (byte) 0xf4, (byte) 0x8e, (byte) 0xe0, (byte) 0x9f, (byte) 0xe7, 0x3a, 0x2c,
     70        0x2f, 0x21, (byte) 0xd4, 0x46, (byte) 0xba, (byte) 0x95, 0x70, (byte) 0xa9, 0x5b, 0x20, 0x2a, (byte) 0xfa, 0x52, 0x3e,
     71        (byte) 0x9d, (byte) 0xd9, (byte) 0xef, 0x28, (byte) 0xc5, (byte) 0xd1, 0x60, (byte) 0x89, 0x68, 0x6e, 0x7f, (byte) 0xd7, (byte) 0x9e, (byte) 0x89,
     72        0x4c, (byte) 0xeb, 0x4d, (byte) 0xd2, (byte) 0xc6, (byte) 0xf4, 0x2d, 0x02, 0x5d, (byte) 0xda, (byte) 0xde, 0x33, (byte) 0xfe, (byte) 0xc1,
     73        0x7e, (byte) 0xde, 0x4f, 0x1f, (byte) 0x9b, 0x6e, 0x6f, 0x0f, 0x66, 0x71, 0x19, (byte) 0xe9, 0x43, 0x3c,
     74        (byte) 0x83, 0x0a, 0x0f, 0x28, 0x21, (byte) 0xc8, 0x38, (byte) 0xd3, 0x4e, 0x48, (byte) 0xdf, (byte) 0xd4, (byte) 0x99, (byte) 0xb5,
     75        (byte) 0xc6, (byte) 0x8d, (byte) 0xd4, (byte) 0xc1, 0x69, 0x58, 0x79, (byte) 0x82, 0x32, (byte) 0x82, (byte) 0xd4, (byte) 0x86, (byte) 0xe2, 0x04,
     76        0x08, 0x63, (byte) 0x87, (byte) 0xf0, 0x2a, (byte) 0xf6, (byte) 0xec, 0x3e, 0x51, 0x0f, (byte) 0xda, (byte) 0xb4, 0x67, 0x19,
     77        0x5e, 0x16, 0x02, (byte) 0x9f, (byte) 0xf1, 0x19, 0x0c, 0x3e, (byte) 0xb8, 0x04, 0x49, 0x07, 0x53, 0x02,
     78        0x03, 0x01, 0x00, 0x01
     79    };
     80
     81    private static final String WINDOWS_ROOT = "Windows-ROOT";
    4682
    4783    @Override
     
    138174    }
    139175
    140     @Override
    141     public void setupHttpsCertificate(KeyStore.PrivateKeyEntry privateKeyEntry)
    142             throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
    143         KeyStore ks = KeyStore.getInstance("Windows-ROOT");
     176    /**
     177     * Loads Windows-ROOT keystore.
     178     * @return Windows-ROOT keystore
     179     * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found
     180     * @throws CertificateException if any of the certificates in the keystore could not be loaded
     181     * @throws IOException if there is an I/O or format problem with the keystore data, if a password is required but not given
     182     * @throws KeyStoreException if no Provider supports a KeyStore implementation for the type "Windows-ROOT"
     183     */
     184    private KeyStore getWindowsKeystore() throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException {
     185        KeyStore ks = KeyStore.getInstance(WINDOWS_ROOT);
    144186        ks.load(null, null);
     187        return ks;
     188    }
     189
     190    /**
     191     * Removes potential insecure certificates installed with previous versions of JOSM on Windows.
     192     * @throws NoSuchAlgorithmException on unsupported signature algorithms
     193     * @throws CertificateException if any of the certificates in the Windows keystore could not be loaded
     194     * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the type "Windows-ROOT"
     195     * @throws IOException if there is an I/O or format problem with the keystore data, if a password is required but not given
     196     * @since 7335
     197     */
     198    public void removeInsecureCertificates() throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException {
     199        // 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)
     200        PublicKey insecurePubKey = null;
     201        try {
     202            insecurePubKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(INSECURE_PUBLIC_KEY));
     203        } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
     204            Main.error(e);
     205            return;
     206        }
     207        KeyStore ks = getWindowsKeystore();
    145208        Enumeration<String> en = ks.aliases();
     209        Collection<String> insecureCertificates = new ArrayList<>();
    146210        while (en.hasMoreElements()) {
    147211            String alias = en.nextElement();
    148             Certificate c = ks.getCertificate(alias);
    149             if (ks.isKeyEntry(alias) && c.equals(privateKeyEntry.getCertificate())) {
     212            // Look for certificates associated with a private key
     213            if (ks.isKeyEntry(alias)) {
     214                try {
     215                    ks.getCertificate(alias).verify(insecurePubKey);
     216                    // If no exception, this is a certificate signed with the insecure key -> remove it
     217                    insecureCertificates.add(alias);
     218                } catch (InvalidKeyException | NoSuchProviderException | SignatureException e) {
     219                    // If exception this is not a certificate related to JOSM, just trace it
     220                    Main.trace(alias + " --> " + e.getClass().getName());
     221                }
     222            }
     223        }
     224        // Remove insecure certificates
     225        if (!insecureCertificates.isEmpty()) {
     226            StringBuilder message = new StringBuilder("<html>");
     227            message.append(tr("A previous version of JOSM has installed a custom certificate in order to provide HTTPS support for Remote Control:"));
     228            message.append("<br><ul>");
     229            for (String alias : insecureCertificates) {
     230                message.append("<li>");
     231                message.append(alias);
     232                message.append("</li>");
     233            }
     234            message.append("</ul>");
     235            message.append(tr("It appears it could be an important <b>security risk</b>.<br><br>"+
     236                    "You are now going to be prompted by Windows to remove this insecure certificate.<br>For your own safety, <b>please click Yes</b> in next dialog."));
     237            message.append("</html>");
     238            JOptionPane.showMessageDialog(Main.parent, message.toString(), tr("Warning"), JOptionPane.WARNING_MESSAGE);
     239            for (String alias : insecureCertificates) {
     240                Main.warn(tr("Removing insecure certificate from {0} keystore: {1}", WINDOWS_ROOT, alias));
     241                try {
     242                    ks.deleteEntry(alias);
     243                } catch (KeyStoreException e) {
     244                    Main.error(tr("Unable to remove insecure certificate from keystore: {0}", e.getMessage()));
     245                }
     246            }
     247        }
     248    }
     249
     250    @Override
     251    public void setupHttpsCertificate(KeyStore.TrustedCertificateEntry trustedCert)
     252            throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
     253        KeyStore ks = getWindowsKeystore();
     254        Enumeration<String> en = ks.aliases();
     255
     256        while (en.hasMoreElements()) {
     257            String alias = en.nextElement();
     258            // Look for certificate to install
     259            if (ks.isKeyEntry(alias) && ks.getCertificate(alias).equals(trustedCert.getTrustedCertificate())) {
    150260                // JOSM certificate found, return
    151261                return;
     
    153263        }
    154264        // JOSM certificate not found, install it
    155         Main.info("Adding JOSM localhost certificate to Windows-ROOT keystore");
    156         ks.setEntry("josm_localhost", privateKeyEntry, new KeyStore.PasswordProtection("josm_ssl".toCharArray()));
     265        Main.info(tr("Adding JOSM localhost certificate to {0} keystore", WINDOWS_ROOT));
     266        ks.setEntry("josm_localhost", trustedCert, null);
    157267    }
    158268}
Note: See TracChangeset for help on using the changeset viewer.