mariadb/storage/bdb/crypto/crypto.c
2005-12-05 10:27:46 -08:00

394 lines
11 KiB
C

/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 1996-2005
* Sleepycat Software. All rights reserved.
*
* Some parts of this code originally written by Adam Stubblefield
* -- astubble@rice.edu
*
* $Id: crypto.c,v 12.5 2005/07/20 16:50:56 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;
if (renv->cipher_off == INVALID_ROFF) {
if (!CRYPTO_ON(dbenv))
return (0);
if (!F_ISSET(infop, REGION_CREATE)) {
__db_err(dbenv,
"Joining non-encrypted environment with encryption key");
return (EINVAL);
}
if (F_ISSET(db_cipher, CIPHER_ANY)) {
__db_err(dbenv, "Encryption algorithm not supplied");
return (EINVAL);
}
/*
* 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), 0, &cipher)) != 0)
return (ret);
memset(cipher, 0, sizeof(*cipher));
if ((ret = __db_shalloc(
infop, dbenv->passwd_len, 0, &sh_passwd)) != 0) {
__db_shalloc_free(infop, cipher);
return (ret);
}
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");
return (EINVAL);
}
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");
return (EPERM);
}
if (!F_ISSET(db_cipher, CIPHER_ANY) &&
db_cipher->alg != cipher->flags) {
__db_err(dbenv,
"Environment encrypted using a different algorithm");
return (EINVAL);
}
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)
return (ret);
}
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;
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;
}
ret = 0;
meta = (DBMETA *)mbuf;
/*
* !!!
* We used an "unused" field in the meta-data page to flag whether or
* not the database is encrypted. Unfortunately, that unused field
* was used in Berkeley DB releases before 3.0 (for example, 2.7.7).
* It would have been OK, except encryption doesn't follow the usual
* rules of "upgrade before doing anything else", we check encryption
* before checking for old versions of the database.
*
* We don't have to check Btree databases -- before 3.0, the field of
* interest was the bt_maxkey field (which was never supported and has
* since been removed).
*
* Ugly check to jump out if this format is older than what we support.
* It assumes no encrypted page will have an unencrypted magic number,
* but that seems relatively safe. [#10920]
*/
if (meta->magic == DB_HASHMAGIC && meta->version <= 5)
return (0);
/*
* 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.
*/
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 (__env_set_encrypt(dbenv_dest, sh_passwd, DB_ENCRYPT_AES));
}