Hi,

This mail thread has been sitting marked-for-follow-up in my mailbox for
a while.  Finally found some time to test and jot down my thoughts, see
below.

On 06-10-17 13:23, David Sommerseth wrote:
> On 06/10/17 11:52, Илья Шипицин wrote:
> [...snip...]
>>     >
>>     >     In addition, what happens when you try to use a revoked *client*
>>     >     certificate when connecting to an HTTPS server demanding client
>>     >     certificates to be present?
>>     >
>>     >
>>     > 403
>>     >
>>     > (with customizable error message)
>>     Really?  That shouldn't be possible, as you don't have an established
>>     TLS connection to provide the HTTP 403 response.  Because the server
>>     should reject the connection as the *client* certificate is invalid.
>>
>> I did test on IIS with "certificate required", when you connect without
>> cert, you can see 403.
>> ok, I'll test with revoked cert as well
> 
> Okay, I've done some testing with my own Nginx server.  HTTPS is
> actually different from the plain SSL/TLS protocol.
> 
> In HTTPS, the client certificate transfer is handled differently, where
> it exists an unauthenticated TLS connection when invalid certificates
> are sent to the server.  This allows the server to respond with HTTP 40*
> messages over HTTPS.
> 
> When trying the same using openssl s_client and s_server, the server
> side instantly dumps a "certificate verify failed" message and it
> disconnects.
> 
> This latter behaviour is the approach implemented in OpenVPN.  And I
> doubt this can easily be changed, to have an unauthenticated response
> channel, without breaking backwards compatibility with older clients.
> We need to facilitate a few other approaches which ensures the clients
> won't get confused.
> 
> Since both these HTTPS and the s_client/s_server tests happens over TCP
> (as SSL/TLS is strictly designed for TCP only; which is why OpenVPN
> encapsulates the SSL/TLS packets to allow it to use UDP), the client
> will also disconnect when the TCP socket gets closed.  This is not
> possible with UDP sockets, and is why why have --explicit-exit-notify
> for UDP connections, to simulate the TCP FIN behaviour.

What you see here is the result of "TLS Alert" messages, that a peer
sends when there are errors such as cert expired, revoked, invalid, etc.
 So why doesn't OpenVPN send these alerts?  Because we (1) immediately
kill a connection on errors and (2) also manage the 'ciphertext' side of
the TLS connection.

Point (2) might need some more explanation:  TLS libraries are often
used as a layer between the application and the TCP socket.  Because
OpenVPN must also be able to run over UDP, we don't let the library
manage the external socket, but ask the library to return the cipher
text to us and we take care of sending out the cipher text.  Now,
because we immediately tear down a connection when an error occurs, we
never send the TLS alerts (that are deliverd to us as 'TLS ciphertext')
to the peer.

That behaviour is non-standard, but it has protected us against for
example a Bleichenbacher padding oracle attack that did affect TLS
implementations that do send alerts [0].

With that in mind, I think this boils down to a choice to what we
believe makes OpenVPN a better product:  better resilience to potential
TLS attacks, or a better user experience.  I'm usually an advocate of
the former, but in this case the latter is a rather big improvement.  So
I can definitely see the scale tipping that way.

For discussion only, I hacked together a patch that makes OpenVPN send
out the alerts before killing the connection (attached).  It's rather
ugly (and can probably be implemented much more elegant), but it gets
the job done.  An OpenSSL client connecting with a revoked certificate
would then result in a message connection log like this:

$ ./openvpn-openssl --config loopback-client --key
sample-keys/client-revoked.key --cert sample-keys/client-revoked.crt
Mon Jan  1 13:19:27 2018 OpenVPN 2.5_git
[git:openssl-1.1-tls-version/f880ad611a16c0cf+] x86_64-pc-linux-gnu [SSL
(OpenSSL)] [LZO] [LZ4] [EPOLL] [PKCS11] [MH/PKTINFO] [AEAD] built on Jan
 1 2018
