EAP-PWD Implementation
authorDan Harkins <dharkins@lounge.org>
Tue, 21 Feb 2012 08:12:24 +0000 (09:12 +0100)
committerAlan T. DeKok <aland@freeradius.org>
Tue, 21 Feb 2012 08:12:24 +0000 (09:12 +0100)
http://ietf.org/rfc/rfc5931.txt

src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c [new file with mode: 0644]
src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h [new file with mode: 0644]
src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c [new file with mode: 0644]
src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h [new file with mode: 0644]

diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c b/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c
new file mode 100644 (file)
index 0000000..521d73f
--- /dev/null
@@ -0,0 +1,773 @@
+/*
+ * Copyright (c) Dan Harkins, 2012
+ *
+ *  Copyright holder grants permission for redistribution and use in source 
+ *  and binary forms, with or without modification, provided that the 
+ *  following conditions are met:
+ *     1. Redistribution of source code must retain the above copyright
+ *        notice, this list of conditions, and the following disclaimer
+ *        in all source files.
+ *     2. Redistribution in binary form must retain the above copyright
+ *        notice, this list of conditions, and the following disclaimer
+ *        in the documentation and/or other materials provided with the
+ *        distribution.
+ *
+ *  "DISCLAIMER OF LIABILITY
+ *  
+ *  THIS SOFTWARE IS PROVIDED BY DAN HARKINS ``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 INDUSTRIAL LOUNGE 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."
+ *
+ * This license and distribution terms cannot be changed. In other words,
+ * this code cannot simply be copied and put under a different distribution
+ * license (including the GNU public license).
+ */
+
+#include <freeradius-devel/ident.h>
+RCSID("$Id$")
+
+#include <freeradius-devel/autoconf.h>
+
+#include "eap_pwd.h"
+
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/modules.h>
+
+#ifdef PRINT_BUF
+void
+print_buf (char *str, unsigned char *buf, int len)
+{
+    int i;
+
+    printf("%s\n", str);
+    for (i = 0; i < len; i++) {
+        if (i && (i % 16 == 0)) {
+            printf("\n");
+        }
+        printf("%02x ", buf[i]);
+    }
+    printf("\n");
+}        
+#endif  /* PRINT_BUF */
+
+/* The random function H(x) = HMAC-SHA256(0^32, x) */
+static void 
+H_Init(HMAC_CTX *ctx)
+{
+    unsigned char allzero[SHA256_DIGEST_LENGTH];
+
+    memset(allzero, 0, SHA256_DIGEST_LENGTH);
+    HMAC_Init(ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256());
+}
+
+static void 
+H_Update(HMAC_CTX *ctx, const unsigned char *data, int len)
+{
+    HMAC_Update(ctx, data, len);
+}
+
+static void 
+H_Final(HMAC_CTX *ctx, unsigned char *digest)
+{
+    unsigned int mdlen = SHA256_DIGEST_LENGTH;
+
+    HMAC_Final(ctx, digest, &mdlen);
+    HMAC_CTX_cleanup(ctx);
+}
+
+/* a counter-based KDF based on NIST SP800-108 */
+static void 
+eap_pwd_kdf(unsigned char *key, int keylen, const char *label, int labellen,
+            unsigned char *result, int resultbitlen)
+{
+    HMAC_CTX hctx;
+    unsigned char digest[SHA256_DIGEST_LENGTH];
+    unsigned short i, ctr, L;
+    int resultbytelen, len = 0;
+    unsigned int mdlen = SHA256_DIGEST_LENGTH;
+    unsigned char mask = 0xff;
+
+    resultbytelen = (resultbitlen + 7)/8;
+    ctr = 0;
+    L = htons(resultbitlen);
+    while (len < resultbytelen) {
+        ctr++; i = htons(ctr);
+        HMAC_Init(&hctx, key, keylen, EVP_sha256());
+        if (ctr > 1) {
+            HMAC_Update(&hctx, digest, mdlen);
+        }
+        HMAC_Update(&hctx, (unsigned char *) &i, sizeof(unsigned short));
+        HMAC_Update(&hctx, label, labellen);
+        HMAC_Update(&hctx, (unsigned char *) &L, sizeof(unsigned short));
+        HMAC_Final(&hctx, digest, &mdlen);
+        if ((len + (int) mdlen) > resultbytelen) {
+            memcpy(result + len, digest, resultbytelen - len);
+        } else {
+            memcpy(result + len, digest, mdlen);
+        }
+        len += mdlen;
+        HMAC_CTX_cleanup(&hctx);
+    }
+
+    /* since we're expanding to a bit length, mask off the excess */
+    if (resultbitlen % 8) {
+        mask <<= (8 - (resultbitlen % 8));
+        result[resultbytelen - 1] &= mask;
+    }
+}
+
+int
+compute_password_element (pwd_session_t *sess, unsigned short grp_num,
+                          char *password, int password_len,
+                          char *id_server, int id_server_len,
+                          char *id_peer, int id_peer_len, 
+                          unsigned long *token)
+{
+    BIGNUM *x_candidate = NULL, *rnd = NULL, *cofactor = NULL;
+    HMAC_CTX ctx;
+    unsigned char pwe_digest[SHA256_DIGEST_LENGTH], *prfbuf = NULL, ctr;
+    int nid, is_odd, primebitlen, primebytelen, ret = 0;
+
+    switch (grp_num) { /* from IANA registry for IKE D-H groups */
+        case 19:
+            nid = NID_X9_62_prime256v1;
+            break;
+        case 20:
+            nid = NID_secp384r1;
+            break;
+        case 21:
+            nid = NID_secp521r1;
+            break;
+        case 25:
+            nid = NID_X9_62_prime192v1;
+            break;
+        case 26:
+            nid = NID_secp224r1;
+            break;
+        default:
+            DEBUG("unknown group %d", grp_num);
+            goto fail;
+    }
+
+    sess->pwe = NULL;
+    sess->order = NULL;
+    sess->prime = NULL;
+
+    if ((sess->group = EC_GROUP_new_by_curve_name(nid)) == NULL) {
+        DEBUG("unable to create EC_GROUP");
+        goto fail;
+    }
+
+    if (((rnd = BN_new()) == NULL) ||
+        ((cofactor = BN_new()) == NULL) ||
+        ((sess->pwe = EC_POINT_new(sess->group)) == NULL) ||
+        ((sess->order = BN_new()) == NULL) ||
+        ((sess->prime = BN_new()) == NULL) ||
+        ((x_candidate = BN_new()) == NULL)) {
+        DEBUG("unable to create bignums");
+        goto fail;
+    }
+
+    if (!EC_GROUP_get_curve_GFp(sess->group, sess->prime, NULL, NULL, NULL))
+    {
+        DEBUG("unable to get prime for GFp curve");
+        goto fail;
+    }
+    if (!EC_GROUP_get_order(sess->group, sess->order, NULL)) {
+        DEBUG("unable to get order for curve");
+        goto fail;
+    }
+    if (!EC_GROUP_get_cofactor(sess->group, cofactor, NULL)) {
+        DEBUG("unable to get cofactor for curve");
+        goto fail;
+    }
+    primebitlen = BN_num_bits(sess->prime);
+    primebytelen = BN_num_bytes(sess->prime);
+    if ((prfbuf = malloc(primebytelen)) == NULL) {
+        DEBUG("unable to malloc space for prf buffer");
+        goto fail;
+    }
+    memset(prfbuf, 0, primebytelen);
+    ctr = 0;
+    while (1) {
+        if (ctr > 10) {
+            DEBUG("unable to find random point on curve for group %d, something's fishy", grp_num);
+            goto fail;
+        }
+        ctr++;
+
+        /*
+         * compute counter-mode password value and stretch to prime
+         *    pwd-seed = H(token | peer-id | server-id | password |
+         *                counter)
+         */
+        H_Init(&ctx);
+        H_Update(&ctx, (unsigned char *)token, sizeof(unsigned long));
+        H_Update(&ctx, (unsigned char *)id_peer, id_peer_len);
+        H_Update(&ctx, (unsigned char *)id_server, id_server_len);
+        H_Update(&ctx, (unsigned char *)password, password_len);
+        H_Update(&ctx, (unsigned char *)&ctr, sizeof(ctr));
+        H_Final(&ctx, pwe_digest);
+
+        BN_bin2bn(pwe_digest, SHA256_DIGEST_LENGTH, rnd);
+        eap_pwd_kdf(pwe_digest, SHA256_DIGEST_LENGTH,
+                    "EAP-pwd Hunting And Pecking",
+                    strlen("EAP-pwd Hunting And Pecking"),
+                    prfbuf, primebitlen);
+
+        BN_bin2bn(prfbuf, primebytelen, x_candidate);
+        /*
+         * eap_pwd_kdf() returns a string of bits 0..primebitlen but
+         * BN_bin2bn will treat that string of bits as a big endian
+         * number. If the primebitlen is not an even multiple of 8
+         * then excessive bits-- those _after_ primebitlen-- so now
+         * we have to shift right the amount we masked off.
+         */
+        if (primebitlen % 8) {
+            BN_rshift(x_candidate, x_candidate, (8 - (primebitlen % 8)));
+        }
+        if (BN_ucmp(x_candidate, sess->prime) >= 0) {
+            continue;
+        }
+        /*
+         * need to unambiguously identify the solution, if there is
+         * one...
+         */
+        if (BN_is_odd(rnd)) {
+            is_odd = 1;
+        } else {
+            is_odd = 0;
+        }
+        /*
+         * solve the quadratic equation, if it's not solvable then we
+         * don't have a point
+         */
+        if (!EC_POINT_set_compressed_coordinates_GFp(sess->group,
+                                                     sess->pwe,
+                                                     x_candidate,
+                                                     is_odd, NULL)) {
+            continue;
+        }
+        /*
+         * If there's a solution to the equation then the point must be
+         * on the curve so why check again explicitly? OpenSSL code
+         * says this is required by X9.62. We're not X9.62 but it can't
+         * hurt just to be sure.
+         */
+        if (!EC_POINT_is_on_curve(sess->group, sess->pwe, NULL)) {
+            DEBUG("EAP-pwd: point is not on curve");
+            continue;
+        }
+
+        if (BN_cmp(cofactor, BN_value_one())) {
+            /* make sure the point is not in a small sub-group */
+            if (!EC_POINT_mul(sess->group, sess->pwe, NULL, sess->pwe,
+                              cofactor, NULL)) {
+                DEBUG("EAP-pwd: cannot multiply generator by order");
+                continue;
+            }
+            if (EC_POINT_is_at_infinity(sess->group, sess->pwe)) {
+                DEBUG("EAP-pwd: point is at infinity");
+                continue;
+            }
+        }
+        /* if we got here then we have a new generator. */
+        break;
+    }
+    sess->group_num = grp_num;
+    if (0) {
+fail:
+        EC_GROUP_free(sess->group);
+        EC_POINT_free(sess->pwe);
+        BN_free(sess->order);
+        BN_free(sess->prime);
+        if (prfbuf != NULL) {
+            free(prfbuf);
+        }
+        free(sess);
+        sess = NULL;
+        ret = -1;
+    }
+    /* cleanliness and order.... */
+    BN_free(cofactor);
+    BN_free(x_candidate);
+    BN_free(rnd);
+    if (prfbuf != NULL) {
+        free(prfbuf);
+    }
+
+    return ret;
+}
+
+int
+compute_scalar_element (pwd_session_t *sess, BN_CTX *bnctx)
+{
+    BIGNUM *mask = NULL;
+    int ret = -1;
+
+    if (((sess->private_value = BN_new()) == NULL) ||
+        ((sess->my_element = EC_POINT_new(sess->group)) == NULL) ||
+        ((sess->my_scalar = BN_new()) == NULL) ||
+        ((mask = BN_new()) == NULL)) {
+        DEBUG2("server scalar allocation failed");
+        goto fail;
+    }
+
+    BN_rand_range(sess->private_value, sess->order);
+    BN_rand_range(mask, sess->order);
+    BN_add(sess->my_scalar, sess->private_value, mask);
+    BN_mod(sess->my_scalar, sess->my_scalar, sess->order,
+           bnctx);
+
+    if (!EC_POINT_mul(sess->group, sess->my_element, NULL,
+                      sess->pwe, mask, bnctx)) {
+        DEBUG2("server element allocation failed");
+        goto fail;
+    }
+
+    if (!EC_POINT_invert(sess->group, sess->my_element, bnctx)) {
+        DEBUG2("server element inversion failed");
+        goto fail;
+    }
+
+    ret = 0;
+fail:
+    BN_free(mask);
+
+    return ret;
+}
+
+int
+process_peer_commit (pwd_session_t *sess, unsigned char *commit, BN_CTX *bnctx)
+{
+    unsigned char *ptr;
+    BIGNUM *x = NULL, *y = NULL, *cofactor = NULL;
+    EC_POINT *K = NULL, *point = NULL;
+    int res = 1;
+
+    if (((sess->peer_scalar = BN_new()) == NULL) ||
+        ((sess->k = BN_new()) == NULL) ||
+        ((cofactor = BN_new()) == NULL) ||
+        ((x = BN_new()) == NULL) ||
+        ((y = BN_new()) == NULL) ||
+        ((point = EC_POINT_new(sess->group)) == NULL) ||
+        ((K = EC_POINT_new(sess->group)) == NULL) ||
+        ((sess->peer_element = EC_POINT_new(sess->group)) == NULL)) {
+        DEBUG2("pwd: failed to allocate room to process peer's commit");
+        goto fin;
+    }
+
+    if (!EC_GROUP_get_cofactor(sess->group, cofactor, NULL)) {
+        DEBUG2("pwd: unable to get group co-factor");
+        goto fin;
+    }
+
+    /* element, x then y, followed by scalar */
+    ptr = (unsigned char *)commit;
+    BN_bin2bn(ptr, BN_num_bytes(sess->prime), x);
+    ptr += BN_num_bytes(sess->prime);
+    BN_bin2bn(ptr, BN_num_bytes(sess->prime), y);
+    ptr += BN_num_bytes(sess->prime);
+    BN_bin2bn(ptr, BN_num_bytes(sess->order), sess->peer_scalar);
+    if (!EC_POINT_set_affine_coordinates_GFp(sess->group,
+                                             sess->peer_element, x, y,
+                                             bnctx)) {
+        DEBUG2("pwd: unable to get coordinates of peer's element");
+        goto fin;
+    }
+
+    /* check to ensure peer's element is not in a small sub-group */
+    if (BN_cmp(cofactor, BN_value_one())) {
+        if (!EC_POINT_mul(sess->group, point, NULL,
+                          sess->peer_element, cofactor, NULL)) {
+            DEBUG2("pwd: unable to multiply element by co-factor");
+            goto fin;
+        }
+        if (EC_POINT_is_at_infinity(sess->group, point)) {
+            DEBUG2("pwd: peer's element is in small sub-group");
+            goto fin;
+        }
+    }
+
+    /* compute the shared key, k */
+    if ((!EC_POINT_mul(sess->group, K, NULL, sess->pwe,
+                       sess->peer_scalar, bnctx)) ||
+        (!EC_POINT_add(sess->group, K, K, sess->peer_element,
+                       bnctx)) ||
+        (!EC_POINT_mul(sess->group, K, NULL, K, sess->private_value,
+                       bnctx))) {
+        DEBUG2("pwd: unable to compute shared key, k");
+        goto fin;
+    }
+
+    /* ensure that the shared key isn't in a small sub-group */
+    if (BN_cmp(cofactor, BN_value_one())) {
+        if (!EC_POINT_mul(sess->group, K, NULL, K, cofactor,
+                          NULL)) {
+            DEBUG2("pwd: unable to multiply k by co-factor");
+            goto fin;
+        }
+    }
+
+    /*
+     * This check is strictly speaking just for the case above where
+     * co-factor > 1 but it was suggested that even though this is probably
+     * never going to happen it is a simple and safe check "just to be
+     * sure" so let's be safe.
+     */
+    if (EC_POINT_is_at_infinity(sess->group, K)) {
+        DEBUG2("pwd: k is point-at-infinity!");
+        goto fin;
+    }
+    if (!EC_POINT_get_affine_coordinates_GFp(sess->group, K, sess->k,
+                                             NULL, bnctx)) {
+        DEBUG2("pwd: unable to get shared secret from K");
+        goto fin;
+    }
+    res = 0;
+
+  fin:
+    EC_POINT_free(K);
+    EC_POINT_free(point);
+    BN_free(cofactor);
+    BN_free(x);
+    BN_free(y);
+
+    return res;
+}
+
+int
+compute_server_confirm (pwd_session_t *sess, unsigned char *buf, BN_CTX *bnctx)
+{
+    BIGNUM *x = NULL, *y = NULL;
+    HMAC_CTX ctx;
+    unsigned char *cruft = NULL;
+    int offset, req = -1;
+
+    /* 
+     * Each component of the cruft will be at most as big as the prime 
+     */
+    if (((cruft = malloc(BN_num_bytes(sess->prime))) == NULL) ||
+        ((x = BN_new()) == NULL) || ((y = BN_new()) == NULL)) {
+        DEBUG2("pwd: unable to allocate space to compute confirm!");
+        goto fin;
+    }
+
+    /*
+     * commit is H(k | server_element | server_scalar | peer_element |
+     *        peer_scalar | ciphersuite)
+     */
+    H_Init(&ctx);
+
+    /*
+     * Zero the memory each time because this is mod prime math and some
+     * value may start with a few zeros and the previous one did not.
+     *
+     * First is k
+     */
+    memset(cruft, 0, BN_num_bytes(sess->prime));
+    offset = BN_num_bytes(sess->prime) - BN_num_bytes(sess->k);
+    BN_bn2bin(sess->k, cruft + offset);
+#ifdef PRINTBUF
+    printf("computing server confirm\n");
+    print_buf("k", cruft, BN_num_bytes(sess->prime));
+#endif  /* PRINTBUF */
+    H_Update(&ctx, cruft, BN_num_bytes(sess->prime));
+
+    /* 
+     * next is server element: x, y 
+     */
+    if (!EC_POINT_get_affine_coordinates_GFp(sess->group,
+                                             sess->my_element, x, y,
+                                             bnctx)) {
+        DEBUG2("pwd: unable to get coordinates of server element");
+        goto fin;
+    }
+    memset(cruft, 0, BN_num_bytes(sess->prime));
+    offset = BN_num_bytes(sess->prime) - BN_num_bytes(x);
+    BN_bn2bin(x, cruft + offset);
+#ifdef PRINTBUF
+    print_buf("server x", cruft, BN_num_bytes(sess->prime));
+#endif  /* PRINTBUF */
+    H_Update(&ctx, cruft, BN_num_bytes(sess->prime));
+
+    memset(cruft, 0, BN_num_bytes(sess->prime));
+    offset = BN_num_bytes(sess->prime) - BN_num_bytes(y);
+    BN_bn2bin(y, cruft + offset);
+#ifdef PRINTBUF
+    print_buf("server y", cruft, BN_num_bytes(sess->prime));
+#endif  /* PRINTBUF */
+    H_Update(&ctx, cruft, BN_num_bytes(sess->prime));
+
+    /* 
+     * and server scalar 
+     */
+    memset(cruft, 0, BN_num_bytes(sess->prime));
+    offset = BN_num_bytes(sess->order) - BN_num_bytes(sess->my_scalar);
+    BN_bn2bin(sess->my_scalar, cruft + offset);
+#ifdef PRINTBUF
+    print_buf("server scalar", cruft, BN_num_bytes(sess->order));
+#endif  /* PRINTBUF */
+    H_Update(&ctx, cruft, BN_num_bytes(sess->order));
+
+    /* 
+     * next is peer element: x, y 
+     */
+    if (!EC_POINT_get_affine_coordinates_GFp(sess->group,
+                                             sess->peer_element, x, y,
+                                             bnctx)) {
+        DEBUG2("pwd: unable to get coordinates of peer's element");
+        goto fin;
+    }
+
+    memset(cruft, 0, BN_num_bytes(sess->prime));
+    offset = BN_num_bytes(sess->prime) - BN_num_bytes(x);
+    BN_bn2bin(x, cruft + offset);
+#ifdef PRINTBUF
+    print_buf("peer x", cruft, BN_num_bytes(sess->prime));
+#endif  /* PRINTBUF */
+    H_Update(&ctx, cruft, BN_num_bytes(sess->prime));
+
+    memset(cruft, 0, BN_num_bytes(sess->prime));
+    offset = BN_num_bytes(sess->prime) - BN_num_bytes(y);
+    BN_bn2bin(y, cruft + offset);
+#ifdef PRINTBUF
+    print_buf("peer y", cruft, BN_num_bytes(sess->prime));
+#endif  /* PRINTBUF */
+    H_Update(&ctx, cruft, BN_num_bytes(sess->prime));
+
+    /* 
+     * and peer scalar 
+     */
+    memset(cruft, 0, BN_num_bytes(sess->prime));
+    offset = BN_num_bytes(sess->order) - BN_num_bytes(sess->peer_scalar);
+    BN_bn2bin(sess->peer_scalar, cruft + offset);
+#ifdef PRINTBUF
+    print_buf("peer scalar", cruft, BN_num_bytes(sess->order));
+#endif  /* PRINTBUF */
+    H_Update(&ctx, cruft, BN_num_bytes(sess->order));
+
+#ifdef PRINTBUF
+    print_buf("ciphersuite", (char *)&sess->ciphersuite, sizeof(unsigned long));
+#endif  /* PRINTBUF */
+    /* 
+     * finally, ciphersuite 
+     */
+    H_Update(&ctx, (unsigned char *)&sess->ciphersuite, sizeof(unsigned long));
+
+    H_Final(&ctx, buf);
+#ifdef PRINTBUF
+    print_buf("confirm", buf, SHA256_DIGEST_LENGTH);
+#endif  /* PRINTBUF */
+
+    req = 0;
+fin:
+    if (cruft != NULL) {
+        free(cruft);
+    }
+    BN_free(x);
+    BN_free(y);
+
+    return req;
+}
+
+int
+compute_peer_confirm (pwd_session_t *sess, unsigned char *buf, BN_CTX *bnctx)
+{
+    BIGNUM *x = NULL, *y = NULL;
+    HMAC_CTX ctx;
+    unsigned char *cruft = NULL;
+    int offset, req = -1;
+
+    /* 
+     * Each component of the cruft will be at most as big as the prime 
+     */
+    if (((cruft = malloc(BN_num_bytes(sess->prime))) == NULL) ||
+        ((x = BN_new()) == NULL) || ((y = BN_new()) == NULL)) {
+        DEBUG2("pwd: unable to allocate space to compute confirm!");
+        goto fin;
+    }
+
+    /*
+     * commit is H(k | server_element | server_scalar | peer_element |
+     *        peer_scalar | ciphersuite)
+     */
+    H_Init(&ctx);
+
+    /*
+     * Zero the memory each time because this is mod prime math and some
+     * value may start with a few zeros and the previous one did not.
+     *
+     * First is k
+     */
+    memset(cruft, 0, BN_num_bytes(sess->prime));
+    offset = BN_num_bytes(sess->prime) - BN_num_bytes(sess->k);
+    BN_bn2bin(sess->k, cruft + offset);
+#ifdef PRINTBUF
+    printf("computing peer confirm\n");
+    print_buf("k", cruft, BN_num_bytes(sess->prime));
+#endif  /* PRINTBUF */
+    H_Update(&ctx, cruft, BN_num_bytes(sess->prime));
+
+    /* 
+     * then peer element: x, y 
+     */
+    if (!EC_POINT_get_affine_coordinates_GFp(sess->group,
+                                             sess->peer_element, x, y,
+                                             bnctx)) {
+        DEBUG2("pwd: unable to get coordinates of peer's element");
+        goto fin;
+    }
+
+    memset(cruft, 0, BN_num_bytes(sess->prime));
+    offset = BN_num_bytes(sess->prime) - BN_num_bytes(x);
+    BN_bn2bin(x, cruft + offset);
+#ifdef PRINTBUF
+    print_buf("peer x", cruft, BN_num_bytes(sess->prime));
+#endif  /* PRINTBUF */
+    H_Update(&ctx, cruft, BN_num_bytes(sess->prime));
+
+    memset(cruft, 0, BN_num_bytes(sess->prime));
+    offset = BN_num_bytes(sess->prime) - BN_num_bytes(y);
+    BN_bn2bin(y, cruft + offset);
+#ifdef PRINTBUF
+    print_buf("peer x", cruft, BN_num_bytes(sess->prime));
+#endif  /* PRINTBUF */
+    H_Update(&ctx, cruft, BN_num_bytes(sess->prime));
+
+    /* 
+     * and peer scalar 
+     */
+    memset(cruft, 0, BN_num_bytes(sess->prime));
+    offset = BN_num_bytes(sess->order) - BN_num_bytes(sess->peer_scalar);
+    BN_bn2bin(sess->peer_scalar, cruft + offset);
+#ifdef PRINTBUF
+    print_buf("peer scalar", cruft, BN_num_bytes(sess->order));
+#endif  /* PRINTBUF */
+    H_Update(&ctx, cruft, BN_num_bytes(sess->order));
+
+    /* 
+     * then server element: x, y 
+     */
+    if (!EC_POINT_get_affine_coordinates_GFp(sess->group,
+                                             sess->my_element, x, y,
+                                             bnctx)) {
+        DEBUG2("pwd: unable to get coordinates of server element");
+        goto fin;
+    }
+    memset(cruft, 0, BN_num_bytes(sess->prime));
+    offset = BN_num_bytes(sess->prime) - BN_num_bytes(x);
+    BN_bn2bin(x, cruft + offset);
+#ifdef PRINTBUF
+    print_buf("server x", cruft, BN_num_bytes(sess->prime));
+#endif  /* PRINTBUF */
+    H_Update(&ctx, cruft, BN_num_bytes(sess->prime));
+
+    memset(cruft, 0, BN_num_bytes(sess->prime));
+    offset = BN_num_bytes(sess->prime) - BN_num_bytes(y);
+    BN_bn2bin(y, cruft + offset);
+#ifdef PRINTBUF
+    print_buf("server y", cruft, BN_num_bytes(sess->prime));
+#endif  /* PRINTBUF */
+    H_Update(&ctx, cruft, BN_num_bytes(sess->prime));
+
+    /* 
+     * and server scalar 
+     */
+    memset(cruft, 0, BN_num_bytes(sess->prime));
+    offset = BN_num_bytes(sess->order) - BN_num_bytes(sess->my_scalar);
+    BN_bn2bin(sess->my_scalar, cruft + offset);
+#ifdef PRINTBUF
+    print_buf("server scalar", cruft, BN_num_bytes(sess->order));
+#endif  /* PRINTBUF */
+    H_Update(&ctx, cruft, BN_num_bytes(sess->order));
+
+#ifdef PRINTBUF
+    print_buf("ciphersuite", (char *)&sess->ciphersuite, sizeof(unsigned long));
+#endif  /* PRINTBUF */
+    /* 
+     * finally, ciphersuite 
+     */
+    H_Update(&ctx, (unsigned char *)&sess->ciphersuite, sizeof(unsigned long));
+
+    H_Final(&ctx, buf);
+
+    req = 0;
+fin:
+    if (cruft != NULL) {
+        free(cruft);
+    }
+    BN_free(x);
+    BN_free(y);
+
+    return req;
+}
+
+int
+compute_keys (pwd_session_t *sess, unsigned char *peer_confirm, 
+              unsigned char *msk, unsigned char *emsk)
+{
+    HMAC_CTX ctx;
+    unsigned char mk[SHA256_DIGEST_LENGTH], *cruft;
+    unsigned char session_id[SHA256_DIGEST_LENGTH + 1];
+    unsigned char msk_emsk[128];                /* 64 each */
+    int offset;
+
+    if ((cruft = malloc(BN_num_bytes(sess->prime))) == NULL) {
+        DEBUG2("pwd: unable to allocate space to compute keys");
+        return -1;
+    }
+    /*
+     * first compute the session-id = TypeCode | H(ciphersuite | scal_p |
+     * scal_s)
+     */
+    session_id[0] = PW_EAP_PWD;
+    H_Init(&ctx);
+    H_Update(&ctx, (unsigned char *)&sess->ciphersuite, sizeof(unsigned long));
+    offset = BN_num_bytes(sess->order) - BN_num_bytes(sess->peer_scalar);
+    memset(cruft, 0, BN_num_bytes(sess->prime));
+    BN_bn2bin(sess->peer_scalar, cruft + offset);
+    H_Update(&ctx, cruft, BN_num_bytes(sess->order));
+    offset = BN_num_bytes(sess->order) - BN_num_bytes(sess->my_scalar);
+    memset(cruft, 0, BN_num_bytes(sess->prime));
+    BN_bn2bin(sess->my_scalar, cruft + offset);
+    H_Update(&ctx, cruft, BN_num_bytes(sess->order));
+    H_Final(&ctx, &session_id[1]);
+
+    /* then compute MK = H(k | commit-peer | commit-server) */
+    H_Init(&ctx);
+
+    memset(cruft, 0, BN_num_bytes(sess->prime));
+    offset = BN_num_bytes(sess->prime) - BN_num_bytes(sess->k);
+    BN_bn2bin(sess->k, cruft + offset);
+    H_Update(&ctx, cruft, BN_num_bytes(sess->prime));
+
+    H_Update(&ctx, peer_confirm, SHA256_DIGEST_LENGTH);
+
+    H_Update(&ctx, sess->my_confirm, SHA256_DIGEST_LENGTH);
+
+    H_Final(&ctx, mk);
+
+    /* stretch the mk with the session-id to get MSK | EMSK */
+    eap_pwd_kdf(mk, SHA256_DIGEST_LENGTH,
+                session_id, SHA256_DIGEST_LENGTH+1,
+                msk_emsk, 1024);  /* it's bits, ((64 + 64) * 8) */
+
+    memcpy(msk, msk_emsk, 64);
+    memcpy(emsk, msk_emsk + 64, 64);
+
+    free(cruft);
+    return 0;
+}
+
+
+
+
diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h b/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h
new file mode 100644 (file)
index 0000000..d2e81a2
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) Dan Harkins, 2012
+ *
+ *  Copyright holder grants permission for redistribution and use in source 
+ *  and binary forms, with or without modification, provided that the 
+ *  following conditions are met:
+ *     1. Redistribution of source code must retain the above copyright
+ *        notice, this list of conditions, and the following disclaimer
+ *        in all source files.
+ *     2. Redistribution in binary form must retain the above copyright
+ *        notice, this list of conditions, and the following disclaimer
+ *        in the documentation and/or other materials provided with the
+ *        distribution.
+ *
+ *  "DISCLAIMER OF LIABILITY
+ *  
+ *  THIS SOFTWARE IS PROVIDED BY DAN HARKINS ``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 INDUSTRIAL LOUNGE 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."
+ *
+ * This license and distribution terms cannot be changed. In other words,
+ * this code cannot simply be copied and put under a different distribution
+ * license (including the GNU public license).
+ */
+
+#ifndef _EAP_PWD_H
+#define _EAP_PWD_H
+
+#include <freeradius-devel/ident.h>
+RCSIDH(eap_pwd_h, "$Id$")
+#include <openssl/bn.h>
+#include <openssl/sha.h>
+#include <openssl/ec.h>
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#include "eap.h"
+
+typedef struct _pwd_hdr {
+    unsigned char lm_exchange;
+#define EAP_PWD_EXCH_ID                 1
+#define EAP_PWD_EXCH_COMMIT             2
+#define EAP_PWD_EXCH_CONFIRM            3
+//    unsigned short total_length;      /* there if the L-bit is set */
+    unsigned char data[0];
+} __attribute__ ((packed)) pwd_hdr;
+
+#define EAP_PWD_GET_LENGTH_BIT(x)       ((x)->lm_exchange & 0x80)
+#define EAP_PWD_SET_LENGTH_BIT(x)       ((x)->lm_exchange |= 0x80)
+#define EAP_PWD_GET_MORE_BIT(x)         ((x)->lm_exchange & 0x40)
+#define EAP_PWD_SET_MORE_BIT(x)         ((x)->lm_exchange |= 0x40)
+#define EAP_PWD_GET_EXCHANGE(x)         ((x)->lm_exchange & 0x3f)
+#define EAP_PWD_SET_EXCHANGE(x,y)       ((x)->lm_exchange |= (y))
+
+typedef struct _pwd_id_packet {
+    unsigned short group_num;
+    unsigned char random_function;
+#define EAP_PWD_DEF_RAND_FUN            1
+    unsigned char prf;
+#define EAP_PWD_DEF_PRF                 1
+    unsigned char token[4];
+    unsigned char prep;
+#define EAP_PWD_PREP_NONE               0
+#define EAP_PWD_PREP_MS                 1
+#define EAP_PWD_PREP_SASL               2
+    unsigned char identity[0];
+} __attribute__ ((packed)) pwd_id_packet;
+
+typedef struct _pwd_session_t {
+    unsigned short state;
+#define PWD_STATE_ID_REQ                1
+#define PWD_STATE_COMMIT                2
+#define PWD_STATE_CONFIRM               3
+    unsigned short group_num;
+    unsigned long ciphersuite;
+    unsigned long token;
+    char peer_id[MAX_STRING_LEN];
+    int peer_id_len;
+    int mtu;
+    unsigned char *in_buf;      /* reassembled fragments */
+    int in_buf_pos;
+    int in_buf_len;
+    unsigned char *out_buf;     /* message to fragment */
+    int out_buf_pos;
+    int out_buf_len;
+    EC_GROUP *group;
+    EC_POINT *pwe;
+    BIGNUM *order;
+    BIGNUM *prime;
+    BIGNUM *k;
+    BIGNUM *private_value;
+    BIGNUM *peer_scalar;
+    BIGNUM *my_scalar;
+    EC_POINT *my_element;
+    EC_POINT *peer_element;
+    unsigned char my_confirm[SHA256_DIGEST_LENGTH];
+} pwd_session_t;
+
+int compute_password_element(pwd_session_t *sess, unsigned short grp_num,
+                             char *password, int password_len,
+                             char *id_server, int id_server_len,
+                             char *id_peer, int id_peer_len, 
+                             unsigned long *token);
+int compute_scalar_element(pwd_session_t *sess, BN_CTX *bnctx);
+int process_peer_commit (pwd_session_t *sess, unsigned char *commit, BN_CTX *bnctx);
+int compute_server_confirm(pwd_session_t *sess, unsigned char *buf, BN_CTX *bnctx);
+int compute_peer_confirm(pwd_session_t *sess, unsigned char *buf, BN_CTX *bnctx);
+int compute_keys(pwd_session_t *sess, unsigned char *peer_confirm,
+                 unsigned char *msk, unsigned char *emsk);
+#ifdef PRINTBUF
+void print_buf(char *str, unsigned char *buf, int len);
+#endif  /* PRINTBUF */
+
+#endif  /* _EAP_PWD_H */
diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c b/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c
new file mode 100644 (file)
index 0000000..1b6b7e3
--- /dev/null
@@ -0,0 +1,599 @@
+/*
+ * Copyright (c) Dan Harkins, 2012
+ *
+ *  Copyright holder grants permission for redistribution and use in source 
+ *  and binary forms, with or without modification, provided that the 
+ *  following conditions are met:
+ *     1. Redistribution of source code must retain the above copyright
+ *        notice, this list of conditions, and the following disclaimer
+ *        in all source files.
+ *     2. Redistribution in binary form must retain the above copyright
+ *        notice, this list of conditions, and the following disclaimer
+ *        in the documentation and/or other materials provided with the
+ *        distribution.
+ *
+ *  "DISCLAIMER OF LIABILITY
+ *  
+ *  THIS SOFTWARE IS PROVIDED BY DAN HARKINS ``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 INDUSTRIAL LOUNGE 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."
+ *
+ * This license and distribution terms cannot be changed. In other words,
+ * this code cannot simply be copied and put under a different distribution
+ * license (including the GNU public license).
+ */
+
+#include <freeradius-devel/ident.h>
+RCSID("$Id$")
+
+#include <freeradius-devel/autoconf.h>
+
+#include "rlm_eap_pwd.h"
+
+#include "eap_pwd.h"
+
+#define MSK_EMSK_LEN    64
+#define MPPE_KEY_LEN    32
+
+static CONF_PARSER pwd_module_config[] = {
+    { "group", PW_TYPE_INTEGER,
+      offsetof(EAP_PWD_CONF, group), NULL, "19"},
+    { "fragment_size", PW_TYPE_INTEGER,
+      offsetof(EAP_PWD_CONF, fragment_size), NULL, "1020"},
+    { "server_id", PW_TYPE_STRING_PTR,
+      offsetof(EAP_PWD_CONF, server_id), NULL, NULL },
+    { NULL, -1, 0, NULL, NULL }
+};
+
+/*
+ * stolen from rlm_eap_sim: Add value pair to reply
+ */
+static void add_reply(VALUE_PAIR** vp,
+                     const char* name, const uint8_t *value, size_t len)
+{
+    VALUE_PAIR *reply_attr;
+    reply_attr = pairmake(name, "", T_OP_EQ);
+    if (!reply_attr) {
+        DEBUG("rlm_eap_pwd: "
+              "add_reply failed to create attribute %s: %s\n",
+              name, fr_strerror());
+        return;
+    }
+
+    memcpy(reply_attr->vp_strvalue, value, len);
+    reply_attr->length = len;
+    pairadd(vp, reply_attr);
+}
+
+static int
+eap_pwd_detach (void *arg)
+{
+    EAP_PWD_CONF *conf;
+    eap_pwd_t *inst;
+
+    inst = (eap_pwd_t *)arg;
+    if (inst == NULL) {
+        return -1;
+    }
+    conf = inst->conf;
+    if (conf != NULL) {
+        memset(conf, 0, sizeof(*conf));
+        free(inst->conf);
+        inst->conf = NULL;
+    }
+    if (inst->bnctx != NULL) {
+        BN_CTX_free(inst->bnctx);
+    }
+    free(inst);
+
+    return 0;
+}
+
+static int
+eap_pwd_attach (CONF_SECTION *cs, void **instance)
+{
+    EAP_PWD_CONF *conf;
+    eap_pwd_t *inst;
+
+    if ((inst = (eap_pwd_t *)malloc(sizeof(*inst))) == NULL) {
+        radlog(L_ERR, "rlm_eap_pwd: attach, out of memory (1)");
+        return -1;
+    }
+    if ((conf = (EAP_PWD_CONF *)malloc(sizeof(*conf))) == NULL) {
+        radlog(L_ERR, "rlm_eap_pwd: attach, out of memory (2)");
+        free(inst);
+        return -1;
+    }
+    memset(conf, 0, sizeof(*conf));
+    inst->conf = conf;
+    if (cf_section_parse(cs, conf, pwd_module_config) < 0) {
+        radlog(L_ERR, "rlm_eap_pwd: failed to initialize module");
+        eap_pwd_detach(inst);
+        return -1;
+    }
+    if ((inst->bnctx = BN_CTX_new()) == NULL) {
+        radlog(L_ERR, "rlm_eap_pwd: failed to get BN context!");
+        eap_pwd_detach(inst);
+        return -1;
+    }
+    *instance = inst;
+
+    return 0;
+}
+
+static void
+free_session (void *data)
+{
+    pwd_session_t *session = (pwd_session_t *)data;
+
+    if (session == NULL) {
+        radlog(L_ERR, "rlm_eap_pwd: freeing a NULL session...naughty, naughty");
+        return;
+    }
+    BN_free(session->private_value);
+    BN_free(session->peer_scalar);
+    BN_free(session->my_scalar);
+    BN_free(session->k);
+    EC_POINT_free(session->my_element);
+    EC_POINT_free(session->peer_element);
+    EC_GROUP_free(session->group);
+    EC_POINT_free(session->pwe);
+    BN_free(session->order);
+    BN_free(session->prime);
+
+    free(session);
+}
+
+static int
+send_pwd_request (pwd_session_t *sess, EAP_DS *eap_ds)
+{
+    int len;
+    unsigned short totlen;
+    pwd_hdr *hdr;
+
+    len = (sess->out_buf_len - sess->out_buf_pos) + sizeof(pwd_hdr);
+    eap_ds->request->code = PW_EAP_REQUEST;
+    eap_ds->request->type.type = PW_EAP_PWD;
+    eap_ds->request->type.length = (len > sess->mtu) ? sess->mtu : len;
+    eap_ds->request->type.data = malloc(eap_ds->request->type.length);
+    memset(eap_ds->request->type.data, 0, eap_ds->request->type.length);
+
+    hdr = (pwd_hdr *)eap_ds->request->type.data;
+    switch (sess->state) {
+        case PWD_STATE_ID_REQ:
+            EAP_PWD_SET_EXCHANGE(hdr, EAP_PWD_EXCH_ID);
+            break;
+        case PWD_STATE_COMMIT:
+            EAP_PWD_SET_EXCHANGE(hdr, EAP_PWD_EXCH_COMMIT);
+            break;
+        case PWD_STATE_CONFIRM:
+            EAP_PWD_SET_EXCHANGE(hdr, EAP_PWD_EXCH_CONFIRM);
+            break;
+        default:
+            DEBUG2("pwd state is messed up! Cannot send request");
+            return 0;
+    }
+    /*
+     * are we fragmenting?
+     */
+    if ((int)((sess->out_buf_len - sess->out_buf_pos) + sizeof(pwd_hdr)) > sess->mtu) {
+        EAP_PWD_SET_MORE_BIT(hdr);
+        if (sess->out_buf_pos == 0) {
+            /*
+             * the first fragment, add the total length
+             */
+            EAP_PWD_SET_LENGTH_BIT(hdr);
+            totlen = ntohs(sess->out_buf_len);
+            memcpy(hdr->data, (char *)&totlen, sizeof(unsigned short));
+            memcpy(hdr->data + sizeof(unsigned short),
+                   sess->out_buf,
+                   sess->mtu - sizeof(pwd_hdr) - sizeof(unsigned short));
+            sess->out_buf_pos += (sess->mtu - sizeof(pwd_hdr) - sizeof(unsigned short));
+        } else {
+            /*
+             * an intermediate fragment
+             */
+            memcpy(hdr->data, sess->out_buf + sess->out_buf_pos, (sess->mtu - sizeof(pwd_hdr)));
+            sess->out_buf_pos += (sess->mtu - sizeof(pwd_hdr));
+        }
+    } else {
+        /*
+         * either it's not a fragment or it's the last fragment. 
+         * The out buffer isn't needed anymore though so get rid of it.
+         */
+        memcpy(hdr->data, sess->out_buf + sess->out_buf_pos, 
+               (sess->out_buf_len - sess->out_buf_pos));
+        free(sess->out_buf);
+        sess->out_buf_pos = sess->out_buf_len = 0;
+    }
+    return 1;
+}
+
+static int
+eap_pwd_initiate (void *type_data, EAP_HANDLER *handler)
+{
+    pwd_session_t *pwd_session;
+    eap_pwd_t *inst = (eap_pwd_t *)type_data;
+    VALUE_PAIR *vp;
+    pwd_id_packet *pack;
+
+    if ((inst == NULL) ||
+        (handler == NULL)) {
+        radlog(L_ERR, "rlm_eap_pwd: initiate, NULL data provided");
+        return -1;
+    }
+    if ((pwd_session = (pwd_session_t *)malloc(sizeof(pwd_session_t))) == NULL) {
+        radlog(L_ERR, "rlm_eap_pwd: initiate, out of memory (1)");
+        return -1;
+    }
+    /*
+     * set things up so they can be free'd reliably
+     */
+    pwd_session->group_num = inst->conf->group;
+    pwd_session->private_value = NULL;
+    pwd_session->peer_scalar = NULL;
+    pwd_session->my_scalar = NULL;
+    pwd_session->k = NULL;
+    pwd_session->my_element = NULL;
+    pwd_session->peer_element = NULL;
+    pwd_session->group = NULL;
+    pwd_session->pwe = NULL;
+    pwd_session->order = NULL;
+    pwd_session->prime = NULL;
+
+    /*
+     * figure out the MTU (basically do what eap-tls does)
+     */
+    pwd_session->mtu = inst->conf->fragment_size;
+    vp = pairfind(handler->request->packet->vps, PW_FRAMED_MTU, 0);
+    if (vp && ((int)(vp->vp_integer - 9) < pwd_session->mtu)) {
+        /*
+         * 9 = 4 (EAPOL header) + 4 (EAP header) + 1 (EAP type)
+         *
+         * the fragmentation code deals with the included length
+         * so we don't need to subtract that here.
+         */
+        pwd_session->mtu = vp->vp_integer - 9;
+    }
+
+    pwd_session->state = PWD_STATE_ID_REQ;
+    pwd_session->in_buf_len = pwd_session->in_buf_pos = pwd_session->out_buf_pos = 0;
+    handler->opaque = pwd_session;
+    handler->free_opaque = free_session;
+
+    /*
+     * construct an EAP-pwd-ID/Request
+     */
+    pwd_session->out_buf_len = sizeof(pwd_id_packet) + strlen(inst->conf->server_id);
+    if ((pwd_session->out_buf = malloc(pwd_session->out_buf_len)) == NULL) {
+        radlog(L_ERR, "rlm_eap_pwd: initiate, out of memory to send ID request");
+        return -1;
+    }
+    memset(pwd_session->out_buf, 0, pwd_session->out_buf_len);
+
+    pack = (pwd_id_packet *)pwd_session->out_buf;
+    pack->group_num = htons(pwd_session->group_num);
+    pack->random_function = EAP_PWD_DEF_RAND_FUN;
+    pack->prf = EAP_PWD_DEF_PRF;
+    pwd_session->token = random();
+    memcpy(pack->token, (char *)&pwd_session->token, 4);
+    pack->prep = EAP_PWD_PREP_NONE;
+    strcpy(pack->identity, inst->conf->server_id);
+
+    handler->stage = AUTHENTICATE;
+
+    return send_pwd_request(pwd_session, handler->eap_ds);
+}
+
+static int
+eap_pwd_authenticate (void *arg, EAP_HANDLER *handler)
+{
+    pwd_session_t *pwd_session;
+    pwd_hdr *hdr;
+    pwd_id_packet *id;
+    EAP_PACKET *response;
+    REQUEST *request, *fake;
+    VALUE_PAIR *pw, **outvps;
+    EAP_DS *eap_ds;
+    int len, ret = 0;
+    eap_pwd_t *inst = (eap_pwd_t *)arg;
+    unsigned short offset;
+    unsigned char exch, *buf, *ptr, msk[MSK_EMSK_LEN], emsk[MSK_EMSK_LEN];
+    unsigned char peer_confirm[SHA256_DIGEST_LENGTH];
+    BIGNUM *x = NULL, *y = NULL;
+
+    if ((handler == NULL) || 
+        ((eap_ds = handler->eap_ds) == NULL) ||
+        (inst == NULL)) {
+        return 0;
+    }
+    pwd_session = (pwd_session_t *)handler->opaque;
+    request = handler->request;
+    response = handler->eap_ds->response;
+    hdr = (pwd_hdr *)response->type.data;
+
+    buf = hdr->data;
+    len = response->type.length - sizeof(pwd_hdr);
+
+    /*
+     * see if we're fragmenting, if so continue until we're done
+     */
+    if (pwd_session->out_buf_pos) {
+        if (len) {
+            RDEBUG2("pwd got something more than an ACK for a fragment");
+        }
+        return send_pwd_request(pwd_session, eap_ds);
+    }
+
+    /*
+     * the first fragment will have a total length, make a
+     * buffer to hold all the fragments
+     */
+    if (EAP_PWD_GET_LENGTH_BIT(hdr)) {
+        if (pwd_session->in_buf_len) {
+            RDEBUG2("pwd already alloced buffer for fragments");
+            return 0;
+        }
+        pwd_session->in_buf_len = ntohs(*((unsigned short *)buf));
+        if ((pwd_session->in_buf = malloc(pwd_session->in_buf_len)) == NULL) {
+            RDEBUG2("pwd cannot malloc %d buffer to hold fragments",
+                    pwd_session->in_buf_len);
+            return 0;
+        }
+        memset(pwd_session->in_buf, 0, pwd_session->in_buf_len);
+        pwd_session->in_buf_pos = 0;
+        buf += sizeof(unsigned short);
+        len -= sizeof(unsigned short);
+    }
+    /*
+     * all fragments, including the 1st will have the M(ore) bit set,
+     * buffer those fragments!
+     */
+    if (EAP_PWD_GET_MORE_BIT(hdr)) {
+        if ((pwd_session->in_buf_pos + len) > pwd_session->in_buf_len) {
+            RDEBUG2("pwd will not overflow a fragment buffer. Nope, not prudent.");
+            return 0;
+        }
+        memcpy(pwd_session->in_buf + pwd_session->in_buf_pos, buf, len);
+        pwd_session->in_buf_pos += len;
+
+        /*
+         * send back an ACK for this fragment
+         */
+        exch = EAP_PWD_GET_EXCHANGE(hdr);
+        eap_ds->request->code = PW_EAP_REQUEST;
+        eap_ds->request->type.type = PW_EAP_PWD;
+        eap_ds->request->type.length = sizeof(pwd_hdr);
+        if ((eap_ds->request->type.data = malloc(sizeof(pwd_hdr))) == NULL) {
+            radlog(L_ERR, "rlm_eap_pwd: fragment ACK, out of memory");
+            return 0;
+        }
+        hdr = (pwd_hdr *)eap_ds->request->type.data;
+        EAP_PWD_SET_EXCHANGE(hdr, exch);
+        return 1;
+
+    } else if (pwd_session->in_buf_len) {
+        /*
+         * the last fragment...
+         */
+        if ((pwd_session->in_buf_pos + len) > pwd_session->in_buf_len) {
+            RDEBUG2("pwd will not overflow a fragment buffer. Nope, not prudent.");
+            return 0;
+        }
+        memcpy(pwd_session->in_buf + pwd_session->in_buf_pos, buf, len);
+        buf = pwd_session->in_buf;
+        len = pwd_session->in_buf_len;
+    }
+
+    switch (pwd_session->state) {
+        case PWD_STATE_ID_REQ:
+            if (EAP_PWD_GET_EXCHANGE(hdr) != EAP_PWD_EXCH_ID) {
+                RDEBUG2("pwd exchange is incorrect: not ID");
+                return 0;
+            }
+            id = (pwd_id_packet *)buf;
+            if ((id->prf != EAP_PWD_DEF_PRF) ||
+                (id->random_function != EAP_PWD_DEF_RAND_FUN) ||
+                (id->prep != EAP_PWD_PREP_NONE) ||
+                (memcmp(id->token, (char *)&pwd_session->token, 4)) ||
+                (id->group_num != ntohs(pwd_session->group_num))) {
+                RDEBUG2("pwd id response is invalid");
+                return 0;
+            }
+            /*
+             * we've agreed on the ciphersuite, record it...
+             */
+            ptr = (unsigned char *)&pwd_session->ciphersuite;
+            memcpy(ptr, (char *)&id->group_num, sizeof(unsigned short));
+            ptr += sizeof(unsigned short);
+            *ptr = EAP_PWD_DEF_RAND_FUN;
+            ptr += sizeof(unsigned char);
+            *ptr = EAP_PWD_DEF_PRF;
+            
+            pwd_session->peer_id_len = len - sizeof(pwd_id_packet);
+            if (pwd_session->peer_id_len > MAX_STRING_LEN) {
+                RDEBUG2("pwd id response is malformed");
+                return 0;
+            }
+            memset(pwd_session->peer_id, 0, MAX_STRING_LEN);
+            strncpy(pwd_session->peer_id, id->identity,
+                    pwd_session->peer_id_len);
+
+            /*
+             * make fake request to get the password for the usable ID
+             */
+            if ((fake = request_alloc_fake(handler->request)) == NULL) {
+                RDEBUG("pwd unable to create fake request!");
+                return 0;
+            }
+            if ((fake->username = pairmake("User-Name", "", T_OP_EQ)) == NULL) {
+                RDEBUG("pwd unanable to create value pair for username!");
+                request_free(&fake);
+                return 0;
+            }
+            memcpy(fake->username->vp_strvalue, pwd_session->peer_id, 
+                   pwd_session->peer_id_len);
+            fake->username->length = pwd_session->peer_id_len;
+            fake->username->vp_strvalue[fake->username->length] = 0;
+            module_authorize(0, fake);
+
+            if ((pw = pairfind(fake->config_items, PW_CLEARTEXT_PASSWORD, 0)) == NULL) {
+                DEBUG2("failed to find password for %s to do pwd authentication",
+                       pwd_session->peer_id);
+                request_free(&fake);
+                return 0;
+            }
+
+            if (compute_password_element(pwd_session, pwd_session->group_num,
+                                         pw->data.strvalue, strlen(pw->data.strvalue),
+                                         inst->conf->server_id, strlen(inst->conf->server_id),
+                                         pwd_session->peer_id, strlen(pwd_session->peer_id),
+                                         &pwd_session->token)) {
+                DEBUG2("failed to obtain password element :-(");
+                request_free(&fake);
+                return 0;
+            }
+            request_free(&fake);
+
+            /*
+             * compute our scalar and element
+             */
+            if (compute_scalar_element(pwd_session, inst->bnctx)) {
+                DEBUG2("failed to compute server's scalar and element");
+                return 0;
+            }
+            if (((x = BN_new()) == NULL) ||
+                ((y = BN_new()) == NULL)) {
+                DEBUG2("server point allocation failed");
+                return 0;
+            }
+            /*
+             * element is a point, get both coordinates: x and y
+             */
+            if (!EC_POINT_get_affine_coordinates_GFp(pwd_session->group,
+                                                     pwd_session->my_element, x, y,
+                                                     inst->bnctx)) {
+                DEBUG2("server point assignment failed");
+                BN_free(x);
+                BN_free(y);
+                return 0;
+            }
+            /*
+             * construct request
+             */
+            pwd_session->out_buf_len = BN_num_bytes(pwd_session->order) + 
+                (2 * BN_num_bytes(pwd_session->prime));
+            if ((pwd_session->out_buf = malloc(pwd_session->out_buf_len)) == NULL) {
+                radlog(L_ERR, "rlm_eap_pwd: out of memory to send commit");
+                return 0;
+            }
+            memset(pwd_session->out_buf, 0, pwd_session->out_buf_len);
+
+            ptr = pwd_session->out_buf;
+            offset = BN_num_bytes(pwd_session->prime) - BN_num_bytes(x);
+            BN_bn2bin(x, ptr + offset);
+
+            ptr += BN_num_bytes(pwd_session->prime);
+            offset = BN_num_bytes(pwd_session->prime) - BN_num_bytes(y);
+            BN_bn2bin(y, ptr + offset);
+            
+            ptr += BN_num_bytes(pwd_session->prime);
+            offset = BN_num_bytes(pwd_session->order) - BN_num_bytes(pwd_session->my_scalar);
+            BN_bn2bin(pwd_session->my_scalar, ptr + offset);
+
+            pwd_session->state = PWD_STATE_COMMIT;
+            ret = send_pwd_request(pwd_session, eap_ds);
+            break;
+        case PWD_STATE_COMMIT:
+            if (EAP_PWD_GET_EXCHANGE(hdr) != EAP_PWD_EXCH_COMMIT) {
+                RDEBUG2("pwd exchange is incorrect: not commit!");
+                return 0;
+            }
+            /*
+             * process the peer's commit and generate the shared key, k
+             */
+            if (process_peer_commit(pwd_session, buf, inst->bnctx)) {
+                RDEBUG2("failed to process peer's commit");
+                return 0;
+            }
+
+            /*
+             * compute our confirm blob
+             */
+            if (compute_server_confirm(pwd_session, pwd_session->my_confirm, inst->bnctx)) {
+                radlog(L_ERR, "rlm_eap_pwd: failed to compute confirm!");
+                return 0;
+            }
+            /*
+             * construct a response...which is just our confirm blob
+             */
+            pwd_session->out_buf_len = SHA256_DIGEST_LENGTH;
+            if ((pwd_session->out_buf = malloc(pwd_session->out_buf_len)) == NULL) {
+                radlog(L_ERR, "rlm_eap_pwd: out of memory to send confirm");
+                return 0;
+            }
+            memset(pwd_session->out_buf, 0, pwd_session->out_buf_len);
+            memcpy(pwd_session->out_buf, pwd_session->my_confirm, SHA256_DIGEST_LENGTH);
+
+            pwd_session->state = PWD_STATE_CONFIRM;
+            ret = send_pwd_request(pwd_session, eap_ds);
+            break;
+        case PWD_STATE_CONFIRM:
+            if (EAP_PWD_GET_EXCHANGE(hdr) != EAP_PWD_EXCH_CONFIRM) {
+                RDEBUG2("pwd exchange is incorrect: not commit!");
+                return 0;
+            }
+            if (compute_peer_confirm(pwd_session, peer_confirm, inst->bnctx)) {
+                RDEBUG2("pwd exchange cannot compute peer's confirm");
+                return 0;
+            }
+            if (memcmp(peer_confirm, buf, SHA256_DIGEST_LENGTH)) {
+                RDEBUG2("pwd exchange fails: peer confirm is incorrect!");
+                return 0;
+            }
+            if (compute_keys(pwd_session, peer_confirm, msk, emsk)) {
+                RDEBUG2("pwd exchange cannot generate (E)MSK!");
+                return 0;
+            }
+            eap_ds->request->code = PW_EAP_SUCCESS;
+            /*
+             * return the MSK (in halves)
+             */
+            outvps = &handler->request->reply->vps;
+            add_reply(outvps, "MS-MPPE-Recv-Key", msk, MPPE_KEY_LEN);
+            add_reply(outvps, "MS-MPPE-Send-Key", msk+MPPE_KEY_LEN, MPPE_KEY_LEN);
+            ret = 1;
+            break;
+        default:
+            RDEBUG2("unknown PWD state");
+            return 0;
+    }
+    /*
+     * we processed the buffered fragments, get rid of them
+     */
+    if (pwd_session->in_buf_len) {
+        free(pwd_session->in_buf);
+        pwd_session->in_buf_len = pwd_session->in_buf_pos = 0;
+    }
+
+    return ret;
+}
+
+
+EAP_TYPE rlm_eap_pwd = {
+    "eap_pwd",
+    eap_pwd_attach,                     /* attach */
+    eap_pwd_initiate,                   /* initiate to a client */
+    NULL,                               /* no authorization */
+    eap_pwd_authenticate,               /* pwd authentication */
+    eap_pwd_detach                      /* detach */
+};
+
diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h b/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h
new file mode 100644 (file)
index 0000000..70cedd5
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) Dan Harkins, 2012
+ *
+ *  Copyright holder grants permission for redistribution and use in source 
+ *  and binary forms, with or without modification, provided that the 
+ *  following conditions are met:
+ *     1. Redistribution of source code must retain the above copyright
+ *        notice, this list of conditions, and the following disclaimer
+ *        in all source files.
+ *     2. Redistribution in binary form must retain the above copyright
+ *        notice, this list of conditions, and the following disclaimer
+ *        in the documentation and/or other materials provided with the
+ *        distribution.
+ *
+ *  "DISCLAIMER OF LIABILITY
+ *  
+ *  THIS SOFTWARE IS PROVIDED BY DAN HARKINS ``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 INDUSTRIAL LOUNGE 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."
+ *
+ * This license and distribution terms cannot be changed. In other words,
+ * this code cannot simply be copied and put under a different distribution
+ * license (including the GNU public license).
+ */
+
+#ifndef _RLM_EAP_PWD_H
+#define _RLM_EAP_PWD_H
+
+#include "eap_pwd.h"
+
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/modules.h>
+
+typedef struct eap_pwd_conf {
+    int group;
+    int fragment_size;
+    char *server_id;
+} EAP_PWD_CONF;
+
+typedef struct _eap_pwd_t {
+    EAP_PWD_CONF *conf;
+    BN_CTX *bnctx;
+} eap_pwd_t;
+
+#endif  /* _RLM_EAP_PWD_H */