Derek Lesho : bcrypt: Add support for signing hashes with ECDSA keys.

Alexandre Julliard julliard at winehq.org
Fri Dec 6 16:06:39 CST 2019


Module: wine
Branch: master
Commit: 741f76fc2c9203105ca7f87c0014bb5a9da4d936
URL:    https://source.winehq.org/git/wine.git/?a=commit;h=741f76fc2c9203105ca7f87c0014bb5a9da4d936

Author: Derek Lesho <dlesho at codeweavers.com>
Date:   Fri Dec  6 15:42:48 2019 +0100

bcrypt: Add support for signing hashes with ECDSA keys.

Signed-off-by: Derek Lesho <dlesho at codeweavers.com>
Signed-off-by: Hans Leidekker <hans at codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard at winehq.org>

---

 dlls/bcrypt/gnutls.c | 147 +++++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 130 insertions(+), 17 deletions(-)

diff --git a/dlls/bcrypt/gnutls.c b/dlls/bcrypt/gnutls.c
index a6a07fff19..28a14e9b85 100644
--- a/dlls/bcrypt/gnutls.c
+++ b/dlls/bcrypt/gnutls.c
@@ -92,6 +92,7 @@ MAKE_FUNCPTR(gnutls_cipher_decrypt2);
 MAKE_FUNCPTR(gnutls_cipher_deinit);
 MAKE_FUNCPTR(gnutls_cipher_encrypt2);
 MAKE_FUNCPTR(gnutls_cipher_init);
+MAKE_FUNCPTR(gnutls_decode_rs_value);
 MAKE_FUNCPTR(gnutls_global_deinit);
 MAKE_FUNCPTR(gnutls_global_init);
 MAKE_FUNCPTR(gnutls_global_set_log_function);
@@ -189,6 +190,7 @@ BOOL gnutls_initialize(void)
     LOAD_FUNCPTR(gnutls_cipher_deinit)
     LOAD_FUNCPTR(gnutls_cipher_encrypt2)
     LOAD_FUNCPTR(gnutls_cipher_init)
+    LOAD_FUNCPTR(gnutls_decode_rs_value)
     LOAD_FUNCPTR(gnutls_global_deinit)
     LOAD_FUNCPTR(gnutls_global_init)
     LOAD_FUNCPTR(gnutls_global_set_log_function)
@@ -711,6 +713,7 @@ NTSTATUS key_asymmetric_generate( struct key *key )
         break;
 
     case ALG_ID_ECDH_P256:
+    case ALG_ID_ECDSA_P256:
         pk_alg = GNUTLS_PK_ECC; /* compatible with ECDSA and ECDH */
         bitlen = GNUTLS_CURVE_TO_BITS( GNUTLS_ECC_CURVE_SECP256R1 );
         break;
@@ -1029,6 +1032,17 @@ static NTSTATUS prepare_gnutls_signature( struct key *key, UCHAR *signature, ULO
     }
 }
 
