Skip to contentSkip to conversation

Pass the hash without the DigestInfo header to NCryptSignHash()

PATCH In review [email protected]... 3 participants 7 messages Apr 26, 2018 – Oct 6, 2018

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 Pass the hash without the DigestInfo header to NCryptSignHash()

From: Selva Nair <[email protected]...>

In case of TLS 1.2 signatures, the callback rsa_priv_enc() gets the hash with the DigestInfo prepended. Signing this using NCryptSignHash() with hash algorithm id set to NULL works in most cases. But when using some hardware tokens, the data gets interpreted as the pre TLS 1.2 MD5+SHA1 hash and is silently truncated to 36 bytes. Avoid this by passing the raw hash to NCryptSignHash() and let it add the DigestInfo.

To get the raw hash we set the RSA_sign() method in the rsa_method structure. This callback bypasses rsa_priv_enc() and gets called with the hash type and the hash.

Fixes Trac #1050
---

Tested by JJK and gizi as described in Trac #1050

 src/openvpn/cryptoapi.c | 84 ++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 79 insertions(+), 5 deletions(-)
src/openvpn/cryptoapi.c +79 -5
index 89d253c..d765954 100644
@@ -217,14 +217,16 @@ rsa_pub_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, in
  * Sign the hash in 'from' using NCryptSignHash(). This requires an NCRYPT
  * key handle in cd->crypt_prov. On return the signature is in 'to'. Returns
  * the length of the signature or 0 on error.
+ * If the hash_algo is not NULL, PKCS #1 DigestInfo header gets added
+ * to 'from', else it is signed as is.
  * For now we support only RSA and the padding is assumed to be PKCS1 v1.5
  */
 static int
-priv_enc_CNG(const CAPI_DATA *cd, const unsigned char *from, int flen,
-              unsigned char *to, int tlen, int padding)
+priv_enc_CNG(const CAPI_DATA *cd, const wchar_t *hash_algo, const unsigned char *from,
+             int flen, unsigned char *to, int tlen, int padding)
 {
     NCRYPT_KEY_HANDLE hkey = cd->crypt_prov;
-    DWORD len;
+    DWORD len = 0;
     ASSERT(cd->key_spec == CERT_NCRYPT_KEY_SPEC);
 
     msg(D_LOW, "Signing hash using CNG: data size = %d", flen);
@@ -232,7 +234,7 @@ priv_enc_CNG(const CAPI_DATA *cd, const unsigned char *from, int flen,
     /* The hash OID is already in 'from'.  So set the hash algorithm
      * in the padding info struct to NULL.
      */
-    BCRYPT_PKCS1_PADDING_INFO padinfo = {NULL};
+    BCRYPT_PKCS1_PADDING_INFO padinfo = {hash_algo};
     DWORD status;
 
     status = NCryptSignHash(hkey, padding? &padinfo : NULL, (BYTE*) from, flen,
@@ -270,7 +272,7 @@ rsa_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, i
     }
     if (cd->key_spec == CERT_NCRYPT_KEY_SPEC)
     {
-        return priv_enc_CNG(cd, from, flen, to, RSA_size(rsa), padding);
+        return priv_enc_CNG(cd, NULL, from, flen, to, RSA_size(rsa), padding);
     }
 
     /* Unfortunately, there is no "CryptSign()" function in CryptoAPI, that would
@@ -334,6 +336,69 @@ rsa_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, i
     return len;
 }
 
+/*
+ * Sign the hash in |m| and return the signature in |sig|.
+ * Returns 1 on success, 0 on error.
+ * NCryptSignHash() is used to sign and it is instructed to add the
+ * the PKCS #1 DigestInfo header to |m| unless the hash algorithm is
+ * the MD5/SHA1 combination used in TLS 1.1 and earlier versions.
+ */
+static int
+rsa_sign_CNG(int type, const unsigned char *m, unsigned int m_len,
+              unsigned char *sig, unsigned int *siglen, const RSA *rsa)
+{
+    CAPI_DATA *cd = (CAPI_DATA *) RSA_meth_get0_app_data(RSA_get_method(rsa));
+    const wchar_t *alg = NULL;
+    int padding = RSA_PKCS1_PADDING;
+
+    *siglen = 0;
+    if (cd == NULL)
+    {
+        RSAerr(RSA_F_RSA_OSSL_PRIVATE_ENCRYPT, ERR_R_PASSED_NULL_PARAMETER);
+        return 0;
+    }
+
+    switch (type)
+    {
+        case NID_md5:
+            alg = BCRYPT_MD5_ALGORITHM;
+            break;
+
+        case NID_sha1:
+            alg = BCRYPT_SHA1_ALGORITHM;
+            break;
+
+        case NID_sha256:
+            alg = BCRYPT_SHA256_ALGORITHM;
+            break;
+
+        case NID_sha384:
+            alg = BCRYPT_SHA384_ALGORITHM;
+            break;
+
+        case NID_sha512:
+            alg = BCRYPT_SHA512_ALGORITHM;
+            break;
+
+        case NID_md5_sha1:
+            if (m_len != SSL_SIG_LENGTH)
+            {
+                RSAerr(RSA_F_RSA_SIGN, RSA_R_INVALID_MESSAGE_LENGTH);
+                return 0;
+            }
+            /* No DigestInfo header is required -- set alg-name to NULL */
+            alg = NULL;
+            break;
+        default:
+            msg(M_WARN, "cryptoapicert: Unknown hash type NID=0x%x", type);
+            RSAerr(RSA_F_RSA_SIGN, RSA_R_UNKNOWN_ALGORITHM_TYPE);
+            return 0;
+    }
+
+    *siglen = priv_enc_CNG(cd, alg, m, (int)m_len, sig, RSA_size(rsa), padding);
+    return (siglen == 0) ? 0 : 1;
+}
+
 /* decrypt */
 static int
 rsa_priv_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)
@@ -555,6 +620,15 @@ SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop)
     RSA_meth_set_finish(my_rsa_method, finish);
     RSA_meth_set0_app_data(my_rsa_method, cd);
 
