Skip to content

Commit

Permalink
[MRESOLVER-328] SSL insecure mode (#255)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
cstamas authored Mar 2, 2023
1 parent b66fe9e commit 5e8d513
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,12 @@
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;
Expand Down Expand Up @@ -154,19 +158,42 @@ public static HttpClientConnectionManager newConnectionManager(SslConfig sslConf
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,17 @@ 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)
: null;

cipherSuites = split(get(session, CIPHER_SUITES));
protocols = split(get(session, PROTOCOLS));
this.httpsSecurityMode = httpsSecurityMode;
}

private static String get(RepositorySystemSession session, String key) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,27 @@ public String getHttpsUrl() {
}

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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,37 @@ public void testGet_SSL() throws Exception {
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);
Expand Down
Binary file not shown.
1 change: 1 addition & 0 deletions src/site/markdown/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 5e8d513

Please sign in to comment.