diff options
Diffstat (limited to 'crypto/asymmetric_keys/public_key.c')
| -rw-r--r-- | crypto/asymmetric_keys/public_key.c | 447 |
1 files changed, 291 insertions, 156 deletions
diff --git a/crypto/asymmetric_keys/public_key.c b/crypto/asymmetric_keys/public_key.c index f5d85b47fcc6..e5b177c8e842 100644 --- a/crypto/asymmetric_keys/public_key.c +++ b/crypto/asymmetric_keys/public_key.c @@ -1,26 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* In-software asymmetric public-key crypto subtype * - * See Documentation/crypto/asymmetric-keys.txt + * See Documentation/crypto/asymmetric-keys.rst * * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. * Written by David Howells (dhowells@redhat.com) - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public Licence - * as published by the Free Software Foundation; either version - * 2 of the Licence, or (at your option) any later version. */ #define pr_fmt(fmt) "PKEY: "fmt -#include <linux/module.h> -#include <linux/export.h> +#include <crypto/akcipher.h> +#include <crypto/public_key.h> +#include <crypto/sig.h> +#include <keys/asymmetric-subtype.h> +#include <linux/asn1.h> +#include <linux/err.h> #include <linux/kernel.h> -#include <linux/slab.h> +#include <linux/module.h> #include <linux/seq_file.h> -#include <linux/scatterlist.h> -#include <keys/asymmetric-subtype.h> -#include <crypto/public_key.h> -#include <crypto/akcipher.h> +#include <linux/slab.h> +#include <linux/string.h> MODULE_DESCRIPTION("In-software asymmetric public-key subtype"); MODULE_AUTHOR("Red Hat, Inc."); @@ -44,7 +42,8 @@ static void public_key_describe(const struct key *asymmetric_key, void public_key_free(struct public_key *key) { if (key) { - kfree(key->key); + kfree_sensitive(key->key); + kfree(key->params); kfree(key); } } @@ -60,38 +59,102 @@ static void public_key_destroy(void *payload0, void *payload3) } /* - * Determine the crypto algorithm name. + * Given a public_key, and an encoding and hash_algo to be used for signing + * and/or verification with that key, determine the name of the corresponding + * akcipher algorithm. Also check that encoding and hash_algo are allowed. */ -static -int software_key_determine_akcipher(const char *encoding, - const char *hash_algo, - const struct public_key *pkey, - char alg_name[CRYPTO_MAX_ALG_NAME]) +static int +software_key_determine_akcipher(const struct public_key *pkey, + const char *encoding, const char *hash_algo, + char alg_name[CRYPTO_MAX_ALG_NAME], bool *sig, + enum kernel_pkey_operation op) { int n; - if (strcmp(encoding, "pkcs1") == 0) { - /* The data wangled by the RSA algorithm is typically padded - * and encoded in some manner, such as EMSA-PKCS1-1_5 [RFC3447 - * sec 8.2]. + *sig = true; + + if (!encoding) + return -EINVAL; + + if (strcmp(pkey->pkey_algo, "rsa") == 0) { + /* + * RSA signatures usually use EMSA-PKCS1-1_5 [RFC3447 sec 8.2]. + */ + if (strcmp(encoding, "pkcs1") == 0) { + *sig = op == kernel_pkey_sign || + op == kernel_pkey_verify; + if (!*sig) { + /* + * For encrypt/decrypt, hash_algo is not used + * but allowed to be set for historic reasons. + */ + n = snprintf(alg_name, CRYPTO_MAX_ALG_NAME, + "pkcs1pad(%s)", + pkey->pkey_algo); + } else { + if (!hash_algo) + hash_algo = "none"; + n = snprintf(alg_name, CRYPTO_MAX_ALG_NAME, + "pkcs1(%s,%s)", + pkey->pkey_algo, hash_algo); + } + return n >= CRYPTO_MAX_ALG_NAME ? -EINVAL : 0; + } + if (strcmp(encoding, "raw") != 0) + return -EINVAL; + /* + * Raw RSA cannot differentiate between different hash + * algorithms. + */ + if (hash_algo) + return -EINVAL; + *sig = false; + } else if (strncmp(pkey->pkey_algo, "ecdsa", 5) == 0) { + if (strcmp(encoding, "x962") != 0 && + strcmp(encoding, "p1363") != 0) + return -EINVAL; + /* + * ECDSA signatures are taken over a raw hash, so they don't + * differentiate between different hash algorithms. That means + * that the verifier should hard-code a specific hash algorithm. + * Unfortunately, in practice ECDSA is used with multiple SHAs, + * so we have to allow all of them and not just one. */ if (!hash_algo) - n = snprintf(alg_name, CRYPTO_MAX_ALG_NAME, - "pkcs1pad(%s)", - pkey->pkey_algo); - else - n = snprintf(alg_name, CRYPTO_MAX_ALG_NAME, - "pkcs1pad(%s,%s)", - pkey->pkey_algo, hash_algo); + return -EINVAL; + if (strcmp(hash_algo, "sha1") != 0 && + strcmp(hash_algo, "sha224") != 0 && + strcmp(hash_algo, "sha256") != 0 && + strcmp(hash_algo, "sha384") != 0 && + strcmp(hash_algo, "sha512") != 0 && + strcmp(hash_algo, "sha3-256") != 0 && + strcmp(hash_algo, "sha3-384") != 0 && + strcmp(hash_algo, "sha3-512") != 0) + return -EINVAL; + n = snprintf(alg_name, CRYPTO_MAX_ALG_NAME, "%s(%s)", + encoding, pkey->pkey_algo); return n >= CRYPTO_MAX_ALG_NAME ? -EINVAL : 0; + } else if (strcmp(pkey->pkey_algo, "ecrdsa") == 0) { + if (strcmp(encoding, "raw") != 0) + return -EINVAL; + if (!hash_algo) + return -EINVAL; + if (strcmp(hash_algo, "streebog256") != 0 && + strcmp(hash_algo, "streebog512") != 0) + return -EINVAL; + } else { + /* Unknown public key algorithm */ + return -ENOPKG; } + if (strscpy(alg_name, pkey->pkey_algo, CRYPTO_MAX_ALG_NAME) < 0) + return -EINVAL; + return 0; +} - if (strcmp(encoding, "raw") == 0) { - strcpy(alg_name, pkey->pkey_algo); - return 0; - } - - return -ENOPKG; +static u8 *pkey_pack_u32(u8 *dst, u32 val) +{ + memcpy(dst, &val, sizeof(val)); + return dst + sizeof(val); } /* @@ -100,45 +163,100 @@ int software_key_determine_akcipher(const char *encoding, static int software_key_query(const struct kernel_pkey_params *params, struct kernel_pkey_query *info) { - struct crypto_akcipher *tfm; struct public_key *pkey = params->key->payload.data[asym_crypto]; char alg_name[CRYPTO_MAX_ALG_NAME]; + u8 *key, *ptr; int ret, len; + bool issig; - ret = software_key_determine_akcipher(params->encoding, - params->hash_algo, - pkey, alg_name); + ret = software_key_determine_akcipher(pkey, params->encoding, + params->hash_algo, alg_name, + &issig, kernel_pkey_sign); if (ret < 0) return ret; - tfm = crypto_alloc_akcipher(alg_name, 0, 0); - if (IS_ERR(tfm)) - return PTR_ERR(tfm); + key = kmalloc(pkey->keylen + sizeof(u32) * 2 + pkey->paramlen, + GFP_KERNEL); + if (!key) + return -ENOMEM; - if (pkey->key_is_private) - ret = crypto_akcipher_set_priv_key(tfm, - pkey->key, pkey->keylen); - else - ret = crypto_akcipher_set_pub_key(tfm, - pkey->key, pkey->keylen); - if (ret < 0) - goto error_free_tfm; + memcpy(key, pkey->key, pkey->keylen); + ptr = key + pkey->keylen; + ptr = pkey_pack_u32(ptr, pkey->algo); + ptr = pkey_pack_u32(ptr, pkey->paramlen); + memcpy(ptr, pkey->params, pkey->paramlen); - len = crypto_akcipher_maxsize(tfm); - info->key_size = len * 8; - info->max_data_size = len; - info->max_sig_size = len; - info->max_enc_size = len; - info->max_dec_size = len; - info->supported_ops = (KEYCTL_SUPPORTS_ENCRYPT | - KEYCTL_SUPPORTS_VERIFY); - if (pkey->key_is_private) - info->supported_ops |= (KEYCTL_SUPPORTS_DECRYPT | - KEYCTL_SUPPORTS_SIGN); - ret = 0; + memset(info, 0, sizeof(*info)); -error_free_tfm: - crypto_free_akcipher(tfm); + if (issig) { + struct crypto_sig *sig; + + sig = crypto_alloc_sig(alg_name, 0, 0); + if (IS_ERR(sig)) { + ret = PTR_ERR(sig); + goto error_free_key; + } + + if (pkey->key_is_private) + ret = crypto_sig_set_privkey(sig, key, pkey->keylen); + else + ret = crypto_sig_set_pubkey(sig, key, pkey->keylen); + if (ret < 0) + goto error_free_sig; + + len = crypto_sig_keysize(sig); + info->key_size = len; + info->max_sig_size = crypto_sig_maxsize(sig); + info->max_data_size = crypto_sig_digestsize(sig); + + info->supported_ops = KEYCTL_SUPPORTS_VERIFY; + if (pkey->key_is_private) + info->supported_ops |= KEYCTL_SUPPORTS_SIGN; + + if (strcmp(params->encoding, "pkcs1") == 0) { + info->max_enc_size = len / BITS_PER_BYTE; + info->max_dec_size = len / BITS_PER_BYTE; + + info->supported_ops |= KEYCTL_SUPPORTS_ENCRYPT; + if (pkey->key_is_private) + info->supported_ops |= KEYCTL_SUPPORTS_DECRYPT; + } + +error_free_sig: + crypto_free_sig(sig); + } else { + struct crypto_akcipher *tfm; + + tfm = crypto_alloc_akcipher(alg_name, 0, 0); + if (IS_ERR(tfm)) { + ret = PTR_ERR(tfm); + goto error_free_key; + } + + if (pkey->key_is_private) + ret = crypto_akcipher_set_priv_key(tfm, key, pkey->keylen); + else + ret = crypto_akcipher_set_pub_key(tfm, key, pkey->keylen); + if (ret < 0) + goto error_free_akcipher; + + len = crypto_akcipher_maxsize(tfm); + info->key_size = len * BITS_PER_BYTE; + info->max_sig_size = len; + info->max_data_size = len; + info->max_enc_size = len; + info->max_dec_size = len; + + info->supported_ops = KEYCTL_SUPPORTS_ENCRYPT; + if (pkey->key_is_private) + info->supported_ops |= KEYCTL_SUPPORTS_DECRYPT; + +error_free_akcipher: + crypto_free_akcipher(tfm); + } + +error_free_key: + kfree_sensitive(key); pr_devel("<==%s() = %d\n", __func__, ret); return ret; } @@ -150,70 +268,96 @@ static int software_key_eds_op(struct kernel_pkey_params *params, const void *in, void *out) { const struct public_key *pkey = params->key->payload.data[asym_crypto]; - struct akcipher_request *req; - struct crypto_akcipher *tfm; - struct crypto_wait cwait; - struct scatterlist in_sg, out_sg; char alg_name[CRYPTO_MAX_ALG_NAME]; + struct crypto_akcipher *tfm; + struct crypto_sig *sig; + char *key, *ptr; + bool issig; int ret; pr_devel("==>%s()\n", __func__); - ret = software_key_determine_akcipher(params->encoding, - params->hash_algo, - pkey, alg_name); + ret = software_key_determine_akcipher(pkey, params->encoding, + params->hash_algo, alg_name, + &issig, params->op); if (ret < 0) return ret; - tfm = crypto_alloc_akcipher(alg_name, 0, 0); - if (IS_ERR(tfm)) - return PTR_ERR(tfm); - - req = akcipher_request_alloc(tfm, GFP_KERNEL); - if (!req) - goto error_free_tfm; - - if (pkey->key_is_private) - ret = crypto_akcipher_set_priv_key(tfm, - pkey->key, pkey->keylen); - else - ret = crypto_akcipher_set_pub_key(tfm, - pkey->key, pkey->keylen); - if (ret) - goto error_free_req; + key = kmalloc(pkey->keylen + sizeof(u32) * 2 + pkey->paramlen, + GFP_KERNEL); + if (!key) + return -ENOMEM; + + memcpy(key, pkey->key, pkey->keylen); + ptr = key + pkey->keylen; + ptr = pkey_pack_u32(ptr, pkey->algo); + ptr = pkey_pack_u32(ptr, pkey->paramlen); + memcpy(ptr, pkey->params, pkey->paramlen); + + if (issig) { + sig = crypto_alloc_sig(alg_name, 0, 0); + if (IS_ERR(sig)) { + ret = PTR_ERR(sig); + goto error_free_key; + } + + if (pkey->key_is_private) + ret = crypto_sig_set_privkey(sig, key, pkey->keylen); + else + ret = crypto_sig_set_pubkey(sig, key, pkey->keylen); + if (ret) + goto error_free_tfm; + } else { + tfm = crypto_alloc_akcipher(alg_name, 0, 0); + if (IS_ERR(tfm)) { + ret = PTR_ERR(tfm); + goto error_free_key; + } + + if (pkey->key_is_private) + ret = crypto_akcipher_set_priv_key(tfm, key, pkey->keylen); + else + ret = crypto_akcipher_set_pub_key(tfm, key, pkey->keylen); + if (ret) + goto error_free_tfm; + } - sg_init_one(&in_sg, in, params->in_len); - sg_init_one(&out_sg, out, params->out_len); - akcipher_request_set_crypt(req, &in_sg, &out_sg, params->in_len, - params->out_len); - crypto_init_wait(&cwait); - akcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG | - CRYPTO_TFM_REQ_MAY_SLEEP, - crypto_req_done, &cwait); + ret = -EINVAL; /* Perform the encryption calculation. */ switch (params->op) { case kernel_pkey_encrypt: - ret = crypto_akcipher_encrypt(req); + if (issig) + break; + ret = crypto_akcipher_sync_encrypt(tfm, in, params->in_len, + out, params->out_len); break; case kernel_pkey_decrypt: - ret = crypto_akcipher_decrypt(req); + if (issig) + break; + ret = crypto_akcipher_sync_decrypt(tfm, in, params->in_len, + out, params->out_len); break; case kernel_pkey_sign: - ret = crypto_akcipher_sign(req); + if (!issig) + break; + ret = crypto_sig_sign(sig, in, params->in_len, + out, params->out_len); break; default: BUG(); } - ret = crypto_wait_req(ret, &cwait); - if (ret == 0) - ret = req->dst_len; + if (!issig && ret == 0) + ret = crypto_akcipher_maxsize(tfm); -error_free_req: - akcipher_request_free(req); error_free_tfm: - crypto_free_akcipher(tfm); + if (issig) + crypto_free_sig(sig); + else + crypto_free_akcipher(tfm); +error_free_key: + kfree_sensitive(key); pr_devel("<==%s() = %d\n", __func__, ret); return ret; } @@ -224,13 +368,10 @@ error_free_tfm: int public_key_verify_signature(const struct public_key *pkey, const struct public_key_signature *sig) { - struct crypto_wait cwait; - struct crypto_akcipher *tfm; - struct akcipher_request *req; - struct scatterlist sig_sg, digest_sg; char alg_name[CRYPTO_MAX_ALG_NAME]; - void *output; - unsigned int outlen; + struct crypto_sig *tfm; + char *key, *ptr; + bool issig; int ret; pr_devel("==>%s()\n", __func__); @@ -239,64 +380,58 @@ int public_key_verify_signature(const struct public_key *pkey, BUG_ON(!sig); BUG_ON(!sig->s); - ret = software_key_determine_akcipher(sig->encoding, - sig->hash_algo, - pkey, alg_name); + /* + * If the signature specifies a public key algorithm, it *must* match + * the key's actual public key algorithm. + * + * Small exception: ECDSA signatures don't specify the curve, but ECDSA + * keys do. So the strings can mismatch slightly in that case: + * "ecdsa-nist-*" for the key, but "ecdsa" for the signature. + */ + if (sig->pkey_algo) { + if (strcmp(pkey->pkey_algo, sig->pkey_algo) != 0 && + (strncmp(pkey->pkey_algo, "ecdsa-", 6) != 0 || + strcmp(sig->pkey_algo, "ecdsa") != 0)) + return -EKEYREJECTED; + } + + ret = software_key_determine_akcipher(pkey, sig->encoding, + sig->hash_algo, alg_name, + &issig, kernel_pkey_verify); if (ret < 0) return ret; - tfm = crypto_alloc_akcipher(alg_name, 0, 0); + tfm = crypto_alloc_sig(alg_name, 0, 0); if (IS_ERR(tfm)) return PTR_ERR(tfm); - ret = -ENOMEM; - req = akcipher_request_alloc(tfm, GFP_KERNEL); - if (!req) + key = kmalloc(pkey->keylen + sizeof(u32) * 2 + pkey->paramlen, + GFP_KERNEL); + if (!key) { + ret = -ENOMEM; goto error_free_tfm; + } + + memcpy(key, pkey->key, pkey->keylen); + ptr = key + pkey->keylen; + ptr = pkey_pack_u32(ptr, pkey->algo); + ptr = pkey_pack_u32(ptr, pkey->paramlen); + memcpy(ptr, pkey->params, pkey->paramlen); if (pkey->key_is_private) - ret = crypto_akcipher_set_priv_key(tfm, - pkey->key, pkey->keylen); + ret = crypto_sig_set_privkey(tfm, key, pkey->keylen); else - ret = crypto_akcipher_set_pub_key(tfm, - pkey->key, pkey->keylen); - if (ret) - goto error_free_req; - - ret = -ENOMEM; - outlen = crypto_akcipher_maxsize(tfm); - output = kmalloc(outlen, GFP_KERNEL); - if (!output) - goto error_free_req; - - sg_init_one(&sig_sg, sig->s, sig->s_size); - sg_init_one(&digest_sg, output, outlen); - akcipher_request_set_crypt(req, &sig_sg, &digest_sg, sig->s_size, - outlen); - crypto_init_wait(&cwait); - akcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG | - CRYPTO_TFM_REQ_MAY_SLEEP, - crypto_req_done, &cwait); - - /* Perform the verification calculation. This doesn't actually do the - * verification, but rather calculates the hash expected by the - * signature and returns that to us. - */ - ret = crypto_wait_req(crypto_akcipher_verify(req), &cwait); + ret = crypto_sig_set_pubkey(tfm, key, pkey->keylen); if (ret) - goto out_free_output; + goto error_free_key; - /* Do the actual verification step. */ - if (req->dst_len != sig->digest_size || - memcmp(sig->digest, output, sig->digest_size) != 0) - ret = -EKEYREJECTED; + ret = crypto_sig_verify(tfm, sig->s, sig->s_size, + sig->digest, sig->digest_size); -out_free_output: - kfree(output); -error_free_req: - akcipher_request_free(req); +error_free_key: + kfree_sensitive(key); error_free_tfm: - crypto_free_akcipher(tfm); + crypto_free_sig(tfm); pr_devel("<==%s() = %d\n", __func__, ret); if (WARN_ON_ONCE(ret > 0)) ret = -EINVAL; |
