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.
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'm currently trying to transfer data over the internet via SSL/TLS in java and I want both parties to authenticate themselves. I have implemented the KeyManager myself to load the key pair and present the other party the appropriate certificate.
Now, I'm trying to check the certificate and I'm doing that by implementing my own TrustManager (both parties hold the cert of the other party, everything is self-signed). However, getAcceptedIssuers doesn't work like I want it to, because even when I return none the connection still gets established without problem.
Why doesn't the certificate get refused?
protected static class SelectingTrustManager implements X509TrustManager{
final X509TrustManager delegate;
private String[] trustedAliases;
private final KeyStore keystore;
public SelectingTrustManager(X509TrustManager delegate, KeyStore keystore, String[] trustedAliases) {
this.trustedAliases = trustedAliases;
this.keystore = keystore;
this.delegate = delegate;
}
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException{
delegate.checkClientTrusted(chain, authType);
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException{
delegate.checkServerTrusted(chain, authType);
}
public X509Certificate[] getAcceptedIssuers(){
return new X509Certificate[0];
}
}
You aren't clear if your code is client or server, so I answer both, although it pretty much matters only for server.
Although the javadoc isn't specific, X509TM.getAcceptedIssuers is NOT used to decide whether to trust a received cert or chain; that is done solely by checkServerTrusted in the client or checkClientTrusted in the server.
The value of getAcceptedIssuers is used for, and affects, only two things:
in the server, (only) if client auth is enabled (by calling needClientAuth(true) or wantClientAuth(true)), the Subject names from its elements are used to create the CA list in the server's CertificateRequest message. This is not forced to be the same as the list of CAs that will be used as trust anchors for the client cert chain, if one is received; in fact, the trustmanager isn't actually required to use a list of trust anchors or even the rest of the standard validation algorithm -- although if your 'delegate' is the standard X509[Extended]TrustManager which uses the standard CertPathValidator that does. However, if you tell client(s) to use cert(s) from certain CAs, and then don't accept valid cert chain(s) from those CAs, you will likely have unhappy client(s) who may come after you with various heavy, sharp and/or otherwise unpleasant objects.
In the specific case of no 'CAs' (an array of 0 length, as you have), the client can send a cert from any CA it chooses, and the server will accept it depending only on checkClientTrusted.
For completeness note the RFCs define an extension for the client to specify what CA(s) it wants the server cert to use, but I don't know any implementation that supports this extension and Java/JSSE definitely doesn't, so in practice the server either has only one cert (per algorithm), or it selects based on SNI (and nothing else), and if that cert isn't trusted by the client too bad.
if you have algorithm constraints in effect (and nowadays you usually do by default even if you don't explicitly set some) they are not enforced on the last cert in a chain (the putative anchor) if it is among the certs returned by getAcceptedIssuers, which are presumed to be (actual) anchors. In other words if a cert is a trust anchor presumably the user has decided to trust it even though it may use algorithms that do not meet current standards (like MD5, or RSA smaller than 1024).
Whether the person who put the cert in a truststore or otherwise made it an anchor actually evaluated its security correctly is a different question that Java doesn't try to answer. Even Stackexchange may not be able to do that, although I'm sure there will be people here glad to try. (I make no commitment whether I will be one of them.)
I want to find host name from TLS Client Hello Message. I want to find host name before java does complete handshake for transparent ssl proxy.
Is there any way to find SNI extension value without writing whole ssl handshake logic ?
Is Java supports ssl handshake with initial memory buffer ?
My Idea is:
Read TLS client hello message, parse it and find SNI value
Call sslSocket.startHandshake(initialBuffer) Initial buffer will contain TLS client hello packet data. So Java can do handshake.
Second Idea is to use SSLEngine class. But it seems a lot more implementation than requirement. I assume SSLEngine is used most of async in case which I don't require it.
Third idea is to implement complete TLS protocol.
Which idea is better ?
Both SSLSocket and SSLEngine lack (quite inexplicable) proper SNI support for server connections.
I came across the same problem myself and ended up writing a library: TLS Channel. It does not only that, it is actually a complete abstraction for SSLEngine, exposed as a ByteChannel. Regarding SNI, the library does the parsing of the first bytes before creating the SSLEngine. The user can then supply a function to the server channel, to select SSLContexts depending on the received domain name.
The accepted answer to this question is a bit old and does not really provide an answer to the question.
You should use a custom KeyManager when initializing the SSLContext and the trick is to use a javax.net.ssl.X509ExtendedKeyManager (note the Extended term). This will offer the possibility to expose the SSLEngine during the key alias selection process instead of the plain (useless) socket.
The method chooseEngineServerAlias(...) will be called during the SSL handshake process in order to select the proper alias (cert/key) from the KeyStore. And the SNI information is already populated and available in the SSLEngine. The trick here is to cast the SSLSession as an ExtendedSSLSession (note the Extended term) and then to getRequestedServerNames().
KeyManager[] km = new KeyManager[]
{
new X509ExtendedKeyManager()
{
public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine)
{
if( engine.getHandshakeSession() instanceof ExtendedSSLSession )
{
List<SNIServerName> sni = ((ExtendedSSLSession)engine.getHandshakeSession()).getRequestedServerNames();
// select the proper certificate alias based on SNI here
}
}
}
}
SSLContext context = SSLContext.getInstance("TLS");
context.init(km, null, null);
I am using Mina with a SslFilter, configured with "client authentication needed".
Once a connection is made and a handshake is done, how to get the certificate (or the principal) of the peer on the other side of the connection from my subclass of the IoHandler on the server ?
Edit: in other words, how to get the relation between a IoSession and the principal once the handshake is done and the IoHandler.sessionOpened() is called.
It looks like you can get the SSLSession using SslFilter.getSslSession(...). Then, use SSLSession.getPeerCertificateChain() to get the client certificate chain. The actual client certificate is at position 0 in that array.
You don't work with the IoHandler directly for that, it is done using the SSLContext which you need to provide for the SslFilter constructor. If you take a look at the echo server example, you can see that the actual check is done in DefaultTrustManagerFactory. Personally I find that example a little too complicated, if I can find something simpler I will post it.
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!