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