Mon Jan  1 13:19:27 2018 library versions: OpenSSL 1.0.1v-dev  xx XXX
xxxx, LZO 2.08
Mon Jan  1 13:19:27 2018 TLS: Initial packet from
[AF_INET]127.0.0.1:1194, sid=c4269920 b6f4bcf1
Mon Jan  1 13:19:27 2018 VERIFY OK: depth=1, C=KG, ST=NA, L=BISHKEK,
O=OpenVPN-TEST, emailAddress=me@myhost.mydomain
Mon Jan  1 13:19:27 2018 VERIFY KU OK
Mon Jan  1 13:19:27 2018 Validating certificate extended key usage
Mon Jan  1 13:19:27 2018 ++ Certificate has EKU (str) TLS Web Server
Authentication, expects TLS Web Server Authentication
Mon Jan  1 13:19:27 2018 VERIFY EKU OK
Mon Jan  1 13:19:27 2018 VERIFY OK: depth=0, C=KG, ST=NA,
O=OpenVPN-TEST, CN=Test-Server, emailAddress=me@myhost.mydomain
Mon Jan  1 13:19:27 2018 OpenSSL: error:14094415:SSL
routines:SSL3_READ_BYTES:sslv3 alert certificate expired
Mon Jan  1 13:19:27 2018 OpenSSL: error:140940E5:SSL
routines:SSL3_READ_BYTES:ssl handshake failure
Mon Jan  1 13:19:27 2018 TLS_ERROR: BIO read tls_read_plaintext error
Mon Jan  1 13:19:27 2018 TLS Error: TLS object -> incoming plaintext
read error
Mon Jan  1 13:19:27 2018 TLS: Handshake failed
Mon Jan  1 13:19:27 2018 TLS Error: TLS handshake failed
Mon Jan  1 13:19:27 2018 Closing TUN/TAP interface
Mon Jan  1 13:19:27 2018 SIGUSR1[soft,tls-error] received, process
restarting
Mon Jan  1 13:19:27 2018 Restart pause, 5 second(s)
Mon Jan  1 13:19:32 2018 UDP link local (bound): [AF_INET]127.0.0.1:16050
Mon Jan  1 13:19:32 2018 UDP link remote: [AF_INET]127.0.0.1:1194
Mon Jan  1 13:19:32 2018 TLS: Initial packet from
[AF_INET]127.0.0.1:1194, sid=49dd46a3 911f97cc
Mon Jan  1 13:19:33 2018 VERIFY OK: depth=1, C=KG, ST=NA, L=BISHKEK,
O=OpenVPN-TEST, emailAddress=me@myhost.mydomain
Mon Jan  1 13:19:33 2018 VERIFY KU OK
Mon Jan  1 13:19:33 2018 Validating certificate extended key usage
Mon Jan  1 13:19:33 2018 ++ Certificate has EKU (str) TLS Web Server
Authentication, expects TLS Web Server Authentication
Mon Jan  1 13:19:33 2018 VERIFY EKU OK
Mon Jan  1 13:19:33 2018 VERIFY OK: depth=0, C=KG, ST=NA,
O=OpenVPN-TEST, CN=Test-Server, emailAddress=me@myhost.mydomain
Mon Jan  1 13:19:33 2018 OpenSSL: error:14094415:SSL
routines:SSL3_READ_BYTES:sslv3 alert certificate expired
Mon Jan  1 13:19:33 2018 OpenSSL: error:140940E5:SSL
routines:SSL3_READ_BYTES:ssl handshake failure
Mon Jan  1 13:19:33 2018 TLS_ERROR: BIO read tls_read_plaintext error
Mon Jan  1 13:19:33 2018 TLS Error: TLS object -> incoming plaintext
read error
Mon Jan  1 13:19:33 2018 TLS: Handshake failed
Mon Jan  1 13:19:33 2018 TLS Error: TLS handshake failed
Mon Jan  1 13:19:33 2018 Closing TUN/TAP interface
Mon Jan  1 13:19:33 2018 SIGUSR1[soft,tls-error] received, process
restarting
Mon Jan  1 13:19:33 2018 Restart pause, 5 second(s)

.. etc.

(The useful error message is a bit hidden in the all the other error,
but we can make that stand out better if we want.)

