OPCFoundation / UA-Java-Legacy

This repository is provided by OPC Foundation as legacy support for an Java version for OPC UA.
https://github.com/OPCFoundation/UA-.NETStandard
Other
355 stars 227 forks source link

Problem in certificate thumbprint calcualtion #145

Closed d-rothe closed 6 years ago

d-rothe commented 6 years ago

Hi, I found a problem in the Java stack which prevents secured communication between a java-based client and a c++-based server (in my case). The problem appears only when certificate chains are used, like in the case of CA-signed certificates obtained from a GDS.

My java test client uses the OPC Foundation Java Stack version 1.3.343. My reference server creates an application certificate with sha256RSA signature. If I use the Java test client to connect to my reference server which has a self-signed certificate, establishing an encrypted connection works. However, after I use the OPC Foundation GDS to exchange the server certificate by one signed by a CA, connecting no longer works.

When the clients sends the OpenSecureChannel request (Frame 139 in wireshark file connect-certfailure3.pcapng), the server responds with Bad_CertificateInvalid. This error is shown in the client, as

  org.opcfoundation.ua.common.ServiceResultException: Bad_CertificateInvalid (0x80120000) "The certificate provided as a parameter is not valid."

The reason for the failure is also found in the wireshark log: Frame 139 OpenSecureChannel Message (client to server): ReceiverCertificateThumbprint: af00aa9711c28ee2ba053bdffa45dcc74cb6240a (hex) However, the correct thumbprint of the server certificate is (in my case) ‎74 64 eb 0b cc 76 96 14 6e 87 0f d2 2f 57 3b 78 7c c7 4a 34 (hex).

This thumbprint is not saved inside the certificate. Instead, it is calculated by the certificate viewing tool. I find that this thumbprint is shown by the Windows certificate info tool as well as by openssl, using the command

openssl x509 -inform der -in <cert> -fingerprint

So the thumbprint calculated by the UA C++ SDK (which is set up to use openssl) seems to be correct, and the thumbprint calculated by the OPC Foundation Java Stack is wrong in this case, and that the UA SDK needs to deny the connection since it receives an incorrect thumbprint from the java client.

The Java Stack implements the relevant thumbprint handling in the class org.opcfoundation.ua.transport.security.Cert. The thumbprint is calculated as SHA-1 hash in the function

org.opcfoundation.ua.utils.CertificateUtils.createThumbprint(byte[])

and is stored in the field Cert.encodedCertificateThumbprint.

In my case, using Eclipse debugger on the test client, I find that
byte[] serverencodedCertificateThumbprint = [-81, 0, -86, -105, 17, -62, -114, -30, -70, 5, 59, -33, -6, 69, -36, -57, 76, -74, 36, 10] (dec) = [ af, 00, aa, 97, 11, c2, 8e, e2, ba, 05,3b, df, fa, 45, dc, c7, 4c, b6, 24, 0a ] (hex)

By debugging the OPC Foundation Java Stack, I found following problem. The thumbprint is sometimes incorrectly calculated as SHA-1 hash over the certificate chain instead of just the certificate - see commented code below, which fixes the issue.

public Cert(byte[] data) 
throws ServiceResultException
{
    try {
        encodedCertificate = data;
        certificate = CertificateUtils.decodeX509Certificate(data);
        // original code. The problem here is that data may be a certificate chain instead of a certificate.
        // Then, thumb print is incorrectly calculated as SHA-1 over chain.  
        // encodedCertificateThumbprint = CertificateUtils.createThumbprint(encodedCertificate);
        // my version
        encodedCertificateThumbprint = createThumbprint();
    } catch (CertificateNotYetValidException ce) {
        throw new ServiceResultException(StatusCodes.Bad_CertificateTimeInvalid, ce);
    } catch (CertificateExpiredException ce) {
        throw new ServiceResultException(StatusCodes.Bad_CertificateTimeInvalid, ce);
    } catch (CertificateParsingException ce) {
        throw new ServiceResultException(StatusCodes.Bad_CertificateInvalid, ce);
    } catch (CertificateException ce) {
        throw new ServiceResultException(StatusCodes.Bad_CertificateInvalid, ce);
    }
}

public Cert(X509Certificate certificate) throws CertificateEncodingException
{
    encodedCertificate = certificate.getEncoded();
    this.certificate = certificate;
    // original code. here, it should be ok.
    // encodedCertificateThumbprint = CertificateUtils.createThumbprint(encodedCertificate);
    // my version
    encodedCertificateThumbprint = createThumbprint();
}

private byte[] createThumbprint() throws CertificateEncodingException
{
    byte[] data;
    data = getCertificate().getEncoded(); // this is only a single certificate, even if constructor input was a chain.
    try {
        MessageDigest shadigest = MessageDigest.getInstance("SHA1");
        return shadigest.digest(data);
    } catch (NoSuchAlgorithmException e) {
        throw new Error(e);
    }
}

connect-certfailure3b.zip

bjakke commented 6 years ago

Hi,

Thanks for the issue and suggested fixes. In practice this is more of a missing feature than maybe a direct bug, as receiving certificate chains to my knowledge is not supported at all at the moment. Also at least personally I have not been aware of any server that would actually try to send one (they would send the last of the chain and clients are expected to have all middle->root steps).

Even the option to actually receive more than a single certificate is quite hidden in the spec, as most req/res and data structures only mention "ApplicationInstanceCertificate", which is a single ByteString of the certificate, without any references to chains. I remember reading somewhere that it should be possible to append the chain within the ByteString, but I'm currently unable to find the exact spec location where this is mentioned.

So there are 2 issues:

  1. Calculating the thumbprint correctly
  2. Actually parsing all the certs in the chain and using them for validation (currently all the chain certs must already be available from CertificateStore.getTrustedCerts for the DefaultCertificateValidator)
d-rothe commented 6 years ago

Hi, the spec has a very general statement about certificate chains, saying that in every place where a certificate is provided as a ByteString, one may also provide a chain, appending issuer certificates. In most cases, providing the full chain will be the best way to handle certificates. In 1.04 Part 6, 6.2.3 Certificate Chains it says: "applications have the option of including a partial or complete chain whenever they pass a Certificate to a peer during the SecureChannel negotiation and during the CreateSession/ActivateSession handshake. All OPC UA applications shall accept partial or complete chains in any field that contains a DER encoded Certificate." Therefore, the client must be able to distinguish between certificates and chains. In my case, the issue appears because the C++ UA server provides a certificate chain, and the client takes it as a simple certificate, leading to the incorrect thumbprint. The fix that I posted just handles the thumbprint calculation. But I a agree that full support of certificate chains is another issue, which would indeed be a great improvement.

bjakke commented 6 years ago

It would seem that spec part is added only in 1.04 (Part 6, 6.2.3). However there is a small mention in 1.03 in Part 6 section 6.7.2 Table 27.

The included fix in 1.3.344 handles only thumbprint calculation correctly, cert chains will be handled in a separate, future, issue.