+static gnutls_digest_algorithm_t get_digest_from_id( const WCHAR *alg_id )
+{
+    if (!strcmpW( alg_id, BCRYPT_SHA1_ALGORITHM ))   return GNUTLS_DIG_SHA1;
+    if (!strcmpW( alg_id, BCRYPT_SHA256_ALGORITHM )) return GNUTLS_DIG_SHA256;
+    if (!strcmpW( alg_id, BCRYPT_SHA384_ALGORITHM )) return GNUTLS_DIG_SHA384;
+    if (!strcmpW( alg_id, BCRYPT_SHA512_ALGORITHM )) return GNUTLS_DIG_SHA512;
+    if (!strcmpW( alg_id, BCRYPT_MD2_ALGORITHM ))    return GNUTLS_DIG_MD2;
+    if (!strcmpW( alg_id, BCRYPT_MD5_ALGORITHM ))    return GNUTLS_DIG_MD5;
+    return -1;
+}
+
 NTSTATUS key_asymmetric_verify( struct key *key, void *padding, UCHAR *hash, ULONG hash_len, UCHAR *signature,
                                 ULONG signature_len, DWORD flags )
 {
@@ -1068,11 +1082,7 @@ NTSTATUS key_asymmetric_verify( struct key *key, void *padding, UCHAR *hash, ULO
         if (!(flags & BCRYPT_PAD_PKCS1) || !info) return STATUS_INVALID_PARAMETER;
         if (!info->pszAlgId) return STATUS_INVALID_SIGNATURE;
 
-        if (!strcmpW( info->pszAlgId, BCRYPT_SHA1_ALGORITHM )) hash_alg = GNUTLS_DIG_SHA1;
-        else if (!strcmpW( info->pszAlgId, BCRYPT_SHA256_ALGORITHM )) hash_alg = GNUTLS_DIG_SHA256;
-        else if (!strcmpW( info->pszAlgId, BCRYPT_SHA384_ALGORITHM )) hash_alg = GNUTLS_DIG_SHA384;
-        else if (!strcmpW( info->pszAlgId, BCRYPT_SHA512_ALGORITHM )) hash_alg = GNUTLS_DIG_SHA512;
-        else
+        if ((hash_alg = get_digest_from_id(info->pszAlgId)) == -1)
         {
             FIXME( "hash algorithm %s not supported\n", debugstr_w(info->pszAlgId) );
             return STATUS_NOT_SUPPORTED;
@@ -1107,26 +1117,130 @@ NTSTATUS key_asymmetric_verify( struct key *key, void *padding, UCHAR *hash, ULO
     return (ret < 0) ? STATUS_INVALID_SIGNATURE : STATUS_SUCCESS;
 }
 
+static unsigned int get_signature_length( enum alg_id id )
+{
+    switch (id)
+    {
+    case ALG_ID_ECDSA_P256: return 64;
+    case ALG_ID_ECDSA_P384: return 96;
+    default:
+        FIXME( "unhandled algorithm %u\n", id );
+        return 0;
+    }
+}
+
+NTSTATUS format_gnutls_signature( enum alg_id type, gnutls_datum_t signature, UCHAR *output,
+                                  ULONG output_len, ULONG *ret_len )
+{
+    switch (type)
+    {
+    case ALG_ID_RSA:
+    case ALG_ID_RSA_SIGN:
+    {
+        if (output_len < signature.size) return STATUS_BUFFER_TOO_SMALL;
+        memcpy( output, signature.data, signature.size );
+        *ret_len = signature.size;
+        return STATUS_SUCCESS;
+    }
+    case ALG_ID_ECDSA_P256:
+    case ALG_ID_ECDSA_P384:
+    {
+        int err;
+        unsigned int pad_size, sig_len = get_signature_length( type );
+        gnutls_datum_t r, s; /* format as r||s */
+
+        if ((err = pgnutls_decode_rs_value( &signature, &r, &s )))
+        {
+            pgnutls_perror( err );
+            return STATUS_INTERNAL_ERROR;
+        }
+
+        if (output_len < sig_len) return STATUS_BUFFER_TOO_SMALL;
+
+        /* remove prepended zero byte */
+        if (r.size % 2)
+        {
+            r.size--;
+            r.data += 1;
+        }
+        if (s.size % 2)
+        {
+            s.size--;
+            s.data += 1;
+        }
+
+        if (r.size != s.size || r.size + s.size > sig_len)
+        {
+            ERR( "we didn't get a correct signature\n" );
+            return STATUS_INTERNAL_ERROR;
+        }
+
+        pad_size = (sig_len / 2) - s.size;
+        memset( output, 0, sig_len );
+
+        memcpy( output + pad_size, r.data, r.size );
+        memcpy( output + (sig_len / 2) + pad_size, s.data, s.size );
+
+        *ret_len = sig_len;
+        return STATUS_SUCCESS;
+    }
+    default:
+        return STATUS_INTERNAL_ERROR;
+    }
+}
+
 NTSTATUS key_asymmetric_sign( struct key *key, void *padding, UCHAR *input, ULONG input_len, UCHAR *output,
                               ULONG output_len, ULONG *ret_len, ULONG flags )
 {
     BCRYPT_PKCS1_PADDING_INFO *pad = padding;
     gnutls_datum_t hash, signature;
+    gnutls_digest_algorithm_t hash_alg;
+    NTSTATUS status;
     int ret;
 
-    if (key->alg_id != ALG_ID_RSA && key->alg_id != ALG_ID_RSA_SIGN)
+    if (key->alg_id == ALG_ID_ECDSA_P256 || key->alg_id == ALG_ID_ECDSA_P384)
     {
-        FIXME( "algorithm %u not supported\n", key->alg_id );
-        return STATUS_NOT_IMPLEMENTED;
+        /* With ECDSA, we find the digest algorithm from the hash length, and verify it */
+        switch (input_len)
+        {
+        case 20: hash_alg = GNUTLS_DIG_SHA1; break;
+        case 32: hash_alg = GNUTLS_DIG_SHA256; break;
+        case 48: hash_alg = GNUTLS_DIG_SHA384; break;
+        case 64: hash_alg = GNUTLS_DIG_SHA512; break;
+
+        default:
+            FIXME( "hash size %u not yet supported\n", input_len );
+            return STATUS_INVALID_PARAMETER;
+        }
+
+        if (flags == BCRYPT_PAD_PKCS1 && pad && pad->pszAlgId && get_digest_from_id( pad->pszAlgId ) != hash_alg)
+        {
+            WARN( "incorrect hashing algorithm %s, expected %u\n", debugstr_w(pad->pszAlgId), hash_alg );
+            return STATUS_INVALID_PARAMETER;
+        }
     }
-    if (flags != BCRYPT_PAD_PKCS1)
+    else if (flags == BCRYPT_PAD_PKCS1)
     {
-        FIXME( "flags %08x not implemented\n", flags );
-        return STATUS_NOT_IMPLEMENTED;
+        if (!pad || !pad->pszAlgId)
+        {
+            WARN( "padding info not found\n" );
+            return STATUS_INVALID_PARAMETER;
+        }
+
+        if ((hash_alg = get_digest_from_id( pad->pszAlgId )) == -1)
+        {
+            FIXME( "hash algorithm %s not recognized\n", debugstr_w(pad->pszAlgId) );
+            return STATUS_NOT_SUPPORTED;
+        }
+    }
+    else if (!flags)
+    {
+         WARN( "invalid flags %08x\n", flags );
+         return STATUS_INVALID_PARAMETER;
     }
-    if (!pad || !pad->pszAlgId || lstrcmpiW(pad->pszAlgId, BCRYPT_SHA1_ALGORITHM))
+    else
     {
-        FIXME( "%s padding not implemented\n", debugstr_w(pad ? pad->pszAlgId : NULL) );
+        FIXME( "flags %08x not implemented\n", flags );
         return STATUS_NOT_IMPLEMENTED;
     }
 
@@ -1143,17 +1257,16 @@ NTSTATUS key_asymmetric_sign( struct key *key, void *padding, UCHAR *input, ULON
     signature.data = NULL;
     signature.size = 0;
 
-    if ((ret = pgnutls_privkey_sign_hash( key->u.a.handle, GNUTLS_DIG_SHA1, 0, &hash, &signature )))
+    if ((ret = pgnutls_privkey_sign_hash( key->u.a.handle, hash_alg, 0, &hash, &signature )))
     {
         pgnutls_perror( ret );
         return STATUS_INTERNAL_ERROR;
     }
 
-    if (output_len >= signature.size) memcpy( output, signature.data, signature.size );
-    *ret_len = signature.size;
+    status = format_gnutls_signature( key->alg_id, signature, output, output_len, ret_len );
 
     free( signature.data );
-    return STATUS_SUCCESS;
+    return status;
 }
 
 NTSTATUS key_destroy( struct key *key )




More information about the wine-cvs mailing list