From d64d9cfbe9dc44db04b253aa08c05e645e10708a Mon Sep 17 00:00:00 2001 From: Sumit Bose 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 --- 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