summaryrefslogtreecommitdiffstats
path: root/src/ntlm_crypto.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ntlm_crypto.c')
-rw-r--r--src/ntlm_crypto.c409
1 files changed, 409 insertions, 0 deletions
diff --git a/src/ntlm_crypto.c b/src/ntlm_crypto.c
new file mode 100644
index 0000000..52e195a
--- /dev/null
+++ b/src/ntlm_crypto.c
@@ -0,0 +1,409 @@
+/*
+ Copyright (C) 2013 Simo Sorce <simo@samba.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+/* This File implements the NTLM protocol as specified by:
+ * [MS-NLMP]: NT LAN Manager (NTLM) Authentication Protocol
+ *
+ * Additional cross checking with:
+ * http://davenport.sourceforge.net/ntlm.html
+ */
+
+#include <errno.h>
+#include <stddef.h>
+#include <string.h>
+
+#include <unicase.h>
+#include <uniconv.h>
+
+#include "ntlm.h"
+#include "crypto.h"
+
+/* lm response, v1 or v2 */
+#pragma pack(push, 1)
+union wire_lm_response {
+ struct {
+ uint8_t resp[24];
+ } v1;
+ struct {
+ uint8_t resp[16];
+ uint8_t cli_chal[8];
+ } v2;
+};
+#pragma pack(pop)
+
+/* ntlm response, v1 or v2 */
+#pragma pack(push, 1)
+union wire_ntlm_response {
+ struct {
+ uint8_t resp[24];
+ } v1;
+ struct {
+ uint8_t resp[16];
+ uint8_t cli_chal[];
+ } v2;
+};
+#pragma pack(pop)
+
+#pragma pack(push, 1)
+struct wire_ntlmv2_cli_chal {
+ uint8_t resp_version;
+ uint8_t hi_resp_version;
+ uint8_t zero_6[6];
+ uint64_t timestamp;
+ uint8_t client_chal[8];
+ uint8_t zero_4[4];
+ uint8_t target_info[];
+ /* NOTE: the target_info array must terminate with 4 zero bytes.
+ * This is consistent with just copying the target_info array
+ * returned in the challenge message as the last AV_PAIR there is
+ * always MSV_AV_EOL which happens to be 4 bytes of zeros */
+};
+#pragma pack(pop)
+
+/* the max username is 20 chars, max NB domain len is 15, so 128 should be
+ * plenty including conversion to UTF8 using max lenght for each code point
+ */
+#define MAX_USER_DOM_LEN 512
+
+
+int ntlm_pwd_to_nt_hash(const char *password, struct ntlm_key *result)
+{
+ struct ntlm_buffer payload;
+ struct ntlm_buffer hash;
+ char *retstr;
+ size_t out;
+ size_t len;
+ int ret;
+
+ len = strlen(password);
+ retstr = u8_conv_to_encoding("UCS-2LE", iconveh_error,
+ (const uint8_t *)password, len,
+ NULL, NULL, &out);
+ if (!retstr) return ERR_CRYPTO;
+
+ payload.data = (uint8_t *)retstr;
+ payload.length = out;
+ hash.data = result->data;
+ hash.length= result->length;
+
+ ret = MD4_HASH(&payload, &hash);
+ free(retstr);
+ return ret;
+}
+
+int NTOWFv2(struct ntlm_ctx *ctx, struct ntlm_key *nt_hash,
+ const char *user, const char *domain, struct ntlm_key *result)
+{
+ struct ntlm_buffer key = { nt_hash->data, nt_hash->length };
+ struct ntlm_buffer hmac = { result->data, result->length };
+ struct ntlm_buffer payload;
+ uint8_t upcased[MAX_USER_DOM_LEN];
+ uint8_t *retstr;
+ size_t offs;
+ size_t out;
+ size_t len;
+ int ret;
+
+ len = strlen(user);
+ out = MAX_USER_DOM_LEN;
+ retstr = u8_toupper((const uint8_t *)user, len,
+ NULL, NULL, upcased, &out);
+ if (!retstr) return ERR_CRYPTO;
+ offs = out;
+
+ len = strlen(domain);
+ /*
+ out = MAX_USER_DOM_LEN - offs;
+ retstr = u8_toupper((const uint8_t *)domain, len,
+ NULL, NULL, &upcased[offs], &out);
+ if (!retstr) return ERR_CRYPTO;
+ offs += out;
+ */
+ memcpy(&upcased[offs], domain, len);
+ offs += len;
+
+ retstr = (uint8_t *)u8_conv_to_encoding("UCS-2LE", iconveh_error,
+ upcased, offs, NULL, NULL, &out);
+ if (!retstr) return ERR_CRYPTO;
+
+ payload.data = (uint8_t *)retstr;
+ payload.length = out;
+
+ ret = HMAC_MD5(&key, &payload, &hmac);
+ free(retstr);
+ return ret;
+}
+
+int ntlmv2_compute_nt_response(struct ntlm_key *ntlmv2_key,
+ uint8_t server_chal[8], uint8_t client_chal[8],
+ uint64_t timestamp,
+ struct ntlm_buffer *target_info,
+ struct ntlm_buffer *nt_response)
+{
+ union wire_ntlm_response *nt_resp = NULL;
+ struct wire_ntlmv2_cli_chal *r;
+ struct ntlm_buffer key = { ntlmv2_key->data, ntlmv2_key->length };
+ struct ntlm_buffer payload;
+ struct ntlm_buffer nt_proof;
+ size_t r_len;
+ int ret;
+
+ /* add additional 4 0s trailing target_info */
+ r_len = sizeof(struct wire_ntlmv2_cli_chal) + target_info->length + 4;
+ nt_resp = calloc(1, sizeof(nt_resp->v2) + r_len);
+ if (!nt_resp) return ENOMEM;
+
+ r = (struct wire_ntlmv2_cli_chal *)nt_resp->v2.cli_chal;
+ r->resp_version = 1;
+ r->hi_resp_version = 1;
+ r->timestamp = htole64(timestamp);
+ memcpy(r->client_chal, client_chal, 8);
+ memcpy(r->target_info, target_info->data, target_info->length);
+
+ /* use nt_resp as a buffer to calculate the NT proof as they share
+ * the cli_chal part */
+ payload.data = &nt_resp->v2.resp[8];
+ payload.length = 8 + r_len;
+ memcpy(payload.data, server_chal, 8);
+ nt_proof.data = nt_resp->v2.resp;
+ nt_proof.length = 16;
+ ret = HMAC_MD5(&key, &payload, &nt_proof);
+
+ if (ret) {
+ safefree(nt_resp);
+ } else {
+ nt_response->data = (uint8_t *)nt_resp;
+ nt_response->length = 16 + r_len;
+ }
+ return ret;
+}
+
+int ntlmv2_compute_lm_response(struct ntlm_key *ntlmv2_key,
+ uint8_t server_chal[8], uint8_t client_chal[8],
+ struct ntlm_buffer *lm_response)
+{
+ union wire_lm_response *lm_resp = NULL;
+ struct ntlm_buffer key = { ntlmv2_key->data, ntlmv2_key->length };
+ uint8_t payload_buf[16];
+ struct ntlm_buffer payload = { payload_buf, 16 };
+ struct ntlm_buffer lm_proof;
+ int ret;
+
+ /* now caluclate the LM Proof */
+ lm_resp = malloc(sizeof(union wire_lm_response));
+ if (!lm_resp) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ memcpy(payload.data, server_chal, 8);
+ memcpy(&payload.data[8], client_chal, 8);
+ lm_proof.data = lm_resp->v2.resp;
+ lm_proof.length = 16;
+ ret = HMAC_MD5(&key, &payload, &lm_proof);
+
+done:
+ if (ret) {
+ safefree(lm_resp);
+ } else {
+ memcpy(lm_resp->v2.cli_chal, client_chal, 8);
+
+ lm_response->data = (uint8_t *)lm_resp;
+ lm_response->length = 24;
+ }
+ return ret;
+}
+
+int ntlmv2_session_base_key(struct ntlm_key *ntlmv2_key,
+ struct ntlm_buffer *nt_response,
+ struct ntlm_key *session_base_key)
+{
+ struct ntlm_buffer key = { ntlmv2_key->data, ntlmv2_key->length };
+ struct ntlm_buffer hmac = { session_base_key->data,
+ session_base_key->length };
+
+ if (session_base_key->length != 16) return EINVAL;
+
+ return HMAC_MD5(&key, nt_response, &hmac);
+}
+
+int ntlm_exported_session_key(struct ntlm_key *key_exchange_key,
+ bool key_exch,
+ struct ntlm_key *exported_session_key)
+{
+ struct ntlm_buffer nonce;
+
+ if (!key_exch) {
+ *exported_session_key = *key_exchange_key;
+ return 0;
+ }
+
+ exported_session_key->length = 16;
+ nonce.data = exported_session_key->data;
+ nonce.length = exported_session_key->length;
+ return RAND_BUFFER(&nonce);
+}
+
+int ntlm_encrypted_session_key(struct ntlm_key *key_exchange_key,
+ struct ntlm_key *exported_session_key,
+ struct ntlm_key *encrypted_random_session_key)
+{
+ struct ntlm_buffer key = { key_exchange_key->data,
+ key_exchange_key->length };
+ struct ntlm_buffer nonce = { exported_session_key->data,
+ exported_session_key->length };
+ struct ntlm_buffer cipher = { encrypted_random_session_key->data,
+ encrypted_random_session_key->length };
+
+ return RC4K(&key, NTLM_CIPHER_ENCRYPT, &nonce, &cipher);
+}
+
+static int ntlm_key_derivation_function(struct ntlm_key *key,
+ const char *magic_constant,
+ struct ntlm_key *derived_key)
+{
+ uint8_t buf[80]; /* key + constant is never larger than 80 */
+ struct ntlm_buffer payload = { buf, 0 };
+ struct ntlm_buffer result = { derived_key->data, 16 };
+ size_t len;
+ int ret;
+
+ if (key->length > 16) return ERR_CRYPTO;
+ len = strlen(magic_constant);
+ if (len > 64) return ERR_CRYPTO;
+
+ payload.length = key->length;
+ memcpy(payload.data, key->data, key->length);
+ memcpy(&payload.data[payload.length], magic_constant, len);
+ payload.length += len;
+
+ ret = MD5_HASH(&payload, &result);
+ if (ret == 0) {
+ derived_key->length = 16;
+ }
+ return ret;
+}
+
+#define NTLM_MODE_CLIENT true
+#define NTLM_MODE_SERVER false
+
+static int ntlm_signkey(uint32_t flags, bool mode,
+ struct ntlm_key *random_session_key,
+ struct ntlm_key *signing_key)
+{
+ const char *mc;
+
+ if (flags & NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY) {
+ if (mode == NTLM_MODE_CLIENT) {
+ mc = "session key to client-to-server signing key magic constant";
+ } else {
+ mc = "session key to server-to-client signing key magic constant";
+ }
+ return ntlm_key_derivation_function(random_session_key,
+ mc, signing_key);
+ } else {
+ signing_key->length = 0;
+ }
+ return 0;
+}
+
+static int ntlm_sealkey(uint32_t flags, bool mode,
+ struct ntlm_key *random_session_key,
+ struct ntlm_key *sealing_key)
+{
+ struct ntlm_key key;
+ const char *mc;
+
+ if (flags & NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY) {
+ if (flags & NTLMSSP_NEGOTIATE_128) {
+ key.length = 16;
+ } else if (flags & NTLMSSP_NEGOTIATE_56) {
+ key.length = 7;
+ } else {
+ key.length = 5;
+ }
+ memcpy(key.data, random_session_key->data, key.length);
+
+ if (mode == NTLM_MODE_CLIENT) {
+ mc = "session key to client-to-server sealing key magic constant";
+ } else {
+ mc = "session key to server-to-client sealing key magic constant";
+ }
+
+ return ntlm_key_derivation_function(&key, mc, sealing_key);
+
+ } else if (flags & NTLMSSP_NEGOTIATE_LM_KEY) {
+ if (flags & NTLMSSP_NEGOTIATE_56) {
+ memcpy(sealing_key->data, random_session_key->data, 7);
+ sealing_key->data[7] = 0xA0;
+ } else {
+ memcpy(sealing_key->data, random_session_key->data, 5);
+ sealing_key->data[5] = 0xE5;
+ sealing_key->data[6] = 0x38;
+ sealing_key->data[7] = 0xB0;
+ }
+ sealing_key->length = 8;
+ } else {
+ *sealing_key = *random_session_key;
+ }
+ return 0;
+}
+
+int ntlm_signseal_keys(uint32_t flags, bool client,
+ struct ntlm_key *random_session_key,
+ struct ntlm_key *sign_send_key,
+ struct ntlm_key *sign_recv_key,
+ struct ntlm_key *seal_send_key,
+ struct ntlm_key *seal_recv_key,
+ struct ntlm_rc4_handle **seal_send_handle,
+ struct ntlm_rc4_handle **seal_recv_handle)
+{
+ struct ntlm_buffer rc4_key;
+ bool mode;
+ int ret;
+
+ /* send key */
+ mode = client ? NTLM_MODE_CLIENT : NTLM_MODE_SERVER;
+ ret = ntlm_signkey(flags, mode, random_session_key, sign_send_key);
+ if (ret) return ret;
+ /* recv key */
+ mode = client ? NTLM_MODE_SERVER : NTLM_MODE_CLIENT;
+ ret = ntlm_signkey(flags, mode, random_session_key, sign_recv_key);
+ if (ret) return ret;
+
+ /* send key */
+ mode = client ? NTLM_MODE_CLIENT : NTLM_MODE_SERVER;
+ ret = ntlm_sealkey(flags, mode, random_session_key, seal_send_key);
+ if (ret) return ret;
+ /* recv key */
+ mode = client ? NTLM_MODE_SERVER : NTLM_MODE_CLIENT;
+ ret = ntlm_sealkey(flags, mode, random_session_key, seal_recv_key);
+ if (ret) return ret;
+
+ rc4_key.data = seal_send_key->data;
+ rc4_key.length = seal_send_key->length;
+ ret = RC4_INIT(&rc4_key, NTLM_CIPHER_ENCRYPT, seal_send_handle);
+ if (ret) return ret;
+
+ rc4_key.data = seal_recv_key->data;
+ rc4_key.length = seal_recv_key->length;
+ ret = RC4_INIT(&rc4_key, NTLM_CIPHER_DECRYPT, seal_recv_handle);
+ if (ret) return ret;
+
+ return 0;
+}