[Openvpn-devel,v3,2/3] Allow external EC key through --management-external-key

Message ID 1516909513-31683-1-git-send-email-selva.nair@gmail.com
State Accepted
Headers show
Series None | expand

Commit Message

Selva Nair Jan. 25, 2018, 8:45 a.m. UTC
From: Selva Nair <selva.nair@gmail.com>

- This automatically supports EC certificates through
  --management-external-cert
- EC signature request from management is prompted by
  >PK_SIGN if the client supports it (or >RSA_SIGN)
  Response should be of the form 'pk-sig' (or rsa-sig
  by older clients) followed by DER encoded signature
  as base64 terminated by 'END' on a new line.

v3: This is v2 adapted to the client_version capability
Requires pacthes 1 and 2 of the series 147:
https://patchwork.openvpn.net/project/openvpn2/list/?series=147

Signed-off-by: Selva Nair <selva.nair@gmail.com>
---
 doc/management-notes.txt  |   9 +-
 src/openvpn/ssl_openssl.c | 210 +++++++++++++++++++++++++++++++++++++---------
 2 files changed, 176 insertions(+), 43 deletions(-)

Comments

Arne Schwabe Jan. 28, 2018, 11:18 p.m. UTC | #1
Am 25.01.18 um 20:45 schrieb selva.nair@gmail.com:
> From: Selva Nair <selva.nair@gmail.com>
> 
> - This automatically supports EC certificates through
>   --management-external-cert
> - EC signature request from management is prompted by
>   >PK_SIGN if the client supports it (or >RSA_SIGN)
>   Response should be of the form 'pk-sig' (or rsa-sig
>   by older clients) followed by DER encoded signature
>   as base64 terminated by 'END' on a new line.
> 
>

Acked-by: Arne Schwabe <arne@rfc2549.org>

Ack from my side. Note that this patch will ask for a ec signatre with >
RSA-SIGN when the user has put a EC certificate in the config and the ui
does not send "version 2". I am not sure that we need to address this
tough. That kind of configuration will break anyway. It just now does at
a different point. And putting an EC certificate is not something that
should happen on accident.

Arne

------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
Selva Nair Jan. 29, 2018, 6:32 a.m. UTC | #2
Hi,

Thanks for the review and ACK.

On Mon, Jan 29, 2018 at 5:18 AM, Arne Schwabe <arne@rfc2549.org> wrote:
> Am 25.01.18 um 20:45 schrieb selva.nair@gmail.com:
>> From: Selva Nair <selva.nair@gmail.com>
>>
>> - This automatically supports EC certificates through
>>   --management-external-cert
>> - EC signature request from management is prompted by
>>   >PK_SIGN if the client supports it (or >RSA_SIGN)
>>   Response should be of the form 'pk-sig' (or rsa-sig
>>   by older clients) followed by DER encoded signature
>>   as base64 terminated by 'END' on a new line.
>>
>>
>
> Acked-by: Arne Schwabe <arne@rfc2549.org>
>
> Ack from my side. Note that this patch will ask for a ec signatre with >
> RSA-SIGN when the user has put a EC certificate in the config and the ui
> does not send "version 2". I am not sure that we need to address this
> tough. That kind of configuration will break anyway. It just now does at
> a different point. And putting an EC certificate is not something that
> should happen on accident.

I do not think we need to be concerned about this. An old UI would
just say either it doesn't support signing using the key or that it
got incorrect signature request from the daemon. So instead of
erroring out in openvpn we will error out in the UI.

But that's the theory, I do not know how extant UI's out there would
respond to RSA_SIGN request with data != 36 bytes or when key is not
RSA. The !=36 byte thing already happened when we merged the TLS1.2
patch. What will the android client do in this case?

Jonathan, would tunnelblick cope if it gets an RSA_SIGN when the key
is EC or with unexpected data size?

Selva

------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
Gert Doering Feb. 19, 2018, 11:30 p.m. UTC | #3
Your patch has been applied to the master branch.

(I have not done any real review on it, except "compile test" and "stare
a bit at the code for really obvious issues" - trusting Arne since his
GUI seems to be the only thing really using it today, so if he's happy,
I am as well)

commit 7eca140c70ff76177371dc94c19aeb8644c2c3b5
Author: Selva Nair
Date:   Thu Jan 25 14:45:13 2018 -0500

     Allow external EC key through --management-external-key

     Signed-off-by: Selva Nair <selva.nair@gmail.com>
     Acked-by: Arne Schwabe <arne@rfc2549.org>
     Message-Id: <1516909513-31683-1-git-send-email-selva.nair@gmail.com>
     URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg16365.html
     Signed-off-by: Gert Doering <gert@greenie.muc.de>


