diff options
author | YASUOKA Masahiko <yasuoka@cvs.openbsd.org> | 2024-07-14 13:44:31 +0000 |
---|---|---|
committer | YASUOKA Masahiko <yasuoka@cvs.openbsd.org> | 2024-07-14 13:44:31 +0000 |
commit | 673c641e1e58c8c7da39708dfea35dc8879d9884 (patch) | |
tree | ee1b11a43e5123bb378a6402d19798834b7688d6 /usr.sbin/radiusd | |
parent | a5606e10b1a371d7239e9e5b50b9a32aab9adc85 (diff) |
Add radiusd_file(8) module. It provides authencation by a local file.
Diffstat (limited to 'usr.sbin/radiusd')
-rw-r--r-- | usr.sbin/radiusd/Makefile | 3 | ||||
-rw-r--r-- | usr.sbin/radiusd/chap_ms.c | 375 | ||||
-rw-r--r-- | usr.sbin/radiusd/chap_ms.h | 48 | ||||
-rw-r--r-- | usr.sbin/radiusd/parse.y | 3 | ||||
-rw-r--r-- | usr.sbin/radiusd/radiusd_file.c | 586 | ||||
-rw-r--r-- | usr.sbin/radiusd/radiusd_file/Makefile | 12 |
6 files changed, 1025 insertions, 2 deletions
diff --git a/usr.sbin/radiusd/Makefile b/usr.sbin/radiusd/Makefile index 61ba31825f3..1fc21891f9e 100644 --- a/usr.sbin/radiusd/Makefile +++ b/usr.sbin/radiusd/Makefile @@ -1,7 +1,8 @@ -# $OpenBSD: Makefile,v 1.4 2024/07/09 17:26:14 yasuoka Exp $ +# $OpenBSD: Makefile,v 1.5 2024/07/14 13:44:30 yasuoka Exp $ SUBDIR= radiusd SUBDIR+= radiusd_bsdauth +SUBDIR+= radiusd_file SUBDIR+= radiusd_ipcp SUBDIR+= radiusd_radius SUBDIR+= radiusd_standard diff --git a/usr.sbin/radiusd/chap_ms.c b/usr.sbin/radiusd/chap_ms.c new file mode 100644 index 00000000000..878fd4fd08c --- /dev/null +++ b/usr.sbin/radiusd/chap_ms.c @@ -0,0 +1,375 @@ +/* $OpenBSD: chap_ms.c,v 1.1 2024/07/14 13:44:30 yasuoka Exp $ */ + +/* + * Copyright (c) 2010-2013 Reyk Floeter <reyk@openbsd.org> + * Copyright (c) 1997-2001 Brian Somers <brian@Awfulhak.org> + * Copyright (c) 1997 Gabor Kincses <gabor@acm.org> + * Copyright (c) 1995 Eric Rosenquist + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> + +#include <ctype.h> +#include <string.h> +#include <stdio.h> + +#include <openssl/evp.h> +#include <openssl/des.h> +#include <openssl/md4.h> +#include <openssl/md5.h> +#include <openssl/sha.h> + +#include "chap_ms.h" + +/* + * Documentation & specifications: + * + * MS-CHAP (CHAP80) RFC2433 + * MS-CHAP-V2 (CHAP81) RFC2759 + * MPPE key management RFC3079 + * + * Security analysis: + * Schneier/Mudge/Wagner, "MS-CHAP-v2", Oct 99 + * "It is unclear to us why this protocol is so complicated." + */ + +static u_int8_t sha1_pad1[40] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static u_int8_t sha1_pad2[40] = { + 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, + 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, + 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, + 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 +}; + +u_int8_t get7bits(u_int8_t *, int); +void mschap_des_addparity(u_int8_t *, u_int8_t *); +void mschap_des_encrypt(u_int8_t *, u_int8_t *, u_int8_t *); +void mschap_challenge_response(u_int8_t *, u_int8_t *, u_int8_t *); + +u_int8_t +get7bits(u_int8_t *in, int start) +{ + u_int word; + + word = (u_int)in[start / 8] << 8; + word |= (u_int)in[start / 8 + 1]; + word >>= 15 - (start % 8 + 7); + + return (word & 0xfe); +} + +/* IN 56 bit DES key missing parity bits + OUT 64 bit DES key with parity bits added */ +void +mschap_des_addparity(u_int8_t *key, u_int8_t *des_key) +{ + des_key[0] = get7bits(key, 0); + des_key[1] = get7bits(key, 7); + des_key[2] = get7bits(key, 14); + des_key[3] = get7bits(key, 21); + des_key[4] = get7bits(key, 28); + des_key[5] = get7bits(key, 35); + des_key[6] = get7bits(key, 42); + des_key[7] = get7bits(key, 49); + + DES_set_odd_parity((DES_cblock *)des_key); +} + +void +mschap_des_encrypt(u_int8_t *clear, u_int8_t *key, u_int8_t *cipher) +{ + DES_cblock des_key; + DES_key_schedule key_schedule; + + mschap_des_addparity(key, des_key); + + DES_set_key(&des_key, &key_schedule); + DES_ecb_encrypt((DES_cblock *)clear, (DES_cblock *)cipher, + &key_schedule, 1); +} + +void +mschap_challenge_response(u_int8_t *challenge, u_int8_t *pwhash, + u_int8_t *response) +{ + u_int8_t padpwhash[21 + 1]; + + bzero(&padpwhash, sizeof(padpwhash)); + memcpy(padpwhash, pwhash, MSCHAP_HASH_SZ); + + mschap_des_encrypt(challenge, padpwhash + 0, response + 0); + mschap_des_encrypt(challenge, padpwhash + 7, response + 8); + mschap_des_encrypt(challenge, padpwhash + 14, response + 16); +} + +void +mschap_ntpassword_hash(u_int8_t *in, int inlen, u_int8_t *hash) +{ + EVP_MD_CTX *ctx; + u_int mdlen; + + ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, EVP_md4(), NULL); + EVP_DigestUpdate(ctx, in, inlen); + EVP_DigestFinal_ex(ctx, hash, &mdlen); + EVP_MD_CTX_free(ctx); +} + +void +mschap_challenge_hash(u_int8_t *peer_challenge, u_int8_t *auth_challenge, + u_int8_t *username, int usernamelen, u_int8_t *challenge) +{ + EVP_MD_CTX *ctx; + u_int8_t md[SHA_DIGEST_LENGTH]; + u_int mdlen; + u_int8_t *name; + + if ((name = strrchr(username, '\\')) == NULL) + name = username; + else + name++; + + ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, EVP_sha1(), NULL); + EVP_DigestUpdate(ctx, peer_challenge, MSCHAPV2_CHALLENGE_SZ); + EVP_DigestUpdate(ctx, auth_challenge, MSCHAPV2_CHALLENGE_SZ); + EVP_DigestUpdate(ctx, name, strlen(name)); + EVP_DigestFinal_ex(ctx, md, &mdlen); + EVP_MD_CTX_free(ctx); + + memcpy(challenge, md, MSCHAP_CHALLENGE_SZ); +} + +void +mschap_nt_response(u_int8_t *auth_challenge, u_int8_t *peer_challenge, + u_int8_t *username, int usernamelen, u_int8_t *password, int passwordlen, + u_int8_t *response) +{ + u_int8_t challenge[MSCHAP_CHALLENGE_SZ]; + u_int8_t password_hash[MSCHAP_HASH_SZ]; + + mschap_challenge_hash(peer_challenge, auth_challenge, + username, usernamelen, challenge); + + mschap_ntpassword_hash(password, passwordlen, password_hash); + mschap_challenge_response(challenge, password_hash, response); +} + +void +mschap_auth_response(u_int8_t *password, int passwordlen, + u_int8_t *ntresponse, u_int8_t *auth_challenge, u_int8_t *peer_challenge, + u_int8_t *username, int usernamelen, u_int8_t *auth_response) +{ + EVP_MD_CTX *ctx; + u_int8_t password_hash[MSCHAP_HASH_SZ]; + u_int8_t password_hash2[MSCHAP_HASH_SZ]; + u_int8_t challenge[MSCHAP_CHALLENGE_SZ]; + u_int8_t md[SHA_DIGEST_LENGTH], *ptr; + u_int mdlen; + int i; + const u_int8_t hex[] = "0123456789ABCDEF"; + static u_int8_t magic1[39] = { + 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 u_int8_t magic2[41] = { + 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 + }; + + mschap_ntpassword_hash(password, passwordlen, password_hash); + mschap_ntpassword_hash(password_hash, MSCHAP_HASH_SZ, password_hash2); + + ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, EVP_sha1(), NULL); + EVP_DigestUpdate(ctx, password_hash2, sizeof(password_hash2)); + EVP_DigestUpdate(ctx, ntresponse, 24); + EVP_DigestUpdate(ctx, magic1, 39); + EVP_DigestFinal_ex(ctx, md, &mdlen); + + mschap_challenge_hash(peer_challenge, auth_challenge, + username, usernamelen, challenge); + + EVP_DigestInit_ex(ctx, EVP_sha1(), NULL); + EVP_DigestUpdate(ctx, md, sizeof(md)); + EVP_DigestUpdate(ctx, challenge, sizeof(challenge)); + EVP_DigestUpdate(ctx, magic2, 41); + EVP_DigestFinal_ex(ctx, md, &mdlen); + EVP_MD_CTX_free(ctx); + + /* + * Encode the value of 'Digest' as "S=" followed by + * 40 ASCII hexadecimal digits and return it in + * AuthenticatorResponse. + * For example, + * "S=0123456789ABCDEF0123456789ABCDEF01234567" + */ + ptr = auth_response; + *ptr++ = 'S'; + *ptr++ = '='; + for (i = 0; i < SHA_DIGEST_LENGTH; i++) { + *ptr++ = hex[md[i] >> 4]; + *ptr++ = hex[md[i] & 0x0f]; + } +} + +void +mschap_masterkey(u_int8_t *password_hash2, u_int8_t *ntresponse, + u_int8_t *masterkey) +{ + u_int8_t md[SHA_DIGEST_LENGTH]; + u_int mdlen; + EVP_MD_CTX *ctx; + static u_int8_t magic1[27] = { + 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d, + 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 + }; + + ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, EVP_sha1(), NULL); + EVP_DigestUpdate(ctx, password_hash2, MSCHAP_HASH_SZ); + EVP_DigestUpdate(ctx, ntresponse, 24); + EVP_DigestUpdate(ctx, magic1, 27); + EVP_DigestFinal_ex(ctx, md, &mdlen); + EVP_MD_CTX_free(ctx); + + memcpy(masterkey, md, 16); +} + +void +mschap_asymetric_startkey(u_int8_t *masterkey, u_int8_t *sessionkey, + int sessionkeylen, int issend, int isserver) +{ + EVP_MD_CTX *ctx; + u_int8_t md[SHA_DIGEST_LENGTH]; + u_int mdlen; + u_int8_t *s; + static u_int8_t magic2[84] = { + 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20, + 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79, + 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65, + 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20, + 0x6b, 0x65, 0x79, 0x2e + }; + static u_int8_t magic3[84] = { + 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20, + 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20, + 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, + 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, + 0x6b, 0x65, 0x79, 0x2e + }; + + if (issend) + s = isserver ? magic3 : magic2; + else + s = isserver ? magic2 : magic3; + + ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, EVP_sha1(), NULL); + EVP_DigestUpdate(ctx, masterkey, 16); + EVP_DigestUpdate(ctx, sha1_pad1, 40); + EVP_DigestUpdate(ctx, s, 84); + EVP_DigestUpdate(ctx, sha1_pad2, 40); + EVP_DigestFinal_ex(ctx, md, &mdlen); + EVP_MD_CTX_free(ctx); + + memcpy(sessionkey, md, sessionkeylen); +} + +void +mschap_msk(u_int8_t *password, int passwordlen, + u_int8_t *ntresponse, u_int8_t *msk) +{ + u_int8_t password_hash[MSCHAP_HASH_SZ]; + u_int8_t password_hash2[MSCHAP_HASH_SZ]; + u_int8_t masterkey[MSCHAP_MASTERKEY_SZ]; + u_int8_t sendkey[MSCHAP_MASTERKEY_SZ]; + u_int8_t recvkey[MSCHAP_MASTERKEY_SZ]; + + mschap_ntpassword_hash(password, passwordlen, password_hash); + mschap_ntpassword_hash(password_hash, MSCHAP_HASH_SZ, password_hash2); + + mschap_masterkey(password_hash2, ntresponse, masterkey); + mschap_asymetric_startkey(masterkey, recvkey, sizeof(recvkey), 0, 1); + mschap_asymetric_startkey(masterkey, sendkey, sizeof(sendkey), 1, 1); + + /* 16 bytes receive key + 16 bytes send key + 32 bytes 0 padding */ + bzero(msk, MSCHAP_MSK_SZ); + memcpy(msk, &recvkey, sizeof(recvkey)); + memcpy(msk + sizeof(recvkey), &sendkey, sizeof(sendkey)); +} + +void +mschap_radiuskey(u_int8_t *plain, const u_int8_t *encrypted, + const u_int8_t *authenticator, const u_int8_t *secret) +{ + EVP_MD_CTX *ctx; + u_int8_t b[MD5_DIGEST_LENGTH], p[32]; + u_int i, mdlen; + + ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, EVP_md5(), NULL); + EVP_DigestUpdate(ctx, secret, strlen(secret)); + EVP_DigestUpdate(ctx, authenticator, 16); + EVP_DigestUpdate(ctx, encrypted, 2); + EVP_DigestFinal_ex(ctx, b, &mdlen); + + for (i = 0; i < mdlen; i++) { + p[i] = b[i] ^ encrypted[i+2]; + } + + EVP_DigestInit_ex(ctx, EVP_md5(), NULL); + EVP_DigestUpdate(ctx, secret, strlen(secret)); + EVP_DigestUpdate(ctx, encrypted + 2, mdlen); + EVP_DigestFinal_ex(ctx, b, &mdlen); + EVP_MD_CTX_free(ctx); + + for (i = 0; i < mdlen; i++) { + p[i+16] = b[i] ^ encrypted[i+18]; + } + + memcpy(plain, p+1, 16); +} diff --git a/usr.sbin/radiusd/chap_ms.h b/usr.sbin/radiusd/chap_ms.h new file mode 100644 index 00000000000..b46a8862426 --- /dev/null +++ b/usr.sbin/radiusd/chap_ms.h @@ -0,0 +1,48 @@ +/* $OpenBSD: chap_ms.h,v 1.1 2024/07/14 13:44:30 yasuoka Exp $ */ +/* $vantronix: chap_ms.h,v 1.6 2010/05/19 09:37:00 reyk Exp $ */ + +/* + * Copyright (c) 2010 Reyk Floeter <reyk@vantronix.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _CHAP_MS_H +#define _CHAP_MS_H + +#define MSCHAP_CHALLENGE_SZ 8 +#define MSCHAPV2_CHALLENGE_SZ 16 +#define MSCHAP_HASH_SZ 16 +#define MSCHAP_MASTERKEY_SZ 16 +#define MSCHAP_MSK_KEY_SZ 32 +#define MSCHAP_MSK_PADDING_SZ 32 +#define MSCHAP_MSK_SZ 64 + +#define MSCHAP_MAXNTPASSWORD_SZ 255 /* unicode chars */ + +void mschap_nt_response(u_int8_t *, u_int8_t *, u_int8_t *, int, + u_int8_t *, int , u_int8_t *); +void mschap_auth_response(u_int8_t *, int, u_int8_t *, u_int8_t *, + u_int8_t *, u_int8_t *, int, u_int8_t *); + +void mschap_ntpassword_hash(u_int8_t *, int, u_int8_t *); +void mschap_challenge_hash(u_int8_t *, u_int8_t *, u_int8_t *, + int, u_int8_t *); + +void mschap_asymetric_startkey(u_int8_t *, u_int8_t *, int, int, int); +void mschap_masterkey(u_int8_t *, u_int8_t *, u_int8_t *); +void mschap_radiuskey(u_int8_t *, const u_int8_t *, const u_int8_t *, + const u_int8_t *); +void mschap_msk(u_int8_t *, int, u_int8_t *, u_int8_t *); + +#endif /* _CHAP_MS_H */ diff --git a/usr.sbin/radiusd/parse.y b/usr.sbin/radiusd/parse.y index c1aac39f444..b8dc055eef1 100644 --- a/usr.sbin/radiusd/parse.y +++ b/usr.sbin/radiusd/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.23 2024/07/13 13:06:47 yasuoka Exp $ */ +/* $OpenBSD: parse.y,v 1.24 2024/07/14 13:44:30 yasuoka Exp $ */ /* * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org> @@ -1010,6 +1010,7 @@ default_module_path(const char *name) const char *path; } module_paths[] = { { "bsdauth", "/usr/libexec/radiusd/radiusd_bsdauth" }, + { "file", "/usr/libexec/radiusd/radiusd_file" }, { "ipcp", "/usr/libexec/radiusd/radiusd_ipcp" }, { "radius", "/usr/libexec/radiusd/radiusd_radius" }, { "standard", "/usr/libexec/radiusd/radiusd_standard" } diff --git a/usr.sbin/radiusd/radiusd_file.c b/usr.sbin/radiusd/radiusd_file.c new file mode 100644 index 00000000000..562c0dacdcf --- /dev/null +++ b/usr.sbin/radiusd/radiusd_file.c @@ -0,0 +1,586 @@ +/* $OpenBSD: radiusd_file.c,v 1.1 2024/07/14 13:44:30 yasuoka Exp $ */ + +/* + * Copyright (c) 2024 YASUOKA Masahiko <yasuoka@yasuoka.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/cdefs.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <netinet/in.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <imsg.h> +#include <limits.h> +#include <md5.h> +#include <radius.h> +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "chap_ms.h" +#include "imsg_subr.h" +#include "log.h" +#include "radiusd.h" +#include "radiusd_module.h" + +struct module_file_params { + int debug; + char path[PATH_MAX]; +}; + +struct module_file { + struct module_base *base; + struct imsgbuf ibuf; + struct module_file_params + params; +}; + +struct module_file_userinfo { + struct in_addr frame_ip_address; + char password[0]; +}; + +/* IPC between priv and main */ +enum { + IMSG_RADIUSD_FILE_OK = 1000, + IMSG_RADIUSD_FILE_NG, + IMSG_RADIUSD_FILE_PARAMS, + IMSG_RADIUSD_FILE_USERINFO +}; + +static void parent_dispatch_main(struct module_file_params *, + struct imsgbuf *, struct imsg *); +static void module_file_main(void) __dead; +static pid_t start_child(char *, int); +static void module_file_config_set(void *, const char *, int, + char * const *); +static void module_file_start(void *); +static void module_file_access_request(void *, u_int, const u_char *, + size_t); +static void auth_pap(struct module_file *, u_int, RADIUS_PACKET *, char *, + struct module_file_userinfo *); +static void auth_md5chap(struct module_file *, u_int, RADIUS_PACKET *, + char *, struct module_file_userinfo *); +static void auth_mschapv2(struct module_file *, u_int, RADIUS_PACKET *, + char *, struct module_file_userinfo *); + +static struct module_handlers module_file_handlers = { + .access_request = module_file_access_request, + .config_set = module_file_config_set, + .start = module_file_start +}; + +int +main(int argc, char *argv[]) +{ + int ch, pairsock[2], status; + pid_t pid; + char *saved_argv0; + struct imsgbuf ibuf; + struct imsg imsg; + ssize_t n; + size_t datalen; + struct module_file_params *paramsp, params; + + while ((ch = getopt(argc, argv, "M")) != -1) + switch (ch) { + case 'M': + module_file_main(); + /* not reached */ + break; + } + saved_argv0 = argv[0]; + + argc -= optind; + argv += optind; + + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, PF_UNSPEC, + pairsock) == -1) + err(EXIT_FAILURE, "socketpair"); + + log_init(0); + + pid = start_child(saved_argv0, pairsock[1]); + + /* Privileged process */ + setproctitle("[priv]"); + imsg_init(&ibuf, pairsock[0]); + + if (imsg_sync_read(&ibuf, 2000) <= 0 || + (n = imsg_get(&ibuf, &imsg)) <= 0) + exit(EXIT_FAILURE); + if (imsg.hdr.type != IMSG_RADIUSD_FILE_PARAMS) + err(EXIT_FAILURE, "Receieved unknown message type %d", + imsg.hdr.type); + datalen = imsg.hdr.len - IMSG_HEADER_SIZE; + if (datalen < sizeof(params)) + err(EXIT_FAILURE, "Receieved IMSG_RADIUSD_FILE_PARAMS " + "message is wrong size"); + paramsp = imsg.data; + if (paramsp->path[0] != '\0') { + if (unveil(paramsp->path, "r") == -1) + err(EXIT_FAILURE, "unveil"); + } + if (paramsp->debug) + log_init(1); + + if (unveil(NULL, NULL) == -1) + err(EXIT_FAILURE, "unveil"); + if (pledge("stdio rpath", NULL) == -1) + err(EXIT_FAILURE, "pledge"); + + memcpy(¶ms, paramsp, sizeof(params)); + + for (;;) { + if ((n = imsg_read(&ibuf)) <= 0 && errno != EAGAIN) + break; + for (;;) { + if ((n = imsg_get(&ibuf, &imsg)) == -1) + break; + if (n == 0) + break; + parent_dispatch_main(¶ms, &ibuf, &imsg); + imsg_free(&imsg); + imsg_flush(&ibuf); + } + imsg_flush(&ibuf); + } + imsg_clear(&ibuf); + + while (waitpid(pid, &status, 0) == -1) { + if (errno != EINTR) + break; + } + exit(WEXITSTATUS(status)); +} + +void +parent_dispatch_main(struct module_file_params *params, struct imsgbuf *ibuf, + struct imsg *imsg) +{ + size_t datalen, entsz, passz; + const char *username; + char *buf, *db[2], *str; + int ret; + struct module_file_userinfo *ent; + + datalen = imsg->hdr.len - IMSG_HEADER_SIZE; + switch (imsg->hdr.type) { + case IMSG_RADIUSD_FILE_USERINFO: + if (datalen == 0 || + *((char *)imsg->data + datalen - 1) != '\0') { + log_warn("%s: received IMSG_RADIUSD_FILE_USERINFO " + "is wrong", __func__); + goto on_error; + } + username = imsg->data; + db[0] = params->path; + db[1] = NULL; + if ((ret = cgetent(&buf, db, username)) < 0) { + log_info("user `%s' is not configured", username); + goto on_error; + } + if ((ret = cgetstr(buf, "password", &str)) < 0) { + log_info("password for `%s' is not configured", + username); + goto on_error; + } + passz = strlen(str) + 1; + entsz = offsetof(struct module_file_userinfo, password[passz]); + if ((ent = calloc(1, entsz)) == NULL) { + log_warn("%s; calloc", __func__); + goto on_error; + } + strlcpy(ent->password, str, passz); + imsg_compose(ibuf, IMSG_RADIUSD_FILE_USERINFO, 0, -1, -1, + ent, entsz); + freezero(ent, entsz); + break; + } + return; + on_error: + imsg_compose(ibuf, IMSG_RADIUSD_FILE_NG, 0, -1, -1, NULL, 0); +} + +/* main process */ +void +module_file_main(void) +{ + struct module_file module_file; + + setproctitle("[main]"); + + memset(&module_file, 0, sizeof(module_file)); + if ((module_file.base = module_create(STDIN_FILENO, &module_file, + &module_file_handlers)) == NULL) + err(1, "Could not create a module instance"); + + module_drop_privilege(module_file.base, 0); + + module_load(module_file.base); + imsg_init(&module_file.ibuf, 3); + + if (pledge("stdio", NULL) == -1) + err(EXIT_FAILURE, "pledge"); + while (module_run(module_file.base) == 0) + ; + + module_destroy(module_file.base); + + exit(0); +} + +pid_t +start_child(char *argv0, int fd) +{ + char *argv[5]; + int argc = 0; + pid_t pid; + + switch (pid = fork()) { + case -1: + fatal("cannot fork"); + case 0: + break; + default: + close(fd); + return (pid); + } + + if (fd != 3) { + if (dup2(fd, 3) == -1) + fatal("cannot setup imsg fd"); + } else if (fcntl(fd, F_SETFD, 0) == -1) + fatal("cannot setup imsg fd"); + + argv[argc++] = argv0; + argv[argc++] = "-M"; /* main proc */ + argv[argc++] = NULL; + execvp(argv0, argv); + fatal("execvp"); +} + +void +module_file_config_set(void *ctx, const char *name, int valc, + char * const * valv) +{ + struct module_file *module = ctx; + char *errmsg; + + if (strcmp(name, "path") == 0) { + SYNTAX_ASSERT(valc == 1, "`path' must have a argument"); + if (strlcpy(module->params.path, valv[0], sizeof( + module->params.path)) >= sizeof(module->params.path)) { + module_send_message(module->base, IMSG_NG, + "`path' is too long"); + return; + } + module_send_message(module->base, IMSG_OK, NULL); + } else if (strcmp(name, "_debug") == 0) { + log_init(1); + module->params.debug = 1; + module_send_message(module->base, IMSG_OK, NULL); + } else if (strncmp(name, "_", 1) == 0) + /* ignore all internal messages */ + module_send_message(module->base, IMSG_OK, NULL); + else + module_send_message(module->base, IMSG_NG, + "Unknown config parameter `%s'", name); + return; + syntax_error: + module_send_message(module->base, IMSG_NG, "%s", errmsg); + return; +} + +void +module_file_start(void *ctx) +{ + struct module_file *module = ctx; + + if (module->params.path[0] == '\0') { + module_send_message(module->base, IMSG_NG, + "`path' is not configured"); + return; + } + imsg_compose(&module->ibuf, IMSG_RADIUSD_FILE_PARAMS, 0, -1, -1, + &module->params, sizeof(module->params)); + imsg_flush(&module->ibuf); + + module_send_message(module->base, IMSG_OK, NULL); +} + +void +module_file_access_request(void *ctx, u_int query_id, const u_char *pkt, + size_t pktlen) +{ + size_t datalen; + struct module_file *self = ctx; + RADIUS_PACKET *radpkt = NULL; + char username[256]; + ssize_t n; + struct imsg imsg; + struct module_file_userinfo *ent; + + memset(&imsg, 0, sizeof(imsg)); + + if ((radpkt = radius_convert_packet(pkt, pktlen)) == NULL) { + log_warn("%s: radius_convert_packet()", __func__); + goto on_error; + } + radius_get_string_attr(radpkt, RADIUS_TYPE_USER_NAME, username, + sizeof(username)); + + imsg_compose(&self->ibuf, IMSG_RADIUSD_FILE_USERINFO, 0, -1, -1, + username, strlen(username) + 1); + imsg_flush(&self->ibuf); + if ((n = imsg_read(&self->ibuf)) == -1 || n == 0) { + log_warn("%s: imsg_read()", __func__); + goto on_error; + } + if ((n = imsg_get(&self->ibuf, &imsg)) <= 0) { + log_warn("%s: imsg_get()", __func__); + goto on_error; + } + + datalen = imsg.hdr.len - IMSG_HEADER_SIZE; + if (imsg.hdr.type == IMSG_RADIUSD_FILE_USERINFO) { + if (datalen <= offsetof(struct module_file_userinfo, + password[0])) { + log_warn("%s: received IMSG_RADIUSD_FILE_USERINFO is " + "invalid", __func__); + goto on_error; + } + ent = imsg.data; + } else + goto on_error; + + if (radius_has_attr(radpkt, RADIUS_TYPE_USER_PASSWORD)) + auth_pap(self, query_id, radpkt, username, ent); + else if (radius_has_attr(radpkt, RADIUS_TYPE_CHAP_PASSWORD)) + auth_md5chap(self, query_id, radpkt, username, ent); + else if (radius_has_vs_attr(radpkt, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MS_CHAP2_RESPONSE)) + auth_mschapv2(self, query_id, radpkt, username, ent); + else { + log_info("q=%u unsupported authentication methods", query_id); + explicit_bzero(ent->password, strlen(ent->password)); + } + on_error: + if (radpkt != NULL) + radius_delete_packet(radpkt); + imsg_free(&imsg); + return; +} + +void +auth_pap(struct module_file *self, u_int q_id, RADIUS_PACKET *radpkt, + char *username, struct module_file_userinfo *ent) +{ + RADIUS_PACKET *respkt = NULL; + char pass[256]; + int ret; + + if (radius_get_string_attr(radpkt, RADIUS_TYPE_USER_PASSWORD, pass, + sizeof(pass)) != 0) { + log_warnx("%s: radius_get_string_attr", __func__); + return; + } + ret = strcmp(ent->password, pass); + log_info("%s %s", ent->password, pass); + explicit_bzero(ent->password, strlen(ent->password)); + log_info("q=%u User `%s' authentication %s (PAP)", q_id, username, + (ret == 0)? "succeeded" : "failed"); + if ((respkt = radius_new_response_packet((ret == 0)? + RADIUS_CODE_ACCESS_ACCEPT : RADIUS_CODE_ACCESS_REJECT, radpkt)) + == NULL) { + log_warn("%s: radius_new_response_packet()", __func__); + return; + } + module_accsreq_answer(self->base, q_id, + radius_get_data(respkt), radius_get_length(respkt)); + radius_delete_packet(respkt); +} + +void +auth_md5chap(struct module_file *self, u_int q_id, RADIUS_PACKET *radpkt, + char *username, struct module_file_userinfo *ent) +{ + RADIUS_PACKET *respkt = NULL; + size_t attrlen, challlen; + u_char chall[256], idpass[17], digest[16]; + int ret; + MD5_CTX md5; + + attrlen = sizeof(idpass); + if (radius_get_raw_attr(radpkt, RADIUS_TYPE_CHAP_PASSWORD, idpass, + &attrlen) != 0) { + log_warnx("%s: radius_get_string_attr", __func__); + return; + } + challlen = sizeof(chall); + if (radius_get_raw_attr(radpkt, RADIUS_TYPE_CHAP_CHALLENGE, chall, + &challlen) != 0) { + log_warnx("%s: radius_get_string_attr", __func__); + return; + } + MD5Init(&md5); + MD5Update(&md5, idpass, 1); + MD5Update(&md5, ent->password, strlen(ent->password)); + MD5Update(&md5, chall, challlen); + MD5Final(digest, &md5); + + ret = timingsafe_bcmp(idpass + 1, digest, sizeof(digest)); + log_info("q=%u User `%s' authentication %s (CHAP)", q_id, username, + (ret == 0)? "succeeded" : "failed"); + if ((respkt = radius_new_response_packet((ret == 0)? + RADIUS_CODE_ACCESS_ACCEPT : RADIUS_CODE_ACCESS_REJECT, radpkt)) + == NULL) { + log_warn("%s: radius_new_response_packet()", __func__); + return; + } + module_accsreq_answer(self->base, q_id, + radius_get_data(respkt), radius_get_length(respkt)); + radius_delete_packet(respkt); +} + +void +auth_mschapv2(struct module_file *self, u_int q_id, RADIUS_PACKET *radpkt, + char *username, struct module_file_userinfo *ent) +{ + RADIUS_PACKET *respkt = NULL; + size_t attrlen; + int i, lpass; + char *pass = NULL; + uint8_t chall[MSCHAPV2_CHALLENGE_SZ]; + uint8_t ntresponse[24], authenticator[16]; + uint8_t pwhash[16], pwhash2[16], master[64]; + struct { + uint8_t salt[2]; + uint8_t len; + uint8_t key[16]; + uint8_t pad[15]; + } __packed rcvkey, sndkey; + struct { + uint8_t ident; + uint8_t flags; + uint8_t peerchall[16]; + uint8_t reserved[8]; + uint8_t ntresponse[24]; + } __packed resp; + struct authresp { + uint8_t ident; + uint8_t authresp[42]; + } __packed authresp; + + + attrlen = sizeof(chall); + if (radius_get_vs_raw_attr(radpkt, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MS_CHAP_CHALLENGE, chall, &attrlen) != 0) { + log_info("q=%u failed to retribute MS-CHAP-Challenge", q_id); + goto on_error; + } + attrlen = sizeof(resp); + if (radius_get_vs_raw_attr(radpkt, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MS_CHAP2_RESPONSE, &resp, &attrlen) != 0) { + log_info("q=%u failed to retribute MS-CHAP2-Response", q_id); + goto on_error; + } + + /* convert the password to UTF16-LE */ + lpass = strlen(ent->password); + if ((pass = calloc(1, lpass * 2)) == NULL) { + log_warn("%s: calloc()", __func__); + goto on_error; + } + for (i = 0; i < lpass; i++) { + pass[i * 2] = ent->password[i]; + pass[i * 2 + 1] = '\0'; + } + + /* calculate NT-Response by the password */ + mschap_nt_response(chall, resp.peerchall, + username, strlen(username), pass, lpass * 2, ntresponse); + + if (timingsafe_bcmp(ntresponse, resp.ntresponse, 24) != 0) { + log_info("q=%u User `%s' authentication failed (MSCHAPv2)", + q_id, username); + if ((respkt = radius_new_response_packet( + RADIUS_CODE_ACCESS_REJECT, radpkt)) == NULL) { + log_warn("%s: radius_new_response_packet()", __func__); + goto on_error; + } + authresp.ident = resp.ident; + strlcpy(authresp.authresp, "E=691 R=0 V=3", + sizeof(authresp.authresp)); + radius_put_vs_raw_attr(respkt, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MS_CHAP_ERROR, &authresp, + offsetof(struct authresp, authresp[13])); + } else { + log_info("q=%u User `%s' authentication succeeded (MSCHAPv2)", + q_id, username); + if ((respkt = radius_new_response_packet( + RADIUS_CODE_ACCESS_ACCEPT, radpkt)) == NULL) { + log_warn("%s: radius_new_response_packet()", __func__); + goto on_error; + } + mschap_auth_response(pass, lpass * 2, ntresponse, chall, + resp.peerchall, username, strlen(username), + authresp.authresp); + authresp.ident = resp.ident; + + radius_put_vs_raw_attr(respkt, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MS_CHAP2_SUCCESS, &authresp, + offsetof(struct authresp, authresp[42])); + + mschap_ntpassword_hash(pass, lpass * 2, pwhash); + mschap_ntpassword_hash(pwhash, sizeof(pwhash), pwhash2); + mschap_masterkey(pwhash2, ntresponse, master); + radius_get_authenticator(radpkt, authenticator); + + /* MS-MPPE-Recv-Key */ + memset(&rcvkey, 0, sizeof(rcvkey)); + arc4random_buf(rcvkey.salt, sizeof(rcvkey.salt)); + rcvkey.salt[0] |= 0x80; + mschap_asymetric_startkey(master, rcvkey.key, 16, 0, 1); + radius_put_vs_raw_attr(respkt, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MPPE_RECV_KEY, &rcvkey, sizeof(rcvkey)); + + /* MS-MPPE-Send-Key */ + memset(&sndkey, 0, sizeof(sndkey)); + arc4random_buf(sndkey.salt, sizeof(sndkey.salt)); + sndkey.salt[0] |= 0x80; + mschap_asymetric_startkey(master, sndkey.key, 16, 1, 1); + radius_put_vs_raw_attr(respkt, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MPPE_SEND_KEY, &sndkey, sizeof(sndkey)); + } + + module_accsreq_answer(self->base, q_id, + radius_get_data(respkt), radius_get_length(respkt)); + on_error: + /* bzero password */ + explicit_bzero(ent->password, strlen(ent->password)); + if (pass != NULL) + explicit_bzero(pass, lpass * 2); + free(pass); + if (respkt != NULL) + radius_delete_packet(respkt); +} diff --git a/usr.sbin/radiusd/radiusd_file/Makefile b/usr.sbin/radiusd/radiusd_file/Makefile new file mode 100644 index 00000000000..aa248fc38f5 --- /dev/null +++ b/usr.sbin/radiusd/radiusd_file/Makefile @@ -0,0 +1,12 @@ +# $OpenBSD: Makefile,v 1.1 2024/07/14 13:44:30 yasuoka Exp $ + +PROG= radiusd_file +BINDIR= /usr/libexec/radiusd +SRCS= radiusd_file.c radiusd_module.c imsg_subr.c log.c chap_ms.c +#SRCS+= radius_subr.c +LDADD+= -lradius -lcrypto -lutil +DPADD+= ${LIBRADIUS} ${LIBCRYPTO} ${LIBUTIL} +#MAN= radiusd_file.8 +NOMAN= # + +.include <bsd.prog.mk> |