2

I'm trying to set up stunnel to provide a TLS wrapper to an HTTP service that doesn't natively support TLS. I have this working well without using TLS client certificates.

When adding the client certificate configuration:

CAfile = /path/to/trusted.crt
verify = 4

I can't seem to connect using openssl s_client. (Note that I'm cheating a bit -- using my TLS server's certificate as the client cert as well, but the cert says it has an extension for X509v3 Extended Key Usage: TLS Web Client Authentication, TLS Web Server Authentication, so I figure it should work.)

$ openssl s_client -state -connect [my-host-name]:[port] -cert [my-host-name].crt -key [my-host-name].key-nopass
CONNECTED(00000003)
SSL_connect:before/connect initialization
SSL_connect:SSLv2/v3 write client hello A
SSL_connect:SSLv3 read server hello A
depth=2 C = IL, O = StartCom Ltd., OU = Secure Digital Certificate Signing, CN = StartCom Certification Authority
verify return:1
depth=1 C = IL, O = StartCom Ltd., OU = StartCom Certification Authority, CN = StartCom Class 1 DV Server CA
verify return:1
depth=0 CN = [my-hostname]
verify return:1
SSL_connect:SSLv3 read server certificate A
SSL_connect:SSLv3 read server key exchange A
SSL_connect:SSLv3 read server certificate request A
SSL_connect:SSLv3 read server done A
SSL_connect:SSLv3 write client certificate A
SSL_connect:SSLv3 write client key exchange A
SSL_connect:SSLv3 write certificate verify A
SSL_connect:SSLv3 write change cipher spec A
SSL_connect:SSLv3 write finished A
SSL_connect:SSLv3 flush data
SSL3 alert read:fatal:unknown CA
SSL_connect:failed in SSLv3 read server session ticket A
140391435724640:error:14094418:SSL routines:SSL3_READ_BYTES:tlsv1 alert unknown ca:s3_pkt.c:1293:SSL alert number 48
140391435724640:error:140790E5:SSL routines:SSL23_WRITE:ssl handshake failure:s23_lib.c:184:
---
Certificate chain
 0 s:/CN=[my-host-name]
   i:/C=IL/O=StartCom Ltd./OU=StartCom Certification Authority/CN=StartCom Class 1 DV Server CA
 1 s:/C=IL/O=StartCom Ltd./OU=StartCom Certification Authority/CN=StartCom Class 1 DV Server CA
   i:/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority
 2 s:/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority
   i:/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority
---
Server certificate
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
subject=/CN=[my-host-name]
issuer=/C=IL/O=StartCom Ltd./OU=StartCom Certification Authority/CN=StartCom Class 1 DV Server CA
---
Acceptable client certificate CA names
/C=[the actual client cert I'd like to use]
/CN=[my-host-name] (for testing)
Server Temp Key: ECDH, prime256v1, 256 bits
---
SSL handshake has read 5620 bytes and written 1937 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES256-SHA
Server public key is 3072 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
    Protocol  : TLSv1
    Cipher    : ECDHE-RSA-AES256-SHA
    Session-ID:
    Session-ID-ctx:
    Master-Key: [...]
    Key-Arg   : None
    Krb5 Principal: None
    PSK identity: None
    PSK identity hint: None
    Start Time: 1465393332
    Timeout   : 300 (sec)
    Verify return code: 0 (ok)
---

(The connection is closed at this point.)

If I set verify = 0 in the stunnel configuration, the connection is successful and I can make a request. If I set verify = 1 in the stunnel configuration, the connection is dropped if I provide a client certificate, but accepted if I do not provide one.

So it looks like something is wrong with the certificate itself (for testing, it's the identical certificate being used for the TLS server, so I don't think there is anything wrong with it), or my configuration for how to verify the certificate.

I would like to only verify certificates which actually appear in my trusted.crts file. These debug lines lead me to believe that I have specified the certificates correctly (this is output from the client):

Acceptable client certificate CA names
/C=[the actual client cert I'd like to use]
/CN=[my-host-name] (for testing)

Since my certificate for testing appears in that list, I expect that it should, well, be acceptable.

I'm not sure what I'm missing.

In /var/log/secure, stunnel is logging this:

Jun  8 13:58:59 ip-10-0-0-176 stunnel: LOG5[17497:139964729063168]: Service [mysvc] accepted connection from 52.203.5.20:57224
Jun  8 13:58:59 ip-10-0-0-176 stunnel: LOG4[17497:139964729063168]: CERT: Verification error: unable to get local issuer certificate
Jun  8 13:58:59 ip-10-0-0-176 stunnel: LOG4[17497:139964729063168]: Certificate check failed: depth=0, /CN=[my-host-name]
Jun  8 13:58:59 ip-10-0-0-176 stunnel: LOG3[17497:139964729063168]: SSL_accept: 140890B2: error:140890B2:SSL routines:SSL3_GET_CLIENT_CERTIFICATE:no certificate returned
Jun  8 13:58:59 ip-10-0-0-176 stunnel: LOG5[17497:139964729063168]: Connection reset: 0 byte(s) sent to SSL, 0 byte(s) sent to socket

It looks like stunnel is trying to verify the certificate against a CA, rather than directly-trusting the certificate that is in my trusted.crts file.

Any suggestions?

UPDATE

If I leave verify = 4 in the stunnel configuration file but place the client certificate's parent certificate (the one from StartCom) into the trusted.crts file, I can connect using my test certificate. If I change to using verify = 3 with both the test cert and its signer in the trusted.crts file, I again cannot connect.

Is there a way to give stunnel the exact certificate(s) I am willing to accept from clients? The documentation suggests that using verify = 3 or verify = 4 is the way to do that, but I can't seem to make it work in the way I think I should be able to.

Christopher Schultz
  • 1,056
  • 1
  • 11
  • 20

1 Answers1

2

This answer is not tested as I just start using stunnel, but from documentation I guess:

a) you should drop the verify option, as it is either not complete or not that clear as verifyChain and verifyPeer options and it is obsolete:

verify = LEVEL
       verify the peer certificate

       This option is obsolete and should be replaced with the verifyChain
       and verifyPeer options.

b) verifyChain should check your certificate chain by either using the given in the CAfile or in a file within CApath. For a chain with intermediate certificates all intermediate certificates should be valid against the CA root, that has to be contained in the CA collections (file or path).

