Configuring Spring Security for X509 client authentication, it isn't prompting browser for certificate - authentication

I'm trying to enable an application to use Client-Auth security with spring. Here's my configuration:
<security:http pattern="/api/**" >
<security:x509 subject-principal-regex="CN=(.*?),"
user-service-ref="x509UserService" />
<security:intercept-url pattern="/api/**" access="IS_AUTHENTICATED_FULLY" requires-channel="https" />
</security:http>
If I don't configure anything within web.xml or tomcat's server.xml, the browser is never prompted to send along a certificate with the request. Consequently, it always return null in in org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter.extractClientCertificate(HttpServletRequest) method.
Is there something special that needs to be configured to have Spring participate in the SSL flow and request the client certificate?
Any help would be appreciated.
If I specify clientAuth='want' in the server.xml connector configuration, this has the undesirable side-effect of prompting everyone for certificates regardless of if they're accessing the /api path.
Similarly, if I specify the login-config and auth-method to be CLIENT-CERT, it also challenges the user, but then I would basically have to duplicate all of the cert checking I'd be doing in spring (or so it would seem).

Depending on your current java and build variety, at the very minimum you would either need to include something similar to the
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>
org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/api/*</url-pattern>
</filter-mapping>
or if you're using Java configuration with spring...
public class SecurityWebApplicationInitializer
extends AbstractSecurityWebApplicationInitializer {
}

Related

Tomcat 9.x.x Client Authentication using X.509 Certificates

I’m using Tomcat 9.0.19 and trying to enable X.509 cert.-based client authentication (AKA I&A) for a particular Web application.
In summary, the Tomcat works for an application that has basic I&A enabled over one-way TLS. When accessing the Web application that has certificate-based I&A, Tomcat does not seem to request a client certificate as part of the Server Hello message, prior to sending Server Hello Done and it later fails the authentication check:
02-Jan-2020 13:00:40.371 FINE [https-jsse-nio-443-exec-10] org.apache.catalina.authenticator.SSLAuthenticator.doAuthenticate Looking up certificates
02-Jan-2020 13:00:40.830 FINE [https-jsse-nio-443-exec-10] org.apache.catalina.authenticator.SSLAuthenticator.doAuthenticate No certificates included with this request
Traced the TLS flow in Wireshark and saw the TLS 1.2 handshake. Shortly after encrypted data is exchanged, the Tomcat sends an “Encrypted Alert” message and the socket is closed. Trying to contact the Tomcat from the browser, doing a GET. The browser does not prompt me to select a certificate, which also seems to point to Tomcat not requesting it from the browser.
Any help will be greatly appreciated!
More Details:
We have a set of certificates for the Tomcat and the client, issued by an Intermediate CA, which is signed (issued) by a Root CA. The trust stores have been setup on both sides (client and server) as well as key stores with the right certs/keys in them. The Web application is setup to require certificate I&A (web.xml):
<security-constraint>
<web-resource-collection>
<web-resource-name>All by default</web-resource-name>
<url-pattern>/*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>OTService</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<login-config>
<auth-method>CLIENT-CERT</auth-method>
<realm-name>certificate</realm-name>
</login-config>
The OTService role is setup in the Tomcat-Users.xml, along with a single user account:
Now, the Connector in server.xml is configured as follows:
<Connector port="443" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="100" SSLEnabled="true" scheme="https" secure="true">
<SSLHostConfig>
<Certificate certificateKeystoreFile="/apache-tomcat-9.0.19/conf/km/keyStore.jks"
certificateKeystorePassword="PASSWORD"
certificateKeyAlias="tomcat"
type="RSA" />
truststoreFile="/apache-tomcat-9.0.19/conf/km/trust_store.jks"
truststorePass="PASSWORD"
truststoreType="JKS"
certificateVerification="required"
clientAuth="true"
protocols="TLSv1.2"
</SSLHostConfig>
</Connector>
Any ideas why Tomcat would not request a client certificate?
The first issue that I discovered was that Tomcat ignored the Connector->SSLHostConfig settings for the trust store and used the JRE default trust store anyway. The way I discovered it was to have a browser save the negotiated TLS session key to a file (Google SSLKEYLOGFILE), then configured the Wireshark to use that file, captured the browser-Tomcat session and then was able to see every message in plaintext.
Next, I discovered that Tomcat was actually asking for a client cert., but the list of accepted Root CAs it was sending was from the default JRE cacerts file, not from the file specified by the truststoreFile attribute. Can have Tomcat use a different file across the board by adding a setenv.sh file to the Tomcat bin directory with Java properties to override default trust store location.
Now, I was in business, the browser was able to complete the TLS handshake, but then the authentication and authorization steps were failing. I finally determinate that the proper way to provide the cert. subject field in tomcat_users.xml file was not "CN=OU Client, OU=Control Systems, O=IoTOY, L=Scottsdale, S=AZ, C=US", but "CN=OU Client, OU=Control Systems, O=IoTOY, L=Scottsdale, ST=AZ, C=US". Finally, I had 2-way TLS working.
One thing to keep in mind is if anything running on the Tomcat attempts to connect over TLS to another system that uses commercial CA certs, it will fail because the truststore you're using now does not have commercial Root CAs' certs. One way to remediate it is to make a copy of the default JRE cacerts file and add your system-specific CA cert(s) to it and point to it from the setenv.sh file noted above.
When you have:
<Connector ...>
<SSLHostConfig>
<Certificate A=1 B=2 C=3 />
D=4 E=5 F=6
</SSLHostConfig>
</Connector>
then A,B,C are attributes of the Certificate object but D,E,F are NOT attributes of the SSLHostConfig object -- they are XML content which is different. Attributes need to be put IN THE TAG:
<Connector ... >
<SSLHostConfig certificateVerification="required" truststoreFile=... >
<Certificate ...keystore... />
</SSLHostConfig>
</Connector>
and that does cert-request on the initial handshake as desired (for me, tested on tomcat 9.0.14).

In Jetty, how to support certificate authentication only on a subset of API

I'm building a web service and are using Jetty as the server. For some of the API-s this service provides, we want them to be authenticated by certificate. So I have following code:
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setWantClientAuth(true);
Server server = new Server(pool);
ServerConnector sslConnector = new ServerConnector(server,
new SslConnectionFactory(sslContextFactory, "HTTP/1.1"),
new HttpConnectionFactory(httpsConfig));
server.addConnector(sslConnector);
Now, my service also has a corresponding web UI. When users access the web UI which in turn calls backend API-s, the browser prompts the user for a cert. I don't want this to happen because the API called by the web UI do not support certificate authentication. However, the above code is configuring in a global way. Is there any way to resolve this ?
Update:
I've looked at other server implementations.
For example, in ASP.NET, we can define following config:
<location path="some-api">
<system.webServer>
<security>
<access sslFlags="SslNegotiateCert"/>
</security>
</system.webServer>
</location>
There is also similar settings in Apache Http Server
So it seems SSL/TLS itself isn't prohibiting me from doing so. Are there any Jetty settings that I have missed ?
The TLS level certificate validation occurs before the HTTP Request is even sent/processed/parsed.
It's not possible to skip that validation based on information after the TLS handshake.
You could, as an alternate method, just put the certificate validation on a different port on the same machine (with a different ServerConnector configuration), leaving the normal connector without client certificate validation.

unable to set security domain via jboss-web.xml

We have an app that is being deployed as an ear. Within this ear, there is a war that needs to use a specific security domain.
To achieve this, we have configured the standalone-full-ha.xml with the following security section
<security-domain name="ourDomain" cache-type="default">
<authentication>
<login-module code="blah.blah.OurDomain" flag="required" />
</authentication>
</security-domain>
ear/war/WEB-INF/jboss-web.xml is configured as follows:
<?xml version="1.0" encoding="UTF-8"?>
<jboss-web>
<security-domain>Quark</security-domain>
<disable-audit>true</disable-audit>
</jboss-web>
With this configuration, the app tries to authenticate against the "other" domain which is there in JBoss by default.
log entries as follows:
TRACE [org.jboss.security] (http-/127.0.0.1:8080-6) PBOX000224: End getAppConfigurationEntry(other), AuthInfo: AppConfigurationEntry[]:
[0]
LoginModule Class: org.jboss.as.security.remoting.RemotingLoginModule
ControlFlag: LoginModuleControlFlag: optional
Options:
name=password-stacking, value=useFirstPass
[1]
LoginModule Class: org.jboss.as.security.RealmDirectLoginModule
ControlFlag: LoginModuleControlFlag: required
Options:
name=password-stacking, value=useFirstPass
When trying to define this as part of the ear in ear/META-INF/jboss-app.xml, that made the whole thing blow up pretty spectacularly - so figured that was less likely to be the way to resolve this.
If the default security domain is changed to ourDomain however, it all works as expected.
This doesn't seem to be a big deal - however, it feels better to be able to leave as much of the configuration in the app as possible.
Any pointers to resolve this appreciated.
Your security domain name specified in jboss-web.xml needs to match the name of some security domain in your JBoss config, in your case the web descriptor specifies Quark while the security subsystem defined domain named ourDomain.
Whenever JBoss can not find the security domain you request in your jboss-web.xml, it will fallback to the default security domain, which in case of 7.x is named other.

Twitter + Camel Using HTTPS / SSL

I use twitter4j+ camel plugin to read the tweets
<route id="twitter-timeline-incoming">
<from
uri="twitter://timeline/user?type=polling&delay=180&consumerKey=xxxx&consumerSecret=xxxx&accessToken=xxxx8&accessTokenSecret=xxxxg&user=user_account" />
<process ref="setTwitterTimestampProcessor" />
<filter>
<method ref="twitterFeedFilter" />
<to uri="direct:twitterProcessFeed" />
</filter>
</route>
Twitter has made it mandatory to use SSL from today, https://dev.twitter.com/discussions/24239
I would like to know how to enable SSL for these requests , which is not given in here
http://camel.apache.org/twitter.html.
Thanks.
On Jan 14 2014 Twitter updated it's API to require all endpoints be https. The default API endpoints in Twitter4J are http, rather than https. You can change them manually for now, until they update the defaults in Twitter4J:
ConfigurationBuilder confBuilder=new ConfigurationBuilder();
confBuilder.setOAuthConsumerKey(consumerKey);
confBuilder.setOAuthConsumerSecret(consumerSecret);
confBuilder.setHttpRetryCount(3);
confBuilder.setHttpRetryIntervalSeconds(30);
confBuilder.setRestBaseURL("https://api.twitter.com/1.1/");
confBuilder.setStreamBaseURL("https://stream.twitter.com/1.1/");
confBuilder.setSiteStreamBaseURL("https://sitestream.twitter.com/1.1/");
confBuilder.setUserStreamBaseURL("https://userstream.twitter.com/1.1/");
confBuilder.setOAuthRequestTokenURL("https://api.twitter.com/oauth/request_token");
confBuilder.setOAuthAccessTokenURL("https://api.twitter.com/oauth/access_token");
confBuilder.setOAuthAuthorizationURL("https://api.twitter.com/oauth/authorize");
confBuilder.setOAuthAuthenticationURL("https://api.twitter.com/oauth/authenticate");
Configuration conf=confBuilder.build();
Twitter twitter=new TwitterFactory(conf).getInstance();
you can pass parameter to JVM to turn on SSL:
-Dtwitter4j.http.useSSL=true
At least this solved my issue.
Setting JVM is not an option for me,
Ticket has been raised in camel + twitter plugin
https://issues.apache.org/jira/browse/CAMEL-7134 and seems a patch is available in fix versions 2.11.4, 2.12.3, 2.13.0

worklight http adapter and NTLM authentication

i'm trying to implement NTLM authentication in a Worklight HTTP Adapter in order to connect to M$ back-end servers, such as Sharepoint Web services.
i've set-up my adapter.xml file with <ntlm>.
The adapter.xml structure first version was:
<authentication>
<ntlm />
<serverIdentity>
<username>user</username>
<password>password</password>
</serverIdentity>
</authentication>
My tests are done locally with Worklight studio, i get the following issues:
1) error when invocating WL procedure:
Procedure invocation failed:Could not resolve placeholder 'local.hostname'
where do i have to put this 'local.hostname' setting?
2) i tried to specifiy the hostname property of ntlm tag as given in documentation (IBM infocenter), WL Studio says that the xml is bad formed.
<authentication>
<ntlm hostname="myComputer.intranet.com"/>
<serverIdentity>
<username>user</username>
<password>password</password>
</serverIdentity>
</authentication>
Where "myComputer.intranet.com" is my computer's name within my corporate network.
Attribute 'hostname' is not allowed to appear in element 'ntlm'
Response from IBM Service Request:
The username used to authenticate with NTLM-enabled back-end system must be left padded with the windows domain name followed by a \, and the username.
<serverIdentity>
<username>domain\user</username>
<password>password</password>
</serverIdentity>
This works with hard-coded serverIdentity feature.
Due to security governance in my company, there can't be "generic" server identity. So i have to forward the end users' credentials to back-end systems to authenticate.
How to do this with Worklight authentication mecanisms (adapter-based for instance), where can i set the domain for my username?
Can i mix several kinds of security realms depending on adapters?