From 91bbab9e34d79d054ba745898dc893a2ffc3bd1c Mon Sep 17 00:00:00 2001 From: Florian Forster Date: Tue, 28 Jan 2014 15:03:03 +0100 Subject: [PATCH] libcollectdclient: Add support for signing packets on Windows. --- src/libcollectdclient/collectd/win_hmac.h | 47 ++++++ src/libcollectdclient/network_buffer.c | 85 +++++++++- src/libcollectdclient/win_hmac.c | 257 ++++++++++++++++++++++++++++++ 3 files changed, 387 insertions(+), 2 deletions(-) create mode 100644 src/libcollectdclient/collectd/win_hmac.h create mode 100644 src/libcollectdclient/win_hmac.c diff --git a/src/libcollectdclient/collectd/win_hmac.h b/src/libcollectdclient/collectd/win_hmac.h new file mode 100644 index 00000000..6c7d239c --- /dev/null +++ b/src/libcollectdclient/collectd/win_hmac.h @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2010-2014 Florian Forster + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + **/ + +#ifndef WIN_HMAC_H +#define WIN_HMAC_H 1 + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +BOOL CreateHMAC (HCRYPTPROV hProv, + ALG_ID Algid, + BYTE *pbKey, DWORD dwKeySize, + DWORD dwFlags, + HCRYPTHASH *phHash, + HCRYPTKEY *phKey); + +BOOL DestroyHMAC (HCRYPTHASH hHash, HCRYPTKEY hKey); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +/* vim: set ts=4 : */ +#endif /* WIN_HMAC_H */ diff --git a/src/libcollectdclient/network_buffer.c b/src/libcollectdclient/network_buffer.c index b4767942..44a58c81 100644 --- a/src/libcollectdclient/network_buffer.c +++ b/src/libcollectdclient/network_buffer.c @@ -70,6 +70,9 @@ GCRY_THREAD_OPTION_PTHREAD_IMPL; #endif /* !WIN32 */ #include "collectd/network_buffer.h" +#if WIN32 +# include "collectd/win_hmac.h" +#endif #define TYPE_HOST 0x0000 #define TYPE_TIME 0x0001 @@ -519,7 +522,82 @@ static int nb_add_value_list (lcc_network_buffer_t *nb, /* {{{ */ } /* }}} int nb_add_value_list */ /* TODO: Add encryption for Windows */ -#if HAVE_LIBGCRYPT +#if WIN32 +static int nb_add_signature (lcc_network_buffer_t *nb) /* {{{ */ +{ + BYTE *buffer; + DWORD buffer_size; + + BYTE hash[32] = { 0 }; + DWORD hash_size = sizeof (hash) / sizeof (hash[0]); + + HCRYPTPROV hProv; + HCRYPTHASH hHash; + HCRYPTKEY hKey; + BOOL status; + + /* The type, length and username have already been filled in by + * "lcc_network_buffer_initialize". All we do here is calculate the hash over + * the username and the data and add the hash value to the buffer. */ + buffer = (LPBYTE) (nb->buffer + PART_SIGNATURE_SHA256_SIZE); + assert (nb->size >= (nb->free + PART_SIGNATURE_SHA256_SIZE)); + buffer_size = (DWORD) (nb->size - (nb->free + PART_SIGNATURE_SHA256_SIZE)); + + status = CryptAcquireContext (&hProv, + /* szContainer = */ NULL, + /* CSP name = */ NULL, + /* provider type = */ PROV_RSA_AES, + /* flags = */ CRYPT_VERIFYCONTEXT); + if (!status) + return (-1); + + status = CreateHMAC (hProv, + /* algorithm = */ CALG_SHA_256, + /* lpbKey = */ (LPBYTE) nb->password, + /* dwKeySize = */ (DWORD) strlen (nb->password), + /* dwFlags = */ 0, + /* lphHash = */ &hHash, + /* lphKey = */ &hKey); + if (!status) + { + CryptReleaseContext (hProv, /* dwFlags = */ 0); + return (-1); + } + + status = CryptHashData (hHash, + /* pbData = */ buffer, + /* dwDataSize = */ buffer_size, + /* dwFlags = */ 0); + if (!status) + { + CryptDestroyHash (hHash); + CryptDestroyKey (hKey); + CryptReleaseContext (hProv, /* dwFlags = */ 0); + return (-1); + } + + status = CryptGetHashParam (hHash, + /* dwParam = */ HP_HASHVAL, + /* pbData = */ hash, + /* pwdDataLen = */ &hash_size, + /* dwFlags = */ 0); + if (!status) + { + CryptDestroyHash (hHash); + CryptDestroyKey (hKey); + CryptReleaseContext (hProv, /* dwFlags = */ 0); + return (-1); + } + + assert (((2 * sizeof (uint16_t)) + hash_size) == PART_SIGNATURE_SHA256_SIZE); + memcpy (nb->buffer + (2 * sizeof (uint16_t)), hash, hash_size); + + CryptDestroyHash (hHash); + CryptDestroyKey (hKey); + CryptReleaseContext (hProv, /* dwFlags = */ 0); + return (0); +} /* }}} int nb_add_signature */ +#elif HAVE_LIBGCRYPT static int nb_add_signature (lcc_network_buffer_t *nb) /* {{{ */ { char *buffer; @@ -803,7 +881,10 @@ int lcc_network_buffer_finalize (lcc_network_buffer_t *nb) /* {{{ */ if (nb == NULL) return (EINVAL); -#if HAVE_LIBGCRYPT +#if WIN32 + if (nb->seclevel == SIGN) + nb_add_signature (nb); +#elif HAVE_LIBGCRYPT if (nb->seclevel == SIGN) nb_add_signature (nb); else if (nb->seclevel == ENCRYPT) diff --git a/src/libcollectdclient/win_hmac.c b/src/libcollectdclient/win_hmac.c new file mode 100644 index 00000000..cf102d2b --- /dev/null +++ b/src/libcollectdclient/win_hmac.c @@ -0,0 +1,257 @@ +/** + * Copyright (c) 2010-2014 Florian Forster + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + **/ + +#include "win_hmac.h" + +#include +#include +#include + +/* + * Creating a HCRYPTKEY from a plain text password is probably the most tricky + * part when calculating an RFC2104 HMAC using Microsoft CryptoAPI. The + * examples provided at the MSDN website encourage you to use "CryptDeriveKey", + * which doesn't do the conditional hashing required by HMAC. (That is, it + * might. The documentation on any of the functions is so vague that it's well + * possible the elegant solution is just not documented nor demonstrated. + */ +static HCRYPTKEY create_hmac_key_exact (HCRYPTPROV hProv, + BYTE *pbKey, DWORD dwKeySize) +{ + /* Layout of the memory expected when importing a plain text blob is + * documented at the "CryptImportKey" under "Remarks". The four bytes + * following the PUBLICKEYSTRUC structure hold the size of the key, then + * follows the key itself. */ + struct plain_text_data_s + { + PUBLICKEYSTRUC blob; + DWORD key_size; + } *data; + DWORD data_size; + HCRYPTKEY ret_key = 0; + BOOL status; + + data_size = sizeof (*data) + dwKeySize; + data = (struct plain_text_data_s *) malloc (data_size); + if (data == NULL) + return (0); + memset (data, 0, data_size); + + /* The key is not encrypted. */ + data->blob.bType = PLAINTEXTKEYBLOB; + data->blob.bVersion = CUR_BLOB_VERSION; + data->blob.reserved = 0; + /* The "CryptImportKey" page explicitly states that "RC2" is to be used for + * HMAC keys. What anyone might have been thinking, I don't know. */ + data->blob.aiKeyAlg = CALG_RC2; + + /* Copy the key to the memory right behind the struct. "memcpy" should only + * add memory protection crap *after* the region written to, so the blob + * shouldn't be destroyed. We play it save, though, and set the key size + * last. Should problems arise, we should switch to a loop just to be sure. */ + memcpy (data + 1, pbKey, dwKeySize); + data->key_size = dwKeySize; + + /* Actually convert our memory region to this mysterious key structure. + * The "CRYPT_IPSEC_HMAC_KEY" is required to allow RC2 keys longer than + * 16 byte. Again, this is documented on the "CryptImportKey" page as a + * side note. */ + status = CryptImportKey (hProv, (BYTE *) data, sizeof (data), + /* public key = */ 0, + /* flags = */ CRYPT_IPSEC_HMAC_KEY, + &ret_key); + if (!status) + { + free (data); + return (0); + } + + free (data); + return (ret_key); +} /* HCRYPTKEY create_hmac_key_exact */ + +/* If the key of the HMAC is larger than the hash size, use the hash of the + * key instead of using the key directly. */ +static HCRYPTKEY create_hmac_key_hashed (HCRYPTPROV hProv, + BYTE *pbKey, DWORD dwKeySize, + DWORD dwHashSize, HCRYPTHASH hHash) +{ + BOOL status; + BYTE *hash_data; + DWORD hash_data_size; + HCRYPTKEY key; + + assert (dwKeySize > dwHashSize); + + status = CryptHashData (hHash, pbKey, dwKeySize, /* dwFlags = */ 0); + if (!status) + return (0); + + hash_data = (BYTE *) malloc (dwHashSize); + if (hash_data == NULL) + return (0); + memset (hash_data, 0, dwHashSize); + hash_data_size = dwHashSize; + + status = CryptGetHashParam (hHash, HP_HASHVAL, + hash_data, &hash_data_size, /* flags = */ 0); + if (!status) + { + free (hash_data); + return (0); + } + + assert (hash_data_size == dwHashSize); + + key = create_hmac_key_exact (hProv, hash_data, hash_data_size); + + free (hash_data); + return (key); +} /* HCRYPTKEY create_hmac_key_hashed */ + +/* If the key is short enough, it is (right-)padded with zeros and otherwise + * used as-is. */ +static HCRYPTKEY create_hmac_key_padded (HCRYPTPROV hProv, + BYTE *pbKey, DWORD dwKeySize, + DWORD dwHashSize) +{ + BYTE *padded_key; + HCRYPTKEY key; + DWORD i; + + assert (dwKeySize <= dwHashSize); + + if (dwKeySize == dwHashSize) + return (create_hmac_key_exact (hProv, pbKey, dwKeySize)); + + padded_key = (BYTE *) malloc (dwHashSize); + if (padded_key == NULL) + return (0); + + /* Copy the key and right-pad with zeros. Don't use "memcpy" here because + * the fucked up version of VS will corrupt memory. */ + for (i = 0; i < dwHashSize; i++) + padded_key[i] = (i < dwKeySize) ? pbKey[i] : 0; + + key = create_hmac_key_exact (hProv, padded_key, dwHashSize); + + free (padded_key); + return (key); +} /* HCRYPTKEY create_hmac_key_padded */ + +static HCRYPTKEY create_hmac_key (HCRYPTPROV hProv, + ALG_ID Algid, + BYTE *pbKey, DWORD dwKeySize) +{ + HCRYPTHASH hash = 0; + HCRYPTKEY key; + DWORD hash_size = 0; + DWORD param_size; + BOOL status; + + /* Allocate a hash object to determine the hash size. */ + status = CryptCreateHash (hProv, Algid, + /* hKey = */ 0, + /* dwFlags = */ 0, + /* out phHash = */ &hash); + if (!status) + return (0); + + param_size = (DWORD) sizeof (hash_size); + status = CryptGetHashParam (hash, HP_HASHSIZE, + (BYTE *) &hash_size, ¶m_size, + /* flags = */ 0); + if (!status) + { + CryptDestroyHash (hash); + return (0); + } + + /* Determine whether we need to calculate the hash of the key or if + * padding is sufficient. */ + if (dwKeySize > hash_size) + key = create_hmac_key_hashed (hProv, pbKey, dwKeySize, hash_size, hash); + else + key = create_hmac_key_padded (hProv, pbKey, dwKeySize, hash_size); + + CryptDestroyHash (hash); + return (key); +} /* HCRYPTKEY create_hmac_key */ + +BOOL CreateHMAC (HCRYPTPROV hProv, + ALG_ID Algid, + BYTE *pbKey, DWORD dwKeySize, + DWORD dwFlags, + HCRYPTHASH *phHash, + HCRYPTKEY *phKey) +{ + HCRYPTKEY hmac_key; + HCRYPTHASH hash = 0; + HMAC_INFO hmac_info; + BOOL status; + + hmac_key = create_hmac_key (hProv, Algid, pbKey, dwKeySize); + if (hmac_key == 0) + return (FALSE); + + status = CryptCreateHash (hProv, CALG_HMAC, hmac_key, + /* flags = */ dwFlags, + &hash); + if (!status) + { + CryptDestroyKey (hmac_key); + return (status); + } + + memset (&hmac_info, 0, sizeof (hmac_info)); + hmac_info.HashAlgid = Algid; + hmac_info.pbInnerString = NULL; + hmac_info.cbInnerString = 0; + hmac_info.pbOuterString = NULL; + hmac_info.cbOuterString = 0; + + status = CryptSetHashParam (hash, HP_HMAC_INFO, (BYTE *) &hmac_info, + /* flags = */ 0); + if (!status) + { + CryptDestroyHash (hash); + CryptDestroyKey (hmac_key); + return (status); + } + + *phHash = hash; + *phKey = hmac_key; + return (TRUE); +} /* BOOL CreateHMAC */ + +BOOL DestroyHMAC (HCRYPTHASH hHash, HCRYPTKEY hKey) +{ + if (hHash != 0) + CryptDestroyHash (hHash); + + if (hKey != 0) + CryptDestroyKey (hKey); + + return (TRUE); +} /* BOOL DestroyHMAC */ + +/* vim: set ts=4 sw=4 noet : */ -- 2.11.0