https certificate issue with subdomain - ssl

I have my application running on EC2 behind the load balancer.
Have got https certificate for www.example.com and *.example.com.
Application is running on http but https is been setup in load balancer.
I have added sub-domain support in my application based on the company.
Like, https://XYZ.example.com for company XYZ.
If i access using, https://XYZ.example.com, its working fine.
If I access using, https://www.XYZ.example.com, browser warns like,
"The owner of www.arun.contactcentral.io has configured their website improperly. To protect your information from being stolen, Firefox has not connected to this website."
But, If i access https://www.example.com, it works fine.
Though, I have got certification for *.example.com, it doesnt work even i access www.XYZ.example.com.
I have a filter to handle http to https direction, but still it is not filtering WWW from the url.
public class HttpsFilter implements Filter {
private static final String HTTP = "http";
private static final String HTTPS = "https";
private static final String X_FORWARDED_PROTO = "X-Forwarded-Proto";
#Override
public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse httpResponse = (HttpServletResponse) res;
String xfp = request.getHeader(X_FORWARDED_PROTO);
if (HTTPS.equals(xfp)) {
//httpResponse.setHeader("Strict-Transport-Security", "max-age=60");
chain.doFilter(req, res);
return;
}
else if (HTTP.equals(xfp)) {
String serverUrl = HTTPS+"://"+req.getServerName()+((HttpServletRequest)req).getServletPath();
httpResponse.sendRedirect(serverUrl);
return;
}
}
}
Thanks,
Baskar.S

Wildcard SSL certificates will match only ONE level of subdomains (except in very rare and not well supported cases). The wildcard asterisk will not match . (dot).
So, a certificate for *.example.com WILL match
www.example.com
xyz.example.com
some-really-long-name.example.com
but it will NOT match
example.com
www.xyz.example.com
abc.def.ghi.example.com
If you want to match www.xyz.example.com and xyz.example.com, you will need two different certificates.
https://en.wikipedia.org/wiki/Wildcard_certificate#Limitation

Related

Host name check in Custom Trust Manager

We have a java client that allows both secure and non-secure connections to LDAP hosts.
It comes as part of a software suite which
has its own server component.
We are good with non-secure connections but need to switch to secure only.
The trusted public certificates are maintained (root+intermediate+host are copy pasted into one PEM file) in a
central location with the server component external to the clients.
The custom trust manager downloads the externally held trusted certificates on demand
and builds the trusted certificate chain. This way, I guess, it avoids pre-saving the trusted certicate chain in each client.
Our LDAP hosts are load balanced and that setup has not gone well with the trust manager. When we investigated, we found two questionable lines
in the code.
An environment variable to by-pass the host name verification.
if ("T".equals(System.getenv("IGNORE_HOSTNAME_CHECK"))) return true;
It seems like doing something similar to below which I have seen elsewhere.
HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
Host name check relies on CN value of subject alone.
if (this.tgtHostname.equalsIgnoreCase(leafCn)) return true;
I have skimmed through some RFCs related to TLS and have come across SNI, SAN:DNSName and MITM warnings
but my rudimentary knowledge is not enough to make a case one way or the other.
Any advice on improvements (or against the use of it altogether) around commented out lines labelled H1 and H2 will be greatly valued.
I intend to pass them on to the right entity later.
The cut-down version of checkServerTrusted() of the custom trust manager is pasted below.
public void checkServerTrusted(X509Certificate[] certsRcvdFromTgt, String authType) throws CertificateException
{
// Some stuff
// Verify that the last certificate in the chain corresponds to the tgt server we want to access.
checkLastCertificate(certsRcvdFromTgt[certsRcvdFromTgt.length - 1]);
// Some more stuff
}
private boolean checkLastCertificate(X509Certificate leafCert) throws CertificateException
{
// need some advice here ... (H1)
if ("T".equals(System.getenv("IGNORE_HOSTNAME_CHECK"))) return true;
try
{
String leafCn = null;
X500Principal subject = leafCert.getSubjectX500Principal();
String dn = subject.getName();
LdapName ldapDN = new LdapName(dn);
for (Rdn rdn : ldapDN.getRdns())
{
if (rdn.getType().equalsIgnoreCase("cn"))
{
leafCn = rdn.getValue().toString();
break;
}
}
// need some advice here ... (H2)
if (this.tgtHostname.equalsIgnoreCase(leafCn)) return true;
}
catch (InvalidNameException e){/*error handling*/}
throw new CertificateException("Failed to verify that the last certificate in the chain is for target " + this.tgtHostname);
}

