summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--librpc/idl/drsblobs.idl30
-rw-r--r--source4/dsdb/samdb/ldb_modules/encrypted_secrets.c1755
-rw-r--r--source4/dsdb/samdb/ldb_modules/tests/test_encrypted_secrets.c1186
-rw-r--r--source4/dsdb/samdb/ldb_modules/wscript32
-rw-r--r--source4/dsdb/samdb/ldb_modules/wscript_build13
-rw-r--r--source4/dsdb/samdb/ldb_modules/wscript_build_server17
-rw-r--r--source4/dsdb/samdb/samdb.h1
-rwxr-xr-xsource4/selftest/tests.py2
8 files changed, 3036 insertions, 0 deletions
diff --git a/librpc/idl/drsblobs.idl b/librpc/idl/drsblobs.idl
index 9fca2cb8b1f..5fd11bbc880 100644
--- a/librpc/idl/drsblobs.idl
+++ b/librpc/idl/drsblobs.idl
@@ -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
index 00000000000..bc03fee3e21
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/encrypted_secrets.c
@@ -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
index 00000000000..7c1363156c4
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/tests/test_encrypted_secrets.c
@@ -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);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/wscript b/source4/dsdb/samdb/ldb_modules/wscript
index 47fde74a2ab..e2f2cda3312 100644
--- a/source4/dsdb/samdb/ldb_modules/wscript
+++ b/source4/dsdb/samdb/ldb_modules/wscript
@@ -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:
diff --git a/source4/dsdb/samdb/ldb_modules/wscript_build b/source4/dsdb/samdb/ldb_modules/wscript_build
index b0afcf9d826..0ff94711763 100644
--- a/source4/dsdb/samdb/ldb_modules/wscript_build
+++ b/source4/dsdb/samdb/ldb_modules/wscript_build
@@ -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")
diff --git a/source4/dsdb/samdb/ldb_modules/wscript_build_server b/source4/dsdb/samdb/ldb_modules/wscript_build_server
index 4aac7f2f9f3..71af9b27278 100644
--- a/source4/dsdb/samdb/ldb_modules/wscript_build_server
+++ b/source4/dsdb/samdb/ldb_modules/wscript_build_server
@@ -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
+ '''
+ )
diff --git a/source4/dsdb/samdb/samdb.h b/source4/dsdb/samdb/samdb.h
index 617edc9aa2b..6a4820c3378 100644
--- a/source4/dsdb/samdb/samdb.h
+++ b/source4/dsdb/samdb/samdb.h
@@ -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__ */
diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py
index 1c5714d8a7f..0a0bc931908 100755
--- a/source4/selftest/tests.py
+++ b/source4/selftest/tests.py
@@ -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")])