qemu/crypto/cipher-gnutls.c.inc
<<
>>
Prefs
   1/*
   2 * QEMU Crypto cipher gnutls algorithms
   3 *
   4 * Copyright (c) 2021 Red Hat, Inc.
   5 *
   6 * This library is free software; you can redistribute it and/or
   7 * modify it under the terms of the GNU Lesser General Public
   8 * License as published by the Free Software Foundation; either
   9 * version 2.1 of the License, or (at your option) any later version.
  10 *
  11 * This library is distributed in the hope that it will be useful,
  12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14 * Lesser General Public License for more details.
  15 *
  16 * You should have received a copy of the GNU Lesser General Public
  17 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
  18 *
  19 */
  20
  21#include "qemu/osdep.h"
  22#include "cipherpriv.h"
  23
  24#include <gnutls/crypto.h>
  25
  26#if GNUTLS_VERSION_NUMBER >= 0x030608
  27#define QEMU_GNUTLS_XTS
  28#endif
  29
  30bool qcrypto_cipher_supports(QCryptoCipherAlgorithm alg,
  31                             QCryptoCipherMode mode)
  32{
  33
  34    switch (mode) {
  35    case QCRYPTO_CIPHER_MODE_ECB:
  36    case QCRYPTO_CIPHER_MODE_CBC:
  37        switch (alg) {
  38        case QCRYPTO_CIPHER_ALG_AES_128:
  39        case QCRYPTO_CIPHER_ALG_AES_192:
  40        case QCRYPTO_CIPHER_ALG_AES_256:
  41        case QCRYPTO_CIPHER_ALG_DES:
  42        case QCRYPTO_CIPHER_ALG_3DES:
  43            return true;
  44        default:
  45            return false;
  46        }
  47#ifdef QEMU_GNUTLS_XTS
  48    case QCRYPTO_CIPHER_MODE_XTS:
  49        switch (alg) {
  50        case QCRYPTO_CIPHER_ALG_AES_128:
  51        case QCRYPTO_CIPHER_ALG_AES_256:
  52            return true;
  53        default:
  54            return false;
  55        }
  56#endif
  57    default:
  58        return false;
  59    }
  60}
  61
  62typedef struct QCryptoCipherGnutls QCryptoCipherGnutls;
  63struct QCryptoCipherGnutls {
  64    QCryptoCipher base;
  65    gnutls_cipher_hd_t handle; /* XTS & CBC mode */
  66    gnutls_cipher_algorithm_t galg; /* ECB mode */
  67    guint8 *key; /* ECB mode */
  68    size_t nkey; /* ECB mode */
  69    size_t blocksize;
  70};
  71
  72
  73static void
  74qcrypto_gnutls_cipher_free(QCryptoCipher *cipher)
  75{
  76    QCryptoCipherGnutls *ctx = container_of(cipher, QCryptoCipherGnutls, base);
  77
  78    g_free(ctx->key);
  79    if (ctx->handle) {
  80        gnutls_cipher_deinit(ctx->handle);
  81    }
  82    g_free(ctx);
  83}
  84
  85
  86static int
  87qcrypto_gnutls_cipher_encrypt(QCryptoCipher *cipher,
  88                              const void *in,
  89                              void *out,
  90                              size_t len,
  91                              Error **errp)
  92{
  93    QCryptoCipherGnutls *ctx = container_of(cipher, QCryptoCipherGnutls, base);
  94    int err;
  95
  96    if (len % ctx->blocksize) {
  97        error_setg(errp, "Length %zu must be a multiple of block size %zu",
  98                   len, ctx->blocksize);
  99        return -1;
 100    }
 101
 102    if (ctx->handle) { /* CBC / XTS mode */
 103        err = gnutls_cipher_encrypt2(ctx->handle,
 104                                     in, len,
 105                                     out, len);
 106        if (err != 0) {
 107            error_setg(errp, "Cannot encrypt data: %s",
 108                       gnutls_strerror(err));
 109            return -1;
 110        }
 111    } else { /* ECB mode very inefficiently faked with CBC */
 112        g_autofree unsigned char *iv = g_new0(unsigned char, ctx->blocksize);
 113        while (len) {
 114            gnutls_cipher_hd_t handle;
 115            gnutls_datum_t gkey = { (unsigned char *)ctx->key, ctx->nkey };
 116            int err = gnutls_cipher_init(&handle, ctx->galg, &gkey, NULL);
 117            if (err != 0) {
 118                error_setg(errp, "Cannot initialize cipher: %s",
 119                           gnutls_strerror(err));
 120                return -1;
 121            }
 122
 123            gnutls_cipher_set_iv(handle, iv, ctx->blocksize);
 124
 125            err = gnutls_cipher_encrypt2(handle,
 126                                         in, ctx->blocksize,
 127                                         out, ctx->blocksize);
 128            if (err != 0) {
 129                gnutls_cipher_deinit(handle);
 130                error_setg(errp, "Cannot encrypt data: %s",
 131                           gnutls_strerror(err));
 132                return -1;
 133            }
 134            gnutls_cipher_deinit(handle);
 135
 136            len -= ctx->blocksize;
 137            in += ctx->blocksize;
 138            out += ctx->blocksize;
 139        }
 140    }
 141
 142    return 0;
 143}
 144
 145
 146static int
 147qcrypto_gnutls_cipher_decrypt(QCryptoCipher *cipher,
 148                              const void *in,
 149                              void *out,
 150                              size_t len,
 151                              Error **errp)
 152{
 153    QCryptoCipherGnutls *ctx = container_of(cipher, QCryptoCipherGnutls, base);
 154    int err;
 155
 156    if (len % ctx->blocksize) {
 157        error_setg(errp, "Length %zu must be a multiple of block size %zu",
 158                   len, ctx->blocksize);
 159        return -1;
 160    }
 161
 162    if (ctx->handle) { /* CBC / XTS mode */
 163        err = gnutls_cipher_decrypt2(ctx->handle,
 164                                     in, len,
 165                                     out, len);
 166
 167        if (err != 0) {
 168            error_setg(errp, "Cannot decrypt data: %s",
 169                       gnutls_strerror(err));
 170            return -1;
 171        }
 172    } else { /* ECB mode very inefficiently faked with CBC */
 173        g_autofree unsigned char *iv = g_new0(unsigned char, ctx->blocksize);
 174        while (len) {
 175            gnutls_cipher_hd_t handle;
 176            gnutls_datum_t gkey = { (unsigned char *)ctx->key, ctx->nkey };
 177            int err = gnutls_cipher_init(&handle, ctx->galg, &gkey, NULL);
 178            if (err != 0) {
 179                error_setg(errp, "Cannot initialize cipher: %s",
 180                           gnutls_strerror(err));
 181                return -1;
 182            }
 183
 184            gnutls_cipher_set_iv(handle, iv, ctx->blocksize);
 185
 186            err = gnutls_cipher_decrypt2(handle,
 187                                         in, ctx->blocksize,
 188                                         out, ctx->blocksize);
 189            if (err != 0) {
 190                gnutls_cipher_deinit(handle);
 191                error_setg(errp, "Cannot encrypt data: %s",
 192                           gnutls_strerror(err));
 193                return -1;
 194            }
 195            gnutls_cipher_deinit(handle);
 196
 197            len -= ctx->blocksize;
 198            in += ctx->blocksize;
 199            out += ctx->blocksize;
 200        }
 201    }
 202
 203    return 0;
 204}
 205
 206static int
 207qcrypto_gnutls_cipher_setiv(QCryptoCipher *cipher,
 208                            const uint8_t *iv, size_t niv,
 209                            Error **errp)
 210{
 211    QCryptoCipherGnutls *ctx = container_of(cipher, QCryptoCipherGnutls, base);
 212
 213    if (niv != ctx->blocksize) {
 214        error_setg(errp, "Expected IV size %zu not %zu",
 215                   ctx->blocksize, niv);
 216        return -1;
 217    }
 218
 219    gnutls_cipher_set_iv(ctx->handle, (unsigned char *)iv, niv);
 220
 221    return 0;
 222}
 223
 224
 225static struct QCryptoCipherDriver gnutls_driver = {
 226    .cipher_encrypt = qcrypto_gnutls_cipher_encrypt,
 227    .cipher_decrypt = qcrypto_gnutls_cipher_decrypt,
 228    .cipher_setiv = qcrypto_gnutls_cipher_setiv,
 229    .cipher_free = qcrypto_gnutls_cipher_free,
 230};
 231
 232static QCryptoCipher *qcrypto_cipher_ctx_new(QCryptoCipherAlgorithm alg,
 233                                             QCryptoCipherMode mode,
 234                                             const uint8_t *key,
 235                                             size_t nkey,
 236                                             Error **errp)
 237{
 238    QCryptoCipherGnutls *ctx;
 239    gnutls_datum_t gkey = { (unsigned char *)key, nkey };
 240    gnutls_cipher_algorithm_t galg = GNUTLS_CIPHER_UNKNOWN;
 241    int err;
 242
 243    switch (mode) {
 244#ifdef QEMU_GNUTLS_XTS
 245    case QCRYPTO_CIPHER_MODE_XTS:
 246        switch (alg) {
 247        case QCRYPTO_CIPHER_ALG_AES_128:
 248            galg = GNUTLS_CIPHER_AES_128_XTS;
 249            break;
 250        case QCRYPTO_CIPHER_ALG_AES_256:
 251            galg = GNUTLS_CIPHER_AES_256_XTS;
 252            break;
 253        default:
 254            break;
 255        }
 256        break;
 257#endif
 258
 259    case QCRYPTO_CIPHER_MODE_ECB:
 260    case QCRYPTO_CIPHER_MODE_CBC:
 261        switch (alg) {
 262        case QCRYPTO_CIPHER_ALG_AES_128:
 263            galg = GNUTLS_CIPHER_AES_128_CBC;
 264            break;
 265        case QCRYPTO_CIPHER_ALG_AES_192:
 266            galg = GNUTLS_CIPHER_AES_192_CBC;
 267            break;
 268        case QCRYPTO_CIPHER_ALG_AES_256:
 269            galg = GNUTLS_CIPHER_AES_256_CBC;
 270            break;
 271        case QCRYPTO_CIPHER_ALG_DES:
 272            galg = GNUTLS_CIPHER_DES_CBC;
 273            break;
 274        case QCRYPTO_CIPHER_ALG_3DES:
 275            galg = GNUTLS_CIPHER_3DES_CBC;
 276            break;
 277        default:
 278            break;
 279        }
 280        break;
 281    default:
 282        break;
 283    }
 284
 285    if (galg == GNUTLS_CIPHER_UNKNOWN) {
 286        error_setg(errp, "Unsupported cipher algorithm %s with %s mode",
 287                   QCryptoCipherAlgorithm_str(alg),
 288                   QCryptoCipherMode_str(mode));
 289        return NULL;
 290    }
 291
 292    if (!qcrypto_cipher_validate_key_length(alg, mode, nkey, errp)) {
 293        return NULL;
 294    }
 295
 296    ctx = g_new0(QCryptoCipherGnutls, 1);
 297    ctx->base.driver = &gnutls_driver;
 298
 299    if (mode == QCRYPTO_CIPHER_MODE_ECB) {
 300        ctx->key = g_new0(guint8, nkey);
 301        memcpy(ctx->key, key, nkey);
 302        ctx->nkey = nkey;
 303        ctx->galg = galg;
 304    } else {
 305        err = gnutls_cipher_init(&ctx->handle, galg, &gkey, NULL);
 306        if (err != 0) {
 307            error_setg(errp, "Cannot initialize cipher: %s",
 308                       gnutls_strerror(err));
 309            goto error;
 310        }
 311    }
 312
 313    if (alg == QCRYPTO_CIPHER_ALG_DES ||
 314        alg == QCRYPTO_CIPHER_ALG_3DES)
 315        ctx->blocksize = 8;
 316    else
 317        ctx->blocksize = 16;
 318
 319    /*
 320     * Our API contract for requires iv to be optional
 321     * but nettle gets unhappy when called by gnutls
 322     * in this case, so we just force set a default
 323     * all-zeros IV, to match behaviour of other backends.
 324     */
 325    if (mode != QCRYPTO_CIPHER_MODE_ECB) {
 326        g_autofree unsigned char *iv = g_new0(unsigned char, ctx->blocksize);
 327        gnutls_cipher_set_iv(ctx->handle, iv, ctx->blocksize);
 328    }
 329
 330    return &ctx->base;
 331
 332 error:
 333    qcrypto_gnutls_cipher_free(&ctx->base);
 334    return NULL;
 335}
 336