Mutual Authentication with Reactive Netty on restricted urls

I am using spring cloud gateway to front a legacy application so that we can start migrating things behind the scenes. Some of the urls that are hosted by the application are public facing and some are device restricted. We control the devices and they use a browser client to access the restricted urls. We have mutual authentication setup for the device restricted urls on the server using tomcat and security constraints like this in web.xml:
<security-constraint>
<web-resource-collection>
<web-resource-name>Certificate Content</web-resource-name>
<!-- URL for authentication endpoint - this is locked down with the role assigned by tomcat -->
<url-pattern>/rest/secure/url1</url-pattern>
<url-pattern>/rest/secure/url2</url-pattern>
<url-pattern>/rest/secure/url3</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>certificate</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<!-- All other endpoints- force the switch from http to https with transport-guarantee -->
<security-constraint>
<web-resource-collection>
<web-resource-name>Protected Context</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<login-config>
<auth-method>CLIENT-CERT</auth-method>
</login-config>
<security-role>
<role-name>certificate</role-name>
</security-role>
That is coupled with a truststore setup in tomcat's server.xml (I can add it, but I don't think that is relevant to this conversation).
My goal is to implement a similar setup in spring cloud gateway which is using reactive netty under-the-hood and remove the web.xml restrictions from the legacy application. I think I could switch it to using tomcat and probably get the web.xml from above to work, but I'd rather stick to the performance benefits of using reactive netty.
Key Goals:
Only deploy one api gateway for the app. The number of urls that
require mutual auth is very small so I'd rather not include a whole
other container to manage just to support them.
Do not ask for a client cert on the public urls.
Require valid client certs for the restricted urls.
I've setup mutual authentication and can get it to work with need/want/none as expected (truststores setup, etc), but it applies to ALL urls. I've also setup X509 security restrictions and that all seems to work.
I think what I want to setup is tsl renegotiation using the SslHandler after the http request is decrypted (so that I can access the url) based on the path. But I'm having trouble with the details and I've failed at finding any examples that incorporate spring-boot applications using reactive netty to do a tsl renegotiation. Any tips on how to perform a renegotiation of the ssl connection with needClientAuth set to true would be appreciated. I think I need to invalidate the session or something because when I try to do it manually it appears that it is skipping negotiation because the connection is already marked as negotiated in the ssl engine.
This is one of the iterations I've tried (this doesn't restrict on urls, but I plan to add that after I get this working):
#Component
public class NettyWebServerFactoryGatewayCustomizer implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
private static final Logger LOG = LoggerFactory.getLogger(NettyWebServerFactoryGatewayCustomizer.class);
#Override
public void customize(NettyReactiveWebServerFactory serverFactory) {
serverFactory.addServerCustomizers(httpServer -> {
httpServer = httpServer.wiretap(true);
return httpServer.tcpConfiguration(tcpServer -> {
tcpServer = tcpServer.doOnConnection(connection ->
connection.addHandler("request client cert",
new SimpleChannelInboundHandler<HttpRequest>() {
#Override
protected void channelRead0(ChannelHandlerContext ctx, HttpRequest httpRequest) {
LOG.error("HttpRequest: {}", httpRequest);
final ChannelPipeline pipeline = ctx.pipeline();
final SslHandler sslHandler = pipeline.get(SslHandler.class);
final SSLEngine sslEngine = sslHandler.engine();
sslEngine.setNeedClientAuth(true);
sslHandler.renegotiate()
.addListener(future -> ctx.fireChannelRead(httpRequest));
}
}
)
);
return tcpServer;
});
});
}
}
I see it performing the renegotiation in the debugger, but it still seems to be set to client auth none (as set in the application.properties) instead of need as set in the code before renegotiation. I've tried sslEngine.getSession().invalidate(); but that didn't help. I've also tried generating a new ssl handler from the ssl provider but that seemed to really screw things up.
Thank you for any help provided.
Edit: Doing more research it appears that this approach is not appropriate going forward since ssl renegotiation is being dropped entirely in tsl 1.3 (see https://security.stackexchange.com/a/230327). Is there a way to perform the equivalent of SSL verify client post handshake as described here: https://www.openssl.org/docs/manmaster/man3/SSL_verify_client_post_handshake.html ?
Edit2: Looks like this was an issue where TLS1.3 post handshake was not supported by the browser I was testing with. Setting the server to just accept TLS 1.2 seemed to work. Not sure if there is a better way to solve this but this is what I added to my application.properties:
server.ssl.enabled-protocols=TLSv1.2
Here is what I used to get it to work. I'm going to leave out the spring security side of it since that is separate from requesting the certificate from the client.
There are so many ways to configure the child pipeline that is used to process the request. Please let me know if there is a more accepted way to configure it.
Configure the HttpServer by adding to the bootstrap pipeline that is applied when a connection is established with the client:
#Component
public class NettyWebServerFactoryGatewayCustomizer implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
private static final HttpRenegotiateClientCertHandler HTTP_RENEGOTIATE_CLIENT_CERT_HANDLER =
new HttpRenegotiateClientCertHandler(SecurityConfig.X509_PROTECTED_ENDPOINTS);
#Override
public void customize(NettyReactiveWebServerFactory serverFactory) {
serverFactory.addServerCustomizers(NettyWebServerFactoryGatewayCustomizer::addRenegotiateHandlerToHttpServer);
}
private static HttpServer addRenegotiateHandlerToHttpServer(HttpServer httpServer) {
return httpServer.tcpConfiguration(NettyWebServerFactoryGatewayCustomizer::addRenegotiateHandlerToTcpServer);
}
private static TcpServer addRenegotiateHandlerToTcpServer(TcpServer server) {
return server.doOnBind(NettyWebServerFactoryGatewayCustomizer::addRenegotiateHandlerToServerBootstrap);
}
private static void addRenegotiateHandlerToServerBootstrap(ServerBootstrap serverBootstrap) {
BootstrapHandlers.updateConfiguration(
serverBootstrap,
HttpRenegotiateClientCertHandler.NAME,
NettyWebServerFactoryGatewayCustomizer::addRenegotiateHandlerToChannel
);
}
private static void addRenegotiateHandlerToChannel(ConnectionObserver connectionObserver, Channel channel) {
final ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(HttpRenegotiateClientCertHandler.NAME, HTTP_RENEGOTIATE_CLIENT_CERT_HANDLER);
}
}
Child Handler that performs the renegotiation:
#ChannelHandler.Sharable
public class HttpRenegotiateClientCertHandler extends SimpleChannelInboundHandler<HttpRequest> {
public static final String NAME = NettyPipeline.LEFT + "clientRenegotiate";
private static final PathPatternParser DEFAULT_PATTERN_PARSER = new PathPatternParser();
private final Collection<PathPattern> pathPatterns;
public HttpRenegotiateClientCertHandler(String ... antPatterns) {
Assert.notNull(antPatterns, "patterns cannot be null");
Assert.notEmpty(antPatterns, "patterns cannot be empty");
Assert.noNullElements(antPatterns, "patterns cannot have null items");
pathPatterns = Arrays.stream(antPatterns)
.map(DEFAULT_PATTERN_PARSER::parse)
.collect(Collectors.toSet());
}
#Override
protected void channelRead0(ChannelHandlerContext ctx, HttpRequest request) {
if (shouldNotRenegotiate(request)) {
ctx.fireChannelRead(request);
return;
}
final ChannelPipeline pipeline = ctx.pipeline();
final SslHandler sslHandler = pipeline.get(SslHandler.class);
final SSLEngine sslEngine = sslHandler.engine();
sslEngine.setNeedClientAuth(true);
sslHandler.renegotiate()
.addListener(renegotiateFuture -> ctx.fireChannelRead(request));
}
/**
* Determine if the request uri matches the configured uris for this handler.
* #param request to match the path from.
* #return true if any of the path patterns are matched.
*/
private boolean shouldNotRenegotiate(HttpRequest request) {
final String requestUri = request.uri();
final PathContainer path = PathContainer.parsePath(requestUri);
return pathPatterns.stream()
.noneMatch(matcher -> matcher.matches(path));
}
}
And these configurations in application.properties:
# Setup Client Auth Truststore:
server.ssl.trust-store=<path to truststore>
server.ssl.trust-store-password=<truststore password>
server.ssl.trust-store-type=<truststore type>
# Set to none by default so we do not ask for client auth until needed.
server.ssl.client-auth=none
# This is specifically not including TLSv1.3 because there are issues
# with older browsers' implementation of TLSv1.3 that prevent verify
# client post handshake client from working.
server.ssl.enabled-protocols=TLSv1.2
Edit: Updated because handler gateway route code wasn't being invoked properly.

