diff options
Diffstat (limited to 'storage/bdb/crypto/crypto.c')
-rw-r--r-- | storage/bdb/crypto/crypto.c | 385 |
1 files changed, 385 insertions, 0 deletions
diff --git a/storage/bdb/crypto/crypto.c b/storage/bdb/crypto/crypto.c new file mode 100644 index 00000000000..f753ec3f0fc --- /dev/null +++ b/storage/bdb/crypto/crypto.c @@ -0,0 +1,385 @@ +/*- + * See the file LICENSE for redistribution information. + * + * Copyright (c) 1996-2004 + * Sleepycat Software. All rights reserved. + * + * Some parts of this code originally written by Adam Stubblefield + * -- astubble@rice.edu + * + * $Id: crypto.c,v 1.31 2004/10/15 16:59:38 bostic Exp $ + */ + +#include "db_config.h" + +#ifndef NO_SYSTEM_INCLUDES +#include <string.h> +#endif + +#include "db_int.h" +#include "dbinc/db_page.h" +#include "dbinc/crypto.h" + +/* + * __crypto_region_init -- + * Initialize crypto. + */ +int +__crypto_region_init(dbenv) + DB_ENV *dbenv; +{ + REGENV *renv; + REGINFO *infop; + CIPHER *cipher; + DB_CIPHER *db_cipher; + char *sh_passwd; + int ret; + + db_cipher = dbenv->crypto_handle; + + ret = 0; + infop = dbenv->reginfo; + renv = infop->primary; + MUTEX_LOCK(dbenv, &renv->mutex); + if (renv->cipher_off == INVALID_ROFF) { + if (!CRYPTO_ON(dbenv)) + goto err; + if (!F_ISSET(infop, REGION_CREATE)) { + __db_err(dbenv, + "Joining non-encrypted environment with encryption key"); + ret = EINVAL; + goto err; + } + if (F_ISSET(db_cipher, CIPHER_ANY)) { + __db_err(dbenv, "Encryption algorithm not supplied"); + ret = EINVAL; + goto err; + } + /* + * Must create the shared information. We need: + * Shared cipher information that contains the passwd. + * After we copy the passwd, we smash and free the one in the + * dbenv. + */ + if ((ret = __db_shalloc( + infop, sizeof(CIPHER), MUTEX_ALIGN, &cipher)) != 0) + goto err; + memset(cipher, 0, sizeof(*cipher)); + if ((ret = __db_shalloc( + infop, dbenv->passwd_len, 0, &sh_passwd)) != 0) { + __db_shalloc_free(infop, cipher); + goto err; + } + memset(sh_passwd, 0, dbenv->passwd_len); + cipher->passwd = R_OFFSET(infop, sh_passwd); + cipher->passwd_len = dbenv->passwd_len; + cipher->flags = db_cipher->alg; + memcpy(sh_passwd, dbenv->passwd, cipher->passwd_len); + renv->cipher_off = R_OFFSET(infop, cipher); + } else { + if (!CRYPTO_ON(dbenv)) { + __db_err(dbenv, + "Encrypted environment: no encryption key supplied"); + ret = EINVAL; + goto err; + } + cipher = R_ADDR(infop, renv->cipher_off); + sh_passwd = R_ADDR(infop, cipher->passwd); + if ((cipher->passwd_len != dbenv->passwd_len) || + memcmp(dbenv->passwd, sh_passwd, cipher->passwd_len) != 0) { + __db_err(dbenv, "Invalid password"); + ret = EPERM; + goto err; + } + if (!F_ISSET(db_cipher, CIPHER_ANY) && + db_cipher->alg != cipher->flags) { + __db_err(dbenv, + "Environment encrypted using a different algorithm"); + ret = EINVAL; + goto err; + } + if (F_ISSET(db_cipher, CIPHER_ANY)) + /* + * We have CIPHER_ANY and we are joining the + * existing env. Setup our cipher structure + * for whatever algorithm this env has. + */ + if ((ret = __crypto_algsetup(dbenv, db_cipher, + cipher->flags, 0)) != 0) + goto err; + } + MUTEX_UNLOCK(dbenv, &renv->mutex); + ret = db_cipher->init(dbenv, db_cipher); + + /* + * On success, no matter if we allocated it or are using the + * already existing one, we are done with the passwd in the dbenv. + * We smash N-1 bytes so that we don't overwrite the nul. + */ + memset(dbenv->passwd, 0xff, dbenv->passwd_len-1); + __os_free(dbenv, dbenv->passwd); + dbenv->passwd = NULL; + dbenv->passwd_len = 0; + + if (0) { +err: MUTEX_UNLOCK(dbenv, &renv->mutex); + } + return (ret); +} + +/* + * __crypto_dbenv_close -- + * Crypto-specific destruction of DB_ENV structure. + * + * PUBLIC: int __crypto_dbenv_close __P((DB_ENV *)); + */ +int +__crypto_dbenv_close(dbenv) + DB_ENV *dbenv; +{ + DB_CIPHER *db_cipher; + int ret; + + ret = 0; + db_cipher = dbenv->crypto_handle; + if (dbenv->passwd != NULL) { + memset(dbenv->passwd, 0xff, dbenv->passwd_len-1); + __os_free(dbenv, dbenv->passwd); + dbenv->passwd = NULL; + } + if (!CRYPTO_ON(dbenv)) + return (0); + if (!F_ISSET(db_cipher, CIPHER_ANY)) + ret = db_cipher->close(dbenv, db_cipher->data); + __os_free(dbenv, db_cipher); + return (ret); +} + +/* + * __crypto_region_destroy -- + * Destroy any system resources allocated in the primary region. + * + * PUBLIC: int __crypto_region_destroy __P((DB_ENV *)); + */ +int +__crypto_region_destroy(dbenv) + DB_ENV *dbenv; +{ + CIPHER *cipher; + REGENV *renv; + REGINFO *infop; + + infop = dbenv->reginfo; + renv = infop->primary; + if (renv->cipher_off != INVALID_ROFF) { + cipher = R_ADDR(infop, renv->cipher_off); + __db_shalloc_free(infop, R_ADDR(infop, cipher->passwd)); + __db_shalloc_free(infop, cipher); + } + return (0); +} + +/* + * __crypto_algsetup -- + * Given a db_cipher structure and a valid algorithm flag, call + * the specific algorithm setup function. + * + * PUBLIC: int __crypto_algsetup __P((DB_ENV *, DB_CIPHER *, u_int32_t, int)); + */ +int +__crypto_algsetup(dbenv, db_cipher, alg, do_init) + DB_ENV *dbenv; + DB_CIPHER *db_cipher; + u_int32_t alg; + int do_init; +{ + int ret; + + ret = 0; + if (!CRYPTO_ON(dbenv)) { + __db_err(dbenv, "No cipher structure given"); + return (EINVAL); + } + F_CLR(db_cipher, CIPHER_ANY); + switch (alg) { + case CIPHER_AES: + db_cipher->alg = CIPHER_AES; + ret = __aes_setup(dbenv, db_cipher); + break; + default: + __db_panic(dbenv, EINVAL); + /* NOTREACHED */ + } + if (do_init) + ret = db_cipher->init(dbenv, db_cipher); + return (ret); +} + +/* + * __crypto_decrypt_meta -- + * Perform decryption on a metapage if needed. + * + * PUBLIC: int __crypto_decrypt_meta __P((DB_ENV *, DB *, u_int8_t *, int)); + */ +int +__crypto_decrypt_meta(dbenv, dbp, mbuf, do_metachk) + DB_ENV *dbenv; + DB *dbp; + u_int8_t *mbuf; + int do_metachk; +{ + DB_CIPHER *db_cipher; + DB dummydb; + DBMETA *meta; + size_t pg_off; + int ret; + u_int8_t *iv; + + /* + * If we weren't given a dbp, we just want to decrypt the page + * on behalf of some internal subsystem, not on behalf of a user + * with a dbp. Therefore, set up a dummy dbp so that the call + * to P_OVERHEAD below works. + */ + if (dbp == NULL) { + memset(&dummydb, 0, sizeof(DB)); + dbp = &dummydb; + } + /* + * Meta-pages may be encrypted for DBMETASIZE bytes. If + * we have a non-zero IV (that is written after encryption) + * then we decrypt (or error if the user isn't set up for + * security). We guarantee that the IV space on non-encrypted + * pages will be zero and a zero-IV is illegal for encryption. + * Therefore any non-zero IV means an encrypted database. + * This basically checks the passwd on the file + * if we cannot find a good magic number. + * We walk through all the algorithms we know about attempting + * to decrypt (and possibly byteswap). + * + * !!! + * All method meta pages have the IV and checksum at the + * exact same location, but not in DBMETA, use BTMETA. + */ + ret = 0; + meta = (DBMETA *)mbuf; + if (meta->encrypt_alg != 0) { + db_cipher = (DB_CIPHER *)dbenv->crypto_handle; + if (!F_ISSET(dbp, DB_AM_ENCRYPT)) { + if (!CRYPTO_ON(dbenv)) { + __db_err(dbenv, + "Encrypted database: no encryption flag specified"); + return (EINVAL); + } + /* + * User has a correct, secure env, but has + * encountered a database in that env that is + * secure, but user didn't dbp->set_flags. Since + * it is existing, use encryption if it is that + * way already. + */ + F_SET(dbp, DB_AM_ENCRYPT|DB_AM_CHKSUM); + } + /* + * This was checked in set_flags when DB_AM_ENCRYPT was set. + * So it better still be true here. + */ + DB_ASSERT(CRYPTO_ON(dbenv)); + if (!F_ISSET(db_cipher, CIPHER_ANY) && + meta->encrypt_alg != db_cipher->alg) { + __db_err(dbenv, + "Database encrypted using a different algorithm"); + return (EINVAL); + } + DB_ASSERT(F_ISSET(dbp, DB_AM_CHKSUM)); + iv = ((BTMETA *)mbuf)->iv; + /* + * For ALL pages, we do not encrypt the beginning + * of the page that contains overhead information. + * This is true of meta and all other pages. + */ + pg_off = P_OVERHEAD(dbp); +alg_retry: + /* + * If they asked for a specific algorithm, then + * use it. Otherwise walk through those we know. + */ + if (!F_ISSET(db_cipher, CIPHER_ANY)) { + if (do_metachk && (ret = db_cipher->decrypt(dbenv, + db_cipher->data, iv, mbuf + pg_off, + DBMETASIZE - pg_off))) + return (ret); + if (((BTMETA *)meta)->crypto_magic != + meta->magic) { + __db_err(dbenv, "Invalid password"); + return (EINVAL); + } + /* + * Success here. The algorithm asked for and the one + * on the file match. We've just decrypted the meta + * page and checked the magic numbers. They match, + * indicating the password is right. All is right + * with the world. + */ + return (0); + } + /* + * If we get here, CIPHER_ANY must be set. + */ + ret = __crypto_algsetup(dbenv, db_cipher, meta->encrypt_alg, 1); + goto alg_retry; + } else if (F_ISSET(dbp, DB_AM_ENCRYPT)) { + /* + * They gave us a passwd, but the database is not + * encrypted. This is an error. We do NOT want to + * silently allow them to write data in the clear when + * the user set up and expects encrypted data. + * + * This covers at least the following scenario. + * 1. User creates and sets up an encrypted database. + * 2. Attacker cannot read the actual data in the database + * because it is encrypted, but can remove/replace the file + * with an empty, unencrypted database file. + * 3. User sets encryption and we get to this code now. + * If we allowed the file to be used in the clear since + * it is that way on disk, the user would unsuspectingly + * write sensitive data in the clear. + * 4. Attacker reads data that user thought was encrypted. + * + * Therefore, asking for encryption with a database that + * was not encrypted is an error. + */ + __db_err(dbenv, + "Unencrypted database with a supplied encryption key"); + return (EINVAL); + } + return (ret); +} + +/* + * __crypto_set_passwd -- + * Get the password from the shared region; and set it in a new + * environment handle. Use this to duplicate environment handles. + * + * PUBLIC: int __crypto_set_passwd __P((DB_ENV *, DB_ENV *)); + */ +int +__crypto_set_passwd(dbenv_src, dbenv_dest) + DB_ENV *dbenv_src, *dbenv_dest; +{ + CIPHER *cipher; + REGENV *renv; + REGINFO *infop; + char *sh_passwd; + int ret; + + ret = 0; + infop = dbenv_src->reginfo; + renv = infop->primary; + + DB_ASSERT(CRYPTO_ON(dbenv_src)); + + cipher = R_ADDR(infop, renv->cipher_off); + sh_passwd = R_ADDR(infop, cipher->passwd); + return (__dbenv_set_encrypt(dbenv_dest, sh_passwd, DB_ENCRYPT_AES)); +} |