c) verifyPeer checks if the certificate itself should be contained in the CAfile or CApath.

Note that actually, without reference to extensions, a root CA certificate is simply just a self-signed certificate, with the CA flag set to yes. Of course such CA certificates are not contained in the set of trusted CAs commonly distributed with browsers or within the operating system. In linux, the CApath, where openssl stores its trusted certificates authorities (/etc/ssl/certs) should do to check against the standard set of trusted certificates. Of course you can add your own CA there too.

The terms /C and /CN are part of the certificates subject, where /C is Country and /CN is the common name. See the output of

openssl x509 -noout -subject </etc/ssl/certs/Deutsche_Telekom_Root_CA_2.pem
subject=C = DE, O = Deutsche Telekom AG, OU = T-TeleSec Trust Center, CN = Deutsche Telekom Root CA 2

as an example. Again: These root certificates are only usefull if you are using verifyChain=yes. If the option is verifyPeer=yes the certificate itself is checked, if you set both to yes, both, the root and the peer certificates, possibly with all intermediates (not checked) will be tested.

From stunnel(8):

   cert = CERT_FILE
       certificate chain file name

       The parameter specifies the file containing certificates used by
       stunnel to authenticate itself against the remote client or server.
       The file should contain the whole certificate chain starting from
       the actual server/client certificate, and ending with the self-
       signed root CA certificate.  The file must be either in PEM or P12
       format.

I guess that the root CA certificates need not to be contained in the chain actually. Most configurations don't have that, but it should be seen as a good virtue to know the whole chain, so why not add it to the file!

Now we can check if a certificate is valid, but we still don't know how to check if the certificate really belongs to a peer. The peer might have stolen a valid certificate and tries to use it now.

In stunnel we have now:

checkEmail, to check the DN (distinguished name or directory name) field in the subject or in the Subject Alternative Name (rfc822Name (see RFC280)) extension (X509v3).

checkHost, check for the DNS Name of the certificate. This might be hard to use or unusable for client certificate checks.

checkIP, the IP address of the certificate is checked.

In the manual it reads "Certificates are accepted if no subject checks where specified". I don't actually understand this, because I think the checkIP, checkHost and checkEmail fields are the "subject checks" and it is hard to specify a checkEmail subject check, while "no subject checks are specified".

I guess they mean "no other subject check". Of course if no subject check at all is specified, the certificate should be good if its valid.

Note that Host and IP from the certificate is checked and there is no word about if it is compared with the Hostname or IP of the peer when the connection is made, as it is done when communicating whith HTTPS servers, where the CN must match the Hostname or the DNS Name in the alternatives or the IP in the alternatives.

All these side cases are untested and they might have skipped some of the documentation.

Also the subject checks are redundant if you use verifyPeer as verifyPeer contains the whole peer certificate with subject and alternatives and so on.

I cannot see that the certificate usage fields is checked somewhere. If it is, this point is missing in the documentation!

I will post an update when I checked all the side cases.

ikrabbe
  • 151
  • 7