Wed, 04 Dec 2019 11:47:51 +0100
replace openssl on windows with cng/bcrypt
libidav/crypto.c | file | annotate | diff | comparison | revisions | |
libidav/crypto.h | file | annotate | diff | comparison | revisions | |
mingw.mk | file | annotate | diff | comparison | revisions | |
test/crypto.c | file | annotate | diff | comparison | revisions |
--- a/libidav/crypto.c Mon Dec 02 13:29:36 2019 +0100 +++ b/libidav/crypto.c Wed Dec 04 11:47:51 2019 +0100 @@ -450,7 +450,7 @@ /* -------------------- Apple Crypto Functions -------------------- */ -#ifdef __APPLE__ +#ifdef DAV_CRYPTO_COMMON_CRYPTO #define RANDOM_BUFFER_LENGTH 256 static char randbuf[RANDOM_BUFFER_LENGTH]; @@ -891,6 +891,574 @@ #endif +/* -------------------- Windows Crypto Functions -------------------- */ +#ifdef DAV_CRYPTO_CNG + +static void cng_cleanup(BCRYPT_ALG_HANDLE hAesAlg, BCRYPT_KEY_HANDLE hKey, BCRYPT_HASH_HANDLE hHash, void *pbObject) { + if(hAesAlg) { + BCryptCloseAlgorithmProvider(hAesAlg,0); + } + if(hKey) { + BCryptDestroyKey(hKey); + } + if(hHash) { + BCryptDestroyHash(hHash); + } + if(pbObject) { + free(pbObject); + } +} + +static int cng_init_key(BCRYPT_ALG_HANDLE *alg, BCRYPT_KEY_HANDLE *key, void **keyobj, DavKey *aesKey) { + BCRYPT_ALG_HANDLE hAesAlg = NULL; + BCRYPT_KEY_HANDLE hKey = NULL; + + void *pbKeyObject = NULL; + ULONG keyObjectLength = 0; + + ULONG result = 0; + + // check DavKey and get AES key length + if(!aesKey) { + return 1; + } + + ULONG aesKeyLength = 0; + if(aesKey->type == DAV_KEY_AES128) { + aesKeyLength = 16; + } else if(aesKey->type == DAV_KEY_AES256) { + aesKeyLength = 32; + } + if(aesKeyLength > aesKey->length || !aesKey->data) { + // invalid DavKey + return 1; + } + + // initialize BCrypt stuff + if(BCryptOpenAlgorithmProvider(&hAesAlg, BCRYPT_AES_ALGORITHM, NULL, 0)) { + fprintf(stderr, "Error: BCryptOpenAlgorithmProvider failed\n"); + return 1; + } + + if(BCryptGetProperty(hAesAlg, BCRYPT_OBJECT_LENGTH, (PUCHAR)&keyObjectLength, sizeof(DWORD), &result, 0)) { + fprintf(stderr, "Error: BCrypt: Cannot get BCRYPT_OBJECT_LENGTH\n"); + cng_cleanup(hAesAlg, hKey, NULL, pbKeyObject); + return 1; + } + + if(BCryptSetProperty(hAesAlg, BCRYPT_CHAINING_MODE, (PBYTE)BCRYPT_CHAIN_MODE_CBC, sizeof(BCRYPT_CHAIN_MODE_CBC), 0)) { + fprintf(stderr, "Error: BCrypt: Cannot set CBC mode\n"); + cng_cleanup(hAesAlg, hKey, NULL, pbKeyObject); + return 1; + } + + pbKeyObject = calloc(1, keyObjectLength); + if(!pbKeyObject) { + cng_cleanup(hAesAlg, hKey, NULL, pbKeyObject); + return 1; + } + + // init key + if(BCryptGenerateSymmetricKey(hAesAlg, &hKey, pbKeyObject, keyObjectLength, aesKey->data, aesKeyLength, 0)) { + fprintf(stderr, "Error: BCrypt: Cannot set key\n"); + cng_cleanup(hAesAlg, hKey, NULL, pbKeyObject); + return 1; + } + + *alg = hAesAlg; + *key = hKey; + *keyobj = pbKeyObject; + + return 0; +} + +static int cng_hash_init(WinBCryptSHACTX *ctx) { + if(BCryptOpenAlgorithmProvider(&ctx->hAlg, BCRYPT_SHA256_ALGORITHM, NULL, 0)) { + fprintf(stderr, "Error: BCryptOpenAlgorithmProvider failed\n"); + return 1; + } + + ULONG hashObjectLen; + ULONG result; + if(BCryptGetProperty(ctx->hAlg, BCRYPT_OBJECT_LENGTH, (PBYTE)&hashObjectLen, sizeof(DWORD), &result, 0)) { + cng_cleanup(ctx->hAlg, NULL, NULL, NULL); + return 1; + } + + ctx->pbHashObject = calloc(1, hashObjectLen); + + if(BCryptCreateHash(ctx->hAlg, &ctx->hHash, ctx->pbHashObject, hashObjectLen, NULL, 0, 0)) { + cng_cleanup(ctx->hAlg, NULL, ctx->hHash, ctx->pbHashObject); + return 1; + } + + return 0; +} + + +int dav_rand_bytes(unsigned char *buf, size_t len) { + if(BCryptGenRandom(NULL, (unsigned char*)buf, (ULONG)len, BCRYPT_USE_SYSTEM_PREFERRED_RNG)) { + return 1; + } + return 0; +} + +AESDecrypter* aes_decrypter_new(DavKey *key, void *stream, dav_write_func write_func) { + AESDecrypter *dec = calloc(1, sizeof(AESDecrypter)); + if(!dec) { + return NULL; + } + if(cng_hash_init(&dec->sha256)) { + free(dec); + return NULL; + } + + dec->stream = stream; + dec->write = write_func; + dec->key = key; + dec->init = 0; + dec->ivpos = 0; + + return dec; +} + +static void aes_decrypter_init(AESDecrypter *dec) { + if(cng_init_key(&dec->ctx.hAlg, &dec->ctx.hKey, &dec->ctx.pbKeyObject, dec->key)) { + fprintf(stderr, "Error: cng_init_key failed\n"); + exit(-1); + } + // copy iv + memcpy(dec->ctx.pbIV, dec->ivtmp, 16); +} + +size_t aes_write(const void *buf, size_t s, size_t n, AESDecrypter *dec) { + int len = s*n; + if(!dec->init) { + size_t n = 16 - dec->ivpos; + size_t cp = n > len ? len : n; + memcpy(dec->ivtmp + dec->ivpos, buf, cp); + dec->ivpos += cp; + if(dec->ivpos >= 16) { + aes_decrypter_init(dec); + } + if(len == cp) { + return len; + } else { + buf = (char*)buf + cp; + len -= cp; + } + } + + // the cipher text must be a multiply of 16 + // remaining bytes are stored in ctx.buf and must be added to cibuf + // the next time + size_t cbufalloc = len + 32; + ULONG clen = 0; + char *cbuf = malloc(cbufalloc); + + // add previous remaining bytes + if(dec->ctx.buflen > 0) { + memcpy(cbuf, dec->ctx.buf, dec->ctx.buflen); + clen = dec->ctx.buflen; + } + // add current bytes + memcpy(cbuf + clen, buf, len); + clen += len; + + // check if the message fits the blocksize + int remaining = clen % 16; + if(remaining > 0) { + // add remaining bytes to ctx.buf for the next aes_write run + clen -= remaining; + memcpy(dec->ctx.buf, cbuf + clen - remaining, remaining); + } + dec->ctx.buflen = remaining; + + // ready to decrypt the message + ULONG outlen = clen + 32; + unsigned char *out = malloc(outlen); + + // decrypt + if(clen > 0) { + if(BCryptDecrypt(dec->ctx.hKey, cbuf, clen, NULL, dec->ctx.pbIV, 16, out, outlen, &outlen, BCRYPT_BLOCK_PADDING)) { + fprintf(stderr, "Error: BCryptDecrypt failed\n"); + free(out); + free(cbuf); + return 0; + } + } + + // write decrypted data to the output stream and update the hash + dec->write(out, 1, outlen, dec->stream); + BCryptHashData(dec->sha256.hHash, out, outlen, 0); + free(out); + free(cbuf); + + return (s*n) / s; +} + +void aes_decrypter_shutdown(AESDecrypter *dec) { + if(dec->init && dec->ctx.buflen > 0) { + ULONG outlen = 64; + char out[64]; + if(BCryptDecrypt(dec->ctx.hKey, dec->ctx.buf, dec->ctx.buflen, NULL, dec->ctx.pbIV, 16, out, outlen, &outlen, BCRYPT_BLOCK_PADDING)) { + fprintf(stderr, "Error: BCryptDecrypt failed\n"); + return; + } + dec->write(out, 1, outlen, dec->stream); + BCryptHashData(dec->sha256.hHash, out, outlen, 0); + } +} + +void aes_decrypter_close(AESDecrypter *dec) { + cng_cleanup(dec->ctx.hAlg, dec->ctx.hKey, NULL, dec->ctx.pbKeyObject); + cng_cleanup(dec->sha256.hAlg, NULL, dec->sha256.hHash, dec->sha256.pbHashObject); + free(dec); +} + +AESEncrypter* aes_encrypter_new(DavKey *key, void *stream, dav_read_func read_func, dav_seek_func seek_func) { + unsigned char *iv = malloc(16); + if(dav_rand_bytes(iv, 16)) { + free(iv); + return NULL; + } + + AESEncrypter *enc = calloc(1, sizeof(AESEncrypter)); + if(cng_hash_init(&enc->sha256)) { + free(iv); + free(enc); + return NULL; + } + + enc->stream = stream; + enc->read = read_func; + enc->seek = seek_func; + enc->tmp = NULL; + enc->tmplen = 0; + enc->tmpoff = 0; + enc->end = 0; + enc->iv = iv; + enc->ivlen = 0; + + if(cng_init_key(&enc->ctx.hAlg, &enc->ctx.hKey, &enc->ctx.pbKeyObject, key)) { + fprintf(stderr, "Error: cng_init_key failed\n"); + exit(-1); + } + + enc->ctx.buflen = 0; + memcpy(enc->ctx.pbIV, iv, 16); + + return enc; +} + +size_t aes_read(void *buf, size_t s, size_t n, AESEncrypter *enc) { + size_t len = s*n; + size_t nread = 0; + + if(enc->tmp) { + // the temp buffer contains bytes that are already encrypted, but + // the last aes_read had not enough read buffer space + + // in case we have a tmp buf, we just return this + size_t tmp_diff = enc->tmplen - enc->tmpoff; + size_t cp_len = tmp_diff > len ? len : tmp_diff; + memcpy(buf, enc->tmp + enc->tmpoff, cp_len); + enc->tmpoff += cp_len; + if(enc->tmpoff >= enc->tmplen) { + free(enc->tmp); + enc->tmp = NULL; + enc->tmplen = 0; + enc->tmpoff = 0; + } + return cp_len / s; + } + + if(enc->ivlen < 16) { + size_t copy_iv_len = 16 - enc->ivlen; + copy_iv_len = len > copy_iv_len ? copy_iv_len : len; + + memcpy(buf, enc->iv, copy_iv_len); + buf += copy_iv_len; + len -= copy_iv_len; + nread = copy_iv_len; + + enc->ivlen += copy_iv_len; + + if(len == 0) { + return copy_iv_len / s; + } + } + + if(enc->end) { + return 0; + } + + size_t remaining = len % 16; + len -= remaining; + + size_t inalloc = len; + ULONG inlen = 0; + unsigned char *in = malloc(inalloc); + + // fill the input buffer + while(inlen < inalloc) { + size_t r = enc->read(in + inlen, 1, inalloc - inlen, enc->stream); + if(r == 0) { + enc->end = 1; + break; + } + inlen += r; + } + + if(inlen == 0) { + return nread / s; + } + + // hash read data + BCryptHashData(enc->sha256.hHash, in, inlen, 0); + + // create output buffer + ULONG outalloc = inlen + 16; + ULONG outlen = 0; + char *out = malloc(outalloc); + + // encrypt + if(BCryptEncrypt(enc->ctx.hKey, in, inlen, NULL, enc->ctx.pbIV, 16, out, outalloc, &outlen, BCRYPT_BLOCK_PADDING)) { + fprintf(stderr, "Error: BCryptEncrypt failed\n"); + } + + // check if the output fits in buf, if not, save the remaining bytes in tmp + if(outlen > len) { + size_t tmplen = outlen - len; + char *tmp = malloc(tmplen); + memcpy(tmp, out+len, tmplen); + + enc->tmp = tmp; + enc->tmplen = tmplen; + enc->tmpoff = 0; + + outlen = len; + } + + // fill read buffer and return + memcpy(buf, out, outlen); + nread += outlen; + + free(in); + free(out); + + return nread / s; +} + +void aes_encrypter_close(AESEncrypter *enc) { + enc->end = 1; +} + +int aes_encrypter_reset(AESEncrypter *enc, curl_off_t offset, int origin) { + if(origin != SEEK_SET || offset != 0 || !enc->seek) { + return CURL_SEEKFUNC_CANTSEEK; + } + + enc->ivlen = 0; + memcpy(enc->ctx.pbIV, enc->iv, 16); + if(enc->seek(enc->stream, 0, SEEK_SET) != 0) { + return CURL_SEEKFUNC_FAIL; + } + return CURL_SEEKFUNC_OK; +} + +char* aes_encrypt(const char *in, size_t len, DavKey *key) { + // create random IV + char iv[16]; + if(dav_rand_bytes(iv, 16)) { + return NULL; + } + + // initialize bcrypt stuff + BCRYPT_ALG_HANDLE hAlg = NULL; + BCRYPT_KEY_HANDLE hKey = NULL; + void *pbKeyObject = NULL; + if(cng_init_key(&hAlg, &hKey, &pbKeyObject, key)) { + return NULL; + } + + // create output buffer + ULONG outlen = len + 128; + char *out = malloc(outlen); + + // the output must start with the IV + memcpy(out, iv, 16); + char *encbuf = out + 16; + ULONG enclen = outlen - 16; + ULONG encoutlen = 0; + + // encrypt + if(BCryptEncrypt(hKey, (PUCHAR)in, len, NULL, (PUCHAR)iv, 16, encbuf, enclen, &encoutlen, BCRYPT_BLOCK_PADDING)) { + fprintf(stderr, "Error: BCryptEncrypt failed\n"); + cng_cleanup(hAlg, hKey, NULL, pbKeyObject); + free(out); + return NULL; + } + + outlen = encoutlen + 16; // length of encrypted data + 16 bytes IV + + // base64 encode + char *outstr = util_base64encode(out, outlen); + + cng_cleanup(hAlg, hKey, NULL, pbKeyObject); + free(out); + + return outstr; +} + +char* aes_decrypt(const char *in, size_t *len, DavKey *key) { + BCRYPT_ALG_HANDLE hAlg = NULL; + BCRYPT_KEY_HANDLE hKey = NULL; + void *pbKeyObject = NULL; + if(cng_init_key(&hAlg, &hKey, &pbKeyObject, key)) { + return NULL; + } + + int inlen; + unsigned char *buf = (unsigned char*)util_base64decode_len(in, &inlen); + if(inlen < 16 || !buf) { + cng_cleanup(hAlg, hKey, NULL, pbKeyObject); + if(buf) { + free(buf); + } + return NULL; + } + + // encrypted data starts with IV + char iv[16]; + memcpy(iv, buf, 16); + + // decrypt data + char *data = buf + 16; // encrypted data starts after IV + size_t datalen = inlen - 16; + + // create output buffer + ULONG outlen = inlen; + char *out = malloc(outlen + 1); + + // decrypt + if(BCryptDecrypt(hKey, data, datalen, NULL, iv, 16, out, outlen, &outlen, BCRYPT_BLOCK_PADDING)) { + cng_cleanup(hAlg, hKey, NULL, pbKeyObject); + free(out); + free(buf); + return NULL; + } + + // decrypt finished, return + out[outlen] = 0; + *len = (size_t)outlen; + return out; +} + +void dav_get_hash(DAV_SHA_CTX *sha256, unsigned char *buf) { + BCryptFinishHash(sha256->hHash, buf, DAV_SHA256_DIGEST_LENGTH, 0); +} + + +char* dav_create_hash(const char *data, size_t len) { + unsigned char hash[DAV_SHA256_DIGEST_LENGTH]; + DAV_SHA_CTX *ctx = dav_hash_init(); + if(ctx) { + dav_hash_update(ctx, data, len); + dav_hash_final(ctx, hash); + } + return util_hexstr(hash, DAV_SHA256_DIGEST_LENGTH); +} + +DAV_SHA_CTX* dav_hash_init(void) { + DAV_SHA_CTX *ctx = malloc(sizeof(DAV_SHA_CTX)); + if(!ctx) { + return NULL; + } + if(cng_hash_init(ctx)) { + free(ctx); + return NULL; + } + return ctx; +} + +void dav_hash_update(DAV_SHA_CTX *ctx, const char *data, size_t len) { + BCryptHashData(ctx->hHash, (PUCHAR)data, len, 0); +} + +void dav_hash_final(DAV_SHA_CTX *ctx, unsigned char *buf) { + BCryptFinishHash(ctx->hHash, (PUCHAR)buf, DAV_SHA256_DIGEST_LENGTH, 0); + + // cleanup + cng_cleanup(ctx->hAlg, NULL, ctx->hHash, ctx->pbHashObject); + free(ctx); +} + +DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, int pwfunc, int enc) { + if(!password) { + return NULL; + } + size_t len = strlen(password); + if(len == 0) { + return NULL; + } + + // setup key data and length + unsigned char keydata[128]; + int keylen = 32; + switch(enc) { + case DAV_KEY_AES128: keylen = 16; break; + case DAV_KEY_AES256: keylen = 32; break; + default: return NULL; + } + + LPCWSTR algid; + switch(pwfunc) { + case DAV_PWFUNC_PBKDF2_SHA256: algid = BCRYPT_SHA256_ALGORITHM; break; + case DAV_PWFUNC_PBKDF2_SHA512: algid = BCRYPT_SHA512_ALGORITHM; break; + default: return NULL; + } + + // open algorithm provider + BCRYPT_ALG_HANDLE hAlg; + ULONG status = BCryptOpenAlgorithmProvider(&hAlg, algid, NULL, BCRYPT_ALG_HANDLE_HMAC_FLAG); + if(status > 0) { + fprintf(stderr, "Error: dav_pw2key: BCryptOpenAlgorithmProvider failed: 0x%X\n", (unsigned int)status); + return NULL; + } + + // derive key + status = BCryptDeriveKeyPBKDF2( + hAlg, + (PUCHAR)password, + len, + (PUCHAR)salt, + saltlen, + DAV_CRYPTO_ITERATION_COUNT, + keydata, + 128, + 0); + + BCryptCloseAlgorithmProvider(hAlg,0); + + if(status) { + fprintf(stderr, "Error: dav_pw2key: BCryptDeriveKeyPBKDF2 failed: 0x%X\n", (unsigned int)status); + return NULL; + } + + // create DavKey with generated data + DavKey *key = malloc(sizeof(DavKey)); + key->data = malloc(keylen); + key->length = keylen; + key->name = NULL; + key->type = enc; + memcpy(key->data, keydata, keylen); + return key; +} +#endif + + + UcxBuffer* aes_encrypt_buffer(UcxBuffer *in, DavKey *key) { UcxBuffer *encbuf = ucx_buffer_new( NULL,
--- a/libidav/crypto.h Mon Dec 02 13:29:36 2019 +0100 +++ b/libidav/crypto.h Wed Dec 04 11:47:51 2019 +0100 @@ -34,6 +34,9 @@ #ifdef __APPLE__ /* macos */ + +#define DAV_CRYPTO_COMMON_CRYPTO + #define DAV_AES_CTX CCCryptorRef #define DAV_SHA_CTX CC_SHA256_CTX #define DAV_SHA256_DIGEST_LENGTH 32 @@ -41,14 +44,42 @@ #include <CommonCrypto/CommonCrypto.h> #include <CommonCrypto/CommonDigest.h> +#elif defined(_WIN32) + +#define DAV_CRYPTO_CNG + +#include <windows.h> +#include <bcrypt.h> + +typedef struct WinBCryptCTX { + BCRYPT_ALG_HANDLE hAlg; + BCRYPT_KEY_HANDLE hKey; + void *pbKeyObject; + unsigned char pbIV[16]; + + char buf[16]; + size_t buflen; +} WinBCryptCTX; + +typedef struct WinBCryptSHACTX { + BCRYPT_ALG_HANDLE hAlg; + BCRYPT_HASH_HANDLE hHash; + void *pbHashObject; +} WinBCryptSHACTX; + +#define DAV_AES_CTX WinBCryptCTX +#define DAV_SHA_CTX WinBCryptSHACTX +#define DAV_SHA256_DIGEST_LENGTH 32 + #else -/* unix/linux and still windows */ +/* unix/linux */ + +#define DAV_USE_OPENSSL + #define DAV_AES_CTX EVP_CIPHER_CTX* #define DAV_SHA_CTX SHA256_CTX #define DAV_SHA256_DIGEST_LENGTH 32 -#define DAV_USE_OPENSSL - #include <openssl/evp.h> #include <openssl/rand.h>
--- a/mingw.mk Mon Dec 02 13:29:36 2019 +0100 +++ b/mingw.mk Wed Dec 04 11:47:51 2019 +0100 @@ -43,5 +43,5 @@ APP_EXT = .exe DAV_CFLAGS = `xml2-config --cflags` -I/mingw/include -DAV_LDFLAGS = `xml2-config --libs` -L/mingw/lib -lcurl -lssl -lcrypto -lws2_32 -lgdi32 -luuid -lole32 -loleaut32 -lgnurx +DAV_LDFLAGS = `xml2-config --libs` -L/mingw/lib -lcurl -lws2_32 -lgdi32 -luuid -lole32 -loleaut32 -lgnurx -lbcrypt
--- a/test/crypto.c Mon Dec 02 13:29:36 2019 +0100 +++ b/test/crypto.c Wed Dec 04 11:47:51 2019 +0100 @@ -261,6 +261,14 @@ UcxBuffer *enc = aes_encrypt_buffer(content, key); UCX_TEST_ASSERT(enc->size >= content->size + 16, "aes_encrypt_buffer failed"); + char *base64 = util_base64encode(enc->space, enc->size); + size_t plainlen = 0; + char *plain = aes_decrypt(base64, &plainlen, key); + + UCX_TEST_ASSERT(plain, "aes_decrypt failed"); + UCX_TEST_ASSERT(plainlen == content->size, "aes_decrypt: wrong length"); + UCX_TEST_ASSERT(!memcmp(plain, content->space, plainlen), "aes_decrypt: wrong content"); + UcxBuffer *dec = aes_decrypt_buffer(enc, key); UCX_TEST_ASSERT(dec->size == content->size, "aes_decrypt_buffer failed"); @@ -269,6 +277,8 @@ ucx_buffer_free(content); ucx_buffer_free(enc); ucx_buffer_free(dec); + free(base64); + free(plain); } } @@ -447,6 +457,7 @@ int index = 16*p + 4*s + i; int keylen = index % 2 == 0 ? 16 : 32; + UCX_TEST_ASSERT(key, "no key"); UCX_TEST_ASSERT(keylen == key->length, "wrong key length"); UCX_TEST_ASSERT(!memcmp(key->data, pwgenkeys[index], keylen), "wrong key data"); }