/* * QEMU Cryptodev backend for QEMU cipher APIs * * Copyright (c) 2016 HUAWEI TECHNOLOGIES CO., LTD. * * Authors: * Gonglei * * 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 . * */ #include "qemu/osdep.h" #include "sysemu/cryptodev.h" #include "qemu/error-report.h" #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 */ #define TYPE_CRYPTODEV_BACKEND_BUILTIN "cryptodev-backend-builtin" 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? */ QCryptoAkCipher *akcipher; QTAILQ_ENTRY(CryptoDevBackendBuiltinSession) next; } CryptoDevBackendBuiltinSession; /* Max number of symmetric/asymmetric sessions */ #define MAX_NUM_SESSIONS 256 #define CRYPTODEV_BUITLIN_MAX_AUTH_KEY_LEN 512 #define CRYPTODEV_BUITLIN_MAX_CIPHER_KEY_LEN 64 struct CryptoDevBackendBuiltin { CryptoDevBackend parent_obj; CryptoDevBackendBuiltinSession *sessions[MAX_NUM_SESSIONS]; }; static void cryptodev_builtin_init_akcipher(CryptoDevBackend *backend) { QCryptoAkCipherOptions opts; opts.alg = QCRYPTO_AK_CIPHER_ALGO_RSA; opts.u.rsa.padding_alg = QCRYPTO_RSA_PADDING_ALGO_RAW; if (qcrypto_akcipher_supports(&opts)) { backend->conf.crypto_services |= (1u << QCRYPTODEV_BACKEND_SERVICE_TYPE_AKCIPHER); backend->conf.akcipher_algo = 1u << VIRTIO_CRYPTO_AKCIPHER_RSA; } } static void cryptodev_builtin_init( CryptoDevBackend *backend, Error **errp) { /* Only support one queue */ int queues = backend->conf.peers.queues; CryptoDevBackendClient *cc; if (queues != 1) { error_setg(errp, "Only support one queue in cryptdov-builtin backend"); return; } cc = cryptodev_backend_new_client(); cc->info_str = g_strdup_printf("cryptodev-builtin0"); cc->queue_index = 0; cc->type = QCRYPTODEV_BACKEND_TYPE_BUILTIN; backend->conf.peers.ccs[0] = cc; backend->conf.crypto_services = 1u << QCRYPTODEV_BACKEND_SERVICE_TYPE_CIPHER | 1u << QCRYPTODEV_BACKEND_SERVICE_TYPE_HASH | 1u << QCRYPTODEV_BACKEND_SERVICE_TYPE_MAC; backend->conf.cipher_algo_l = 1u << VIRTIO_CRYPTO_CIPHER_AES_CBC; backend->conf.hash_algo = 1u << VIRTIO_CRYPTO_HASH_SHA1; /* * 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(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; cryptodev_builtin_init_akcipher(backend); cryptodev_backend_set_ready(backend, true); } static int cryptodev_builtin_get_unused_session_index( CryptoDevBackendBuiltin *builtin) { size_t i; for (i = 0; i < MAX_NUM_SESSIONS; i++) { if (builtin->sessions[i] == NULL) { return i; } } return -1; } #define AES_KEYSIZE_128 16 #define AES_KEYSIZE_192 24 #define AES_KEYSIZE_256 32 #define AES_KEYSIZE_128_XTS AES_KEYSIZE_256 #define AES_KEYSIZE_256_XTS 64 static int cryptodev_builtin_get_aes_algo(uint32_t key_len, int mode, Error **errp) { int algo; if (key_len == AES_KEYSIZE_128) { algo = QCRYPTO_CIPHER_ALGO_AES_128; } else if (key_len == AES_KEYSIZE_192) { algo = QCRYPTO_CIPHER_ALGO_AES_192; } else if (key_len == AES_KEYSIZE_256) { /* equals AES_KEYSIZE_128_XTS */ if (mode == QCRYPTO_CIPHER_MODE_XTS) { algo = QCRYPTO_CIPHER_ALGO_AES_128; } else { algo = QCRYPTO_CIPHER_ALGO_AES_256; } } else if (key_len == AES_KEYSIZE_256_XTS) { if (mode == QCRYPTO_CIPHER_MODE_XTS) { algo = QCRYPTO_CIPHER_ALGO_AES_256; } else { goto err; } } else { goto err; } return algo; err: error_setg(errp, "Unsupported key length :%u", key_len); return -1; } static int cryptodev_builtin_get_rsa_hash_algo( int virtio_rsa_hash, Error **errp) { switch (virtio_rsa_hash) { case VIRTIO_CRYPTO_RSA_MD5: return QCRYPTO_HASH_ALGO_MD5; case VIRTIO_CRYPTO_RSA_SHA1: return QCRYPTO_HASH_ALGO_SHA1; case VIRTIO_CRYPTO_RSA_SHA256: return QCRYPTO_HASH_ALGO_SHA256; case VIRTIO_CRYPTO_RSA_SHA512: return QCRYPTO_HASH_ALGO_SHA512; default: error_setg(errp, "Unsupported rsa hash algo: %d", virtio_rsa_hash); return -1; } } static int cryptodev_builtin_set_rsa_options( int virtio_padding_algo, int virtio_hash_algo, QCryptoAkCipherOptionsRSA *opt, Error **errp) { if (virtio_padding_algo == VIRTIO_CRYPTO_RSA_PKCS1_PADDING) { int hash_alg; hash_alg = cryptodev_builtin_get_rsa_hash_algo(virtio_hash_algo, errp); if (hash_alg < 0) { return -1; } opt->hash_alg = hash_alg; opt->padding_alg = QCRYPTO_RSA_PADDING_ALGO_PKCS1; return 0; } if (virtio_padding_algo == VIRTIO_CRYPTO_RSA_RAW_PADDING) { opt->padding_alg = QCRYPTO_RSA_PADDING_ALGO_RAW; return 0; } error_setg(errp, "Unsupported rsa padding algo: %d", virtio_padding_algo); return -1; } static int cryptodev_builtin_create_cipher_session( CryptoDevBackendBuiltin *builtin, CryptoDevBackendSymSessionInfo *sess_info, Error **errp) { int algo; int mode; QCryptoCipher *cipher; int index; CryptoDevBackendBuiltinSession *sess; if (sess_info->op_type != VIRTIO_CRYPTO_SYM_OP_CIPHER) { error_setg(errp, "Unsupported optype :%u", sess_info->op_type); 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; } switch (sess_info->cipher_alg) { case VIRTIO_CRYPTO_CIPHER_AES_ECB: mode = QCRYPTO_CIPHER_MODE_ECB; algo = cryptodev_builtin_get_aes_algo(sess_info->key_len, mode, errp); if (algo < 0) { return -1; } break; case VIRTIO_CRYPTO_CIPHER_AES_CBC: mode = QCRYPTO_CIPHER_MODE_CBC; algo = cryptodev_builtin_get_aes_algo(sess_info->key_len, mode, errp); if (algo < 0) { return -1; } break; case VIRTIO_CRYPTO_CIPHER_AES_CTR: mode = QCRYPTO_CIPHER_MODE_CTR; algo = cryptodev_builtin_get_aes_algo(sess_info->key_len, mode, errp); if (algo < 0) { return -1; } break; case VIRTIO_CRYPTO_CIPHER_AES_XTS: mode = QCRYPTO_CIPHER_MODE_XTS; algo = cryptodev_builtin_get_aes_algo(sess_info->key_len, mode, errp); if (algo < 0) { return -1; } break; case VIRTIO_CRYPTO_CIPHER_3DES_ECB: mode = QCRYPTO_CIPHER_MODE_ECB; algo = QCRYPTO_CIPHER_ALGO_3DES; break; case VIRTIO_CRYPTO_CIPHER_3DES_CBC: mode = QCRYPTO_CIPHER_MODE_CBC; algo = QCRYPTO_CIPHER_ALGO_3DES; break; case VIRTIO_CRYPTO_CIPHER_3DES_CTR: mode = QCRYPTO_CIPHER_MODE_CTR; algo = QCRYPTO_CIPHER_ALGO_3DES; break; default: error_setg(errp, "Unsupported cipher alg :%u", sess_info->cipher_alg); return -1; } cipher = qcrypto_cipher_new(algo, mode, sess_info->cipher_key, sess_info->key_len, errp); if (!cipher) { return -1; } sess = g_new0(CryptoDevBackendBuiltinSession, 1); sess->cipher = cipher; sess->direction = sess_info->direction; sess->type = sess_info->op_type; builtin->sessions[index] = sess; return index; } static int cryptodev_builtin_create_akcipher_session( CryptoDevBackendBuiltin *builtin, CryptoDevBackendAsymSessionInfo *sess_info, Error **errp) { CryptoDevBackendBuiltinSession *sess; QCryptoAkCipher *akcipher; int index; QCryptoAkCipherKeyType type; QCryptoAkCipherOptions opts; switch (sess_info->algo) { case VIRTIO_CRYPTO_AKCIPHER_RSA: opts.alg = QCRYPTO_AK_CIPHER_ALGO_RSA; if (cryptodev_builtin_set_rsa_options(sess_info->u.rsa.padding_algo, sess_info->u.rsa.hash_algo, &opts.u.rsa, errp) != 0) { return -1; } break; /* TODO support DSA&ECDSA until qemu crypto framework support these */ default: error_setg(errp, "Unsupported akcipher alg %u", sess_info->algo); return -1; } switch (sess_info->keytype) { case VIRTIO_CRYPTO_AKCIPHER_KEY_TYPE_PUBLIC: type = QCRYPTO_AK_CIPHER_KEY_TYPE_PUBLIC; break; case VIRTIO_CRYPTO_AKCIPHER_KEY_TYPE_PRIVATE: type = QCRYPTO_AK_CIPHER_KEY_TYPE_PRIVATE; 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(&opts, type, sess_info->key, sess_info->keylen, errp); if (!akcipher) { return -1; } sess = g_new0(CryptoDevBackendBuiltinSession, 1); sess->akcipher = akcipher; builtin->sessions[index] = sess; return index; } static int cryptodev_builtin_create_session( CryptoDevBackend *backend, CryptoDevBackendSessionInfo *sess_info, uint32_t queue_index, CryptoDevCompletionFunc cb, void *opaque) { CryptoDevBackendBuiltin *builtin = CRYPTODEV_BACKEND_BUILTIN(backend); CryptoDevBackendSymSessionInfo *sym_sess_info; CryptoDevBackendAsymSessionInfo *asym_sess_info; int ret, status; Error *local_error = NULL; switch (sess_info->op_code) { case VIRTIO_CRYPTO_CIPHER_CREATE_SESSION: sym_sess_info = &sess_info->u.sym_sess_info; ret = cryptodev_builtin_create_cipher_session( builtin, sym_sess_info, &local_error); break; case VIRTIO_CRYPTO_AKCIPHER_CREATE_SESSION: asym_sess_info = &sess_info->u.asym_sess_info; ret = cryptodev_builtin_create_akcipher_session( builtin, asym_sess_info, &local_error); break; case VIRTIO_CRYPTO_HASH_CREATE_SESSION: case VIRTIO_CRYPTO_MAC_CREATE_SESSION: default: error_report("Unsupported opcode :%" PRIu32 "", sess_info->op_code); return -VIRTIO_CRYPTO_NOTSUPP; } if (local_error) { error_report_err(local_error); } if (ret < 0) { status = -VIRTIO_CRYPTO_ERR; } else { sess_info->session_id = ret; status = VIRTIO_CRYPTO_OK; } if (cb) { cb(opaque, status); } return 0; } static int cryptodev_builtin_close_session( CryptoDevBackend *backend, uint64_t session_id, uint32_t queue_index, CryptoDevCompletionFunc cb, void *opaque) { CryptoDevBackendBuiltin *builtin = CRYPTODEV_BACKEND_BUILTIN(backend); CryptoDevBackendBuiltinSession *session; if (session_id >= MAX_NUM_SESSIONS || !builtin->sessions[session_id]) { return -VIRTIO_CRYPTO_INVSESS; } session = builtin->sessions[session_id]; if (session->cipher) { qcrypto_cipher_free(session->cipher); } else if (session->akcipher) { qcrypto_akcipher_free(session->akcipher); } g_free(session); builtin->sessions[session_id] = NULL; if (cb) { cb(opaque, VIRTIO_CRYPTO_OK); } return 0; } static int cryptodev_builtin_sym_operation( CryptoDevBackendBuiltinSession *sess, CryptoDevBackendSymOpInfo *op_info, Error **errp) { int ret; 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; } if (op_info->iv_len > 0) { ret = qcrypto_cipher_setiv(sess->cipher, op_info->iv, op_info->iv_len, errp); if (ret < 0) { return -VIRTIO_CRYPTO_ERR; } } if (sess->direction == VIRTIO_CRYPTO_OP_ENCRYPT) { ret = qcrypto_cipher_encrypt(sess->cipher, op_info->src, op_info->dst, op_info->src_len, errp); if (ret < 0) { return -VIRTIO_CRYPTO_ERR; } } else { ret = qcrypto_cipher_decrypt(sess->cipher, op_info->src, op_info->dst, op_info->src_len, errp); if (ret < 0) { 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 < 0) { if (op_code == VIRTIO_CRYPTO_AKCIPHER_VERIFY) { return -VIRTIO_CRYPTO_KEY_REJECTED; } return -VIRTIO_CRYPTO_ERR; } /* Buffer is too short, typically the driver should handle this case */ if (unlikely(ret > op_info->dst_len)) { if (errp && !*errp) { error_setg(errp, "dst buffer too short"); } return -VIRTIO_CRYPTO_ERR; } op_info->dst_len = ret; return VIRTIO_CRYPTO_OK; } static int cryptodev_builtin_operation( CryptoDevBackend *backend, CryptoDevBackendOpInfo *op_info) { CryptoDevBackendBuiltin *builtin = CRYPTODEV_BACKEND_BUILTIN(backend); CryptoDevBackendBuiltinSession *sess; CryptoDevBackendSymOpInfo *sym_op_info; CryptoDevBackendAsymOpInfo *asym_op_info; QCryptodevBackendAlgoType algtype = op_info->algtype; int status = -VIRTIO_CRYPTO_ERR; Error *local_error = NULL; if (op_info->session_id >= MAX_NUM_SESSIONS || builtin->sessions[op_info->session_id] == NULL) { error_report("Cannot find a valid session id: %" PRIu64 "", op_info->session_id); return -VIRTIO_CRYPTO_INVSESS; } sess = builtin->sessions[op_info->session_id]; if (algtype == QCRYPTODEV_BACKEND_ALGO_TYPE_SYM) { sym_op_info = op_info->u.sym_op_info; status = cryptodev_builtin_sym_operation(sess, sym_op_info, &local_error); } else if (algtype == QCRYPTODEV_BACKEND_ALGO_TYPE_ASYM) { asym_op_info = op_info->u.asym_op_info; status = cryptodev_builtin_asym_operation(sess, op_info->op_code, asym_op_info, &local_error); } if (local_error) { error_report_err(local_error); } if (op_info->cb) { op_info->cb(op_info->opaque, status); } return 0; } static void cryptodev_builtin_cleanup( CryptoDevBackend *backend, Error **errp) { CryptoDevBackendBuiltin *builtin = CRYPTODEV_BACKEND_BUILTIN(backend); size_t i; int queues = backend->conf.peers.queues; CryptoDevBackendClient *cc; for (i = 0; i < MAX_NUM_SESSIONS; i++) { if (builtin->sessions[i] != NULL) { cryptodev_builtin_close_session(backend, i, 0, NULL, NULL); } } for (i = 0; i < queues; i++) { cc = backend->conf.peers.ccs[i]; if (cc) { cryptodev_backend_free_client(cc); backend->conf.peers.ccs[i] = NULL; } } cryptodev_backend_set_ready(backend, false); } static void cryptodev_builtin_class_init(ObjectClass *oc, void *data) { CryptoDevBackendClass *bc = CRYPTODEV_BACKEND_CLASS(oc); bc->init = cryptodev_builtin_init; bc->cleanup = cryptodev_builtin_cleanup; 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 = { .name = TYPE_CRYPTODEV_BACKEND_BUILTIN, .parent = TYPE_CRYPTODEV_BACKEND, .class_init = cryptodev_builtin_class_init, .instance_size = sizeof(CryptoDevBackendBuiltin), }; static void cryptodev_builtin_register_types(void) { type_register_static(&cryptodev_builtin_info); } type_init(cryptodev_builtin_register_types);