summaryrefslogtreecommitdiffstats
path: root/source4/dsdb/samdb/ldb_modules/encrypted_secrets.c
diff options
context:
space:
mode:
Diffstat (limited to 'source4/dsdb/samdb/ldb_modules/encrypted_secrets.c')
-rw-r--r--source4/dsdb/samdb/ldb_modules/encrypted_secrets.c1401
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..1f04389
--- /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 algorithm 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 compatible 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);
+}