Received fatal alert: unknown_ca while mutual auth

2019-04-15 16:19发布

问题:

I'm working on a web service client which need to communicate with a server who has mutual authentication enabled.

I've done the following steps.

  1. Added the server certificate(self signed) to a trust store and set it to the HttpsURLConnection using theSSLSocketFactory.
  2. Gave the client certificate(CA signed) to the server admin to add it to their trusted store.
  3. Created a keystore by importing the client certificate and created a keymanager to `HttpsURLConnection'.

When I try to run this sample I'm getting an exception saying

org.springframework.ws.soap.axiom.AxiomSoapMessageException: Could not write message to OutputStream: java.net.SocketException: Software caused connection abort: recv failed; nested exception is javax.xml.stream.XMLStreamException: java.net.SocketException: Software caused connection abort: recv failed
        at org.springframework.ws.soap.axiom.AxiomSoapMessage.writeTo(AxiomSoapMessage.java:261)
        at org.springframework.ws.transport.AbstractWebServiceConnection.send(AbstractWebServiceConnection.java:45)
        at org.springframework.ws.client.core.WebServiceTemplate.sendRequest(WebServiceTemplate.java:586)
        at org.springframework.ws.client.core.WebServiceTemplate.doSendAndReceive(WebServiceTemplate.java:549)
        at org.springframework.ws.client.core.WebServiceTemplate.sendAndReceive(WebServiceTemplate.java:502)
        at org.springframework.ws.client.core.WebServiceTemplate.sendAndReceive(WebServiceTemplate.java:480)
        at test.SamlTest.request(SamlTest.java:29)
        at test.SamlTest.main(SamlTest.java:63)
    Caused by: javax.xml.stream.XMLStreamException: java.net.SocketException: Software caused connection abort: recv failed
        at com.sun.xml.internal.stream.writers.XMLStreamWriterImpl.writeStartDocument(Unknown Source)
        at org.apache.axiom.soap.impl.llom.SOAPEnvelopeImpl.internalSerialize(SOAPEnvelopeImpl.java:193)
        at org.apache.axiom.om.impl.llom.OMElementImpl.internalSerialize(OMElementImpl.java:756)
        at org.apache.axiom.soap.impl.llom.SOAPMessageImpl.internalSerialize(SOAPMessageImpl.java:71)
        at org.apache.axiom.om.impl.llom.OMDocumentImpl.internalSerialize(OMDocumentImpl.java:324)
        at org.apache.axiom.om.impl.llom.OMDocumentImpl.serialize(OMDocumentImpl.java:375)
        at org.springframework.ws.soap.axiom.AxiomSoapMessage.writeTo(AxiomSoapMessage.java:252)
        ... 7 more
    Caused by: java.net.SocketException: Software caused connection abort: recv failed
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.read(Unknown Source)
        at com.sun.net.ssl.internal.ssl.InputRecord.readFully(Unknown Source)
        at com.sun.net.ssl.internal.ssl.InputRecord.readV3Record(Unknown Source)
        at com.sun.net.ssl.internal.ssl.InputRecord.read(Unknown Source)
        at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(Unknown Source)
        at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(Unknown Source)
        at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(Unknown Source)
        at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(Unknown Source)
        at sun.net.www.protocol.https.HttpsClient.afterConnect(Unknown Source)
        at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(Unknown Source)
        at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(Unknown Source)
        at sun.net.www.protocol.https.HttpsURLConnectionImpl.getOutputStream(Unknown Source)
        at org.springframework.ws.transport.http.HttpUrlConnection.getRequestOutputStream(HttpUrlConnection.java:81)
        at org.springframework.ws.transport.AbstractSenderConnection$RequestTransportOutputStream.createOutputStream(AbstractSenderConnection.java:101)
        at org.springframework.ws.transport.TransportOutputStream.getOutputStream(TransportOutputStream.java:41)
        at org.springframework.ws.transport.TransportOutputStream.write(TransportOutputStream.java:64)
        at com.sun.xml.internal.stream.writers.UTF8OutputStreamWriter.write(Unknown Source)
        at com.sun.xml.internal.stream.writers.UTF8OutputStreamWriter.write(Unknown Source)
        ... 14 more

What am I missing?

UPDATED

Please find the log after enabling jvm log

trigger seeding of SecureRandom
    done seeding SecureRandom
    %% No cached client session
    *** ClientHello, TLSv1
    RandomCookie:  GMT: 1274794757 bytes = { 250, 192, 120, 159, 84, 244, 96, 103, 128, 221, 36, 200, 229, 95, 84, 152, 179, 202, 161, 56, 95, 161, 234, 136, 128, 52, 45, 228 }
    Session ID:  {}
    Compression Methods:  { 0 }
    ***
    main, WRITE: TLSv1 Handshake, length = 73
    main, WRITE: SSLv2 client hello message, length = 98
    main, READ: TLSv1 Handshake, length = 74
    *** ServerHello, TLSv1
    Cipher Suite: SSL_RSA_WITH_RC4_128_MD5
    Compression Method: 0
    ***
    %% Created:  [Session-1, SSL_RSA_WITH_RC4_128_MD5]
    ** SSL_RSA_WITH_RC4_128_MD5
    main, READ: TLSv1 Handshake, length = 976
    *** Certificate chain
    chain [0] = [
    [
      Version: V3
      Subject: EMAILADDRESS=email@email.com, CN=email@email.com, OU=MyOU, O=xyz
      Signature Algorithm: MD5withRSA, OID = 1.2.840.113549.1.1.4

      Key:  Sun RSA public key, 1024 bits
      public exponent: 65537
      Validity: [From: Tue Jul 18 02:30:07 IST 2006,
                   To: Mon Jul 13 02:30:07 IST 2026]
      Issuer: EMAILADDRESS=email@email.com, CN=email@email.com, OU=MyOU, O=xyz
      SerialNumber: [   ]

    Certificate Extensions: 3
    [1]: ObjectId: 2.5.29.14 Criticality=false
    SubjectKeyIdentifier [
    KeyIdentifier [
    ]
    ]

    [2]: ObjectId: 2.5.29.35 Criticality=false
    AuthorityKeyIdentifier [
    KeyIdentifier [
    ]

    [EMAILADDRESS=email@email.com, CN=email@email.com, OU=MyOU, O=xyz]
    SerialNumber: [    ecbcae10 2ba4c279]
    ]

    [3]: ObjectId: 2.5.29.19 Criticality=false
    BasicConstraints:[
      CA:true
      PathLen:2147483647
    ]

    ]
      Algorithm: [MD5withRSA]
      Signature:

    ]
    ***
    main, SEND TLSv1 ALERT:  fatal, description = certificate_unknown
    main, WRITE: TLSv1 Alert, length = 2
    main, called closeSocket()
    main, handling exception: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    Exception in thread "main" javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Unknown Source)
        at com.sun.net.ssl.internal.ssl.SSLSocketImpl.fatal(Unknown Source)
        at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Unknown Source)
        at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Unknown Source)
        at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(Unknown Source)
        at com.sun.net.ssl.internal.ssl.ClientHandshaker.processMessage(Unknown Source)
        at com.sun.net.ssl.internal.ssl.Handshaker.processLoop(Unknown Source)
        at com.sun.net.ssl.internal.ssl.Handshaker.process_record(Unknown Source)
        at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(Unknown Source)
        at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(Unknown Source)
        at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(Unknown Source)
        at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(Unknown Source)
        at sun.net.www.protocol.https.HttpsClient.afterConnect(Unknown Source)
        at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(Unknown Source)
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream(Unknown Source)
        at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(Unknown Source)
        at com.arun.test.http.TiMutualAuthClient.main(TiMutualAuthClient.java:71)
    Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.validator.PKIXValidator.doBuild(Unknown Source)
        at sun.security.validator.PKIXValidator.engineValidate(Unknown Source)
        at sun.security.validator.Validator.validate(Unknown Source)
        at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.validate(Unknown Source)
        at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkServerTrusted(Unknown Source)
        at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkServerTrusted(Unknown Source)
        ... 13 more
    Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(Unknown Source)
        at java.security.cert.CertPathBuilder.build(Unknown Source)
        ... 19 more

Thank you

回答1:

Does the client keystore contain a private key entry? Not clear from your description. It is also not clear where the unknown_ca comes from: it doesn't show up in your stack trace. It would be best to run the client with -Djavax.net.debug=ssl,handshake and post the results.



回答2:

I think the problem was with the client certificate used by me. It has the following extension values set

Certificate Key Usage
Critical
Signing
Non-repudiation
Key Encipherment
Data Encipherment

Extended Key Usage
Not Critical
TLS Web Server Authentication (1.3.6.1.5.5.7.3.1)
TLS Web Client Authentication (1.3.6.1.5.5.7.3.2)

Once I created a self signed certificate without any extensions and used it as my client certificate, it worked fine.



回答3:

You need to put the public key of the CA that certified the user (or the public key of the user themselves if it is a self-certified key that they're using) into the server's keystore. Otherwise the server simply doesn't know whether the user's certificate is for real or is being presented by someone impersonating them. Having the certificate in there ahead of time allows the server to understand the identity and trust it (which the SSL protocol requires as part of preventing trivial man-in-the-middle attacks).