diff options
Diffstat (limited to 'source4/dsdb/samdb/ldb_modules/encrypted_secrets.c')
-rw-r--r-- | source4/dsdb/samdb/ldb_modules/encrypted_secrets.c | 1401 |
1 files changed, 1401 insertions, 0 deletions
diff --git a/source4/dsdb/samdb/ldb_modules/encrypted_secrets.c b/source4/dsdb/samdb/ldb_modules/encrypted_secrets.c new file mode 100644 index 0000000..8c59418 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/encrypted_secrets.c @@ -0,0 +1,1401 @@ +/* + ldb database library + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* + * Encrypt the samba secret attributes on disk. This is intended to + * mitigate the inadvertent disclosure of the sam.ldb file, and to mitigate + * memory read attacks. + * + * Currently the key file is stored in the same directory as sam.ldb but + * this could be changed at a later date to use an HSM or similar mechanism + * to protect the key. + * + * Data is encrypted with AES 128 GCM. The encryption uses gnutls where + * available and if it supports AES 128 GCM AEAD modes, otherwise the + * samba internal implementation is used. + * + */ + +#include "includes.h" +#include <ldb_module.h> + +#include "librpc/gen_ndr/ndr_drsblobs.h" +#include "dsdb/samdb/samdb.h" +#include "dsdb/samdb/ldb_modules/util.h" + +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +static const char * const secret_attributes[] = {DSDB_SECRET_ATTRIBUTES}; +static const size_t num_secret_attributes = ARRAY_SIZE(secret_attributes); + +#define SECRET_ATTRIBUTE_VERSION 1 +#define SECRET_ENCRYPTION_ALGORITHM ENC_SECRET_AES_128_AEAD +#define NUMBER_OF_KEYS 1 +#define SECRETS_KEY_FILE "encrypted_secrets.key" + +#undef strcasecmp + +struct es_data { + /* + * Should secret attributes be encrypted and decrypted? + */ + bool encrypt_secrets; + /* + * Encryption keys for secret attributes + */ + DATA_BLOB keys[NUMBER_OF_KEYS]; + /* + * The gnutls algorithm used to encrypt attributes + */ + int encryption_algorithm; +}; + +/* + * @brief Get the key used to encrypt and decrypt secret attributes on disk. + * + * @param data the private context data for this module. + * + * @return A data blob containing the key. + * This should be treated as read only. + */ +static const DATA_BLOB get_key(const struct es_data *data) { + + return data->keys[0]; +} + +/* + * @brief Get the directory containing the key files. + * + * @param ctx talloc memory context that will own the return value + * @param ldb ldb context, to allow logging + * + * @return zero terminated string, the directory containing the key file + * allocated on ctx. + * + */ +static const char* get_key_directory(TALLOC_CTX *ctx, struct ldb_context *ldb) +{ + + const char *sam_ldb_path = NULL; + const char *private_dir = NULL; + char *p = NULL; + + + /* + * Work out where *our* key file is. It must be in + * the same directory as sam.ldb + */ + sam_ldb_path = ldb_get_opaque(ldb, "ldb_url"); + if (sam_ldb_path == NULL) { + ldb_set_errstring(ldb, "Unable to get ldb_url\n"); + return NULL; + } + + if (strncmp("tdb://", sam_ldb_path, 6) == 0) { + sam_ldb_path += 6; + } + else if (strncmp("ldb://", sam_ldb_path, 6) == 0) { + sam_ldb_path += 6; + } + else if (strncmp("mdb://", sam_ldb_path, 6) == 0) { + sam_ldb_path += 6; + } + private_dir = talloc_strdup(ctx, sam_ldb_path); + if (private_dir == NULL) { + ldb_set_errstring(ldb, + "Out of memory building encrypted " + "secrets key\n"); + return NULL; + } + + p = strrchr(private_dir, '/'); + if (p != NULL) { + *p = '\0'; + } else { + private_dir = talloc_strdup(ctx, "."); + } + + return private_dir; +} + +/* + * @brief log details of an error that set errno + * + * @param ldb ldb context, to allow logging. + * @param err the value of errno. + * @param desc extra text to help describe the error. + * + */ +static void log_error(struct ldb_context *ldb, int err, const char *desc) +{ + char buf[1024]; + int e = strerror_r(err, buf, sizeof(buf)); + if (e != 0) { + strlcpy(buf, "Unknown error", sizeof(buf)-1); + } + ldb_asprintf_errstring(ldb, "Error (%d) %s - %s\n", err, buf, desc); +} + +/* + * @brief Load the keys into the encrypted secrets module context. + * + * @param module the current ldb module + * @param data the private data for the current module + * + * Currently the keys are stored in a binary file in the same directory + * as the database. + * + * @return an LDB result code. + * + */ +static int load_keys(struct ldb_module *module, struct es_data *data) +{ + + const char *key_dir = NULL; + const char *key_path = NULL; + + struct ldb_context *ldb = NULL; + FILE *fp = NULL; + const int key_size = 16; + int read; + DATA_BLOB key = data_blob_null; + + TALLOC_CTX *frame = talloc_stackframe(); + + ldb = ldb_module_get_ctx(module); + key_dir = get_key_directory(frame, ldb); + if (key_dir == NULL) { + TALLOC_FREE(frame); + return LDB_ERR_OPERATIONS_ERROR; + } + + key_path = talloc_asprintf(frame, "%s/%s", key_dir, SECRETS_KEY_FILE); + if (key_path == NULL) { + TALLOC_FREE(frame); + return ldb_oom(ldb); + } + + + key = data_blob_talloc_zero(module, key_size); + key.length = key_size; + + fp = fopen(key_path, "rb"); + if (fp == NULL) { + TALLOC_FREE(frame); + data_blob_free(&key); + if (errno == ENOENT) { + ldb_debug(ldb, + LDB_DEBUG_WARNING, + "No encrypted secrets key file. " + "Secret attributes will not be encrypted or " + "decrypted\n"); + data->encrypt_secrets = false; + return LDB_SUCCESS; + } else { + log_error(ldb, + errno, + "Opening encrypted_secrets key file\n"); + return LDB_ERR_OPERATIONS_ERROR; + } + } + + read = fread(key.data, 1, key.length, fp); + fclose(fp); + if (read == 0) { + TALLOC_FREE(frame); + ldb_debug(ldb, + LDB_DEBUG_WARNING, + "Zero length encrypted secrets key file. " + "Secret attributes will not be encrypted or " + "decrypted\n"); + data->encrypt_secrets = false; + return LDB_SUCCESS; + } + if (read != key.length) { + TALLOC_FREE(frame); + if (errno) { + log_error(ldb, + errno, + "Reading encrypted_secrets key file\n"); + } else { + ldb_debug(ldb, + LDB_DEBUG_ERROR, + "Invalid encrypted_secrets key file, " + "only %d bytes read should be %d bytes\n", + read, + key_size); + } + return LDB_ERR_OPERATIONS_ERROR; + } + + data->keys[0] = key; + data->encrypt_secrets = true; + data->encryption_algorithm = GNUTLS_CIPHER_AES_128_GCM; + TALLOC_FREE(frame); + + return LDB_SUCCESS; + +} + +/* + * @brief should this element be encrypted. + * + * @param el the element to examine + * + * @return true if the element should be encrypted, + * false otherwise. + */ +static bool should_encrypt(const struct ldb_message_element *el) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(secret_attributes); i++) { + if (strcasecmp(secret_attributes[i], el->name) == 0) { + return true; + } + } + return false; +} + +/* + * @brief Round a size up to a multiple of the encryption cipher block size. + * + * @param block_size The cipher block size + * @param size The size to round + * + * @return Size rounded up to the nearest multiple of block_size + */ +static size_t round_to_block_size(size_t block_size, size_t size) +{ + if ((size % block_size) == 0) { + return size; + } else { + return ((int)(size/block_size) + 1) * block_size; + } +} + +/* + * @brief Create an new EncryptedSecret owned by the supplied talloc context. + * + * Create a new encrypted secret and initialise the header. + * + * @param ldb ldb context, to allow logging. + * @param ctx The talloc memory context that will own the new EncryptedSecret + * + * @return pointer to the new encrypted secret, or NULL if there was an error + */ +static struct EncryptedSecret *makeEncryptedSecret(struct ldb_context *ldb, + TALLOC_CTX *ctx) +{ + struct EncryptedSecret *es = NULL; + + es = talloc_zero_size(ctx, sizeof(struct EncryptedSecret)); + if (es == NULL) { + ldb_set_errstring(ldb, + "Out of memory, allocating " + "struct EncryptedSecret\n"); + return NULL; + } + es->header.magic = ENCRYPTED_SECRET_MAGIC_VALUE; + es->header.version = SECRET_ATTRIBUTE_VERSION; + es->header.algorithm = SECRET_ENCRYPTION_ALGORITHM; + es->header.flags = 0; + return es; +} + +/* + * @brief Allocate and populate a data blob with a PlaintextSecret structure. + * + * Allocate a new data blob and populate it with a serialised PlaintextSecret, + * containing the ldb_val + * + * @param ctx The talloc memory context that will own the allocated memory. + * @param ldb ldb context, to allow logging. + * @param val The ldb value to serialise. + * + * @return The populated data blob or data_blob_null if there was an error. + */ +static DATA_BLOB makePlainText(TALLOC_CTX *ctx, + struct ldb_context *ldb, + const struct ldb_val val) +{ + struct PlaintextSecret ps = { .cleartext = data_blob_null}; + DATA_BLOB pt = data_blob_null; + int rc; + + ps.cleartext.length = val.length; + ps.cleartext.data = val.data; + + rc = ndr_push_struct_blob(&pt, + ctx, + &ps, + (ndr_push_flags_fn_t) + ndr_push_PlaintextSecret); + if (!NDR_ERR_CODE_IS_SUCCESS(rc)) { + ldb_set_errstring(ldb, + "Unable to ndr push PlaintextSecret\n"); + return data_blob_null; + } + return pt; +} + + +/* + * Helper function converts a data blob to a gnutls_datum_t. + * Note that this does not copy the data. + * So the returned value should be treated as read only. + * And that changes to the length of the underlying DATA_BLOB + * will not be reflected in the returned object. + * + */ +static const gnutls_datum_t convert_from_data_blob(DATA_BLOB blob) { + + const gnutls_datum_t datum = { + .size = blob.length, + .data = blob.data, + }; + return datum; +} + +/* + * @brief Get the gnutls algorithm needed to decrypt the EncryptedSecret + * + * @param ldb ldb context, to allow logging. + * @param es the encrypted secret + * + * @return The gnutls algoritm number, or 0 if there is no match. + * + */ +static int gnutls_get_algorithm(struct ldb_context *ldb, + struct EncryptedSecret *es) { + + switch (es->header.algorithm) { + case ENC_SECRET_AES_128_AEAD: + return GNUTLS_CIPHER_AES_128_GCM; + default: + ldb_asprintf_errstring(ldb, + "Unsupported encryption algorithm %d\n", + es->header.algorithm); + return 0; + } +} + +/* + * + * @param err Pointer to an error code, set to: + * LDB_SUCESS If the value was successfully encrypted + * LDB_ERR_OPERATIONS_ERROR If there was an error. + * + * @param ctx Talloc memory context the will own the memory allocated + * @param ldb ldb context, to allow logging. + * @param val The ldb value to encrypt, not altered or freed + * @param data The context data for this module. + * + * @return The encrypted ldb_val, or data_blob_null if there was an error. + */ +static struct ldb_val gnutls_encrypt_aead(int *err, + TALLOC_CTX *ctx, + struct ldb_context *ldb, + const struct ldb_val val, + const struct es_data *data) +{ + struct EncryptedSecret *es = NULL; + struct ldb_val enc = data_blob_null; + DATA_BLOB pt = data_blob_null; + gnutls_aead_cipher_hd_t cipher_hnd; + int rc; + + TALLOC_CTX *frame = talloc_stackframe(); + + es = makeEncryptedSecret(ldb, frame); + if (es == NULL) { + goto error_exit; + } + + pt = makePlainText(frame, ldb, val); + if (pt.length == 0) { + goto error_exit; + } + + /* + * Set the encryption key and initialize the encryption handle. + */ + { + const size_t key_size = gnutls_cipher_get_key_size( + data->encryption_algorithm); + gnutls_datum_t cipher_key; + DATA_BLOB key_blob = get_key(data); + + if (key_blob.length != key_size) { + ldb_asprintf_errstring(ldb, + "Invalid EncryptedSecrets key " + "size, expected %zu bytes and " + "it is %zu bytes\n", + key_size, + key_blob.length); + goto error_exit; + } + cipher_key = convert_from_data_blob(key_blob); + + rc = gnutls_aead_cipher_init(&cipher_hnd, + data->encryption_algorithm, + &cipher_key); + if (rc !=0) { + ldb_asprintf_errstring(ldb, + "gnutls_aead_cipher_init failed " + "%s - %s\n", + gnutls_strerror_name(rc), + gnutls_strerror(rc)); + goto error_exit; + } + + } + + /* + * Set the initialisation vector + */ + { + unsigned iv_size = gnutls_cipher_get_iv_size( + data->encryption_algorithm); + uint8_t *iv; + + iv = talloc_zero_size(frame, iv_size); + if (iv == NULL) { + ldb_set_errstring(ldb, + "Out of memory allocating IV\n"); + goto error_exit_handle; + } + + rc = gnutls_rnd(GNUTLS_RND_NONCE, iv, iv_size); + if (rc !=0) { + ldb_asprintf_errstring(ldb, + "gnutls_rnd failed %s - %s\n", + gnutls_strerror_name(rc), + gnutls_strerror(rc)); + goto error_exit_handle; + } + es->iv.length = iv_size; + es->iv.data = iv; + } + + /* + * Encrypt the value. + */ + { + const unsigned block_size = gnutls_cipher_get_block_size( + data->encryption_algorithm); + const unsigned tag_size = gnutls_cipher_get_tag_size( + data->encryption_algorithm); + const size_t ed_size = round_to_block_size( + block_size, + sizeof(struct PlaintextSecret) + val.length); + const size_t en_size = ed_size + tag_size; + uint8_t *ct = talloc_zero_size(frame, en_size); + size_t el = en_size; + + if (ct == NULL) { + ldb_set_errstring(ldb, + "Out of memory allocation cipher " + "text\n"); + goto error_exit_handle; + } + + rc = gnutls_aead_cipher_encrypt( + cipher_hnd, + es->iv.data, + es->iv.length, + &es->header, + sizeof(struct EncryptedSecretHeader), + tag_size, + pt.data, + pt.length, + ct, + &el); + if (rc !=0) { + ldb_asprintf_errstring(ldb, + "gnutls_aead_cipher_encrypt '" + "failed %s - %s\n", + gnutls_strerror_name(rc), + gnutls_strerror(rc)); + *err = LDB_ERR_OPERATIONS_ERROR; + return data_blob_null; + } + es->encrypted.length = el; + es->encrypted.data = ct; + gnutls_aead_cipher_deinit(cipher_hnd); + } + + rc = ndr_push_struct_blob(&enc, + ctx, + es, + (ndr_push_flags_fn_t) + ndr_push_EncryptedSecret); + if (!NDR_ERR_CODE_IS_SUCCESS(rc)) { + ldb_set_errstring(ldb, + "Unable to ndr push EncryptedSecret\n"); + goto error_exit; + } + TALLOC_FREE(frame); + return enc; + +error_exit_handle: + gnutls_aead_cipher_deinit(cipher_hnd); +error_exit: + *err = LDB_ERR_OPERATIONS_ERROR; + TALLOC_FREE(frame); + return data_blob_null; +} + +/* + * @brief Decrypt data encrypted using an aead algorithm. + * + * Decrypt the data in ed and insert it into ev. The data was encrypted + * with one of the gnutls aead compatable algorithms. + * + * @param err Pointer to an error code, set to: + * LDB_SUCESS If the value was successfully decrypted + * LDB_ERR_OPERATIONS_ERROR If there was an error. + * + * @param ctx The talloc context that will own the PlaintextSecret + * @param ldb ldb context, to allow logging. + * @param ev The value to be updated with the decrypted data. + * @param ed The data to decrypt. + * @param data The context data for this module. + * + * @return ev is updated with the unencrypted data. + */ +static void gnutls_decrypt_aead(int *err, + TALLOC_CTX *ctx, + struct ldb_context *ldb, + struct EncryptedSecret *es, + struct PlaintextSecret *ps, + const struct es_data *data) +{ + + gnutls_aead_cipher_hd_t cipher_hnd; + DATA_BLOB pt = data_blob_null; + const unsigned tag_size = + gnutls_cipher_get_tag_size(es->header.algorithm); + int rc; + + /* + * Get the encryption key and initialise the encryption handle + */ + { + gnutls_datum_t cipher_key; + DATA_BLOB key_blob; + const int algorithm = gnutls_get_algorithm(ldb, es); + const size_t key_size = gnutls_cipher_get_key_size(algorithm); + key_blob = get_key(data); + + if (algorithm == 0) { + goto error_exit; + } + + if (key_blob.length != key_size) { + ldb_asprintf_errstring(ldb, + "Invalid EncryptedSecrets key " + "size, expected %zu bytes and " + "it is %zu bytes\n", + key_size, + key_blob.length); + goto error_exit; + } + cipher_key = convert_from_data_blob(key_blob); + + rc = gnutls_aead_cipher_init( + &cipher_hnd, + algorithm, + &cipher_key); + if (rc != 0) { + ldb_asprintf_errstring(ldb, + "gnutls_aead_cipher_init failed " + "%s - %s\n", + gnutls_strerror_name(rc), + gnutls_strerror(rc)); + goto error_exit; + } + } + + /* + * Decrypt and validate the encrypted value + */ + + pt.length = es->encrypted.length; + pt.data = talloc_zero_size(ctx, es->encrypted.length); + + if (pt.data == NULL) { + ldb_set_errstring(ldb, + "Out of memory allocating plain text\n"); + goto error_exit_handle; + } + + rc = gnutls_aead_cipher_decrypt(cipher_hnd, + es->iv.data, + es->iv.length, + &es->header, + sizeof(struct EncryptedSecretHeader), + tag_size, + es->encrypted.data, + es->encrypted.length, + pt.data, + &pt.length); + if (rc != 0) { + /* + * Typically this will indicate that the data has been + * corrupted i.e. the tag comparison has failed. + * At the moment gnutls does not provide a separate + * error code to indicate this + */ + ldb_asprintf_errstring(ldb, + "gnutls_aead_cipher_decrypt failed " + "%s - %s. Data possibly corrupted or " + "altered\n", + gnutls_strerror_name(rc), + gnutls_strerror(rc)); + goto error_exit_handle; + } + gnutls_aead_cipher_deinit(cipher_hnd); + + rc = ndr_pull_struct_blob(&pt, + ctx, + ps, + (ndr_pull_flags_fn_t) + ndr_pull_PlaintextSecret); + if(!NDR_ERR_CODE_IS_SUCCESS(rc)) { + ldb_asprintf_errstring(ldb, + "Error(%d) unpacking decrypted data, " + "data possibly corrupted or altered\n", + rc); + goto error_exit; + } + return; + +error_exit_handle: + gnutls_aead_cipher_deinit(cipher_hnd); +error_exit: + *err = LDB_ERR_OPERATIONS_ERROR; + return; +} + +/* + * @brief Encrypt an attribute value using the default encryption algorithm. + * + * Returns an encrypted copy of the value, the original value is left intact. + * The original content of val is encrypted and wrapped in an encrypted_value + * structure. + * + * @param err Pointer to an error code, set to: + * LDB_SUCESS If the value was successfully encrypted + * LDB_ERR_OPERATIONS_ERROR If there was an error. + * + * @param ctx Talloc memory context the will own the memory allocated + * @param ldb ldb context, to allow logging. + * @param val The ldb value to encrypt, not altered or freed + * @param data The context data for this module. + * + * @return The encrypted ldb_val, or data_blob_null if there was an error. + */ +static struct ldb_val encrypt_value(int *err, + TALLOC_CTX *ctx, + struct ldb_context *ldb, + const struct ldb_val val, + const struct es_data *data) +{ + return gnutls_encrypt_aead(err, ctx, ldb, val, data); +} + +/* + * @brief Encrypt all the values on an ldb_message_element + * + * Returns a copy of the original attribute with all values encrypted + * by encrypt_value(), the original attribute is left intact. + * + * @param err Pointer to an error code, set to: + * LDB_SUCESS If the value was successfully encrypted + * LDB_ERR_OPERATIONS_ERROR If there was an error. + * + * @param ctx Talloc memory context the will own the memory allocated + * for the new ldb_message_element. + * @param ldb ldb context, to allow logging. + * @param el The ldb_message_elemen to encrypt, not altered or freed + * @param data The context data for this module. + * + * @return Pointer encrypted lsb_message_element, will be NULL if there was + * an error. + */ +static struct ldb_message_element *encrypt_element( + int *err, + TALLOC_CTX *ctx, + struct ldb_context *ldb, + const struct ldb_message_element *el, + const struct es_data *data) +{ + struct ldb_message_element* enc; + unsigned int i; + + enc = talloc_zero(ctx, struct ldb_message_element); + if (enc == NULL) { + ldb_set_errstring(ldb, + "Out of memory, allocating ldb_message_" + "element\n"); + *err = LDB_ERR_OPERATIONS_ERROR; + return NULL; + } + + enc->flags = el->flags; + enc->num_values = el->num_values; + enc->values = talloc_array(enc, struct ldb_val, enc->num_values); + if (enc->values == NULL) { + TALLOC_FREE(enc); + ldb_set_errstring(ldb, + "Out of memory, allocating values array\n"); + *err = LDB_ERR_OPERATIONS_ERROR; + return NULL; + } + + enc->name = talloc_strdup(enc, el->name); + if (enc->name == NULL) { + TALLOC_FREE(enc); + ldb_set_errstring(ldb, + "Out of memory, copying element name\n"); + *err = LDB_ERR_OPERATIONS_ERROR; + return NULL; + } + + for (i = 0; i < el->num_values; i++) { + enc->values[i] = + encrypt_value( + err, + enc->values, + ldb, + el->values[i], + data); + if (*err != LDB_SUCCESS) { + TALLOC_FREE(enc); + return NULL; + } + } + return enc; +} + +/* + * @brief Encrypt all the secret attributes on an ldb_message + * + * Encrypt all the secret attributes on an ldb_message. Any secret + * attributes are removed from message and encrypted copies of the + * attributes added. In the event of an error the contents of the + * message will be inconsistent. + * + * @param err Pointer to an error code, set to: + * LDB_SUCESS If the value was successfully encrypted + * LDB_ERR_OPERATIONS_ERROR If there was an error. + * @param ldb ldb context, to allow logging. + * @param msg The ldb_message to have it's secret attributes encrypted. + * + * @param data The context data for this module. + */ +static const struct ldb_message *encrypt_secret_attributes( + int *err, + TALLOC_CTX *ctx, + struct ldb_context *ldb, + const struct ldb_message *msg, + const struct es_data *data) +{ + struct ldb_message *encrypted_msg = NULL; + + unsigned int i; + + if (ldb_dn_is_special(msg->dn)) { + return NULL; + } + + for (i = 0; i < msg->num_elements; i++) { + + const struct ldb_message_element *el = &msg->elements[i]; + if (should_encrypt(el)) { + struct ldb_message_element* enc = NULL; + if (encrypted_msg == NULL) { + encrypted_msg = ldb_msg_copy_shallow(ctx, msg); + if (encrypted_msg == NULL) { + ldb_set_errstring( + ldb, + "Out of memory, allocating " + "ldb_message_element\n"); + *err = LDB_ERR_OPERATIONS_ERROR; + return NULL; + } + encrypted_msg->dn = msg->dn; + } + enc = encrypt_element(err, + msg->elements, + ldb, + el, + data); + if (*err != LDB_SUCCESS) { + return NULL; + } + encrypted_msg->elements[i] = *enc; + } + } + return encrypted_msg; +} + +/* + * @brief Check the encrypted secret header to ensure it's valid + * + * Check an Encrypted secret and ensure it's header is valid. + * A header is assumed to be valid if it: + * - it starts with the MAGIC_VALUE + * - The version number is valid + * - The algorithm is valid + * + * @param val The EncryptedSecret to check. + * + * @return true if the header is valid, false otherwise. + * + */ +static bool check_header(struct EncryptedSecret *es) +{ + struct EncryptedSecretHeader *eh; + + eh = &es->header; + if (eh->magic != ENCRYPTED_SECRET_MAGIC_VALUE) { + /* + * Does not start with the magic value so not + * an encrypted_value + */ + return false; + } + + if (eh->version > SECRET_ATTRIBUTE_VERSION) { + /* + * Invalid version, so not an encrypted value + */ + return false; + } + + if (eh->algorithm != ENC_SECRET_AES_128_AEAD) { + /* + * Invalid algorithm, so not an encrypted value + */ + return false; + } + /* + * Length looks ok, starts with magic value, and the version and + * algorithm are valid + */ + return true; +} +/* + * @brief Decrypt an attribute value. + * + * Returns a decrypted copy of the value, the original value is left intact. + * + * @param err Pointer to an error code, set to: + * LDB_SUCESS If the value was successfully decrypted + * LDB_ERR_OPERATIONS_ERROR If there was an error. + * + * @param ctx Talloc memory context the will own the memory allocated + * @param ldb ldb context, to allow logging. + * @param val The ldb value to decrypt, not altered or freed + * @param data The context data for this module. + * + * @return The decrypted ldb_val, or data_blob_null if there was an error. + */ +static struct ldb_val decrypt_value(int *err, + TALLOC_CTX *ctx, + struct ldb_context *ldb, + const struct ldb_val val, + const struct es_data *data) +{ + + struct ldb_val dec; + + struct EncryptedSecret es; + struct PlaintextSecret ps = { data_blob_null}; + int rc; + TALLOC_CTX *frame = talloc_stackframe(); + + rc = ndr_pull_struct_blob(&val, + frame, + &es, + (ndr_pull_flags_fn_t) + ndr_pull_EncryptedSecret); + if(!NDR_ERR_CODE_IS_SUCCESS(rc)) { + ldb_asprintf_errstring(ldb, + "Error(%d) unpacking encrypted secret, " + "data possibly corrupted or altered\n", + rc); + *err = LDB_ERR_OPERATIONS_ERROR; + TALLOC_FREE(frame); + return data_blob_null; + } + if (!check_header(&es)) { + /* + * Header is invalid so can't be an encrypted value + */ + ldb_set_errstring(ldb, "Invalid EncryptedSecrets header\n"); + *err = LDB_ERR_OPERATIONS_ERROR; + return data_blob_null; + } + gnutls_decrypt_aead(err, frame, ldb, &es, &ps, data); + + if (*err != LDB_SUCCESS) { + TALLOC_FREE(frame); + return data_blob_null; + } + + dec = data_blob_talloc(ctx, + ps.cleartext.data, + ps.cleartext.length); + if (dec.data == NULL) { + TALLOC_FREE(frame); + ldb_set_errstring(ldb, "Out of memory, copying value\n"); + *err = LDB_ERR_OPERATIONS_ERROR; + return data_blob_null; + } + + TALLOC_FREE(frame); + return dec; +} + +/* + * @brief Decrypt all the encrypted values on an ldb_message_element + * + * Returns a copy of the original attribute with all values decrypted by + * decrypt_value(), the original attribute is left intact. + * + * @param err Pointer to an error code, set to: + * LDB_SUCESS If the value was successfully encrypted + * LDB_ERR_OPERATIONS_ERROR If there was an error. + * + * @param ctx Talloc memory context the will own the memory allocated + * for the new ldb_message_element. + * @param ldb ldb context, to allow logging. + * @param el The ldb_message_elemen to decrypt, not altered or freed + * @param data The context data for this module. + * + * @return Pointer decrypted lsb_message_element, will be NULL if there was + * an error. + */ +static struct ldb_message_element *decrypt_element( + int *err, + TALLOC_CTX *ctx, + struct ldb_context *ldb, + struct ldb_message_element* el, + struct es_data *data) +{ + unsigned int i; + struct ldb_message_element* dec = + talloc_zero(ctx, struct ldb_message_element); + + *err = LDB_SUCCESS; + if (dec == NULL) { + ldb_set_errstring(ldb, + "Out of memory, allocating " + "ldb_message_element\n"); + *err = LDB_ERR_OPERATIONS_ERROR; + return NULL; + } + dec->num_values = el->num_values; + + dec->values = talloc_array(dec, struct ldb_val, dec->num_values); + if (dec->values == NULL) { + TALLOC_FREE(dec); + ldb_set_errstring(ldb, + "Out of memory, allocating values array\n"); + *err = LDB_ERR_OPERATIONS_ERROR; + return NULL; + } + + dec->name = talloc_strdup(dec, el->name); + if (dec->name == NULL) { + TALLOC_FREE(dec); + ldb_set_errstring(ldb, "Out of memory, copying element name\n"); + *err = LDB_ERR_OPERATIONS_ERROR; + return NULL; + } + + for (i = 0; i < el->num_values; i++) { + dec->values[i] = + decrypt_value(err, + el->values, + ldb, + el->values[i], + data); + if (*err != LDB_SUCCESS) { + TALLOC_FREE(dec); + return NULL; + } + } + return dec; +} + + +/* + * @brief Decrypt all the secret attributes on an ldb_message + * + * Decrypt all the secret attributes on an ldb_message. Any secret attributes + * are removed from message and decrypted copies of the attributes added. + * In the event of an error the contents of the message will be inconsistent. + * + * @param ldb ldb context, to allow logging. + * @param msg The ldb_message to have it's secret attributes encrypted. + * @param data The context data for this module. + * + * @returns ldb status code + * LDB_SUCESS If the value was successfully encrypted + * LDB_ERR_OPERATIONS_ERROR If there was an error. + */ +static int decrypt_secret_attributes(struct ldb_context *ldb, + struct ldb_message *msg, + struct es_data *data) +{ + size_t i; + int ret; + + if (ldb_dn_is_special(msg->dn)) { + return LDB_SUCCESS; + } + + for (i = 0; i < num_secret_attributes; i++) { + struct ldb_message_element *el = + ldb_msg_find_element(msg, secret_attributes[i]); + if (el != NULL) { + const int flags = el->flags; + struct ldb_message_element* dec = + decrypt_element(&ret, + msg->elements, + ldb, + el, + data); + if (ret != LDB_SUCCESS) { + return ret; + } + ldb_msg_remove_element(msg, el); + ret = ldb_msg_add(msg, dec, flags); + if (ret != LDB_SUCCESS) { + return ret; + } + } + } + return LDB_SUCCESS; +} + +static int es_search_post_process(struct ldb_module *module, + struct ldb_message *msg) +{ + struct ldb_context *ldb = ldb_module_get_ctx(module); + struct es_data *data = + talloc_get_type(ldb_module_get_private(module), + struct es_data); + + + /* + * Decrypt any encrypted secret attributes + */ + if (data && data->encrypt_secrets) { + int err = decrypt_secret_attributes(ldb, msg, data); + if (err != LDB_SUCCESS) { + return err; + } + } + return LDB_SUCCESS; +} + +/* + hook search operations +*/ +struct es_context { + struct ldb_module *module; + struct ldb_request *req; +}; + +static int es_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + struct es_context *ec; + int ret; + + + ec = talloc_get_type(req->context, struct es_context); + + if (!ares) { + return ldb_module_done(ec->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(ec->req, ares->controls, + ares->response, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + /* + * for each record returned decrypt any encrypted attributes + */ + ret = es_search_post_process(ec->module, ares->message); + if (ret != 0) { + return ldb_module_done(ec->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + return ldb_module_send_entry(ec->req, + ares->message, ares->controls); + + case LDB_REPLY_REFERRAL: + return ldb_module_send_referral(ec->req, ares->referral); + + case LDB_REPLY_DONE: + + return ldb_module_done(ec->req, ares->controls, + ares->response, LDB_SUCCESS); + } + + talloc_free(ares); + return LDB_SUCCESS; +} + +static int es_search(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + struct es_context *ec; + struct ldb_request *down_req; + int ret; + + /* There are no encrypted attributes on special DNs */ + if (ldb_dn_is_special(req->op.search.base)) { + return ldb_next_request(module, req); + } + + ldb = ldb_module_get_ctx(module); + + ec = talloc(req, struct es_context); + if (ec == NULL) { + return ldb_oom(ldb); + } + + ec->module = module; + ec->req = req; + ret = ldb_build_search_req_ex(&down_req, + ldb, + ec, + req->op.search.base, + req->op.search.scope, + req->op.search.tree, + req->op.search.attrs, + req->controls, + ec, + es_callback, + req); + LDB_REQ_SET_LOCATION(down_req); + if (ret != LDB_SUCCESS) { + return ldb_operr(ldb); + } + + /* perform the search */ + return ldb_next_request(module, down_req); +} +static int es_add(struct ldb_module *module, struct ldb_request *req) +{ + + struct es_data *data = + talloc_get_type(ldb_module_get_private(module), + struct es_data); + const struct ldb_message *encrypted_msg = NULL; + struct ldb_context *ldb = NULL; + int rc = LDB_SUCCESS; + + if (!data->encrypt_secrets) { + return ldb_next_request(module, req); + } + + ldb = ldb_module_get_ctx(module); + encrypted_msg = encrypt_secret_attributes(&rc, + req, + ldb, + req->op.add.message, + data); + if (rc != LDB_SUCCESS) { + return rc; + } + /* + * If we did not encrypt any of the attributes + * continue on to the next module + */ + if (encrypted_msg == NULL) { + return ldb_next_request(module, req); + } + + /* + * Encrypted an attribute, now need to build a copy of the request + * so that we're not altering the original callers copy + */ + { + struct ldb_request* new_req = NULL; + rc = ldb_build_add_req(&new_req, + ldb, + req, + encrypted_msg, + req->controls, + req, + dsdb_next_callback, + req); + if (rc != LDB_SUCCESS) { + return rc; + } + return ldb_next_request(module, new_req); + } +} + +static int es_modify(struct ldb_module *module, struct ldb_request *req) +{ + struct es_data *data = + talloc_get_type(ldb_module_get_private(module), + struct es_data); + const struct ldb_message *encrypted_msg = NULL; + struct ldb_context *ldb = NULL; + int rc = LDB_SUCCESS; + + if (!data->encrypt_secrets) { + return ldb_next_request(module, req); + } + + ldb = ldb_module_get_ctx(module); + encrypted_msg = encrypt_secret_attributes(&rc, + req, + ldb, + req->op.mod.message, + data); + if (rc != LDB_SUCCESS) { + return rc; + } + /* + * If we did not encrypt any of the attributes + * continue on to the next module + */ + if (encrypted_msg == NULL) { + return ldb_next_request(module, req); + } + + + /* + * Encrypted an attribute, now need to build a copy of the request + * so that we're not altering the original callers copy + */ + { + struct ldb_request* new_req = NULL; + rc = ldb_build_mod_req(&new_req, + ldb, + req, + encrypted_msg, + req->controls, + req, + dsdb_next_callback, + req); + if (rc != LDB_SUCCESS) { + return rc; + } + return ldb_next_request(module, new_req); + } +} + +static int es_delete(struct ldb_module *module, struct ldb_request *req) +{ + return ldb_next_request(module, req); +} + +static int es_rename(struct ldb_module *module, struct ldb_request *req) +{ + return ldb_next_request(module, req); +} +static int es_init(struct ldb_module *ctx) +{ + struct es_data *data; + int ret; + + data = talloc_zero(ctx, struct es_data); + if (!data) { + return ldb_module_oom(ctx); + } + + { + struct ldb_context *ldb = ldb_module_get_ctx(ctx); + struct ldb_dn *samba_dsdb_dn; + struct ldb_result *res; + static const char *samba_dsdb_attrs[] = { + SAMBA_REQUIRED_FEATURES_ATTR, + NULL + }; + TALLOC_CTX *frame = talloc_stackframe(); + + samba_dsdb_dn = ldb_dn_new(frame, ldb, "@SAMBA_DSDB"); + if (!samba_dsdb_dn) { + TALLOC_FREE(frame); + return ldb_oom(ldb); + } + ret = dsdb_module_search_dn(ctx, + frame, + &res, + samba_dsdb_dn, + samba_dsdb_attrs, + DSDB_FLAG_NEXT_MODULE, + NULL); + if (ret != LDB_SUCCESS) { + TALLOC_FREE(frame); + return ret; + } + data->encrypt_secrets = + ldb_msg_check_string_attribute( + res->msgs[0], + SAMBA_REQUIRED_FEATURES_ATTR, + SAMBA_ENCRYPTED_SECRETS_FEATURE); + if (data->encrypt_secrets) { + ret = load_keys(ctx, data); + if (ret != LDB_SUCCESS) { + TALLOC_FREE(frame); + return ret; + } + } + TALLOC_FREE(frame); + } + ldb_module_set_private(ctx, data); + + ret = ldb_next_init(ctx); + if (ret != LDB_SUCCESS) { + return ret; + } + return LDB_SUCCESS; +} + +static const struct ldb_module_ops ldb_encrypted_secrets_module_ops = { + .name = "encrypted_secrets", + .search = es_search, + .add = es_add, + .modify = es_modify, + .del = es_delete, + .rename = es_rename, + .init_context = es_init +}; + +int ldb_encrypted_secrets_module_init(const char *version) +{ + LDB_MODULE_CHECK_VERSION(version); + return ldb_register_module(&ldb_encrypted_secrets_module_ops); +} |