mirror of
https://github.com/MariaDB/server.git
synced 2025-01-21 22:34:18 +01:00
394 lines
11 KiB
C
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));
|
|
}
|