Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2201.5.x] Add support for configuring server name to be used in the SSL SNI extension #2235

Merged
merged 20 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright (c) 2024 WSO2 Inc. (http://www.wso2.org).
//
// WSO2 Inc. licenses this file to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file except
// in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

import ballerina/http;
import ballerina/test;
import ballerina/http_test_common as common;

listener http:Listener http2SniListener = new (9207, http2SslServiceConf);

http:ListenerConfiguration http1SniServiceConf = {
httpVersion: http:HTTP_1_1,
secureSocket: {
key: {
path: common:KEYSTORE_PATH,
password: "ballerina"
}
}
};

listener http:Listener http1SniListener = new (9208, http1SniServiceConf);

service /http2SniService on http2SniListener {
resource function get .() returns string {
return "Sni works with HTTP_2!";
}
}

service /http1SniService on http1SniListener {
resource function get .() returns string {
return "Sni works with HTTP_1.1!";
}
}

http:ClientConfiguration http2SniClientConf = {
secureSocket: {
cert: {
path: common:TRUSTSTORE_PATH,
password: "ballerina"
},
serverName: "localhost"
}
};

http:ClientConfiguration http1SniClientConf = {
httpVersion: http:HTTP_1_1,
secureSocket: {
cert: {
path: common:TRUSTSTORE_PATH,
password: "ballerina"
},
serverName: "localhost"
}
};

@test:Config {enable: true}
public function testHttp2Sni() returns error? {
http:Client clientEP = check new ("https://127.0.0.1:9207", http2SniClientConf);
string resp = check clientEP->get("/http2SniService/");
common:assertTextPayload(resp, "Sni works with HTTP_2!");
}

@test:Config {enable: true}
public function testHttp1Sni() returns error? {
http:Client clientEP = check new ("https://127.0.0.1:9208", http1SniClientConf);
string resp = check clientEP->get("/http1SniService/");
common:assertTextPayload(resp, "Sni works with HTTP_1.1!");
}
2 changes: 2 additions & 0 deletions ballerina/http_client_config.bal
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ public type RetryConfig record {|
# + shareSession - Enable/disable new SSL session creation
# + handshakeTimeout - SSL handshake time out
# + sessionTimeout - SSL session time out
# + serverName - Server name indication(SNI) to be used. If this is not present, hostname from the target URL will be used
public type ClientSecureSocket record {|
boolean enable = true;
crypto:TrustStore|string cert?;
Expand All @@ -112,6 +113,7 @@ public type ClientSecureSocket record {|
boolean shareSession = true;
decimal handshakeTimeout?;
decimal sessionTimeout?;
string serverName?;
|};

# Provides configurations for controlling the endpoint's behaviour in response to HTTP redirect related responses.
Expand Down
5 changes: 4 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
# Change Log
This file contains all the notable changes done to the Ballerina HTTP package through the releases.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to
Bhashinee marked this conversation as resolved.
Show resolved Hide resolved
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
- [Add support for configuring server name to be used in the SSL SNI extension](https://github.com/ballerina-platform/ballerina-library/issues/7435)

Bhashinee marked this conversation as resolved.
Show resolved Hide resolved
## [2.7.8] - 2024-10-16

### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ public class HttpConstants {
public static final BString SECURESOCKET_CONFIG_VERIFY_CLIENT = StringUtils.fromString("verifyClient");
public static final BString SECURESOCKET_CONFIG_CERT_VALIDATION_TYPE_OCSP_STAPLING =
StringUtils.fromString("OCSP_STAPLING");
public static final BString SECURESOCKET_CONFIG_SNI_HOST_NAME = StringUtils.fromString("serverName");

//Socket Config
public static final BString SOCKET_CONFIG = StringUtils.fromString("socketConfig");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1840,6 +1840,12 @@ private static void evaluateCommonFields(BMap<BString, Object> secureSocket, Ssl
Parameter enableSessionCreationParam = new Parameter(HttpConstants.SECURESOCKET_CONFIG_SHARE_SESSION.getValue(),
enableSessionCreation);
paramList.add(enableSessionCreationParam);
String sniHostName = "null".equals(String.valueOf(secureSocket.getStringValue(
TharmiganK marked this conversation as resolved.
Show resolved Hide resolved
HttpConstants.SECURESOCKET_CONFIG_SNI_HOST_NAME))) ? null
: String.valueOf(secureSocket.getStringValue(HttpConstants.SECURESOCKET_CONFIG_SNI_HOST_NAME));
Parameter sniHostNameParam = new Parameter(HttpConstants.SECURESOCKET_CONFIG_SNI_HOST_NAME.getValue(),
sniHostName);
paramList.add(sniHostNameParam);
}

private static BMap<BString, Object> getBMapValueIfPresent(BMap<BString, Object> map, BString key) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public final class Constants {
public static final String CLIENT_SUPPORT_CIPHERS = "ciphers";
public static final String CLIENT_SUPPORT_SSL_PROTOCOLS = "sslEnabledProtocols";
public static final String CLIENT_ENABLE_SESSION_CREATION = "shareSession";
public static final String SNI_SERVER_NAME = "serverName";
public static final String MUTUAL_SSL_PASSED = "passed";
public static final String MUTUAL_SSL_FAILED = "failed";
public static final String MUTUAL_SSL_DISABLED = "disabled";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import static io.ballerina.stdlib.http.transport.contract.Constants.SERVER_SUPPORTED_SNIMATCHERS;
import static io.ballerina.stdlib.http.transport.contract.Constants.SERVER_SUPPORT_CIPHERS;
import static io.ballerina.stdlib.http.transport.contract.Constants.SERVER_SUPPORT_SSL_PROTOCOLS;
import static io.ballerina.stdlib.http.transport.contract.Constants.SNI_SERVER_NAME;
import static io.ballerina.stdlib.http.transport.contract.Constants.TLS_PROTOCOL;
/**
* SSL configuration for HTTP connection.
Expand Down Expand Up @@ -277,6 +278,9 @@ private SSLConfig getSSLConfigForSender() {
case CLIENT_ENABLE_SESSION_CREATION:
sslConfig.setEnableSessionCreation(Boolean.parseBoolean(parameter.getValue()));
break;
case SNI_SERVER_NAME:
sslConfig.setSniHostName(parameter.getValue());
break;
default:
//do nothing
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ private static SSLEngine getSslEngineForCerts(SocketChannel socketChannel, Strin
SslHandler sslHandler = sslContext.newHandler(socketChannel.alloc(), host, port);
SSLEngine sslEngine = sslHandler.engine();
sslHandlerFactory.addCommonConfigs(sslEngine);
sslHandlerFactory.setSNIServerNames(sslEngine, host);
configureSniServerName(sslConfig, host, sslHandlerFactory, sslEngine);
if (sslConfig.isHostNameVerificationEnabled()) {
setHostNameVerfication(sslEngine);
}
Expand Down Expand Up @@ -506,14 +506,20 @@ private static SSLEngine instantiateAndConfigSSL(SSLConfig sslConfig, String hos
if (sslConfig != null) {
sslEngine = sslHandlerFactory.buildClientSSLEngine(host, port);
sslEngine.setUseClientMode(true);
sslHandlerFactory.setSNIServerNames(sslEngine, host);
configureSniServerName(sslConfig, host, sslHandlerFactory, sslEngine);
if (hostNameVerificationEnabled) {
sslHandlerFactory.setHostNameVerfication(sslEngine);
}
}
return sslEngine;
}

private static void configureSniServerName(SSLConfig sslConfig, String host, SSLHandlerFactory sslHandlerFactory,
SSLEngine sslEngine) {
sslHandlerFactory.setSNIServerNames(sslEngine,
sslConfig.getSniHostName() != null ? sslConfig.getSniHostName() : host);
}

/**
* Get integer type property value from a property map.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public class SSLConfig {
private long handshakeTimeOut;
private boolean disableSsl = false;
private boolean useJavaDefaults = false;
private String sniHostName;

public SSLConfig() {}

Expand Down Expand Up @@ -179,6 +180,14 @@ public void setEnableSessionCreation(boolean enableSessionCreation) {
this.enableSessionCreation = enableSessionCreation;
}

public void setSniHostName(String sniHostName) {
this.sniHostName = sniHostName;
}

public String getSniHostName() {
return sniHostName;
}

public String[] getEnableProtocols() {
return enableProtocols == null ? null : enableProtocols.clone();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,8 @@ private void configureSslForHttp2(SocketChannel ch, ChannelPipeline clientPipeli
SslContext sslCtx = sslHandlerFactory.createHttp2TLSContextForClient(false);
SslHandler sslHandler = sslCtx.newHandler(ch.alloc(), httpRoute.getHost(), httpRoute.getPort());
SSLEngine sslEngine = sslHandler.engine();
sslHandlerFactory.setSNIServerNames(sslEngine, httpRoute.getHost());
sslHandlerFactory.setSNIServerNames(sslEngine,
sslConfig.getSniHostName() != null ? sslConfig.getSniHostName() : httpRoute.getHost());
if (sslConfig.isHostNameVerificationEnabled()) {
setHostNameVerfication(sslEngine);
}
Expand Down
Loading