--- /dev/null
+/*
+ * 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;
+}
+
+
+
+
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
+};
+
--- /dev/null
+/*
+ * 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 */