| /* |
| * |
| * 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 <config.h> |
| #endif |
| |
| #include <ell/ell.h> |
| |
| #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; |
| } |