dsdb encrypted secrets module
authorGary Lockyer <gary@catalyst.net.nz>
Thu, 14 Dec 2017 18:21:10 +0000 (07:21 +1300)
committerAndrew Bartlett <abartlet@samba.org>
Sun, 17 Dec 2017 23:10:16 +0000 (00:10 +0100)
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 nettle is
used.

Signed-off-by: Gary Lockyer <gary@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
librpc/idl/drsblobs.idl
source4/dsdb/samdb/ldb_modules/encrypted_secrets.c [new file with mode: 0644]
source4/dsdb/samdb/ldb_modules/tests/test_encrypted_secrets.c [new file with mode: 0644]
source4/dsdb/samdb/ldb_modules/wscript
source4/dsdb/samdb/ldb_modules/wscript_build
source4/dsdb/samdb/ldb_modules/wscript_build_server
source4/dsdb/samdb/samdb.h
source4/selftest/tests.py

index 9fca2cb8b1f96bfbf29863212835a20626c2173b..5fd11bbc88065c2019a07b3625c3576b78ec38bc 100644 (file)
@@ -721,4 +721,34 @@ interface drsblobs {
        [nopython] void decode_ForestTrustInfo(
                [in] ForestTrustInfo blob
                );
+
+       typedef enum {
+               ENC_SECRET_AES_128_AEAD = 1
+       } EncryptedSecretAlgorithm;
+
+       const uint32 ENCRYPTED_SECRET_MAGIC_VALUE = 0xCA5CADED;
+
+       typedef [public] struct {
+               DATA_BLOB cleartext;
+       } PlaintextSecret;
+
+       /* The AEAD routines uses this as the additional authenticated data */
+       typedef [public] struct {
+               uint32 magic;
+               uint32 version;
+               uint32 algorithm;
+               uint32 flags;
+       } EncryptedSecretHeader;
+
+       typedef [public] struct {
+               /*
+                * The iv is before the header to ensure that the first bytes of
+                * the encrypted values are not predictable.
+                * We do this so that if the decryption gets disabled, we don't
+                * end up with predictable unicodePasswords.
+                */
+               DATA_BLOB iv;
+               EncryptedSecretHeader header;
+               [flag(NDR_REMAINING)] DATA_BLOB encrypted;
+       } EncryptedSecret;
 }
