/* * * Wireless daemon for Linux * * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. * * 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 2.1 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, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include "src/missing.h" #include "src/mschaputil.h" /** * Internal function for generate_nt_response. * The DES keys specified for generate_nt_response are 56bit, while the api we * use takes 64bit keys, so we have to generate the parity bits. **/ static bool mschap_des_encrypt(const uint8_t challenge[static 8], const uint8_t key[static 7], uint8_t cipher_text[static 8]) { uint8_t pkey[8], tmp; int i; struct l_cipher *cipher; uint8_t next; for (i = 0, next = 0; i < 7; ++i) { tmp = key[i]; pkey[i] = (tmp >> i) | next | 1; next = tmp << (7 - i); } pkey[i] = next | 1; cipher = l_cipher_new(L_CIPHER_DES, pkey, 8); explicit_bzero(pkey, 8); if (!cipher) return false; l_cipher_encrypt(cipher, challenge, cipher_text, 8); l_cipher_free(cipher); return true; } bool mschap_challenge_response(const uint8_t *challenge, const uint8_t *password_hash, uint8_t *response) { uint8_t buf[21]; bool r; memset(buf, 0, sizeof(buf)); memcpy(buf, password_hash, 16); r = mschap_des_encrypt(challenge, buf + 0, response + 0) && mschap_des_encrypt(challenge, buf + 7, response + 8) && mschap_des_encrypt(challenge, buf + 14, response + 16); explicit_bzero(buf, sizeof(buf)); return r; } /** * Hash the utf8 encoded nt password. * It is asumed, that the password is valid utf8! * The rfc says "unicode-char", but never specifies which encoding. * This function converts the password to ucs-2. * The example in the code uses LE for the unicode chars, so it is forced here. * https://tools.ietf.org/html/draft-ietf-pppext-mschap-00#ref-8 */ bool mschap_nt_password_hash(const char *password, uint8_t *password_hash) { size_t size = l_utf8_strlen(password); size_t bsize = strlen(password); uint16_t buffer[size]; unsigned int i, pos; struct l_checksum *check; bool r = false; for (i = 0, pos = 0; i < size; ++i) { wchar_t val; pos += l_utf8_get_codepoint(password + pos, bsize - pos, &val); if (val > 0xFFFF) { l_error("Encountered password with value not valid in " "ucs-2"); goto cleanup; } buffer[i] = L_CPU_TO_LE16(val); } check = l_checksum_new(L_CHECKSUM_MD4); if (!check) goto cleanup; l_checksum_update(check, (uint8_t *) buffer, size * 2); l_checksum_get_digest(check, password_hash, 16); l_checksum_free(check); r = true; cleanup: explicit_bzero(buffer, size * 2); return r; } static const char *mschapv2_exlude_domain_name(const char *username) { const char *c; for (c = username; *c; c++) { if (*c != '\\') continue; return c + 1; } return username; } /** * Internal function to generate the challenge used in nt_response * https://tools.ietf.org/html/rfc2759 * * @peer_challenge: The challenge generated by the peer (us) * @server_challenge: The challenge generated by the authenticator * @user: The username utf8 encoded * @challenge: The destination * * Returns: true on success, false if hash/encrypt couldn't be done **/ static bool mschapv2_challenge_hash(const uint8_t *peer_challenge, const uint8_t *server_challenge, const char *username, uint8_t challenge[static 8]) { struct l_checksum *check; check = l_checksum_new(L_CHECKSUM_SHA1); if (!check) return false; username = mschapv2_exlude_domain_name(username); l_checksum_update(check, peer_challenge, 16); l_checksum_update(check, server_challenge, 16); l_checksum_update(check, username, strlen(username)); l_checksum_get_digest(check, challenge, 8); l_checksum_free(check); return true; } /** * Generate the nt_response for mschapv2. * This function is specified in: * https://tools.ietf.org/html/rfc2759 * * @password_hash: The MD4 hash of the ucs2 encoded user password * @peer_challenge: the challenge generated by the peer (us) * @server_challenge: the challenge generated by the authenticator * @user: The username, utf8 encoded * @nt_response: The destination * * Returns: true on success, false if hash/encrypt couldn't be done **/ bool mschapv2_generate_nt_response(const uint8_t password_hash[static 16], const uint8_t peer_challenge[static 16], const uint8_t server_challenge[static 16], const char *user, uint8_t response[static 24]) { uint8_t challenge[8]; uint8_t buffer[21]; bool r; if (!mschapv2_challenge_hash(peer_challenge, server_challenge, user, challenge)) return false; memset(buffer, 0, sizeof(buffer)); memcpy(buffer, password_hash, 16); r = mschap_des_encrypt(challenge, buffer + 0, response + 0) && mschap_des_encrypt(challenge, buffer + 7, response + 8) && mschap_des_encrypt(challenge, buffer + 14, response + 16); explicit_bzero(buffer, sizeof(buffer)); return r; } /** * Generate the hash of the password hash * * @password_hash: The hash of the password * @password_hash_hash: The MD4 hash of the password hash * * Returns: true on success, false if hash/encrypt couldn't be done **/ bool mschapv2_hash_nt_password_hash(const uint8_t password_hash[static 16], uint8_t password_hash_hash[static 16]) { struct l_checksum *check; check = l_checksum_new(L_CHECKSUM_MD4); if (!check) return false; l_checksum_update(check, password_hash, 16); l_checksum_get_digest(check, password_hash_hash, 16); l_checksum_free(check); return true; } /** * Generate the mschapv2 authenticator response for verifying authenticator * This function is specified in: * https://tools.ietf.org/html/rfc2759 * * @password_hash_hash: The MD4 hash of the password hash * @nt_response: The nt_response generated for this exchange * @peer_challenge: The challenge generated by the peer (us) * @server_challenge: The challenge generated by the authenticator * @user: The username utf8 encoded * @response: The destination * * Returns: true on success, false if hash/encrypt couldn't be done **/ bool mschapv2_generate_authenticator_response( const uint8_t pw_hash_hash[static 16], const uint8_t nt_response[static 24], const uint8_t peer_challenge[static 16], const uint8_t server_challenge[static 16], const char *user, char response[static 42]) { static const uint8_t magic1[] = { 0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67, 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74 }; static const uint8_t magic2[] = { 0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B, 0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F, 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E, 0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F, 0x6E }; uint8_t digest[20]; uint8_t challenge[8]; char *ascii; struct l_checksum *check; check = l_checksum_new(L_CHECKSUM_SHA1); if (!check) return false; l_checksum_update(check, pw_hash_hash, 16); l_checksum_update(check, nt_response, 24); l_checksum_update(check, magic1, sizeof(magic1)); l_checksum_get_digest(check, digest, sizeof(digest)); l_checksum_free(check); if (!mschapv2_challenge_hash(peer_challenge, server_challenge, user, challenge)) return false; check = l_checksum_new(L_CHECKSUM_SHA1); if (!check) return false; l_checksum_update(check, digest, sizeof(digest)); l_checksum_update(check, challenge, sizeof(challenge)); l_checksum_update(check, magic2, sizeof(magic2)); l_checksum_get_digest(check, digest, sizeof(digest)); l_checksum_free(check); response[0] = 'S'; response[1] = '='; ascii = l_util_hexstring_upper(digest, sizeof(digest)); if (!ascii) return false; memcpy(response + 2, ascii, 40); l_free(ascii); return true; }