--
kind regards,

Gert Doering


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
Gert Doering Feb. 20, 2018, 1:03 a.m. UTC | #4
Hi,

On Tue, Feb 20, 2018 at 11:30:35AM +0100, Gert Doering wrote:
> Your patch has been applied to the master branch.
> 
> (I have not done any real review on it, except "compile test" and "stare
> a bit at the code for really obvious issues" - trusting Arne since his
> GUI seems to be the only thing really using it today, so if he's happy,
> I am as well)
> 
> commit 7eca140c70ff76177371dc94c19aeb8644c2c3b5
> Author: Selva Nair
> Date:   Thu Jan 25 14:45:13 2018 -0500
> 
>      Allow external EC key through --management-external-key

*sigh*

Today's the day of buildbot exploisions.

*This* one breaks LibreSSL compilation (namely, the OpenBSD buildbot),
because

ssl_openssl.c: In function 'tls_ctx_use_external_ec_key':
ssl_openssl.c:1229: error: 'EC_KEY_METHOD' undeclared (first use in this function)
ssl_openssl.c:1229: error: 'ec_method' undeclared (first use in this function)
ssl_openssl.c:1233: warning: implicit declaration of function 'EC_KEY_METHOD_new'
ssl_openssl.c:1233: warning: implicit declaration of function 'EC_KEY_OpenSSL'
ssl_openssl.c:1240: warning: implicit declaration of function 'EC_KEY_METHOD_set_init'
ssl_openssl.c:1241: warning: implicit declaration of function 'EC_KEY_METHOD_set_sign'
ssl_openssl.c:1249: warning: implicit declaration of function 'EC_KEY_set_method'

So we need another "&& !defined(LIBRESSL)" here, I think.

