Changeset 7335 in josm for trunk/src/org/openstreetmap/josm/io
- Timestamp:
- 2014-07-26T03:50:31+02:00 (10 years ago)
- Location:
- trunk/src/org/openstreetmap/josm/io/remotecontrol
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/io/remotecontrol/RemoteControl.java
r6941 r7335 2 2 package org.openstreetmap.josm.io.remotecontrol; 3 3 4 import org.openstreetmap.josm.Main; 4 5 import org.openstreetmap.josm.data.preferences.BooleanProperty; 5 6 import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler; … … 11 12 * and increment the major version and set minor to 0 on incompatible changes. 12 13 */ 13 public class RemoteControl 14 { 14 public class RemoteControl { 15 15 16 /** 16 17 * If the remote control feature is enabled or disabled. If disabled, … … 18 19 */ 19 20 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); 20 28 21 29 /** … … 54 62 RequestProcessor.addRequestHandlerClass(command, handlerClass); 55 63 } 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 } 56 73 } -
trunk/src/org/openstreetmap/josm/io/remotecontrol/RemoteControlHttpsServer.java
r7206 r7335 7 7 import java.io.IOException; 8 8 import java.io.InputStream; 9 import java.math.BigInteger; 9 10 import java.net.BindException; 10 11 import java.net.InetAddress; … … 12 13 import java.net.Socket; 13 14 import java.net.SocketException; 14 import java.security.Key; 15 import java.security.KeyManagementException; 15 import java.nio.file.Files; 16 import java.nio.file.Path; 17 import java.nio.file.Paths; 18 import java.nio.file.StandardOpenOption; 19 import java.security.GeneralSecurityException; 20 import java.security.KeyPair; 21 import java.security.KeyPairGenerator; 16 22 import java.security.KeyStore; 17 import java.security.KeyStoreException;18 23 import java.security.NoSuchAlgorithmException; 19 24 import java.security.PrivateKey; 20 import java.security. UnrecoverableEntryException;25 import java.security.SecureRandom; 21 26 import java.security.cert.Certificate; 22 import java.security.cert. CertificateException;27 import java.security.cert.X509Certificate; 23 28 import java.util.Arrays; 29 import java.util.Date; 24 30 import java.util.Enumeration; 31 import java.util.Vector; 25 32 26 33 import javax.net.ssl.KeyManagerFactory; … … 32 39 33 40 import org.openstreetmap.josm.Main; 41 import org.openstreetmap.josm.data.preferences.StringProperty; 42 43 import sun.security.util.ObjectIdentifier; 44 import sun.security.x509.AlgorithmId; 45 import sun.security.x509.BasicConstraintsExtension; 46 import sun.security.x509.CertificateAlgorithmId; 47 import sun.security.x509.CertificateExtensions; 48 import sun.security.x509.CertificateIssuerName; 49 import sun.security.x509.CertificateSerialNumber; 50 import sun.security.x509.CertificateSubjectName; 51 import sun.security.x509.CertificateValidity; 52 import sun.security.x509.CertificateVersion; 53 import sun.security.x509.CertificateX509Key; 54 import sun.security.x509.DNSName; 55 import sun.security.x509.ExtendedKeyUsageExtension; 56 import sun.security.x509.GeneralName; 57 import sun.security.x509.GeneralNameInterface; 58 import sun.security.x509.GeneralNames; 59 import sun.security.x509.IPAddressName; 60 import sun.security.x509.OIDName; 61 import sun.security.x509.SubjectAlternativeNameExtension; 62 import sun.security.x509.URIName; 63 import sun.security.x509.X500Name; 64 import sun.security.x509.X509CertImpl; 65 import sun.security.x509.X509CertInfo; 34 66 35 67 /** … … 47 79 private SSLContext sslContext; 48 80 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 } 51 176 52 177 private void initialize() { 53 178 if (!initOK) { 54 179 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()); 72 223 } 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;97 224 } 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) { 101 248 Main.error(e); 102 249 } … … 112 259 stopRemoteControlHttpsServer(); 113 260 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 } 117 266 } 118 267 } catch (BindException ex) { … … 152 301 initialize(); 153 302 303 if (!initOK) { 304 Main.error(tr("Unable to initialize Remote Control HTTPS Server")); 305 return; 306 } 307 154 308 // Create SSL Server factory 155 309 SSLServerSocketFactory factory = sslContext.getServerSocketFactory(); 156 if (Main.is DebugEnabled()) {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())); 158 312 } 159 313 … … 165 319 InetAddress.getByName(Main.pref.get("remote.control.host", "localhost"))); 166 320 167 if (Main.is DebugEnabled() && server instanceof SSLServerSocket) {321 if (Main.isTraceEnabled() && server instanceof SSLServerSocket) { 168 322 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()); 175 329 } 176 330 } … … 187 341 @SuppressWarnings("resource") 188 342 Socket request = server.accept(); 189 if (Main.is DebugEnabled() && request instanceof SSLSocket) {343 if (Main.isTraceEnabled() && request instanceof SSLSocket) { 190 344 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()); 198 352 } 199 353 RequestProcessor.processRequest(request); … … 214 368 */ 215 369 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 } 218 374 } 219 375 }
Note:
See TracChangeset
for help on using the changeset viewer.