diff options
-rw-r--r-- | Makefile.am | 19 | ||||
-rw-r--r-- | src/tests/crypto-tests.c | 139 | ||||
-rw-r--r-- | src/util/crypto/libcrypto/crypto_obfuscate.c | 16 | ||||
-rw-r--r-- | src/util/crypto/nss/nss_obfuscate.c | 480 | ||||
-rw-r--r-- | src/util/crypto/sss_crypto.h | 14 |
5 files changed, 667 insertions, 1 deletions
diff --git a/Makefile.am b/Makefile.am index b0e04b5d5..c06a36055 100644 --- a/Makefile.am +++ b/Makefile.am @@ -81,6 +81,7 @@ if HAVE_CHECK auth-tests \ ipa_ldap_opt-tests \ simple_access-tests \ + crypto-tests \ util-tests endif @@ -118,11 +119,13 @@ noinst_LTLIBRARIES = \ if HAVE_NSS SSS_CRYPT_SOURCES = src/util/crypto/nss/nss_sha512crypt.c \ + src/util/crypto/nss/nss_obfuscate.c \ src/util/crypto/nss/nss_util.c SSS_CRYPT_CFLAGS = $(NSS_CFLAGS) SSS_CRYPT_LIBS = $(NSS_LIBS) else - SSS_CRYPT_SOURCES = src/util/crypto/libcrypto/crypto_sha512crypt.c + SSS_CRYPT_SOURCES = src/util/crypto/libcrypto/crypto_sha512crypt.c \ + src/util/crypto/libcrypto/crypto_obfuscate.c SSS_CRYPT_CFLAGS = $(CRYPTO_CFLAGS) SSS_CRYPT_LIBS = $(CRYPTO_LIBS) endif @@ -678,6 +681,20 @@ util_tests_LDADD = \ $(CHECK_LIBS) \ libsss_test_common.la +crypto_tests_SOURCES = \ + $(SSSD_DEBUG_OBJ) \ + $(SSS_CRYPT_SOURCES) \ + src/tests/crypto-tests.c +crypto_tests_CFLAGS = \ + $(SSS_CRYPT_CFLAGS) \ + $(AM_CFLAGS) \ + $(CHECK_CFLAGS) +crypto_tests_LDADD = \ + $(SSS_CRYPT_LIBS) \ + $(SSSD_LIBS) \ + $(CHECK_LIBS) \ + libsss_test_common.la + endif stress_tests_SOURCES = \ diff --git a/src/tests/crypto-tests.c b/src/tests/crypto-tests.c new file mode 100644 index 000000000..d02a84371 --- /dev/null +++ b/src/tests/crypto-tests.c @@ -0,0 +1,139 @@ +/* + SSSD + + Crypto tests + + Author: Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) Red Hat, Inc 2010 + + 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 <stdlib.h> +#include <popt.h> +#include <check.h> + +#include "util/util.h" +#include "tests/common.h" + +/* interfaces under test */ +#include "util/crypto/sss_crypto.h" +#include "util/crypto/nss/nss_util.h" + +static TALLOC_CTX *test_ctx = NULL; + +#ifdef HAVE_NSS +START_TEST(test_nss_init) +{ + int ret; + + ret = nspr_nss_init(); + fail_if(ret != EOK); + + ret = nspr_nss_cleanup(); + fail_if(ret != EOK); +} +END_TEST +#endif + +START_TEST(test_encrypt_decrypt) +{ + const char *password[] = { "test123", /* general */ + "12345678901234567", /* just above blocksize */ + "", /* empty */ + NULL}; /* sentinel */ + int i; + char *obfpwd; + char *ctpwd; + int ret; + + test_ctx = talloc_new(NULL); + fail_if(test_ctx == NULL); + check_leaks_push(test_ctx); + + for (i=0; password[i]; i++) { + ret = sss_password_encrypt(test_ctx, password[i], strlen(password[i])+1, + AES_256, &obfpwd); + fail_if(ret != EOK); + + ret = sss_password_decrypt(test_ctx, obfpwd, &ctpwd); + fail_if(ret != EOK); + + fail_if(strcmp(password[i], ctpwd) != 0); + + talloc_free(obfpwd); + talloc_free(ctpwd); + } + + check_leaks_pop(test_ctx); + talloc_free(test_ctx); +} +END_TEST + +Suite *crypto_suite(void) +{ + Suite *s = suite_create("sss_crypto"); + + TCase *tc = tcase_create("sss crypto tests"); + tcase_add_checked_fixture(tc, leak_check_setup, leak_check_teardown); + /* Do some testing */ +#ifdef HAVE_NSS + tcase_add_test(tc, test_nss_init); +#endif + tcase_add_test(tc, test_encrypt_decrypt); + /* Add all test cases to the test suite */ + suite_add_tcase(s, tc); + + return s; +} + + +int main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + int number_failed; + int debug; + + struct poptOption long_options[] = { + POPT_AUTOHELP + { "debug-level", 'd', POPT_ARG_INT, &debug, 0, "Set debug level", NULL }, + POPT_TABLEEND + }; + + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + poptFreeContext(pc); + + debug_level = debug; + tests_set_cwd(); + + Suite *s = crypto_suite(); + SRunner *sr = srunner_create(s); + srunner_run_all(sr, CK_ENV); + number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + + return (number_failed == 0 ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/src/util/crypto/libcrypto/crypto_obfuscate.c b/src/util/crypto/libcrypto/crypto_obfuscate.c new file mode 100644 index 000000000..026e993c6 --- /dev/null +++ b/src/util/crypto/libcrypto/crypto_obfuscate.c @@ -0,0 +1,16 @@ +#include <talloc.h> +#include <errno.h> + +#include "util/crypto/sss_crypto.h" + +int sss_password_encrypt(TALLOC_CTX *mem_ctx, const char *password, int plen, + enum obfmethod meth, char **obfpwd) +{ + return ENOSYS; +} + +int sss_password_decrypt(TALLOC_CTX *mem_ctx, char *b64encoded, + char **password) +{ + return ENOSYS; +} diff --git a/src/util/crypto/nss/nss_obfuscate.c b/src/util/crypto/nss/nss_obfuscate.c new file mode 100644 index 000000000..69dbbf37d --- /dev/null +++ b/src/util/crypto/nss/nss_obfuscate.c @@ -0,0 +1,480 @@ +/* + SSSD + + Password obfuscation logic + + Author: Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) Red Hat, Inc 2010 + + 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 "config.h" + +#include <prerror.h> +#include <nss.h> +#include <pk11func.h> +#include <base64.h> +#include <talloc.h> + +#include "util/util.h" +#include "util/crypto/sss_crypto.h" +#include "util/crypto/nss/nss_util.h" + +#define OBF_BUFFER_SENTINEL "\0\1\2\3" +#define OBF_BUFFER_SENTINEL_SIZE 4 + +#define MAKE_SECITEM(sdata, slen, sitem) do { \ + (sitem)->type = (siBuffer); \ + (sitem)->data = (sdata); \ + (sitem)->len = (slen); \ +} while(0); + +struct sss_nss_crypto_ctx { + PK11SlotInfo *slot; + PK11Context *ectx; + PK11SymKey *keyobj; + SECItem *sparam; + + SECItem *iv; + SECItem *key; +}; + +struct crypto_mech_data { + CK_MECHANISM_TYPE cipher; + uint16_t keylen; + uint16_t bsize; +}; + +static struct crypto_mech_data cmdata[] = { + /* AES with automatic padding, 256b key, 128b block */ + { CKM_AES_CBC_PAD, 32, 16 }, + /* sentinel */ + { 0, 0, 0 } +}; + +static struct crypto_mech_data *get_crypto_mech_data(enum obfmethod meth) +{ + if (meth >= NUM_OBFMETHODS) { + DEBUG(1, ("Unsupported cipher type\n")); + return NULL; + } + return &cmdata[meth]; +} + +static int generate_random_key(TALLOC_CTX *mem_ctx, size_t keylen, + unsigned char **_key) +{ + unsigned char *randkey; + SECStatus sret; + int ret; + + randkey = talloc_size(mem_ctx, keylen); + if (randkey == NULL) { + ret = ENOMEM; + goto done; + } + + sret = PK11_GenerateRandom(randkey, keylen); + if (sret != SECSuccess) { + DEBUG(1, ("Unable to generate random data (err %d)\n", + PR_GetError())); + ret = EIO; + goto done; + } + + *_key = randkey; + ret = EOK; +done: + if (ret != EOK) talloc_zfree(randkey); + return ret; +} + +static int sss_nss_crypto_ctx_destructor(struct sss_nss_crypto_ctx *cctx) +{ + if (cctx->ectx) PK11_DestroyContext(cctx->ectx, PR_TRUE); + if (cctx->sparam) SECITEM_FreeItem(cctx->sparam, PR_TRUE); + if (cctx->slot) PK11_FreeSlot(cctx->slot); + if (cctx->keyobj) PK11_FreeSymKey(cctx->keyobj); + + return EOK; +} + +static int nss_encrypt_decrypt_init(TALLOC_CTX *mem_ctx, + struct crypto_mech_data *mech_props, + bool encrypt, + unsigned char *ivbuf, + unsigned char *keybuf, + struct sss_nss_crypto_ctx **_cctx) +{ + struct sss_nss_crypto_ctx *cctx; + CK_ATTRIBUTE_TYPE op; + int ret; + + cctx = talloc_zero(mem_ctx, struct sss_nss_crypto_ctx); + if (!cctx) { + return ENOMEM; + } + talloc_set_destructor(cctx, sss_nss_crypto_ctx_destructor); + + cctx->iv = talloc_zero(cctx, SECItem); + cctx->key = talloc_zero(cctx, SECItem); + if (!cctx->iv || !cctx->key) { + ret = ENOMEM; + goto done; + } + + MAKE_SECITEM(ivbuf, mech_props->bsize, cctx->iv); + MAKE_SECITEM(keybuf, mech_props->keylen, cctx->key); + + op = encrypt ? CKA_ENCRYPT : CKA_DECRYPT; + + cctx->slot = PK11_GetBestSlot(mech_props->cipher, NULL); + if (cctx->slot == NULL) { + DEBUG(1, ("Unable to find security device (err %d)\n", + PR_GetError())); + ret = EIO; + goto done; + } + + /* turn the raw key into a key object */ + cctx->keyobj = PK11_ImportSymKey(cctx->slot, mech_props->cipher, + PK11_OriginUnwrap, op, cctx->key, NULL); + if (cctx->keyobj == NULL) { + DEBUG(1, ("Failure to import key into NSS (err %d)\n", + PR_GetError())); + ret = EIO; + goto done; + } + + /* turn the raw IV into a initialization vector object */ + cctx->sparam = PK11_ParamFromIV(mech_props->cipher, cctx->iv); + if (cctx->sparam == NULL) { + DEBUG(1, ("Failure to set up PKCS11 param (err %d)\n", + PR_GetError())); + ret = EIO; + goto done; + } + + /* Create cipher context */ + cctx->ectx = PK11_CreateContextBySymKey(mech_props->cipher, op, + cctx->keyobj, cctx->sparam); + if (cctx->ectx == NULL) { + DEBUG(1, ("Cannot create cipher context (err %d)\n", + PORT_GetError())); + ret = EIO; + goto done; + } + + ret = EOK; + *_cctx = cctx; +done: + if (ret) talloc_zfree(cctx); + return ret; +} + +/* NSS wraps b64 encoded buffers with CRLF automatically after 64 chars. This + * function strips the CRLF double-chars. The buffer can be decoded with plain + * NSS calls */ +static char *b64_encode(TALLOC_CTX *mem_ctx, + unsigned char *inbuf, + size_t inbufsize) +{ + char *b64encoded = NULL; + int i, j, b64size; + char *outbuf; + + b64encoded = BTOA_DataToAscii(inbuf, inbufsize); + if (!b64encoded) return NULL; + + b64size = strlen(b64encoded) + 1; + outbuf = talloc_array(mem_ctx, char, b64size); + if (outbuf == NULL) { + PORT_Free(b64encoded); + return NULL; + } + + for (i=0, j=0; i < b64size; i++) { + if (b64encoded[i] == '\n' || b64encoded[i] == '\r') { + continue; + } + outbuf[j++] = b64encoded[i]; /* will also copy the trailing \0 char */ + } + + PORT_Free(b64encoded); + return outbuf; +} + +int sss_password_encrypt(TALLOC_CTX *mem_ctx, const char *password, int plen, + enum obfmethod meth, char **obfpwd) +{ + SECStatus sret; + int ret; + TALLOC_CTX *tmp_ctx = NULL; + struct crypto_mech_data *mech_props; + struct sss_nss_crypto_ctx *cctx; + + unsigned char *keybuf; + unsigned char *ivbuf; + unsigned char *plaintext; + + unsigned char *cryptotext; + int ct_maxsize; + int ctlen; + unsigned int digestlen; + int result_len; + + unsigned char *obfbuf; + size_t obufsize = 0; + size_t p = 0; + + tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) { + return ENOMEM; + } + + /* initialize NSS if needed */ + ret = nspr_nss_init(); + if (ret != EOK) { + ret = EIO; + goto done; + } + + mech_props = get_crypto_mech_data(meth); + if (mech_props == NULL) { + ret = EINVAL; + goto done; + } + + /* generate random encryption and IV key */ + ret = generate_random_key(tmp_ctx, mech_props->keylen, &keybuf); + if (ret != EOK) { + DEBUG(1, ("Could not generate encryption key\n")); + goto done; + } + + ret = generate_random_key(tmp_ctx, mech_props->bsize, &ivbuf); + if (ret != EOK) { + DEBUG(1, ("Could not generate initialization vector\n")); + goto done; + } + + ret = nss_encrypt_decrypt_init(tmp_ctx, mech_props, true, + keybuf, ivbuf, &cctx); + if (ret) { + goto done; + } + + plaintext = (unsigned char *) talloc_strndup(tmp_ctx, password, plen); + if (!plaintext) { + ret = ENOMEM; + goto done; + } + + /* cryptotext buffer must be at least len(plaintext)+blocksize */ + ct_maxsize = plen + (mech_props->bsize); + cryptotext = talloc_array(tmp_ctx, unsigned char, ct_maxsize); + if (!cryptotext) { + ret = ENOMEM; + goto done; + } + + /* sample data we'll encrypt and decrypt */ + sret = PK11_CipherOp(cctx->ectx, cryptotext, &ctlen, ct_maxsize, + plaintext, plen); + if (sret != SECSuccess) { + DEBUG(1, ("Cannot execute the encryption operation (err %d)\n", + PR_GetError())); + ret = EIO; + goto done; + } + + sret = PK11_DigestFinal(cctx->ectx, cryptotext+ctlen, &digestlen, + ct_maxsize-ctlen); + if (sret != SECSuccess) { + DEBUG(1, ("Cannot execute the digest operation (err %d)\n", + PR_GetError())); + ret = EIO; + goto done; + } + result_len = ctlen + digestlen; + + /* Pack the obfuscation buffer */ + /* The buffer consists of: + * uint16_t the type of the cipher + * uint32_t length of the cryptotext in bytes (clen) + * uint8_t[klen] key + * uint8_t[blen] IV + * uint8_t[clen] cryptotext + * 4 bytes of "sentinel" denoting end of the buffer + */ + obufsize = sizeof(uint16_t) + sizeof(uint32_t) + + mech_props->keylen + mech_props->bsize + + result_len + OBF_BUFFER_SENTINEL_SIZE; + obfbuf = talloc_array(tmp_ctx, unsigned char, obufsize); + if (!obfbuf) { + ret = ENOMEM; + goto done; + } + + DEBUG(8, ("Writing method: %d\n", meth)); + SAFEALIGN_SET_UINT16(&obfbuf[p], meth, &p); + DEBUG(8, ("Writing bufsize: %d\n", result_len)); + SAFEALIGN_SET_UINT16(&obfbuf[p], result_len, &p); + safealign_memcpy(&obfbuf[p], cctx->key->data, mech_props->keylen, &p); + safealign_memcpy(&obfbuf[p], cctx->iv->data, mech_props->bsize, &p); + safealign_memcpy(&obfbuf[p], cryptotext, result_len, &p); + safealign_memcpy(&obfbuf[p], OBF_BUFFER_SENTINEL, + OBF_BUFFER_SENTINEL_SIZE, &p); + + /* Base64 encode the resulting buffer */ + *obfpwd = b64_encode(mem_ctx, obfbuf, obufsize); + if (*obfpwd == NULL) { + ret = ENOMEM; + goto done; + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + nspr_nss_cleanup(); + return ret; +} + +int sss_password_decrypt(TALLOC_CTX *mem_ctx, char *b64encoded, + char **password) +{ + SECStatus sret; + int ret; + TALLOC_CTX *tmp_ctx = NULL; + struct crypto_mech_data *mech_props; + struct sss_nss_crypto_ctx *cctx; + + int plainlen; + unsigned int digestlen; + unsigned char *obfbuf = NULL; + unsigned int obflen; + char *pwdbuf; + + /* for unmarshaling data */ + uint16_t meth; + uint16_t ctsize; + size_t p = 0; + unsigned char *cryptotext; + unsigned char *keybuf; + unsigned char *ivbuf; + unsigned char sentinel_check[OBF_BUFFER_SENTINEL_SIZE]; + + tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) { + return ENOMEM; + } + + /* initialize NSS if needed */ + ret = nspr_nss_init(); + if (ret != EOK) { + ret = EIO; + goto done; + } + + /* Base64 decode the incoming buffer */ + obfbuf = ATOB_AsciiToData(b64encoded, &obflen); + if (!obfbuf) { + ret = ENOMEM; + goto done; + } + + /* unpack obfuscation buffer */ + SAFEALIGN_COPY_UINT16_CHECK(&meth, obfbuf+p, obflen, &p); + DEBUG(8, ("Read method: %d\n", meth)); + SAFEALIGN_COPY_UINT16_CHECK(&ctsize, obfbuf+p, obflen, &p); + DEBUG(8, ("Read bufsize: %d\n", ctsize)); + + mech_props = get_crypto_mech_data(meth); + if (mech_props == NULL) { + ret = EINVAL; + goto done; + } + + /* check that we got sane mechanism properties and cryptotext size */ + memcpy(sentinel_check, + obfbuf + p + mech_props->keylen + mech_props->bsize + ctsize, + OBF_BUFFER_SENTINEL_SIZE); + if (memcmp(sentinel_check, OBF_BUFFER_SENTINEL, OBF_BUFFER_SENTINEL_SIZE) != 0) { + DEBUG(0, ("Obfuscation buffer seems corrupt, aborting\n")); + ret = EFAULT; + goto done; + } + + /* copy out key, ivbuf and cryptotext */ + keybuf = talloc_array(tmp_ctx, unsigned char, mech_props->keylen); + if (keybuf == NULL) { + ret = ENOMEM; + goto done; + } + safealign_memcpy(keybuf, obfbuf+p, mech_props->keylen, &p); + + ivbuf = talloc_array(tmp_ctx, unsigned char, mech_props->bsize); + if (ivbuf == NULL) { + ret = ENOMEM; + goto done; + } + safealign_memcpy(ivbuf, obfbuf+p, mech_props->bsize, &p); + + cryptotext = talloc_array(tmp_ctx, unsigned char, ctsize); + if (cryptotext == NULL) { + ret = ENOMEM; + goto done; + } + safealign_memcpy(cryptotext, obfbuf+p, ctsize, &p); + + ret = nss_encrypt_decrypt_init(tmp_ctx, mech_props, false, + ivbuf, keybuf, &cctx); + if (ret) { + goto done; + } + + pwdbuf = talloc_array(tmp_ctx, char, ctsize); + if (!pwdbuf) { + ret = ENOMEM; + goto done; + } + + sret = PK11_CipherOp(cctx->ectx, (unsigned char *) pwdbuf, &plainlen, ctsize, + cryptotext, ctsize); + if (sret != SECSuccess) { + DEBUG(1, ("Cannot execute the encryption operation (err %d)\n", + PR_GetError())); + ret = EIO; + goto done; + } + + sret = PK11_DigestFinal(cctx->ectx, (unsigned char *) pwdbuf+plainlen, &digestlen, + ctsize - plainlen); + if (sret != SECSuccess) { + DEBUG(1, ("Cannot execute the encryption operation (err %d)\n", + PR_GetError())); + ret = EIO; + goto done; + } + + *password = talloc_move(mem_ctx, &pwdbuf); + ret = EOK; +done: + PORT_Free(obfbuf); + talloc_free(tmp_ctx); + nspr_nss_cleanup(); + return ret; +} diff --git a/src/util/crypto/sss_crypto.h b/src/util/crypto/sss_crypto.h index 5512c5d96..66394aeb4 100644 --- a/src/util/crypto/sss_crypto.h +++ b/src/util/crypto/sss_crypto.h @@ -2,3 +2,17 @@ int s3crypt_sha512(TALLOC_CTX *mmectx, const char *key, const char *salt, char **_hash); int s3crypt_gen_salt(TALLOC_CTX *memctx, char **_salt); + +/* Methods of obfuscation. */ +enum obfmethod { + AES_256, + NUM_OBFMETHODS +}; + +int test2(void); + +int sss_password_encrypt(TALLOC_CTX *mem_ctx, const char *password, int plen, + enum obfmethod meth, char **obfpwd); + +int sss_password_decrypt(TALLOC_CTX *mem_ctx, char *b64encoded, + char **password); |