--- /dev/null
-#include <freeradius-devel/rad_assert.h>
+ /*
+ * $Id$
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * Copyright 2001,2002 Google, Inc.
+ * Copyright 2005,2006 TRI-D Systems, Inc.
+ */
+
+ static const char rcsid[] = "$Id$";
+
+ /* avoid inclusion of these FR headers which conflict w/ OpenSSL */
+ #define _LRAD_MD4_H
+ #define _LRAD_SHA1_H
+
- char *username = request->username->vp_strvalue;
++#include <rad_assert.h>
+
+ #include "extern.h"
+ #include "otp.h"
+ #include "otp_mppe.h"
+
+ #include <openssl/des.h>
+ #include <openssl/md4.h>
+ #include <openssl/md5.h>
+ #include <openssl/sha.h>
+
+ #include <string.h>
+
+ /*
+ * Add MPPE attributes to a request, if required.
+ */
+ void
+ otp_mppe(REQUEST *request, otp_pwe_t pwe, const otp_option_t *opt,
+ const char *passcode)
+ {
+ VALUE_PAIR **avp = &request->reply->vps;
+ VALUE_PAIR *cvp, *rvp, *vp;
+
+ cvp = pairfind(request->packet->vps, pwattr[pwe - 1]);
+ rvp = pairfind(request->packet->vps, pwattr[pwe]);
+
+ switch (pwe) {
+ case PWE_PAP:
+ case PWE_CHAP:
+ return;
+
+ case PWE_MSCHAP:
+ /* First, set some related attributes. */
+ vp = pairmake("MS-MPPE-Encryption-Policy",
+ otp_mppe_policy[opt->mschap_mppe_policy], T_OP_EQ);
+ rad_assert(vp != NULL);
+ pairadd(avp, vp);
+ vp = pairmake("MS-MPPE-Encryption-Types",
+ otp_mppe_types[opt->mschap_mppe_types], T_OP_EQ);
+ rad_assert(vp != NULL);
+ pairadd(avp, vp);
+
+ /* If no MPPE, we're done. */
+ if (!opt->mschap_mppe_policy)
+ return;
+
+ /*
+ * Generate the MS-CHAP-MPPE-Keys attribute. This is not specified
+ * anywhere -- RFC 2548, par. 2.4.1 is the authority but it has
+ * typos and omissions that make this unimplementable. The
+ * code here is based on experimental results provided by
+ * Takahiro Wagatsuma <waga@sic.shibaura-it.ac.jp>.
+ * We only support 128-bit keys derived from the NT hash; 40-bit
+ * and 56-bit keys are derived from the LM hash, which besides
+ * being deprecated, has severe security problems.
+ */
+ {
+ size_t i, passcode_len;
+ unsigned char password_unicode[2 * OTP_MAX_PASSCODE_LEN];
+ unsigned char password_md[MD4_DIGEST_LENGTH];
+ unsigned char mppe_keys[32];
+ /* 0x ASCII(mppe_keys) '\0' */
+ char mppe_keys_string[2 + (2 * sizeof(mppe_keys)) + 1];
+
+ /* Zero the LM-Key sub-field (and padding). */
+ (void) memset(mppe_keys, 0, sizeof(mppe_keys));
+
+ /*
+ * The NT-Key sub-field is MD4(MD4(unicode(password))).
+ * Start by hashing the unicode passcode.
+ * This is broken because unicode chars are machine-ordered,
+ * but the spec (RFC 2433) doesn't say how to prepare
+ * the password for md4 (other than by example values).
+ */
+ passcode_len = strlen(passcode);
+ for (i = 0; i < passcode_len; ++i) {
+ /* Set the high order 8 bits to 0 (little-endian) */
+ password_unicode[i * 2] = *passcode++;
+ password_unicode[i * 2 + 1] = 0;
+ }
+ /* first md4 */
+ (void) MD4(password_unicode, 2 * passcode_len, password_md);
+ /* second md4 */
+ (void) MD4(password_md, MD4_DIGEST_LENGTH, &mppe_keys[8]);
+
+ #if 0 /* encoding now handled in lib/radius.c:rad_pwencode() */
+ {
+ unsigned char md5_md[MD5_DIGEST_LENGTH];
+ unsigned char encode_buf[AUTH_VECTOR_LEN + MAX_STRING_LEN];
+ int secretlen;
+
+ /* Now we must encode the key as User-Password is encoded. */
+ secretlen = strlen(request->secret);
+ (void) memcpy(encode_buf, request->secret, secretlen);
+ (void) memcpy(encode_buf + secretlen, request->packet->vector,
+ AUTH_VECTOR_LEN);
+ (void) MD5(encode_buf, secretlen + AUTH_VECTOR_LEN, md5_md);
+ for (i = 0; i < 16; ++i)
+ mppe_keys[i] ^= md5_md[i];
+ (void) memcpy(encode_buf + secretlen, mppe_keys, MD5_DIGEST_LENGTH);
+ (void) MD5(encode_buf, secretlen + MD5_DIGEST_LENGTH, md5_md);
+ for (i = 0; i < 16; ++i)
+ mppe_keys[i + 16] ^= md5_md[i];
+ }
+ #endif /* 0 */
+
+ /* Whew. Now stringify it for pairmake(). */
+ mppe_keys_string[0] = '0';
+ mppe_keys_string[1] = 'x';
+ for (i = 0; i < 32; ++i)
+ (void) sprintf(&mppe_keys_string[i*2+2], "%02X", mppe_keys[i]);
+ vp = pairmake("MS-CHAP-MPPE-Keys", mppe_keys_string, T_OP_EQ);
+ rad_assert(vp != NULL);
+ pairadd(avp, vp);
+ } /* (doing mppe) */
+ break; /* PWE_MSCHAP */
+
+ case PWE_MSCHAP2:
+ {
+ size_t i;
+ unsigned char password_md_md[MD4_DIGEST_LENGTH];
+
+ /*
+ * MS-CHAPv2 requires mutual authentication; we must prove
+ * that we know the secret. This is a bit circuitous: set
+ * MD1 = SHA(MD4(MD4(unicode(password)))|NT_RESPONSE|MAGIC1),
+ * MD2 = MSB8(SHA(PEER_CHALLENGE|MS_CHAP_CHALLENGE|USERNAME)),
+ * and finally use SHA(MD1|MD2|MAGIC2) as the authenticator.
+ * The authenticator is returned as the string "S=<auth>",
+ * <auth> is the authenticator expressed as [uppercase] ASCII.
+ * See RFC 2759.
+ */
+ {
+ size_t passcode_len;
+ unsigned char password_unicode[2 * OTP_MAX_PASSCODE_LEN];
+ unsigned char password_md[MD4_DIGEST_LENGTH];
+
+ SHA_CTX ctx;
+ unsigned char md1[SHA_DIGEST_LENGTH];
+ unsigned char md2[SHA_DIGEST_LENGTH];
+ unsigned char auth_md[SHA_DIGEST_LENGTH];
+ /* S= ( ASCII(auth_md) ) \0 */
+ char auth_md_string[2 + (2 * sizeof(auth_md)) + 1];
+ /*
+ * ugh. The ASCII authenticator (auth_md_string) is sent
+ * along with a single (useless) binary byte (the ID).
+ * So we must "stringify" it again (for pairmake()) since the
+ * binary byte requires the attribute to be of type "octets".
+ */
+ /* 0x (ID) ( ASCII("S="ASCII(auth_md))) */
+ char auth_octet_string[2 + 2 + (2 * sizeof(auth_md_string))];
+
- SHA1_Update(&ctx, rvp->vp_strvalue + 26, 24);
++ char *username = request->username->strvalue;
+ int username_len = request->username->length;
+
+ /* "Magic server to client signing constant" */
+ unsigned char 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 };
+ /* "Pad to make it do more than one iteration" */
+ unsigned char 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 };
+
+ /*
+ * Start by hashing the unicode passcode.
+ * This is broken because unicode chars are machine-ordered,
+ * but the spec (RFC 2759) doesn't say how to prepare
+ * the password for md4 (other than by example values).
+ */
+ passcode_len = strlen(passcode);
+ for (i = 0; i < passcode_len; ++i) {
+ /* Set the high order 8 bits to 0 (little-endian) */
+ password_unicode[i * 2] = *passcode++;
+ password_unicode[i * 2 + 1] = 0;
+ }
+ /* first md4 */
+ (void) MD4(password_unicode, 2 * passcode_len, password_md);
+ /* second md4 */
+ (void) MD4(password_md, MD4_DIGEST_LENGTH, password_md_md);
+
+ /* MD1 */
+ SHA1_Init(&ctx);
+ SHA1_Update(&ctx, password_md_md, MD4_DIGEST_LENGTH);
- SHA1_Update(&ctx, rvp->vp_strvalue + 2, 16);
- SHA1_Update(&ctx, cvp->vp_strvalue, 16);
++ SHA1_Update(&ctx, rvp->strvalue + 26, 24);
+ SHA1_Update(&ctx, magic1, sizeof(magic1));
+ SHA1_Final(md1, &ctx);
+
+ /* MD2 */
+ SHA1_Init(&ctx);
- (void) sprintf(&auth_octet_string[2], "%02X", rvp->vp_strvalue[0]);
++ SHA1_Update(&ctx, rvp->strvalue + 2, 16);
++ SHA1_Update(&ctx, cvp->strvalue, 16);
+ SHA1_Update(&ctx, username, username_len);
+ SHA1_Final(md2, &ctx);
+
+ /* The Authenticator */
+ SHA1_Init(&ctx);
+ SHA1_Update(&ctx, md1, SHA_DIGEST_LENGTH);
+ SHA1_Update(&ctx, md2, 8);
+ SHA1_Update(&ctx, magic2, sizeof(magic2));
+ SHA1_Final(auth_md, &ctx);
+
+ /* String conversion. */
+ auth_md_string[0] = 'S';
+ auth_md_string[1] = '=';
+ for (i = 0; i < sizeof(auth_md); ++i)
+ (void) sprintf(&auth_md_string[i * 2 + 2], "%02X", auth_md[i]);
+
+ /* And then octet conversion. Ugh! */
+ auth_octet_string[0] = '0';
+ auth_octet_string[1] = 'x';
- SHA1_Update(&ctx, rvp->vp_strvalue + 26, 24);
++ (void) sprintf(&auth_octet_string[2], "%02X", rvp->strvalue[0]);
+ for (i = 0; i < sizeof(auth_md_string) - 1; ++i)
+ (void) sprintf(&auth_octet_string[i * 2 +4], "%02X", auth_md_string[i]);
+
+ vp = pairmake("MS-CHAP2-Success", auth_octet_string, T_OP_EQ);
+ rad_assert(vp != NULL);
+ pairadd(avp, vp);
+ } /* Generate mutual auth info. */
+
+ /*
+ * Now, set some MPPE related attributes.
+ */
+ vp = pairmake("MS-MPPE-Encryption-Policy",
+ otp_mppe_policy[opt->mschapv2_mppe_policy], T_OP_EQ);
+ rad_assert(vp != NULL);
+ pairadd(avp, vp);
+ vp = pairmake("MS-MPPE-Encryption-Types",
+ otp_mppe_types[opt->mschapv2_mppe_types], T_OP_EQ);
+ rad_assert(vp != NULL);
+ pairadd(avp, vp);
+
+ /* If no MPPE, we're done. */
+ if (!opt->mschapv2_mppe_policy)
+ return;
+
+ /*
+ * Generate the MPPE initial session key, per RFC 3079.
+ * (Although, RFC 2548 leaves us guessing at how to generate this.)
+ * For MS-CHAPv2 we support all key lengths (40-, 56- and 128-bit),
+ * although MPPE via RADIUS supports only 40- and 128-bit keys.
+ * This is a bit more complicated than MS-CHAP. Start by generating
+ * a "master session key"
+ * MSB16(SHA(NTPasswordHashHash|NT_RESPONSE|MAGIC1)), where
+ * NTPasswordHashHash is MD4(MD4(unicode(password))), NT_RESPONSE
+ * is from the MS-CHAP2-Response attribute, and MAGIC1 is a
+ * constant from RFC 3079. Then, we derive asymmetric send/receive
+ * keys from the master session key. The "master send key" is
+ * MSBx(SHA(MASTERKEY|SHSPAD1|MAGIC3|SHSPAD2)),
+ * and the "master receive key" is
+ * MSBx(SHA(MASTERKEY|SHSPAD1|MAGIC2|SHSPAD2)), where
+ * MASTERKEY is the "master session key" generated above, and the
+ * other values are constants from RFC 3079. MSBx is the x-most
+ * significant bytes, where x is 5, 7, or 16 as appropriate for
+ * the desired key length. We always generate 16 byte (128-bit)
+ * keys, the NAS is required to truncate as needed.
+ */
+ {
+ /* These constants and key vars are named from RFC 3079. */
+ /* "This is the MPPE Master Key" */
+ unsigned char 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 };
+ /* "On the client side, this is the send key; "
+ "on the server side, it is the receive key." */
+ unsigned char 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 };
+ /* "On the client side, this is the receive key; "
+ "on the server side, it is the send key." */
+ unsigned char 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 };
+ unsigned char SHSpad1[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 };
+ unsigned char SHSpad2[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 };
+ unsigned char MasterKey[16];
+ unsigned char MasterSendKey[16];
+ unsigned char MasterReceiveKey[16];
+
+ SHA_CTX ctx;
+ unsigned char sha_md[SHA_DIGEST_LENGTH];
+ #if 0 /* salting/encoding now handled in lib/radius.c:tunnel_pwencode() */
+ unsigned char md5_md[MD5_DIGEST_LENGTH];
+
+ /* From RFC 2548: S R A */
+ unsigned char encode_buf[MAX_STRING_LEN + AUTH_VECTOR_LEN + 2];
+ int secretlen;
+
+ /* A useless value required by RFC 2548. */
+ unsigned char salt[2];
+ unsigned char mppe_key[32]; /* 1 + 16 + padding */
+ /* 0x ( ASCII(salt) ) */
+ unsigned char mppe_key_string[2 + (2 * sizeof(salt)) +
+ /* ( ASCII(mppe_key) ) \0 */
+ (2 * sizeof(mppe_key)) + 1];
+ #else /* 0 */
+ /* 0x ( ASCII(mppe_key) ) \0 */
+ unsigned char mppe_key_string[2 + (2 * sizeof(MasterKey)) + 1];
+ #endif /* else !0 */
+
+ /* Generate the master session key. */
+ SHA1_Init(&ctx);
+ SHA1_Update(&ctx, password_md_md, MD4_DIGEST_LENGTH);
++ SHA1_Update(&ctx, rvp->strvalue + 26, 24);
+ SHA1_Update(&ctx, Magic1, sizeof(Magic1));
+ SHA1_Final(sha_md, &ctx);
+ (void) memcpy(MasterKey, sha_md, 16);
+
+ /* Generate the master send key. */
+ SHA1_Init(&ctx);
+ SHA1_Update(&ctx, MasterKey, 16);
+ SHA1_Update(&ctx, SHSpad1, 40);
+ SHA1_Update(&ctx, Magic3, sizeof(Magic3));
+ SHA1_Update(&ctx, SHSpad2, 40);
+ SHA1_Final(sha_md, &ctx);
+ (void) memcpy(MasterSendKey, sha_md, 16);
+
+ /* Generate the master receive key. */
+ SHA1_Init(&ctx);
+ SHA1_Update(&ctx, MasterKey, 16);
+ SHA1_Update(&ctx, SHSpad1, 40);
+ SHA1_Update(&ctx, Magic2, sizeof(Magic3));
+ SHA1_Update(&ctx, SHSpad2, 40);
+ SHA1_Final(sha_md, &ctx);
+ (void) memcpy(MasterReceiveKey, sha_md, 16);
+
+ /*
+ * Now, generate the MS-MPPE-Send-Key attribute.
+ */
+ #if 0
+ /* Setup the salt value. */
+ salt[0] = 0x80;
+ salt[1] = 0x01;
+
+ /* Encode the key. */
+ (void) memset(mppe_key, 0, sizeof(mppe_key));
+ mppe_key[0] = 16; /* length */
+ (void) memcpy(&mppe_key[1], MasterSendKey, 16);
+ secretlen = strlen(request->secret);
+ (void) memcpy(encode_buf, request->secret, secretlen);
+ (void) memcpy(encode_buf + secretlen, request->packet->vector,
+ AUTH_VECTOR_LEN);
+ (void) memcpy(encode_buf + secretlen + 16, salt, 2);
+ (void) MD5(encode_buf, secretlen + AUTH_VECTOR_LEN + 2, md5_md);
+ for (i = 0; i < 16; ++i)
+ mppe_key[i] ^= md5_md[i];
+ (void) memcpy(encode_buf + secretlen, mppe_key, 16);
+ (void) MD5(encode_buf, secretlen + 16, md5_md);
+ for (i = 0; i < 16; ++i)
+ mppe_key[i + 16] ^= md5_md[i];
+
+ /* Whew. Now stringify it for pairmake(). */
+ mppe_key_string[0] = '0';
+ mppe_key_string[1] = 'x';
+ (void) sprintf(&mppe_key_string[2], "%02X", salt[0]);
+ (void) sprintf(&mppe_key_string[4], "%02X", salt[1]);
+ for (i = 0; i < sizeof(mppe_key); ++i)
+ (void) sprintf(&mppe_key_string[i*2+6], "%02X", mppe_key[i]);
+ #else /* 0 */
+ mppe_key_string[0] = '0';
+ mppe_key_string[1] = 'x';
+ for (i = 0; i < sizeof(MasterSendKey); ++i)
+ (void) sprintf(&mppe_key_string[i*2+2], "%02X", MasterSendKey[i]);
+ #endif /* else !0 */
+ vp = pairmake("MS-MPPE-Send-Key", mppe_key_string, T_OP_EQ);
+ rad_assert(vp != NULL);
+ pairadd(avp, vp);
+
+ /*
+ * Generate the MS-MPPE-Recv-Key attribute.
+ */
+ #if 0
+ /* Setup the salt value. */
+ salt[0] = 0x80;
+ salt[1] = 0x02;
+
+ /* Encode the key. */
+ (void) memset(mppe_key, 0, sizeof(mppe_key));
+ mppe_key[0] = 16; /* length */
+ (void) memcpy(&mppe_key[1], MasterReceiveKey, 16);
+ secretlen = strlen(request->secret);
+ (void) memcpy(encode_buf, request->secret, secretlen);
+ (void) memcpy(encode_buf + secretlen, request->packet->vector,
+ AUTH_VECTOR_LEN);
+ (void) memcpy(encode_buf + secretlen + 16, salt, 2);
+ (void) MD5(encode_buf, secretlen + AUTH_VECTOR_LEN + 2, md5_md);
+ for (i = 0; i < 16; ++i)
+ mppe_key[i] ^= md5_md[i];
+ (void) memcpy(encode_buf + secretlen, mppe_key, 16);
+ (void) MD5(encode_buf, secretlen + 16, md5_md);
+ for (i = 0; i < 16; ++i)
+ mppe_key[i + 16] ^= md5_md[i];
+
+ /* Whew. Now stringify it for pairmake(). */
+ mppe_key_string[0] = '0';
+ mppe_key_string[1] = 'x';
+ (void) sprintf(&mppe_key_string[2], "%02X", salt[0]);
+ (void) sprintf(&mppe_key_string[4], "%02X", salt[1]);
+ for (i = 0; i < sizeof(mppe_key); ++i)
+ (void) sprintf(&mppe_key_string[i*2+6], "%02X", mppe_key[i]);
+ #else /* 0 */
+ mppe_key_string[0] = '0';
+ mppe_key_string[1] = 'x';
+ for (i = 0; i < sizeof(MasterReceiveKey); ++i)
+ (void) sprintf(&mppe_key_string[i*2+2], "%02X", MasterReceiveKey[i]);
+ #endif /* else !0 */
+ vp = pairmake("MS-MPPE-Recv-Key", mppe_key_string, T_OP_EQ);
+ rad_assert(vp != NULL);
+ pairadd(avp, vp);
+
+ } /* (doing mppe) */
+ } /* PWE_MSCHAP2 */
+ break; /* PWE_MSCHAP2 */
+
+ } /* switch (pwe) */
+
+ return;
+ }
--- /dev/null
-#include <freeradius-devel/autoconf.h>
-#include <freeradius-devel/radiusd.h>
-#include <freeradius-devel/modules.h>
+ /*
+ * $Id$
+ *
+ * Passcode verification function (otpd client) for rlm_otp.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ *
+ * Copyright 2006 TRI-D Systems, Inc.
+ */
+
+ static const char rcsid[] = "$Id$";
+
- char *username = request->username->vp_strvalue;
++#include <autoconf.h>
++#include <radiusd.h>
++#include <modules.h>
+
+ #include "extern.h"
+ #include "otp.h"
+ #include "otp_pw_valid.h"
+
+ #include <errno.h>
+ #include <pthread.h>
+ #include <stdlib.h>
+ #include <string.h>
+ #include <sys/types.h>
+ #include <sys/socket.h>
+ #include <sys/un.h>
+ #include <unistd.h>
+
+
+ /* transform otpd return codes into rlm return codes */
+ static int
+ otprc2rlmrc(int rc)
+ {
+ switch (rc) {
+ case OTP_RC_OK: return RLM_MODULE_OK;
+ case OTP_RC_USER_UNKNOWN: return RLM_MODULE_REJECT;
+ case OTP_RC_AUTHINFO_UNAVAIL: return RLM_MODULE_REJECT;
+ case OTP_RC_AUTH_ERR: return RLM_MODULE_REJECT;
+ case OTP_RC_MAXTRIES: return RLM_MODULE_USERLOCK;
+ case OTP_RC_SERVICE_ERR: return RLM_MODULE_FAIL;
+ default: return RLM_MODULE_FAIL;
+ }
+ }
+
+ static otp_fd_t *otp_fd_head;
+ static pthread_mutex_t otp_fd_head_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+ /*
+ * Test for passcode validity by asking otpd.
+ *
+ * If challenge is supplied, it is used to generate the card response
+ * against which the passcode will be compared. If challenge is not
+ * supplied, or if the comparison fails, synchronous responses are
+ * generated and tested. NOTE: for async authentications, sync mode
+ * responses are still considered valid! (Assuming module configuration
+ * allows sync mode.)
+ *
+ * Returns one of the RLM_MODULE_* codes. passcode is filled in.
+ * NB: The returned passcode will contain the PIN! DO NOT LOG!
+ */
+ int
+ otp_pw_valid(REQUEST *request, int pwe, const char *challenge,
+ const otp_option_t *opt, char passcode[OTP_MAX_PASSCODE_LEN + 1])
+ {
+ otp_request_t otp_request;
+ otp_reply_t otp_reply;
+ VALUE_PAIR *cvp, *rvp;
- (void) strcpy(otp_request.pwe.passcode, rvp->vp_strvalue);
++ char *username = request->username->strvalue;
+ int rc;
+
+ if (request->username->length > OTP_MAX_USERNAME_LEN) {
+ (void) radlog(L_AUTH, "rlm_otp: username [%s] too long\n", username);
+ return RLM_MODULE_REJECT;
+ }
+ /* we already know challenge is short enough */
+
+ otp_request.version = 1;
+ (void) strcpy(otp_request.username, username);
+ (void) strcpy(otp_request.challenge, challenge);
+ otp_request.pwe.pwe = pwe;
+
+ /* otp_pwe_present() (done by caller) guarantees that both of these exist */
+ rvp = pairfind(request->packet->vps, pwattr[pwe - 1]);
+ cvp = pairfind(request->packet->vps, pwattr[pwe]);
+
+ /*
+ * Validate available vps based on pwe type.
+ * Unfortunately (?) otpd must do this also.
+ */
+ switch (otp_request.pwe.pwe) {
+ case PWE_PAP:
+ if (rvp->length > OTP_MAX_PASSCODE_LEN) {
+ (void) radlog(L_AUTH, "rlm_otp: passcode for [%s] too long\n",
+ username);
+ return RLM_MODULE_REJECT;
+ }
- (void) memcpy(otp_request.pwe.challenge, cvp->vp_strvalue, cvp->length);
++ (void) strcpy(otp_request.pwe.passcode, rvp->strvalue);
+ break;
+
+ case PWE_CHAP:
+ if (cvp->length > 16) {
+ (void) radlog(L_AUTH, "rlm_otp: CHAP challenge for [%s] too long\n",
+ username);
+ return RLM_MODULE_INVALID;
+ }
+ if (rvp->length != 17) {
+ (void) radlog(L_AUTH, "rlm_otp: CHAP response for [%s] wrong size\n",
+ username);
+ return RLM_MODULE_INVALID;
+ }
- (void) memcpy(otp_request.pwe.response, rvp->vp_strvalue, rvp->length);
++ (void) memcpy(otp_request.pwe.challenge, cvp->strvalue, cvp->length);
+ otp_request.pwe.clen = cvp->length;
- (void) memcpy(otp_request.pwe.challenge, cvp->vp_strvalue, cvp->length);
++ (void) memcpy(otp_request.pwe.response, rvp->strvalue, rvp->length);
+ otp_request.pwe.rlen = rvp->length;
+ break;
+
+ case PWE_MSCHAP:
+ if (cvp->length != 8) {
+ (void) radlog(L_AUTH, "rlm_otp: MS-CHAP challenge for [%s] wrong size\n",
+ username);
+ return RLM_MODULE_INVALID;
+ }
+ if (rvp->length != 50) {
+ (void) radlog(L_AUTH, "rlm_otp: MS-CHAP response for [%s] wrong size\n",
+ username);
+ return RLM_MODULE_INVALID;
+ }
- (void) memcpy(otp_request.pwe.response, rvp->vp_strvalue, rvp->length);
++ (void) memcpy(otp_request.pwe.challenge, cvp->strvalue, cvp->length);
+ otp_request.pwe.clen = cvp->length;
- (void) memcpy(otp_request.pwe.challenge, cvp->vp_strvalue, cvp->length);
++ (void) memcpy(otp_request.pwe.response, rvp->strvalue, rvp->length);
+ otp_request.pwe.rlen = rvp->length;
+ break;
+
+ case PWE_MSCHAP2:
+ if (cvp->length != 16) {
+ (void) radlog(L_AUTH, "rlm_otp: MS-CHAP2 challenge for [%s] wrong size\n",
+ username);
+ return RLM_MODULE_INVALID;
+ }
+ if (rvp->length != 50) {
+ (void) radlog(L_AUTH, "rlm_otp: MS-CHAP2 response for [%s] wrong size\n",
+ username);
+ return RLM_MODULE_INVALID;
+ }
- (void) memcpy(otp_request.pwe.response, rvp->vp_strvalue, rvp->length);
++ (void) memcpy(otp_request.pwe.challenge, cvp->strvalue, cvp->length);
+ otp_request.pwe.clen = cvp->length;
++ (void) memcpy(otp_request.pwe.response, rvp->strvalue, rvp->length);
+ otp_request.pwe.rlen = rvp->length;
+ break;
+ } /* switch (otp_request.pwe.pwe) */
+
+ /* last byte must also be a terminator so otpd can verify length easily */
+ otp_request.username[OTP_MAX_USERNAME_LEN] = '\0';
+ otp_request.challenge[OTP_MAX_CHALLENGE_LEN] = '\0';
+ otp_request.pwe.passcode[OTP_MAX_PASSCODE_LEN] = '\0';
+
+ otp_request.allow_sync = opt->allow_sync;
+ otp_request.allow_async = opt->allow_async;
+ otp_request.challenge_delay = opt->challenge_delay;
+ otp_request.resync = 1;
+
+ rc = otp_verify(opt, &otp_request, &otp_reply);
+ if (rc == OTP_RC_OK)
+ (void) strcpy(passcode, otp_reply.passcode);
+ return otprc2rlmrc(rc);
+ }
+
+ /*
+ * Verify an otp by asking otpd.
+ * Returns an OTP_* code, or -1 on system failure.
+ * Fills in reply.
+ */
+ static int
+ otp_verify(const otp_option_t *opt,
+ const otp_request_t *request, otp_reply_t *reply)
+ {
+ otp_fd_t *fdp;
+ int rc;
+ int tryagain = 2;
+
+ retry:
+ if (!tryagain--)
+ return -1;
+ fdp = otp_getfd(opt);
+ if (!fdp || fdp->fd == -1)
+ return -1;
+
+ if ((rc = otp_write(fdp, (const char *) request, sizeof(*request))) != 0) {
+ if (rc == EPIPE)
+ goto retry; /* otpd disconnect */ /*TODO: pause */
+ else
+ return -1;
+ }
+
+ if ((rc = otp_read(fdp, (char *) reply, sizeof(*reply))) != sizeof(*reply)) {
+ if (rc == 0)
+ goto retry; /* otpd disconnect */ /*TODO: pause */
+ else
+ return -1;
+ }
+
+ /* validate the reply */
+ if (reply->version != 1) {
+ (void) radlog(L_AUTH, "rlm_otp: otpd reply for [%s] invalid "
+ "(version %d != 1)",
+ request->username, reply->version);
+ return -1;
+ }
+
+ if (reply->passcode[OTP_MAX_PASSCODE_LEN] != '\0') {
+ (void) radlog(L_AUTH, "rlm_otp: otpd reply for [%s] invalid (passcode)",
+ request->username);
+ return -1;
+ }
+
+ return reply->rc;
+ }
+
+ /*
+ * Full read with logging, and close on failure.
+ * Returns nread on success, 0 on EOF, -1 on other failures.
+ */
+ static int
+ otp_read(otp_fd_t *fdp, char *buf, size_t len)
+ {
+ ssize_t n;
+ size_t nread = 0; /* bytes read into buf */
+
+ while (nread < len) {
+ if ((n = read(fdp->fd, &buf[nread], len - nread)) == -1) {
+ if (errno == EINTR) {
+ continue;
+ } else {
+ (void) radlog(L_ERR, "rlm_otp: %s: read from otpd: %s",
+ __func__, strerror(errno));
+ otp_putfd(fdp);
+ return -1;
+ }
+ }
+ if (!n) {
+ (void) radlog(L_ERR, "rlm_otp: %s: otpd disconnect", __func__);
+ otp_putfd(fdp);
+ return 0;
+ }
+ nread += n;
+ } /* while (more to read) */
+
+ return nread;
+ }
+
+ /*
+ * Full write with logging, and close on failure.
+ * Returns 0 on success, errno on failure.
+ */
+ static int
+ otp_write(otp_fd_t *fdp, const char *buf, size_t len)
+ {
+ size_t nleft = len;
+ ssize_t nwrote;
+
+ while (nleft) {
+ if ((nwrote = write(fdp->fd, &buf[len - nleft], nleft)) == -1) {
+ if (errno == EINTR || errno == EPIPE) {
+ continue;
+ } else {
+ (void) radlog(L_ERR, "rlm_otp: %s: write to otpd: %s",
+ __func__, strerror(errno));
+ otp_putfd(fdp);
+ return errno;
+ }
+ }
+ nleft -= nwrote;
+ }
+
+ return 0;
+ }
+
+ /* connect to otpd and return fd */
+ static int
+ otp_connect(const char *path)
+ {
+ int fd;
+ struct sockaddr_un sa;
+ size_t sp_len; /* sun_path length (strlen) */
+
+ /* setup for unix domain socket */
+ sp_len = strlen(path);
+ if (sp_len > sizeof(sa.sun_path) - 1) {
+ (void) radlog(L_ERR, "rlm_otp: %s: rendezvous point name too long",
+ __func__);
+ return -1;
+ }
+ sa.sun_family = AF_UNIX;
+ (void) strcpy(sa.sun_path, path);
+
+ /* connect to otpd */
+ if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) {
+ (void) radlog(L_ERR, "rlm_otp: %s: socket: %s", __func__, strerror(errno));
+ return -1;
+ }
+ if (connect(fd, (struct sockaddr *) &sa,
+ sizeof(sa.sun_family) + sp_len) == -1) {
+ (void) radlog(L_ERR, "rlm_otp: %s: connect(%s): %s",
+ __func__, path, strerror(errno));
+ (void) close(fd);
+ return -1;
+ }
+ return fd;
+ }
+
+ /*
+ * Retrieve an fd (from pool) to use for otpd connection.
+ * It'd be simpler to use TLS but FR can have lots of threads
+ * and we don't want to waste fd's that way.
+ * We can't have a global fd because we'd then be pipelining
+ * requests to otpd and we have no way to demultiplex
+ * the responses.
+ */
+ static otp_fd_t *
+ otp_getfd(const otp_option_t *opt)
+ {
+ int rc;
+ otp_fd_t *fdp;
+
+ /* walk the connection pool looking for an available fd */
+ for (fdp = otp_fd_head; fdp; fdp = fdp->next) {
+ rc = otp_pthread_mutex_trylock(&fdp->mutex);
+ if (!rc)
+ if (!strcmp(fdp->path, opt->otpd_rp)) /* could just use == */
+ break;
+ }
+
+ if (!fdp) {
+ /* no fd was available, add a new one */
+ fdp = rad_malloc(sizeof(*fdp));
+ otp_pthread_mutex_init(&fdp->mutex, NULL);
+ otp_pthread_mutex_lock(&fdp->mutex);
+ /* insert new fd at head */
+ otp_pthread_mutex_lock(&otp_fd_head_mutex);
+ fdp->next = otp_fd_head;
+ otp_fd_head = fdp;
+ otp_pthread_mutex_unlock(&otp_fd_head_mutex);
+ /* initialize */
+ fdp->path = opt->otpd_rp;
+ fdp->fd = -1;
+ }
+
+ /* establish connection */
+ if (fdp->fd == -1)
+ fdp->fd = otp_connect(fdp->path);
+
+ return fdp;
+ }
+
+ /* disconnect from otpd */
+ static void
+ otp_putfd(otp_fd_t *fdp)
+ {
+ (void) close(fdp->fd);
+ fdp->fd = -1;
+ /* make connection available to another thread */
+ otp_pthread_mutex_unlock(&fdp->mutex);
+ }
* Copyright 2005,2006 TRI-D Systems, Inc.
*/
- /*
- * STRONG WARNING SECTION:
- *
- * ANSI X9.9 has been withdrawn as a standard, due to the weakness of DES.
- * An attacker can learn the token's secret by observing two
- * challenge/response pairs. See ANSI document X9 TG-24-1999
- * <URL:http://www.x9.org/docs/TG24_1999.pdf>.
- *
- * Please read the accompanying docs.
- */
+ static const char rcsid[] = "$Id$";
-#include <freeradius-devel/autoconf.h>
-#include <freeradius-devel/radiusd.h>
-#include <freeradius-devel/modules.h>
++#include <autoconf.h>
++#include <radiusd.h>
++#include <modules.h>
+
+ #include "extern.h"
+ #include "otp.h"
+ #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
/* User-Name attribute required. */
if (!request->username) {
- otp_log(OTP_LOG_AUTH,
- "%s: %s: Attribute \"User-Name\" required for authentication.",
- log_prefix, __func__);
+ (void) radlog(L_AUTH, "rlm_otp: %s: Attribute \"User-Name\" required "
+ "for authentication.",
+ __func__);
return RLM_MODULE_INVALID;
}
- username = request->username->vp_strvalue;
+ username = request->username->strvalue;
- if ((data.pwattr = otp_pwe_present(request, log_prefix)) == 0) {
- otp_log(OTP_LOG_AUTH, "%s: %s: Attribute \"User-Password\" "
+ if ((pwe = otp_pwe_present(request)) == 0) {
+ (void) radlog(L_AUTH, "rlm_otp: %s: Attribute \"User-Password\" "
"or equivalent required for authentication.",
- log_prefix, __func__);
+ __func__);
return RLM_MODULE_INVALID;
}
int32_t then; /* state timestamp */
int e_length; /* expected State length */
- if ((vp = pairfind(request->packet->vps, PW_STATE)) != NULL) {
- /* set expected State length */
- if (inst->allow_async)
- e_length = inst->chal_len * 2 + 8 + 8 + 32; /* see otp_gen_state() */
- else
- e_length = 1;
-
- if (vp->length != e_length) {
- otp_log(OTP_LOG_AUTH, "%s: %s: bad state for [%s]: length",
- log_prefix, __func__, username);
- return RLM_MODULE_INVALID;
- }
-
- if (inst->allow_async) {
- /*
- * Verify the state.
- */
-
- rad_state = rad_malloc(e_length + 1);
- raw_state = rad_malloc(e_length / 2);
-
- /* ASCII decode */
- (void) memcpy(rad_state, vp->strvalue, vp->length);
- rad_state[e_length] = '\0';
- (void) otp_keystring2keyblock(rad_state, raw_state);
- free(rad_state);
-
- /* extract data from State */
- (void) memcpy(challenge, raw_state, inst->chal_len);
- (void) memcpy(&sflags, raw_state + inst->chal_len, 4);
- (void) memcpy(&then, raw_state + inst->chal_len + 4, 4);
- free(raw_state);
-
- /* generate new state from returned input data */
- if (otp_gen_state(NULL, &state, challenge, inst->chal_len,
- sflags, then, hmac_key) != 0) {
- otp_log(OTP_LOG_ERR, "%s: %s: failed to generate state",
- log_prefix, __func__);
- return RLM_MODULE_FAIL;
- }
- /* compare generated state against returned state to verify hmac */
- if (memcmp(state, vp->strvalue, vp->length)) {
- otp_log(OTP_LOG_AUTH, "%s: %s: bad state for [%s]: hmac",
- log_prefix, __func__, username);
- free(state);
- return RLM_MODULE_REJECT;
- }
- free(state);
-
- /* State is valid, but check expiry. */
- then = ntohl(then);
- if (time(NULL) - then > inst->chal_delay) {
- otp_log(OTP_LOG_AUTH, "%s: %s: bad state for [%s]: expired",
- log_prefix, __func__, username);
- return RLM_MODULE_REJECT;
- }
- resync = ntohl(sflags) & 1;
- } /* if (State should have been protected) */
- } /* if (State present) */
- } /* code block */
+ /* set expected State length */
+ e_length = inst->challenge_len * 2 + 8 + 8 + 32; /* see otp_gen_state() */
+
+ if (vp->length != e_length) {
+ (void) radlog(L_AUTH, "rlm_otp: %s: bad state for [%s]: length",
+ __func__, username);
+ return RLM_MODULE_INVALID;
+ }
+
+ /*
+ * Verify the state.
+ */
+
+ /* ASCII decode; this is why OTP_MAX_RADSTATE_LEN has +1 */
- (void) memcpy(rad_state, vp->vp_strvalue, vp->length);
++ (void) memcpy(rad_state, vp->strvalue, vp->length);
+ rad_state[e_length] = '\0';
+ if (otp_a2x(rad_state, raw_state) == -1) {
+ (void) radlog(L_AUTH, "rlm_otp: %s: bad state for [%s]: not hex",
+ __func__, username);
+ return RLM_MODULE_INVALID;
+ }
+
+ /* extract data from State */
+ (void) memcpy(challenge, raw_state, inst->challenge_len);
+ /* skip flag data */
+ (void) memcpy(&then, raw_state + inst->challenge_len + 4, 4);
+
+ /* generate new state from returned input data */
+ if (otp_gen_state(NULL, state, challenge, inst->challenge_len, 0,
+ then, hmac_key) != 0) {
+ (void) radlog(L_ERR, "rlm_otp: %s: failed to generate state",
+ __func__);
+ return RLM_MODULE_FAIL;
+ }
+ /* compare generated state against returned state to verify hmac */
- if (memcmp(state, vp->vp_strvalue, vp->length)) {
++ if (memcmp(state, vp->strvalue, vp->length)) {
+ (void) radlog(L_AUTH, "rlm_otp: %s: bad state for [%s]: hmac",
+ __func__, username);
+ return RLM_MODULE_REJECT;
+ }
+
+ /* State is valid, but check expiry. */
+ then = ntohl(then);
+ if (time(NULL) - then > inst->challenge_delay) {
+ (void) radlog(L_AUTH, "rlm_otp: %s: bad state for [%s]: expired",
+ __func__, username);
+ return RLM_MODULE_REJECT;
+ }
+ } /* if (State present) */
/* do it */
- rc = otprc2rlmrc(otp_pw_valid(username, challenge, NULL, resync, inst,
- otp_pwe_cmp, &data, log_prefix));
-
- /* Handle any vps returned from otp_pwe_cmp(). */
- if (rc == RLM_MODULE_OK) {
- pairadd(&request->reply->vps, add_vps);
- } else {
- pairfree(&add_vps);
- }
+ rc = otp_pw_valid(request, pwe, challenge, inst, passcode);
+
+ /* Add MPPE data as needed. */
+ if (rc == RLM_MODULE_OK)
+ otp_mppe(request, pwe, inst, passcode);
+
return rc;
}
* is single-threaded.
*/
module_t rlm_otp = {
- RLM_MODULE_INIT,
"otp",
RLM_TYPE_THREAD_SAFE, /* type */
- NULL, /* initialization */
++ NULL,
otp_instantiate, /* instantiation */
- otp_detach, /* detach */
{
otp_authenticate, /* authentication */
otp_authorize, /* authorization */
NULL, /* post-proxy */
NULL /* post-auth */
},
- NULL, /* destroy */
+ otp_detach, /* detach */
++ NULL,
};