(And this, dear OpenBSD folks, is why application developers are just
a tiny little bit unhappy with the way LibreSSL claims OpenSSL "recent API"
compatibility by means of OPENSSL_VERSION but then... doesn't)

gert
Selva Nair Feb. 20, 2018, 4:14 a.m. UTC | #5
Hi,

On Tue, Feb 20, 2018 at 7:03 AM, Gert Doering <gert@greenie.muc.de> wrote:
> Hi,
>
> On Tue, Feb 20, 2018 at 11:30:35AM +0100, Gert Doering wrote:
>> Your patch has been applied to the master branch.
>>
>> (I have not done any real review on it, except "compile test" and "stare
>> a bit at the code for really obvious issues" - trusting Arne since his
>> GUI seems to be the only thing really using it today, so if he's happy,
>> I am as well)
>>
>> commit 7eca140c70ff76177371dc94c19aeb8644c2c3b5
>> Author: Selva Nair
>> Date:   Thu Jan 25 14:45:13 2018 -0500
>>
>>      Allow external EC key through --management-external-key
>
> *sigh*
>
> Today's the day of buildbot exploisions.
>
> *This* one breaks LibreSSL compilation (namely, the OpenBSD buildbot),
> because
>
> ssl_openssl.c: In function 'tls_ctx_use_external_ec_key':
> ssl_openssl.c:1229: error: 'EC_KEY_METHOD' undeclared (first use in this function)
> ssl_openssl.c:1229: error: 'ec_method' undeclared (first use in this function)
> ssl_openssl.c:1233: warning: implicit declaration of function 'EC_KEY_METHOD_new'
> ssl_openssl.c:1233: warning: implicit declaration of function 'EC_KEY_OpenSSL'
> ssl_openssl.c:1240: warning: implicit declaration of function 'EC_KEY_METHOD_set_init'
> ssl_openssl.c:1241: warning: implicit declaration of function 'EC_KEY_METHOD_set_sign'
> ssl_openssl.c:1249: warning: implicit declaration of function 'EC_KEY_set_method'
>
> So we need another "&& !defined(LIBRESSL)" here, I think.
>
> (And this, dear OpenBSD folks, is why application developers are just
> a tiny little bit unhappy with the way LibreSSL claims OpenSSL "recent API"
> compatibility by means of OPENSSL_VERSION but then... doesn't)

Adding && !defined(LIBRESSL_VERSION_NUMBER)
to the line
#if OPENSSL_VERSION_NUMBER > 0x10100000L && !defined(OPENSSL_NO_EC)

should fix it, I think. I don't have a freebsd build env to test this.

Thanks,

Selva

------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
Gert Doering Feb. 20, 2018, 8:36 p.m. UTC | #6
Hi,

On Wed, Feb 21, 2018 at 02:07:03AM -0500, Selva Nair wrote:
> >> *This* one breaks LibreSSL compilation (namely, the OpenBSD buildbot),
> >> because
[..]
> Tested using the freebsd-11 box in openvpn-vagrant and did pkg install
> libressl-2.6.4 (that replaces openssl 1.1.0).
> 
> This particular error gets fixed by adding
> !defined(LIBRESSL_VERSION_NUMBER) in two places.
> 
> I can send a patch for this. 

This would be appreciated (please --cc me directly as the list is still
not working properly).

> But there are other errors:
> 
> In file included from crypto_openssl.c:44:
> ./openssl_compat.h:699:1: error: static declaration of
> 'SSL_CTX_set_min_proto_version' follows
[..]
> ./openssl_compat.h:728:1: error: static declaration of
> 'SSL_CTX_set_max_proto_version' follows
>       non-static declaration
[..]
> In openssl those are macros but not in libressl so the current
> openssl_compat.h defs don't work.

This is interesting.  Seems my OpenBSD version is too old, as I've not
seen these errors in my buildbots.

Let's fix one thing at a time, and start with the easy ones, which will
make the buildbots happy for the time being :-) - and then look at
"more recent LibreSSL fallouts".

gert
Selva Nair Feb. 21, 2018, 5:09 a.m. UTC | #7
Hi,

On Wed, Feb 21, 2018 at 2:36 AM, Gert Doering <gert@greenie.muc.de> wrote:
> Hi,
>
> On Wed, Feb 21, 2018 at 02:07:03AM -0500, Selva Nair wrote:
>> >> *This* one breaks LibreSSL compilation (namely, the OpenBSD buildbot),
>> >> because
> [..]
>> Tested using the freebsd-11 box in openvpn-vagrant and did pkg install
>> libressl-2.6.4 (that replaces openssl 1.1.0).
>>
>> This particular error gets fixed by adding
>> !defined(LIBRESSL_VERSION_NUMBER) in two places.
>>
>> I can send a patch for this.
>
> This would be appreciated (please --cc me directly as the list is still
> not working properly).
>
>> But there are other errors:
>>
>> In file included from crypto_openssl.c:44:
>> ./openssl_compat.h:699:1: error: static declaration of
>> 'SSL_CTX_set_min_proto_version' follows
> [..]
>> ./openssl_compat.h:728:1: error: static declaration of
>> 'SSL_CTX_set_max_proto_version' follows
>>       non-static declaration
> [..]
>> In openssl those are macros but not in libressl so the current
>> openssl_compat.h defs don't work.
>
> This is interesting.  Seems my OpenBSD version is too old, as I've not
> seen these errors in my buildbots.

This could be also because of using the vagrant box with freebsd-11.
It came up with openssl 1.0 (not libressl) installed -- is that the
default? And there were no build errors as external-ec-key gets
disabled with openssl 1.0. On installing openssl 1.1.0g and
rebuilding, again, no errors as expected.

The only libressl package that showed up with  "pkg search" was 2.6.4
and installing that removed openssl 1.1.0g (which it shouldn't but
that's a different topic). Then the missing EC_KEY_METHOD and
conflicting signatures for SSL_CTX_set_max_proto_version etc showed
up.

Anyway, I am sending a patch for disabling external-ec-key code when
libressl is in use.

Selva

------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot

Patch

diff --git a/doc/management-notes.txt b/doc/management-notes.txt
index 070c2d6..96470d0 100644
--- a/doc/management-notes.txt
+++ b/doc/management-notes.txt
@@ -779,14 +779,14 @@  COMMAND -- rsa-sig (OpenVPN 2.3 or higher, management version <= 1)
 Provides support for external storage of the private key. Requires the
 --management-external-key option. This option can be used instead of "key"
 in client mode, and allows the client to run without the need to load the
-actual private key. When the SSL protocol needs to perform an RSA sign
+actual private key. When the SSL protocol needs to perform a sign
 operation, the data to be signed will be sent to the management interface
 via a notification as follows:
 
 >PK_SIGN:[BASE64_DATA] (if client announces support for management version > 1)
 >RSA_SIGN:[BASE64_DATA] (only older clients will be prompted like this)
 
-The management interface client should then create a PKCS#1 v1.5 signature of
+The management interface client should then create an appropriate signature of
 the (decoded) BASE64_DATA using the private key and return the SSL signature as
 follows:
 
@@ -797,8 +797,9 @@  pk-sig   (or rsa-sig)
 .
 END
 
-Base64 encoded output of RSA_private_encrypt() (OpenSSL) or mbedtls_pk_sign()
-(mbed TLS) will provide a correct signature.
+Base64 encoded output of RSA_private_encrypt for RSA or ECDSA_sign() for EC
+using OpenSSL or mbedtls_pk_sign() using mbed TLS will provide a correct
+signature.
 
 This capability is intended to allow the use of arbitrary cryptographic
 service providers with OpenVPN via the management interface.
diff --git a/src/openvpn/ssl_openssl.c b/src/openvpn/ssl_openssl.c
index 242b464..74ce596 100644
--- a/src/openvpn/ssl_openssl.c
+++ b/src/openvpn/ssl_openssl.c
@@ -1043,58 +1043,51 @@  openvpn_extkey_rsa_finish(RSA *rsa)
     return 1;
 }
 
-/* sign arbitrary data */
+/* Pass the input hash in 'dgst' to management and get the signature back.
+ * On input siglen contains the capacity of the buffer 'sig'.
+ * On return signature is in sig.
+ * Return value is signature length or -1 on error.
+ */
 static int
-rsa_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)
+get_sig_from_man(const unsigned char *dgst, unsigned int dgstlen,
+                 unsigned char *sig, unsigned int siglen)
 {
-    /* optional app data in rsa->meth->app_data; */
     char *in_b64 = NULL;
     char *out_b64 = NULL;
-    int ret = -1;
-    int len;
+    int len = -1;
 
-    if (padding != RSA_PKCS1_PADDING)
-    {
-        RSAerr(RSA_F_RSA_OSSL_PRIVATE_ENCRYPT, RSA_R_UNKNOWN_PADDING_TYPE);
-        goto done;
-    }
-
-    /* convert 'from' to base64 */
-    if (openvpn_base64_encode(from, flen, &in_b64) <= 0)
-    {
-        goto done;
-    }
-
-    /* call MI for signature */
-    if (management)
+    /* convert 'dgst' to base64 */
+    if (management
+        && openvpn_base64_encode(dgst, dgstlen, &in_b64) > 0)
     {
         out_b64 = management_query_pk_sig(management, in_b64);
     }
-    if (!out_b64)
+    if (out_b64)
     {
-        goto done;
+        len = openvpn_base64_decode(out_b64, sig, siglen);
     }
 
-    /* decode base64 signature to binary */
-    len = RSA_size(rsa);
-    ret = openvpn_base64_decode(out_b64, to, len);
+    free(in_b64);
+    free(out_b64);
+    return len;
+}
 
