Under what circumstances would one use a HostnameVerifier over a TrustManager in Java? Is one recommended over the other? Looking at the Java docs (Interface HostnameVerifier and Interface TrustManager), I can't tell when its best to use either (though the TrustManager seems more versatile).
In the past, I have always used a custom TrustManager. However, I noticed Heartbleed exploit in java uses both (but I don't think its correct).
EDIT: when using HostnameVerifier, are the other customary X509 checks performed, like path building and expiration and revocation (if configured)? I think I am essentially asking if HostnameVerifier supplements the other checks (rather than replacing them).
For example, suppose a dev server is at dev.example.com and its signed by an internal CA. There's one DNS name in dev.example.com's certificate, and its dev.example.com. Further, suppose I connect to it as 192.168.1.10. Could I use a HostnameVerifier to allow both dev.example.com and 192.168.1.10? In this scenario, is the additional name allowed and are the other customary X509 checks are performed?
Under what circumstances would one use a HostnameVerifier over a TrustManager in Java?
Never. They do different things. TrustManage authenticates certificates as part of SSL. HostnameVerifier verifies host names as part of HTTPS. They're not in competition.
Is one recommended over the other?
No.
EDIT
The TrustManager runs during the TLS handshake. If it indicates failure, the handshake is aborted and the connect fails.
The HostnameVerifier runs after the TLS handshake, over a TLS connection that is already valid from the TLS point of view, so at that point you know that the certificate is valid, signed by a trusted issuer, non-expired (?), etc., and all you have to do is decide (a) whether it's from the correct server and (b) whether you trust that server. You might do (b) inside a TrustManager, but far more commonly you wouldn't provide your own TrustManager at all.
Related
Could you please help me on following two questions?
I have a FIX engine that connects to FIX servers. There is a FIX server that requires client to authenticate itself during SSL handshake. It also provides a SSL certificate that I need to use during SSL handshake as client side certificate.
Question#1: Can I store multiple certificates (private keys) in a keystore that I will load later in my FIX engine?
Question#2: If answer to #1 is yes, then how would the SSL context select a client certificate during SSL handshake when it establishes a SSL connection with the server?
Note- I am thinking of these questions because in future there may be another Servers that also have similar requirement.
Question#1: Can I store multiple certificates (private keys) in a keystore that I will load later in my FIX engine?
As far as Java is concerned, you definitely can have multiple privateKey entries, each containing a privatekey plus certificate or (usually) chain, in one keystore file or other storage object. (Note that if you use PKCS12 to interchange with other software that other software may not support multiple entries in one PKCS12. If you use PKCS12 only for better security in Java, or to silence the warnings in j9+, this is not a concern.)
I don't know what FIX engine you are using, or how it handles its key-and-cert info for TLS-formerly-SSL (or indeed how it handles TLS at all). If it simply loads a keystore file (or stream) as a KeyStore object, the the Java capability above applies. If it does something else, what it can do depends on that something else.
Question#2: If answer to #1 is yes, then how would the SSL context select a client certificate during SSL handshake when it establishes a SSL connection with the server?
In the TLS protocol, when a server requests that the client use a certificate, it specifies algorithm constraints (the leaf key algorithms through 1.2 and/or the chain signature algorithms in 1.2 and 1.3) and may specify a list of desired Certificate Authorities (CAs) (which may have issued any cert in the chain).
If your client (FIX engine) uses the Java-standard implementation of TLS (JSSE) with its standard (and default) 'SunX509' KeyManager, that will choose from the keystore an entry satisfying the above constraints from the server; if you select or configure the 'NewSunX509' or 'PKIX' KeyManager, it also checks any algorithm constraints defined for your JVM (for example Oracle JVMs since about 2017 prohibit certs using MD5- or SHA1-based signatures, or RSA keys less than 1024 bits), and gives preference to entries where the cert is not expired and does not have inappropriate KeyUsage or ExtendedKeyUsage extensions. Among multiple acceptable or preferred entries the selection is arbitrary -- however the keystore implementation enumerated them. Most servers support all standard (maybe and non-obsolete) algorithms and usually cannot be distinguished by that. If a server accepts certs from 'public' CAs like Digicert, GoDaddy, LetsEncrypt those also are not likely distinct, but if it uses a CA (or perhaps a few) specific to that server or its operator, such CA name(s) will often be unique and thus the key-and-cert will be selected only for that server.
If your client uses a custom KeyManager -- either explicitly in your application or via middleware (for example Apache HttpClient) -- it can do something different. It can even choose to use a key-and-cert the server will reject, although that would normally not be considered useful.
If your client uses a different TLS implementation, that could use the standard KeyManager structure, probably with the options above, or could do anything else.
if you use spring framework you can specify alias of certificate that you prefer to select by adding
-Dserver.ssl.key-alias=your_preffered_alias
You said that you have a FIX engine that connects to FIX server, then asked if private keys can be stored in a keystore for your FIX engine. This leads me to believe that the FIX engine is in a client application. You should NOT store private keys publicly in a keystore. Instead, you should be providing the client with a truststore, which just contains the certificate.
I don't have an answer for this, but this post might be helpful; https://stackoverflow.com/a/1710543/4417924
I am running the Java process (Microservice) and trustStore configured in the JVM parameter. If the Microservice needs to connect the external URL which required the cert to be imported in trustStore.
Example:
example.co.uk -> examplecouk as alias in trustStore
example.com -> examplecom as alias in trustStore
example.in -> examplein as alias in trustStore
How does Java know that which certs and alias to be picked from the trustStore for the particular endpoint as I don't pass/mention the alias in the JVM params? Is it pick randomly?
user207421 is nearly correct. To be more exact:
When you as client open an SSL/TLS connection to a server, as part of the handshake the server sends its certificate 'chain' containing its own cert, plus usually one or more linked CA (Certificate Authority) certificates ending with a 'root' CA that should be trusted. See the neighboring Stack https://security.stackexchange.com/questions/20803/how-does-ssl-work/ for an epically complete explanation. Public servers normally use a cert issued and signed by a public CA like Digicert, GoDaddy, LetsEncrypt/ISRG which are already in the standard default truststore (for Java in the file JRE/lib/security/cacerts) so no action is needed. If a server uses a cert from a off-brand or private CA, or a self-signed cert (from no CA at all), then (for Java) some cert in the chain must be added to the client truststore, or otherwise overridden; this is only required to be the server cert in the case where the server cert is self-signed (which is a chain by itself and has no relevant CA certs).
Java/JSSE implements this through an SSLContext which contains among other things a TrustManager, more specifically an X509ExtendedTrustManager, which is initialized from a truststore. You can create an SSLContext explicitly in code from any set of trusted certs (which doesn't even need to be from a file), or use the default context which uses the default truststore file, which defaults to the filename above unless overridden by a system property.
When the server cert chain is received it is passed to the context's TrustManager for validation; among (many!) other checks, at each level of a normal chain, or the single level of a self-signed cert, the JSSE TrustManager looks for an anchor cert with the same Subject and (Subject)PublicKey and if so uses it to validate the cert chain. Note that a normal (CA-issued) leaf cert can have Subject empty if Subject Alternative Name is used instead -- see rfc5280 and rfc2818 -- but a self-signed cert cannot, because it has Subject = Issuer and Issuer must not be empty. Certs for different entities (e.g. different servers) normally are expected to have different keys, although a single entity can have multiple certs with either the same key or different keys, and might correspond to multiple server names and/or addresses.
If the certificate is determined valid in general, for some TLS applications, notably HTTPS, the validator also checks it is for the correct server, specifically that the CommonName attribute in the Subject field, or an entry in the Subject Alternative Name extension if present -- which for public CAs for at least a decade it is, matches the host DNS name or IP adddress in the URL. In older versions of Java (through 6 IIRC) this was not done in JSSE but rather in the calling application(s) or library, such as HttpsURLConnection, which as a legacy still has the option to use its own HostnameVerifier instead.
All of this can be altered by using a custom TrustManager in place of the standard one, and some things like Apache HttpClient do so validly, but you will find (too many) answers here and some other Stacks recommending you 'solve' TLS errors by using a neutered TrustManager that just accepts any cert, regardless of whether it is actually valid and correct, and thus happily connects and sends sensitive data to, or accepts changes from, any attacker who manages to intercept the IP traffic, which is often pretty easy nowadays.
Alias is a way to directly access the certificate but your keystore has other information regarding the certificate as well. X.509 certificates have a field called SAN (Subject Alternative name) which contains the DNS information of the certificate. When you try to connect to a specific URL, the keystore is looked up for the corresponding DNS name in SAN and correct certificate is picked up.
I hope it clarifies your doubt about java not asking for the alias. Rest assured, there is nothing random in this process.
I am trying to understand a piece of code that does not override HostnameVerifier and SSLSocketFactory for HttpsURLConnection. The current code is able to make the SSL request fine, which is a little confusing to me.
How does the default implementation of HostnameVerifier and SSLSocketFactory classes work? Does it verify the certificate CA and hostname or it bypasses all these checks all together?
Also, is there any special behavior if the request has localhost in it?
The URL looks like this
https://localhost:/
Thanks for your help.
It will check the CA certificate against the trusted ca-certificates in the trusted store of the JDK jre/lib/security/cacerts or whatever you have configured.
It will also check, that all certificates in the chain are trusted, not only the immediate signer. It will, by default, not check online for revoked certificates, but you can enable that in the java.security policy file.
It will also check the hostname against the certificate.
It supports additional CNs and wildcard CNs.
Extended question regarding localhost:
localhost is just a hostname like any other. It must be listed in the certificate. But you will not find an official CA to give you that certificate, I guess :-)
If you want to play with your real cert on localhost, you address it with a different hostname. If you want to run through a cert for foo.bar.com, add to your hosts file (Win: %WINDOWS%\system32\drivers\etc\hosts, Unix /etc/hosts) the line:
127.0.0.1 foo.bar.com
Now fire up your browser and connect to https://foo.bar.com. It will connect to your local server and post a SSL encrypted HTTP request. That request contains the hostname from the URL, not the real hostname.
I was oversimplyfying: The browser receives the SSL certificate from the server and compares the CN against the hostname it just called. If this matches, it will check if the certificate can be trusted (don't nail me down on the order, maybe it will check the trustlevel first).
Here is where you fail: The certificate provided by the server does not contain the CN localhost, and thus, the client will abort the connection. In your case, the client is the HttpSSLrequest.
To make the client believe all is well, you can do two things:
- fake the hostname, and add the signing CA in the cacerts truststore.
- implement a hostname and certificate validator, that lets you through.
Regarding the tutorial, a self-signed certificate is used, you will have to convince the browser to accept it by overriding the warning on first access.
I am using SSLContext so set up Jersey client, and need to disable the common name check in order to avoid unnecessary issues. However, I can find no documentation as to how we can do it correctly. So is the common name check disabled by default in SSLContext (assuming using TLS) or do we need to explicitly disable it? If so, how? Thanks.
This does not answer your question, but it tells you that what you are doing is a bad idea and probably caused by a misunderstanding of how verification works.
...need to disable the common name check in order to avoid unnecessary issues
I don't know what the "unnecessary issues" are which you are trying to avoid, but not verifying the hostname is more or less that same as disabling all validation and thus make it possible to incorporate the server and/or to do man-in-the-middle attacks.
If you don't verify the hostname but still validate the certificate trust chain the attacker can now simply use a certificate signed by a trusted CA for the attackers own site, e.g. attacker.example.com. CA's will issue such certificate since the attacker can prove ownership of its own site.
Using its own certificate the attacker can now incorporate all the other sites, since the trust chain is valid even if the hostname is not. This is the same as if you would accept any kind of identification issued by a state, without even looking if the picture in the I.D. matches the person showing the I.D.
If I'm understanding you correctly, I think you can accomplish what you are trying to do by implementing a HostnameVerifier, and just returning true in the verify method. You can set up the verifier on the ClientBuilder. For example
Client client = ClientBuilder.newBuilder()
.sslContext(sslContext)
.hostnameVerifier(hostnameVerifier)
.build();
I'm implementing a protocol that specifies TLS1.2 as transport layer and requires client-side server authentication to verify the server's hostname by comparing the hostname value of the connecting client socket to the value indicated by the server in its certificate, namely subjectAltName extension of type dNSName.
I've created a test, put this value in the server's certificate and it seemed to get ignored by the client completely, but I'd like to be sure. Do I have to code this check in an implementation of X509ExtendedTrustManager.checkServerTrusted(X509Certificate[], String, Socket) or can I enable it through some obscure property? The reference guide appears silent on this matter.
The protocol specification (the one I'm implementing) also mentions that wildcards may be used as a prefix of the value in the certificate.
A "*" wildcard character MAY be used as the leftmost name
component in the certificate. For example, *.example.com would
match a.example.com, foo.example.com, etc., but would not match
example.com.
However, when I tried to create such an extension value with keytool, it refused to do so. What's going on?
"C:\Program Files\Java\jdk1.7.0_51\bin\keytool.exe" -genkeypair -alias server -keyalg RSA -validity 365 -ext san=dns:*.example.com -keystore mykeystore
...
keytool error: java.lang.RuntimeException: java.io.IOException: DNSName components must begin with a letter
Java 7 provides a way to verify the host name automatically on an SSLSocket or SSLEngine, but it is not enabled automatically (this didn't exist in Java 6). The implementation can use the naming specification of LDAP or HTTPS, not the more generic RFC 6125 (at least not yet). In most cases, using this part of the HTTPS specification should be fine for other protocols, certainly better than nothing.
You can use this as follows:
SSLParameters sslParams = new SSLParameters();
sslParams.setEndpointIdentificationAlgorithm("HTTPS");
sslSocket.setSSLParameters(sslParams); // or SSLEngine
You can find references for this:
in the SSLParameters.setEndpointIdentificationAlgorithm(...) documentation,
by searching for "endpoint identification" in the JSSE Reference Guide,
in the JSSE Ref Guide section on X509ExtendedTrustManager ("the X509ExtendedTrustManager class also support algorithm constraints and SSL layer hostname verification"),
by scrolling down to the very last table in the Standard Names section of the Java™ Cryptography Architecture.
Standard Algorithm Name Documentation
The second problem you have seems to be an issue with keytool, to generate such a certificate. This doesn't affect how such certificates presented to a Java client are verified. You can use other tools to generate a certificate with a wildcard if necessary.
EDIT:
Note that for this to work, you need to create the SSLSocket with a method that uses String host (so that it knows the host name) or the SSLEngine with SSLContext.createSSLEngine(String peerHost, int peerPort). This is how it knows which hostname to try to match in the certificate. (Using the name is also useful for SNI.)
The reference guide on host name verification:
http://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#ciphersuitechoice
When using raw SSLSocket and SSLEngine classes, you should always check the peer's credentials before sending any data. The SSLSocket and SSLEngine classes do not automatically verify that the host name in a URL matches the host name in the peer's credentials. An application could be exploited with URL spoofing if the host name is not verified.
Therefore you have to manually do that. Conceptually, this task is not part of the TrustManager's responsibility; it's probably better done after connection is established, by examining peer certificate.
Oddly though, there is no public API for doing that. If you don't mind relying on sun.* package, you can use sun.security.util.HostnameChecker.match(String expectedName, X509Certificate cert).
I am also working on related issues, and I'll publish my API in a few days.
Wildcard certificate - I just raised the same question to jdk dev, waiting for a resonse -
http://mail.openjdk.java.net/pipermail/security-dev/2014-September/011153.html