Access remote IP address in resource server proxied through Zuul and Apache

For a security check I need access to the user's remote IP address in my resource service. This resource service is a simple recent Spring Boot app, that registers itself with my Eureka server:
#SpringBootApplication
#EnableEurekaClient
public class ServletInitializer extends SpringBootServletInitializer {
public static void main(final String[] args) {
SpringApplication.run(ServletInitializer.class, args);
}
}
All services registered with my Eureka server are dynamically routed through my Zuul routing proxy server based on Angel.SR3 starter-zuul and starter-eureka:
#SpringBootApplication
#EnableZuulProxy
#EnableEurekaClient
public class RoutingProxyServer {
public static void main(final String[] args) {
SpringApplication.run(RoutingProxyServer.class, args);
}
}
The Zuul routing proxy server also configures an AJP connector for the next step:
#Configuration
#ConditionalOnProperty("ajp.port")
public class TomcatAjpConfig extends TomcatWebSocketContainerCustomizer {
#Value("${ajp.port}")
private int port;
#Override
public void doCustomize(final TomcatEmbeddedServletContainerFactory tomcat) {
super.doCustomize(tomcat);
// Listen for AJP requests
Connector ajp = new Connector("AJP/1.3");
ajp.setPort(port);
tomcat.addAdditionalTomcatConnectors(ajp);
}
}
All requests to the dynamic routing zuul proxy are proxied themselves through Apache to provide HTTPS on the standard 443 port:
# Preserve Host when proxying so jar apps return working URLs in JSON responses
RequestHeader set X-Forwarded-Proto "https"
ProxyPreserveHost On
# Redirect remaining traffic to routing proxy server
ProxyPass / ajp://192.168.x.x:8009/
# Also update Location, Content-Location and URI headers on HTTP redirect responses
ProxyPassReverse / ajp://192.168.x.x:8009/
With all this in place the resource service is made available, but unfortunately the remoteAddress that I get from Spring Security is the address of the Zuul proxy/Apache server, not the remote client IP address.
In the past I had used a org.springframework.security.authentication.AuthenticationDetailsSource that preferred the X-Forwarded-For header value over the normal remoteAddress to get the correct IP address, but I can not work out how to pass the proper remote IP address to my resource service when passing through two proxies (Apache + Zuul).
Can anyone help me access the correct remote IP address behind these two proxies, or suggest an alternative approach to get this to work?
Turns out the X-Forwarded-For header was taken from the original request feeding into Zuul to populate HttpServletRequest#getRemoteAddr(). This would then have to be passed on to the proxied backend services through RequestContext#getZuulRequestHeaders().put("X-Forwarded-For", remoteAddr). The following ZuulFilter accomplishes this, even if it isn't appending it's own value to the X-Forwarded-For filter just yet.
#Component
#Slf4j
public class XForwardedForFilter extends ZuulFilter {
private static final String X_FORWARDED_FOR = "X-Forwarded-For";
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
// Rely on HttpServletRequest to retrieve the correct remote address from upstream X-Forwarded-For header
HttpServletRequest request = ctx.getRequest();
String remoteAddr = request.getRemoteAddr();
// Pass remote address downstream by setting X-Forwarded for header again on Zuul request
log.debug("Settings X-Forwarded-For to: {}", remoteAddr);
ctx.getZuulRequestHeaders().put(X_FORWARDED_FOR, remoteAddr);
return null;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 10000;
}
}
One might want to clear the header value in Apache before proxying to Zuul to prevent accepting just any value provided by the user: RequestHeader unset X-Forwarded-For
Normally, you can use servletRequest.getRemoteAddr() to get the client’s IP address that’s accessing your Java web application.
String ipAddress = request.getRemoteAddr();
But, if user is behind a proxy server or access your web server through a load balancer (for example, in cloud hosting), the above code will get the IP address of the proxy server or load balancer server, not the original IP address of a client.
To solve it, you should get the IP address of the request’s HTTP header “X-Forwarded-For (XFF)“.
String ipAddress = request.getHeader("X-FORWARDED-FOR");
if (ipAddress == null) {
ipAddress = request.getRemoteAddr();
}