-    /* verify length */
-    if (ret != len)
-    {
-        ret = -1;
-    }
+/* sign arbitrary data */
+static int
+rsa_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)
+{
+    unsigned int len = RSA_size(rsa);
+    int ret = -1;
 
-done:
-    if (in_b64)
-    {
-        free(in_b64);
-    }
-    if (out_b64)
+    if (padding != RSA_PKCS1_PADDING)
     {
-        free(out_b64);
+        RSAerr(RSA_F_RSA_OSSL_PRIVATE_ENCRYPT, RSA_R_UNKNOWN_PADDING_TYPE);
+        return -1;
     }
-    return ret;
+
+    ret = get_sig_from_man(from, flen, to, len);
+
+    return (ret == len)? ret : -1;
 }
 
 static int
@@ -1166,6 +1159,130 @@  err:
     return 0;
 }
 
+#if OPENSSL_VERSION_NUMBER > 0x10100000L && !defined(OPENSSL_NO_EC)
+
+/* called when EC_KEY is destroyed */
+static void
+openvpn_extkey_ec_finish(EC_KEY *ec)
+{
+    /* release the method structure */
+    const EC_KEY_METHOD *ec_meth = EC_KEY_get_method(ec);
+    EC_KEY_METHOD_free((EC_KEY_METHOD *) ec_meth);
+}
+
+/* EC_KEY_METHOD callback: sign().
+ * Sign the hash using EC key and return DER encoded signature in sig,
+ * its length in siglen. Return value is 1 on success, 0 on error.
+ */
+static int
+ecdsa_sign(int type, const unsigned char *dgst, int dgstlen, unsigned char *sig,
+           unsigned int *siglen, const BIGNUM *kinv, const BIGNUM *r, EC_KEY *ec)
+{
+    int capacity = ECDSA_size(ec);
+    int len = get_sig_from_man(dgst, dgstlen, sig, capacity);
+
+    if (len > 0)
+    {
+        *siglen = len;
+        return 1;
+    }
+    return 0;
+}
+
+/* EC_KEY_METHOD callback: sign_setup(). We do no precomputations */
+static int
+ecdsa_sign_setup(EC_KEY *ec, BN_CTX *ctx_in, BIGNUM **kinvp, BIGNUM **rp)
+{
+    return 1;
+}
+
+/* EC_KEY_METHOD callback: sign_sig().
+ * Sign the hash and return the result as a newly allocated ECDS_SIG
+ * struct or NULL on error.
+ */
+static ECDSA_SIG *
+ecdsa_sign_sig(const unsigned char *dgst, int dgstlen, const BIGNUM *in_kinv,
+               const BIGNUM *in_r, EC_KEY *ec)
+{
+    ECDSA_SIG *ecsig = NULL;
+    unsigned int len = ECDSA_size(ec);
+    struct gc_arena gc = gc_new();
+
+    unsigned char *buf = gc_malloc(len, false, &gc);
+    if (ecdsa_sign(0, dgst, dgstlen, buf, &len, NULL, NULL, ec) != 1)
+    {
+        goto out;
+    }
+    /* const char ** should be avoided: not up to us, so we cast our way through */
+    ecsig = d2i_ECDSA_SIG(NULL, (const unsigned char **)&buf, len);
+
+out:
+    gc_free(&gc);
+    return ecsig;
+}
+
+static int
+tls_ctx_use_external_ec_key(struct tls_root_ctx *ctx, EVP_PKEY *pkey)
+{
+    EC_KEY *ec = NULL;
+    EVP_PKEY *privkey = NULL;
+    EC_KEY_METHOD *ec_method;
+
+    ASSERT(ctx);
+
+    ec_method = EC_KEY_METHOD_new(EC_KEY_OpenSSL());
+    if (!ec_method)
+    {
+        goto err;
+    }
+
+    /* Among init methods, we only need the finish method */
+    EC_KEY_METHOD_set_init(ec_method, NULL, openvpn_extkey_ec_finish, NULL, NULL, NULL, NULL);
+    EC_KEY_METHOD_set_sign(ec_method, ecdsa_sign, ecdsa_sign_setup, ecdsa_sign_sig);
+
+    ec = EC_KEY_dup(EVP_PKEY_get0_EC_KEY(pkey));
+    if (!ec)
+    {
+        EC_KEY_METHOD_free(ec_method);
+        goto err;
+    }
+    if (!EC_KEY_set_method(ec, ec_method))
+    {
+        EC_KEY_METHOD_free(ec_method);
+        goto err;
+    }
+    /* from this point ec_method will get freed when ec is freed */
+
+    privkey = EVP_PKEY_new();
+    if (!EVP_PKEY_assign_EC_KEY(privkey, ec))
+    {
+        goto err;
+    }
+    /* from this point ec will get freed when privkey is freed */
+
+    if (!SSL_CTX_use_PrivateKey(ctx->ctx, privkey))
+    {
+        ec = NULL; /* avoid double freeing it below */
+        goto err;
+    }
+
+    EVP_PKEY_free(privkey); /* this will down ref privkey and ec */
+    return 1;
+
+err:
+    /* Reach here only when ec and privkey can be independenly freed */
+    if (privkey)
+    {
+        EVP_PKEY_free(privkey);
+    }
+    if(ec)
+    {
+        EC_KEY_free(ec);
+    }
+    return 0;
+}
+#endif /* OPENSSL_VERSION_NUMBER > 1.1.0 dev */
+
 int
 tls_ctx_use_external_private_key(struct tls_root_ctx *ctx,
                                  const char *cert_file, const char *cert_file_inline)