diff --git a/source4/dsdb/samdb/ldb_modules/encrypted_secrets.c b/source4/dsdb/samdb/ldb_modules/encrypted_secrets.c
new file mode 100644 (file)
index 0000000..bc03fee
--- /dev/null
@@ -0,0 +1,1755 @@
+/*
+   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 nettle 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"
+
+#ifdef TEST_ENCRYPTED_SECRETS
+       #ifdef HAVE_NETTLE_AES_GCM
+               #define BUILD_WITH_NETTLE_AES_GCM
+       #endif
+       #ifdef HAVE_GNUTLS_AEAD
+               #define BUILD_WITH_GNUTLS_AEAD
+       #endif
+#else
+       #ifdef HAVE_GNUTLS_AEAD
+               #define BUILD_WITH_GNUTLS_AEAD
+       #elif defined  HAVE_NETTLE_AES_GCM
+               #define BUILD_WITH_NETTLE_AES_GCM
+       #endif
+#endif
+
+#ifdef BUILD_WITH_GNUTLS_AEAD
+       #include <gnutls/gnutls.h>
+       #include <gnutls/crypto.h>
+#endif /* BUILD_WITH_GNUTLS_AEAD */
+
+#ifdef BUILD_WITH_NETTLE_AES_GCM
+       #include <nettle/gcm.h>
+#endif /* BUILD_WITH_NETTLE_AES_GCM */
+
+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"
+
+
+#ifdef BUILD_WITH_NETTLE_AES_GCM
+/*
+ * Details of a nettle encryption algorithm
+ */
+
+#ifndef AES128_KEY_SIZE
+       #define AES128_KEY_SIZE 16
+       #define NETTLE_SIZE_TYPE unsigned
+#else
+       #define NETTLE_SIZE_TYPE size_t
+#endif
+#ifndef GCM_DIGEST_SIZE
+       #define GCM_DIGEST_SIZE 16
+#endif
+struct nettle_details {
+       size_t context_size;    /* Size of the nettle encryption context */
+       size_t block_size;      /* Cipher block size */
+       size_t key_size;        /* Size of the key */
+       size_t iv_size;         /* Size of the initialisation routine */
+       size_t tag_size;        /* Size of the aead tag */
+       /*
+        * Function to load the encryption key into the context
+        */
+       void (*set_key)(struct gcm_aes_ctx *,
+                       NETTLE_SIZE_TYPE,
+                       const uint8_t *);
+       /*
+        * Function to load the initialisation vector into the context
+        */
+       void (*set_iv)(struct gcm_aes_ctx *, NETTLE_SIZE_TYPE, const uint8_t *);
+       /*
+        * Function to encrypt data
+        */
+       void (*encrypt)
+               (struct gcm_aes_ctx *,
+                NETTLE_SIZE_TYPE,
+                uint8_t *,
+                const uint8_t *);
+       /*
+        * Function to decrypt data
+        */
+       void (*decrypt)
+               (struct gcm_aes_ctx *,
+                NETTLE_SIZE_TYPE,
+                uint8_t *,
+                const uint8_t *);
+       /*
+        * Function to add extra authentication data to the context.
+        */
+       void (*update)(struct gcm_aes_ctx *, NETTLE_SIZE_TYPE, const uint8_t *);
+       /*
+        * Function returning the authentication tag data
+        */
+       void (*digest)(struct gcm_aes_ctx *, NETTLE_SIZE_TYPE, uint8_t *);
+};
+
+/*
+ * Configuration of the supported nettle algorithms
+ */
+static struct nettle_details nettle_algorithms[] = {
+       /* Algorithms are numbered from 1, so element 0 is empty */
+       {},
+       /* AES-GCM 128 */
+       {
+               .context_size   = sizeof(struct gcm_aes_ctx),
+               .block_size     = GCM_BLOCK_SIZE,
+               .key_size       = AES128_KEY_SIZE,
+               .iv_size        = GCM_IV_SIZE,
+               .tag_size       = GCM_DIGEST_SIZE,
+               .set_key        = gcm_aes_set_key,
+               .set_iv         = gcm_aes_set_iv,
+               .encrypt        = gcm_aes_encrypt,
+               .decrypt        = gcm_aes_decrypt,
+               .update         = gcm_aes_update,
+               .digest         = gcm_aes_digest,
+       }
+};
+#endif /* BUILD_WITH_NETTLE_AES_GCM*/
+
+struct es_data {
+       /*
+        * Should secret attributes be encrypted and decrypted?
+        */
+       bool encrypt_secrets;
+       /*
+        * Encryption keys for secret attributes
+        */
+       DATA_BLOB keys[NUMBER_OF_KEYS];
+#ifdef BUILD_WITH_GNUTLS_AEAD
+       /*
+        * The gnutls algorithm used to encrypt attributes
+        */
+       int encryption_algorithm;
+#endif /* BUILD_WITH_GNUTLS_AEAD */
+};
+
+/*
+ * @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;
+       }
+       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);
+       if (read == 0) {
+               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);
+               fclose(fp);
+               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;
+       }
+       fclose(fp);
+
+
+       data->keys[0] = key;
+       data->encrypt_secrets = true;
+#ifdef BUILD_WITH_GNUTLS_AEAD
+       data->encryption_algorithm = GNUTLS_CIPHER_AES_128_GCM;
+#endif
+       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)
+{
+       int 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;
+}
+
+#ifdef BUILD_WITH_NETTLE_AES_GCM
+/*
+ * @brief Encrypt an ldb value using an aead algorithm.
+ *
+ * This function uses lib nettle directly to perform the encryption. However
+ * the encrypted data and tag are stored in a manner compatible with gnutls,
+ * so the gnutls aead functions can be used to decrypt and verify the data.
+ *
+ * @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 nettle_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;
+       DATA_BLOB pt = data_blob_null;
+       const struct nettle_details *ntl = NULL;
+       void *ntl_ctx = NULL;
+       struct ldb_val enc = data_blob_null;
+       DATA_BLOB key_blob = data_blob_null;
+       int rc;
+
+       TALLOC_CTX *frame = talloc_stackframe();
+       ntl = &nettle_algorithms[SECRET_ENCRYPTION_ALGORITHM];
+
+       es = makeEncryptedSecret(ldb, frame);
+       if (es == NULL) {
+               goto error_exit;
+       }
+
+       pt = makePlainText(frame, ldb, val);
+       if (pt.length == 0) {
+               goto error_exit;
+       }
+
+       ntl_ctx = talloc_zero_size(frame,  ntl->context_size);
+       if (ntl_ctx == NULL) {
+               ldb_set_errstring(ldb,
+                                 "Out of memory allocating nettle "
+                                 "encryption context\n");
+               goto error_exit;
+       }
+
+       /*
+        * Set the encryption key
+        */
+       key_blob = get_key(data);
+       if (key_blob.length != ntl->key_size) {
+               ldb_asprintf_errstring(ldb,
+                                      "Invalid EncryptedSecrets key size, "
+                                      "expected %ld bytes and is %ld bytes\n",
+                                      ntl->key_size,
+                                      key_blob.length);
+               goto error_exit;
+       }
+       ntl->set_key(ntl_ctx, key_blob.length, key_blob.data);
+
+       /*
+        * Set the initialisation vector
+        */
+       {
+               uint8_t *iv = talloc_zero_size(frame, ntl->iv_size);
+               if (iv == NULL) {
+                       ldb_set_errstring(ldb,
+                                         "Out of memory allocating iv\n");
+                       goto error_exit;
+               }
+
+               generate_random_buffer(iv, ntl->iv_size);
+
+               es->iv.length = ntl->iv_size;
+               es->iv.data   = iv;
+               ntl->set_iv(ntl_ctx, es->iv.length, es->iv.data);
+       }
+
+       /*
+        * Set the extra authentication data
+        */
+       ntl->update(ntl_ctx,
+                   sizeof(struct EncryptedSecretHeader),
+                   (uint8_t *)&es->header);
+
+       /*
+        * Encrypt the value, and append the GCM digest to the encrypted
+        * data so that it can be decrypted and validated by the
+        * gnutls aead decryption routines.
+        */
+       {
+               const unsigned tag_size = ntl->tag_size;
+               const size_t ed_size = round_to_block_size(
+                       ntl->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);
+
+               ntl->encrypt(ntl_ctx, pt.length, ct, pt.data);
+               ntl->digest(ntl_ctx, tag_size, &ct[pt.length]);
+
+               es->encrypted.length = pt.length + tag_size;
+               es->encrypted.data   = ct;
+       }
+
+       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:
+       *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 nettle 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  Talloc memory context that will own the memory allocated
+ * @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 nettle_decrypt_aead(int *err,
+                               TALLOC_CTX *ctx,
+                               struct ldb_context *ldb,
+                               struct EncryptedSecret *es,
+                               struct PlaintextSecret *ps,
+                               const struct es_data *data)
+{
+
+       const struct nettle_details *ntl =
+               &nettle_algorithms[es->header.algorithm];
+       const unsigned tag_size = ntl->tag_size;
+       void *ntl_ctx = NULL;
+       DATA_BLOB pt = data_blob_null;
+       DATA_BLOB key_blob = data_blob_null;
+       int rc;
+
+       TALLOC_CTX *frame = talloc_stackframe();
+
+
+       ntl_ctx = talloc_zero_size(frame,  ntl->context_size);
+       if (ntl_ctx == NULL) {
+               ldb_set_errstring(ldb,
+                                 "Out of memory allocating nettle "
+                                 "encryption context\n");
+               goto error_exit;
+       }
+
+       /*
+        * Set the encryption key
+        */
+       key_blob = get_key(data);
+       if (key_blob.length != ntl->key_size) {
+               ldb_asprintf_errstring(ldb,
+                                      "Invalid EncryptedSecrets key size, "
+                                      "expected %ld bytes and is %ld bytes\n",
+                                      ntl->key_size,
+                                      key_blob.length);
+               goto error_exit;
+       }
+       ntl->set_key(ntl_ctx, key_blob.length, key_blob.data);
+
+       ntl->set_iv(ntl_ctx, es->iv.length, es->iv.data);
+       ntl->update(ntl_ctx,
+                   sizeof(struct EncryptedSecretHeader),
+                   (uint8_t *)&es->header);
+
+       pt.length = es->encrypted.length - tag_size;
+       pt.data   = talloc_zero_size(ctx, pt.length);
+       if (pt.data == NULL) {
+               ldb_set_errstring(ldb,
+                                 "Out of memory allocating space for "
+                                 "plain text\n");
+               goto error_exit;
+       }
+       ntl->decrypt(ntl_ctx, pt.length, pt.data, es->encrypted.data);
+
+       /*
+        * Check the authentication tag
+        */
+       {
+               uint8_t *tag = talloc_zero_size(frame, tag_size);
+               if (tag == NULL) {
+                       ldb_set_errstring(ldb,
+                                         "Out of memory allocating tag\n");
+                       goto error_exit;
+               }
+
+               ntl->digest(ntl_ctx, tag_size, tag);
+               if (memcmp(&es->encrypted.data[pt.length],
+                          tag,
+                          tag_size) != 0) {
+
+                       ldb_set_errstring(ldb,
+                                         "Tag does not match, "
+                                         "data corrupted or altered\n");
+                       goto error_exit;
+               }
+       }
+
+       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;
+       }
+       TALLOC_FREE(frame);
+       return;
+
+error_exit:
+       *err = LDB_ERR_OPERATIONS_ERROR;
+       TALLOC_FREE(frame);
+       return;
+}
+#endif /* BUILD_WITH_NETTLE_AES_GCM */
+
+#ifdef BUILD_WITH_GNUTLS_AEAD
+
+/*
+ * 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 %ld bytes and "
+                                              "it is %ld 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.
+        */
+       {
+               size_t el;
+               const unsigned block_size = gnutls_cipher_get_tag_size(
+                       data->encryption_algorithm);
+               const unsigned tag_size = gnutls_cipher_get_block_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);
+
+               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 %ld bytes and "
+                                              "it is %ld 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;
+}
+#endif /* BUILD_WITH_GNUTLS_AEAD */
+
+/*
+ * @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)
+{
+#ifdef BUILD_WITH_GNUTLS_AEAD
+       return gnutls_encrypt_aead(err, ctx, ldb, val, data);
+#elif defined BUILD_WITH_NETTLE_AES_GCM
+       return nettle_encrypt_aead(err, ctx, ldb, val, data);
+#endif
+}
+
+/*
+ * @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;
+       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;
+
+       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);
+                               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;
+       }
+#ifdef BUILD_WITH_GNUTLS_AEAD
+       gnutls_decrypt_aead(err, frame, ldb, &es, &ps, data);
+#elif defined BUILD_WITH_NETTLE_AES_GCM
+       nettle_decrypt_aead(err, frame, ldb, &es, &ps, data);
+#endif
+
+       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)
+{
+       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)
+{
+
+       int i, 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->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);
+       }
+       req->op.add.message = encrypted_msg;
+       return ldb_next_request(module, 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);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/tests/test_encrypted_secrets.c b/source4/dsdb/samdb/ldb_modules/tests/test_encrypted_secrets.c
new file mode 100644 (file)
index 0000000..7c13631
--- /dev/null
@@ -0,0 +1,1186 @@
+/*
+   Unit tests for the encrypted secrets code in encrypted_secrets.c
+
+   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/>.
+*/
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <unistd.h>
+#include <cmocka.h>
+
+int ldb_encrypted_secrets_module_init(const char *version);
+#define TEST_ENCRYPTED_SECRETS
+#include "../encrypted_secrets.c"
+
+#define TEST_BE "tdb"
+struct ldbtest_ctx {
+       struct tevent_context *ev;
+       struct ldb_context *ldb;
+       struct ldb_module *module;
+
+       const char *dbfile;
+       const char *lockfile;   /* lockfile is separate */
+       const char *keyfile;
+
+       const char *dbpath;
+};
+
+/* -------------------------------------------------------------------------- */
+/*
+ * Replace the dsdb helper routines used by the operational_init function
+ *
+ */
+int dsdb_module_search_dn(
+       struct ldb_module *module,
+       TALLOC_CTX *mem_ctx,
+       struct ldb_result **_res,
+       struct ldb_dn *basedn,
+       const char * const *attrs,
+       uint32_t dsdb_flags,
+       struct ldb_request *parent)
+{
+       struct ldb_context *ldb = ldb_module_get_ctx(module);
+       struct ldb_message *msg = ldb_msg_new(ldb);
+       struct ldb_result  *res = talloc_zero(mem_ctx, struct ldb_result);
+
+       msg->dn = ldb_dn_new(msg, ldb, "@SAMBA_DSDB");
+       ldb_msg_add_string(
+               msg,
+               SAMBA_REQUIRED_FEATURES_ATTR,
+               SAMBA_ENCRYPTED_SECRETS_FEATURE);
+
+       res->msgs = talloc_array(mem_ctx, struct ldb_message*, 1);
+       res->msgs[0] = msg;
+       *_res = res;
+       return LDB_SUCCESS;
+}
+
+int dsdb_module_reference_dn(
+       struct ldb_module *module,
+       TALLOC_CTX *mem_ctx,
+       struct ldb_dn *base,
+       const char *attribute,
+       struct ldb_dn **dn,
+       struct ldb_request *parent)
+{
+       return LDB_SUCCESS;
+}
+/* -------------------------------------------------------------------------- */
+
+static void unlink_old_db(struct ldbtest_ctx *test_ctx)
+{
+       int ret;
+
+       errno = 0;
+       ret = unlink(test_ctx->lockfile);
+       if (ret == -1 && errno != ENOENT) {
+               fail();
+       }
+
+       errno = 0;
+       ret = unlink(test_ctx->dbfile);
+       if (ret == -1 && errno != ENOENT) {
+               fail();
+       }
+
+       errno = 0;
+       ret = unlink(test_ctx->keyfile);
+       if (ret == -1 && errno != ENOENT) {
+               fail();
+       }
+}
+
+static void write_key(void **state, DATA_BLOB key) {
+
+       struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+                                                       struct ldbtest_ctx);
+       FILE *fp = NULL;
+       int written = 0;
+
+       fp = fopen(test_ctx->keyfile, "wb");
+       assert_non_null(fp);
+
+       written = fwrite(key.data, 1, key.length, fp);
+       assert_int_equal(written, key.length);
+       fclose(fp);
+}
+
+static const struct ldb_module_ops eol_ops = {
+       .name              = "eol",
+       .search            = NULL,
+       .add               = NULL,
+       .modify            = NULL,
+       .del               = NULL,
+       .rename            = NULL,
+       .init_context      = NULL
+};
+
+static int setup(void **state)
+{
+       struct ldbtest_ctx *test_ctx = NULL;
+       struct ldb_module *eol = NULL;
+       int rc;
+
+       test_ctx = talloc_zero(NULL, struct ldbtest_ctx);
+       assert_non_null(test_ctx);
+
+       test_ctx->ev = tevent_context_init(test_ctx);
+       assert_non_null(test_ctx->ev);
+
+       test_ctx->ldb = ldb_init(test_ctx, test_ctx->ev);
+       assert_non_null(test_ctx->ldb);
+
+
+
+        test_ctx->module = ldb_module_new(
+               test_ctx,
+               test_ctx->ldb,
+               "encrypted_secrets",
+               &ldb_encrypted_secrets_module_ops);
+       assert_non_null(test_ctx->module);
+       eol = ldb_module_new(test_ctx, test_ctx->ldb, "eol", &eol_ops);
+       assert_non_null(eol);
+       ldb_module_set_next(test_ctx->module, eol);
+
+       test_ctx->dbfile = talloc_strdup(test_ctx, "apitest.ldb");
+       assert_non_null(test_ctx->dbfile);
+
+       test_ctx->lockfile = talloc_asprintf(test_ctx, "%s-lock",
+                                            test_ctx->dbfile);
+       assert_non_null(test_ctx->lockfile);
+
+       test_ctx->keyfile = talloc_strdup(test_ctx, SECRETS_KEY_FILE);
+       assert_non_null(test_ctx->keyfile);
+
+       test_ctx->dbpath = talloc_asprintf(test_ctx,
+                       TEST_BE"://%s", test_ctx->dbfile);
+       assert_non_null(test_ctx->dbpath);
+
+       unlink_old_db(test_ctx);
+
+       rc = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, NULL);
+       assert_int_equal(rc, 0);
+       *state = test_ctx;
+       return 0;
+}
+
+static int setup_with_key(void **state)
+{
+       struct ldbtest_ctx *test_ctx = NULL;
+       DATA_BLOB key = data_blob_null;
+       uint8_t key_data[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+                             0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
+       int rc;
+
+       setup(state);
+       key.data   = key_data;
+       key.length = sizeof(key_data);
+
+       write_key(state, key);
+
+       test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx);
+       {
+               struct ldb_message *msg = ldb_msg_new(test_ctx->ldb);
+               msg->dn = ldb_dn_new(msg, test_ctx->ldb, "@SAMBA_DSDB");
+               ldb_msg_add_string(
+                       msg,
+                       SAMBA_REQUIRED_FEATURES_ATTR,
+                       SAMBA_ENCRYPTED_SECRETS_FEATURE);
+               ldb_add(test_ctx->ldb, msg);
+       }
+
+       rc = es_init(test_ctx->module);
+       assert_int_equal(rc, LDB_SUCCESS);
+
+       return 0;
+}
+
+static int teardown(void **state)
+{
+       struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+                                                       struct ldbtest_ctx);
+
+       unlink_old_db(test_ctx);
+       talloc_free(test_ctx);
+       return 0;
+}
+/*
+ * No key file present.
+ *
+ * The key should be empty and encrypt_secrets should be false.
+ */
+static void test_no_key_file(void **state)
+{
+       struct ldbtest_ctx *test_ctx =
+               talloc_get_type_abort(*state, struct ldbtest_ctx);
+       struct es_data *data = NULL;
+
+       int rc;
+
+       rc = es_init(test_ctx->module);
+       assert_int_equal(rc, LDB_SUCCESS);
+
+       data = talloc_get_type(ldb_module_get_private(test_ctx->module),
+                              struct es_data);
+
+       assert_false(data->encrypt_secrets);
+       assert_int_equal(0, data->keys[0].length);
+
+}
+
+/*
+ * Key file present.
+ *
+ * The key should be loaded and encrypt secrets should be true;
+ */
+static void test_key_file(void **state)
+{
+       struct ldbtest_ctx *test_ctx =
+               talloc_get_type_abort(*state, struct ldbtest_ctx);
+       struct es_data *data = NULL;
+       int rc;
+       DATA_BLOB key = data_blob_null;
+       uint8_t key_data[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+                             0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
+
+       key.data   = key_data;
+       key.length = sizeof(key_data);
+
+       write_key(state, key);
+
+
+       rc = es_init(test_ctx->module);
+       assert_int_equal(rc, LDB_SUCCESS);
+
+       data = talloc_get_type(ldb_module_get_private(test_ctx->module),
+                              struct es_data);
+
+       assert_true(data->encrypt_secrets);
+       assert_int_equal(16, data->keys[0].length);
+       assert_int_equal(0, data_blob_cmp(&key, &data->keys[0]));
+
+}
+
+/*
+ * Key file present, short key.
+ *
+ * The key should be not be loaded and an error returned.
+ */
+static void test_key_file_short_key(void **state)
+{
+       struct ldbtest_ctx *test_ctx =
+               talloc_get_type_abort(*state, struct ldbtest_ctx);
+       int rc;
+       DATA_BLOB key = data_blob_null;
+       uint8_t key_data[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+                             0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e};
+
+       key.data   = key_data;
+       key.length = sizeof(key_data);
+
+       write_key(state, key);
+
+
+       rc = es_init(test_ctx->module);
+       assert_int_equal(rc, LDB_ERR_OPERATIONS_ERROR);
+}
+
+/*
+ * Key file present, long key.
+ *
+ * Only the first 16 bytes of the key should be loaded.
+ */
+static void test_key_file_long_key(void **state)
+{
+       struct ldbtest_ctx *test_ctx =
+               talloc_get_type_abort(*state, struct ldbtest_ctx);
+       struct es_data *data = NULL;
+       int rc;
+       DATA_BLOB key = data_blob_null;
+       uint8_t key_data[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+                             0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf,
+                             0x10};
+
+       key.data   = key_data;
+       key.length = sizeof(key_data);
+
+       write_key(state, key);
+
+       rc = es_init(test_ctx->module);
+       assert_int_equal(rc, LDB_SUCCESS);
+
+       data = talloc_get_type(ldb_module_get_private(test_ctx->module),
+                              struct es_data);
+
+       assert_true(data->encrypt_secrets);
+       assert_int_equal(16, data->keys[0].length);
+
+       /*
+        * Should have only read the first 16 bytes of the written key
+        */
+       key.length = 16;
+       assert_int_equal(0, data_blob_cmp(&key, &data->keys[0]));
+}
+
+#ifdef HAVE_GNUTLS_AEAD
+/*
+ *  Test gnutls_encryption and decryption.
+ */
+static void test_gnutls_value_encryption(void **state)
+{
+       struct ldbtest_ctx *test_ctx =
+               talloc_get_type_abort(*state, struct ldbtest_ctx);
+       struct ldb_val plain_text = data_blob_null;
+       struct ldb_val cipher_text = data_blob_null;
+       struct EncryptedSecret es;
+
+       struct es_data *data = talloc_get_type(
+               ldb_module_get_private(test_ctx->module),
+               struct es_data);
+       int err = LDB_SUCCESS;
+       int rc;
+
+       plain_text = data_blob_string_const("A text value");
+       cipher_text = gnutls_encrypt_aead(
+                       &err,
+                       test_ctx,
+                       test_ctx->ldb,
+                       plain_text,
+                       data);
+       assert_int_equal(LDB_SUCCESS, err);
+
+       rc = ndr_pull_struct_blob(
+               &cipher_text,
+               test_ctx,
+               &es,
+               (ndr_pull_flags_fn_t) ndr_pull_EncryptedSecret);
+       assert_true(NDR_ERR_CODE_IS_SUCCESS(rc));
+       assert_true(check_header(&es));
+
+       {
+               struct PlaintextSecret *decrypted =
+                       talloc_zero(test_ctx, struct PlaintextSecret);
+               gnutls_decrypt_aead(
+                       &err,
+                       test_ctx,
+                       test_ctx->ldb,
+                       &es,
+                       decrypted,
+                       data);
+               assert_int_equal(LDB_SUCCESS, err);
+               assert_int_equal(
+                       plain_text.length,
+                       decrypted->cleartext.length);
+               assert_int_equal(0,
+                       data_blob_cmp(
+                               &decrypted->cleartext,
+                               &plain_text));
+       }
+
+#ifdef HAVE_NETTLE_AES_GCM
+       {
+               struct PlaintextSecret *decrypted =
+                       talloc_zero(test_ctx, struct PlaintextSecret);
+               nettle_decrypt_aead(
+                       &err,
+                       test_ctx,
+                       test_ctx->ldb,
+                       &es,
+                       decrypted,
+                       data);
+               assert_int_equal(LDB_SUCCESS, err);
+               assert_int_equal(
+                       plain_text.length,
+                       decrypted->cleartext.length);
+               assert_int_equal(0,
+                       data_blob_cmp(
+                               &decrypted->cleartext,
+                               &plain_text));
+       }
+#endif /* HAVE_NETTLE_AES_GCM */
+}
+#endif /* HAVE_GNUTLS_AEAD */
+
+#ifdef HAVE_GNUTLS_AEAD
+static void test_gnutls_altered_header(void **state)
+{
+       struct ldbtest_ctx *test_ctx =
+               talloc_get_type_abort(*state, struct ldbtest_ctx);
+       struct ldb_val plain_text = data_blob_null;
+       struct ldb_val cipher_text = data_blob_null;
+       struct EncryptedSecret es;
+
+       struct es_data *data = talloc_get_type(
+               ldb_module_get_private(test_ctx->module),
+               struct es_data);
+       int err = LDB_SUCCESS;
+       int rc;
+
+       plain_text = data_blob_string_const("A text value");
+       cipher_text = gnutls_encrypt_aead(
+                       &err,
+                       test_ctx,
+                       test_ctx->ldb,
+                       plain_text,
+                       data);
+       assert_int_equal(LDB_SUCCESS, err);
+
+       rc = ndr_pull_struct_blob(
+               &cipher_text,
+               test_ctx,
+               &es,
+               (ndr_pull_flags_fn_t) ndr_pull_EncryptedSecret);
+       assert_true(NDR_ERR_CODE_IS_SUCCESS(rc));
+       assert_true(check_header(&es));
+
+       {
+               struct PlaintextSecret *decrypted =
+                       talloc_zero(test_ctx, struct PlaintextSecret);
+               gnutls_decrypt_aead(
+                       &err,
+                       test_ctx,
+                       test_ctx->ldb,
+                       &es,
+                       decrypted,
+                       data);
+               assert_int_equal(LDB_SUCCESS, err);
+               assert_int_equal(
+                       plain_text.length,
+                       decrypted->cleartext.length);
+               assert_int_equal(0,
+                       data_blob_cmp(
+                               &decrypted->cleartext,
+                               &plain_text));
+       }
+       es.header.flags = es.header.flags ^ 0xffffffff;
+       {
+               struct PlaintextSecret *decrypted =
+                       talloc_zero(test_ctx, struct PlaintextSecret);
+               gnutls_decrypt_aead(
+                       &err,
+                       test_ctx,
+                       test_ctx->ldb,
+                       &es,
+                       decrypted,
+                       data);
+               assert_int_equal(LDB_ERR_OPERATIONS_ERROR, err);
+       }
+}
+#endif /* HAVE_GNUTLS_AEAD */
+
+#ifdef HAVE_GNUTLS_AEAD
+static void test_gnutls_altered_data(void **state)
+{
+       struct ldbtest_ctx *test_ctx =
+               talloc_get_type_abort(*state, struct ldbtest_ctx);
+       struct ldb_val plain_text = data_blob_null;
+       struct ldb_val cipher_text = data_blob_null;
+       struct EncryptedSecret es;
+
+       struct es_data *data = talloc_get_type(
+               ldb_module_get_private(test_ctx->module),
+               struct es_data);
+       int err = LDB_SUCCESS;
+       int rc;
+
+       plain_text = data_blob_string_const("A text value");
+       cipher_text = gnutls_encrypt_aead(
+                       &err,
+                       test_ctx,
+                       test_ctx->ldb,
+                       plain_text,
+                       data);
+       assert_int_equal(LDB_SUCCESS, err);
+
+       rc = ndr_pull_struct_blob(
+               &cipher_text,
+               test_ctx,
+               &es,
+               (ndr_pull_flags_fn_t) ndr_pull_EncryptedSecret);
+       assert_true(NDR_ERR_CODE_IS_SUCCESS(rc));
+       assert_true(check_header(&es));
+
+       {
+               struct PlaintextSecret *decrypted =
+                       talloc_zero(test_ctx, struct PlaintextSecret);
+               gnutls_decrypt_aead(
+                       &err,
+                       test_ctx,
+                       test_ctx->ldb,
+                       &es,
+                       decrypted,
+                       data);
+               assert_int_equal(LDB_SUCCESS, err);
+               assert_int_equal(
+                       plain_text.length,
+                       decrypted->cleartext.length);
+               assert_int_equal(0,
+                       data_blob_cmp(
+                               &decrypted->cleartext,
+                               &plain_text));
+       }
+       es.encrypted.data[0] = es.encrypted.data[0] ^ 0xff;
+       {
+               struct PlaintextSecret *decrypted =
+                       talloc_zero(test_ctx, struct PlaintextSecret);
+               gnutls_decrypt_aead(
+                       &err,
+                       test_ctx,
+                       test_ctx->ldb,
+                       &es,
+                       decrypted,
+                       data);
+               assert_int_equal(LDB_ERR_OPERATIONS_ERROR, err);
+       }
+}
+#endif /* HAVE_GNUTLS_AEAD */
+
+#ifdef HAVE_GNUTLS_AEAD
+static void test_gnutls_altered_iv(void **state)
+{
+       struct ldbtest_ctx *test_ctx =
+               talloc_get_type_abort(*state, struct ldbtest_ctx);
+       struct ldb_val plain_text = data_blob_null;
+       struct ldb_val cipher_text = data_blob_null;
+       struct EncryptedSecret es;
+
+       struct es_data *data = talloc_get_type(
+               ldb_module_get_private(test_ctx->module),
+               struct es_data);
+       int err = LDB_SUCCESS;
+       int rc;
+
+       plain_text = data_blob_string_const("A text value");
+       cipher_text = gnutls_encrypt_aead(
+                       &err,
+                       test_ctx,
+                       test_ctx->ldb,
+                       plain_text,
+                       data);
+       assert_int_equal(LDB_SUCCESS, err);
+
+       rc = ndr_pull_struct_blob(
+               &cipher_text,
+               test_ctx,
+               &es,
+               (ndr_pull_flags_fn_t) ndr_pull_EncryptedSecret);
+       assert_true(NDR_ERR_CODE_IS_SUCCESS(rc));
+       assert_true(check_header(&es));
+
+       {
+               struct PlaintextSecret *decrypted =
+                       talloc_zero(test_ctx, struct PlaintextSecret);
+               gnutls_decrypt_aead(
+                       &err,
+                       test_ctx,
+                       test_ctx->ldb,
+                       &es,
+                       decrypted,
+                       data);
+               assert_int_equal(LDB_SUCCESS, err);
+               assert_int_equal(
+                       plain_text.length,
+                       decrypted->cleartext.length);
+               assert_int_equal(0,
+                       data_blob_cmp(
+                               &decrypted->cleartext,
+                               &plain_text));
+       }
+       es.iv.data[0] = es.iv.data[0] ^ 0xff;
+       {
+               struct PlaintextSecret *decrypted =
+                       talloc_zero(test_ctx, struct PlaintextSecret);
+               gnutls_decrypt_aead(
+                       &err,
+                       test_ctx,
+                       test_ctx->ldb,
+                       &es,
+                       decrypted,
+                       data);
+               assert_int_equal(LDB_ERR_OPERATIONS_ERROR, err);
+       }
+}
+#endif /* HAVE_GNUTLS_AEAD */
+#ifdef HAVE_NETTLE_AES_GCM
+/*
+ *  Test nettle encryption and decryption and decryption.
+ */
+static void test_nettle_value_encryption(void **state)
+{
+       struct ldbtest_ctx *test_ctx =
+               talloc_get_type_abort(*state, struct ldbtest_ctx);
+       struct ldb_val plain_text = data_blob_null;
+       struct ldb_val cipher_text = data_blob_null;
+       struct EncryptedSecret es;
+
+       struct es_data *data = talloc_get_type(
+               ldb_module_get_private(test_ctx->module),
+               struct es_data);
+       int err = LDB_SUCCESS;
+       int rc;
+
+       plain_text = data_blob_string_const("A text value");
+       cipher_text = nettle_encrypt_aead(
+                       &err,
+                       test_ctx,
+                       test_ctx->ldb,
+                       plain_text,
+                       data);
+       assert_int_equal(LDB_SUCCESS, err);
+
+       rc = ndr_pull_struct_blob(
+               &cipher_text,
+               test_ctx,
+               &es,
+               (ndr_pull_flags_fn_t) ndr_pull_EncryptedSecret);
+       assert_true(NDR_ERR_CODE_IS_SUCCESS(rc));
+       assert_true(check_header(&es));
+
+#ifdef HAVE_GNUTLS_AEAD
+       {
+               struct PlaintextSecret *decrypted =
+                       talloc_zero(test_ctx, struct PlaintextSecret);
+               gnutls_decrypt_aead(
+                       &err,
+                       test_ctx,
+                       test_ctx->ldb,
+                       &es,
+                       decrypted,
+                       data);
+               assert_int_equal(LDB_SUCCESS, err);
+               assert_int_equal(
+                       plain_text.length,
+                       decrypted->cleartext.length);
+               assert_int_equal(0,
+                       data_blob_cmp(
+                               &decrypted->cleartext,
+                               &plain_text));
+       }
+#endif /* HAVE_GNUTLS_AEAD */
+
+
+       {
+               struct PlaintextSecret *decrypted =
+                       talloc_zero(test_ctx, struct PlaintextSecret);
+               nettle_decrypt_aead(
+                       &err,
+                       test_ctx,
+                       test_ctx->ldb,
+                       &es,
+                       decrypted,
+                       data);
+               assert_int_equal(LDB_SUCCESS, err);
+               assert_int_equal(
+                       plain_text.length,
+                       decrypted->cleartext.length);
+               assert_int_equal(0,
+                       data_blob_cmp(
+                               &decrypted->cleartext,
+                               &plain_text));
+       }
+
+}
+#endif /* HAVE_NETTLE_AES_GCM  */
+
+#ifdef HAVE_NETTLE_AES_GCM
+static void test_nettle_altered_header(void **state)
+{
+       struct ldbtest_ctx *test_ctx =
+               talloc_get_type_abort(*state, struct ldbtest_ctx);
+       struct ldb_val plain_text = data_blob_null;
+       struct ldb_val cipher_text = data_blob_null;
+       struct EncryptedSecret es;
+
+       struct es_data *data = talloc_get_type(
+               ldb_module_get_private(test_ctx->module),
+               struct es_data);
+       int err = LDB_SUCCESS;
+       int rc;
+
+       plain_text = data_blob_string_const("A text value");
+       cipher_text = nettle_encrypt_aead(
+                       &err,
+                       test_ctx,
+                       test_ctx->ldb,
+                       plain_text,
+                       data);
+       assert_int_equal(LDB_SUCCESS, err);
+
+       rc = ndr_pull_struct_blob(
+               &cipher_text,
+               test_ctx,
+               &es,
+               (ndr_pull_flags_fn_t) ndr_pull_EncryptedSecret);
+       assert_true(NDR_ERR_CODE_IS_SUCCESS(rc));
+       assert_true(check_header(&es));
+
+       {
+               struct PlaintextSecret *decrypted =
+                       talloc_zero(test_ctx, struct PlaintextSecret);
+               nettle_decrypt_aead(
+                       &err,
+                       test_ctx,
+                       test_ctx->ldb,
+                       &es,
+                       decrypted,
+                       data);
+               assert_int_equal(LDB_SUCCESS, err);
+               assert_int_equal(
+                       plain_text.length,
+                       decrypted->cleartext.length);
+               assert_int_equal(0,
+                       data_blob_cmp(
+                               &decrypted->cleartext,
+                               &plain_text));
+       }
+       es.header.flags = es.header.flags ^ 0xffffffff;
+       {
+               struct PlaintextSecret *decrypted =
+                       talloc_zero(test_ctx, struct PlaintextSecret);
+               nettle_decrypt_aead(
+                       &err,
+                       test_ctx,
+                       test_ctx->ldb,
+                       &es,
+                       decrypted,
+                       data);
+               assert_int_equal(LDB_ERR_OPERATIONS_ERROR, err);
+       }
+}
+#endif /* HAVE_NETTLE_AES_GCM */
+
+#ifdef HAVE_NETTLE_AES_GCM
+static void test_nettle_altered_data(void **state)
+{
+       struct ldbtest_ctx *test_ctx =
+               talloc_get_type_abort(*state, struct ldbtest_ctx);
+       struct ldb_val plain_text = data_blob_null;
+       struct ldb_val cipher_text = data_blob_null;
+       struct EncryptedSecret es;
+
+       struct es_data *data = talloc_get_type(
+               ldb_module_get_private(test_ctx->module),
+               struct es_data);
+       int err = LDB_SUCCESS;
+       int rc;
+
+       plain_text = data_blob_string_const("A text value");
+       cipher_text = nettle_encrypt_aead(
+                       &err,
+                       test_ctx,
+                       test_ctx->ldb,
+                       plain_text,
+                       data);
+       assert_int_equal(LDB_SUCCESS, err);
+
+       rc = ndr_pull_struct_blob(
+               &cipher_text,
+               test_ctx,
+               &es,
+               (ndr_pull_flags_fn_t) ndr_pull_EncryptedSecret);
+       assert_true(NDR_ERR_CODE_IS_SUCCESS(rc));
+       assert_true(check_header(&es));
+
+       {
+               struct PlaintextSecret *decrypted =
+                       talloc_zero(test_ctx, struct PlaintextSecret);
+               nettle_decrypt_aead(
+                       &err,
+                       test_ctx,
+                       test_ctx->ldb,
+                       &es,
+                       decrypted,
+                       data);
+               assert_int_equal(LDB_SUCCESS, err);
+               assert_int_equal(
+                       plain_text.length,
+                       decrypted->cleartext.length);
+               assert_int_equal(0,
+                       data_blob_cmp(
+                               &decrypted->cleartext,
+                               &plain_text));
+       }
+       es.encrypted.data[0] = es.encrypted.data[0] ^ 0xff;
+       {
+               struct PlaintextSecret *decrypted =
+                       talloc_zero(test_ctx, struct PlaintextSecret);
+               nettle_decrypt_aead(
+                       &err,
+                       test_ctx,
+                       test_ctx->ldb,
+                       &es,
+                       decrypted,
+                       data);
+               assert_int_equal(LDB_ERR_OPERATIONS_ERROR, err);
+       }
+}
+#endif /* HAVE_NETTLE_AES_GCM */
+
+#ifdef HAVE_NETTLE_AES_GCM
+static void test_nettle_altered_iv(void **state)
+{
+       struct ldbtest_ctx *test_ctx =
+               talloc_get_type_abort(*state, struct ldbtest_ctx);
+       struct ldb_val plain_text = data_blob_null;
+       struct ldb_val cipher_text = data_blob_null;
+       struct EncryptedSecret es;
+
+       struct es_data *data = talloc_get_type(
+               ldb_module_get_private(test_ctx->module),
+               struct es_data);
+       int err = LDB_SUCCESS;
+       int rc;
+
+       plain_text = data_blob_string_const("A text value");
+       cipher_text = nettle_encrypt_aead(
+                       &err,
+                       test_ctx,
+                       test_ctx->ldb,
+                       plain_text,
+                       data);
+       assert_int_equal(LDB_SUCCESS, err);
+
+       rc = ndr_pull_struct_blob(
+               &cipher_text,
+               test_ctx,
+               &es,
+               (ndr_pull_flags_fn_t) ndr_pull_EncryptedSecret);
+       assert_true(NDR_ERR_CODE_IS_SUCCESS(rc));
+       assert_true(check_header(&es));
+
+       {
+               struct PlaintextSecret *decrypted =
+                       talloc_zero(test_ctx, struct PlaintextSecret);
+               nettle_decrypt_aead(
+                       &err,
+                       test_ctx,
+                       test_ctx->ldb,
+                       &es,
+                       decrypted,
+                       data);
+               assert_int_equal(LDB_SUCCESS, err);
+               assert_int_equal(
+                       plain_text.length,
+                       decrypted->cleartext.length);
+               assert_int_equal(0,
+                       data_blob_cmp(
+                               &decrypted->cleartext,
+                               &plain_text));
+       }
+       es.iv.data[0] = es.iv.data[0] ^ 0xff;
+       {
+               struct PlaintextSecret *decrypted =
+                       talloc_zero(test_ctx, struct PlaintextSecret);
+               nettle_decrypt_aead(
+                       &err,
+                       test_ctx,
+                       test_ctx->ldb,
+                       &es,
+                       decrypted,
+                       data);
+               assert_int_equal(LDB_ERR_OPERATIONS_ERROR, err);
+       }
+}
+#endif /* HAVE_NETTLE_AES_GCM */
+
+/*
+ *  Test message encryption.
+ *  Test the secret attributes of a message are encrypted and decrypted.
+ *  Test that the non secret attributes are not encrypted.
+ *
+ */
+static void test_message_encryption_decryption(void **state)
+{
+       struct ldbtest_ctx *test_ctx =
+               talloc_get_type_abort(*state, struct ldbtest_ctx);
+       struct ldb_context *ldb = test_ctx->ldb;
+       const char * const secrets[] = {DSDB_SECRET_ATTRIBUTES};
+       const size_t num_secrets
+               = (sizeof(secrets)/sizeof(secrets[0]));
+       struct ldb_message *msg = ldb_msg_new(ldb);
+       const struct ldb_message *encrypted_msg = NULL;
+       struct es_data *data = talloc_get_type(
+               ldb_module_get_private(test_ctx->module),
+               struct es_data);
+       struct ldb_message_element *el = NULL;
+       int ret = LDB_SUCCESS;
+       int i, j;
+
+       msg->dn = ldb_dn_new(msg, ldb, "dc=test");
+       ldb_msg_add_string(msg, "cmocka_test_name01", "value01");
+       for (i=0; i < num_secrets; i++) {
+               ldb_msg_add_string(
+                       msg,
+                       secrets[i],
+                       secrets[i]);
+       }
+       ldb_msg_add_string(msg, "cmocka_test_name02", "value02");
+
+       encrypted_msg = encrypt_secret_attributes(
+               &ret,
+               test_ctx,
+               test_ctx->ldb,
+               msg,
+               data);
+       assert_int_equal(LDB_SUCCESS, ret);
+
+       /*
+        * Check that all the secret attributes have been encrypted
+        *
+        */
+       for (i=0; i < num_secrets; i++) {
+               el = ldb_msg_find_element(encrypted_msg, secrets[i]);
+               assert_non_null(el);
+               for (j = 0; j < el->num_values; j++) {
+                       int rc = LDB_SUCCESS;
+                       struct ldb_val dc = decrypt_value(
+                               &rc,
+                               test_ctx,
+                               test_ctx->ldb,
+                               el->values[j],
+                               data);
+                       assert_int_equal(LDB_SUCCESS, rc);
+                       assert_memory_equal(
+                               secrets[i],
+                               dc.data,
+                               dc.length);
+                       TALLOC_FREE(dc.data);
+               }
+       }
+
+       /*
+        * Check that the normal attributes have not been encrypted
+        */
+       el = ldb_msg_find_element(encrypted_msg, "cmocka_test_name01");
+       assert_non_null(el);
+       assert_memory_equal(
+               "value01",
+               el->values[0].data,
+               el->values[0].length);
+
+       el = ldb_msg_find_element(encrypted_msg, "cmocka_test_name02");
+       assert_non_null(el);
+       assert_memory_equal(
+               "value02",
+               el->values[0].data,
+               el->values[0].length);
+
+       /*
+        * Now decrypt the message
+        */
+       ret = decrypt_secret_attributes(test_ctx->ldb,
+                                       discard_const(encrypted_msg),
+                                       data);
+       assert_int_equal(LDB_SUCCESS, ret);
+
+       /*
+        * Check that all the secret attributes have been decrypted
+        */
+       for (i=0; i < num_secrets; i++) {
+               el = ldb_msg_find_element(encrypted_msg, secrets[i]);
+               assert_non_null(el);
+               for (j = 0; j < el->num_values; j++) {
+                       assert_memory_equal(
+                               secrets[i],
+                               el->values[j].data,
+                               el->values[j].length);
+               }
+       }
+
+       /*
+        * Check that the normal attributes are intact
+        */
+       el = ldb_msg_find_element(msg, "cmocka_test_name01");
+       assert_non_null(el);
+       assert_memory_equal(
+               "value01",
+               el->values[0].data,
+               el->values[0].length);
+
+       el = ldb_msg_find_element(msg, "cmocka_test_name02");
+       assert_non_null(el);
+       assert_memory_equal(
+               "value02",
+               el->values[0].data,
+               el->values[0].length);
+
+}
+
+static void test_check_header(void **state)
+{
+       struct ldbtest_ctx *test_ctx =
+               talloc_get_type_abort(*state, struct ldbtest_ctx);
+
+       struct ldb_val enc = data_blob_null;
+       struct EncryptedSecret *es = NULL;
+       int rc;
+
+       /*
+        * Valid EncryptedSecret
+        */
+       es = makeEncryptedSecret(test_ctx->ldb, test_ctx);
+       rc = ndr_push_struct_blob(
+               &enc,
+               test_ctx,
+               es,
+               (ndr_push_flags_fn_t) ndr_push_EncryptedSecret);
+       assert_true(NDR_ERR_CODE_IS_SUCCESS(rc));
+       assert_true(check_header(es));
+       TALLOC_FREE(enc.data);
+       TALLOC_FREE(es);
+
+       /*
+        * invalid magic value
+        */
+       es = makeEncryptedSecret(test_ctx->ldb, test_ctx);
+       es->header.magic = 0xca5cadee;
+       rc = ndr_push_struct_blob(
+               &enc,
+               test_ctx,
+               es,
+               (ndr_push_flags_fn_t) ndr_push_EncryptedSecret);
+       assert_true(NDR_ERR_CODE_IS_SUCCESS(rc));
+       assert_false(check_header(es));
+       TALLOC_FREE(enc.data);
+       TALLOC_FREE(es);
+
+       /*
+        * invalid version
+        */
+       es = makeEncryptedSecret(test_ctx->ldb, test_ctx);
+       es->header.version = SECRET_ATTRIBUTE_VERSION + 1;
+       rc = ndr_push_struct_blob(
+               &enc,
+               test_ctx,
+               es,
+               (ndr_push_flags_fn_t) ndr_push_EncryptedSecret);
+       assert_true(NDR_ERR_CODE_IS_SUCCESS(rc));
+       assert_false(check_header(es));
+       TALLOC_FREE(enc.data);
+       TALLOC_FREE(es);
+
+       /*
+        * invalid algorithm
+        */
+       es = makeEncryptedSecret(test_ctx->ldb, test_ctx);
+       es->header.algorithm = SECRET_ENCRYPTION_ALGORITHM + 1;
+       rc = ndr_push_struct_blob(
+               &enc,
+               test_ctx,
+               es,
+               (ndr_push_flags_fn_t) ndr_push_EncryptedSecret);
+       assert_true(NDR_ERR_CODE_IS_SUCCESS(rc));
+       assert_false(check_header(es));
+       TALLOC_FREE(enc.data);
+       TALLOC_FREE(es);
+}
+
+/*
+ * Attempt to decrypt a message containing an unencrypted secret attribute
+ * this should fail
+ */
+static void test_unencrypted_secret(void **state)
+{
+       struct ldbtest_ctx *test_ctx =
+               talloc_get_type_abort(*state, struct ldbtest_ctx);
+       struct ldb_context *ldb = test_ctx->ldb;
+       struct ldb_message *msg = ldb_msg_new(ldb);
+       struct es_data *data = talloc_get_type(
+               ldb_module_get_private(test_ctx->module),
+               struct es_data);
+       int ret = LDB_SUCCESS;
+
+       msg->dn = ldb_dn_new(msg, ldb, "dc=test");
+       ldb_msg_add_string(msg, "unicodePwd", "value01");
+
+       ret = decrypt_secret_attributes(test_ctx->ldb, msg, data);
+       assert_int_equal(LDB_ERR_OPERATIONS_ERROR, ret);
+}
+
+
+int main(void) {
+       const struct CMUnitTest tests[] = {
+               cmocka_unit_test_setup_teardown(
+                       test_no_key_file,
+                       setup,
+                       teardown),
+               cmocka_unit_test_setup_teardown(
+                       test_key_file,
+                       setup,
+                       teardown),
+               cmocka_unit_test_setup_teardown(
+                       test_key_file_short_key,
+                       setup,
+                       teardown),
+               cmocka_unit_test_setup_teardown(
+                       test_key_file_long_key,
+                       setup,
+                       teardown),
+               cmocka_unit_test_setup_teardown(
+                       test_check_header,
+                       setup,
+                       teardown),
+#ifdef HAVE_GNUTLS_AEAD
+               cmocka_unit_test_setup_teardown(
+                       test_gnutls_value_encryption,
+                       setup_with_key,
+                       teardown),
+               cmocka_unit_test_setup_teardown(
+                       test_gnutls_altered_header,
+                       setup_with_key,
+                       teardown),
+               cmocka_unit_test_setup_teardown(
+                       test_gnutls_altered_data,
+                       setup_with_key,
+                       teardown),
+               cmocka_unit_test_setup_teardown(
+                       test_gnutls_altered_iv,
+                       setup_with_key,
+                       teardown),
+#endif /* HAVE_GNUTLS_AEAD */
+#ifdef HAVE_NETTLE_AES_GCM
+               cmocka_unit_test_setup_teardown(
+                       test_nettle_value_encryption,
+                       setup_with_key,
+                       teardown),
+               cmocka_unit_test_setup_teardown(
+                       test_nettle_altered_header,
+                       setup_with_key,
+                       teardown),
+               cmocka_unit_test_setup_teardown(
+                       test_nettle_altered_data,
+                       setup_with_key,
+                       teardown),
+               cmocka_unit_test_setup_teardown(
+                       test_nettle_altered_iv,
+                       setup_with_key,
+                       teardown),
+#endif /* HAVE_NETTLE_AES_GCM */
+               cmocka_unit_test_setup_teardown(
+                       test_message_encryption_decryption,
+                       setup_with_key,
+                       teardown),
+               cmocka_unit_test_setup_teardown(
+                       test_unencrypted_secret,
+                       setup_with_key,
+                       teardown),
+       };
+
+       cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+       return cmocka_run_group_tests(tests, NULL, NULL);
+}
index 47fde74a2ab2ec81664d97fb0f7cde4dc4683e8a..e2f2cda33127732f0130b88c4d6bb020de8238c4 100644 (file)
@@ -12,6 +12,38 @@ def set_options(opt):
     return
 
 def configure(conf):
+    conf.SET_TARGET_TYPE('nettle', 'EMPTY')
+    if conf.CHECK_CFG(
+        package="nettle",
+        args="--cflags --libs",
+        msg='Checking for nettle support'):
+
+        if conf.CHECK_FUNCS_IN(
+            'nettle_gcm_aes_encrypt',
+            'nettle',
+            headers='nettle/gcm.h'):
+
+            conf.DEFINE('HAVE_NETTLE_AES_GCM', '1')
+        else:
+            Logs.warn('No nettle support for AES GCM')
+    else:
+        Logs.warn('No nettle encryption libraries')
+
+    if conf.env.HAVE_GNUTLS:
+        if conf.CHECK_FUNCS_IN(
+            'gnutls_aead_cipher_init',
+            'gnutls',
+            headers='gnutls/gnutls.h'):
+
+                conf.DEFINE('HAVE_GNUTLS_AEAD', '1')
+        else:
+            Logs.warn('No gnutls support for AEAD encryption')
+
+    if not conf.env.HAVE_GNUTLS_AEAD and not conf.env.HAVE_NETTLE_AES_GCM:
+        conf.fatal("No AES GCM AEAD support"
+                   "Try installing gnutls if that does not support AEAD "
+                   "try installing nettle-dev or nettle-devel")
+
     conf.SET_TARGET_TYPE('gpgme', 'EMPTY')
 
     if Options.options.with_gpgme != False:
index b0afcf9d8269f9b18154d09af6696e1c4ce0f4ce..0ff94711763c32b2386a354e8267ca29ff4a300e 100644 (file)
@@ -28,6 +28,19 @@ bld.SAMBA_BINARY('test_unique_object_sids',
             DSDB_MODULE_HELPERS
         ''',
         install=False)
+bld.SAMBA_BINARY('test_encrypted_secrets',
+        source='tests/test_encrypted_secrets.c',
+        deps='''
+            talloc
+            samba-util
+            samdb-common
+            samdb
+            cmocka
+            nettle
+            gnutls
+            DSDB_MODULE_HELPERS
+        ''',
+        install=False)
 
 if bld.AD_DC_BUILD_IS_ENABLED():
     bld.PROCESS_SEPARATE_RULE("server")
index 4aac7f2f9f35a826496187a425386465dbbb0696..71af9b27278193b6d3cea2c97779cd379d836e1c 100644 (file)
@@ -409,3 +409,20 @@ bld.SAMBA_MODULE('ldb_unique_object_sids',
        deps='samdb-common DSDB_MODULE_HELPERS',
        subsystem='ldb'
        )
+
+bld.SAMBA_MODULE('ldb_encrypted_secrets',
+       source='encrypted_secrets.c',
+       subsystem='ldb',
+       init_function='ldb_encrypted_secrets_module_init',
+       module_init_name='ldb_init_module',
+       internal_module=False,
+       deps='''
+            talloc
+            samba-util
+            samdb-common
+            DSDB_MODULE_HELPERS
+            samdb
+            nettle
+            gnutls
+        '''
+       )
index 617edc9aa2bc8dea32a92ac910f0ed77f5bfb435..6a4820c3378aac55aa826e3ceb834e23d2977c29 100644 (file)
@@ -332,5 +332,6 @@ struct dsdb_extended_sec_desc_propagation_op {
 #define SAMBA_FEATURES_SUPPORTED_FLAG "@SAMBA_FEATURES_SUPPORTED"
 
 #define SAMBA_SORTED_LINKS_FEATURE "sortedLinks"
+#define SAMBA_ENCRYPTED_SECRETS_FEATURE "encryptedSecrets"
 
 #endif /* __SAMDB_H__ */
index 1c5714d8a7f376ae53236efdc612addfc993168f..0a0bc9319089139265f08551c42da7377a5ec888 100755 (executable)
@@ -1008,3 +1008,5 @@ for env in ["ad_dc_ntvfs", "ad_dc", "fl2000dc", "fl2003dc", "fl2008r2dc", 'vampi
 #
 plantestsuite("samba4.dsdb.samdb.ldb_modules.unique_object_sids" , "none",
               [os.path.join(bindir(), "test_unique_object_sids")])
+plantestsuite("samba4.dsdb.samdb.ldb_modules.encrypted_secrets", "none",
+                  [os.path.join(bindir(), "test_encrypted_secrets")])