Note the '5 seconds' reconnect loop, which is the same as what current
released openvpn would do in response to an alert.  So if we change our
servers to send alerts, they will experience quite a bit more load from
clients attempting to reconnect.  We can make newer clients use some
exponential back-off, but older clients will be around for quite a while.

And that's where this braindump ends.  I'm not sure what I believe would
be best, but at least I've shared my considerations with you :)

-Steffan

[0]
https://www.usenix.org/system/files/conference/usenixsecurity14/sec14-paper-meyer.pdf
From f880ad611a16c0cfc2cffa2d12c71cd9199729dc Mon Sep 17 00:00:00 2001
From: Steffan Karger <stef...@karger.me>
Date: Mon, 1 Jan 2018 12:52:26 +0100
Subject: [PATCH] XXX hacky way to send TLS alerts

Only for discussion - not ready for production!
---
 src/openvpn/ssl.c        | 21 +++++++++++++++++----
 src/openvpn/ssl_common.h |  1 +
 2 files changed, 18 insertions(+), 4 deletions(-)

diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c
index a849b348..940bf876 100644
--- a/src/openvpn/ssl.c
+++ b/src/openvpn/ssl.c
@@ -2933,7 +2933,13 @@ tls_process(struct tls_multi *multi,
             if (status == -1)
             {
                 msg(D_TLS_ERRORS, "TLS Error: TLS object -> incoming plaintext read error");
-                goto error;
+                if (ks->state == S_HANDSHAKE_FAIL)
+                {
+                    /* Allow one iteration to send alert, error out next time */
+                    goto error;
+                }
+                ks->state = S_HANDSHAKE_FAIL;
+                state_change = true;
             }
             if (status == 1)
             {
@@ -3020,7 +3026,7 @@ tls_process(struct tls_multi *multi,
         }
 
         /* Outgoing Ciphertext to reliable buffer */
-        if (ks->state >= S_START)
+        if (ks->state >= S_START || ks->state == S_HANDSHAKE_FAIL)
         {
             buf = reliable_get_buf_output_sequenced(ks->send_reliable);
             if (buf)
@@ -3039,6 +3045,12 @@ tls_process(struct tls_multi *multi,
                     state_change = true;
                     dmsg(D_TLS_DEBUG, "Outgoing Ciphertext -> Reliable");
                 }
+                if (status == 0 && ks->state == S_HANDSHAKE_FAIL)
+                {
+                    /* No alerts to send, kill connection */
+                    msg(D_TLS_ERRORS, "TLS: Handshake failed");
+                    goto error;
+                }
             }
         }
     }
@@ -3060,7 +3072,7 @@ tls_process(struct tls_multi *multi,
 
     /* When should we wake up again? */
     {
-        if (ks->state >= S_INITIAL)
+        if (ks->state >= S_INITIAL || ks->state == S_HANDSHAKE_FAIL)
         {
             compute_earliest_wakeup(wakeup,
                                     reliable_send_timeout(ks->send_reliable));
@@ -3152,7 +3164,8 @@ tls_multi_process(struct tls_multi *multi,
              session_id_print(&ks->session_id_remote, &gc),
              print_link_socket_actual(&ks->remote_addr, &gc));
 
-        if (ks->state >= S_INITIAL && link_socket_actual_defined(&ks->remote_addr))
+        if ((ks->state >= S_INITIAL || ks->state == S_HANDSHAKE_FAIL)
+            && link_socket_actual_defined(&ks->remote_addr))
         {
             struct link_socket_actual *tla = NULL;
 
diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h
index 60ed5f8a..c72c7bc4 100644
--- a/src/openvpn/ssl_common.h
+++ b/src/openvpn/ssl_common.h
@@ -74,6 +74,7 @@
  *
  * @{
  */
+#define S_HANDSHAKE_FAIL -2
 #define S_ERROR          -1     /**< Error state.  */
 #define S_UNDEF           0     /**< Undefined state, used after a \c
                                  *   key_state is cleaned up. */
-- 
2.14.1

Attachment: signature.asc
Description: OpenPGP digital signature

------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
_______________________________________________
Openvpn-devel mailing list
Openvpn-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/openvpn-devel

Reply via email to