@@ -1183,18 +1300,33 @@  tls_ctx_use_external_private_key(struct tls_root_ctx *ctx,
     ASSERT(pkey); /* NULL before SSL_CTX_use_certificate() is called */
     X509_free(cert);
 
-    if (EVP_PKEY_get0_RSA(pkey))
+    if (EVP_PKEY_id(pkey) == EVP_PKEY_RSA)
     {
         if (!tls_ctx_use_external_rsa_key(ctx, pkey))
         {
             goto err;
         }
     }
+#if OPENSSL_VERSION_NUMBER > 0x10100000L && !defined(OPENSSL_NO_EC)
+    else if (EVP_PKEY_id(pkey) == EVP_PKEY_EC)
+    {
+        if (!tls_ctx_use_external_ec_key(ctx, pkey))
+        {
+            goto err;
+        }
+    }
+    else
+    {
+        crypto_msg(M_WARN, "management-external-key requires an RSA or EC certificate");
+        goto err;
+    }
+#else
     else
     {
-        crypto_msg(M_WARN, "management-external-key requires a RSA certificate");
+        crypto_msg(M_WARN, "management-external-key requires an RSA certificate");
         goto err;
     }
+#endif /* OPENSSL_VERSION_NUMBER > 1.1.0 dev */
     return 1;
 
 err: