Verify non-SSL certificate against default system keystore in Java - java

I have a X509 certificate (java.security.cert.X509Certificate) and I need to verify it. The trusted anchor is already in system default certstore ( jre\lib\security\cacerts ) but I could populate it into something else if needed. The certificate is not a SSL certificate.
My first idea was to use Java's trust manager as I am already using HTTPs with server certificates issued by the same CA. But it seems that this can be used only for SSL certifictaes as it provides only checkServerTrusted and checkClientTrusted. For sake of completness, this is my attempt:
X509TrustManager x509TrustManager=null;
trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore)null);
for (TrustManager trustManager : trustManagerFactory.getTrustManagers())
{
if (trustManager instanceof X509TrustManager)
{
x509TrustManager = (X509TrustManager)trustManager;
}
}
java.security.cert.X509Certificate x509 = .... get certificate ...
x509TrustManager.checkServerTrusted(new java.security.cert.X509Certificate[]{x509}, "RSA");
This does all I need but in addition it checks whether the certificate is valid for SSL server (or client if I use checkClientTrusted) and that obviously fails.
Is there any simple way how to specify the cert and/or key usage for the trust manager or at least a way how to disable the check (I can do it myself).
In the future I would like to use OCSP or CRL list to check the actual state of the certificate.

Related

Java - verifying certificate with system TrustStore

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.

SSLSocket: why do we need to do the handshake before accessing the server certificate?

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.

Validate X509 certificates using Java APis

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

Java access to intermediate CAs from Windows keystores?

I need to build a certificate chain on Windows, from an X.509 smart card cert through one or more intermediate CAs to a root CA. That's easy when the CA certs are in a JKS keystore, but I need to use the Windows keystores as well.
I can get the root CA cert from "Windows-ROOT", but I can't get to the "Intermediate Certification Authorities" keystore.
Has anyone done this?
Thanks!
The SunMSCAPI Cryptographic provider does only support two keystores: Windows-MY (personal certificate store) and Windows-ROOT (trusted authorities certificate store), thus I don't thinks it is possible to directly access to other windows certificate stores. However it may not be necessart since it seems that the Windows-MY keystore is able to build certificate chains with the certificates from other stores.
Here is a code snippet I use to test it:
KeyStore ks = KeyStore.getInstance("Windows-MY");
ks.load(null, null) ;
Enumeration en = ks.aliases() ;
while (en.hasMoreElements()) {
String aliasKey = (String)en.nextElement() ;
Certificate c = ks.getCertificate(aliasKey) ;
System.out.println("---> alias : " + aliasKey) ;
if (ks.isKeyEntry(aliasKey)) {
Certificate[] chain = ks.getCertificateChain(aliasKey);
System.out.println("---> chain length: " + chain.length);
for (Certificate cert: chain) {
System.out.println(cert);
}
}
If I add a single certificate with private key in the personal certificate store the chain length is 1. After adding the CA in the intermediate CA certificate store the I launch the program a second time and the chain length is now 2.
UPDATE (April, 2nd)
It is possible to programmatically add certificates in the Windows-MY and Windows-ROOT keystore with some limitations:
when adding a certificate in the Windows-ROOT the user is prompted for confirmation
all certificate added in the Windows-MY keystore is a TrustedCertificateEntry (from the keystore point of view, not the Windows point of view). The keystore seems to build the longest possible chain with all available certificates.
the certifcates with no associated private key are not visible in the Windows certificate store browser but it is possible to programmatically delete them.
Adding a certificate in a keystore is straightforward:
Certificate c = CertificateFactory.getInstance("X.509").generateCertificate(new FileInputStream("C:/Users/me/Downloads/myca.crt"));
KeyStore.TrustedCertificateEntry entry = new KeyStore.TrustedCertificateEntry(c);
ks.setEntry("CA1", entry , null);
Jcs had the answer, but I want to show some pseudocode so:
// load the Windows keystore
KeyStore winKeystore = KeyStore.getInstance("Windows-MY", "SunMSCAPI");
winKeystore.load(null, null);
// add the user's smart card cert to the keystore
winKeystore.setCertificateEntry(myAlias, userCertificate);
// build the cert chain! this will include intermediate CAs
Certificate[] chain = winKeystore.getCertificateChain(myAlias);
Windows cert chains aren't validated as they're built, but now you can do the usual thing of creating a CertPath and PKIXParameters and using them to validate the chain.
CertPathValidator certPathValidator = CertPathValidator.getInstance(CertPathValidator.getDefaultType());
certPathValidator.validate(certPath, params);

Java: How to show a dialog to let the user accept SSL certificates

I'm currently having a self signed certificate for my HTTPS webserver.
In my java program there is a SSLSocketFactory that will create a socket to the webserver. The default implementation of sun blocks the self signed certificate. With an own implementation of a X509TrustManager I can only check whether the date of the certificate is valid.
Is there any possibility to let the default implementation check the validity (date and hostname, ...), and if it fails to show a dialog to let the user accept this certificate?
Each code I found until now only disabled the ssl check and accepted every invalid certificate.
I haven't actually tried this, but why can't you implement your own trust manager, which first delegates to the default trust manager to check if the certificate is valid and if not, asks the user if he still wants to accept the certificate?
You can initialize most of the security classes with null arguments to use default values. To obtain the default trust manager, you must get the available trust managers and choose the first one in the mgrs arrays to implement the X509TrustManager interface. Usually, the array will only contain one elment anyway.
TrustManagerFactory trustmanagerfactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustmanagerfactory.init((KeyStore)null);
TrustManager[] mgrs = trustmanagerfactory.getTrustManagers();
After you've wrapped the default trust manager with your own extension, you have to initialize an SSL context and get a socket factory from it:
SSLContext sslContext=SSLContext.getInstance("SSL","SunJSSE");
sslContext.init(null, new TrustManager[] {myTm}, null);
SSLSocketFactory sf = sslContext.getSocketFactory();
Then use this socket factory to create new client sockets or pass it to HttpsURLConnection.setDefaultSSLSocketFactory to use the https protocol in URLs with your own trust manager.

Categories

Resources