This is an automated email from the ASF dual-hosted git repository. cstamas pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/maven-resolver.git
The following commit(s) were added to refs/heads/master by this push: new 5e8d5137 [MRESOLVER-328] SSL insecure mode (#255) 5e8d5137 is described below commit 5e8d51371d7e0dd4188278fd85875cb5f68da8e1 Author: Tamas Cservenak <ta...@cservenak.net> AuthorDate: Thu Mar 2 10:14:32 2023 +0100 [MRESOLVER-328] SSL insecure mode (#255) The transport-http now has "insecure" HTTPS mode that simply ignores any kind of SSL validation error (trust, certificate dates, hostnames). This mode is NOT MEANT for production, as it is inherently insecure but may come handy in small shops using self signed certificates. As mode value is string, we can later improve by adding flags, like ignore-hostname-validation, ignore-cert-dates etc. --- https://issues.apache.org/jira/browse/MRESOLVER-328 --- .../eclipse/aether/ConfigurationProperties.java | 24 ++++++++++++ .../eclipse/aether/transport/http/GlobalState.java | 41 +++++++++++++++++---- .../aether/transport/http/HttpTransporter.java | 7 +++- .../eclipse/aether/transport/http/SslConfig.java | 5 ++- .../eclipse/aether/transport/http/HttpServer.java | 24 +++++++++--- .../aether/transport/http/HttpTransporterTest.java | 31 ++++++++++++++++ .../src/test/resources/ssl/server-store-selfsigned | Bin 0 -> 2750 bytes src/site/markdown/configuration.md | 1 + 8 files changed, 119 insertions(+), 14 deletions(-) diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/ConfigurationProperties.java b/maven-resolver-api/src/main/java/org/eclipse/aether/ConfigurationProperties.java index 5c9095ba..0cfa9f4e 100644 --- a/maven-resolver-api/src/main/java/org/eclipse/aether/ConfigurationProperties.java +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/ConfigurationProperties.java @@ -159,6 +159,30 @@ public final class ConfigurationProperties { */ public static final boolean DEFAULT_HTTP_PREEMPTIVE_AUTH = false; + /** + * The mode that sets HTTPS transport "security mode": to ignore any SSL errors (certificate validity checks, + * hostname verification). The default value is {@link #HTTPS_SECURITY_MODE_DEFAULT}. + * + * @see #HTTPS_SECURITY_MODE_DEFAULT + * @see #HTTPS_SECURITY_MODE_INSECURE + * @since 1.9.6 + */ + public static final String HTTPS_SECURITY_MODE = PREFIX_CONNECTOR + "https.securityMode"; + + /** + * The default HTTPS security mode. + * + * @since 1.9.6 + */ + public static final String HTTPS_SECURITY_MODE_DEFAULT = "default"; + + /** + * The insecure HTTPS security mode (certificate validation, hostname verification are all ignored). + * + * @since 1.9.6 + */ + public static final String HTTPS_SECURITY_MODE_INSECURE = "insecure"; + /** * A flag indicating whether checksums which are retrieved during checksum validation should be persisted in the * local filesystem next to the file they provide the checksum for. diff --git a/maven-resolver-transport-http/src/main/java/org/eclipse/aether/transport/http/GlobalState.java b/maven-resolver-transport-http/src/main/java/org/eclipse/aether/transport/http/GlobalState.java index 4d925aa6..1f6d7789 100644 --- a/maven-resolver-transport-http/src/main/java/org/eclipse/aether/transport/http/GlobalState.java +++ b/maven-resolver-transport-http/src/main/java/org/eclipse/aether/transport/http/GlobalState.java @@ -33,8 +33,12 @@ import org.apache.http.config.RegistryBuilder; import org.apache.http.conn.HttpClientConnectionManager; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.ssl.SSLContextBuilder; +import org.apache.http.ssl.SSLInitializationException; +import org.eclipse.aether.ConfigurationProperties; import org.eclipse.aether.RepositoryCache; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.util.ConfigUtils; @@ -154,19 +158,42 @@ final class GlobalState implements Closeable { if (sslConfig == null) { registryBuilder.register("https", SSLConnectionSocketFactory.getSystemSocketFactory()); } else { - SSLSocketFactory sslSocketFactory = (sslConfig.context != null) - ? sslConfig.context.getSocketFactory() - : (SSLSocketFactory) SSLSocketFactory.getDefault(); - - HostnameVerifier hostnameVerifier = (sslConfig.verifier != null) - ? sslConfig.verifier - : SSLConnectionSocketFactory.getDefaultHostnameVerifier(); + // config present: use provided, if any, or create (depending on httpsSecurityMode) + SSLSocketFactory sslSocketFactory = sslConfig.context != null ? sslConfig.context.getSocketFactory() : null; + HostnameVerifier hostnameVerifier = sslConfig.verifier; + if (ConfigurationProperties.HTTPS_SECURITY_MODE_DEFAULT.equals(sslConfig.httpsSecurityMode)) { + if (sslSocketFactory == null) { + sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); + } + if (hostnameVerifier == null) { + hostnameVerifier = SSLConnectionSocketFactory.getDefaultHostnameVerifier(); + } + } else if (ConfigurationProperties.HTTPS_SECURITY_MODE_INSECURE.equals(sslConfig.httpsSecurityMode)) { + if (sslSocketFactory == null) { + try { + sslSocketFactory = new SSLContextBuilder() + .loadTrustMaterial(null, (chain, auth) -> true) + .build() + .getSocketFactory(); + } catch (Exception e) { + throw new SSLInitializationException( + "Could not configure '" + sslConfig.httpsSecurityMode + "' HTTPS security mode", e); + } + } + if (hostnameVerifier == null) { + hostnameVerifier = NoopHostnameVerifier.INSTANCE; + } + } else { + throw new IllegalArgumentException( + "Unsupported '" + sslConfig.httpsSecurityMode + "' HTTPS security mode."); + } registryBuilder.register( "https", new SSLConnectionSocketFactory( sslSocketFactory, sslConfig.protocols, sslConfig.cipherSuites, hostnameVerifier)); } + PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager(registryBuilder.build()); connMgr.setMaxTotal(100); connMgr.setDefaultMaxPerRoute(50); diff --git a/maven-resolver-transport-http/src/main/java/org/eclipse/aether/transport/http/HttpTransporter.java b/maven-resolver-transport-http/src/main/java/org/eclipse/aether/transport/http/HttpTransporter.java index 59bbfc07..3e370d18 100644 --- a/maven-resolver-transport-http/src/main/java/org/eclipse/aether/transport/http/HttpTransporter.java +++ b/maven-resolver-transport-http/src/main/java/org/eclipse/aether/transport/http/HttpTransporter.java @@ -146,7 +146,12 @@ final class HttpTransporter extends AbstractTransporter { this.repoAuthContext = AuthenticationContext.forRepository(session, repository); this.proxyAuthContext = AuthenticationContext.forProxy(session, repository); - this.state = new LocalState(session, repository, new SslConfig(session, repoAuthContext)); + String httpsSecurityMode = ConfigUtils.getString( + session, + ConfigurationProperties.HTTPS_SECURITY_MODE_DEFAULT, + ConfigurationProperties.HTTPS_SECURITY_MODE + "." + repository.getId(), + ConfigurationProperties.HTTPS_SECURITY_MODE); + this.state = new LocalState(session, repository, new SslConfig(session, repoAuthContext, httpsSecurityMode)); this.headers = ConfigUtils.getMap( session, diff --git a/maven-resolver-transport-http/src/main/java/org/eclipse/aether/transport/http/SslConfig.java b/maven-resolver-transport-http/src/main/java/org/eclipse/aether/transport/http/SslConfig.java index b08e393c..33f04617 100644 --- a/maven-resolver-transport-http/src/main/java/org/eclipse/aether/transport/http/SslConfig.java +++ b/maven-resolver-transport-http/src/main/java/org/eclipse/aether/transport/http/SslConfig.java @@ -45,7 +45,9 @@ final class SslConfig { final String[] protocols; - SslConfig(RepositorySystemSession session, AuthenticationContext authContext) { + final String httpsSecurityMode; + + SslConfig(RepositorySystemSession session, AuthenticationContext authContext, String httpsSecurityMode) { context = (authContext != null) ? authContext.get(AuthenticationContext.SSL_CONTEXT, SSLContext.class) : null; verifier = (authContext != null) ? authContext.get(AuthenticationContext.SSL_HOSTNAME_VERIFIER, HostnameVerifier.class) @@ -53,6 +55,7 @@ final class SslConfig { cipherSuites = split(get(session, CIPHER_SUITES)); protocols = split(get(session, PROTOCOLS)); + this.httpsSecurityMode = httpsSecurityMode; } private static String get(RepositorySystemSession session, String key) { diff --git a/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/HttpServer.java b/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/HttpServer.java index 3834096f..0c46e31f 100644 --- a/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/HttpServer.java +++ b/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/HttpServer.java @@ -136,13 +136,27 @@ public class HttpServer { } public HttpServer addSslConnector() { + return addSslConnector(true); + } + + public HttpServer addSelfSignedSslConnector() { + return addSslConnector(false); + } + + private HttpServer addSslConnector(boolean needClientAuth) { if (httpsConnector == null) { SslContextFactory.Server ssl = new SslContextFactory.Server(); - ssl.setNeedClientAuth(true); - ssl.setKeyStorePath(new File("src/test/resources/ssl/server-store").getAbsolutePath()); - ssl.setKeyStorePassword("server-pwd"); - ssl.setTrustStorePath(new File("src/test/resources/ssl/client-store").getAbsolutePath()); - ssl.setTrustStorePassword("client-pwd"); + if (needClientAuth) { + ssl.setNeedClientAuth(true); + ssl.setKeyStorePath(new File("src/test/resources/ssl/server-store").getAbsolutePath()); + ssl.setKeyStorePassword("server-pwd"); + ssl.setTrustStorePath(new File("src/test/resources/ssl/client-store").getAbsolutePath()); + ssl.setTrustStorePassword("client-pwd"); + } else { + ssl.setNeedClientAuth(false); + ssl.setKeyStorePath(new File("src/test/resources/ssl/server-store-selfsigned").getAbsolutePath()); + ssl.setKeyStorePassword("server-pwd"); + } httpsConnector = new ServerConnector(server, ssl); server.addConnector(httpsConnector); try { diff --git a/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/HttpTransporterTest.java b/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/HttpTransporterTest.java index db00dc66..752640a7 100644 --- a/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/HttpTransporterTest.java +++ b/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/HttpTransporterTest.java @@ -395,6 +395,37 @@ public class HttpTransporterTest { assertEquals(task.getDataString(), new String(listener.baos.toByteArray(), StandardCharsets.UTF_8)); } + @Test + public void testGet_HTTPS_Unknown_SecurityMode() throws Exception { + session.setConfigProperty("aether.connector.https.securityMode", "unknown"); + httpServer.addSelfSignedSslConnector(); + try { + newTransporter(httpServer.getHttpsUrl()); + fail("Unsupported security mode"); + } catch (IllegalArgumentException a) { + // good + } + } + + @Test + public void testGet_HTTPS_Insecure_SecurityMode() throws Exception { + // here we use alternate server-store-selfigned key (as the key set it static initalizer is probably already + // used to init SSLContext/SSLSocketFactory/etc + session.setConfigProperty( + "aether.connector.https.securityMode", ConfigurationProperties.HTTPS_SECURITY_MODE_INSECURE); + httpServer.addSelfSignedSslConnector(); + newTransporter(httpServer.getHttpsUrl()); + RecordingTransportListener listener = new RecordingTransportListener(); + GetTask task = new GetTask(URI.create("repo/file.txt")).setListener(listener); + transporter.get(task); + assertEquals("test", task.getDataString()); + assertEquals(0L, listener.dataOffset); + assertEquals(4L, listener.dataLength); + assertEquals(1, listener.startedCount); + assertTrue("Count: " + listener.progressedCount, listener.progressedCount > 0); + assertEquals(task.getDataString(), new String(listener.baos.toByteArray(), StandardCharsets.UTF_8)); + } + @Test public void testGet_WebDav() throws Exception { httpServer.setWebDav(true); diff --git a/maven-resolver-transport-http/src/test/resources/ssl/server-store-selfsigned b/maven-resolver-transport-http/src/test/resources/ssl/server-store-selfsigned new file mode 100644 index 00000000..caf6e52b Binary files /dev/null and b/maven-resolver-transport-http/src/test/resources/ssl/server-store-selfsigned differ diff --git a/src/site/markdown/configuration.md b/src/site/markdown/configuration.md index 649cc3c9..615b7121 100644 --- a/src/site/markdown/configuration.md +++ b/src/site/markdown/configuration.md @@ -40,6 +40,7 @@ Option | Type | Description | Default Value | Supports Repo ID Suffix `aether.connector.http.preemptiveAuth` | boolean | Should HTTP client use preemptive-authentication (works only w/ BASIC) or not. | `false` | yes `aether.connector.http.retryHandler.count` | int | The maximum number of times a request to a remote HTTP server should be retried in case of an error. | `3` | yes `aether.connector.https.cipherSuites` | String | Comma-separated list of [Cipher Suites](https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#ciphersuites) which are enabled for HTTPS connections. | - (no restriction) | no +`aether.connector.https.securityMode` | String | Using this flag resolver may set the "security mode" of HTTPS connector. Any other mode than 'default' is NOT MEANT for production, as it is inherently not secure. Accepted values: "default", "insecure" (ignore any kind of certificate validation errors and hostname validation checks). | `"default"` | yes `aether.connector.https.protocols` | String | Comma-separated list of [Protocols](https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#jssenames) which are enabled for HTTPS connections. | - (no restriction) | no `aether.connector.perms.fileMode` | String | [Octal numerical notation of permissions](https://en.wikipedia.org/wiki/File_system_permissions#Numeric_notation) to set for newly created files. Only considered by certain Wagon providers. | - | no `aether.connector.perms.dirMode` | String | [Octal numerical notation of permissions](https://en.wikipedia.org/wiki/File_system_permissions#Numeric_notation) to set for newly created directories. Only considered by certain Wagon providers. | - | no