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.
Related
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 want to setup mutual authentication on Tomcat 8.
I have done the setup of keys primarily keystore and truststore and the setup is working with Firefox tested.
When accessing the URL I am prompted for certificate and when appropriate certificate is provided I am allowed the access.
The piece of puzzle I am missing is, the truststore that I have which has CA cert and public keys of few clients. This CA should be a self signed CA, not from known authorities on the web, as.
I just do not want anybody to have access, people using my CA only should be able to access. Assuming say abc.com has certificate signed by authority godady or verisign, which are well known and trusted should not be able to call. I want strict access.
In my truststore I just want to keep CA no public keys, I do not want to keep adding certs as clients grow up. Clients would use my CA.
I think my self signed CA in truststore and clients using my CA only in chain for their certificates should be good enough for secure(only clients with my CA) scalable solution, where I do not have to keep adding clients in truststore.
Please advice my assumptions are correct for a production cloud based system.
Thanks for the time.
Yes, your assumption are correct. During SSL handshake server will ask client to provide any certificate issued by CA present in server trust-store. Also you need to consider that client trust-store should have issuing CA of server certificate otherwise it will show untrusted server certificate on client/browser.
I'm having an issue sending our certificate (client) to another server during a web service call.
We're expected to return the certificate with the CN:
b2b-test
however whenever we receive the servers certificate and attempt to send our own, we're getting:
Unable to find valid certificate path to requested target
From my understanding, if I put our certificate b2b-test inside of our keystore jre/lib/security/cacerts, then when receiving the server request, it should send the appropriate certificate.
How does the client know which key from the keystore it should send to the server? Should I be specifying the exact certificate to send during my request, or is it enough for it to be in the keystore?
Currently the server is sending back the certificate
b2b-a
The certificate chain shows:
*** Certificate chain
chain [0] = [
[
Version: V3
Subject: CN=b2b-a, OU=Web Technologies
The URL for the service is:
https://b2b-a/service
If I configure it like one way ssl, and put the servers certificate into our cacerts, then it picks up their cert and fails the handshake (I'm assuming because it's expecting our cert and not theirs back due to it being a two way setup).
I'm a little stumped here. Any clarification on what I've said is greatly appreciated.
The "Unable to find valid certificate path to requested target" error usually occurs when one side passes a certificate for which the other side doesn't trust any of the certs in the chain. You'll generally (always?) see the error message on the side that's receiving the cert (the side that's missing the truststore).
If you're the client and they're the server, one-way SSL would send their cert from them to you and you would validate it (making sure that the hostname matches the content in the cert), but it would not send any cert from you to them nor would they do any validation.
You'll want to make sure you're configuring a truststore that contains either the b2b-a certificate or a certificate that was used to sign it (but that cert doesn't appear to have any root CAs, so you're stuck with directly importing the cert itself). So yes, you want to do what you wrote in your post and put their certificate into your cacerts. And you'll also need to make sure that the b2b-a service trusts the cert you're sending (or a root CA that was used to sign your cert, if any), so you'll need to put your cert into their cacerts as well, or do something equivalent.
Note that putting a cert in cacerts (or in a truststore JKS) DOES NOT send any certs to anyone; any truststore is used only to allow you to validate that you trust the certificate someone else provides you, either because you trust the cert or because you trust a CA cert that was used to sign the cert. No certs are picked out of the cacerts directory to send to another machine.
If you're using a JKS rather than the JRE directory (which is generally a better idea, since you can specify a different set of trusted certs for your process without changing the default set for anyone running a Java project within your JRE), it's possible to use the same JKS as both a keystore and a truststore (you'll just provide the same filename and password for both the keystore and the truststore properties), or you can have two separate files; either approach will work.
From my understanding, if I put our certificate b2b-test inside of our keystore jre/lib/security/cacerts, then when receiving the server request, it should send the appropriate certificate.
Certainly not. That's a truststore. A source of trusted certificates. Don't mess with it. It isn't used for the purpose you require and in any case it is updated every Java dot release, which clobbers your changes.
Your own certificate and private/public key pair should go in a file of your own, a keystore, which is defined to JSSE by the javax.net.ssl.keyStore and javax.net.ssl.keyStorePassword system properties, or by more complex means described in the JSSE Reference Guide.
The keystore is a precious and confidential file containing your private key. Guard and protect it. Private key leakage would compromise your security and identity.
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
I want to understand the below line
"The SSL Cert is bound to the actual host name. If you have an SSL cert for "qa.example.com", it won't work on your machine named "dev.example.com".
Questions:
How is possible to create a cert bound to a hostname?
I have a client and server model. The server is not validating the client during communication. So far I had ONE set of key and certs which is installed on multiple servers and the communication works fine.
Now we have new set of certs which is bound to a hostname. Each server has a set of certs.
When swapped the communication does not work. "Handshake failure" obtained.
What's happening here? Who validates the hostname?
The client validates the certificate returned by the server. Among the various verifications performed the client checks that the hostname embedded in the certificate matches the hostname of the server. If the hostname doesn't match the handshake fails.
This means that if your certificate was issued for qa.example.com, it won't work on dev.example.com. For the same certificate to work for both hostnames you'll need a wildcard certificate (issued for *.example.com).
The hostname is in the certificates, you need to generate new certificates with different hostnames. The certificate names is validates by the client when it attempts to establish a secure connection.