+ 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 = talloc_zero_array(sess, uint8_t, primebytelen)) == NULL) {
+ DEBUG("unable to alloc space for prf buffer");
+ goto fail;
+ }
+ 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, (uint8_t *)token, sizeof(*token));
+ H_Update(&ctx, (uint8_t const *)id_peer, id_peer_len);
+ H_Update(&ctx, (uint8_t const *)id_server, id_server_len);
+ H_Update(&ctx, (uint8_t const *)password, password_len);
+ H_Update(&ctx, (uint8_t *)&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...
+ */
+ is_odd = BN_is_odd(rnd) ? 1 : 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: /* DON'T free sess, it's in handler->opaque */
+ ret = -1;
+ }
+
+ /* cleanliness and order.... */
+ BN_free(cofactor);
+ BN_free(x_candidate);
+ BN_free(rnd);
+ talloc_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, uint8_t *commit, BN_CTX *bnctx)
+{
+ uint8_t *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 finish;
+ }
+
+ if (!EC_GROUP_get_cofactor(sess->group, cofactor, NULL)) {
+ DEBUG2("pwd: unable to get group co-factor");
+ goto finish;
+ }
+
+ /* element, x then y, followed by scalar */
+ ptr = (uint8_t *)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 finish;
+ }
+
+ /* 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 finish;
+ }
+
+ if (EC_POINT_is_at_infinity(sess->group, point)) {
+ DEBUG2("pwd: peer's element is in small sub-group");
+ goto finish;
+ }
+ }
+
+ /* 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 finish;
+ }
+
+ /* 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 finish;
+ }