NetBSD/sys/dev/cgd_crypto.c

455 lines
11 KiB
C
Raw Normal View History

Fix a longstanding bug in key-handling for the blowfish cipher. This is an incompatible change, and will break all existing cgd images encrypted with blowfish. Users will need to dump their data before booting a kernel with this change, and recreate cgd's and restore data afterwards. I believe this affects a very small number of users other than myself; indeed after several alert mails in an attempt to find them, only 2 such users have come forward. They have both agreed the requirement for backwards compatibility does not warrant the effort nor the mess in the code. This code does exist, if it should later prove to be needed, but will not be in the tree. Further, by the nature of the issue, I have strong reasons to believe that, even if they missed these mails, there would be few other users of blowfish who update their systems with any regularity; any such users would have tripped over the problem in the same way I did when it was first found over a year ago. The problem stems from two issues with the underlying blowfish encryption routines used by cgd: - they take key length arguments counted in bytes, rather than bits like all the opther ciphers. - they silently truncate any keys longer than an internal limit, rather than returning an error (which would have exposed the previous discrepancy immediately). As a result, the kernel reads too much data as the key from cgdconfig, and then truncates most of it. This can easily be demonstrated/tested. Currently, Blowfish users will find that if they mis-enter the cgd passphrase on the first attempt, when validation fails and cgdconfig prompts for the passphrase again, the cgd will not correctly configure even when given a correct passphrase.
2004-03-18 13:42:08 +03:00
/* $NetBSD: cgd_crypto.c,v 1.3 2004/03/18 10:42:08 dan Exp $ */
2002-10-04 22:22:35 +04:00
/*-
* Copyright (c) 2002 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Roland C. Dowdeswell.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the NetBSD
* Foundation, Inc. and its contributors.
* 4. Neither the name of The NetBSD Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Crypto Framework For cgd.c
*
* This framework is temporary and awaits a more complete
* kernel wide crypto implementation.
*/
#include <sys/cdefs.h>
Fix a longstanding bug in key-handling for the blowfish cipher. This is an incompatible change, and will break all existing cgd images encrypted with blowfish. Users will need to dump their data before booting a kernel with this change, and recreate cgd's and restore data afterwards. I believe this affects a very small number of users other than myself; indeed after several alert mails in an attempt to find them, only 2 such users have come forward. They have both agreed the requirement for backwards compatibility does not warrant the effort nor the mess in the code. This code does exist, if it should later prove to be needed, but will not be in the tree. Further, by the nature of the issue, I have strong reasons to believe that, even if they missed these mails, there would be few other users of blowfish who update their systems with any regularity; any such users would have tripped over the problem in the same way I did when it was first found over a year ago. The problem stems from two issues with the underlying blowfish encryption routines used by cgd: - they take key length arguments counted in bytes, rather than bits like all the opther ciphers. - they silently truncate any keys longer than an internal limit, rather than returning an error (which would have exposed the previous discrepancy immediately). As a result, the kernel reads too much data as the key from cgdconfig, and then truncates most of it. This can easily be demonstrated/tested. Currently, Blowfish users will find that if they mis-enter the cgd passphrase on the first attempt, when validation fails and cgdconfig prompts for the passphrase again, the cgd will not correctly configure even when given a correct passphrase.
2004-03-18 13:42:08 +03:00
__KERNEL_RCSID(0, "$NetBSD: cgd_crypto.c,v 1.3 2004/03/18 10:42:08 dan Exp $");
2002-10-04 22:22:35 +04:00
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <dev/cgd_crypto.h>
#ifdef DIAGNOSTIC
#define DIAGPANIC(x) panic(x)
#else
#define DIAGPANIC(x)
#endif
/*
* The general framework provides only one generic function.
* It takes the name of an algorith and returns a struct cryptfuncs *
* for it. It is up to the initialisation routines of the algorithm
* to check key size and block size.
*/
extern struct cryptfuncs cgd_AES_funcs;
extern struct cryptfuncs cgd_3des_funcs;
extern struct cryptfuncs cgd_BF_funcs;
struct cryptfuncs *
cryptfuncs_find(char *alg)
{
if (!strcmp("aes-cbc", alg))
return &cgd_AES_funcs;
if (!strcmp("3des-cbc", alg))
return &cgd_3des_funcs;
if (!strcmp("blowfish-cbc", alg))
return &cgd_BF_funcs;
return NULL;
}
typedef void (*cipher_func)(void *, void *, void *, int);
void
cgd_cipher_uio_cbc(void *privdata, cipher_func cipher,
struct uio *dstuio, struct uio *srcuio);
/*
* cgd_cipher_uio_cbc takes a simple cbc cipher and iterates
* it over two struct uio's. It presumes that the cipher function
* that is passed to it keeps the IV state between calls.
*
* We assume that the caller has ensured that each segment is evenly
* divisible by the block size, which for the cgd is a valid assumption.
* If we were to make this code more generic, we might need to take care
* of this case, either by issuing an error or copying the data.
*/
void
cgd_cipher_uio_cbc(void *privdata, cipher_func cipher,
struct uio *dstuio, struct uio *srcuio)
{
struct iovec *dst;
struct iovec *src;
int dstnum;
int dstoff = 0;
int srcnum;
int srcoff = 0;
dst = dstuio->uio_iov;
dstnum = dstuio->uio_iovcnt;
src = srcuio->uio_iov;
srcnum = srcuio->uio_iovcnt;
for (;;) {
int l = MIN(dst->iov_len - dstoff, src->iov_len - srcoff);
u_int8_t *d = (u_int8_t *)dst->iov_base + dstoff;
u_int8_t *s = (u_int8_t *)src->iov_base + srcoff;
cipher(privdata, d, s, l);
dstoff += l;
srcoff += l;
/*
* We assume that {dst,src} == {dst,src}->iov_len,
* because it should not be possible for it not to be.
*/
if (dstoff == dst->iov_len) {
dstoff = 0;
dstnum--;
dst++;
}
if (srcoff == src->iov_len) {
srcoff = 0;
srcnum--;
src++;
}
if (!srcnum || !dstnum)
break;
}
}
/*
* AES Framework
*/
#include <crypto/rijndael/rijndael-api-fst.h>
cfunc_init cgd_cipher_aes_init;
cfunc_destroy cgd_cipher_aes_destroy;
cfunc_cipher cgd_cipher_aes_cbc;
struct cryptfuncs cgd_AES_funcs = {
cgd_cipher_aes_init,
cgd_cipher_aes_destroy,
cgd_cipher_aes_cbc,
};
/*
* NOTE: we do not store the blocksize in here, because it is not
* variable [yet], we hardcode the blocksize to 16 (128 bits).
*/
struct aes_privdata {
keyInstance ap_enckey;
keyInstance ap_deckey;
};
struct aes_encdata {
keyInstance *ae_key; /* key for this direction */
u_int8_t ae_iv[16]; /* Initialization Vector */
};
static void aes_cbc_enc_int(void *, void *, void *, int);
static void aes_cbc_dec_int(void *, void *, void *, int);
caddr_t
cgd_cipher_aes_init(int keylen, caddr_t key, int *blocksize)
{
struct aes_privdata *ap;
if (!blocksize)
return NULL;
if (keylen != 128 && keylen != 192 && keylen != 256)
return NULL;
if (*blocksize == -1)
*blocksize = 128;
if (*blocksize != 128)
return NULL;
ap = malloc(sizeof(*ap), M_DEVBUF, 0);
if (!ap)
return NULL;
rijndael_makeKey(&ap->ap_enckey, DIR_ENCRYPT, keylen, key);
rijndael_makeKey(&ap->ap_deckey, DIR_DECRYPT, keylen, key);
return (caddr_t)ap;
}
void
cgd_cipher_aes_destroy(caddr_t data)
{
struct aes_privdata *apd = (void *)data;
free(apd, M_DEVBUF);
}
void
aes_cbc_enc_int(void *privdata, void *dst, void *src, int len)
{
struct aes_encdata *ae = (void *)privdata;
cipherInstance cipher;
rijndael_cipherInit(&cipher, MODE_CBC, ae->ae_iv);
rijndael_blockEncrypt(&cipher, ae->ae_key, src, len * 8, dst);
memcpy(ae->ae_iv, (u_int8_t *)dst + (len - 16), 16);
}
void
aes_cbc_dec_int(void *privdata, void *dst, void *src, int len)
{
struct aes_encdata *ae = (void *)privdata;
cipherInstance cipher;
rijndael_cipherInit(&cipher, MODE_CBC, ae->ae_iv);
rijndael_blockDecrypt(&cipher, ae->ae_key, src, len * 8, dst);
memcpy(ae->ae_iv, (u_int8_t *)src + (len - 16), 16);
}
void
cgd_cipher_aes_cbc(caddr_t privdata, struct uio *dstuio,
struct uio *srcuio, caddr_t iv, int dir)
{
struct aes_privdata *apd = (void *)privdata;
struct aes_encdata encd;
memcpy(encd.ae_iv, iv, 16);
switch (dir) {
case CGD_CIPHER_ENCRYPT:
encd.ae_key = &apd->ap_enckey;
cgd_cipher_uio_cbc(&encd, aes_cbc_enc_int, dstuio, srcuio);
break;
case CGD_CIPHER_DECRYPT:
encd.ae_key = &apd->ap_deckey;
cgd_cipher_uio_cbc(&encd, aes_cbc_dec_int, dstuio, srcuio);
break;
default:
DIAGPANIC("cgd_cipher_aes_cbc: unrecgnised direction");
}
}
/*
* 3DES Framework
*/
#include <crypto/des/des.h>
cfunc_init cgd_cipher_3des_init;
cfunc_destroy cgd_cipher_3des_destroy;
cfunc_cipher cgd_cipher_3des_cbc;
struct cryptfuncs cgd_3des_funcs = {
cgd_cipher_3des_init,
cgd_cipher_3des_destroy,
cgd_cipher_3des_cbc,
};
struct c3des_privdata {
des_key_schedule cp_key1;
des_key_schedule cp_key2;
des_key_schedule cp_key3;
};
static void c3des_cbc_enc_int(void *, void *, void *, int);
static void c3des_cbc_dec_int(void *, void *, void *, int);
struct c3des_encdata {
des_key_schedule *ce_key1;
des_key_schedule *ce_key2;
des_key_schedule *ce_key3;
u_int8_t ce_iv[8];
};
caddr_t
cgd_cipher_3des_init(int keylen, caddr_t key, int *blocksize)
{
struct c3des_privdata *cp;
int error = 0;
if (!blocksize)
return NULL;
if (*blocksize == -1)
*blocksize = 64;
if (keylen != (DES_KEY_SZ * 3 * 8) || *blocksize != 64)
return NULL;
cp = malloc(sizeof(*cp), M_DEVBUF, 0);
if (!cp)
return NULL;
error = des_key_sched((des_cblock *)key, cp->cp_key1);
error |= des_key_sched((des_cblock *)key + 1, cp->cp_key2);
error |= des_key_sched((des_cblock *)key + 2, cp->cp_key3);
if (error) {
free(cp, M_DEVBUF);
return NULL;
}
return (caddr_t)cp;
}
void
cgd_cipher_3des_destroy(caddr_t data)
{
struct c3des_privdata *cp = (void *)data;
free(cp, M_DEVBUF);
}
static void
c3des_cbc_enc_int(void *privdata, void *dst, void *src, int len)
{
struct c3des_encdata *ce = (void *)privdata;
des_ede3_cbc_encrypt(src, dst, len, *ce->ce_key1, *ce->ce_key2,
*ce->ce_key3, (des_cblock *)ce->ce_iv, 1);
memcpy(ce->ce_iv, (u_int8_t *)dst + (len - 8), 8);
}
static void
c3des_cbc_dec_int(void *privdata, void *dst, void *src, int len)
{
struct c3des_encdata *ce = (void *)privdata;
des_ede3_cbc_encrypt(src, dst, len, *ce->ce_key1, *ce->ce_key2,
*ce->ce_key3, (des_cblock *)ce->ce_iv, 0);
memcpy(ce->ce_iv, (u_int8_t *)src + (len - 8), 8);
}
void
cgd_cipher_3des_cbc(caddr_t privdata, struct uio *dstuio,
struct uio *srcuio, caddr_t iv, int dir)
{
struct c3des_privdata *cp = (void *)privdata;
struct c3des_encdata ce;
memcpy(ce.ce_iv, iv, 8);
ce.ce_key1 = &cp->cp_key1;
ce.ce_key2 = &cp->cp_key2;
ce.ce_key3 = &cp->cp_key3;
switch (dir) {
case CGD_CIPHER_ENCRYPT:
cgd_cipher_uio_cbc(&ce, c3des_cbc_enc_int, dstuio, srcuio);
break;
case CGD_CIPHER_DECRYPT:
cgd_cipher_uio_cbc(&ce, c3des_cbc_dec_int, dstuio, srcuio);
break;
default:
DIAGPANIC("cgd_cipher_3des_cbc: unrecognised direction");
}
}
/*
* Blowfish Framework
*/
#include <crypto/blowfish/blowfish.h>
cfunc_init cgd_cipher_bf_init;
cfunc_destroy cgd_cipher_bf_destroy;
cfunc_cipher cgd_cipher_bf_cbc;
struct cryptfuncs cgd_BF_funcs = {
cgd_cipher_bf_init,
cgd_cipher_bf_destroy,
cgd_cipher_bf_cbc,
};
static void bf_cbc_enc_int(void *, void *, void *, int);
static void bf_cbc_dec_int(void *, void *, void *, int);
struct bf_privdata {
BF_KEY bp_key;
};
struct bf_encdata {
BF_KEY *be_key;
u_int8_t be_iv[8];
};
caddr_t
cgd_cipher_bf_init(int keylen, caddr_t key, int *blocksize)
{
struct bf_privdata *bp;
if (!blocksize)
return NULL;
Fix a longstanding bug in key-handling for the blowfish cipher. This is an incompatible change, and will break all existing cgd images encrypted with blowfish. Users will need to dump their data before booting a kernel with this change, and recreate cgd's and restore data afterwards. I believe this affects a very small number of users other than myself; indeed after several alert mails in an attempt to find them, only 2 such users have come forward. They have both agreed the requirement for backwards compatibility does not warrant the effort nor the mess in the code. This code does exist, if it should later prove to be needed, but will not be in the tree. Further, by the nature of the issue, I have strong reasons to believe that, even if they missed these mails, there would be few other users of blowfish who update their systems with any regularity; any such users would have tripped over the problem in the same way I did when it was first found over a year ago. The problem stems from two issues with the underlying blowfish encryption routines used by cgd: - they take key length arguments counted in bytes, rather than bits like all the opther ciphers. - they silently truncate any keys longer than an internal limit, rather than returning an error (which would have exposed the previous discrepancy immediately). As a result, the kernel reads too much data as the key from cgdconfig, and then truncates most of it. This can easily be demonstrated/tested. Currently, Blowfish users will find that if they mis-enter the cgd passphrase on the first attempt, when validation fails and cgdconfig prompts for the passphrase again, the cgd will not correctly configure even when given a correct passphrase.
2004-03-18 13:42:08 +03:00
if (keylen < 40 || keylen > 448 || (keylen % 8 != 0))
2002-10-04 22:22:35 +04:00
return NULL;
if (*blocksize == -1)
*blocksize = 64;
if (*blocksize != 64)
return NULL;
bp = malloc(sizeof(*bp), M_DEVBUF, 0);
if (!bp)
return NULL;
Fix a longstanding bug in key-handling for the blowfish cipher. This is an incompatible change, and will break all existing cgd images encrypted with blowfish. Users will need to dump their data before booting a kernel with this change, and recreate cgd's and restore data afterwards. I believe this affects a very small number of users other than myself; indeed after several alert mails in an attempt to find them, only 2 such users have come forward. They have both agreed the requirement for backwards compatibility does not warrant the effort nor the mess in the code. This code does exist, if it should later prove to be needed, but will not be in the tree. Further, by the nature of the issue, I have strong reasons to believe that, even if they missed these mails, there would be few other users of blowfish who update their systems with any regularity; any such users would have tripped over the problem in the same way I did when it was first found over a year ago. The problem stems from two issues with the underlying blowfish encryption routines used by cgd: - they take key length arguments counted in bytes, rather than bits like all the opther ciphers. - they silently truncate any keys longer than an internal limit, rather than returning an error (which would have exposed the previous discrepancy immediately). As a result, the kernel reads too much data as the key from cgdconfig, and then truncates most of it. This can easily be demonstrated/tested. Currently, Blowfish users will find that if they mis-enter the cgd passphrase on the first attempt, when validation fails and cgdconfig prompts for the passphrase again, the cgd will not correctly configure even when given a correct passphrase.
2004-03-18 13:42:08 +03:00
BF_set_key(&bp->bp_key, keylen / 8, key);
2002-10-04 22:22:35 +04:00
return (caddr_t)bp;
}
void
cgd_cipher_bf_destroy(caddr_t data)
{
struct bf_privdata *bp = (void *)data;
free(bp, M_DEVBUF);
}
void
bf_cbc_enc_int(void *privdata, void *dst, void *src, int len)
{
struct bf_encdata *be = (void *)privdata;
BF_cbc_encrypt(src, dst, len, be->be_key, be->be_iv, 1);
memcpy(be->be_iv, (u_int8_t *)dst + (len - 8), 8);
}
void
bf_cbc_dec_int(void *privdata, void *dst, void *src, int len)
{
struct bf_encdata *be = (void *)privdata;
BF_cbc_encrypt(src, dst, len, be->be_key, be->be_iv, 0);
memcpy(be->be_iv, (u_int8_t *)src + (len - 8), 8);
}
void
cgd_cipher_bf_cbc(caddr_t privdata, struct uio *dstuio,
struct uio *srcuio, caddr_t iv, int dir)
{
struct bf_privdata *bp = (void *)privdata;
struct bf_encdata be;
memcpy(be.be_iv, iv, 8);
be.be_key = &bp->bp_key;
switch (dir) {
case CGD_CIPHER_ENCRYPT:
cgd_cipher_uio_cbc(&be, bf_cbc_enc_int, dstuio, srcuio);
break;
case CGD_CIPHER_DECRYPT:
cgd_cipher_uio_cbc(&be, bf_cbc_dec_int, dstuio, srcuio);
break;
default:
DIAGPANIC("cgd_cipher_bf_cbc: unrecognised direction");
}
}