+    /* For CNG, set the RSA_sign method which gets priority over priv_enc().
+     * This method is called with the raw hash without the digestinfo
+     * header and works better when using NCryptSignHash() with some tokens.
+     */
+    if (cd->key_spec == CERT_NCRYPT_KEY_SPEC)
+    {
+        RSA_meth_set_sign(my_rsa_method, rsa_sign_CNG);
+    }
+
     rsa = RSA_new();
     if (rsa == NULL)
     {
Signature · 1 line
2.1.4
Acked-by on v1 1/1
#1 lore raw

Hi,

Quoted 141 lines · On 26-04-18 16:24, [email protected]... wrote:

From: Selva Nair <[email protected]...>

In case of TLS 1.2 signatures, the callback rsa_priv_enc() gets the hash with the DigestInfo prepended. Signing this using NCryptSignHash() with hash algorithm id set to NULL works in most cases. But when using some hardware tokens, the data gets interpreted as the pre TLS 1.2 MD5+SHA1 hash and is silently truncated to 36 bytes. Avoid this by passing the raw hash to NCryptSignHash() and let it add the DigestInfo.

To get the raw hash we set the RSA_sign() method in the rsa_method structure. This callback bypasses rsa_priv_enc() and gets called with the hash type and the hash.

Fixes Trac #1050
---

Tested by JJK and gizi as described in Trac #1050

 src/openvpn/cryptoapi.c | 84 ++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 79 insertions(+), 5 deletions(-)
src/openvpn/cryptoapi.c +72 -5
index 89d253c..d765954 100644
@@ -217,14 +217,16 @@ rsa_pub_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, in
  * Sign the hash in 'from' using NCryptSignHash(). This requires an NCRYPT
  * key handle in cd->crypt_prov. On return the signature is in 'to'. Returns
  * the length of the signature or 0 on error.
+ * If the hash_algo is not NULL, PKCS #1 DigestInfo header gets added
+ * to 'from', else it is signed as is.
  * For now we support only RSA and the padding is assumed to be PKCS1 v1.5
  */
 static int
-priv_enc_CNG(const CAPI_DATA *cd, const unsigned char *from, int flen,
-              unsigned char *to, int tlen, int padding)
+priv_enc_CNG(const CAPI_DATA *cd, const wchar_t *hash_algo, const unsigned char *from,
+             int flen, unsigned char *to, int tlen, int padding)
 {
     NCRYPT_KEY_HANDLE hkey = cd->crypt_prov;
-    DWORD len;
+    DWORD len = 0;
     ASSERT(cd->key_spec == CERT_NCRYPT_KEY_SPEC);
 
     msg(D_LOW, "Signing hash using CNG: data size = %d", flen);
@@ -232,7 +234,7 @@ priv_enc_CNG(const CAPI_DATA *cd, const unsigned char *from, int flen,
     /* The hash OID is already in 'from'.  So set the hash algorithm
      * in the padding info struct to NULL.
      */
-    BCRYPT_PKCS1_PADDING_INFO padinfo = {NULL};
+    BCRYPT_PKCS1_PADDING_INFO padinfo = {hash_algo};
     DWORD status;
 
     status = NCryptSignHash(hkey, padding? &padinfo : NULL, (BYTE*) from, flen,
@@ -270,7 +272,7 @@ rsa_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, i
     }
     if (cd->key_spec == CERT_NCRYPT_KEY_SPEC)
     {
-        return priv_enc_CNG(cd, from, flen, to, RSA_size(rsa), padding);
+        return priv_enc_CNG(cd, NULL, from, flen, to, RSA_size(rsa), padding);
     }
 
     /* Unfortunately, there is no "CryptSign()" function in CryptoAPI, that would
@@ -334,6 +336,69 @@ rsa_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, i
     return len;
 }
 
+/*
+ * Sign the hash in |m| and return the signature in |sig|.
+ * Returns 1 on success, 0 on error.
+ * NCryptSignHash() is used to sign and it is instructed to add the
+ * the PKCS #1 DigestInfo header to |m| unless the hash algorithm is
+ * the MD5/SHA1 combination used in TLS 1.1 and earlier versions.
+ */
+static int
+rsa_sign_CNG(int type, const unsigned char *m, unsigned int m_len,
+              unsigned char *sig, unsigned int *siglen, const RSA *rsa)
+{
+    CAPI_DATA *cd = (CAPI_DATA *) RSA_meth_get0_app_data(RSA_get_method(rsa));
+    const wchar_t *alg = NULL;
+    int padding = RSA_PKCS1_PADDING;
+
+    *siglen = 0;
+    if (cd == NULL)
+    {
+        RSAerr(RSA_F_RSA_OSSL_PRIVATE_ENCRYPT, ERR_R_PASSED_NULL_PARAMETER);
+        return 0;
+    }
+
+    switch (type)
+    {
+        case NID_md5:
+            alg = BCRYPT_MD5_ALGORITHM;
+            break;
+
+        case NID_sha1:
+            alg = BCRYPT_SHA1_ALGORITHM;
+            break;
+
+        case NID_sha256:
+            alg = BCRYPT_SHA256_ALGORITHM;
+            break;
+
+        case NID_sha384:
+            alg = BCRYPT_SHA384_ALGORITHM;
+            break;
+
+        case NID_sha512:
+            alg = BCRYPT_SHA512_ALGORITHM;
+            break;
+
+        case NID_md5_sha1:
+            if (m_len != SSL_SIG_LENGTH)
+            {
+                RSAerr(RSA_F_RSA_SIGN, RSA_R_INVALID_MESSAGE_LENGTH);
+                return 0;
+            }
+            /* No DigestInfo header is required -- set alg-name to NULL */
+            alg = NULL;
+            break;
+        default:
+            msg(M_WARN, "cryptoapicert: Unknown hash type NID=0x%x", type);
+            RSAerr(RSA_F_RSA_SIGN, RSA_R_UNKNOWN_ALGORITHM_TYPE);
+            return 0;
+    }
+
+    *siglen = priv_enc_CNG(cd, alg, m, (int)m_len, sig, RSA_size(rsa), padding);
+    return (siglen == 0) ? 0 : 1;
+}
+
 /* decrypt */
 static int
 rsa_priv_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)
@@ -555,6 +620,15 @@ SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop)
     RSA_meth_set_finish(my_rsa_method, finish);
     RSA_meth_set0_app_data(my_rsa_method, cd);
 
+    /* For CNG, set the RSA_sign method which gets priority over priv_enc().
+     * This method is called with the raw hash without the digestinfo

src/openvpn/cryptoapi.c SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop) · line 634

Quoted 10 lines
src/openvpn/cryptoapi.c +7 -0
+     * header and works better when using NCryptSignHash() with some tokens.
+     */
+    if (cd->key_spec == CERT_NCRYPT_KEY_SPEC)
+    {
+        RSA_meth_set_sign(my_rsa_method, rsa_sign_CNG);
+    }
+
     rsa = RSA_new();
     if (rsa == NULL)
     {

Change makes sense and code looks good.

Since this is already tested by Selva, JJK and gizi, I just did the stare-at-code part of review.

-Steffan

#2 lore raw
In reply to [email protected]...

Your patch has been applied to the master branch. Steffan says "it is a bugfix so it should go to 2.4", but the underlying infrastructure does not seem to be there yet (git cherry-pick tries to bring in lots of extra stuff). So we can do that, but I need to be told which master commits I need to bring to 2.4 first.

I have not even stared-at-code very much here, trusting the testers and Steffan's code-staring abilities.

As a side note (rambling about comments today), this hunk makes the comment above it slightly misleading:

     /* The hash OID is already in 'from'.  So set the hash algorithm
      * in the padding info struct to NULL.
      */

- BCRYPT_PKCS1_PADDING_INFO padinfo = {NULL};
+ BCRYPT_PKCS1_PADDING_INFO padinfo = {hash_algo};

.. since we do not set it to NULL anymore...

commit 6b495dc4c5cfc118091ddc9c19330b3c9e3e3dff (master)

Author: Selva Nair 
Date:   Thu Apr 26 10:24:24 2018 -0400
     Pass the hash without the DigestInfo header to NCryptSignHash()

     Acked-by: Steffan Karger <[email protected]...>
     Message-Id: <[email protected]...>
     URL: https://www.mail-archive.com/[email protected]/msg16840.html
     Signed-off-by: Gert Doering <[email protected]...>

--
kind regards,

Gert Doering

author
#3 lore raw

Hi,

Quoted 11 lines

As a side note (rambling about comments today), this hunk makes the comment above it slightly misleading:

     /* The hash OID is already in 'from'.  So set the hash algorithm
      * in the padding info struct to NULL.
      */
src/openvpn/cryptoapi.c +1 -1
-    BCRYPT_PKCS1_PADDING_INFO padinfo = {NULL};
+    BCRYPT_PKCS1_PADDING_INFO padinfo = {hash_algo};

.. since we do not set it to NULL anymore...

A perfect case of how fast comments and code diverge -- I added that comment only
the other day.. My bad. Will try to remember to fix next time this is touched.

Knuth's literate programming never caught on, did it..

Thanks for the ack and merge..

For 2.4, have to look through and figure out --- it has been a while..

Selva

author
#4 lore raw
In reply to Gert Doering

Hi

Quoted 6 lines · Gert Doering

Your patch has been applied to the master branch. Steffan says "it is a bugfix so it should go to 2.4", but the underlying infrastructure does not seem to be there yet (git cherry-pick tries to bring in lots of extra stuff). So we can do that, but I need to be told which master commits I need to bring to 2.4 first.

cherry-picking this needs some manual conflict resolution but no pulling in anything extra from master (one function got refactored and renamed in master
and hence the conflict).

A patch for 2.4 follows -- same as for master except for the context of one hunk.

Thanks,

Selva

#5 lore raw

PATCH 1/1 Pass the hash without the DigestInfo header to NCryptSignHash()

In reply to [email protected]...

From: Selva Nair <[email protected]...>

In case of TLS 1.2 signatures, the callback rsa_priv_enc() gets the hash with the DigestInfo prepended. Signing this using NCryptSignHash() with hash algorithm id set to NULL works in most cases. But when using some hardware tokens, the data gets interpreted as the pre TLS 1.2 MD5+SHA1 hash and is silently truncated to 36 bytes. Avoid this by passing the raw hash to NCryptSignHash() and let it add the DigestInfo.

To get the raw hash we set the RSA_sign() method in the rsa_method structure. This callback bypasses rsa_priv_enc() and gets called with the hash type and the hash.

Fixes Trac #1050

Cherry-picked from master 6b495dc4c5cfc118091ddc9c19330b3c9e3e3dff and conflicts resolved manually

Changes:

- Move setting RSA_sign method from ssl_ctx_set_rsakey() to its right place in SSL_CTX_use_CryptoAPI_certificate(). The former function is only in master and appeared when the code was refactored for EC cert support.

- Remove the stale comment about NULL hash algorithm pointed out by Gert.

Except for the context change of one hunk, the patch is the same as for master and nothing extra is pulled-in.

---
 src/openvpn/cryptoapi.c | 87 ++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 79 insertions(+), 8 deletions(-)
src/openvpn/cryptoapi.c +79 -8
index 89d253c..720fce0 100644
@@ -217,22 +217,21 @@ rsa_pub_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, in
  * Sign the hash in 'from' using NCryptSignHash(). This requires an NCRYPT
  * key handle in cd->crypt_prov. On return the signature is in 'to'. Returns
  * the length of the signature or 0 on error.
+ * If the hash_algo is not NULL, PKCS #1 DigestInfo header gets added
+ * to 'from', else it is signed as is.
  * For now we support only RSA and the padding is assumed to be PKCS1 v1.5
  */
 static int
-priv_enc_CNG(const CAPI_DATA *cd, const unsigned char *from, int flen,
-              unsigned char *to, int tlen, int padding)
+priv_enc_CNG(const CAPI_DATA *cd, const wchar_t *hash_algo, const unsigned char *from,
+             int flen, unsigned char *to, int tlen, int padding)
 {
     NCRYPT_KEY_HANDLE hkey = cd->crypt_prov;
-    DWORD len;
+    DWORD len = 0;
     ASSERT(cd->key_spec == CERT_NCRYPT_KEY_SPEC);
 
     msg(D_LOW, "Signing hash using CNG: data size = %d", flen);
 
-    /* The hash OID is already in 'from'.  So set the hash algorithm
-     * in the padding info struct to NULL.
-     */
-    BCRYPT_PKCS1_PADDING_INFO padinfo = {NULL};
+    BCRYPT_PKCS1_PADDING_INFO padinfo = {hash_algo};
     DWORD status;
 
     status = NCryptSignHash(hkey, padding? &padinfo : NULL, (BYTE*) from, flen,
@@ -270,7 +269,7 @@ rsa_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, i
     }
     if (cd->key_spec == CERT_NCRYPT_KEY_SPEC)
     {
-        return priv_enc_CNG(cd, from, flen, to, RSA_size(rsa), padding);
+        return priv_enc_CNG(cd, NULL, from, flen, to, RSA_size(rsa), padding);
     }
 
     /* Unfortunately, there is no "CryptSign()" function in CryptoAPI, that would
@@ -334,6 +333,69 @@ rsa_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, i
     return len;
 }
 
+/*
+ * Sign the hash in |m| and return the signature in |sig|.
+ * Returns 1 on success, 0 on error.
+ * NCryptSignHash() is used to sign and it is instructed to add the
+ * the PKCS #1 DigestInfo header to |m| unless the hash algorithm is
+ * the MD5/SHA1 combination used in TLS 1.1 and earlier versions.
+ */
+static int
+rsa_sign_CNG(int type, const unsigned char *m, unsigned int m_len,
+              unsigned char *sig, unsigned int *siglen, const RSA *rsa)
+{
+    CAPI_DATA *cd = (CAPI_DATA *) RSA_meth_get0_app_data(RSA_get_method(rsa));
+    const wchar_t *alg = NULL;
+    int padding = RSA_PKCS1_PADDING;
+
+    *siglen = 0;
+    if (cd == NULL)
+    {
+        RSAerr(RSA_F_RSA_OSSL_PRIVATE_ENCRYPT, ERR_R_PASSED_NULL_PARAMETER);
+        return 0;
+    }
+
+    switch (type)
+    {
+        case NID_md5:
+            alg = BCRYPT_MD5_ALGORITHM;
+            break;
+
+        case NID_sha1:
+            alg = BCRYPT_SHA1_ALGORITHM;
+            break;
+
+        case NID_sha256:
+            alg = BCRYPT_SHA256_ALGORITHM;
+            break;
+
+        case NID_sha384:
+            alg = BCRYPT_SHA384_ALGORITHM;
+            break;
+
+        case NID_sha512:
+            alg = BCRYPT_SHA512_ALGORITHM;
+            break;
+
+        case NID_md5_sha1:
+            if (m_len != SSL_SIG_LENGTH)
+            {
+                RSAerr(RSA_F_RSA_SIGN, RSA_R_INVALID_MESSAGE_LENGTH);
+                return 0;
+            }
+            /* No DigestInfo header is required -- set alg-name to NULL */
+            alg = NULL;
+            break;
+        default:
+            msg(M_WARN, "cryptoapicert: Unknown hash type NID=0x%x", type);
+            RSAerr(RSA_F_RSA_SIGN, RSA_R_UNKNOWN_ALGORITHM_TYPE);
+            return 0;
+    }
+
+    *siglen = priv_enc_CNG(cd, alg, m, (int)m_len, sig, RSA_size(rsa), padding);
+    return (siglen == 0) ? 0 : 1;
+}
+
 /* decrypt */
 static int
 rsa_priv_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)
@@ -555,6 +617,15 @@ SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop)
     RSA_meth_set_finish(my_rsa_method, finish);
     RSA_meth_set0_app_data(my_rsa_method, cd);
 
+    /* For CNG, set the RSA_sign method which gets priority over priv_enc().
+     * This method is called with the raw hash without the digestinfo
+     * header and works better when using NCryptSignHash() with some tokens.
+     */
+    if (cd->key_spec == CERT_NCRYPT_KEY_SPEC)
+    {
+        RSA_meth_set_sign(my_rsa_method, rsa_sign_CNG);
+    }
+
     rsa = RSA_new();
     if (rsa == NULL)
     {
Signature · 1 line
2.1.4
Acked-by on v1 1/1
#6 lore raw

Thanks, Selva, for checking what was missing (nothing but context) :-) - I wasn't feeling familiar enough with the code to check myself yesterday, and the large conflict "git cherrypick" created misled me.

I have verified that the patch itself contains the code same changes, just line numbers and context are different ("diff -u master.patch 24.patch"), so I cherrypick Steffan's ACK to the master patch and add my own.

Test built on ubuntu1604 with openssl 1.0.1* (so verifying yesterday's openssl 1.1-compat patch in 2.4 as well).

Your patch has been applied to the release/2.4 branch.

commit 163fa9c1e79bddc07c7dd47e8c313016b482314f (release/2.4)

Author: Selva Nair
Date:   Fri Oct 5 20:08:15 2018 -0400
     Pass the hash without the DigestInfo header to NCryptSignHash()

     Signed-off-by: Selva Nair <[email protected]...>
     Acked-by: Steffan Karger <[email protected]...>
     Acked-by: Gert Doering <[email protected]...>
     Message-Id: <[email protected]...>
     URL: https://www.mail-archive.com/[email protected]/msg17579.html
     Signed-off-by: Gert Doering <[email protected]...>

--
kind regards,

Gert Doering