Implementing X509TrustManager
Asked Answered
I

1

6

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];
    }

}
Idler answered 24/9, 2018 at 20:58 Comment(1)
Could the actual handshake be occurring somewhere else?Ambages
P
5

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.)

Paresh answered 25/9, 2018 at 1:32 Comment(7)
Thank you kindly for your time and your answer! My setup is that both, client and server, have a keystore with their own keypair inside and the other party's self-signed certificate. Both use the same KeyManager and TrustManager - the custom KeyManager returns their own key, the TrustManager returns the other party's certificate. I enabled needClientAuth (on server) and used the default SSL implementation on the client, and the connection still works (which I don't want - only the cert I have saved in the keystore for the other party should be accepted). What am I still doing wrong?Idler
I just figured out that, when I delete the client's certificate from the server's keystore, the handshake fails iff needClientAuth is set. So, that works, it's just that getAcceptedIssuers doesn't work like it should or I still don't understand it, because apparently, the delegate (default X509[Extended]TrustManager) accepts the client's certificate despite it being self-signed and not in getAcceptedIssuers.Idler
As I said, getAcceptedIssuers is not used to decide whether to trust a cert. JSSE calls .check{Client,Server}Trusted and if you use the standard trustmanager initialized from a keystore file, that call will trust any cert that correctly chains to an anchor in the given keystore (and not expired, or revoked if you enable that option, and for server cert only matches the expected hostname if you enable that) and not otherwise. Since you do have the client cert in the trustmanager, checkClientTrusted does accept it and getAcceptedIssuers is irrelevant.Paresh
Also to be clear, keystore and truststore are diffferent concepts, though both are typically stored in keystore format and can be in the same keystore file. The server's keystore is the privatekey AND cert/chain combination that it sends to client and uses to authenticate itself (the server); the server's truststore contains the cert(s) and NOT privatekey(s) that will or can be used to validate the cert and key used by client to authenticate itself (the client).Paresh
@Paresh How do I supply different lists to trust anchors and accepted issuers similar to SSLCADNRequestFile and SSLCACertificateFile?Bummer
@Michael-O: IME easiest create a wrapper, similar to the Q, either delegating checkClientTrusted to a standard trustmanager using a store equivalent to SSLCACertificateFile (and/or Path) and getAcceptedIssuers to a different one using a store equivalent to SSLCADNRequestFile/Path; or since the latter logic is pretty simple, just build the list of certs (probably from a store) and return it, and only delegate the former (and more complicated)Paresh
@Paresh That was my idea too. Unfortunately, this isn't so easily possilble within Tomcat unless one changes Tomcat code. It would actually sufficient to use the same trust store, but filter accepted issues by EKU 1.3.6.1.5.5.7.3.2.Bummer

© 2022 - 2024 — McMap. All rights reserved.