I opted to use acme4j to create a letsencrypt certificate. So far it seems to have worked perfectly and I have some java code that creates a registration, responds to a challenge an ultimately presents me with a x509 certificate for my domain (along with a 'certificate chain'). The code is integrated nicely into my java application and doesn't require any downtime for certificate renewal. Awesome.
From here I'm a bit stuck. My application is a just a main app that has an embedded undertow webserver that I instantiate programatically. In order to create an https listener I need to create an SSLContext object. I've saved the x509 certificate that I got from letsencrypt to disk so it can be reused:
...
X509Certificate x509 = cert.download();
Path pemFile = pathTo(domain + ".pem");
try (Writer writer = Files.newBufferedWriter(pemFile); JcaPEMWriter jcaPEMWriter = new JcaPEMWriter(writer)) {
jcaPEMWriter.writeObject(x509);
}
And then on start up my application reloads that certificate and passes it into the undertow web server:
CertificateFactory cf = CertificateFactory.getInstance("X.509");
FileInputStream finStream = new FileInputStream(certFile.toFile());
X509Certificate x509Certificate = (X509Certificate)cf.generateCertificate(finStream);
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
keyStore.setCertificateEntry("someAlias", x509Certificate);
TrustManagerFactory instance = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
instance.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, instance.getTrustManagers(), null);
SSLContext.setDefault(sslContext);
Undertow.Builder builder = Undertow.builder();
builder.addHttpsListener(httpsPort, ipAddress, sslContext);
The app starts, can't see any errors or warnings until I try and hit an https endpoint where Chrome just shows _ERR_CONNECTION_CLOSED_. I turned on -Djavax.net.debug=all to try and see whats going on:
%% Initialized: [Session-12, SSL_NULL_WITH_NULL_NULL]
XNIO-1 task-12, fatal error: 40: no cipher suites in common
javax.net.ssl.SSLHandshakeException: no cipher suites in common
%% Invalidated: [Session-12, SSL_NULL_WITH_NULL_NULL]
XNIO-1 task-12, SEND TLSv1.1 ALERT: fatal, description = handshake_failure
XNIO-1 task-12, WRITE: TLSv1.1 Alert, length = 2
XNIO-1 I/O-2, fatal: engine already closed. Rethrowing javax.net.ssl.SSLHandshakeException: no cipher suites in common
XNIO-1 I/O-2, called closeInbound()
XNIO-1 I/O-2, fatal: engine already closed. Rethrowing javax.net.ssl.SSLException: Inbound closed before receiving peer's close_notify: possible truncation attack?
2016-09-08 08:34:46,861 DEBUG [io] - UT005013: An IOException occurred
java.io.IOException: javax.net.ssl.SSLException: Inbound closed before receiving peer's close_notify: possible truncation attack?
I'm trying to come up with a pure java solution here. Something that is repeatable, in code and can be tested and checked in to source control. I want to avoid having to do any out-of-jvm machine level set up if possible.
After lots of hackery and reading, it seems like I need to use the keytool and some combination of the certificate I was issued, along with the certificate chain I was issued, along with the root certificate and some/none/all of the intermediate letsencrypt certificates! Seriously?
I tried following the instructions here from the section titled "7.3.1.3. Using an existing Certificate" but only to end up with exactly the same error.
Any help would be greatly appreciated.
An SSL/TLS server (in any language) requires a PRIVATE KEY AND certificate (and nearly always a cert chain) not just a certificate. And a Java SSL/TLS server needs that privatekey+certchain in a KeyManager not a TrustManager -- any advice that recommended you set only a TrustManager for an SSL/TLS server is totally incompetent. The section 7.3.1.3 you link to makes no sense, although the next section 7.3.1.4, although it claims a bug I have never seen and I have used just about every version of OpenSSL, does describe almost the right way to convert OpenSSL key+cert to Java, which matches the earlier sections which described creating keys and certs in OpenSSL format. But you don't have OpenSSL format.
That acme4j page
correctly says you should "generate a separate pair of keys" -- pair meaning private and public -- which the earlier "How to Use" page told you how to do with their KeyPairUtils, then generate a CSR and send it in, and get the certificate and chain. It specifies a single call
X509Certificate[] chain = cert.downloadChain()
which might be what you need if LetsEncrypt is clever; look at chain[0].getSubjectX500Principal() and see if it's you. If not you probably need to do both download() and downloadChain() and put them together in one array with your cert first:
X509Certificate[] fixchain = new X509Certificate [chain.length+1];
fixchain[0] = mycert; System.arraycopy (chain,0, fixchain,1, chain.length);
chain = fixchain; // for simplicity
Once you have the cert chain AND the key pair, do something like
char[] password = /* some value, should be secure if used for file,
if used only in memory doesn't really matter */
KeyStore ks = KeyStore.getInstance("jks"/*or default if you don't care*/);
ks.load (null); // above line and this same as you have now
ks.setKeyEntry ("alias", keypair.getPrivateKey(), password, chain);
// this line different -- KeyEntry not CertificateEntry
// if you want to save in a file for reuse
try( OutputStream os = new FileOutputStream ("blah") ){ ks.store (os, password); }
// if you want to save somewhere else, extrapolate
// if/when you want to run server
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, password); // same password as above (or when stored)
// MAYBE TrustManager if you want to require certs FROM CLIENTS
// in which case the certs you trust probably shouldn't be limited to LetsEncrypt
SSLContext ctx = SSLContext.getInstance("TLS";
ctx.init(kmf.getKeyManagers(), null /*or tmf.getTrustManagers()*/, null);
SSLContext.setDefault (ctx); // or otherwise use ctx
FYI if a certificate had actually been what you needed to store and read, you don't need to convert it to PEM unless you want people to cut&paste etc. Java CertificateFactory has handled both DER and PEM for at least a decade.
Related
I am using the method below to read certificates.
KeyStore ks = KeyStore.getInstance("pkcs12");
ks.load(in, password);
String alias = ks.aliases().nextElement();
Certificate[] chain = ks.getCertificateChain(alias);
...
This method is very functional, however I came across a problem in a certificate that does not load the full string of the certificate.
For example1:
When reading certificate 1 the statement below returns 4 strings
For example2:
When reading certificate 2 the statement below returns only one string
Does anyone have any idea what might be happening?
EDIT
Trying to explain better, this certificate is used to connect to a WS. During the communication process WS returns the following rejection Connection has been shutdown: javax.net.ssl.SSLHandshakeException: Received fatal alert: unknown_ca
The connection to this WS requires the complete certification chain presentation.
As mentioned below the certificate shows the failure in JAVA, but it works perfectly in .NET, correctly displaying the certification chain
According to API documentation:
Returns:
the certificate chain (ordered with the user's certificate first followed by zero or more certificate authorities), or null if the given alias does not exist or does not contain a certificate chain
This means that either, there are no more certificates in the chain (self-signed certificate) or required chain elements are not included in PKCS#12 message and are not available through other sources.
Premise: I have a certificate and I want to verify that the system 'trusts' this certificate (signed by a trusted root CA by Java / Operating System)
I have found some varying solutions on how to accomplish this.
Option 1:
Use SSL classes to derive trust.
TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmfactory.init((KeyStore) null);
for (TrustManager trustManager : tmfactory.getTrustManagers()) {
if (trustManager instanceof X509TrustManager) {
try {
((X509TrustManager) trustManager).checkClientTrusted(new X509Certificate[] {new JcaX509CertificateConverter().getCertificate(holder)}, "RSA");
System.out.println("This certificate is trusted by a Root CA");
} catch (CertificateException e) {
e.printStackTrace();
}
}
}
Since this approach relies heavily on SSL classes (which are not needed by the current project) we are looking for alternatives.
Option 2:
Load Java's cacertsfile into a keystore and check each 'most-trusted' certificate against my certificate for equality.
String filename = System.getProperty("java.home") + "/lib/security/cacerts".replace('/', File.separatorChar);
FileInputStream is = new FileInputStream(filename);
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
String password = "changeit";
keystore.load(is, password.toCharArray());
// This class retrieves the most-trusted CAs from the keystore
PKIXParameters params = new PKIXParameters(keystore);
// Get the set of trust anchors, which contain the most-trusted CA certificates
Set<X509Certificate> rootCertificates = params.getTrustAnchors().parallelStream().map(TrustAnchor::getTrustedCert).collect(Collectors.toSet());
return rootCertificates.contains(holderX509);
The problem with this approach is that it requires a password to verify integrity of the JKS encoded file. While the SSL one seemingly does not (or rather uses System.getProperty("javax.net.ssl.trustStorePassword") which again is heavily tied to SSL.
Question: Does there exist a solution that is in between manually loading certificates from a file and pure SSL? I feel as if there should be some class that I can call to simply verify the system trust of a certificate without having to jump through a couple hoops.
After reading Beginning Cryptography With Java by David Hook I have produced the following example to verify a certificate chain (which accomplishes the original goal of using the system truststore to verify Root CA's)
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509", new BouncyCastleProvider());
InputStream is = new ByteArrayInputStream(some bytes in an array);
CertPath certPath = certificateFactory.generateCertPath(is, "PKCS7"); // Throws Certificate Exception when a cert path cannot be generated
CertPathValidator certPathValidator = CertPathValidator.getInstance("PKIX", new BouncyCastleProvider());
PKIXParameters parameters = new PKIXParameters(KeyTool.getCacertsKeyStore());
PKIXCertPathValidatorResult validatorResult = (PKIXCertPathValidatorResult) certPathValidator.validate(certPath, parameters); // This will throw a CertPathValidatorException if validation fails
This also accomplishes the goal of not having to use SSL classes - instead Java security classes / algorithms are used.
Short of downloading a third-party library, there probably isn't another alternative.
Why are you trying to avoid the "SSL" library? It's part of the standard library and so puts no burden on your program.
In any case, certificate verification is a big part of SSL. I doubt anyone's gone to the trouble of creating a library that does so without also implementing some substantial subset of the SSL protocol. There's just no real reason to do so.
I'm connecting through a SSLSocket to a distant host which is using a certificate for the handshake. As we don't use the default JVM truststore with all the certificate authorities, i need to add the remote host certificates to my truststore.
How can i get the certificates that i should trust from the SSLSocket? It seems do retrieve them i need to use the SSLSession which seems to require the handshake.
Why do we need to perform the handshake to be able to retrieve the certificates?
Is there any tool that permits to extract the remote host certificates used?
Actually certificates are presented during the handshake, so that the server can identify itself, and the eventually the same for the client.
When you do:
SSLContext context = SSLContext.getInstance("TLS");
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
[...]
SavingTrustManager tm = new SavingTrustManager(defaultTrustManager);
SSLSocket socket = (SSLSocket)factory.createSocket(host, port);
try {
socket.startHandshake();
socket.close();
} catch (SSLException e) {
e.printStackTrace(System.out);
}
if you don't get an exception on startHandshake(), it means the certificate is already trusted for some reason (directly present in keystore, of signed by a trusted entity).
Exception occurred or not, you can access the downloaded chain:
X509Certificate[] chain = tm.chain;
if (chain == null) {
// error in downloading certificate chain
return;
}
// loop through chain
for (int i = 0; i < chain.length; i++) {
X509Certificate cert = chain[i];
[....]
}
With the X509Certificate object instance, you can actually update your k-ieth keystore:
X509Certificate cert = chain[k];
String alias = host + "-" + (k + 1);
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
[...]
ks.setCertificateEntry(alias, cert);
OutputStream out = new FileOutputStream("jssecacerts");
ks.store(out, passphrase);
out.close();
Look here for the complete sample.
Alternatively, another maybe safer way to download the certificate for a server you trust, is using openssl command:
# openssl s_client -showcerts -connect $SERVER:$PORT 2>&1 | \
sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' >/tmp/$SERVERNAME.cert
then import it as usual with keytool.
Normally, you shouldn't get the certificate you should trust from an SSLSocket, instead, it should be a configured setting that you obtained independently, as a reference for what you want to trust.
What you seem to want to do is to get the certificate for the first connection, hoping that that connection wasn't intercepted, and then use this as a reference for subsequent connections (similar to what's commonly done with SSH, when you don't necessarily know the server key's fingerprint on the first connection, but check that you get the same later).
Security-wise, this isn't ideal because the initial connection may be intercepted by a MITM attacker (which would make all subsequent connections vulnerable), but that's certainly a way to mitigate the risks. Ideally, you should compare that certificate with a known reference you've obtained some other way.
You can access the remote certificate during the handshake using a custom X509TrustManager (or you can disable trust verification with it and get the certificate later), which you can then use to initialise an SSLContext, from which you can obtain your SSLSocketFactory. It's generally a bad idea to disable trust verification in a trust manager (since it opens the connection to MITM attacks), but it can be acceptable for this purpose. You may be interested in the InstallCert utility, which should do more or less what you're after.
Why do we need to do the handshake before accessing the server
certificate?
This is done during the handshake, because the purpose of the SSL/TLS socket API is to provide the application layer with a socket it can consider secure and use more or less as a normal socket at that stage. Typically, for most uses of JSSE (or generally other SSL/TLS stacks), as an application developer using that stack, you don't want to have to do the verification explicitly. Checking the certificate during the handshake is also recommended as part of the TLS specification:
Upon receipt of the server hello done message, the client SHOULD
verify that the server provided a valid certificate, if required
and check that the server hello parameters are acceptable.
Oh wise and noble Oracle,
I'm adding SSL to a TCP client I've written on my Android phone. I can
successfully connect to servers with properly signed certificates, and I can
connect to self-certifying hosts by cooking up a TrustManager implementation
that always thinks everything is fine.
I now have a decorator TrustManager capturing the certificates (before
delegating to its decoratee) for self-certifying hosts and presenting them for
my breathless perusal, but what I can't work out is how to implement ssh's
behaviour of warning that a host is unknown and offering to remember it for
next time - and doing so.
I presumed all I needed was to store the public key - as ssh does with
known_hosts - and re-represent it, but with this code and 'sslTrust' holding
the public key:
TrustManagerFactory tmf = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null); // initialise!
ks.setKeyEntry("dbentry", Base64.decode(sslTrust, Base64.NO_WRAP), null);
tmf.init(ks);
tms = tmf.getTrustManagers();
ss.stm = new SnoopyTrustManager((X509TrustManager) tms[0]);
// ...
SLContext context = SSLContext.getInstance("SSL");
context.init(null, new TrustManager[] { ss.stm } , null);
ss.factory = context.getSocketFactory();
// ...
SocketFactory factory = ss.getFactory();
mSocket = factory.createSocket(host, port);
attempting to establish a connection results in
SSLHandshakeException: InvalidAlgorithmParameterException: trustAnchors.isEmpty()
which is fair enough: I don't know how to cook things up from the certificate
offered by the remote server. I'm also fairly sure this isn't how I tell a
TrustManager about a remote server's public key anyway.
Since the site is self-certifying, I imagine could probably just verify that
the public keys match in a trivial TrustManager, but I'd like to understand
how this 'should' be done - adding a CA on a per-connection basis, since
I won't trust that CA for anything else.
You need to use your own trust store on pre-ICS version, and add the serer's certificates to it on first error. Subsequent connections will load it from the trust store and thus trust the remote certificate. This is not a complete solution, but here's one way to do it (code on Github), along with some discussion:
http://nelenkov.blogspot.jp/2011/12/using-custom-certificate-trust-store-on.html
I am trying to validate a certificate against java key store and this is the code I am using is as below. If it completes succesfully then I assume the validation has gone through correctly, else if an exception is thrown, then the validation fails.
My concern is:
Is the code below sufficient to validate a certificate? As in is there something I am missing here (Like checking the data signed by the computer sending me the certificate?)?
2. Should the signature contained within the certificate be verified? If yes, how?
Thanks in advance for the response!
pradeep
// To check the validity of the dates
cert.checkValidity();
//Check the chain
CertificateFactory cf = CertificateFactory.getInstance("X.509");
List<X509Certificate> mylist = new ArrayList<X509Certificate>();
mylist.add(cert);
CertPath cp = cf.generateCertPath(mylist);
PKIXParameters params = new PKIXParameters(getTrustStore());
params.setRevocationEnabled(false);
CertPathValidator cpv =
CertPathValidator.getInstance(CertPathValidator.getDefaultType());
PKIXCertPathValidatorResult pkixCertPathValidatorResult =
(PKIXCertPathValidatorResult) cpv.validate(cp, params);
Normally, a certificate will be issued by an intermediate issuing authority, not a "root" authority (which is all that should be in your trust store). Most protocols encourage sending a "chain" of certificates, not just the entity's certificate.
You should add all of the intermediate certs so that a complete chain can be formed.
In order to be certain that the certificate is still valid, you should not disable revocation checks. If you don't want to retrieve a CRL (which can be large), the issuer may offer OCSP support. But, this has to be enabled in the Java runtime by setting certain system properties.
If the path validator returns successfully, you don't need to check anything else. If the certificate is not valid, an exception will be raised.
Also, an explicit check on the validity date is unnecessary. This occurs during validation (using the current time, unless you specify a time via the PKIXParameters).
For a more extensive discussion of validation, including sample code, see a previous answer of mine.
If you're happy with the default trust settings (as they would be used for the default SSLContext), you could build an X509TrustManager independently of SSL/TLS and use if to verify your certificate independently.
It would look like this:
TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore)null);
// you could use a non-default KeyStore as your truststore too, instead of null.
for (TrustManager trustManager: trustManagerFactory.getTrustManagers()) {
if (trustManager instanceof X509TrustManager) {
X509TrustManager x509TrustManager = (X509TrustManager)trustManager;
x509TrustManager.checkServerTrusted(...);
}
}
(You should also check the server's identity and the certificate match, see RFC 6125 (Representation and Verification of Domain-Based Application Service Identity within Internet Public Key Infrastructure Using X.509 (PKIX) Certificates in the Context of Transport Layer Security (TLS)).)
What you are doing here is verifying if a certificate (in your example cert) has been signed (directly) by any of the trusted CA's in the truststore.
Additionally you check for expiration but no revocation checking is performed.
So if the cert has not been signed by any of the trusted CA's you will get an exception.
So the code is sufficient to verify if cert has been signed by any of the trusted CAs
If you are refering to server authentication, then the code in the post is not sufficient.
This code just verifies that a specific certificate is signed by a trusted CA.
You have no indication though if the "entity" that send you this certificate is actually the owner of the certificate (i.e. they own the private key associated with this certificate).
This is part of the SSL authentication, where e.g. the client sends the ClientKeyExchange message encrypted with the remote server's public key and is certain that if the other party is fake then it will not be possible to decrypt the message