Blob Blame History Raw
From d64d9cfbe9dc44db04b253aa08c05e645e10708a Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose@redhat.com>
Date: Fri, 9 Nov 2018 14:01:46 +0100
Subject: [PATCH 70/74] p11_child(openssl): add support for EC keys

Add support for EC keys to the OpenSSL version of p11_child. Please see
comments in the code for some technical details.

Related to https://pagure.io/SSSD/sssd/issue/3887

Reviewed-by: Jakub Hrozek <jhrozek@redhat.com>
---
 src/p11_child/p11_child_openssl.c | 319 +++++++++++++++++++++++++++++-
 1 file changed, 309 insertions(+), 10 deletions(-)

diff --git a/src/p11_child/p11_child_openssl.c b/src/p11_child/p11_child_openssl.c
index af55523a7..0f8ba3d3c 100644
--- a/src/p11_child/p11_child_openssl.c
+++ b/src/p11_child/p11_child_openssl.c
@@ -137,6 +137,7 @@ static OCSP_RESPONSE *query_responder(BIO *cbio, const char *host,
 #define X509_STORE_get0_objects(store) (store->objs)
 #define X509_OBJECT_get_type(object) (object->type)
 #define X509_OBJECT_get0_X509(object) (object->data.x509)
+#define EVP_MD_CTX_free EVP_MD_CTX_destroy
 #endif
 
 OCSP_RESPONSE *process_responder(OCSP_REQUEST *req,
@@ -860,6 +861,243 @@ done:
     return ret;
 }
 
+/* Currently this funtion is only used the print the curve type in the debug
+ * messages. */
+static void get_ec_curve_type(CK_FUNCTION_LIST *module,
+                              CK_SESSION_HANDLE session,
+                              CK_OBJECT_HANDLE key_handle)
+{
+    CK_ATTRIBUTE attribute;
+    CK_RV rv;
+    EC_GROUP *ec_group;
+    const unsigned char *p;
+    int len;
+    char der_buf[128]; /* FIXME: any other size ?? */
+    char oid_buf[128]; /* FIXME: any other size ?? */
+
+    attribute.type = CKA_ECDSA_PARAMS;
+    attribute.pValue = &der_buf;
+    attribute.ulValueLen = sizeof(der_buf);
+
+    rv = module->C_GetAttributeValue(session, key_handle, &attribute, 1);
+    if (rv != CKR_OK) {
+        free(attribute.pValue);
+        DEBUG(SSSDBG_OP_FAILURE,
+              "C_GetAttributeValue failed [%lu][%s].\n",
+              rv, p11_kit_strerror(rv));
+        return;
+    }
+
+    p = (const unsigned char *) attribute.pValue;
+    ec_group = d2i_ECPKParameters(NULL, &p, attribute.ulValueLen);
+    len = OBJ_obj2txt(oid_buf, sizeof(oid_buf),
+                      OBJ_nid2obj(EC_GROUP_get_curve_name(ec_group)), 1);
+    DEBUG(SSSDBG_TRACE_ALL, "Curve name [%s][%s][%.*s].\n",
+                            OBJ_nid2sn(EC_GROUP_get_curve_name(ec_group)),
+                            OBJ_nid2ln(EC_GROUP_get_curve_name(ec_group)),
+                            len, oid_buf);
+
+    return;
+}
+
+static CK_KEY_TYPE get_key_type(CK_FUNCTION_LIST *module,
+                                CK_SESSION_HANDLE session,
+                                CK_OBJECT_HANDLE key_handle)
+{
+    CK_ATTRIBUTE attribute;
+    CK_RV rv;
+    CK_KEY_TYPE type;
+
+    attribute.type = CKA_KEY_TYPE;
+    attribute.pValue = &type;
+    attribute.ulValueLen = sizeof(CK_KEY_TYPE);
+
+    rv = module->C_GetAttributeValue(session, key_handle, &attribute, 1);
+    if (rv != CKR_OK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "C_GetAttributeValue failed [%lu][%s].\n",
+              rv, p11_kit_strerror(rv));
+        return CK_UNAVAILABLE_INFORMATION;
+    }
+
+    if (attribute.ulValueLen == -1) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Key type attribute cannot be read.\n");
+        return CK_UNAVAILABLE_INFORMATION;
+    }
+
+    if (type == CKK_EC && DEBUG_IS_SET(SSSDBG_TRACE_ALL)) {
+        get_ec_curve_type(module, session, key_handle);
+    }
+
+    return type;
+}
+
+static int do_hash(TALLOC_CTX *mem_ctx, const EVP_MD *evp_md,
+                   CK_BYTE *in, size_t in_len,
+                   CK_BYTE **hash, size_t *hash_len)
+{
+    EVP_MD_CTX *md_ctx = NULL;
+    int ret;
+    unsigned char md_value[EVP_MAX_MD_SIZE];
+    unsigned int md_len;
+    CK_BYTE *out = NULL;
+
+    md_ctx = EVP_MD_CTX_create();
+    if (md_ctx == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "EVP_MD_CTX_create failed.\n");
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = EVP_DigestInit(md_ctx, evp_md);
+    if (ret != 1) {
+        DEBUG(SSSDBG_OP_FAILURE, "EVP_DigestInit failed.\n");
+        ret = EINVAL;
+        goto done;
+    }
+
+    ret = EVP_DigestUpdate(md_ctx, in, in_len);
+    if (ret != 1) {
+        DEBUG(SSSDBG_OP_FAILURE, "EVP_DigestUpdate failed.\n");
+        ret = EINVAL;
+        goto done;
+    }
+
+    ret = EVP_DigestFinal_ex(md_ctx, md_value, &md_len);
+    if (ret != 1) {
+        DEBUG(SSSDBG_OP_FAILURE, "EVP_DigestFinal failed.\n");
+        ret = EINVAL;
+        goto done;
+    }
+
+    out = talloc_size(mem_ctx, md_len * sizeof(CK_BYTE));
+    if (out == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n");
+        ret = ENOMEM;
+        goto done;
+    }
+
+    memcpy(out, md_value, md_len);
+
+    *hash = out;
+    *hash_len = md_len;
+
+    ret = EOK;
+
+done:
+
+    if (ret != EOK) {
+        free(out);
+        EVP_MD_CTX_free(md_ctx);
+    }
+
+    return ret;
+}
+
+/* A ECDSA signature consists of 2 integer values r and s. According to the
+ * "PKCS #11 Cryptographic Token Interface Current Mechanisms Specification":
+ *
+ * """
+ * For the purposes of these mechanisms, an ECDSA signature is an octet string
+ * of even length which is at most two times nLen octets, where nLen is the
+ * length in octets of the base point order n. The signature octets correspond
+ * to the concatenation of the ECDSA values r and s, both represented as an
+ * octet string of equal length of at most nLen with the most significant byte
+ * first. If r and s have different octet length, the shorter of both must be
+ * padded with leading zero octets such that both have the same octet length.
+ * Loosely spoken, the first half of the signature is r and the second half is
+ * s. For signatures created by a token, the resulting signature is always of
+ * length 2nLen.
+ * """
+ *
+ * Unfortunately OpenSSL expects the 2 integer values r and s DER encoded as
+ * specified in X9.62 "Public Key Cryptography For The Financial Services
+ * Industry: The Elliptic Curve Digital Signature Algorithm (ECDSA)":
+ *
+ * """
+ * When a digital signature is identified by the OID ecdsa-with-SHA1 , the
+ * digital signature shall be ASN.1 encoded using the following syntax:
+ *   ECDSA-Sig-Value ::= SEQUENCE {
+ *     r  INTEGER,
+ *     s  INTEGER
+ *   }
+ *  """
+ *
+ *  The following function translates from the PKCS#11 to the X9.62 format by
+ *  manually creating the DER sequence after splitting the PKCS#11 signature.
+ *  Since r and s are positive values we have to make sure that the leading
+ *  bit is not set in the DER encoding by prepending a 0-byte if needed.
+ */
+static int rs_to_seq(TALLOC_CTX *mem_ctx, CK_BYTE *rs_sig, CK_ULONG rs_sig_len,
+                     CK_BYTE **seq_sig, CK_ULONG *seq_sig_len)
+{
+    CK_BYTE *r;
+    size_t r_len;
+    CK_BYTE *s;
+    size_t s_len;
+    size_t r_add = 0;
+    size_t s_add = 0;
+    CK_BYTE *out;
+    size_t out_len;
+
+    if (rs_sig_len % 2 != 0) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected signature size [%lu].\n",
+                                   rs_sig_len);
+        return EINVAL;
+    }
+
+    r_len = s_len = rs_sig_len / 2;
+    r = rs_sig;
+    s = rs_sig + r_len;
+
+    /* Remove padding */
+    while(r_len > 1 && *r == 0x00) {
+            r++;
+            r_len--;
+    }
+    while(s_len > 1 && *s == 0x00) {
+            s++;
+            s_len--;
+    }
+
+    /* r and s are positive, check if the highest bit is set which would
+     * indicate a negative value. In this case a 0x00 must be added. */
+    if ( *r & 0x80 ) {
+        r_add = 1;
+    }
+    if ( *s & 0x80 ) {
+        s_add = 1;
+    }
+
+    out_len = r_len + r_add + s_len + s_add + 6;
+    out = talloc_size(mem_ctx, out_len * sizeof(CK_BYTE));
+    if (out == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n");
+        return ENOMEM;
+    }
+
+    out[0] = 0x30;
+    out[1] = (CK_BYTE) (out_len - 2);
+    out[2] = 0x02;
+    out[3] = (CK_BYTE) (r_len + r_add);
+    if (r_add == 1) {
+        out[4] = 0x00;
+    }
+    memcpy(&out[4 + r_add], r, r_len);
+    out[4 + r_add + r_len] = 0x02;
+    out[5 + r_add + r_len] = (CK_BYTE) (s_len + s_add);
+    if (s_add == 1)  {
+        out[6 + r_add + r_len] = 0x00;
+    }
+    memcpy(&out[6 + r_add + r_len + s_add], s, s_len);
+
+    *seq_sig = out;
+    *seq_sig_len = out_len;
+
+    return EOK;
+}
+
 static int sign_data(CK_FUNCTION_LIST *module, CK_SESSION_HANDLE session,
                      struct cert_list *cert)
 {
@@ -870,17 +1108,25 @@ static int sign_data(CK_FUNCTION_LIST *module, CK_SESSION_HANDLE session,
       {CKA_SIGN, &key_sign, sizeof(key_sign)},
       {CKA_ID, NULL, 0}
     };
-    CK_MECHANISM mechanism = { CKM_SHA1_RSA_PKCS, NULL, 0 };
+    CK_MECHANISM mechanism = { CK_UNAVAILABLE_INFORMATION, NULL, 0 };
     CK_OBJECT_HANDLE priv_key_object;
     CK_ULONG object_count;
     CK_BYTE random_value[128];
     CK_BYTE *signature = NULL;
     CK_ULONG signature_size = 0;
+    CK_BYTE *seq_sig = NULL;
+    CK_ULONG seq_sig_size = 0;
     CK_RV rv;
     CK_RV rv_f;
     EVP_PKEY *cert_pub_key = NULL;
     EVP_MD_CTX *md_ctx;
     int ret;
+    const EVP_MD *evp_md = NULL;
+    CK_BYTE *hash_val = NULL;
+    size_t hash_len = 0;
+    CK_BYTE *val_to_sign = NULL;
+    size_t val_to_sign_len = 0;
+    bool card_does_hash = false;
 
     key_template[2].pValue = cert->attributes[ATTR_ID].pValue;
     key_template[2].ulValueLen = cert->attributes[ATTR_ID].ulValueLen;
@@ -910,9 +1156,31 @@ static int sign_data(CK_FUNCTION_LIST *module, CK_SESSION_HANDLE session,
         return EINVAL;
     }
 
+    switch (get_key_type(module, session, priv_key_object)) {
+    case CKK_RSA:
+        DEBUG(SSSDBG_TRACE_ALL, "Found RSA key using CKM_SHA1_RSA_PKCS.\n");
+        mechanism.mechanism = CKM_SHA1_RSA_PKCS;
+        evp_md = EVP_sha1();
+        card_does_hash = true;
+        break;
+    case CKK_EC:
+        DEBUG(SSSDBG_TRACE_ALL, "Found ECC key using CKM_ECDSA.\n");
+        mechanism.mechanism = CKM_ECDSA;
+        evp_md = EVP_sha1();
+        card_does_hash = false;
+        break;
+    case CK_UNAVAILABLE_INFORMATION:
+        DEBUG(SSSDBG_CRIT_FAILURE, "get_key_type failed.\n");
+        return EIO;
+        break;
+    default:
+        DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported key type.\n");
+        return EIO;
+    }
+
     rv = module->C_SignInit(session, &mechanism, priv_key_object);
     if (rv != CKR_OK) {
-        DEBUG(SSSDBG_OP_FAILURE, "C_SignInit failed [%lu][%s].",
+        DEBUG(SSSDBG_OP_FAILURE, "C_SignInit failed [%lu][%s].\n",
                                  rv, p11_kit_strerror(rv));
         return EIO;
     }
@@ -923,7 +1191,22 @@ static int sign_data(CK_FUNCTION_LIST *module, CK_SESSION_HANDLE session,
         return EINVAL;
     }
 
-    rv = module->C_Sign(session, random_value, sizeof(random_value), NULL,
+    if (card_does_hash) {
+        val_to_sign = random_value;
+        val_to_sign_len = sizeof(random_value);
+    } else {
+        ret = do_hash(cert, evp_md, random_value, sizeof(random_value),
+                      &hash_val, &hash_len);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "do_hash failed.\n");
+            return ret;
+        }
+
+        val_to_sign = hash_val;
+        val_to_sign_len = hash_len;
+    }
+
+    rv = module->C_Sign(session, val_to_sign, val_to_sign_len, NULL,
                         &signature_size);
     if (rv != CKR_OK || signature_size == 0) {
         DEBUG(SSSDBG_OP_FAILURE, "C_Sign failed [%lu][%s].\n",
@@ -937,7 +1220,7 @@ static int sign_data(CK_FUNCTION_LIST *module, CK_SESSION_HANDLE session,
         return ENOMEM;
     }
 
-    rv = module->C_Sign(session, random_value, sizeof(random_value), signature,
+    rv = module->C_Sign(session, val_to_sign, val_to_sign_len, signature,
                         &signature_size);
     if (rv != CKR_OK) {
         DEBUG(SSSDBG_OP_FAILURE, "C_Sign failed [%lu][%s].\n",
@@ -958,7 +1241,7 @@ static int sign_data(CK_FUNCTION_LIST *module, CK_SESSION_HANDLE session,
         ret = ENOMEM;
         goto done;
     }
-    ret = EVP_VerifyInit(md_ctx, EVP_sha1());
+    ret = EVP_VerifyInit(md_ctx, evp_md);
     if (ret != 1) {
         DEBUG(SSSDBG_OP_FAILURE, "EVP_VerifyInit failed.\n");
         ret = EINVAL;
@@ -972,11 +1255,27 @@ static int sign_data(CK_FUNCTION_LIST *module, CK_SESSION_HANDLE session,
         goto done;
     }
 
-    ret = EVP_VerifyFinal(md_ctx, signature, signature_size, cert_pub_key);
-    if (ret != 1) {
-        DEBUG(SSSDBG_OP_FAILURE, "EVP_VerifyFinal failed.\n");
-        ret = EINVAL;
-        goto done;
+    if (mechanism.mechanism == CKM_ECDSA) {
+        ret = rs_to_seq(signature, signature, signature_size,
+                        &seq_sig, &seq_sig_size);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "rs_to_seq failed.\n");
+            goto done;
+        }
+
+        ret = EVP_VerifyFinal(md_ctx, seq_sig, seq_sig_size, cert_pub_key);
+        if (ret != 1) {
+            DEBUG(SSSDBG_OP_FAILURE, "EVP_VerifyFinal failed.\n");
+            ret = EINVAL;
+            goto done;
+        }
+    } else {
+        ret = EVP_VerifyFinal(md_ctx, signature, signature_size, cert_pub_key);
+        if (ret != 1) {
+            DEBUG(SSSDBG_OP_FAILURE, "EVP_VerifyFinal failed.\n");
+            ret = EINVAL;
+            goto done;
+        }
     }
 
     ret = EOK;
-- 
2.19.1