Implement DLEQ checks for Trust Token.
Change-Id: I6f263b775aafad6616b31af59096c3b4229fe3e1
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/40684
Commit-Queue: Steven Valdez <svaldez@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/crypto/err/trust_token.errordata b/crypto/err/trust_token.errordata
index d7d9946..7e612dc 100644
--- a/crypto/err/trust_token.errordata
+++ b/crypto/err/trust_token.errordata
@@ -5,6 +5,7 @@
TRUST_TOKEN,109,INVALID_KEY_ID
TRUST_TOKEN,106,INVALID_METADATA
TRUST_TOKEN,113,INVALID_METADATA_KEY
+TRUST_TOKEN,114,INVALID_PROOF
TRUST_TOKEN,110,INVALID_TOKEN
TRUST_TOKEN,100,KEYGEN_FAILURE
TRUST_TOKEN,108,NO_KEYS_CONFIGURED
diff --git a/crypto/fipsmodule/ec/ec.c b/crypto/fipsmodule/ec/ec.c
index e16a379..3a7d4d5 100644
--- a/crypto/fipsmodule/ec/ec.c
+++ b/crypto/fipsmodule/ec/ec.c
@@ -1042,6 +1042,13 @@
return 1;
}
+void ec_point_select(const EC_GROUP *group, EC_RAW_POINT *out, BN_ULONG mask,
+ const EC_RAW_POINT *a, const EC_RAW_POINT *b) {
+ ec_felem_select(group, &out->X, mask, &a->X, &b->X);
+ ec_felem_select(group, &out->Y, mask, &a->Y, &b->Y);
+ ec_felem_select(group, &out->Z, mask, &a->Z, &b->Z);
+}
+
int ec_cmp_x_coordinate(const EC_GROUP *group, const EC_RAW_POINT *p,
const EC_SCALAR *r) {
return group->meth->cmp_x_coordinate(group, p, r);
diff --git a/crypto/fipsmodule/ec/internal.h b/crypto/fipsmodule/ec/internal.h
index ebae837..47ccdbd 100644
--- a/crypto/fipsmodule/ec/internal.h
+++ b/crypto/fipsmodule/ec/internal.h
@@ -145,6 +145,10 @@
void ec_scalar_add(const EC_GROUP *group, EC_SCALAR *r, const EC_SCALAR *a,
const EC_SCALAR *b);
+// ec_scalar_sub sets |r| to |a| - |b|.
+void ec_scalar_sub(const EC_GROUP *group, EC_SCALAR *r, const EC_SCALAR *a,
+ const EC_SCALAR *b);
+
// ec_scalar_to_montgomery sets |r| to |a| in Montgomery form.
void ec_scalar_to_montgomery(const EC_GROUP *group, EC_SCALAR *r,
const EC_SCALAR *a);
@@ -270,6 +274,11 @@
const EC_RAW_POINT *p,
const EC_SCALAR *p_scalar);
+// ec_point_select, in constant time, sets |out| to |a| if |mask| is all ones
+// and |b| if |mask| is all zeros.
+void ec_point_select(const EC_GROUP *group, EC_RAW_POINT *out, BN_ULONG mask,
+ const EC_RAW_POINT *a, const EC_RAW_POINT *b);
+
// ec_cmp_x_coordinate compares the x (affine) coordinate of |p|, mod the group
// order, with |r|. It returns one if the values match and zero if |p| is the
// point at infinity of the values do not match.
diff --git a/crypto/fipsmodule/ec/scalar.c b/crypto/fipsmodule/ec/scalar.c
index af055fc..3b4a7d8 100644
--- a/crypto/fipsmodule/ec/scalar.c
+++ b/crypto/fipsmodule/ec/scalar.c
@@ -98,6 +98,14 @@
OPENSSL_cleanse(tmp, sizeof(tmp));
}
+void ec_scalar_sub(const EC_GROUP *group, EC_SCALAR *r, const EC_SCALAR *a,
+ const EC_SCALAR *b) {
+ const BIGNUM *order = &group->order;
+ BN_ULONG tmp[EC_MAX_WORDS];
+ bn_mod_sub_words(r->words, a->words, b->words, order->d, tmp, order->width);
+ OPENSSL_cleanse(tmp, sizeof(tmp));
+}
+
void ec_scalar_select(const EC_GROUP *group, EC_SCALAR *out, BN_ULONG mask,
const EC_SCALAR *a, const EC_SCALAR *b) {
const BIGNUM *order = &group->order;
diff --git a/crypto/fipsmodule/ec/simple_mul.c b/crypto/fipsmodule/ec/simple_mul.c
index 4ed6c48..9a43120 100644
--- a/crypto/fipsmodule/ec/simple_mul.c
+++ b/crypto/fipsmodule/ec/simple_mul.c
@@ -60,9 +60,7 @@
OPENSSL_memset(&tmp, 0, sizeof(EC_RAW_POINT));
for (size_t j = 0; j < OPENSSL_ARRAY_SIZE(precomp); j++) {
BN_ULONG mask = constant_time_eq_w(j, window);
- ec_felem_select(group, &tmp.X, mask, &precomp[j].X, &tmp.X);
- ec_felem_select(group, &tmp.Y, mask, &precomp[j].Y, &tmp.Y);
- ec_felem_select(group, &tmp.Z, mask, &precomp[j].Z, &tmp.Z);
+ ec_point_select(group, &tmp, mask, &precomp[j], &tmp);
}
if (r_is_at_infinity) {
diff --git a/crypto/trust_token/internal.h b/crypto/trust_token/internal.h
index 92be6ee..6edd111 100644
--- a/crypto/trust_token/internal.h
+++ b/crypto/trust_token/internal.h
@@ -34,6 +34,29 @@
// protocol.
#define PMBTOKEN_NONCE_SIZE 64
+// Structure representing a single Trust Token public key with the specified ID.
+struct trust_token_client_key_st {
+ uint32_t id;
+ EC_RAW_POINT pub0;
+ EC_RAW_POINT pub1;
+ EC_RAW_POINT pubs;
+};
+
+// Structure representing a single Trust Token private key with the specified
+// ID.
+struct trust_token_issuer_key_st {
+ uint32_t id;
+ EC_SCALAR x0;
+ EC_SCALAR y0;
+ EC_SCALAR x1;
+ EC_SCALAR y1;
+ EC_SCALAR xs;
+ EC_SCALAR ys;
+ EC_RAW_POINT pub0;
+ EC_RAW_POINT pub1;
+ EC_RAW_POINT pubs;
+};
+
// PMBTOKEN_PRETOKEN represents the intermediate state a client keeps during a
// PMBToken issuance operation.
typedef struct pmb_pretoken_st {
@@ -60,6 +83,10 @@
// PMBTOKEN_TOKEN_free releases the memory associated with |token|.
void PMBTOKEN_TOKEN_free(PMBTOKEN_TOKEN *token);
+// pmbtoken_compute_public computes the public keypairs from the private
+// keypairs in |key|. It returns one on success and zero on failure.
+int pmbtoken_compute_public(struct trust_token_issuer_key_st *key);
+
// pmbtoken_blind generates a new blinded pretoken based on the configuration of
// |ctx| as per the first stage of the AT.Usr operation and returns the
// resulting pretoken.
@@ -68,19 +95,24 @@
// pmbtoken_sign signs a blinded point based on the configuration of |ctx|
// and the key specified by |key_id| with a private metadata value of
// |private_metadata| as per the AT.Sig operation and stores the resulting nonce
-// and points in |*out_s|, |*out_Wp|, and |*out_Wsp|. It returns one on success
+// and points in |*out_s|, |*out_Wp|, and |*out_Wsp| and the resulting DLEQ
+// proof in |*out_proof|. The caller takes ownership of |*out_proof| and is
+// responsible for freeing it using |OPENSSL_free|. It returns one on success
// and zero on failure.
int pmbtoken_sign(const TRUST_TOKEN_ISSUER *ctx,
uint8_t out_s[PMBTOKEN_NONCE_SIZE], EC_RAW_POINT *out_Wp,
- EC_RAW_POINT *out_Wsp, const EC_RAW_POINT *Tp,
+ EC_RAW_POINT *out_Wsp, uint8_t **out_proof,
+ size_t *out_proof_len, const EC_RAW_POINT *Tp,
uint32_t key_id, uint8_t private_metadata);
// pmbtoken_unblind unblinds the result of an AT.Sig operation as per the final
// stage of the AT.Usr operation and sets |*out_token| to the resulting token.
// It returns one on success and zero on failure.
int pmbtoken_unblind(PMBTOKEN_TOKEN *out_token,
+ const struct trust_token_client_key_st *key,
const uint8_t s[PMBTOKEN_NONCE_SIZE],
const EC_RAW_POINT *Wp, const EC_RAW_POINT *Wsp,
+ const uint8_t *proof, size_t proof_len,
const PMBTOKEN_PRETOKEN *pretoken);
// pmbtoken_read verifies the validity of a PMBToken |token| using the key
@@ -90,26 +122,6 @@
int pmbtoken_read(const TRUST_TOKEN_ISSUER *ctx, uint8_t *out_private_metadata,
const PMBTOKEN_TOKEN *token, uint32_t key_id);
-// Structure representing a single Trust Token public key with the specified ID.
-struct trust_token_client_key_st {
- uint32_t id;
- EC_RAW_POINT pub0;
- EC_RAW_POINT pub1;
- EC_RAW_POINT pubs;
-};
-
-// Structure representing a single Trust Token private key with the specified
-// ID.
-struct trust_token_issuer_key_st {
- uint32_t id;
- EC_SCALAR x0;
- EC_SCALAR y0;
- EC_SCALAR x1;
- EC_SCALAR y1;
- EC_SCALAR xs;
- EC_SCALAR ys;
-};
-
struct trust_token_client_st {
// max_batchsize is the maximum supported batchsize.
uint16_t max_batchsize;
diff --git a/crypto/trust_token/pmbtoken.c b/crypto/trust_token/pmbtoken.c
index a7a87c8..080368a 100644
--- a/crypto/trust_token/pmbtoken.c
+++ b/crypto/trust_token/pmbtoken.c
@@ -122,9 +122,9 @@
return ec_point_from_uncompressed(group, out_h, kH, sizeof(kH));
}
-static int mul_g_and_p(const EC_GROUP *group, EC_RAW_POINT *out,
- const EC_RAW_POINT *g, const EC_SCALAR *g_scalar,
- const EC_RAW_POINT *p, const EC_SCALAR *p_scalar) {
+static int mul_twice(const EC_GROUP *group, EC_RAW_POINT *out,
+ const EC_RAW_POINT *g, const EC_SCALAR *g_scalar,
+ const EC_RAW_POINT *p, const EC_SCALAR *p_scalar) {
EC_RAW_POINT tmp1, tmp2;
if (!ec_point_mul_scalar(group, &tmp1, g, g_scalar) ||
!ec_point_mul_scalar(group, &tmp2, p, p_scalar)) {
@@ -135,22 +135,54 @@
return 1;
}
+static int mul_twice_base(const EC_GROUP *group, EC_RAW_POINT *out,
+ const EC_SCALAR *base_scalar, const EC_RAW_POINT *p,
+ const EC_SCALAR *p_scalar) {
+ EC_RAW_POINT tmp1, tmp2;
+ if (!ec_point_mul_scalar_base(group, &tmp1, base_scalar) ||
+ !ec_point_mul_scalar(group, &tmp2, p, p_scalar)) {
+ return 0;
+ }
+
+ group->meth->add(group, out, &tmp1, &tmp2);
+ return 1;
+}
+
+// (v0;v1) = p_scalar*(G;p1) + q_scalar*(q0;q1) - r_scalar*(r0;r1)
+static int mul_add_and_sub(const EC_GROUP *group, EC_RAW_POINT *out_v0,
+ EC_RAW_POINT *out_v1, const EC_RAW_POINT *p1,
+ const EC_SCALAR *p_scalar, const EC_RAW_POINT *q0,
+ const EC_RAW_POINT *q1, const EC_SCALAR *q_scalar,
+ const EC_RAW_POINT *r0, const EC_RAW_POINT *r1,
+ const EC_SCALAR *r_scalar) {
+ EC_RAW_POINT tmp0, tmp1, v0, v1;
+ if (!mul_twice_base(group, &v0, p_scalar, q0, q_scalar) ||
+ !mul_twice(group, &v1, p1, p_scalar, q1, q_scalar) ||
+ !ec_point_mul_scalar(group, &tmp0, r0, r_scalar) ||
+ !ec_point_mul_scalar(group, &tmp1, r1, r_scalar)) {
+ return 0;
+ }
+ ec_GFp_simple_invert(group, &tmp0);
+ ec_GFp_simple_invert(group, &tmp1);
+ group->meth->add(group, out_v0, &v0, &tmp0);
+ group->meth->add(group, out_v1, &v1, &tmp1);
+ return 1;
+}
+
// generate_keypair generates a keypair for the PMBTokens construction.
// |out_x| and |out_y| are set to the secret half of the keypair, while
// |*out_pub| is set to the public half of the keypair. It returns one on
// success and zero on failure.
static int generate_keypair(EC_SCALAR *out_x, EC_SCALAR *out_y,
EC_RAW_POINT *out_pub, const EC_GROUP *group) {
- EC_RAW_POINT h, tmp1, tmp2;
+ EC_RAW_POINT h;
if (!get_h(&h) ||
!ec_random_nonzero_scalar(group, out_x, kDefaultAdditionalData) ||
!ec_random_nonzero_scalar(group, out_y, kDefaultAdditionalData) ||
- !ec_point_mul_scalar_base(group, &tmp1, out_x) ||
- !ec_point_mul_scalar(group, &tmp2, &h, out_y)) {
+ !mul_twice_base(group, out_pub, out_x, &h, out_y)) {
OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
return 0;
}
- group->meth->add(group, out_pub, &tmp1, &tmp2);
return 1;
}
@@ -239,6 +271,23 @@
OPENSSL_free(token);
}
+int pmbtoken_compute_public(struct trust_token_issuer_key_st *key) {
+ EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp521r1);
+ if (group == NULL) {
+ return 0;
+ }
+
+ EC_RAW_POINT h;
+ if (!get_h(&h) ||
+ !mul_twice_base(group, &key->pubs, &key->xs, &h, &key->ys) ||
+ !mul_twice_base(group, &key->pub0, &key->x0, &h, &key->y0) ||
+ !mul_twice_base(group, &key->pub1, &key->x1, &h, &key->y1)) {
+ return 0;
+ }
+
+ return 1;
+}
+
// hash_t implements the H_t operation in PMBTokens. It returns on on success
// and zero on error.
static int hash_t(EC_GROUP *group, EC_RAW_POINT *out,
@@ -314,9 +363,367 @@
return NULL;
}
+static int hash_c(const EC_GROUP *group, EC_SCALAR *out, uint8_t *buf,
+ size_t len) {
+ const uint8_t kHashCLabel[] = "PMBTokensV0 HashC";
+ return ec_hash_to_scalar_p521_xmd_sha512(group, out, kHashCLabel,
+ sizeof(kHashCLabel), buf, len);
+}
+
+static int scalar_to_cbb(CBB *out, const EC_GROUP *group,
+ const EC_SCALAR *scalar) {
+ uint8_t *buf;
+ size_t scalar_len = BN_num_bytes(&group->order);
+ if (!CBB_add_space(out, &buf, scalar_len)) {
+ OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+ return 0;
+ }
+ ec_scalar_to_bytes(group, buf, &scalar_len, scalar);
+ return 1;
+}
+
+static int scalar_from_cbs(CBS *cbs, const EC_GROUP *group, EC_SCALAR *out) {
+ size_t scalar_len = BN_num_bytes(&group->order);
+ CBS tmp;
+ if (!CBS_get_bytes(cbs, &tmp, scalar_len)) {
+ OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+ return 0;
+ }
+
+ ec_scalar_from_bytes(group, out, CBS_data(&tmp), CBS_len(&tmp));
+ return 1;
+}
+
+static int hash_c_dleq(const EC_GROUP *group, EC_SCALAR *out,
+ const EC_RAW_POINT *X, const EC_RAW_POINT *T,
+ const EC_RAW_POINT *S, const EC_RAW_POINT *W,
+ const EC_RAW_POINT *K0, const EC_RAW_POINT *K1) {
+ static const uint8_t kDLEQ2Label[] = "DLEQ2";
+
+ int ok = 0;
+ CBB cbb;
+ CBB_zero(&cbb);
+ uint8_t *buf = NULL;
+ size_t len;
+ if (!CBB_init(&cbb, 0) ||
+ !CBB_add_bytes(&cbb, kDLEQ2Label, sizeof(kDLEQ2Label)) ||
+ !point_to_cbb(&cbb, group, X) ||
+ !point_to_cbb(&cbb, group, T) ||
+ !point_to_cbb(&cbb, group, S) ||
+ !point_to_cbb(&cbb, group, W) ||
+ !point_to_cbb(&cbb, group, K0) ||
+ !point_to_cbb(&cbb, group, K1) ||
+ !CBB_finish(&cbb, &buf, &len) ||
+ !hash_c(group, out, buf, len)) {
+ OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+ goto err;
+ }
+
+ ok = 1;
+
+err:
+ CBB_cleanup(&cbb);
+ OPENSSL_free(buf);
+ return ok;
+}
+
+static int hash_c_dleqor(const EC_GROUP *group, EC_SCALAR *out,
+ const EC_RAW_POINT *X0, const EC_RAW_POINT *X1,
+ const EC_RAW_POINT *T, const EC_RAW_POINT *S,
+ const EC_RAW_POINT *W, const EC_RAW_POINT *K00,
+ const EC_RAW_POINT *K01, const EC_RAW_POINT *K10,
+ const EC_RAW_POINT *K11) {
+ static const uint8_t kDLEQOR2Label[] = "DLEQOR2";
+
+ int ok = 0;
+ CBB cbb;
+ CBB_zero(&cbb);
+ uint8_t *buf = NULL;
+ size_t len;
+ if (!CBB_init(&cbb, 0) ||
+ !CBB_add_bytes(&cbb, kDLEQOR2Label, sizeof(kDLEQOR2Label)) ||
+ !point_to_cbb(&cbb, group, X0) ||
+ !point_to_cbb(&cbb, group, X1) ||
+ !point_to_cbb(&cbb, group, T) ||
+ !point_to_cbb(&cbb, group, S) ||
+ !point_to_cbb(&cbb, group, W) ||
+ !point_to_cbb(&cbb, group, K00) ||
+ !point_to_cbb(&cbb, group, K01) ||
+ !point_to_cbb(&cbb, group, K10) ||
+ !point_to_cbb(&cbb, group, K11) ||
+ !CBB_finish(&cbb, &buf, &len) ||
+ !hash_c(group, out, buf, len)) {
+ OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+ goto err;
+ }
+
+ ok = 1;
+
+err:
+ CBB_cleanup(&cbb);
+ OPENSSL_free(buf);
+ return ok;
+}
+
+// The DLEQ2 and DLEQOR2 constructions are described in appendix B of
+// https://eprint.iacr.org/2020/072/20200324:214215. DLEQ2 is an instance of
+// DLEQOR2 with only one value (n=1).
+
+static int dleq_generate(const EC_GROUP *group, uint8_t **out_proof,
+ size_t *out_proof_len,
+ const struct trust_token_issuer_key_st *priv,
+ const EC_RAW_POINT *T, const EC_RAW_POINT *S,
+ const EC_RAW_POINT *W, const EC_RAW_POINT *Ws,
+ uint8_t private_metadata) {
+ int ok = 0;
+ CBB proof;
+ if (!CBB_init(&proof, 0)) {
+ OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+ return 0;
+ }
+
+ EC_RAW_POINT h;
+ if (!get_h(&h)) {
+ goto err;
+ }
+
+ // Generate DLEQ2 proof for the validity token.
+
+ // ks0, ks1 <- Zp
+ EC_SCALAR ks0, ks1;
+ if (!ec_random_nonzero_scalar(group, &ks0, kDefaultAdditionalData) ||
+ !ec_random_nonzero_scalar(group, &ks1, kDefaultAdditionalData)) {
+ goto err;
+ }
+
+ // Ks = ks0*(G;T) + ks1*(H;S)
+ EC_RAW_POINT Ks0, Ks1;
+ if (!mul_twice_base(group, &Ks0, &ks0, &h, &ks1) ||
+ !mul_twice(group, &Ks1, T, &ks0, S, &ks1)) {
+ goto err;
+ }
+
+ // cs = Hc(...)
+ EC_SCALAR cs;
+ if (!hash_c_dleq(group, &cs, &priv->pubs, T, S, Ws, &Ks0, &Ks1)) {
+ goto err;
+ }
+
+ EC_SCALAR cs_mont;
+ ec_scalar_to_montgomery(group, &cs_mont, &cs);
+
+ // In each of these products, only one operand is in Montgomery form, so the
+ // product does not need to be converted.
+
+ // us = ks0 + cs*xs
+ EC_SCALAR us;
+ ec_scalar_mul_montgomery(group, &us, &priv->xs, &cs_mont);
+ ec_scalar_add(group, &us, &ks0, &us);
+
+ // vs = ks1 + cs*ys
+ EC_SCALAR vs;
+ ec_scalar_mul_montgomery(group, &vs, &priv->ys, &cs_mont);
+ ec_scalar_add(group, &vs, &ks1, &vs);
+
+ // Store DLEQ2 proof in transcript.
+ if (!scalar_to_cbb(&proof, group, &cs) ||
+ !scalar_to_cbb(&proof, group, &us) ||
+ !scalar_to_cbb(&proof, group, &vs)) {
+ OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+ goto err;
+ }
+
+ // Generate DLEQOR2 proof for the private metadata token.
+ BN_ULONG mask = ((BN_ULONG)0) - (private_metadata&1);
+
+ // Select values of xb, yb (keys corresponding to the private metadata value)
+ // and pubo (public key corresponding to the other value) in constant time.
+ EC_RAW_POINT pubo;
+ EC_SCALAR xb, yb;
+ ec_scalar_select(group, &xb, mask, &priv->x1, &priv->x0);
+ ec_scalar_select(group, &yb, mask, &priv->y1, &priv->y0);
+ ec_point_select(group, &pubo, mask, &priv->pub0, &priv->pub1);
+
+ // k0, k1 <- Zp
+ EC_SCALAR k0, k1;
+ if (!ec_random_nonzero_scalar(group, &k0, kDefaultAdditionalData) ||
+ !ec_random_nonzero_scalar(group, &k1, kDefaultAdditionalData)) {
+ goto err;
+ }
+
+ // Kb = k0*(G;T) + k1*(H;S)
+ EC_RAW_POINT Kb0, Kb1;
+ if (!mul_twice_base(group, &Kb0, &k0, &h, &k1) ||
+ !mul_twice(group, &Kb1, T, &k0, S, &k1)) {
+ goto err;
+ }
+
+ // co, uo, vo <- Zp
+ EC_SCALAR co, uo, vo;
+ if (!ec_random_nonzero_scalar(group, &co, kDefaultAdditionalData) ||
+ !ec_random_nonzero_scalar(group, &uo, kDefaultAdditionalData) ||
+ !ec_random_nonzero_scalar(group, &vo, kDefaultAdditionalData)) {
+ goto err;
+ }
+
+ // Ko = uo*(G;T) + vo*(H;S) - co*(pubo;W)
+ EC_RAW_POINT Ko0, Ko1;
+ if (!mul_add_and_sub(group, &Ko0, &Ko1, T, &uo, &h, S, &vo, &pubo, W, &co)) {
+ goto err;
+ }
+
+ // Select the K corresponding to K0 and K1 in constant-time.
+ EC_RAW_POINT K00, K01, K10, K11;
+ ec_point_select(group, &K00, mask, &Ko0, &Kb0);
+ ec_point_select(group, &K01, mask, &Ko1, &Kb1);
+ ec_point_select(group, &K10, mask, &Kb0, &Ko0);
+ ec_point_select(group, &K11, mask, &Kb1, &Ko1);
+
+ // c = Hc(...)
+ EC_SCALAR c;
+ if (!hash_c_dleqor(group, &c, &priv->pub0, &priv->pub1, T, S, W, &K00, &K01,
+ &K10, &K11)) {
+ goto err;
+ }
+
+ // cb = c - co
+ EC_SCALAR cb, ub, vb;
+ ec_scalar_sub(group, &cb, &c, &co);
+
+ EC_SCALAR cb_mont;
+ ec_scalar_to_montgomery(group, &cb_mont, &cb);
+
+ // In each of these products, only one operand is in Montgomery form, so the
+ // product does not need to be converted.
+
+ // ub = k0 + cb*xb
+ ec_scalar_mul_montgomery(group, &ub, &xb, &cb_mont);
+ ec_scalar_add(group, &ub, &k0, &ub);
+
+ // vb = k1 + cb*yb
+ ec_scalar_mul_montgomery(group, &vb, &yb, &cb_mont);
+ ec_scalar_add(group, &vb, &k1, &vb);
+
+ // Select c, u, v in constant-time.
+ EC_SCALAR c0, c1, u0, u1, v0, v1;
+ ec_scalar_select(group, &c0, mask, &co, &cb);
+ ec_scalar_select(group, &u0, mask, &uo, &ub);
+ ec_scalar_select(group, &v0, mask, &vo, &vb);
+ ec_scalar_select(group, &c1, mask, &cb, &co);
+ ec_scalar_select(group, &u1, mask, &ub, &uo);
+ ec_scalar_select(group, &v1, mask, &vb, &vo);
+
+ // Store DLEQOR2 proof in transcript.
+ if (!scalar_to_cbb(&proof, group, &c0) ||
+ !scalar_to_cbb(&proof, group, &c1) ||
+ !scalar_to_cbb(&proof, group, &u0) ||
+ !scalar_to_cbb(&proof, group, &u1) ||
+ !scalar_to_cbb(&proof, group, &v0) ||
+ !scalar_to_cbb(&proof, group, &v1) ||
+ !CBB_finish(&proof, out_proof, out_proof_len)) {
+ OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+ goto err;
+ }
+
+ ok = 1;
+
+err:
+ CBB_cleanup(&proof);
+ return ok;
+}
+
+static int dleq_verify(const EC_GROUP *group, const uint8_t *proof,
+ size_t proof_len,
+ const struct trust_token_client_key_st *pub,
+ const EC_RAW_POINT *T, const EC_RAW_POINT *S,
+ const EC_RAW_POINT *W, const EC_RAW_POINT *Ws) {
+ EC_RAW_POINT h;
+ if (!get_h(&h)) {
+ return 0;
+ }
+
+ // Verify the DLEQ2 proof over the validity token.
+
+ CBS cbs;
+ CBS_init(&cbs, proof, proof_len);
+ EC_SCALAR cs, us, vs;
+ if (!scalar_from_cbs(&cbs, group, &cs) ||
+ !scalar_from_cbs(&cbs, group, &us) ||
+ !scalar_from_cbs(&cbs, group, &vs)) {
+ OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+ return 0;
+ }
+
+ // Ks = us*(G;T) + vs*(H;S) - cs*(pubs;Ws)
+ EC_RAW_POINT Ks0, Ks1;
+ if (!mul_add_and_sub(group, &Ks0, &Ks1, T, &us, &h, S, &vs, &pub->pubs, Ws,
+ &cs)) {
+ return 0;
+ }
+
+ // calculated = Hc(...)
+ EC_SCALAR calculated;
+ if (!hash_c_dleq(group, &calculated, &pub->pubs, T, S, Ws, &Ks0, &Ks1)) {
+ return 0;
+ }
+
+ // cs == calculated
+ if (!ec_scalar_equal_vartime(group, &cs, &calculated)) {
+ OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_INVALID_PROOF);
+ return 0;
+ }
+
+ // Verify the DLEQOR2 proof over the private metadata token.
+
+ EC_SCALAR c0, c1, u0, u1, v0, v1;
+ if (!scalar_from_cbs(&cbs, group, &c0) ||
+ !scalar_from_cbs(&cbs, group, &c1) ||
+ !scalar_from_cbs(&cbs, group, &u0) ||
+ !scalar_from_cbs(&cbs, group, &u1) ||
+ !scalar_from_cbs(&cbs, group, &v0) ||
+ !scalar_from_cbs(&cbs, group, &v1) ||
+ CBS_len(&cbs) != 0) {
+ OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+ return 0;
+ }
+
+ // K0 = u0*(G;T) + v0*(H;S) - c0*(pub0;W)
+ EC_RAW_POINT K00, K01;
+ if (!mul_add_and_sub(group, &K00, &K01, T, &u0, &h, S, &v0, &pub->pub0, W,
+ &c0)) {
+ return 0;
+ }
+
+ // K1 = u1*(G;T) + v1*(H;S) - c1*(pub1;Ws)
+ EC_RAW_POINT K10, K11;
+ if (!mul_add_and_sub(group, &K10, &K11, T, &u1, &h, S, &v1, &pub->pub1, W,
+ &c1)) {
+ return 0;
+ }
+
+ // calculated = Hc(...)
+ if (!hash_c_dleqor(group, &calculated, &pub->pub0, &pub->pub1, T, S, W, &K00,
+ &K01, &K10, &K11)) {
+ return 0;
+ }
+
+ // c = c0 + c1
+ EC_SCALAR c;
+ ec_scalar_add(group, &c, &c0, &c1);
+
+ // c == calculated
+ if (!ec_scalar_equal_vartime(group, &c, &calculated)) {
+ OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_INVALID_PROOF);
+ return 0;
+ }
+
+ return 1;
+}
+
int pmbtoken_sign(const TRUST_TOKEN_ISSUER *ctx,
uint8_t out_s[PMBTOKEN_NONCE_SIZE], EC_RAW_POINT *out_Wp,
- EC_RAW_POINT *out_Wsp, const EC_RAW_POINT *Tp,
+ EC_RAW_POINT *out_Wsp, uint8_t **out_proof,
+ size_t *out_proof_len, const EC_RAW_POINT *Tp,
uint32_t key_id, uint8_t private_metadata) {
EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp521r1);
if (group == NULL) {
@@ -341,7 +748,7 @@
}
EC_SCALAR xb, yb;
- BN_ULONG mask = ((BN_ULONG)0) - (private_metadata&1);
+ BN_ULONG mask = ((BN_ULONG)0) - (private_metadata & 1);
ec_scalar_select(group, &xb, mask, &key->x1, &key->x0);
ec_scalar_select(group, &yb, mask, &key->y1, &key->y0);
@@ -352,31 +759,35 @@
return 0;
}
- if (!mul_g_and_p(group, out_Wp, Tp, &xb, &Sp, &yb) ||
- !mul_g_and_p(group, out_Wsp, Tp, &key->xs, &Sp, &key->ys)) {
+ if (!mul_twice(group, out_Wp, Tp, &xb, &Sp, &yb) ||
+ !mul_twice(group, out_Wsp, Tp, &key->xs, &Sp, &key->ys)) {
return 0;
}
- // TODO: DLEQ Proofs
- return 1;
+ return dleq_generate(group, out_proof, out_proof_len, key, Tp, &Sp, out_Wp,
+ out_Wsp, private_metadata);
}
int pmbtoken_unblind(PMBTOKEN_TOKEN *out_token,
+ const struct trust_token_client_key_st *key,
const uint8_t s[PMBTOKEN_NONCE_SIZE],
const EC_RAW_POINT *Wp, const EC_RAW_POINT *Wsp,
+ const uint8_t *proof, size_t proof_len,
const PMBTOKEN_PRETOKEN *pretoken) {
EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp521r1);
if (group == NULL) {
return 0;
}
- // TODO: Check DLEQ Proofs
-
EC_RAW_POINT Sp;
if (!hash_s(group, &Sp, &pretoken->Tp, s)) {
return 0;
}
+ if (!dleq_verify(group, proof, proof_len, key, &pretoken->Tp, &Sp, Wp, Wsp)) {
+ return 0;
+ }
+
OPENSSL_memcpy(out_token->t, pretoken->t, PMBTOKEN_NONCE_SIZE);
if (!ec_point_mul_scalar(group, &out_token->S, &Sp, &pretoken->r) ||
!ec_point_mul_scalar(group, &out_token->W, Wp, &pretoken->r) ||
@@ -418,15 +829,15 @@
EC_RAW_POINT calculated;
// Check the validity of the token.
- if (!mul_g_and_p(group, &calculated, &T, &key->xs, &token->S, &key->ys) ||
+ if (!mul_twice(group, &calculated, &T, &key->xs, &token->S, &key->ys) ||
!ec_GFp_simple_points_equal(group, &calculated, &token->Ws)) {
OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_BAD_VALIDITY_CHECK);
return 0;
}
EC_RAW_POINT W0, W1;
- if (!mul_g_and_p(group, &W0, &T, &key->x0, &token->S, &key->y0) ||
- !mul_g_and_p(group, &W1, &T, &key->x1, &token->S, &key->y1)) {
+ if (!mul_twice(group, &W0, &T, &key->x0, &token->S, &key->y0) ||
+ !mul_twice(group, &W1, &T, &key->x1, &token->S, &key->y1)) {
return 0;
}
diff --git a/crypto/trust_token/trust_token.c b/crypto/trust_token/trust_token.c
index bc93562..9826f42 100644
--- a/crypto/trust_token/trust_token.c
+++ b/crypto/trust_token/trust_token.c
@@ -228,17 +228,19 @@
for (size_t i = 0; i < count; i++) {
uint8_t s[PMBTOKEN_NONCE_SIZE];
EC_RAW_POINT Wp, Wsp;
+ CBS proof;
if (!CBS_copy_bytes(&in, s, PMBTOKEN_NONCE_SIZE) ||
!cbs_get_raw_point(&in, group, &Wp) ||
- !cbs_get_raw_point(&in, group, &Wsp)) {
+ !cbs_get_raw_point(&in, group, &Wsp) ||
+ !CBS_get_u16_length_prefixed(&in, &proof)) {
OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
goto err;
}
PMBTOKEN_PRETOKEN *pretoken = sk_PMBTOKEN_PRETOKEN_value(ctx->pretokens, i);
PMBTOKEN_TOKEN pmbtoken;
- if (!pmbtoken_unblind(&pmbtoken, s, &Wp, &Wsp, pretoken)) {
- OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_FAILURE);
+ if (!pmbtoken_unblind(&pmbtoken, key, s, &Wp, &Wsp, CBS_data(&proof),
+ CBS_len(&proof), pretoken)) {
goto err;
}
@@ -408,6 +410,11 @@
return 0;
}
}
+
+ if (!pmbtoken_compute_public(key_s)) {
+ return 0;
+ }
+
key_s->id = key_id;
ctx->num_keys += 1;
return 1;
@@ -498,16 +505,22 @@
uint8_t s[PMBTOKEN_NONCE_SIZE];
EC_RAW_POINT Wp, Wsp;
- if (!pmbtoken_sign(ctx, s, &Wp, &Wsp, &Tp, public_metadata,
- private_metadata)) {
+ uint8_t *proof = NULL;
+ size_t proof_len;
+ if (!pmbtoken_sign(ctx, s, &Wp, &Wsp, &proof, &proof_len, &Tp,
+ public_metadata, private_metadata)) {
goto err;
}
if (!CBB_add_bytes(&response, s, PMBTOKEN_NONCE_SIZE) ||
!cbb_add_raw_point(&response, group, &Wp) ||
- !cbb_add_raw_point(&response, group, &Wsp)) {
+ !cbb_add_raw_point(&response, group, &Wsp) ||
+ !CBB_add_u16(&response, proof_len) ||
+ !CBB_add_bytes(&response, proof, proof_len)) {
+ OPENSSL_free(proof);
goto err;
}
+ OPENSSL_free(proof);
}
*out_tokens_issued = count;
diff --git a/crypto/trust_token/trust_token_test.cc b/crypto/trust_token/trust_token_test.cc
index 1d3f543..5ab3995 100644
--- a/crypto/trust_token/trust_token_test.cc
+++ b/crypto/trust_token/trust_token_test.cc
@@ -436,6 +436,119 @@
ASSERT_EQ(sk_TRUST_TOKEN_num(tokens.get()), 1UL);
}
+
+TEST_P(TrustTokenMetadataTest, TruncatedProof) {
+ ASSERT_NO_FATAL_FAILURE(SetupContexts());
+
+ uint8_t *issue_msg = NULL, *issue_resp = NULL;
+ size_t msg_len, resp_len;
+ ASSERT_TRUE(TRUST_TOKEN_CLIENT_begin_issuance(client.get(), &issue_msg,
+ &msg_len, 10));
+ bssl::UniquePtr<uint8_t> free_issue_msg(issue_msg);
+ uint8_t tokens_issued;
+ ASSERT_TRUE(TRUST_TOKEN_ISSUER_issue(
+ issuer.get(), &issue_resp, &resp_len, &tokens_issued, issue_msg, msg_len,
+ std::get<0>(GetParam()), std::get<1>(GetParam()), /*max_issuance=*/1));
+ bssl::UniquePtr<uint8_t> free_msg(issue_resp);
+
+ CBS real_response;
+ CBS_init(&real_response, issue_resp, resp_len);
+ uint16_t count;
+ uint32_t public_metadata;
+ bssl::ScopedCBB bad_response;
+ ASSERT_TRUE(CBB_init(bad_response.get(), 0));
+ ASSERT_TRUE(CBS_get_u16(&real_response, &count));
+ ASSERT_TRUE(CBB_add_u16(bad_response.get(), count));
+ ASSERT_TRUE(CBS_get_u32(&real_response, &public_metadata));
+ ASSERT_TRUE(CBB_add_u32(bad_response.get(), public_metadata));
+
+ for (size_t i = 0; i < count; i++) {
+ uint8_t s[PMBTOKEN_NONCE_SIZE];
+ CBS tmp;
+ ASSERT_TRUE(CBS_copy_bytes(&real_response, s, PMBTOKEN_NONCE_SIZE));
+ ASSERT_TRUE(CBB_add_bytes(bad_response.get(), s, PMBTOKEN_NONCE_SIZE));
+ ASSERT_TRUE(CBS_get_u16_length_prefixed(&real_response, &tmp));
+ ASSERT_TRUE(CBB_add_u16(bad_response.get(), CBS_len(&tmp)));
+ ASSERT_TRUE(
+ CBB_add_bytes(bad_response.get(), CBS_data(&tmp), CBS_len(&tmp)));
+ ASSERT_TRUE(CBS_get_u16_length_prefixed(&real_response, &tmp));
+ ASSERT_TRUE(CBB_add_u16(bad_response.get(), CBS_len(&tmp)));
+ ASSERT_TRUE(
+ CBB_add_bytes(bad_response.get(), CBS_data(&tmp), CBS_len(&tmp)));
+ ASSERT_TRUE(CBS_get_u16_length_prefixed(&real_response, &tmp));
+ ASSERT_TRUE(CBB_add_u16(bad_response.get(), CBS_len(&tmp) - 2));
+ ASSERT_TRUE(
+ CBB_add_bytes(bad_response.get(), CBS_data(&tmp), CBS_len(&tmp) - 2));
+ }
+
+ uint8_t *bad_buf;
+ size_t bad_len;
+ ASSERT_TRUE(CBB_finish(bad_response.get(), &bad_buf, &bad_len));
+ bssl::UniquePtr<uint8_t> free_bad(bad_buf);
+
+ size_t key_index;
+ bssl::UniquePtr<STACK_OF(TRUST_TOKEN)> tokens(
+ TRUST_TOKEN_CLIENT_finish_issuance(client.get(), &key_index, bad_buf, bad_len));
+ ASSERT_FALSE(tokens);
+}
+
+TEST_P(TrustTokenMetadataTest, ExcessDataProof) {
+ ASSERT_NO_FATAL_FAILURE(SetupContexts());
+
+ uint8_t *issue_msg = NULL, *issue_resp = NULL;
+ size_t msg_len, resp_len;
+ ASSERT_TRUE(TRUST_TOKEN_CLIENT_begin_issuance(client.get(), &issue_msg,
+ &msg_len, 10));
+ bssl::UniquePtr<uint8_t> free_issue_msg(issue_msg);
+ uint8_t tokens_issued;
+ ASSERT_TRUE(TRUST_TOKEN_ISSUER_issue(
+ issuer.get(), &issue_resp, &resp_len, &tokens_issued, issue_msg, msg_len,
+ std::get<0>(GetParam()), std::get<1>(GetParam()), /*max_issuance=*/1));
+ bssl::UniquePtr<uint8_t> free_msg(issue_resp);
+
+ CBS real_response;
+ CBS_init(&real_response, issue_resp, resp_len);
+ uint16_t count;
+ uint32_t public_metadata;
+ bssl::ScopedCBB bad_response;
+ ASSERT_TRUE(CBB_init(bad_response.get(), 0));
+ ASSERT_TRUE(CBS_get_u16(&real_response, &count));
+ ASSERT_TRUE(CBB_add_u16(bad_response.get(), count));
+ ASSERT_TRUE(CBS_get_u32(&real_response, &public_metadata));
+ ASSERT_TRUE(CBB_add_u32(bad_response.get(), public_metadata));
+
+ for (size_t i = 0; i < count; i++) {
+ uint8_t s[PMBTOKEN_NONCE_SIZE];
+ CBS tmp;
+ ASSERT_TRUE(CBS_copy_bytes(&real_response, s, PMBTOKEN_NONCE_SIZE));
+ ASSERT_TRUE(CBB_add_bytes(bad_response.get(), s, PMBTOKEN_NONCE_SIZE));
+ ASSERT_TRUE(CBS_get_u16_length_prefixed(&real_response, &tmp));
+ ASSERT_TRUE(CBB_add_u16(bad_response.get(), CBS_len(&tmp)));
+ ASSERT_TRUE(
+ CBB_add_bytes(bad_response.get(), CBS_data(&tmp), CBS_len(&tmp)));
+ ASSERT_TRUE(CBS_get_u16_length_prefixed(&real_response, &tmp));
+ ASSERT_TRUE(CBB_add_u16(bad_response.get(), CBS_len(&tmp)));
+ ASSERT_TRUE(
+ CBB_add_bytes(bad_response.get(), CBS_data(&tmp), CBS_len(&tmp)));
+ ASSERT_TRUE(CBS_get_u16_length_prefixed(&real_response, &tmp));
+ ASSERT_TRUE(CBB_add_u16(bad_response.get(), CBS_len(&tmp) + 2));
+ ASSERT_TRUE(
+ CBB_add_bytes(bad_response.get(), CBS_data(&tmp), CBS_len(&tmp)));
+ ASSERT_TRUE(CBB_add_u16(bad_response.get(), 42));
+ }
+
+ uint8_t *bad_buf;
+ size_t bad_len;
+ ASSERT_TRUE(CBB_finish(bad_response.get(), &bad_buf, &bad_len));
+ bssl::UniquePtr<uint8_t> free_bad(bad_buf);
+
+ size_t key_index;
+ bssl::UniquePtr<STACK_OF(TRUST_TOKEN)> tokens(
+ TRUST_TOKEN_CLIENT_finish_issuance(client.get(), &key_index, bad_buf,
+ bad_len));
+ ASSERT_FALSE(tokens);
+}
+
INSTANTIATE_TEST_SUITE_P(
TrustTokenAllMetadataTest, TrustTokenMetadataTest,
testing::Combine(testing::Values(TrustTokenProtocolTest::KeyID(0),
@@ -443,5 +556,51 @@
TrustTokenProtocolTest::KeyID(2)),
testing::Bool()));
+
+class TrustTokenBadKeyTest
+ : public TrustTokenProtocolTest,
+ public testing::WithParamInterface<std::tuple<bool, int>> {};
+
+TEST_P(TrustTokenBadKeyTest, BadKey) {
+ ASSERT_NO_FATAL_FAILURE(SetupContexts());
+
+ uint8_t *issue_msg = NULL, *issue_resp = NULL;
+ size_t msg_len, resp_len;
+ ASSERT_TRUE(TRUST_TOKEN_CLIENT_begin_issuance(client.get(), &issue_msg,
+ &msg_len, 10));
+ bssl::UniquePtr<uint8_t> free_issue_msg(issue_msg);
+
+ struct trust_token_issuer_key_st *key = &issuer->keys[0];
+ EC_SCALAR *scalars[] = {&key->x0, &key->y0, &key->x1,
+ &key->y1, &key->xs, &key->ys};
+ int corrupted_key = std::get<1>(GetParam());
+
+ // Corrupt private key scalar.
+ scalars[corrupted_key]->bytes[0] ^= 42;
+
+ uint8_t tokens_issued;
+ ASSERT_TRUE(TRUST_TOKEN_ISSUER_issue(
+ issuer.get(), &issue_resp, &resp_len, &tokens_issued, issue_msg, msg_len,
+ /*public_metadata=*/7, std::get<0>(GetParam()), /*max_issuance=*/1));
+ bssl::UniquePtr<uint8_t> free_msg(issue_resp);
+ size_t key_index;
+ bssl::UniquePtr<STACK_OF(TRUST_TOKEN)> tokens(
+ TRUST_TOKEN_CLIENT_finish_issuance(client.get(), &key_index, issue_resp,
+ resp_len));
+
+ // If the unused private key is corrupted, then the DLEQ proof should succeed.
+ if ((corrupted_key / 2 == 0 && std::get<0>(GetParam()) == true) ||
+ (corrupted_key / 2 == 1 && std::get<0>(GetParam()) == false)) {
+ ASSERT_TRUE(tokens);
+ } else {
+ ASSERT_FALSE(tokens);
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ TrustTokenAllBadKeyTest, TrustTokenBadKeyTest,
+ testing::Combine(testing::Bool(),
+ testing::Values(0, 1, 2, 3, 4, 5)));
+
} // namespace
BSSL_NAMESPACE_END
diff --git a/include/openssl/trust_token.h b/include/openssl/trust_token.h
index 473999d..8bd1eda 100644
--- a/include/openssl/trust_token.h
+++ b/include/openssl/trust_token.h
@@ -273,5 +273,6 @@
#define TRUST_TOKEN_R_BAD_VALIDITY_CHECK 111
#define TRUST_TOKEN_R_NO_SRR_KEY_CONFIGURED 112
#define TRUST_TOKEN_R_INVALID_METADATA_KEY 113
+#define TRUST_TOKEN_R_INVALID_PROOF 114
#endif // OPENSSL_HEADER_TRUST_TOKEN_H