v1 -> v2: - Update virtio_crypto.h from v2 version of related kernel patch. v1: - Support akcipher for virtio-crypto. - Introduce akcipher class. - Introduce ASN1 decoder into QEMU. - Implement RSA backend by nettle/hogweed. Lei He (1): crypto: Introduce RSA algorithm Zhenwei Pi (2): virtio-crypto: header update virtio_crypto: Support virtio crypto asym operation backends/cryptodev-builtin.c | 201 ++++++-- backends/cryptodev-vhost-user.c | 34 +- backends/cryptodev.c | 32 +- crypto/akcipher-nettle.c | 486 ++++++++++++++++++ crypto/akcipher.c | 91 ++++ crypto/asn1_decoder.c | 185 +++++++ crypto/asn1_decoder.h | 42 ++ crypto/meson.build | 4 + hw/virtio/virtio-crypto.c | 328 +++++++++--- include/crypto/akcipher.h | 77 +++ include/hw/virtio/virtio-crypto.h | 5 +- .../standard-headers/linux/virtio_crypto.h | 82 ++- include/sysemu/cryptodev.h | 88 +++- meson.build | 11 + 14 files changed, 1518 insertions(+), 148 deletions(-) create mode 100644 crypto/akcipher-nettle.c create mode 100644 crypto/akcipher.c create mode 100644 crypto/asn1_decoder.c create mode 100644 crypto/asn1_decoder.h create mode 100644 include/crypto/akcipher.h -- 2.20.1
Update header from linux, support akcipher service. Signed-off-by: lei he <helei.sig11 at bytedance.com> Signed-off-by: zhenwei pi <pizhenwei at bytedance.com> --- .../standard-headers/linux/virtio_crypto.h | 82 ++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/include/standard-headers/linux/virtio_crypto.h b/include/standard-headers/linux/virtio_crypto.h index 5ff0b4ee59..68066dafb6 100644 --- a/include/standard-headers/linux/virtio_crypto.h +++ b/include/standard-headers/linux/virtio_crypto.h @@ -37,6 +37,7 @@ #define VIRTIO_CRYPTO_SERVICE_HASH 1 #define VIRTIO_CRYPTO_SERVICE_MAC 2 #define VIRTIO_CRYPTO_SERVICE_AEAD 3 +#define VIRTIO_CRYPTO_SERVICE_AKCIPHER 4 #define VIRTIO_CRYPTO_OPCODE(service, op) (((service) << 8) | (op)) @@ -57,6 +58,10 @@ struct virtio_crypto_ctrl_header { VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_AEAD, 0x02) #define VIRTIO_CRYPTO_AEAD_DESTROY_SESSION \ VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_AEAD, 0x03) +#define VIRTIO_CRYPTO_AKCIPHER_CREATE_SESSION \ + VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_AKCIPHER, 0x04) +#define VIRTIO_CRYPTO_AKCIPHER_DESTROY_SESSION \ + VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_AKCIPHER, 0x05) uint32_t opcode; uint32_t algo; uint32_t flag; @@ -180,6 +185,58 @@ struct virtio_crypto_aead_create_session_req { uint8_t padding[32]; }; +struct virtio_crypto_rsa_session_para { +#define VIRTIO_CRYPTO_RSA_RAW_PADDING 0 +#define VIRTIO_CRYPTO_RSA_PKCS1_PADDING 1 + uint32_t padding_algo; + +#define VIRTIO_CRYPTO_RSA_NO_HASH 0 +#define VIRTIO_CRYPTO_RSA_MD2 1 +#define VIRTIO_CRYPTO_RSA_MD3 2 +#define VIRTIO_CRYPTO_RSA_MD4 3 +#define VIRTIO_CRYPTO_RSA_MD5 4 +#define VIRTIO_CRYPTO_RSA_SHA1 5 +#define VIRTIO_CRYPTO_RSA_SHA256 6 +#define VIRTIO_CRYPTO_RSA_SHA384 7 +#define VIRTIO_CRYPTO_RSA_SHA512 8 +#define VIRTIO_CRYPTO_RSA_SHA224 9 + uint32_t hash_algo; +}; + +struct virtio_crypto_ecdsa_session_para { +#define VIRTIO_CRYPTO_CURVE_UNKNOWN 0 +#define VIRTIO_CRYPTO_CURVE_NIST_P192 1 +#define VIRTIO_CRYPTO_CURVE_NIST_P224 2 +#define VIRTIO_CRYPTO_CURVE_NIST_P256 3 +#define VIRTIO_CRYPTO_CURVE_NIST_P384 4 +#define VIRTIO_CRYPTO_CURVE_NIST_P521 5 + uint32_t curve_id; + uint32_t padding; +}; + +struct virtio_crypto_akcipher_session_para { +#define VIRTIO_CRYPTO_NO_AKCIPHER 0 +#define VIRTIO_CRYPTO_AKCIPHER_RSA 1 +#define VIRTIO_CRYPTO_AKCIPHER_DSA 2 +#define VIRTIO_CRYPTO_AKCIPHER_ECDSA 3 + uint32_t algo; + +#define VIRTIO_CRYPTO_AKCIPHER_KEY_TYPE_PUBLIC 1 +#define VIRTIO_CRYPTO_AKCIPHER_KEY_TYPE_PRIVATE 2 + uint32_t keytype; + uint32_t keylen; + + union { + struct virtio_crypto_rsa_session_para rsa; + struct virtio_crypto_ecdsa_session_para ecdsa; + } u; +}; + +struct virtio_crypto_akcipher_create_session_req { + struct virtio_crypto_akcipher_session_para para; + uint8_t padding[36]; +}; + struct virtio_crypto_alg_chain_session_para { #define VIRTIO_CRYPTO_SYM_ALG_CHAIN_ORDER_HASH_THEN_CIPHER 1 #define VIRTIO_CRYPTO_SYM_ALG_CHAIN_ORDER_CIPHER_THEN_HASH 2 @@ -247,6 +304,8 @@ struct virtio_crypto_op_ctrl_req { mac_create_session; struct virtio_crypto_aead_create_session_req aead_create_session; + struct virtio_crypto_akcipher_create_session_req + akcipher_create_session; struct virtio_crypto_destroy_session_req destroy_session; uint8_t padding[56]; @@ -266,6 +325,14 @@ struct virtio_crypto_op_header { VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_AEAD, 0x00) #define VIRTIO_CRYPTO_AEAD_DECRYPT \ VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_AEAD, 0x01) +#define VIRTIO_CRYPTO_AKCIPHER_ENCRYPT \ + VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_AKCIPHER, 0x00) +#define VIRTIO_CRYPTO_AKCIPHER_DECRYPT \ + VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_AKCIPHER, 0x01) +#define VIRTIO_CRYPTO_AKCIPHER_SIGN \ + VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_AKCIPHER, 0x02) +#define VIRTIO_CRYPTO_AKCIPHER_VERIFY \ + VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_AKCIPHER, 0x03) uint32_t opcode; /* algo should be service-specific algorithms */ uint32_t algo; @@ -390,6 +457,16 @@ struct virtio_crypto_aead_data_req { uint8_t padding[32]; }; +struct virtio_crypto_akcipher_para { + uint32_t src_data_len; + uint32_t dst_data_len; +}; + +struct virtio_crypto_akcipher_data_req { + struct virtio_crypto_akcipher_para para; + uint8_t padding[40]; +}; + /* The request of the data virtqueue's packet */ struct virtio_crypto_op_data_req { struct virtio_crypto_op_header header; @@ -399,6 +476,7 @@ struct virtio_crypto_op_data_req { struct virtio_crypto_hash_data_req hash_req; struct virtio_crypto_mac_data_req mac_req; struct virtio_crypto_aead_data_req aead_req; + struct virtio_crypto_akcipher_data_req akcipher_req; uint8_t padding[48]; } u; }; @@ -408,6 +486,8 @@ struct virtio_crypto_op_data_req { #define VIRTIO_CRYPTO_BADMSG 2 #define VIRTIO_CRYPTO_NOTSUPP 3 #define VIRTIO_CRYPTO_INVSESS 4 /* Invalid session id */ +#define VIRTIO_CRYPTO_NOSPC 5 /* no free session ID */ +#define VIRTIO_CRYPTO_KEY_REJECTED 6 /* Signature verification failed */ /* The accelerator hardware is ready */ #define VIRTIO_CRYPTO_S_HW_READY (1 << 0) @@ -438,7 +518,7 @@ struct virtio_crypto_config { uint32_t max_cipher_key_len; /* Maximum length of authenticated key */ uint32_t max_auth_key_len; - uint32_t reserve; + uint32_t akcipher_algo; /* Maximum size of each crypto request's content */ uint64_t max_size; }; -- 2.20.1
zhenwei pi
2022-Feb-11 08:43 UTC
[PATCH v2 2/3] virtio_crypto: Support virtio crypto asym operation
Several changes in this patch: - Add support for virtio crypto asymmetric handling, include: create/close session encrypt/decrypt/sign/verify guest data - Modify crypto backend to compat sym&asym operation. - Introduce akcipher class without any implementation. Then QEMU can provide asymmetric support for guest, but the asymmetric request from guest shoule always fail until an available asymmetric backend gets supported. Co-developed-by: lei he <helei.sig11 at bytedance.com> Signed-off-by: lei he <helei.sig11 at bytedance.com> Signed-off-by: zhenwei pi <pizhenwei at bytedance.com> --- backends/cryptodev-builtin.c | 201 ++++++++++++++---- backends/cryptodev-vhost-user.c | 34 +++- backends/cryptodev.c | 32 ++- crypto/akcipher.c | 78 +++++++ crypto/meson.build | 1 + hw/virtio/virtio-crypto.c | 328 ++++++++++++++++++++++++------ include/crypto/akcipher.h | 77 +++++++ include/hw/virtio/virtio-crypto.h | 5 +- include/sysemu/cryptodev.h | 88 ++++++-- 9 files changed, 697 insertions(+), 147 deletions(-) create mode 100644 crypto/akcipher.c create mode 100644 include/crypto/akcipher.h diff --git a/backends/cryptodev-builtin.c b/backends/cryptodev-builtin.c index 0671bf9f3e..b3344d230c 100644 --- a/backends/cryptodev-builtin.c +++ b/backends/cryptodev-builtin.c @@ -26,9 +26,9 @@ #include "qapi/error.h" #include "standard-headers/linux/virtio_crypto.h" #include "crypto/cipher.h" +#include "crypto/akcipher.h" #include "qom/object.h" - /** * @TYPE_CRYPTODEV_BACKEND_BUILTIN: * name of backend that uses QEMU cipher API @@ -37,15 +37,15 @@ OBJECT_DECLARE_SIMPLE_TYPE(CryptoDevBackendBuiltin, CRYPTODEV_BACKEND_BUILTIN) - typedef struct CryptoDevBackendBuiltinSession { QCryptoCipher *cipher; uint8_t direction; /* encryption or decryption */ - uint8_t type; /* cipher? hash? aead? */ + uint8_t type; /* cipher? hash? aead? akcipher? */ + QCryptoAkcipher *akcipher; QTAILQ_ENTRY(CryptoDevBackendBuiltinSession) next; } CryptoDevBackendBuiltinSession; -/* Max number of symmetric sessions */ +/* Max number of symmetric/asymmetric sessions */ #define MAX_NUM_SESSIONS 256 #define CRYPTODEV_BUITLIN_MAX_AUTH_KEY_LEN 512 @@ -80,15 +80,17 @@ static void cryptodev_builtin_init( backend->conf.crypto_services 1u << VIRTIO_CRYPTO_SERVICE_CIPHER | 1u << VIRTIO_CRYPTO_SERVICE_HASH | - 1u << VIRTIO_CRYPTO_SERVICE_MAC; + 1u << VIRTIO_CRYPTO_SERVICE_MAC | + 1u << VIRTIO_CRYPTO_SERVICE_AKCIPHER; backend->conf.cipher_algo_l = 1u << VIRTIO_CRYPTO_CIPHER_AES_CBC; backend->conf.hash_algo = 1u << VIRTIO_CRYPTO_HASH_SHA1; + backend->conf.akcipher_algo = 1u << VIRTIO_CRYPTO_AKCIPHER_RSA; /* * Set the Maximum length of crypto request. * Why this value? Just avoid to overflow when * memory allocation for each crypto request. */ - backend->conf.max_size = LONG_MAX - sizeof(CryptoDevBackendSymOpInfo); + backend->conf.max_size = LONG_MAX - sizeof(CryptoDevBackendOpInfo); backend->conf.max_cipher_key_len = CRYPTODEV_BUITLIN_MAX_CIPHER_KEY_LEN; backend->conf.max_auth_key_len = CRYPTODEV_BUITLIN_MAX_AUTH_KEY_LEN; @@ -167,7 +169,7 @@ static int cryptodev_builtin_create_cipher_session( index = cryptodev_builtin_get_unused_session_index(builtin); if (index < 0) { error_setg(errp, "Total number of sessions created exceeds %u", - MAX_NUM_SESSIONS); + MAX_NUM_SESSIONS); return -1; } @@ -240,26 +242,82 @@ static int cryptodev_builtin_create_cipher_session( return index; } -static int64_t cryptodev_builtin_sym_create_session( +static int cryptodev_builtin_create_akcipher_session( + CryptoDevBackendBuiltin *builtin, + CryptoDevBackendAsymSessionInfo *sess_info, + Error **errp) +{ + CryptoDevBackendBuiltinSession *sess; + QCryptoAkcipher *akcipher; + int index; + bool private; + + switch (sess_info->algo) { + case VIRTIO_CRYPTO_AKCIPHER_RSA: + break; + + default: + error_setg(errp, "Unsupported akcipher alg %u", sess_info->algo); + return -1; + } + + switch (sess_info->keytype) { + case VIRTIO_CRYPTO_AKCIPHER_KEY_TYPE_PUBLIC: + private = false; + break; + + case VIRTIO_CRYPTO_AKCIPHER_KEY_TYPE_PRIVATE: + private = true; + break; + + default: + error_setg(errp, "Unsupported akcipher keytype %u", sess_info->keytype); + return -1; + } + + index = cryptodev_builtin_get_unused_session_index(builtin); + if (index < 0) { + error_setg(errp, "Total number of sessions created exceeds %u", + MAX_NUM_SESSIONS); + return -1; + } + + akcipher = qcrypto_akcipher_new(sess_info->algo, private, sess_info->key, + sess_info->keylen, (void *)&sess_info->u, + index, errp); + if (!akcipher) { + return -1; + } + + sess = g_new0(CryptoDevBackendBuiltinSession, 1); + sess->akcipher = akcipher; + + builtin->sessions[index] = sess; + + return index; +} + +static int64_t cryptodev_builtin_create_session( CryptoDevBackend *backend, - CryptoDevBackendSymSessionInfo *sess_info, + CryptoDevBackendSessionInfo *sess_info, uint32_t queue_index, Error **errp) { CryptoDevBackendBuiltin *builtin CRYPTODEV_BACKEND_BUILTIN(backend); - int64_t session_id = -1; - int ret; + CryptoDevBackendSymSessionInfo *sym_sess_info; + CryptoDevBackendAsymSessionInfo *asym_sess_info; switch (sess_info->op_code) { case VIRTIO_CRYPTO_CIPHER_CREATE_SESSION: - ret = cryptodev_builtin_create_cipher_session( - builtin, sess_info, errp); - if (ret < 0) { - return ret; - } else { - session_id = ret; - } - break; + sym_sess_info = &sess_info->u.sym_sess_info; + return cryptodev_builtin_create_cipher_session( + builtin, sym_sess_info, errp); + + case VIRTIO_CRYPTO_AKCIPHER_CREATE_SESSION: + asym_sess_info = &sess_info->u.asym_sess_info; + return cryptodev_builtin_create_akcipher_session( + builtin, asym_sess_info, errp); + case VIRTIO_CRYPTO_HASH_CREATE_SESSION: case VIRTIO_CRYPTO_MAC_CREATE_SESSION: default: @@ -268,10 +326,10 @@ static int64_t cryptodev_builtin_sym_create_session( return -1; } - return session_id; + return -1; } -static int cryptodev_builtin_sym_close_session( +static int cryptodev_builtin_close_session( CryptoDevBackend *backend, uint64_t session_id, uint32_t queue_index, Error **errp) @@ -288,30 +346,17 @@ static int cryptodev_builtin_sym_close_session( } static int cryptodev_builtin_sym_operation( - CryptoDevBackend *backend, - CryptoDevBackendSymOpInfo *op_info, - uint32_t queue_index, Error **errp) + CryptoDevBackendBuiltinSession *sess, + CryptoDevBackendSymOpInfo *op_info, Error **errp) { - CryptoDevBackendBuiltin *builtin - CRYPTODEV_BACKEND_BUILTIN(backend); - CryptoDevBackendBuiltinSession *sess; int ret; - if (op_info->session_id >= MAX_NUM_SESSIONS || - builtin->sessions[op_info->session_id] == NULL) { - error_setg(errp, "Cannot find a valid session id: %" PRIu64 "", - op_info->session_id); - return -VIRTIO_CRYPTO_INVSESS; - } - if (op_info->op_type == VIRTIO_CRYPTO_SYM_OP_ALGORITHM_CHAINING) { error_setg(errp, "Algorithm chain is unsupported for cryptdoev-builtin"); return -VIRTIO_CRYPTO_NOTSUPP; } - sess = builtin->sessions[op_info->session_id]; - if (op_info->iv_len > 0) { ret = qcrypto_cipher_setiv(sess->cipher, op_info->iv, op_info->iv_len, errp); @@ -333,9 +378,85 @@ static int cryptodev_builtin_sym_operation( return -VIRTIO_CRYPTO_ERR; } } + return VIRTIO_CRYPTO_OK; } +static int cryptodev_builtin_asym_operation( + CryptoDevBackendBuiltinSession *sess, uint32_t op_code, + CryptoDevBackendAsymOpInfo *op_info, Error **errp) +{ + int ret; + + switch (op_code) { + case VIRTIO_CRYPTO_AKCIPHER_ENCRYPT: + ret = qcrypto_akcipher_encrypt(sess->akcipher, + op_info->src, op_info->src_len, + op_info->dst, op_info->dst_len, errp); + break; + + case VIRTIO_CRYPTO_AKCIPHER_DECRYPT: + ret = qcrypto_akcipher_decrypt(sess->akcipher, + op_info->src, op_info->src_len, + op_info->dst, op_info->dst_len, errp); + break; + + case VIRTIO_CRYPTO_AKCIPHER_SIGN: + ret = qcrypto_akcipher_sign(sess->akcipher, + op_info->src, op_info->src_len, + op_info->dst, op_info->dst_len, errp); + break; + + case VIRTIO_CRYPTO_AKCIPHER_VERIFY: + ret = qcrypto_akcipher_verify(sess->akcipher, + op_info->src, op_info->src_len, + op_info->dst, op_info->dst_len, errp); + break; + + default: + return -VIRTIO_CRYPTO_ERR; + } + + if (ret) { + return ret; + } + + return VIRTIO_CRYPTO_OK; +} + +static int cryptodev_builtin_operation( + CryptoDevBackend *backend, + CryptoDevBackendOpInfo *op_info, + uint32_t queue_index, Error **errp) +{ + CryptoDevBackendBuiltin *builtin + CRYPTODEV_BACKEND_BUILTIN(backend); + CryptoDevBackendBuiltinSession *sess; + CryptoDevBackendSymOpInfo *sym_op_info; + CryptoDevBackendAsymOpInfo *asym_op_info; + enum CryptoDevBackendAlgType algtype = op_info->algtype; + int ret = -VIRTIO_CRYPTO_ERR; + + if (op_info->session_id >= MAX_NUM_SESSIONS || + builtin->sessions[op_info->session_id] == NULL) { + error_setg(errp, "Cannot find a valid session id: %" PRIu64 "", + op_info->session_id); + return -VIRTIO_CRYPTO_INVSESS; + } + + sess = builtin->sessions[op_info->session_id]; + if (algtype == CRYPTODEV_BACKEND_ALG_SYM) { + sym_op_info = op_info->u.sym_op_info; + ret = cryptodev_builtin_sym_operation(sess, sym_op_info, errp); + } else if (algtype == CRYPTODEV_BACKEND_ALG_ASYM) { + asym_op_info = op_info->u.asym_op_info; + ret = cryptodev_builtin_asym_operation(sess, op_info->op_code, + asym_op_info, errp); + } + + return ret; +} + static void cryptodev_builtin_cleanup( CryptoDevBackend *backend, Error **errp) @@ -348,7 +469,7 @@ static void cryptodev_builtin_cleanup( for (i = 0; i < MAX_NUM_SESSIONS; i++) { if (builtin->sessions[i] != NULL) { - cryptodev_builtin_sym_close_session(backend, i, 0, &error_abort); + cryptodev_builtin_close_session(backend, i, 0, &error_abort); } } @@ -370,9 +491,9 @@ cryptodev_builtin_class_init(ObjectClass *oc, void *data) bc->init = cryptodev_builtin_init; bc->cleanup = cryptodev_builtin_cleanup; - bc->create_session = cryptodev_builtin_sym_create_session; - bc->close_session = cryptodev_builtin_sym_close_session; - bc->do_sym_op = cryptodev_builtin_sym_operation; + bc->create_session = cryptodev_builtin_create_session; + bc->close_session = cryptodev_builtin_close_session; + bc->do_op = cryptodev_builtin_operation; } static const TypeInfo cryptodev_builtin_info = { diff --git a/backends/cryptodev-vhost-user.c b/backends/cryptodev-vhost-user.c index bedb452474..5443a59153 100644 --- a/backends/cryptodev-vhost-user.c +++ b/backends/cryptodev-vhost-user.c @@ -259,7 +259,33 @@ static int64_t cryptodev_vhost_user_sym_create_session( return -1; } -static int cryptodev_vhost_user_sym_close_session( +static int64_t cryptodev_vhost_user_create_session( + CryptoDevBackend *backend, + CryptoDevBackendSessionInfo *sess_info, + uint32_t queue_index, Error **errp) +{ + uint32_t op_code = sess_info->op_code; + CryptoDevBackendSymSessionInfo *sym_sess_info; + + switch (op_code) { + case VIRTIO_CRYPTO_CIPHER_CREATE_SESSION: + case VIRTIO_CRYPTO_HASH_CREATE_SESSION: + case VIRTIO_CRYPTO_MAC_CREATE_SESSION: + case VIRTIO_CRYPTO_AEAD_CREATE_SESSION: + sym_sess_info = &sess_info->u.sym_sess_info; + return cryptodev_vhost_user_sym_create_session(backend, sym_sess_info, + queue_index, errp); + default: + error_setg(errp, "Unsupported opcode :%" PRIu32 "", + sess_info->op_code); + return -1; + + } + + return -1; +} + +static int cryptodev_vhost_user_close_session( CryptoDevBackend *backend, uint64_t session_id, uint32_t queue_index, Error **errp) @@ -351,9 +377,9 @@ cryptodev_vhost_user_class_init(ObjectClass *oc, void *data) bc->init = cryptodev_vhost_user_init; bc->cleanup = cryptodev_vhost_user_cleanup; - bc->create_session = cryptodev_vhost_user_sym_create_session; - bc->close_session = cryptodev_vhost_user_sym_close_session; - bc->do_sym_op = NULL; + bc->create_session = cryptodev_vhost_user_create_session; + bc->close_session = cryptodev_vhost_user_close_session; + bc->do_op = NULL; object_class_property_add_str(oc, "chardev", cryptodev_vhost_user_get_chardev, diff --git a/backends/cryptodev.c b/backends/cryptodev.c index bf52476166..f3d6e338c5 100644 --- a/backends/cryptodev.c +++ b/backends/cryptodev.c @@ -72,9 +72,9 @@ void cryptodev_backend_cleanup( } } -int64_t cryptodev_backend_sym_create_session( +int64_t cryptodev_backend_create_session( CryptoDevBackend *backend, - CryptoDevBackendSymSessionInfo *sess_info, + CryptoDevBackendSessionInfo *sess_info, uint32_t queue_index, Error **errp) { CryptoDevBackendClass *bc @@ -87,7 +87,7 @@ int64_t cryptodev_backend_sym_create_session( return -1; } -int cryptodev_backend_sym_close_session( +int cryptodev_backend_close_session( CryptoDevBackend *backend, uint64_t session_id, uint32_t queue_index, Error **errp) @@ -102,16 +102,16 @@ int cryptodev_backend_sym_close_session( return -1; } -static int cryptodev_backend_sym_operation( +static int cryptodev_backend_operation( CryptoDevBackend *backend, - CryptoDevBackendSymOpInfo *op_info, + CryptoDevBackendOpInfo *op_info, uint32_t queue_index, Error **errp) { CryptoDevBackendClass *bc CRYPTODEV_BACKEND_GET_CLASS(backend); - if (bc->do_sym_op) { - return bc->do_sym_op(backend, op_info, queue_index, errp); + if (bc->do_op) { + return bc->do_op(backend, op_info, queue_index, errp); } return -VIRTIO_CRYPTO_ERR; @@ -123,20 +123,18 @@ int cryptodev_backend_crypto_operation( uint32_t queue_index, Error **errp) { VirtIOCryptoReq *req = opaque; + CryptoDevBackendOpInfo *op_info = &req->op_info; + enum CryptoDevBackendAlgType algtype = req->flags; - if (req->flags == CRYPTODEV_BACKEND_ALG_SYM) { - CryptoDevBackendSymOpInfo *op_info; - op_info = req->u.sym_op_info; - - return cryptodev_backend_sym_operation(backend, - op_info, queue_index, errp); - } else { + if ((algtype != CRYPTODEV_BACKEND_ALG_SYM) + && (algtype != CRYPTODEV_BACKEND_ALG_ASYM)) { error_setg(errp, "Unsupported cryptodev alg type: %" PRIu32 "", - req->flags); - return -VIRTIO_CRYPTO_NOTSUPP; + algtype); + + return -VIRTIO_CRYPTO_NOTSUPP; } - return -VIRTIO_CRYPTO_ERR; + return cryptodev_backend_operation(backend, op_info, queue_index, errp); } static void diff --git a/crypto/akcipher.c b/crypto/akcipher.c new file mode 100644 index 0000000000..ac8d1c9bf1 --- /dev/null +++ b/crypto/akcipher.c @@ -0,0 +1,78 @@ +/* + * QEMU Crypto akcipher algorithms + * + * Copyright (c) 2022 Bytedance + * Author: zhenwei pi <pizhenwei at bytedance.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "qemu/osdep.h" +#include "qemu/host-utils.h" +#include "qapi/error.h" +#include "crypto/akcipher.h" + +QCryptoAkcipher *qcrypto_akcipher_new(uint32_t alg, bool private, + const uint8_t *key, size_t keylen, + void *para, + int index, Error **errp) +{ + QCryptoAkcipher *akcipher = NULL; + + return akcipher; +} + +int qcrypto_akcipher_encrypt(QCryptoAkcipher *akcipher, + const void *data, size_t data_len, + void *enc, size_t enc_len, Error **errp) +{ + const QCryptoAkcipherDriver *drv = akcipher->driver; + + return drv->encrypt(akcipher, data, data_len, enc, enc_len, errp); +} + +int qcrypto_akcipher_decrypt(struct QCryptoAkcipher *akcipher, + const void *enc, size_t enc_len, + void *data, size_t data_len, Error **errp) +{ + const QCryptoAkcipherDriver *drv = akcipher->driver; + + return drv->decrypt(akcipher, enc, enc_len, data, data_len, errp); +} + +int qcrypto_akcipher_sign(struct QCryptoAkcipher *akcipher, + const void *data, size_t data_len, + void *sig, size_t sig_len, Error **errp) +{ + const QCryptoAkcipherDriver *drv = akcipher->driver; + + return drv->sign(akcipher, data, data_len, sig, sig_len, errp); +} + +int qcrypto_akcipher_verify(struct QCryptoAkcipher *akcipher, + const void *sig, size_t sig_len, + const void *data, size_t data_len, Error **errp) +{ + const QCryptoAkcipherDriver *drv = akcipher->driver; + + return drv->verify(akcipher, sig, sig_len, data, data_len, errp); +} + +int qcrypto_akcipher_free(struct QCryptoAkcipher *akcipher, Error **errp) +{ + const QCryptoAkcipherDriver *drv = akcipher->driver; + + return drv->free(akcipher, errp); +} diff --git a/crypto/meson.build b/crypto/meson.build index 95a6a83504..72b36f450a 100644 --- a/crypto/meson.build +++ b/crypto/meson.build @@ -19,6 +19,7 @@ crypto_ss.add(files( 'tlscredspsk.c', 'tlscredsx509.c', 'tlssession.c', + 'akcipher.c', )) if nettle.found() diff --git a/hw/virtio/virtio-crypto.c b/hw/virtio/virtio-crypto.c index 54f9bbb789..3bb79a100b 100644 --- a/hw/virtio/virtio-crypto.c +++ b/hw/virtio/virtio-crypto.c @@ -83,7 +83,8 @@ virtio_crypto_create_sym_session(VirtIOCrypto *vcrypto, struct iovec *iov, unsigned int out_num) { VirtIODevice *vdev = VIRTIO_DEVICE(vcrypto); - CryptoDevBackendSymSessionInfo info; + CryptoDevBackendSessionInfo info; + CryptoDevBackendSymSessionInfo *sym_info; int64_t session_id; int queue_index; uint32_t op_type; @@ -92,11 +93,13 @@ virtio_crypto_create_sym_session(VirtIOCrypto *vcrypto, memset(&info, 0, sizeof(info)); op_type = ldl_le_p(&sess_req->op_type); - info.op_type = op_type; info.op_code = opcode; + sym_info = &info.u.sym_sess_info; + sym_info->op_type = op_type; + if (op_type == VIRTIO_CRYPTO_SYM_OP_CIPHER) { - ret = virtio_crypto_cipher_session_helper(vdev, &info, + ret = virtio_crypto_cipher_session_helper(vdev, sym_info, &sess_req->u.cipher.para, &iov, &out_num); if (ret < 0) { @@ -105,47 +108,47 @@ virtio_crypto_create_sym_session(VirtIOCrypto *vcrypto, } else if (op_type == VIRTIO_CRYPTO_SYM_OP_ALGORITHM_CHAINING) { size_t s; /* cipher part */ - ret = virtio_crypto_cipher_session_helper(vdev, &info, + ret = virtio_crypto_cipher_session_helper(vdev, sym_info, &sess_req->u.chain.para.cipher_param, &iov, &out_num); if (ret < 0) { goto err; } /* hash part */ - info.alg_chain_order = ldl_le_p( + sym_info->alg_chain_order = ldl_le_p( &sess_req->u.chain.para.alg_chain_order); - info.add_len = ldl_le_p(&sess_req->u.chain.para.aad_len); - info.hash_mode = ldl_le_p(&sess_req->u.chain.para.hash_mode); - if (info.hash_mode == VIRTIO_CRYPTO_SYM_HASH_MODE_AUTH) { - info.hash_alg = ldl_le_p(&sess_req->u.chain.para.u.mac_param.algo); - info.auth_key_len = ldl_le_p( + sym_info->add_len = ldl_le_p(&sess_req->u.chain.para.aad_len); + sym_info->hash_mode = ldl_le_p(&sess_req->u.chain.para.hash_mode); + if (sym_info->hash_mode == VIRTIO_CRYPTO_SYM_HASH_MODE_AUTH) { + sym_info->hash_alg + ldl_le_p(&sess_req->u.chain.para.u.mac_param.algo); + sym_info->auth_key_len = ldl_le_p( &sess_req->u.chain.para.u.mac_param.auth_key_len); - info.hash_result_len = ldl_le_p( + sym_info->hash_result_len = ldl_le_p( &sess_req->u.chain.para.u.mac_param.hash_result_len); - if (info.auth_key_len > vcrypto->conf.max_auth_key_len) { + if (sym_info->auth_key_len > vcrypto->conf.max_auth_key_len) { error_report("virtio-crypto length of auth key is too big: %u", - info.auth_key_len); + sym_info->auth_key_len); ret = -VIRTIO_CRYPTO_ERR; goto err; } /* get auth key */ - if (info.auth_key_len > 0) { - DPRINTF("auth_keylen=%" PRIu32 "\n", info.auth_key_len); - info.auth_key = g_malloc(info.auth_key_len); - s = iov_to_buf(iov, out_num, 0, info.auth_key, - info.auth_key_len); - if (unlikely(s != info.auth_key_len)) { + if (sym_info->auth_key_len > 0) { + sym_info->auth_key = g_malloc(sym_info->auth_key_len); + s = iov_to_buf(iov, out_num, 0, sym_info->auth_key, + sym_info->auth_key_len); + if (unlikely(s != sym_info->auth_key_len)) { virtio_error(vdev, "virtio-crypto authenticated key incorrect"); ret = -EFAULT; goto err; } - iov_discard_front(&iov, &out_num, info.auth_key_len); + iov_discard_front(&iov, &out_num, sym_info->auth_key_len); } - } else if (info.hash_mode == VIRTIO_CRYPTO_SYM_HASH_MODE_PLAIN) { - info.hash_alg = ldl_le_p( + } else if (sym_info->hash_mode == VIRTIO_CRYPTO_SYM_HASH_MODE_PLAIN) { + sym_info->hash_alg = ldl_le_p( &sess_req->u.chain.para.u.hash_param.algo); - info.hash_result_len = ldl_le_p( + sym_info->hash_result_len = ldl_le_p( &sess_req->u.chain.para.u.hash_param.hash_result_len); } else { /* VIRTIO_CRYPTO_SYM_HASH_MODE_NESTED */ @@ -161,13 +164,10 @@ virtio_crypto_create_sym_session(VirtIOCrypto *vcrypto, } queue_index = virtio_crypto_vq2q(queue_id); - session_id = cryptodev_backend_sym_create_session( + session_id = cryptodev_backend_create_session( vcrypto->cryptodev, &info, queue_index, &local_err); if (session_id >= 0) { - DPRINTF("create session_id=%" PRIu64 " successfully\n", - session_id); - ret = session_id; } else { if (local_err) { @@ -177,11 +177,82 @@ virtio_crypto_create_sym_session(VirtIOCrypto *vcrypto, } err: - g_free(info.cipher_key); - g_free(info.auth_key); + g_free(sym_info->cipher_key); + g_free(sym_info->auth_key); return ret; } +static int64_t +virtio_crypto_create_asym_session(VirtIOCrypto *vcrypto, + struct virtio_crypto_akcipher_create_session_req *sess_req, + uint32_t queue_id, uint32_t opcode, + struct iovec *iov, unsigned int out_num) +{ + VirtIODevice *vdev = VIRTIO_DEVICE(vcrypto); + CryptoDevBackendSessionInfo info = {0}; + CryptoDevBackendAsymSessionInfo *asym_info; + int64_t session_id; + int queue_index; + uint32_t algo, keytype, keylen; + uint8_t *key = NULL; + Error *local_err = NULL; + + algo = ldl_le_p(&sess_req->para.algo); + keytype = ldl_le_p(&sess_req->para.keytype); + keylen = ldl_le_p(&sess_req->para.keylen); + + if ((keytype != VIRTIO_CRYPTO_AKCIPHER_KEY_TYPE_PUBLIC) + && (keytype != VIRTIO_CRYPTO_AKCIPHER_KEY_TYPE_PRIVATE)) { + error_report("unsupported asym keytype: %d", keytype); + return -VIRTIO_CRYPTO_NOTSUPP; + } + + if (keylen) { + key = g_malloc(keylen); + if (iov_to_buf(iov, out_num, 0, key, keylen) != keylen) { + virtio_error(vdev, "virtio-crypto asym key incorrect"); + g_free(key); + return -EFAULT; + } + iov_discard_front(&iov, &out_num, keylen); + } + + info.op_code = opcode; + asym_info = &info.u.asym_sess_info; + asym_info->algo = algo; + asym_info->keytype = keytype; + asym_info->keylen = keylen; + asym_info->key = key; + switch (asym_info->algo) { + case VIRTIO_CRYPTO_AKCIPHER_RSA: + asym_info->u.rsa.padding_algo + ldl_le_p(&sess_req->para.u.rsa.padding_algo); + asym_info->u.rsa.hash_algo + ldl_le_p(&sess_req->para.u.rsa.hash_algo); + break; + + case VIRTIO_CRYPTO_AKCIPHER_ECDSA: + asym_info->u.ecdsa.curve_id + ldl_le_p(&sess_req->para.u.ecdsa.curve_id); + break; + + default: + return -VIRTIO_CRYPTO_ERR; + } + + queue_index = virtio_crypto_vq2q(queue_id); + session_id = cryptodev_backend_create_session(vcrypto->cryptodev, &info, + queue_index, &local_err); + if (session_id < 0) { + if (local_err) { + error_report_err(local_err); + } + return -VIRTIO_CRYPTO_ERR; + } + + return session_id; +} + static uint8_t virtio_crypto_handle_close_session(VirtIOCrypto *vcrypto, struct virtio_crypto_destroy_session_req *close_sess_req, @@ -193,9 +264,8 @@ virtio_crypto_handle_close_session(VirtIOCrypto *vcrypto, Error *local_err = NULL; session_id = ldq_le_p(&close_sess_req->session_id); - DPRINTF("close session, id=%" PRIu64 "\n", session_id); - ret = cryptodev_backend_sym_close_session( + ret = cryptodev_backend_close_session( vcrypto->cryptodev, session_id, queue_id, &local_err); if (ret == 0) { status = VIRTIO_CRYPTO_OK; @@ -260,13 +330,22 @@ static void virtio_crypto_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq) opcode = ldl_le_p(&ctrl.header.opcode); queue_id = ldl_le_p(&ctrl.header.queue_id); + memset(&input, 0, sizeof(input)); switch (opcode) { case VIRTIO_CRYPTO_CIPHER_CREATE_SESSION: - memset(&input, 0, sizeof(input)); session_id = virtio_crypto_create_sym_session(vcrypto, &ctrl.u.sym_create_session, queue_id, opcode, out_iov, out_num); + goto check_session; + + case VIRTIO_CRYPTO_AKCIPHER_CREATE_SESSION: + session_id = virtio_crypto_create_asym_session(vcrypto, + &ctrl.u.akcipher_create_session, + queue_id, opcode, + out_iov, out_num); + +check_session: /* Serious errors, need to reset virtio crypto device */ if (session_id == -EFAULT) { virtqueue_detach_element(vq, elem, 0); @@ -290,10 +369,12 @@ static void virtio_crypto_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq) virtqueue_push(vq, elem, sizeof(input)); virtio_notify(vdev, vq); break; + case VIRTIO_CRYPTO_CIPHER_DESTROY_SESSION: case VIRTIO_CRYPTO_HASH_DESTROY_SESSION: case VIRTIO_CRYPTO_MAC_DESTROY_SESSION: case VIRTIO_CRYPTO_AEAD_DESTROY_SESSION: + case VIRTIO_CRYPTO_AKCIPHER_DESTROY_SESSION: status = virtio_crypto_handle_close_session(vcrypto, &ctrl.u.destroy_session, queue_id); /* The status only occupy one byte, we can directly use it */ @@ -311,7 +392,6 @@ static void virtio_crypto_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq) case VIRTIO_CRYPTO_AEAD_CREATE_SESSION: default: error_report("virtio-crypto unsupported ctrl opcode: %d", opcode); - memset(&input, 0, sizeof(input)); stl_le_p(&input.status, VIRTIO_CRYPTO_NOTSUPP); s = iov_from_buf(in_iov, in_num, 0, &input, sizeof(input)); if (unlikely(s != sizeof(input))) { @@ -339,28 +419,37 @@ static void virtio_crypto_init_request(VirtIOCrypto *vcrypto, VirtQueue *vq, req->in_num = 0; req->in_len = 0; req->flags = CRYPTODEV_BACKEND_ALG__MAX; - req->u.sym_op_info = NULL; + memset(&req->op_info, 0x00, sizeof(req->op_info)); } static void virtio_crypto_free_request(VirtIOCryptoReq *req) { - if (req) { - if (req->flags == CRYPTODEV_BACKEND_ALG_SYM) { - size_t max_len; - CryptoDevBackendSymOpInfo *op_info = req->u.sym_op_info; - - max_len = op_info->iv_len + - op_info->aad_len + - op_info->src_len + - op_info->dst_len + - op_info->digest_result_len; - - /* Zeroize and free request data structure */ - memset(op_info, 0, sizeof(*op_info) + max_len); + if (!req) { + return; + } + + if (req->flags == CRYPTODEV_BACKEND_ALG_SYM) { + size_t max_len; + CryptoDevBackendSymOpInfo *op_info = req->op_info.u.sym_op_info; + + max_len = op_info->iv_len + + op_info->aad_len + + op_info->src_len + + op_info->dst_len + + op_info->digest_result_len; + + /* Zeroize and free request data structure */ + memset(op_info, 0, sizeof(*op_info) + max_len); + g_free(op_info); + } else if (req->flags == CRYPTODEV_BACKEND_ALG_ASYM) { + CryptoDevBackendAsymOpInfo *op_info = req->op_info.u.asym_op_info; + if (op_info) { + memset(op_info, 0, sizeof(*op_info)); g_free(op_info); } - g_free(req); } + + g_free(req); } static void @@ -397,6 +486,37 @@ virtio_crypto_sym_input_data_helper(VirtIODevice *vdev, } } +static void +virtio_crypto_akcipher_input_data_helper(VirtIODevice *vdev, + VirtIOCryptoReq *req, int32_t status, + CryptoDevBackendAsymOpInfo *asym_op_info) +{ + size_t s, len; + + if (status != VIRTIO_CRYPTO_OK) { + return; + } + + /* For verify, we don't need write anything back */ + if (req->op_info.op_code == VIRTIO_CRYPTO_AKCIPHER_VERIFY) { + return; + } + + len = asym_op_info->dst_len; + if (!len) { + return; + } + + s = iov_from_buf(req->in_iov, req->in_num, 0, asym_op_info->dst, len); + if (s != len) { + virtio_error(vdev, "virtio-crypto asym dest data incorrect"); + return; + } + + iov_discard_front(&req->in_iov, &req->in_num, len); +} + + static void virtio_crypto_req_complete(VirtIOCryptoReq *req, uint8_t status) { VirtIOCrypto *vcrypto = req->vcrypto; @@ -404,7 +524,10 @@ static void virtio_crypto_req_complete(VirtIOCryptoReq *req, uint8_t status) if (req->flags == CRYPTODEV_BACKEND_ALG_SYM) { virtio_crypto_sym_input_data_helper(vdev, req, status, - req->u.sym_op_info); + req->op_info.u.sym_op_info); + } else if (req->flags == CRYPTODEV_BACKEND_ALG_ASYM) { + virtio_crypto_akcipher_input_data_helper(vdev, req, status, + req->op_info.u.asym_op_info); } stb_p(&req->in->status, status); virtqueue_push(req->vq, &req->elem, req->in_len); @@ -543,39 +666,98 @@ err: static int virtio_crypto_handle_sym_req(VirtIOCrypto *vcrypto, struct virtio_crypto_sym_data_req *req, - CryptoDevBackendSymOpInfo **sym_op_info, + CryptoDevBackendOpInfo *op_info, struct iovec *iov, unsigned int out_num) { VirtIODevice *vdev = VIRTIO_DEVICE(vcrypto); + CryptoDevBackendSymOpInfo *sym_op_info; uint32_t op_type; - CryptoDevBackendSymOpInfo *op_info; op_type = ldl_le_p(&req->op_type); - if (op_type == VIRTIO_CRYPTO_SYM_OP_CIPHER) { - op_info = virtio_crypto_sym_op_helper(vdev, &req->u.cipher.para, + sym_op_info = virtio_crypto_sym_op_helper(vdev, &req->u.cipher.para, NULL, iov, out_num); - if (!op_info) { + if (!sym_op_info) { return -EFAULT; } - op_info->op_type = op_type; } else if (op_type == VIRTIO_CRYPTO_SYM_OP_ALGORITHM_CHAINING) { - op_info = virtio_crypto_sym_op_helper(vdev, NULL, + sym_op_info = virtio_crypto_sym_op_helper(vdev, NULL, &req->u.chain.para, iov, out_num); - if (!op_info) { + if (!sym_op_info) { return -EFAULT; } - op_info->op_type = op_type; } else { /* VIRTIO_CRYPTO_SYM_OP_NONE */ error_report("virtio-crypto unsupported cipher type"); return -VIRTIO_CRYPTO_NOTSUPP; } - *sym_op_info = op_info; + sym_op_info->op_type = op_type; + op_info->u.sym_op_info = sym_op_info; + + return 0; +} + +static int +virtio_crypto_handle_asym_req(VirtIOCrypto *vcrypto, + struct virtio_crypto_akcipher_data_req *req, + CryptoDevBackendOpInfo *op_info, + struct iovec *iov, unsigned int out_num) +{ + VirtIODevice *vdev = VIRTIO_DEVICE(vcrypto); + CryptoDevBackendAsymOpInfo *asym_op_info; + uint32_t src_len; + uint32_t dst_len; + uint32_t len; + uint8_t *src = NULL; + uint8_t *dst = NULL; + + asym_op_info = g_malloc0(sizeof(CryptoDevBackendAsymOpInfo)); + src_len = ldl_le_p(&req->para.src_data_len); + dst_len = ldl_le_p(&req->para.dst_data_len); + + if (src_len > 0) { + src = g_malloc0(src_len); + len = iov_to_buf(iov, out_num, 0, src, src_len); + if (unlikely(len != src_len)) { + virtio_error(vdev, "virtio-crypto asym src data incorrect" + "expected %u, actual %u", src_len, len); + goto err; + } + + iov_discard_front(&iov, &out_num, src_len); + } + + if (dst_len > 0) { + dst = g_malloc0(dst_len); + + if (op_info->op_code == VIRTIO_CRYPTO_AKCIPHER_VERIFY) { + len = iov_to_buf(iov, out_num, 0, dst, dst_len); + if (unlikely(len != dst_len)) { + virtio_error(vdev, "virtio-crypto asym dst data incorrect" + "expected %u, actual %u", dst_len, len); + goto err; + } + + iov_discard_front(&iov, &out_num, dst_len); + } + } + + asym_op_info->src_len = src_len; + asym_op_info->dst_len = dst_len; + asym_op_info->src = src; + asym_op_info->dst = dst; + op_info->u.asym_op_info = asym_op_info; return 0; + + err: + g_free(asym_op_info); + g_free(src); + g_free(dst); + + return -EFAULT; } static int @@ -595,8 +777,7 @@ virtio_crypto_handle_request(VirtIOCryptoReq *request) unsigned out_num; uint32_t opcode; uint8_t status = VIRTIO_CRYPTO_ERR; - uint64_t session_id; - CryptoDevBackendSymOpInfo *sym_op_info = NULL; + CryptoDevBackendOpInfo *op_info = &request->op_info; Error *local_err = NULL; if (elem->out_num < 1 || elem->in_num < 1) { @@ -639,15 +820,28 @@ virtio_crypto_handle_request(VirtIOCryptoReq *request) request->in_iov = in_iov; opcode = ldl_le_p(&req.header.opcode); - session_id = ldq_le_p(&req.header.session_id); + op_info->session_id = ldq_le_p(&req.header.session_id); + op_info->op_code = opcode; switch (opcode) { case VIRTIO_CRYPTO_CIPHER_ENCRYPT: case VIRTIO_CRYPTO_CIPHER_DECRYPT: + op_info->algtype = request->flags = CRYPTODEV_BACKEND_ALG_SYM; ret = virtio_crypto_handle_sym_req(vcrypto, - &req.u.sym_req, - &sym_op_info, + &req.u.sym_req, op_info, out_iov, out_num); + goto check_result; + + case VIRTIO_CRYPTO_AKCIPHER_ENCRYPT: + case VIRTIO_CRYPTO_AKCIPHER_DECRYPT: + case VIRTIO_CRYPTO_AKCIPHER_SIGN: + case VIRTIO_CRYPTO_AKCIPHER_VERIFY: + op_info->algtype = request->flags = CRYPTODEV_BACKEND_ALG_ASYM; + ret = virtio_crypto_handle_asym_req(vcrypto, + &req.u.akcipher_req, op_info, + out_iov, out_num); + +check_result: /* Serious errors, need to reset virtio crypto device */ if (ret == -EFAULT) { return -1; @@ -655,11 +849,8 @@ virtio_crypto_handle_request(VirtIOCryptoReq *request) virtio_crypto_req_complete(request, VIRTIO_CRYPTO_NOTSUPP); virtio_crypto_free_request(request); } else { - sym_op_info->session_id = session_id; /* Set request's parameter */ - request->flags = CRYPTODEV_BACKEND_ALG_SYM; - request->u.sym_op_info = sym_op_info; ret = cryptodev_backend_crypto_operation(vcrypto->cryptodev, request, queue_index, &local_err); if (ret < 0) { @@ -674,6 +865,7 @@ virtio_crypto_handle_request(VirtIOCryptoReq *request) virtio_crypto_free_request(request); } break; + case VIRTIO_CRYPTO_HASH: case VIRTIO_CRYPTO_MAC: case VIRTIO_CRYPTO_AEAD_ENCRYPT: @@ -779,6 +971,7 @@ static void virtio_crypto_init_config(VirtIODevice *vdev) vcrypto->conf.mac_algo_l = vcrypto->conf.cryptodev->conf.mac_algo_l; vcrypto->conf.mac_algo_h = vcrypto->conf.cryptodev->conf.mac_algo_h; vcrypto->conf.aead_algo = vcrypto->conf.cryptodev->conf.aead_algo; + vcrypto->conf.akcipher_algo = vcrypto->conf.cryptodev->conf.akcipher_algo; vcrypto->conf.max_cipher_key_len vcrypto->conf.cryptodev->conf.max_cipher_key_len; vcrypto->conf.max_auth_key_len @@ -891,6 +1084,7 @@ static void virtio_crypto_get_config(VirtIODevice *vdev, uint8_t *config) stl_le_p(&crypto_cfg.max_cipher_key_len, c->conf.max_cipher_key_len); stl_le_p(&crypto_cfg.max_auth_key_len, c->conf.max_auth_key_len); stq_le_p(&crypto_cfg.max_size, c->conf.max_size); + stl_le_p(&crypto_cfg.akcipher_algo, c->conf.akcipher_algo); memcpy(config, &crypto_cfg, c->config_size); } diff --git a/include/crypto/akcipher.h b/include/crypto/akcipher.h new file mode 100644 index 0000000000..8c3aae9b6c --- /dev/null +++ b/include/crypto/akcipher.h @@ -0,0 +1,77 @@ +/* + * QEMU Crypto asymmetric algorithms + * + * Copyright (c) 2022 Bytedance + * Author: zhenwei pi <pizhenwei at bytedance.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#ifndef QCRYPTO_AKCIPHER_H +#define QCRYPTO_AKCIPHER_H + +#include "qemu/typedefs.h" + +typedef struct QCryptoAkcipher QCryptoAkcipher; +typedef struct QCryptoAkcipherDriver QCryptoAkcipherDriver; + +struct QCryptoAkcipherDriver { + int (*encrypt)(struct QCryptoAkcipher *akcipher, + const void *data, size_t data_len, + void *enc, size_t enc_len, Error **errp); + int (*decrypt)(struct QCryptoAkcipher *akcipher, + const void *enc, size_t enc_len, + void *data, size_t data_len, Error **errp); + int (*sign)(struct QCryptoAkcipher *akcipher, + const void *data, size_t data_len, + void *sig, size_t sig_len, Error **errp); + int (*verify)(struct QCryptoAkcipher *akcipher, + const void *sig, size_t sig_len, + const void *data, size_t data_len, Error **errp); + int (*free)(struct QCryptoAkcipher *akcipher, Error **errp); +}; + +struct QCryptoAkcipher { + uint32_t alg; + bool private; + uint8_t *key; + size_t keylen; + QCryptoAkcipherDriver *driver; +}; + +QCryptoAkcipher *qcrypto_akcipher_new(uint32_t alg, bool private, + const uint8_t *key, size_t keylen, + void *para, int index, Error **errp); + +int qcrypto_akcipher_encrypt(QCryptoAkcipher *akcipher, + const void *data, size_t data_len, + void *enc, size_t enc_len, Error **errp); + +int qcrypto_akcipher_decrypt(struct QCryptoAkcipher *akcipher, + const void *enc, size_t enc_len, + void *data, size_t data_len, Error **errp); + +int qcrypto_akcipher_sign(struct QCryptoAkcipher *akcipher, + const void *data, size_t data_len, + void *sig, size_t sig_len, Error **errp); + +int qcrypto_akcipher_verify(struct QCryptoAkcipher *akcipher, + const void *sig, size_t sig_len, + const void *data, size_t data_len, Error **errp); + +int qcrypto_akcipher_free(struct QCryptoAkcipher *akcipher, Error **errp); + + +#endif /* QCRYPTO_AKCIPHER_H */ diff --git a/include/hw/virtio/virtio-crypto.h b/include/hw/virtio/virtio-crypto.h index a2228d7b2e..348749f5d5 100644 --- a/include/hw/virtio/virtio-crypto.h +++ b/include/hw/virtio/virtio-crypto.h @@ -50,6 +50,7 @@ typedef struct VirtIOCryptoConf { uint32_t mac_algo_l; uint32_t mac_algo_h; uint32_t aead_algo; + uint32_t akcipher_algo; /* Maximum length of cipher key */ uint32_t max_cipher_key_len; @@ -71,9 +72,7 @@ typedef struct VirtIOCryptoReq { size_t in_len; VirtQueue *vq; struct VirtIOCrypto *vcrypto; - union { - CryptoDevBackendSymOpInfo *sym_op_info; - } u; + CryptoDevBackendOpInfo op_info; } VirtIOCryptoReq; typedef struct VirtIOCryptoQueue { diff --git a/include/sysemu/cryptodev.h b/include/sysemu/cryptodev.h index f4d4057d4d..b306775849 100644 --- a/include/sysemu/cryptodev.h +++ b/include/sysemu/cryptodev.h @@ -50,13 +50,13 @@ typedef struct CryptoDevBackendClient enum CryptoDevBackendAlgType { CRYPTODEV_BACKEND_ALG_SYM, + CRYPTODEV_BACKEND_ALG_ASYM, CRYPTODEV_BACKEND_ALG__MAX, }; /** * CryptoDevBackendSymSessionInfo: * - * @op_code: operation code (refer to virtio_crypto.h) * @cipher_alg: algorithm type of CIPHER * @key_len: byte length of cipher key * @hash_alg: algorithm type of HASH/MAC @@ -74,7 +74,6 @@ enum CryptoDevBackendAlgType { */ typedef struct CryptoDevBackendSymSessionInfo { /* corresponding with virtio crypto spec */ - uint32_t op_code; uint32_t cipher_alg; uint32_t key_len; uint32_t hash_alg; @@ -89,11 +88,41 @@ typedef struct CryptoDevBackendSymSessionInfo { uint8_t *auth_key; } CryptoDevBackendSymSessionInfo; +/** + * CryptoDevBackendAsymSessionInfo: + */ +typedef struct CryptoDevBackendRsaPara { + uint32_t padding_algo; + uint32_t hash_algo; +} CryptoDevBackendRsaPara; + +typedef struct CryptoDevBackendEcdsaPara { + uint32_t curve_id; +} CryptoDevBackendEcdsaPara; + +typedef struct CryptoDevBackendAsymSessionInfo { + /* corresponding with virtio crypto spec */ + uint32_t algo; + uint32_t keytype; + uint32_t keylen; + uint8_t *key; + union { + CryptoDevBackendRsaPara rsa; + CryptoDevBackendEcdsaPara ecdsa; + } u; +} CryptoDevBackendAsymSessionInfo; + +typedef struct CryptoDevBackendSessionInfo { + uint32_t op_code; + union { + CryptoDevBackendSymSessionInfo sym_sess_info; + CryptoDevBackendAsymSessionInfo asym_sess_info; + } u; +} CryptoDevBackendSessionInfo; + /** * CryptoDevBackendSymOpInfo: * - * @session_id: session index which was previously - * created by cryptodev_backend_sym_create_session() * @aad_len: byte length of additional authenticated data * @iv_len: byte length of initialization vector or counter * @src_len: byte length of source data @@ -119,7 +148,6 @@ typedef struct CryptoDevBackendSymSessionInfo { * */ typedef struct CryptoDevBackendSymOpInfo { - uint64_t session_id; uint32_t aad_len; uint32_t iv_len; uint32_t src_len; @@ -138,6 +166,33 @@ typedef struct CryptoDevBackendSymOpInfo { uint8_t data[]; } CryptoDevBackendSymOpInfo; + +/** + * CryptoDevBackendAsymOpInfo: + * + * @src_len: byte length of source data + * @dst_len: byte length of destination data + * @src: point to the source data + * @dst: point to the destination data + * + */ +typedef struct CryptoDevBackendAsymOpInfo { + uint32_t src_len; + uint32_t dst_len; + uint8_t *src; + uint8_t *dst; +} CryptoDevBackendAsymOpInfo; + +typedef struct CryptoDevBackendOpInfo { + enum CryptoDevBackendAlgType algtype; + uint32_t op_code; + uint64_t session_id; + union { + CryptoDevBackendSymOpInfo *sym_op_info; + CryptoDevBackendAsymOpInfo *asym_op_info; + } u; +} CryptoDevBackendOpInfo; + struct CryptoDevBackendClass { ObjectClass parent_class; @@ -145,13 +200,13 @@ struct CryptoDevBackendClass { void (*cleanup)(CryptoDevBackend *backend, Error **errp); int64_t (*create_session)(CryptoDevBackend *backend, - CryptoDevBackendSymSessionInfo *sess_info, + CryptoDevBackendSessionInfo *sess_info, uint32_t queue_index, Error **errp); int (*close_session)(CryptoDevBackend *backend, uint64_t session_id, uint32_t queue_index, Error **errp); - int (*do_sym_op)(CryptoDevBackend *backend, - CryptoDevBackendSymOpInfo *op_info, + int (*do_op)(CryptoDevBackend *backend, + CryptoDevBackendOpInfo *op_info, uint32_t queue_index, Error **errp); }; @@ -190,6 +245,7 @@ struct CryptoDevBackendConf { uint32_t mac_algo_l; uint32_t mac_algo_h; uint32_t aead_algo; + uint32_t akcipher_algo; /* Maximum length of cipher key */ uint32_t max_cipher_key_len; /* Maximum length of authenticated key */ @@ -247,34 +303,34 @@ void cryptodev_backend_cleanup( Error **errp); /** - * cryptodev_backend_sym_create_session: + * cryptodev_backend_create_session: * @backend: the cryptodev backend object * @sess_info: parameters needed by session creating * @queue_index: queue index of cryptodev backend client * @errp: pointer to a NULL-initialized error object * - * Create a session for symmetric algorithms + * Create a session for symmetric/symmetric algorithms * * Returns: session id on success, or -1 on error */ -int64_t cryptodev_backend_sym_create_session( +int64_t cryptodev_backend_create_session( CryptoDevBackend *backend, - CryptoDevBackendSymSessionInfo *sess_info, + CryptoDevBackendSessionInfo *sess_info, uint32_t queue_index, Error **errp); /** - * cryptodev_backend_sym_close_session: + * cryptodev_backend_close_session: * @backend: the cryptodev backend object * @session_id: the session id * @queue_index: queue index of cryptodev backend client * @errp: pointer to a NULL-initialized error object * - * Close a session for symmetric algorithms which was previously - * created by cryptodev_backend_sym_create_session() + * Close a session for which was previously + * created by cryptodev_backend_create_session() * * Returns: 0 on success, or Negative on error */ -int cryptodev_backend_sym_close_session( +int cryptodev_backend_close_session( CryptoDevBackend *backend, uint64_t session_id, uint32_t queue_index, Error **errp); -- 2.20.1
From: Lei He <helei.sig11 at bytedance.com> Implement RSA algorithm by nettle hogweed, and apply it for virtio-crypto akcipher backend. 1, The self-test framework of crypto layer works fine in guest kernel 2, Test with Linux guest(with asym support), the following script test(note that pkey_XXX is supported only in a newer version of keyutils): - both public key & private key - create/close session - encrypt/decrypt/sign/verify basic driver operation - also test with kernel crypto layer(pkey add/query) All the cases work fine. rm -rf *.der *.pem *.pfx modprobe pkcs8_key_parser # if CONFIG_PKCS8_PRIVATE_KEY_PARSER=m rm -rf /tmp/data dd if=/dev/random of=/tmp/data count=1 bs=226 openssl req -nodes -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -subj "/C=CN/ST=BJ/L=HD/O=qemu/OU=dev/CN=qemu/emailAddress=qemu at qemu.org" openssl pkcs8 -in key.pem -topk8 -nocrypt -outform DER -out key.der openssl x509 -in cert.pem -inform PEM -outform DER -out cert.der PRIV_KEY_ID=`cat key.der | keyctl padd asymmetric test_priv_key @s` echo "priv key id = "$PRIV_KEY_ID PUB_KEY_ID=`cat cert.der | keyctl padd asymmetric test_pub_key @s` echo "pub key id = "$PUB_KEY_ID keyctl pkey_query $PRIV_KEY_ID 0 keyctl pkey_query $PUB_KEY_ID 0 echo "Enc with priv key..." keyctl pkey_encrypt $PRIV_KEY_ID 0 /tmp/data enc=pkcs1 >/tmp/enc.priv echo "Dec with pub key..." keyctl pkey_decrypt $PRIV_KEY_ID 0 /tmp/enc.priv enc=pkcs1 >/tmp/dec cmp /tmp/data /tmp/dec echo "Sign with priv key..." keyctl pkey_sign $PRIV_KEY_ID 0 /tmp/data enc=pkcs1 hash=sha1 > /tmp/sig echo "Verify with pub key..." keyctl pkey_verify $PRIV_KEY_ID 0 /tmp/data /tmp/sig enc=pkcs1 hash=sha1 echo "Enc with pub key..." keyctl pkey_encrypt $PUB_KEY_ID 0 /tmp/data enc=pkcs1 >/tmp/enc.pub echo "Dec with priv key..." keyctl pkey_decrypt $PRIV_KEY_ID 0 /tmp/enc.pub enc=pkcs1 >/tmp/dec cmp /tmp/data /tmp/dec echo "Verify with pub key..." keyctl pkey_verify $PUB_KEY_ID 0 /tmp/data /tmp/sig enc=pkcs1 hash=sha1 Signed-off-by: zhenwei pi <pizhenwei at bytedance.com> Signed-off-by: lei he <helei.sig11 at bytedance.com> --- crypto/akcipher-nettle.c | 486 +++++++++++++++++++++++++++++++++++++++ crypto/akcipher.c | 13 ++ crypto/asn1_decoder.c | 185 +++++++++++++++ crypto/asn1_decoder.h | 42 ++++ crypto/meson.build | 3 + meson.build | 11 + 6 files changed, 740 insertions(+) create mode 100644 crypto/akcipher-nettle.c create mode 100644 crypto/asn1_decoder.c create mode 100644 crypto/asn1_decoder.h diff --git a/crypto/akcipher-nettle.c b/crypto/akcipher-nettle.c new file mode 100644 index 0000000000..6ac39e7abc --- /dev/null +++ b/crypto/akcipher-nettle.c @@ -0,0 +1,486 @@ +/* + * QEMU Crypto akcipher algorithms + * + * Copyright (c) 2022 Bytedance + * Author: lei he <helei.sig11 at bytedance.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <stdbool.h> + +#include <nettle/rsa.h> + +#include "asn1_decoder.h" +#include "crypto/akcipher.h" +#include "crypto/random.h" +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "sysemu/cryptodev.h" +#include "standard-headers/linux/virtio_crypto.h" + +typedef struct QCryptoNettleRsa { + QCryptoAkcipher akcipher; + struct rsa_public_key pub; + struct rsa_private_key priv; + int padding_algo; + int hash_algo; +} QCryptoNettleRsa; + +struct asn1_parse_ctx { + const uint8_t *data; + size_t dlen; +}; + +static int extract_value(void *p, const uint8_t *data, size_t dlen) +{ + struct asn1_parse_ctx *ctx = (struct asn1_parse_ctx *)p; + ctx->data = (uint8_t *)data; + ctx->dlen = dlen; + + return 0; +} + +static int extract_mpi(void *p, const uint8_t *data, size_t dlen) +{ + mpz_t *target = (mpz_t *)p; + nettle_mpz_set_str_256_u(*target, dlen, data); + + return 0; +} + +static QCryptoNettleRsa *qcrypto_nettle_rsa_malloc(void); + +static void qcrypto_nettle_rsa_destroy(void *ptr) +{ + QCryptoNettleRsa *rsa = (QCryptoNettleRsa *)ptr; + if (!rsa) { + return; + } + + rsa_public_key_clear(&rsa->pub); + rsa_private_key_clear(&rsa->priv); + g_free(rsa); +} + +static QCryptoAkcipher *qcrypto_nettle_new_rsa(bool private, const uint8_t *key, + size_t keylen, void *para, + int index, Error **errp); + +QCryptoAkcipher *qcrypto_akcipher_nettle_new(uint32_t alg, bool private, + const uint8_t *key, + size_t keylen, void *para, + int index, Error **errp); + +QCryptoAkcipher *qcrypto_akcipher_nettle_new(uint32_t alg, bool private, + const uint8_t *key, + size_t keylen, void *para, + int index, Error **errp) +{ + switch (alg) { + case VIRTIO_CRYPTO_AKCIPHER_RSA: + return qcrypto_nettle_new_rsa(private, key, keylen, para, index, errp); + default: + error_setg(errp, "Unsupported algorithm: %u", alg); + return NULL; + } + + return NULL; +} + +/* + * Parse ber encoded rsa private key, asn1 schema: + * RsaPrivKey ::= SEQUENCE { + * version INTEGER + * n INTEGER + * e INTEGER + * d INTEGER + * p INTEGER + * q INTEGER + * e1 INTEGER + * e2 INTEGER + * u INTEGER + * } + */ +static int parse_rsa_private_key(QCryptoNettleRsa *rsa, + const uint8_t *key, size_t keylen) +{ + struct asn1_parse_ctx ctx; + + if (ber_decode_seq(&key, &keylen, extract_value, &ctx) != 0 || + keylen != 0) { + return -1; + } + + if (ber_decode_int(&ctx.data, &ctx.dlen, NULL, NULL) != 0 || + ber_decode_int(&ctx.data, &ctx.dlen, extract_mpi, &rsa->pub.n) != 0 || + ber_decode_int(&ctx.data, &ctx.dlen, extract_mpi, &rsa->pub.e) != 0 || + ber_decode_int(&ctx.data, &ctx.dlen, extract_mpi, &rsa->priv.d) != 0 || + ber_decode_int(&ctx.data, &ctx.dlen, extract_mpi, &rsa->priv.p) != 0 || + ber_decode_int(&ctx.data, &ctx.dlen, extract_mpi, &rsa->priv.q) != 0 || + ber_decode_int(&ctx.data, &ctx.dlen, extract_mpi, &rsa->priv.a) != 0 || + ber_decode_int(&ctx.data, &ctx.dlen, extract_mpi, &rsa->priv.b) != 0 || + ber_decode_int(&ctx.data, &ctx.dlen, extract_mpi, &rsa->priv.c) != 0 || + ctx.dlen != 0) { + return -1; + } + + if (!rsa_public_key_prepare(&rsa->pub)) { + return -1; + } + + /* + * Since in the kernel's unit test, the p, q, a, b, c of some + * private keys is 0, only the simplest length check is done here + */ + rsa->priv.size = rsa->pub.size; + + return 0; +} + +/* + * Parse ber encoded rsa pubkey, asn1 schema: + * RsaPrivKey ::= SEQUENCE { + * n INTEGER + * e INTEGER + * } + */ +static int parse_rsa_public_key(QCryptoNettleRsa *rsa, + const uint8_t *key, + size_t keylen) +{ + struct asn1_parse_ctx ctx; + + if (ber_decode_seq(&key, &keylen, extract_value, &ctx) != 0 || + keylen != 0) { + return -1; + } + + if (ber_decode_int(&ctx.data, &ctx.dlen, extract_mpi, &rsa->pub.n) != 0 || + ber_decode_int(&ctx.data, &ctx.dlen, extract_mpi, &rsa->pub.e) != 0 || + ctx.dlen != 0) { + return -1; + } + + if (!rsa_public_key_prepare(&rsa->pub)) { + return -1; + } + + return 0; +} + +static QCryptoAkcipher *qcrypto_nettle_new_rsa(bool private, const uint8_t *key, + size_t keylen, void *para, + int index, Error **errp) +{ + QCryptoNettleRsa *rsa = qcrypto_nettle_rsa_malloc(); + CryptoDevBackendRsaPara *p = (CryptoDevBackendRsaPara *)para; + rsa->padding_algo = p->padding_algo; + rsa->hash_algo = p->hash_algo; + + if (private && parse_rsa_private_key(rsa, key, keylen) == 0) { + return (QCryptoAkcipher *)rsa; + } else if (!private && parse_rsa_public_key(rsa, key, keylen) == 0) { + return (QCryptoAkcipher *)rsa; + } + + qcrypto_nettle_rsa_destroy(rsa); + error_setg(errp, "Failed to parse %s key", private ? "private" : "public"); + + return NULL; +} + + +/* + * nettle does not provide RSA interfaces without padding, + * here we implemented rsa algorithm with nettle/mpz. + */ +static int _rsa_enc_raw(QCryptoNettleRsa *rsa, const void *data, + size_t data_len, void *enc, + size_t enc_len, Error **errp) +{ + mpz_t m; + + nettle_mpz_init_set_str_256_u(m, data_len, data); + /* (1) Validate 0 <= m < n */ + if (mpz_cmp_ui(m, 0) < 0 || mpz_cmp(m, rsa->pub.n) >= 0) { + error_setg(errp, "Failed to validate input data"); + return -VIRTIO_CRYPTO_BADMSG; + } + + /* (2) c = m ^ e mod n */ + mpz_powm(m, m, rsa->pub.e, rsa->pub.n); + nettle_mpz_get_str_256(enc_len, (uint8_t *)enc, m); + + mpz_clear(m); + + return 0; +} + +static int _rsa_dec_raw(QCryptoNettleRsa *rsa, + const void *enc, + size_t enc_len, + void *data, + size_t data_len, + Error **errp) +{ + mpz_t c; + nettle_mpz_init_set_str_256_u(c, enc_len, enc); + + /* (1) Validate 0 <= c < n */ + if (mpz_cmp_ui(c, 0) < 0 || mpz_cmp(c, rsa->pub.n) >= 0) { + error_setg(errp, "Failed to validate input data"); + return -VIRTIO_CRYPTO_BADMSG; + } + + /* (2) m = c ^ d mod n */ + mpz_powm(c, c, rsa->priv.d, rsa->pub.n); + nettle_mpz_get_str_256(data_len, (uint8_t *)data, c); + mpz_clear(c); + + return 0; +} + +static void wrap_nettle_random_func(void *ctx, size_t len, uint8_t *out) +{ + /* TODO: check result */ + qcrypto_random_bytes(out, len, NULL); +} + +static int qcrypto_nettle_rsa_encrypt(QCryptoAkcipher *akcipher_driver, + const void *data, size_t data_len, + void *enc, size_t enc_len, + Error **errp) +{ + + QCryptoNettleRsa *rsa + container_of(akcipher_driver, QCryptoNettleRsa, akcipher); + mpz_t c; + int enc_ret; + + if (data_len > rsa->pub.size || enc_len < rsa->pub.size) { + error_setg(errp, "Invalid buffer size"); + return -VIRTIO_CRYPTO_BADMSG; + } + + switch (rsa->padding_algo) { + case VIRTIO_CRYPTO_RSA_RAW_PADDING: + return _rsa_enc_raw(rsa, data, data_len, enc, enc_len, errp); + + case VIRTIO_CRYPTO_RSA_PKCS1_PADDING: + mpz_init(c); + enc_ret = rsa_encrypt(&rsa->pub, NULL, wrap_nettle_random_func, + data_len, (uint8_t *)data, c); + if (enc_ret != 1) { + error_setg(errp, "Failed to encrypt"); + } else { + nettle_mpz_get_str_256(enc_len, (uint8_t *)enc, c); + } + mpz_clear(c); + return enc_ret == 1 ? 0 : -1; + + default: + error_setg(errp, "Unknown padding"); + return -VIRTIO_CRYPTO_NOTSUPP; + } + + return -1; +} + +static int qcrypto_nettle_rsa_decrypt(QCryptoAkcipher *akcipher, + const void *enc, size_t enc_len, + void *data, size_t data_len, + Error **errp) +{ + QCryptoNettleRsa *rsa = container_of(akcipher, QCryptoNettleRsa, akcipher); + mpz_t m; + int dec_ret; + + if (enc_len > rsa->priv.size || data_len < rsa->priv.size) { + error_setg(errp, "Invalid buffer size"); + return -VIRTIO_CRYPTO_BADMSG; + } + + switch (rsa->padding_algo) { + case VIRTIO_CRYPTO_RSA_RAW_PADDING: + return _rsa_dec_raw(rsa, enc, enc_len, data, data_len, errp); + + case VIRTIO_CRYPTO_RSA_PKCS1_PADDING: + mpz_init(m); + dec_ret = rsa_encrypt(&rsa->pub, NULL, wrap_nettle_random_func, + data_len, (uint8_t *)data, m); + if (dec_ret != 1) { + error_setg(errp, "Failed to encrypt"); + } else { + nettle_mpz_get_str_256(data_len, (uint8_t *)data_len, m); + } + mpz_clear(m); + return dec_ret == 1 ? 0 : -1; + + default: + error_setg(errp, "Unknown padding"); + return -VIRTIO_CRYPTO_NOTSUPP; + } + + return -1; +} + + +static int qcrypto_nettle_rsa_sign(QCryptoAkcipher *akcipher, + const void *data, size_t data_len, + void *sig, size_t sig_len, Error **errp) +{ + QCryptoNettleRsa *rsa = container_of(akcipher, QCryptoNettleRsa, akcipher); + int ret; + mpz_t s; + + /* + * The RSA algorithm cannot be used for signature/verification + * without padding. + */ + if (rsa->padding_algo == VIRTIO_CRYPTO_RSA_RAW_PADDING) { + error_setg(errp, "Try to make signature without padding"); + return -VIRTIO_CRYPTO_NOTSUPP; + } + + if (data_len > rsa->priv.size || sig_len < rsa->priv.size) { + error_setg(errp, "Invalid buffer size"); + return -VIRTIO_CRYPTO_BADMSG; + } + + mpz_init(s); + switch (rsa->hash_algo) { + case VIRTIO_CRYPTO_RSA_MD5: + ret = rsa_md5_sign_digest(&rsa->priv, data, s); + break; + + case VIRTIO_CRYPTO_RSA_SHA1: + ret = rsa_sha1_sign_digest(&rsa->priv, data, s); + break; + + case VIRTIO_CRYPTO_RSA_SHA256: + ret = rsa_sha256_sign_digest(&rsa->priv, data, s); + break; + + case VIRTIO_CRYPTO_RSA_SHA512: + ret = rsa_sha512_sign_digest(&rsa->priv, data, s); + break; + + default: + error_setg(errp, "Unknown hash algorithm"); + ret = -VIRTIO_CRYPTO_NOTSUPP; + goto clear; + } + + if (ret != 1) { + error_setg(errp, "Failed to make signature"); + ret = -VIRTIO_CRYPTO_BADMSG; + goto clear; + } + nettle_mpz_get_str_256(sig_len, (uint8_t *)sig, s); + ret = 0; + +clear: + mpz_clear(s); + + return ret; +} + +static int qcrypto_nettle_rsa_verify(QCryptoAkcipher *akcipher, + const void *sig, size_t sig_len, + const void *data, size_t data_len, + Error **errp) +{ + QCryptoNettleRsa *rsa = container_of(akcipher, QCryptoNettleRsa, akcipher); + + int ret; + mpz_t s; + + /* + * The RSA algorithm cannot be used for signature/verification + * without padding. + */ + if (rsa->padding_algo == VIRTIO_CRYPTO_RSA_RAW_PADDING) { + error_setg(errp, "Operation not supported"); + return -1; + } + if (data_len > rsa->pub.size || sig_len < rsa->pub.size) { + error_setg(errp, "Invalid buffer size"); + return -1; + } + + nettle_mpz_init_set_str_256_u(s, sig_len, sig); + switch (rsa->hash_algo) { + case VIRTIO_CRYPTO_RSA_MD5: + ret = rsa_md5_verify_digest(&rsa->pub, data, s); + break; + + case VIRTIO_CRYPTO_RSA_SHA1: + ret = rsa_sha1_verify_digest(&rsa->pub, data, s); + break; + + case VIRTIO_CRYPTO_RSA_SHA256: + ret = rsa_sha256_verify_digest(&rsa->pub, data, s); + break; + + case VIRTIO_CRYPTO_RSA_SHA512: + ret = rsa_sha512_verify_digest(&rsa->pub, data, s); + break; + + default: + error_setg(errp, "Unknown hash algorithm"); + ret = -VIRTIO_CRYPTO_NOTSUPP; + goto clear; + } + + if (ret != 1) { + error_setg(errp, "Failed to verify"); + ret = -VIRTIO_CRYPTO_KEY_REJECTED; + goto clear; + } + ret = 0; + +clear: + mpz_clear(s); + + return ret; +} + +static int qcrypto_nettle_rsa_free(struct QCryptoAkcipher *akcipher, + Error **errp) +{ + qcrypto_nettle_rsa_destroy(akcipher); + + return 0; +} + +QCryptoAkcipherDriver nettle_rsa = { + .encrypt = qcrypto_nettle_rsa_encrypt, + .decrypt = qcrypto_nettle_rsa_decrypt, + .sign = qcrypto_nettle_rsa_sign, + .verify = qcrypto_nettle_rsa_verify, + .free = qcrypto_nettle_rsa_free, +}; + +static QCryptoNettleRsa *qcrypto_nettle_rsa_malloc(void) +{ + QCryptoNettleRsa *rsa = g_malloc0(sizeof(QCryptoNettleRsa)); + memset(rsa, 0, sizeof(QCryptoNettleRsa)); + rsa->akcipher.driver = &nettle_rsa; + rsa_public_key_init(&rsa->pub); + rsa_private_key_init(&rsa->priv); + + return rsa; +} diff --git a/crypto/akcipher.c b/crypto/akcipher.c index ac8d1c9bf1..f5bdc35e18 100644 --- a/crypto/akcipher.c +++ b/crypto/akcipher.c @@ -24,6 +24,12 @@ #include "qapi/error.h" #include "crypto/akcipher.h" +QCryptoAkcipher *qcrypto_akcipher_nettle_new(uint32_t alg, bool private, + const uint8_t *key, + size_t keylen, + void *para, + int index, Error **errp); + QCryptoAkcipher *qcrypto_akcipher_new(uint32_t alg, bool private, const uint8_t *key, size_t keylen, void *para, @@ -31,6 +37,13 @@ QCryptoAkcipher *qcrypto_akcipher_new(uint32_t alg, bool private, { QCryptoAkcipher *akcipher = NULL; +#ifdef CONFIG_HOGWEED + akcipher = qcrypto_akcipher_nettle_new(alg, private, key, keylen, + para, index, errp); +#else + error_setg(errp, "qcrypto akcipher has no nettle/hogweed support"); +#endif + return akcipher; } diff --git a/crypto/asn1_decoder.c b/crypto/asn1_decoder.c new file mode 100644 index 0000000000..bfb145e84e --- /dev/null +++ b/crypto/asn1_decoder.c @@ -0,0 +1,185 @@ +/* + * QEMU Crypto akcipher algorithms + * + * Copyright (c) 2022 Bytedance + * Author: lei he <helei.sig11 at bytedance.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <stddef.h> + +#include "crypto/asn1_decoder.h" + +enum ber_type_tag { + ber_type_tag_bool = 0x1, + ber_type_tag_int = 0x2, + ber_type_tag_bit_str = 0x3, + ber_type_tag_oct_str = 0x4, + ber_type_tag_oct_null = 0x5, + ber_type_tag_oct_oid = 0x6, + ber_type_tag_seq = 0x10, + ber_type_tag_set = 0x11, +}; + +#define BER_CONSTRUCTED_MASK 0x20 +#define BER_SHORT_LEN_MASK 0x80 + +static uint8_t ber_peek_byte(const uint8_t **data, size_t *dlen) +{ + return **data; +} + +static int invoke_callback(BerDecodeCb cb, void *ctx, + const uint8_t *value, size_t vlen) +{ + if (!cb) { + return 0; + } + + return cb(ctx, value, vlen); +} + +static void ber_cut_nbytes(const uint8_t **data, size_t *dlen, + size_t nbytes) +{ + *data += nbytes; + *dlen -= nbytes; +} + +static uint8_t ber_cut_byte(const uint8_t **data, size_t *dlen) +{ + uint8_t val = ber_peek_byte(data, dlen); + + ber_cut_nbytes(data, dlen, 1); + + return val; +} + +static int ber_extract_definite_data(const uint8_t **data, size_t *dlen, + BerDecodeCb cb, void *ctx) +{ + const uint8_t *value; + size_t vlen = 0; + uint8_t byte_count = ber_cut_byte(data, dlen); + + /* short format of definite-length */ + if (!(byte_count & BER_SHORT_LEN_MASK)) { + if (byte_count > *dlen) { + return -1; + } + + value = *data; + vlen = byte_count; + ber_cut_nbytes(data, dlen, vlen); + + return invoke_callback(cb, ctx, value, vlen); + } + + /* Ignore highest bit */ + byte_count &= ~BER_SHORT_LEN_MASK; + + /* + * size_t is enough to express the length, although the ber encoding + * standard supports larger length. + */ + if (byte_count > sizeof(size_t)) { + return -1; + } + + while (byte_count--) { + vlen <<= 8; + vlen += ber_cut_byte(data, dlen); + } + + if (vlen > *dlen) { + return -1; + } + + value = *data; + ber_cut_nbytes(data, dlen, vlen); + + return invoke_callback(cb, ctx, value, vlen); +} + +static int ber_extract_undefinite_data(const uint8_t **data, size_t *dlen, + BerDecodeCb cb, void *ctx) +{ + size_t vlen = 0; + const uint8_t *value; + + if (*dlen < 3) { + return -1; + } + + /* skip undefinite-length-mask 0x80 */ + ber_cut_nbytes(data, dlen, 1); + + value = *data; + while (vlen < *dlen) { + if ((*data)[vlen] != 0) { + vlen++; + continue; + } + + if (vlen + 1 < *dlen && (*data[vlen + 1] == 0)) { + ber_cut_nbytes(data, dlen, vlen + 2); + return invoke_callback(cb, ctx, value, vlen); + } + + vlen += 2; + } + + return -1; +} + +static int ber_extract_data(const uint8_t **data, size_t *dlen, + BerDecodeCb cb, void *ctx) +{ + uint8_t val = ber_peek_byte(data, dlen); + + if (val == BER_SHORT_LEN_MASK) { + return ber_extract_undefinite_data(data, dlen, cb, ctx); + } + + return ber_extract_definite_data(data, dlen, cb, ctx); +} + +int ber_decode_int(const uint8_t **data, size_t *dlen, + BerDecodeCb cb, void *ctx) +{ + uint8_t tag = ber_cut_byte(data, dlen); + + /* INTEGER must encoded in primitive-form */ + if (tag != ber_type_tag_int) { + return -1; + } + + return ber_extract_data(data, dlen, cb, ctx); +} + +int ber_decode_seq(const uint8_t **data, size_t *dlen, + BerDecodeCb cb, void *ctx) +{ + uint8_t val = ber_cut_byte(data, dlen); + + /* SEQUENCE must use constructed form */ + if (val != (ber_type_tag_seq | BER_CONSTRUCTED_MASK)) { + return -1; + } + + return ber_extract_data(data, dlen, cb, ctx); +} diff --git a/crypto/asn1_decoder.h b/crypto/asn1_decoder.h new file mode 100644 index 0000000000..d33a7c81c4 --- /dev/null +++ b/crypto/asn1_decoder.h @@ -0,0 +1,42 @@ +/* + * QEMU Crypto akcipher algorithms + * + * Copyright (c) 2022 Bytedance + * Author: lei he <helei.sig11 at bytedance.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#ifndef QCRYPTO_ASN1_DECODER_H +#define QCRYPTO_ASN1_DECODER_H + +/* + * ctx: user content. + * value: the starting address of |value| part of 'Tag-Length-Value' pattern. + * vlen: length of the |value|. + */ +typedef int (*BerDecodeCb) (void *ctx, const uint8_t *value, size_t vlen); + +int ber_decode_int(const uint8_t **data, + size_t *dlen, + BerDecodeCb cb, + void *ctx); + +int ber_decode_seq(const uint8_t **data, + size_t *dlen, + BerDecodeCb cb, + void *ctx); + +#endif /* QCRYPTO_ASN1_DECODER_H */ diff --git a/crypto/meson.build b/crypto/meson.build index 72b36f450a..28d14cb153 100644 --- a/crypto/meson.build +++ b/crypto/meson.build @@ -27,6 +27,9 @@ if nettle.found() if xts == 'private' crypto_ss.add(files('xts.c')) endif + if hogweed.found() + crypto_ss.add(gmp, hogweed, files('akcipher-nettle.c', 'asn1_decoder.c')) + endif elif gcrypt.found() crypto_ss.add(gcrypt, files('hash-gcrypt.c', 'hmac-gcrypt.c', 'pbkdf-gcrypt.c')) elif gnutls_crypto.found() diff --git a/meson.build b/meson.build index 5f43355071..fdc7ffabef 100644 --- a/meson.build +++ b/meson.build @@ -1027,6 +1027,7 @@ endif # gcrypt over nettle for performance reasons. gcrypt = not_found nettle = not_found +hogweed = not_found xts = 'none' if get_option('nettle').enabled() and get_option('gcrypt').enabled() @@ -1064,6 +1065,14 @@ if not gnutls_crypto.found() endif endif +gmp = dependency('gmp', required: false, method: 'pkg-config', kwargs: static_kwargs) +if nettle.found() and gmp.found() + hogweed = dependency('hogweed', version: '>=3.4', + method: 'pkg-config', + required: get_option('nettle'), + kwargs: static_kwargs) +endif + gtk = not_found gtkx11 = not_found vte = not_found @@ -1516,6 +1525,7 @@ config_host_data.set('CONFIG_GNUTLS', gnutls.found()) config_host_data.set('CONFIG_GNUTLS_CRYPTO', gnutls_crypto.found()) config_host_data.set('CONFIG_GCRYPT', gcrypt.found()) config_host_data.set('CONFIG_NETTLE', nettle.found()) +config_host_data.set('CONFIG_HOGWEED', hogweed.found()) config_host_data.set('CONFIG_QEMU_PRIVATE_XTS', xts == 'private') config_host_data.set('CONFIG_MALLOC_TRIM', has_malloc_trim) config_host_data.set('CONFIG_STATX', has_statx) @@ -3413,6 +3423,7 @@ summary_info += {'libgcrypt': gcrypt} summary_info += {'nettle': nettle} if nettle.found() summary_info += {' XTS': xts != 'private'} + summary_info += {' hogweed': hogweed.found()} endif summary_info += {'crypto afalg': config_host.has_key('CONFIG_AF_ALG')} summary_info += {'rng-none': config_host.has_key('CONFIG_RNG_NONE')} -- 2.20.1