Test For Localhost On ASP.NET Web API ActionFilterAttribute

How can I test for localhost on an ActionFilterAttribute with ASP.NET Web API? I want to skip the SSL check.
public class RequireHttpsAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var request = actionContext.Request;
if (request.RequestUri.Scheme != Uri.UriSchemeHttps)
{
throw new ValidationException(new SecureConnection());
}
base.OnActionExecuting(actionContext);
}
}
If you just want to test whether the request URI is localhost, then IsLoopback on the URI should work fine.
But here's the thing... request URIs can easily be spoofed by computers that aren't the local computer. So any remote computer can actually send you a request with a localhost Host header.
A better way is to use Filip's suggestion in his blog post:
http://www.strathweb.com/2013/01/adding-request-islocal-to-asp-net-web-api/
That should work for both selfhost and webhost and whether the IP address of the client is a loopback.
Looks like this works. Let me know if I am incorrect.
request.RequestUri.IsLoopback

Getting IP Address of the remote peer in Websocket API for Java EE 7

How can I get the IP address of the remote peer in Websocket API for Java Glassfish ?
Another method, based on this answer to another question, is to get the headers of the HandshakeRequest. The headers you are looking for are either of the following.
origin: [IP Address]
x-forwarded-for: [Possibly a separate IP]
Just for clarity, here's my setup, and how I discovered this:
Wamp 2.5 on MyMachine:6060. This hosts a client HTML page.
Wamp 2.5 on LabMachine:6060 (normal connections) and LabMachine:6443 (secure connections). This acts as a proxy.
GlassFish 4.0 on MyMachine:8080 (normal) and MyMachine:8181 (SSL). This is the endpoint.
I connected to the client page via my own machine, the lab machine, and a colleague's machine. In every case, the origin header of the WebSocket request was
http://MyMachine:6060
However, in each case the x-forwarded-host header was different, matching the IP addresses of the actual client.
See getRemoteAddr()
You will need to cast your socket Session instance to a TyrusSession, which implements the standard JSR-356 Session interface.
You might need to upgrade Tyrus, as I am not sure if the version you have supports this method. I am using Tyrus 1.7 and it works fine for me. Here is how to upgrade.
WebSockets are based on HTTP requests. Therefore you are probably extending an HttpServlet or a WebSocketServlet somewhere, so the usual way of getting the IP from the HttpServletRequest should work:
Example:
public class WebSocketsServlet extends HttpServlet {
private final WebSocketApplication app = new WebSocketApplication();
#Override
public void init(ServletConfig config) throws ServletException {
WebSocketEngine.getEngine().register(
config.getServletContext().getContextPath() + "/context", app);
}
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("IP: " + req.getRemoteAddr());
super.doGet(req, resp);
}
}