diff options
-rw-r--r-- | BUILD.txt | 3 | ||||
-rw-r--r-- | Makefile.am | 9 | ||||
-rw-r--r-- | configure.ac | 13 | ||||
-rw-r--r-- | contrib/gssntlmssp.spec.in | 2 | ||||
-rw-r--r-- | external/m4_ax_check_openssl.m4 | 124 | ||||
-rw-r--r-- | src/crypto.c | 221 | ||||
-rw-r--r-- | src/crypto.h | 117 | ||||
-rw-r--r-- | src/ntlm.c | 28 | ||||
-rw-r--r-- | src/ntlm.h | 166 | ||||
-rw-r--r-- | src/ntlm_common.h | 55 | ||||
-rw-r--r-- | src/ntlm_crypto.c | 409 |
11 files changed, 1089 insertions, 58 deletions
@@ -15,6 +15,7 @@ In order to build gss-ntlmssp the following development packages are needed: libtool libxml2 libxslt + libunistring m4 pkgconfig - + openssl diff --git a/Makefile.am b/Makefile.am index cb75e1e..798841b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -64,15 +64,19 @@ AM_CPPFLAGS = \ -DSYSCONFDIR=\"$(sysconfdir)\" \ -DLOCALEDIR=\"$(localedir)\" -EXTRA_DIST = build/config.rpath +#EXTRA_DIST = build/config.rpath -GSS_NTLM_LIBS = $(GSSAPI_LIBS) +GN_MECHGLUE_LIBS = $(GSSAPI_LIBS) $(CRYPTO_LIBS) GN_MECHGLUE_OBJ = \ + src/crypto.c \ + src/ntlm_crypto.c \ src/ntlm.c \ src/gss_ntlmssp.c dist_noinst_HEADERS = \ + src/crypto.h \ + src/ntlm_common.h \ src/ntlm.h \ src/gss_ntlmssp.h @@ -85,6 +89,7 @@ gssntlmssp_la_SOURCES = \ gssntlmssp_la_CFLAGS = \ $(AM_FLAGS) gssntlmssp_la_LDFLAGS = \ + $(GN_MECHGLUE_LIBS) \ -avoid-version \ -module diff --git a/configure.ac b/configure.ac index e3a6887..352d535 100644 --- a/configure.ac +++ b/configure.ac @@ -61,6 +61,7 @@ m4_include([external/pkg.m4]) m4_include([external/docbook.m4]) m4_include([external/sizes.m4]) m4_include([external/ax_pthread.m4]) +m4_include([external/m4_ax_check_openssl.m4]) AX_PTHREAD(,[AC_MSG_ERROR([Could not find Pthreads support])]) @@ -86,6 +87,18 @@ AC_CHECK_LIB(gssapi_krb5, gss_export_cred,, AC_SUBST([GSSAPI_CFLAGS]) AC_SUBST([GSSAPI_LIBS]) +AC_CHECK_HEADERS([unicase.h uniconv.h],, + [AC_MSG_ERROR([Could not find libunistring headers])]) +UNISTRING_LIBS="-lunistring" +AC_CHECK_LIB(unistring, u8_toupper,, + [AC_MSG_ERROR([libunistring does not support u8_toupper])], + [$UNISTRING_LIBS]) + +AX_CHECK_OPENSSL(,[AC_MSG_ERROR([Could not find OpenSSL support])]) + +CRYPTO_LIBS="$OPENSSL_LIBS" +AC_SUBST([CRYPTO_LIBS]) + if test x$HAVE_MANPAGES != x; then CHECK_XML_TOOLS CHECK_STYLESHEET([$SGML_CATALOG_FILES], diff --git a/contrib/gssntlmssp.spec.in b/contrib/gssntlmssp.spec.in index 00eeb53..c1a85cd 100644 --- a/contrib/gssntlmssp.spec.in +++ b/contrib/gssntlmssp.spec.in @@ -29,6 +29,8 @@ BuildRequires: gettext-devel BuildRequires: pkgconfig BuildRequires: krb5-devel >= 1.11.2 BuildRequires: findutils +BuildRequires: libunistring-devel +BuildRequires: openssl-devel %description diff --git a/external/m4_ax_check_openssl.m4 b/external/m4_ax_check_openssl.m4 new file mode 100644 index 0000000..a87c5a6 --- /dev/null +++ b/external/m4_ax_check_openssl.m4 @@ -0,0 +1,124 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_check_openssl.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CHECK_OPENSSL([action-if-found[, action-if-not-found]]) +# +# DESCRIPTION +# +# Look for OpenSSL in a number of default spots, or in a user-selected +# spot (via --with-openssl). Sets +# +# OPENSSL_INCLUDES to the include directives required +# OPENSSL_LIBS to the -l directives required +# OPENSSL_LDFLAGS to the -L or -R flags required +# +# and calls ACTION-IF-FOUND or ACTION-IF-NOT-FOUND appropriately +# +# This macro sets OPENSSL_INCLUDES such that source files should use the +# openssl/ directory in include directives: +# +# #include <openssl/hmac.h> +# +# LICENSE +# +# Copyright (c) 2009,2010 Zmanda Inc. <http://www.zmanda.com/> +# Copyright (c) 2009,2010 Dustin J. Mitchell <dustin@zmanda.com> +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 8 + +AU_ALIAS([CHECK_SSL], [AX_CHECK_OPENSSL]) +AC_DEFUN([AX_CHECK_OPENSSL], [ + found=false + AC_ARG_WITH([openssl], + [AS_HELP_STRING([--with-openssl=DIR], + [root of the OpenSSL directory])], + [ + case "$withval" in + "" | y | ye | yes | n | no) + AC_MSG_ERROR([Invalid --with-openssl value]) + ;; + *) ssldirs="$withval" + ;; + esac + ], [ + # if pkg-config is installed and openssl has installed a .pc file, + # then use that information and don't search ssldirs + AC_PATH_PROG([PKG_CONFIG], [pkg-config]) + if test x"$PKG_CONFIG" != x""; then + OPENSSL_LDFLAGS=`$PKG_CONFIG openssl --libs-only-L 2>/dev/null` + if test $? = 0; then + OPENSSL_LIBS=`$PKG_CONFIG openssl --libs-only-l 2>/dev/null` + OPENSSL_INCLUDES=`$PKG_CONFIG openssl --cflags-only-I 2>/dev/null` + found=true + fi + fi + + # no such luck; use some default ssldirs + if ! $found; then + ssldirs="/usr/local/ssl /usr/lib/ssl /usr/ssl /usr/pkg /usr/local /usr" + fi + ] + ) + + + # note that we #include <openssl/foo.h>, so the OpenSSL headers have to be in + # an 'openssl' subdirectory + + if ! $found; then + OPENSSL_INCLUDES= + for ssldir in $ssldirs; do + AC_MSG_CHECKING([for openssl/ssl.h in $ssldir]) + if test -f "$ssldir/include/openssl/ssl.h"; then + OPENSSL_INCLUDES="-I$ssldir/include" + OPENSSL_LDFLAGS="-L$ssldir/lib" + OPENSSL_LIBS="-lssl -lcrypto" + found=true + AC_MSG_RESULT([yes]) + break + else + AC_MSG_RESULT([no]) + fi + done + + # if the file wasn't found, well, go ahead and try the link anyway -- maybe + # it will just work! + fi + + # try the preprocessor and linker with our new flags, + # being careful not to pollute the global LIBS, LDFLAGS, and CPPFLAGS + + AC_MSG_CHECKING([whether compiling and linking against OpenSSL works]) + echo "Trying link with OPENSSL_LDFLAGS=$OPENSSL_LDFLAGS;" \ + "OPENSSL_LIBS=$OPENSSL_LIBS; OPENSSL_INCLUDES=$OPENSSL_INCLUDES" >&AS_MESSAGE_LOG_FD + + save_LIBS="$LIBS" + save_LDFLAGS="$LDFLAGS" + save_CPPFLAGS="$CPPFLAGS" + LDFLAGS="$LDFLAGS $OPENSSL_LDFLAGS" + LIBS="$OPENSSL_LIBS $LIBS" + CPPFLAGS="$OPENSSL_INCLUDES $CPPFLAGS" + AC_LINK_IFELSE( + [AC_LANG_PROGRAM([#include <openssl/ssl.h>], [SSL_new(NULL)])], + [ + AC_MSG_RESULT([yes]) + $1 + ], [ + AC_MSG_RESULT([no]) + $2 + ]) + CPPFLAGS="$save_CPPFLAGS" + LDFLAGS="$save_LDFLAGS" + LIBS="$save_LIBS" + + AC_SUBST([OPENSSL_INCLUDES]) + AC_SUBST([OPENSSL_LIBS]) + AC_SUBST([OPENSSL_LDFLAGS]) +]) diff --git a/src/crypto.c b/src/crypto.c new file mode 100644 index 0000000..14e6c8d --- /dev/null +++ b/src/crypto.c @@ -0,0 +1,221 @@ +/* + 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/>. +*/ + +#include <errno.h> + +#include <openssl/evp.h> +#include <openssl/hmac.h> +#include <openssl/rand.h> + +#include "crypto.h" + +int RAND_BUFFER(struct ntlm_buffer *random) +{ + int ret; + + ret = RAND_bytes(random->data, random->length); + if (ret != 1) { + return ERR_CRYPTO; + } + return 0; +} + +int HMAC_MD5(struct ntlm_buffer *key, + struct ntlm_buffer *payload, + struct ntlm_buffer *result) +{ + HMAC_CTX hmac_ctx; + unsigned int len; + int ret = 0; + + if (result->length != 16) return EINVAL; + + HMAC_CTX_init(&hmac_ctx); + + ret = HMAC_Init_ex(&hmac_ctx, key->data, key->length, EVP_md5(), NULL); + if (ret == 0) { + ret = ERR_CRYPTO; + goto done; + } + + ret = HMAC_Update(&hmac_ctx, payload->data, payload->length); + if (ret == 0) { + ret = ERR_CRYPTO; + goto done; + } + + ret = HMAC_Final(&hmac_ctx, result->data, &len); + if (ret == 0) { + ret = ERR_CRYPTO; + goto done; + } + + ret = 0; + +done: + HMAC_CTX_cleanup(&hmac_ctx); + return ret; +} + + + +static int mdx_hash(const EVP_MD *type, + struct ntlm_buffer *payload, + struct ntlm_buffer *result) +{ + EVP_MD_CTX ctx; + unsigned int len; + int ret; + + if (result->length != 16) return EINVAL; + + EVP_MD_CTX_init(&ctx); + ret = EVP_DigestInit_ex(&ctx, type, NULL); + if (ret == 0) { + ret = ERR_CRYPTO; + goto done; + } + + ret = EVP_DigestUpdate(&ctx, payload->data, payload->length); + if (ret == 0) { + ret = ERR_CRYPTO; + goto done; + } + + ret = EVP_DigestFinal_ex(&ctx, result->data, &len); + if (ret == 0) { + ret = ERR_CRYPTO; + goto done; + } + + ret = 0; + +done: + EVP_MD_CTX_cleanup(&ctx); + return ret; +} + +int MD4_HASH(struct ntlm_buffer *payload, + struct ntlm_buffer *result) +{ + return mdx_hash(EVP_md4(), payload, result); +} + +int MD5_HASH(struct ntlm_buffer *payload, + struct ntlm_buffer *result) +{ + return mdx_hash(EVP_md5(), payload, result); +} + +struct ntlm_rc4_handle { + EVP_CIPHER_CTX ctx; +}; + +int RC4_INIT(struct ntlm_buffer *rc4_key, + enum ntlm_cipher_mode mode, + struct ntlm_rc4_handle **out) +{ + struct ntlm_rc4_handle *handle; + int enc; + int ret; + + handle = malloc(sizeof(struct ntlm_rc4_handle)); + if (!handle) return ENOMEM; + + switch (mode) { + case NTLM_CIPHER_ENCRYPT: + enc = 1; + break; + case NTLM_CIPHER_DECRYPT: + enc = 0; + break; + default: + enc = -1; + } + + EVP_CIPHER_CTX_init(&handle->ctx); + ret = EVP_CipherInit_ex(&handle->ctx, EVP_rc4(), NULL, NULL, NULL, enc); + if (ret == 0) { + ret = ERR_CRYPTO; + goto done; + } + ret = EVP_CIPHER_CTX_set_key_length(&handle->ctx, rc4_key->length); + if (ret == 0) { + ret = ERR_CRYPTO; + goto done; + } + ret = EVP_CipherInit_ex(&handle->ctx, NULL, NULL, rc4_key->data, NULL, -1); + if (ret == 0) { + ret = ERR_CRYPTO; + goto done; + } + + ret = 0; + +done: + if (ret) { + EVP_CIPHER_CTX_cleanup(&handle->ctx); + safefree(handle); + } + *out = handle; + return ret; +} + +int RC4_UPDATE(struct ntlm_rc4_handle *handle, + struct ntlm_buffer *in, struct ntlm_buffer *out) +{ + int outl = 0; + int ret = 0; + int err; + + if (out->length < in->length) return EINVAL; + + err = EVP_CipherUpdate(&handle->ctx, + out->data, &outl, in->data, in->length); + if (err != 1) ret = ERR_CRYPTO; + if (outl > out->length) ret = ERR_CRYPTO; + + out->length = outl; + return ret; +} + +void RC4_FREE(struct ntlm_rc4_handle **handle) +{ + if (!handle) return; + EVP_CIPHER_CTX_cleanup(&(*handle)->ctx); + safefree(*handle); +} + +int RC4K(struct ntlm_buffer *key, + enum ntlm_cipher_mode mode, + struct ntlm_buffer *payload, + struct ntlm_buffer *result) +{ + struct ntlm_rc4_handle *handle; + int ret; + + if (result->length < payload->length) return EINVAL; + + ret = RC4_INIT(key, mode, &handle); + if (ret) return ret; + + ret = RC4_UPDATE(handle, payload, result); + + RC4_FREE(&handle); + return ret; +} + diff --git a/src/crypto.h b/src/crypto.h new file mode 100644 index 0000000..3a2f6f9 --- /dev/null +++ b/src/crypto.h @@ -0,0 +1,117 @@ +/* + 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/>. +*/ + +#ifndef _SRC_CRYPTO_H_ +#define _SRC_CRYPTO_H_ + +#include <stdbool.h> +#include "ntlm_common.h" + +/** + * @brief Fills the provided preallocated buffer with random data + * + * @param random A preallocated buffer, length determines the amount of + * random bytes the function will return. + * + * @return 0 for success or error otherwise + */ +int RAND_BUFFER(struct ntlm_buffer *random); + +/** + * @brief HMAC-MD5 function + * + * @param key The authentication key + * @param payload The payload to be authenticated + * @param result A preallocated 16 byte buffer + * + * @return 0 on success or ERR_CRYPTO + */ +int HMAC_MD5(struct ntlm_buffer *key, + struct ntlm_buffer *payload, + struct ntlm_buffer *result); + +/** + * @brief MD4 Hash Function + * + * @param payload The payoad to hash + * @param result The resulting Hash (preallocated, length must be 16) + * + * @return 0 on success or an error + */ +int MD4_HASH(struct ntlm_buffer *payload, + struct ntlm_buffer *result); + +/** + * @brief MD5 Hash Function + * + * @param payload The payoad to hash + * @param result The resulting Hash (preallocated, length must be 16) + * + * @return 0 on success or an error + */ +int MD5_HASH(struct ntlm_buffer *payload, + struct ntlm_buffer *result); + +/** + * @brief RC4 engine initialization + * + * @param rc4_key The encryption/decryption key + * @param mode The cipher mode + * @param state Allocated ntlm_rc4_state structure + * + * @return 0 on success or error + */ +int RC4_INIT(struct ntlm_buffer *rc4_key, + enum ntlm_cipher_mode mode, + struct ntlm_rc4_handle **handle); + + +/** + * @brief RC4 encrypt/decrypt function + * + * @param state The state initialized by RC4_INIT + * @param in Input buffer (plaintext for enc or ciphertext for dec) + * @param out Resulting buffer. Must be preallocated. + * + * @return 0 on success or error + */ +int RC4_UPDATE(struct ntlm_rc4_handle *handle, + struct ntlm_buffer *in, struct ntlm_buffer *out); + +/** + * @brief Release an rc4 handle + * + * @param state A pointer to the rc4 handle + */ +void RC4_FREE(struct ntlm_rc4_handle **handle); + +/** + * @brief RC4 encryption/decryption all in one + * + * @param key The encryption/decryption key + * @param mode The cipher mode + * @param payload Input buffer (plaintext for enc or ciphertext for dec) + * @param result Resulting buffer. Must be preallocated. + * + * @return 0 on success or error + */ +int RC4K(struct ntlm_buffer *key, + enum ntlm_cipher_mode mode, + struct ntlm_buffer *payload, +struct ntlm_buffer *result); + +#endif /* _SRC_CRYPTO_H_ */ @@ -28,8 +28,6 @@ #include <errno.h> #include <iconv.h> #include <stddef.h> -#include <stdint.h> -#include <stdlib.h> #include <string.h> #include <sys/time.h> @@ -129,32 +127,6 @@ struct wire_channel_binding { }; #pragma pack(pop) -/* 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[8]; - } v2; -}; -#pragma pack(pop) - #pragma pack(push, 1) struct wire_ntlm_cli_chal { uint8_t resp_type; @@ -18,6 +18,10 @@ #ifndef _NTLM_H_ #define _NTLM_H +#include <stdbool.h> + +#include "ntlm_common.h" + /* Negotiate Flags */ #define NTLMSSP_NEGOTIATE_56 (1 << 31) #define NTLMSSP_NEGOTIATE_KEY_EXCH (1 << 30) @@ -65,33 +69,6 @@ #define NTLMSSP_VERSION_BUILD 0 #define NTLMSSP_VERSION_REV NTLMSSP_REVISION_W2K3 -enum ntlm_err_code { - ERR_BASE = 0x4E540000, /* base error space at 'NT00' */ - ERR_DECODE, - ERR_ENCODE, -}; -#define NTLM_ERR_MASK 0x4E54FFFF -#define IS_NTLM_ERR_CODE(x) (((x) & NTLM_ERR_MASK) ? true : false) - -#define discard_const(ptr) ((void *)((uintptr_t)(ptr))) -#define safefree(x) do { free(x); x = NULL; } while(0) - -struct ntlm_buffer { - uint8_t *data; - size_t length; -}; - -void ntlm_free_buffer_data(struct ntlm_buffer *buf); - -uint64_t ntlm_timestamp_now(void); - - -enum ntlm_role { - NTLM_CLIENT, - NTLM_SERVER, - NTLM_DOMAIN_SERVER, - NTLM_DOMAIN_CONTROLLER -}; struct ntlm_ctx; @@ -114,6 +91,141 @@ int ntlm_init_ctx(struct ntlm_ctx **ctx); */ int ntlm_free_ctx(struct ntlm_ctx **ctx); +void ntlm_free_buffer_data(struct ntlm_buffer *buf); + +uint64_t ntlm_timestamp_now(void); + + +/* ############### CRYPTO FUNCTIONS ################ */ + +struct ntlm_key { + uint8_t data[16]; /* up to 16 bytes (128 bits) */ + size_t length; +}; + +/** + * @brief Turns a utf8 password into an NT Hash + * + * @param password The password + * @param result The returned hash + * + * @return 0 on success or an error; + */ +int ntlm_pwd_to_nt_hash(const char *password, struct ntlm_key *result); + +/** + * @brief Generates a NTLMv2 Response Key + * + * @param ctx An ntlm context + * @param nt_hash The NT Hash of the user password + * @param user The user name + * @param domain The user's domain + * @param result The resulting key + * (must be a preallocated 16 bytes buffer) + * + * @return 0 on success or ERR_CRYPTO + */ +int NTOWFv2(struct ntlm_ctx *ctx, struct ntlm_key *nt_hash, + const char *user, const char *domain, struct ntlm_key *result); + +/** + * @brief Computes The NTLMv2 Response + * + * @param ntlmv2_key The NTLMv2 key computed by NTOWFv2() + * @param server_chal[8] The server provided challenge + * @param client_chal[8] A client generated random challenge + * @param timestamp A FILETIME timestamp + * @param target_info The target info + * @param nt_response The resulting nt_response buffer + * + * @return 0 on success or error. + */ +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); + +/** + * @brief Computes The LMv2 Response + * + * @param ntlmv2_key The NTLMv2 key computed by NTOWFv2() + * @param server_chal[8] The server provided challenge + * @param client_chal[8] A client generated random challenge + * @param lm_response The resulting lm_response buffer + * + * @return 0 on success or error. + */ +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); + +/** + * @brief Computes the NTLMv2 SessionBaseKey + * + * @param ntlmv2_key The NTLMv2 key computed by NTOWFv2() + * @param nt_response The NTLMv2 response + * @param session_base_key The resulting session key + * + * @return 0 on success or error. + */ +int ntlmv2_session_base_key(struct ntlm_key *ntlmv2_key, + struct ntlm_buffer *nt_response, + struct ntlm_key *session_base_key); + +/** + * @brief Comutes the NTLM session key + * + * @param key_exchange_key[16] The Key Exchange Key + * @param key_exch KEY_EXCH has been negotited + * @param exported_session_key[16] Resulting exported session key + * + * @return 0 on success or error. + */ +int ntlm_exported_session_key(struct ntlm_key *key_exchange_key, + bool key_exch, + struct ntlm_key *exported_session_key); + +/** + * @brief Comutes the NTLM encrypted session key + * + * @param key_exchange_key[16] The Key Exchange Key + * @param exported_session_key[16] Resulting exported session key + * @param encrypted_random_session_key Resulting encrypted session key + * + * @return 0 on success or error. + */ +int ntlm_encrypted_session_key(struct ntlm_key *key_exchange_key, + struct ntlm_key *exported_session_key, + struct ntlm_key *encrypted_random_session_key); + +/** + * @brief Computes all the sign and seal keys from the session key + * + * @param flags Incoming challenge/authenticate flags + * @param client Wheter this ia a client or a server + * @param random_session_key The session key + * @param sign_send_key Resulting Signing key for send ops + * @param sign_recv_key Resulting Signing key for recv ops + * @param seal_send_key Resulting Sealing key for send ops + * @param seal_recv_key Resulting Sealing key for recv ops + * @param seal_send_handle Handle for RC4 encryption (v1 sealing) + * @param seal_recv_handle Handle for RC4 decryption (v1 sealing) + * + * @return 0 on success or error. + */ +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); + + +/* ############## ENCODING / DECODING ############## */ + /** * @brief A utility function to construct a target_info structure * diff --git a/src/ntlm_common.h b/src/ntlm_common.h new file mode 100644 index 0000000..c70fe8d --- /dev/null +++ b/src/ntlm_common.h @@ -0,0 +1,55 @@ +/* + 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/>. +*/ + +#ifndef _NTLM_COMMON_H_ +#define _NTLM_COMMON_H_ + +#include <stdint.h> +#include <stdlib.h> + +enum ntlm_err_code { + ERR_BASE = 0x4E540000, /* base error space at 'NT00' */ + ERR_DECODE, + ERR_ENCODE, + ERR_CRYPTO, +}; +#define NTLM_ERR_MASK 0x4E54FFFF +#define IS_NTLM_ERR_CODE(x) (((x) & NTLM_ERR_MASK) ? true : false) + +#define discard_const(ptr) ((void *)((uintptr_t)(ptr))) +#define safefree(x) do { free(x); x = NULL; } while(0) +#define safezero(x, s) do { \ + volatile uint8_t *p = (x); \ + size_t size = (s); \ + while (size--) { *p++ = 0; } \ +} while(0) + + +struct ntlm_buffer { + uint8_t *data; + size_t length; +}; + +struct ntlm_rc4_handle; + +enum ntlm_cipher_mode { + NTLM_CIPHER_IGNORE, + NTLM_CIPHER_ENCRYPT, + NTLM_CIPHER_DECRYPT, +}; + +#endif /* _NTLM_COMMON_H_ */ 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; +} |