This is how browser deals with ssl certificate when connecting to https site
When i type https://myDemoSite.com in browser , first of all my browser asks for the certificate from myDemoSite.com(due to https), then myDemoSite send that certificate to browser
Once browser receives that certificate, browser will check whether it is signed by verified authority or not like verisign
If yes from second step,then as third step it checks whether certificate issues has same url which user in browser typed
Now i am connecting the https site through java program say HttpsConnectProg1.My question is how the programmei.e HttpsConnectProg1 will deal with this
certificate issued with https site connection(though certificate issued by this https site is cerified i.e signed by verified authority).
I just tried a small
programme connecting to https site which issues the certified certificate. I was expected some error like sslhandshake error or some certificate error
(as i did not add this this certified certificate in $JAVA_HOME\jre\lib\security folder)but to my surprise i did not get any error .
Now important question is does HttpsConnectProg1 checks step 3 done by browser as it is very important step? For your reference here it is
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Properties;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
public class ConnectToDemoSite {
/**
* #param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
String urlStr = "https://www.myDemoSite.com/demo1/";
URL url;
Properties reply = new Properties();
try {
url = new URL(urlStr);
URLConnection conn = url.openConnection();
if(conn instanceof HttpsURLConnection)
{
HttpsURLConnection conn1 = (HttpsURLConnection)url.openConnection();
conn1.setHostnameVerifier(new HostnameVerifier()
{
public boolean verify(String hostname, SSLSession session)
{
return true;
}
});
reply.load(conn1.getInputStream());
}
else
{
conn = url.openConnection();
reply.load(conn.getInputStream());
}
} catch (MalformedURLException e) {
e.printStackTrace();
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
catch (Exception e) {
e.printStackTrace();
}
System.out.println("reply is"+reply);
}
}
When you make a connection to an https:// URI with Java, it uses the Java Secure Socket Extension (JSSE) (unless you really want to use a custom implementation of SSL/TLS, but that's very rare).
There are multiple ways of tweaking the trust management (mainly be using custom TrustManagers), but it will use a certain number of sensible settings otherwise.
In your example, the certificate will be verified using the default SSLContext, itself configured with the default X509TrustManager, with trust anchors read from cacerts (see the table in the Customization section of the JSSE Ref. Guide).
By default, the JRE comes with a number of pre-trusted CA certificates (like most browsers or OSes) in cacerts, which is usually similar to what you would find in browsers. Here is what the JSSE Ref. Guide says about it:
IMPORTANT NOTE: The JDK ships with a limited number of trusted root
certificates in the /lib/security/cacerts file. As
documented in keytool, it is your responsibility to maintain (that is,
add/remove) the certificates contained in this file if you use this
file as a truststore.
Depending on the certificate configuration of the servers you contact,
you may need to add additional root certificate(s). Obtain the needed
specific root certificate(s) from the appropriate vendor.
If the certificate is trusted, it then checks whether the host name is valid for the intended URL. (Note that it's not the full URL that is checked, but the host name only.)
Those rules are defined in RFC 2818 (the HTTPS specification), Section 3.1. (Java 7 doesn't implement RFC 6125 yet, but the rules are very similar, especially for HTTPS.) EDIT: When the connection is established, the URLConnection (and the underlying SSLSession) is set with the host name of the server. In short, following the rules in RFC 2818, it looks into the server certificate for a DNS entry in the Subject Alternative Name (SAN) extension of the certificate to see if it matches the host name set for the connection, or look for that name in the certificate's Subject DN's Common Name (CN), when no SAN DNS entry is present.
The host name verification is normally done by the default host name verifier. In your example, you've replaced the default verifier by one that always returns true. Hence, this verification will not actually happen in your case, and everything will be accepted (you're introducing a security hole by doing this).
In addition, the default host name verification done in Java follows RFC 2818 more strictly than a number of browsers. In particular, it won't accept IP addresses in CNs.
(For the same reason as you should use a host name verifier that always returns true, you shouldn't use trust managers that don't do anything, as you'll see a number of examples around, offering a quick fix for some SSL error messages.)
Java includes root certificates from well-known certificate authorities, so if you have a real certificate, it operates much like a browser.
If you sign your own certificate, a browser will warn you and provide a UI to enable use of the certificate in the future. This is where things diverge for a Java program—the right way to handle this depends entirely on the program you are writing, and there can't be a one-size-fits-all approach.
If your application has a UI, you could do something similar to what a browser does.
On the other hand, a "headless" application is usually pre-configured with the necessary root certificates.
In the end, both browsers and the Java PKIX validation libraries are maintaining the security of TLS by requiring you to provide or approve the root certificates on which authentication ultimately depend. Without trusted root certificates, it is impossible to authenticate a server, and thus impossible to ensure privacy or integrity of communications.
Related
We develop a Java application that serves requests over TCP. We also develop client libraries for the application, where each library supports a different language/platform (Java, .NET, etc.).
Until recently, TCP traffic was confined to a secure network. To support usage on an insecure network we implemented TLS using the recipes in java-plain-and-tls-socket-examples. There are recipes here for both server and client, and a script to generate an X.509 certificate. Below is a summary of the recipe for TLS with server-only authentication:
Create an X.509 root certificate that is self-signed.
Configure the server with a keystore file that contains the certificate's identifying data plus the public and private keys.
Configure the client with a trust store file that contains the same identifying data plus only the public key.
When connecting, the client validates the certificate received from the server by comparing its identifying data with the corresponding data in the client's trust store. (This looks like certificate pinning.)
We assume for now that this approach is valid for securing TCP traffic. Signing by a certificate authority seems unnecessary because we control both server and client.
Initial testing shows that the implementation is working in our Java server and Java client:
Client accepts a server certificate that matches data in the client's trust store.
Client rejects a non-matching server certificate.
TCP packets captured by tcpdump contain encrypted data.
.NET Client
We use SslStream to encrypt TCP traffic. As the documentation suggests, we do not specify a TLS version; instead we throw an exception if the version is below 1.2.
We're not confident about how to use X509Chain.ChainPolicy.CustomTrustStore correctly, because the documentation omits information like use cases for this type, and for option types like X509KeyStorageFlags and X509VerificationFlags.
The code below aims to mimic the recipe outlined above, i.e. configure a trust store data structure for the client to use when validating a server certificate. This approach seems equivalent to importing the certificate into the operating system's trust store.
// Import the trust store.
private X509Certificate2Collection GetCertificates(string storePath, string storePassword)
{
byte[] bytes = File.ReadAllBytes(storePath);
var result = new X509Certificate2Collection();
result.Import(bytes, storePassword, X509KeyStorageFlags.EphemeralKeySet);
return result;
}
// Callback function to validate a certificate received from the server.
// fCertificates stores the result of function GetCertificates.
private bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
// Do not allow this client to communicate with unauthenticated servers.
//
// With a self-signed certficate, sslPolicyErrors should be always equal to
// SslPolicyErrors.RemoteCertificateChainErrors.
var result = (SslPolicyErrors.RemoteCertificateChainErrors == sslPolicyErrors);
if (result)
{
// The values below are default values: set them to be explicit.
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
chain.ChainPolicy.CustomTrustStore.AddRange(fCertificates);
result = chain.Build((X509Certificate2)certificate);
}
return result;
}
// Initialize SslStream.
private SslStream GetStream(TcpClient tcpClient, string targetHost)
{
SslStream sslStream = new SslStream(
tcpClient.GetStream(),
false,
new RemoteCertificateValidationCallback(ValidateServerCertificate),
null
);
try
{
sslStream.AuthenticateAsClient(targetHost);
// require TLS 1.2 or higher
if (sslStream.SslProtocol < SslProtocols.Tls12)
{
throw new AuthenticationException($"The SSL protocol ({sslStream.SslProtocol}) must be {SslProtocols.Tls12} or higher.");
}
}
catch (AuthenticationException caught)
{
sslStream.Dispose();
throw caught;
}
return sslStream;
}
Initial testing has yielded results that vary depending on the operating system:
When deployed to Ubuntu on WSL2, this code:
Accepts a valid server certificate.
Rejects an invalid server certificate.
Encrypts TCP packets.
Automatically uses TLS 1.3.
Given a valid server certificate, the callback function argument sslPolicyErrors is equal to SslPolicyErrors.RemoteCertificateChainErrors (expected).
When deployed to MacOS:
This code automatically uses TLS 1.2.
Given a valid server certificate, the callback function argument sslPolicyErrors includes these values:
SslPolicyErrors.RemoteCertificateNameMismatch (unexpected).
SslPolicyErrors.RemoteCertificateChainErrors (expected).
Questions
In what ways could security be compromised with this .NET code?
Upon reviewing discussions about "certificate name mismatch" (see SslPolicyErrors.RemoteCertificateNameMismatch above), it seems that our server certificate should include a subjectAltName field to specify allowed DNS names. Is this necessary, or would it be reasonable, as we are using certificate pinning, to ignore sslPolicyErrors when validating the server certificate?
I can't answer your specific questions but here is some thoughts:
You have not mentioned anything about how the server authenticates clients. So you might consider implementing something like client certificates. If you control both you probably want some way to ensure random attackers cannot connect.
You might consider creating a threat model. In many cases it is the things that you haven't thought about that cause problems.
If you are handling national security data or financial data you might want an external audit. Such might even be required in some cases.
If there is no way an attacker could sell, use, or ransom the data, then you will probably not be directly targeted. So you might worry more about mass attacks against known vulnerabilities, i.e. keep all your software up to date.
Consider other ways to mitigate risks. Are your server/client running in least privilege possible? Are you using a DMZ? Are firewalls correctly configured? Are credentials for support etc well managed?
I have an Android application that calls web services over SSL. In production we will have normal SSL certificates that are signed by a trusted CA. However, we need to be able to support self-signed certificates (signed by our own CA).
I have successfully implemented the suggested solution of accepting self-signed certificates but this will not work due to the risk of man in the middle attacks. I then created a trustmanager that validates that the certificate chain was in fact signed by our CA.
The problem is I have to bypass the normal SSL validation - the application will now only speak to a server that has one of our self-signed certificates installed.
I am a bit lost, I've googled extensively but can't find anything. I was hoping to find a way of programmatically adding our CA to the trust store on the device as this would be the least intrusive way of dealing with the issue.
What I want to achieve:
1. Full standard support for normal SSL certificates.
2. Additional support for self-signed certificates signed by our own CA.
Any advice?
You haven't posted any code, so I can't be sure what you actually did. However, I'll assume that you're setting up a SSLContext using only your custom X509TrustManager subclass. That's fine, but what you can do is have your custom trust manager implementation also chain to the built-in trust managers. You can do this while setting up your trust manager; something like this should work:
private List<X509TrustManager> trustManagers = new ArrayList<X509TrustManager>();
public MyCustomTrustManager() {
TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmFactory.init((KeyStore)null);
for (TrustManager tm : tmFactory.getTrustManagers()) {
if (tm instanceof X509TrustManager)
trustManagers.add((X509TrustManager)tm);
}
}
So now your custom trust manager has a list of all the built-in trust managers. In your override of checkServerTrusted(), you'll want to loop through the built-in trust managers and check each one by calling checkServerTrusted() on each one in turn. If none of them trust the certificate, you can apply your own cert checking. If that passes, you can return normally. If not, just throw a CertificateException as you'd otherwise do.
EDIT: Adding the below about doing things like host name verification.
You can also verify that the hostname in the certificate matches what you expect it to be. You'll want to pass in the valid hostname in your constructor for your custom trust manager and stash it in the class. Your checkServerTrusted() method will get passed an array of X509Certificate. Many "chains" will consist of just a single cert, but others will have several, depending on how the cA signed your cert. Either way, the first cert in the array should be "your" cert that you want to compare against.
After you check for basic cert validity using the trust managers, you'll then want to do something like this:
Principal subjectDN = chain[0].getSubjectDN();
String subjectCN = parseDN(subjectDN.getName(), "CN");
if (this.allowedCN.equals(subjectCN)) {
// certificate is good
}
The implementation of parseDN() is left up to you. subjectDN.getName() will return a comma-separated list of key-value pairs (separated by =), something like C=US,ST=California,L=Mountain View,O=Google Inc,CN=www.google.com. You want the CN ("Common Name") value for your hostname comparison. Note that if you have a wildcard cert, it'll be listed as something like *.example.com, so you'll need to do more than a simple equals match in that case.
Ok so I have a peer to peer (client/server on one host) setup (over a local LAN), this is using Netty, a Java networking framework. I use raw TCP/IP (as in, no HTTP) for communication and transfers.
Currently all data is transferred in "plain-text" and i'm starting the process of securing such transmitted data.
I've had a good read of types of encryption/practices etc (but probably only touched the surface and its melting my brain already)
Netty includes a SSL implemntation, heres some links to hopefully better explain myself:
http://docs.jboss.org/netty/3.2/xref/org/jboss/netty/example/securechat/package-summary.html
Inside "SecureChatTrustManagerFactory" there are 2 methods:
public void checkClientTrusted(
X509Certificate[] chain, String authType) throws CertificateException {
// Always trust - it is an example.
// You should do something in the real world.
// You will reach here only if you enabled client certificate auth,
// as described in SecureChatSslContextFactory.
System.err.println(
"UNKNOWN CLIENT CERTIFICATE: " + chain[0].getSubjectDN());
}
public void checkServerTrusted(
X509Certificate[] chain, String authType) throws CertificateException {
// Always trust - it is an example.
// You should do something in the real world.
System.err.println(
"UNKNOWN SERVER CERTIFICATE: " + chain[0].getSubjectDN());
}
"SecureChatKeyStore" contains a hard coded certificate from what I can see.
So my questions are:
Do I need to generate a certificate?
if so, each time the application is run?
if so, per client?
if so, is this certification passed between client and server?
if so, how is it done securely?
I'm not entirely sure where to start.
From what I can see the Netty implementation is saying "Here's the basis of creating secure connections, but we have left out the part that actually makes them secure/authenticated".
Any other pointers/tips I should know about?
Thank you in advance.
As others have pointed out, there is a difference between application security and transport link security. I think you are aiming for the last one as you mainly mention encryption. Encryption offers confidentiallity from eavesdroppers. Furhermore, as SSL also incorporates message authentication code, it will also offer protection of a third party altering packets during transit. It does not provide any protection of messages once received.
As you may have noticed on the internet for HTTPS connections, you will need at least a server certificate. This certificate can remain static, although it should contain an expiry date at which time you should replace the certificate. The server certificate should be trusted by the client (e.g. by embedding it as a resource). You can also use SSL with client authentication, but that means you need to have ample security measures to keep the private key on the client safe.
It's probably best to start off with a "self-signed" server certificate only. Thats the one you need to trust in the checkServerTrusted method. Basically, the chain is simply that one certificate.
Our system communicates with several web services providers. They are all invoked from a single Java client application. All the web services up until now have been over SSL, but none use client certificates. Well, a new partner is changing that.
Making the application use a certificate for the invocation is easy; setting javax.net.ssl.keyStore and javax.net.ssl.keyStorePassword will do it. However, the problem is now how to make it so that it only uses the certificate when invoking that particular web service. I guess more generally speaking, we'd like to be able to choose the client certificate to be used, if any.
One quick solution could be setting the system properties, invoking the methods, and then unsetting them. The only problem with that is that we're dealing with a multi-threaded application, so now we would need to deal with synchronization or locks or what have you.
Each service client is supposed to be completely independent from each other, and they're individually packaged in separate JARs. Thus, one option that has occurred to me (although we haven't properly analyzed it) is to somehow isolate each JAR, maybe load each one under a different VM with different parameters. That's merely an idea that I don't know how to implement (or if it's even possible, for that matter.)
This post suggests that it is possible to select an individual certificate from a key store, but how to attach it to the request seems to be a different issue altogether.
We're using Java 1.5, Axis2, and client classes generated with either wsimport or wsdl2java.
The configuration is done via an SSLContext, which is effectively a factory for the SSLSocketFactory (or SSLEngine). By default, this will be configured from the javax.net.ssl.* properties. In addition, when a server requests a certificate, it sends a TLS/SSL CertificateRequest message that contains a list of CA's distinguished names that it's willing to accept. Although this list is strictly speaking only indicative (i.e. servers could accept certs from issuers not in the list or could refuse valid certs from CAs in the list), it usually works this way.
By default, the certificate chooser in the X509KeyManager configured within the SSLContext (again you normally don't have to worry about it), will pick one of the certificates that has been issued by one in the list (or can be chained to an issuer there).
That list is the issuers parameter in X509KeyManager.chooseClientAlias (the alias is the alias name for the cert you want to picked, as referred to within the keystore). If you have multiple candidates, you can also use the socket parameter, which will get you the peer's IP address if that helps making a choice.
If this helps, you may find using jSSLutils (and its wrapper) for the configuration of your SSLContext (these are mainly helper classes to build SSLContexts). (Note that this example is for choosing the server-side alias, but it can be adapted, the source code is available.)
Once you've done this, you should look for the documentation regarding the axis.socketSecureFactorysystem property in Axis (and SecureSocketFactory). If you look at the Axis source code, it shouldn't be too difficult to build a org.apache.axis.components.net.SunJSSESocketFactory that's initialized from the SSLContext of your choice (see this question).
Just realized you were talking about Axis2, where the SecureSocketFactory seems to have disappeared. You might be able to find a workaround using the default SSLContext, but this will affect your entire application (which isn't great). If you use a X509KeyManagerWrapper of jSSLutils, you might be able to use the default X509KeyManager and treat only certain hosts as an exception. (This is not an ideal situation, I'm not sure how to use a custom SSLContext/SSLSocketFactory in Axis 2.)
Alternatively, according to this Axis 2 document, it looks like Axis 2 uses Apache HTTP Client 3.x:
If you want to perform SSL client
authentication (2-way SSL), you may
use the Protocol.registerProtocol
feature of HttpClient. You can
overwrite the "https" protocol, or use
a different protocol for your SSL
client authentication communications
if you don't want to mess with regular
https. Find more information at
http://jakarta.apache.org/commons/httpclient/sslguide.html
In this case, the SslContextedSecureProtocolSocketFactory should help you configure an SSLContext.
Java SSL clients will only send a certificate if requested by the server. A server can send an optional hint about what certificates it will accept; this will help a client choose a single certificate if it has multiple.
Normally, a new SSLContext is created with a specific client certificate, and Socket instances are created from a factory obtained from that context. Unfortunately, Axis2 doesn't appear to support the use of an SSLContext or a custom SocketFactory. Its client certificate settings are global.
I initialized EasySSLProtocolSocketFactory and Protocol instances for different endpoints and register the protocol with unique key like this:
/**
* This method does the following:
* 1. Creates a new and unique protocol for each SSL URL that is secured by client certificate
* 2. Bind keyStore related information to this protocol
* 3. Registers it with HTTP Protocol object
* 4. Stores the local reference for this custom protocol for use during furture collect calls
*
* #throws Exception
*/
public void registerProtocolCertificate() throws Exception {
EasySSLProtocolSocketFactory easySSLPSFactory = new EasySSLProtocolSocketFactory();
easySSLPSFactory.setKeyMaterial(createKeyMaterial());
myProtocolPrefix = (HTTPS_PROTOCOL + uniqueCounter.incrementAndGet());
Protocol httpsProtocol = new Protocol(myProtocolPrefix,(ProtocolSocketFactory) easySSLPSFactory, port);
Protocol.registerProtocol(myProtocolPrefix, httpsProtocol);
log.trace("Protocol [ "+myProtocolPrefix+" ] registered for the first time");
}
/**
* Load keystore for CLIENT-CERT protected endpoints
*/
private KeyMaterial createKeyMaterial() throws GeneralSecurityException, Exception {
KeyMaterial km = null;
char[] password = keyStorePassphrase.toCharArray();
File f = new File(keyStoreLocation);
if (f.exists()) {
try {
km = new KeyMaterial(keyStoreLocation, password);
log.trace("Keystore location is: " + keyStoreLocation + "");
} catch (GeneralSecurityException gse) {
if (logErrors){
log.error("Exception occured while loading keystore from the following location: "+keyStoreLocation, gse);
throw gse;
}
}
} else {
log.error("Unable to load Keystore from the following location: " + keyStoreLocation );
throw new CollectorInitException("Unable to load Keystore from the following location: " + keyStoreLocation);
}
return km;
}
When I have to invoke the web service, I do this (which basically replace "https" in the URL with https1, or https2 or something else depending on the Protocol you initialized for that particular endpoint):
httpClient.getHostConfiguration().setHost(host, port,Protocol.getProtocol(myProtocolPrefix));
initializeHttpMethod(this.url.toString().replace(HTTPS_PROTOCOL, myProtocolPrefix));
It works like a charm!
I know there were a lot of questions/answers about how to ignore SSL error in the code.
On our dev region dev.domain.tld we have configured a app server over SSL.
The certificate that is displayed is for somedev.domain.tld.
There is no way to change the certificate, it will always be a domain mismatch.
So when I deploy a web-service to https://dev.domain.tld and try to connect/call my webservice I get an exception:
Caused by:
java.security.cert.CertificateException:
No name matching dev.domain.tld found
And I have the somedev.domain.tld CERT in my trust store.
Now, I saw a lot of samples how to change that in the code (using a Trust Manager that accepts all domains), but how do I specify to the JVM to ignore the domain mismatch when connecting to the server? Is there a -Djavax.net.ssl argument or something?
Thank you!
UPDATE:
Or, since I am using Spring-WS, is there a way to set some property in Spring for that? (WebServiceTemplate)
UPDATE
I guess I'll have to do use something from Spring Security: http://static.springsource.org/spring-ws/sites/1.5/reference/html/security.html
This works for me in a client application of mine, perhaps this will also work for you if you are (or Spring is internally) using HttpsURLConnection anywhere.
HostnameVerifier hv = new HostnameVerifier() {
public boolean verify(String urlHostName, SSLSession session) {
log.warning(String.format("Warning: URL Host: '%s' does not equal '%s'", urlHostName, session.getPeerHost()));
return true;
}
};
HttpsURLConnection.setDefaultHostnameVerifier(hv);
Its hardly SSL best practice though. The best solution would be to use a certificate that matches the hostname.