Use token hash to encode private metadata for Trust Token Experiment V1.

Bug: 328
Change-Id: Iaf3ff1bbe2f21c622b974081281848c60a01f142
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/40764
Commit-Queue: Steven Valdez <svaldez@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/crypto/trust_token/internal.h b/crypto/trust_token/internal.h
index 5f65ced..1c82d83 100644
--- a/crypto/trust_token/internal.h
+++ b/crypto/trust_token/internal.h
@@ -188,6 +188,10 @@
               uint8_t out_nonce[PMBTOKEN_NONCE_SIZE],
               uint8_t *out_private_metadata, const uint8_t *token,
               size_t token_len);
+
+  // use_token_hash determines whether to include the token hash in the SRR and
+  // private metadata encryption.
+  int use_token_hash : 1;
 };
 
 // Structure representing a single Trust Token public key with the specified ID.
diff --git a/crypto/trust_token/trust_token.c b/crypto/trust_token/trust_token.c
index 5ced6cc..f4c4fa1 100644
--- a/crypto/trust_token/trust_token.c
+++ b/crypto/trust_token/trust_token.c
@@ -36,6 +36,7 @@
       pmbtoken_exp0_sign,
       pmbtoken_exp0_unblind,
       pmbtoken_exp0_read,
+      0 /* don't use token hash */,
   };
   return &kMethod;
 }
@@ -49,6 +50,7 @@
       pmbtoken_exp1_sign,
       pmbtoken_exp1_unblind,
       pmbtoken_exp1_read,
+      1 /* use token hash */,
   };
   return &kMethod;
 }
@@ -502,6 +504,12 @@
 }
 
 // https://tools.ietf.org/html/rfc7049#section-2.1
+static int add_cbor_bytes(CBB *cbb, const uint8_t *data, size_t len) {
+  return add_cbor_int_with_type(cbb, 0x40, len) &&
+         CBB_add_bytes(cbb, data, len);
+}
+
+// https://tools.ietf.org/html/rfc7049#section-2.1
 static int add_cbor_text(CBB *cbb, const char *data, size_t len) {
   return add_cbor_int_with_type(cbb, 0x60, len) &&
          CBB_add_bytes(cbb, (const uint8_t *)data, len);
@@ -541,6 +549,8 @@
   uint32_t public_metadata = 0;
   uint8_t private_metadata = 0;
 
+  CBS token_copy = token_cbs;
+
   // Parse the token. If there is an error, treat it as an invalid token.
   if (!CBS_get_u32(&token_cbs, &public_metadata)) {
     OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_INVALID_TOKEN);
@@ -577,9 +587,24 @@
     goto err;
   }
 
-  uint8_t metadata_obfuscator =
-      get_metadata_obfuscator(ctx->metadata_key, ctx->metadata_key_len,
-                              CBS_data(&client_data), CBS_len(&client_data));
+  const uint8_t kTokenHashDSTLabel[] = "TrustTokenV0 TokenHash";
+  uint8_t token_hash[SHA256_DIGEST_LENGTH];
+  SHA256_CTX sha_ctx;
+  SHA256_Init(&sha_ctx);
+  SHA256_Update(&sha_ctx, kTokenHashDSTLabel, sizeof(kTokenHashDSTLabel));
+  SHA256_Update(&sha_ctx, CBS_data(&token_copy), CBS_len(&token_copy));
+  SHA256_Final(token_hash, &sha_ctx);
+
+  uint8_t metadata_obfuscator;
+  if (ctx->method->use_token_hash) {
+    metadata_obfuscator =
+        get_metadata_obfuscator(ctx->metadata_key, ctx->metadata_key_len,
+                                token_hash, sizeof(token_hash));
+  } else {
+    metadata_obfuscator =
+        get_metadata_obfuscator(ctx->metadata_key, ctx->metadata_key_len,
+                                CBS_data(&client_data), CBS_len(&client_data));
+  }
 
   // The SRR is constructed as per the format described in
   // https://docs.google.com/document/d/1TNnya6B8pyomDK2F1R9CL3dY10OAmqWlnCxsWyOBDVQ/edit#heading=h.7mkzvhpqb8l5
@@ -589,10 +614,12 @@
   static const char kMetadataLabel[] = "metadata";
   static const char kPrivateLabel[] = "private";
   static const char kPublicLabel[] = "public";
+  static const char kTokenHashLabel[] = "token-hash";
 
   // CBOR requires map keys to be sorted by length then sorted lexically.
   // https://tools.ietf.org/html/rfc7049#section-3.9
-  assert(strlen(kMetadataLabel) < strlen(kClientDataLabel));
+  assert(strlen(kMetadataLabel) < strlen(kTokenHashLabel));
+  assert(strlen(kTokenHashLabel) < strlen(kClientDataLabel));
   assert(strlen(kClientDataLabel) < strlen(kExpiryTimestampLabel));
   assert(strlen(kPublicLabel) < strlen(kPrivateLabel));
 
@@ -603,8 +630,20 @@
       !add_cbor_text(&srr, kPublicLabel, strlen(kPublicLabel)) ||
       !add_cbor_int(&srr, public_metadata) ||
       !add_cbor_text(&srr, kPrivateLabel, strlen(kPrivateLabel)) ||
-      !add_cbor_int(&srr, private_metadata ^ metadata_obfuscator) ||
-      !add_cbor_text(&srr, kClientDataLabel, strlen(kClientDataLabel)) ||
+      !add_cbor_int(&srr, private_metadata ^ metadata_obfuscator)) {
+    OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  if (ctx->method->use_token_hash) {
+    if (!add_cbor_text(&srr, kTokenHashLabel, strlen(kTokenHashLabel)) ||
+        !add_cbor_bytes(&srr, token_hash, sizeof(token_hash))) {
+      OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
+      goto err;
+    }
+  }
+
+  if (!add_cbor_text(&srr, kClientDataLabel, strlen(kClientDataLabel)) ||
       !CBB_add_bytes(&srr, CBS_data(&client_data), CBS_len(&client_data)) ||
       !add_cbor_text(&srr, kExpiryTimestampLabel,
                      strlen(kExpiryTimestampLabel)) ||
@@ -664,12 +703,11 @@
 
 int TRUST_TOKEN_decode_private_metadata(const TRUST_TOKEN_METHOD *method,
                                         uint8_t *out_value, const uint8_t *key,
-                                        size_t key_len,
-                                        const uint8_t *client_data,
-                                        size_t client_data_len,
+                                        size_t key_len, const uint8_t *nonce,
+                                        size_t nonce_len,
                                         uint8_t encrypted_bit) {
   uint8_t metadata_obfuscator =
-      get_metadata_obfuscator(key, key_len, client_data, client_data_len);
+      get_metadata_obfuscator(key, key_len, nonce, nonce_len);
   *out_value = encrypted_bit ^ metadata_obfuscator;
   return 1;
 }
diff --git a/crypto/trust_token/trust_token_test.cc b/crypto/trust_token/trust_token_test.cc
index 50be906..a326122 100644
--- a/crypto/trust_token/trust_token_test.cc
+++ b/crypto/trust_token/trust_token_test.cc
@@ -30,6 +30,7 @@
 #include <openssl/evp.h>
 #include <openssl/mem.h>
 #include <openssl/rand.h>
+#include <openssl/sha.h>
 #include <openssl/trust_token.h>
 
 #include "../ec_extra/internal.h"
@@ -346,8 +347,8 @@
     bssl::UniquePtr<TRUST_TOKEN> free_rtoken(rtoken);
 
     ASSERT_EQ(redemption_time, kRedemptionTime);
-    ASSERT_TRUE(sizeof(kClientData) - 1 == client_data_len);
-    ASSERT_EQ(OPENSSL_memcmp(kClientData, client_data, client_data_len), 0);
+    ASSERT_EQ(Bytes(kClientData, sizeof(kClientData) - 1),
+              Bytes(client_data, client_data_len));
     resp_len = 10;
 
     uint8_t *srr = NULL, *sig = NULL;
@@ -453,13 +454,23 @@
     const uint8_t kClientData[] = "\x70TEST CLIENT DATA";
     uint64_t kRedemptionTime = 13374242;
 
-    const uint8_t kExpectedSRR[] =
+    const uint8_t kExpectedSRRNoTokenHash[] =
         "\xa3\x68\x6d\x65\x74\x61\x64\x61\x74\x61\xa2\x66\x70\x75\x62\x6c\x69"
         "\x63\x00\x67\x70\x72\x69\x76\x61\x74\x65\x00\x6b\x63\x6c\x69\x65\x6e"
         "\x74\x2d\x64\x61\x74\x61\x70\x54\x45\x53\x54\x20\x43\x4c\x49\x45\x4e"
         "\x54\x20\x44\x41\x54\x41\x70\x65\x78\x70\x69\x72\x79\x2d\x74\x69\x6d"
         "\x65\x73\x74\x61\x6d\x70\x1a\x00\xcc\x15\x7a";
 
+    const uint8_t kExpectedSRRTokenHash[] =
+        "\xa3\x68\x6d\x65\x74\x61\x64\x61\x74\x61\xa2\x66\x70\x75\x62\x6c\x69"
+        "\x63\x00\x67\x70\x72\x69\x76\x61\x74\x65\x00\x6a\x74\x6f\x6b\x65\x6e"
+        "\x2d\x68\x61\x73\x68\x58\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+        "\x00\x00\x00\x00\x00\x6b\x63\x6c\x69\x65\x6e\x74\x2d\x64\x61\x74\x61"
+        "\x70\x54\x45\x53\x54\x20\x43\x4c\x49\x45\x4e\x54\x20\x44\x41\x54\x41"
+        "\x70\x65\x78\x70\x69\x72\x79\x2d\x74\x69\x6d\x65\x73\x74\x61\x6d\x70"
+        "\x1a\x00\xcc\x15\x7a";
+
     uint8_t *redeem_msg = NULL, *redeem_resp = NULL;
     ASSERT_TRUE(TRUST_TOKEN_CLIENT_begin_redemption(
         client.get(), &redeem_msg, &msg_len, token, kClientData,
@@ -477,8 +488,8 @@
     bssl::UniquePtr<TRUST_TOKEN> free_rtoken(rtoken);
 
     ASSERT_EQ(redemption_time, kRedemptionTime);
-    ASSERT_TRUE(sizeof(kClientData) - 1 == client_data_len);
-    ASSERT_EQ(OPENSSL_memcmp(kClientData, client_data, client_data_len), 0);
+    ASSERT_EQ(Bytes(kClientData, sizeof(kClientData) - 1),
+              Bytes(client_data, client_data_len));
 
     uint8_t *srr = NULL, *sig = NULL;
     size_t srr_len, sig_len;
@@ -487,19 +498,50 @@
     bssl::UniquePtr<uint8_t> free_srr(srr);
     bssl::UniquePtr<uint8_t> free_sig(sig);
 
-    uint8_t decode_private_metadata;
-    ASSERT_TRUE(TRUST_TOKEN_decode_private_metadata(
-        method(), &decode_private_metadata, metadata_key, sizeof(metadata_key),
-        kClientData, sizeof(kClientData) - 1, srr[27]));
-    ASSERT_EQ(srr[18], public_metadata());
-    ASSERT_EQ(decode_private_metadata, private_metadata());
+    if (method()->use_token_hash) {
+      const uint8_t kTokenHashDSTLabel[] = "TrustTokenV0 TokenHash";
+      uint8_t token_hash[SHA256_DIGEST_LENGTH];
+      SHA256_CTX sha_ctx;
+      SHA256_Init(&sha_ctx);
+      SHA256_Update(&sha_ctx, kTokenHashDSTLabel, sizeof(kTokenHashDSTLabel));
+      SHA256_Update(&sha_ctx, token->data, token->len);
+      SHA256_Final(token_hash, &sha_ctx);
 
-    // Clear out the metadata bits.
-    srr[18] = 0;
-    srr[27] = 0;
+      // Check the token hash is in the SRR.
+      ASSERT_EQ(Bytes(token_hash), Bytes(srr + 41, sizeof(token_hash)));
 
-    ASSERT_TRUE(sizeof(kExpectedSRR) - 1 == srr_len);
-    ASSERT_EQ(OPENSSL_memcmp(kExpectedSRR, srr, srr_len), 0);
+      uint8_t decode_private_metadata;
+      ASSERT_TRUE(TRUST_TOKEN_decode_private_metadata(
+          method(), &decode_private_metadata, metadata_key,
+          sizeof(metadata_key), token_hash, sizeof(token_hash), srr[27]));
+      ASSERT_EQ(srr[18], public_metadata());
+      ASSERT_EQ(decode_private_metadata, private_metadata());
+
+      // Clear out the metadata bits.
+      srr[18] = 0;
+      srr[27] = 0;
+
+      // Clear out the token hash.
+      OPENSSL_memset(srr + 41, 0, sizeof(token_hash));
+
+      ASSERT_EQ(Bytes(kExpectedSRRTokenHash, sizeof(kExpectedSRRTokenHash) - 1),
+                Bytes(srr, srr_len));
+    } else {
+      uint8_t decode_private_metadata;
+      ASSERT_TRUE(TRUST_TOKEN_decode_private_metadata(
+          method(), &decode_private_metadata, metadata_key,
+          sizeof(metadata_key), kClientData, sizeof(kClientData) - 1, srr[27]));
+      ASSERT_EQ(srr[18], public_metadata());
+      ASSERT_EQ(decode_private_metadata, private_metadata());
+
+      // Clear out the metadata bits.
+      srr[18] = 0;
+      srr[27] = 0;
+
+      ASSERT_EQ(
+          Bytes(kExpectedSRRNoTokenHash, sizeof(kExpectedSRRNoTokenHash) - 1),
+          Bytes(srr, srr_len));
+    }
   }
 }
 
diff --git a/include/openssl/trust_token.h b/include/openssl/trust_token.h
index 4945172..a73a868 100644
--- a/include/openssl/trust_token.h
+++ b/include/openssl/trust_token.h
@@ -230,15 +230,13 @@
 // buffer and must call |OPENSSL_free| when done. It returns one on success or
 // zero on error.
 //
-// The caller must keep track of all values of |*out_token| and
-// |*out_client_data| and seen globally before returning the SRR to the client.
-// If either value has been repeated, the caller must discard the SRR and report
-// an error to the caller. Returning an SRR with replayed values allows an
-// attacker to double-spend tokens and query private metadata bits in SRRs.
+// The caller must keep track of all values of |*out_token| seen globally before
+// returning the SRR to the client. If the value has been reused, the caller
+// must discard the SRR and report an error to the caller. Returning an SRR with
+// replayed values allows an attacker to double-spend tokens.
 //
-// TODO(svaldez): The private metadata bit should not be leaked on replay. This
-// means callers cannot use eventual consistency to trade off double-spending
-// and distributed system performance. See https://crbug.com/boringssl/328.
+// The private metadata construction in |TRUST_TOKEN_experiment_v0| does not
+// keep the value secret and should not be used when secrecy is required.
 OPENSSL_EXPORT int TRUST_TOKEN_ISSUER_redeem(
     const TRUST_TOKEN_ISSUER *ctx, uint8_t **out, size_t *out_len,
     TRUST_TOKEN **out_token, uint8_t **out_client_data,
@@ -247,12 +245,14 @@
 
 // TRUST_TOKEN_decode_private_metadata decodes |encrypted_bit| using the
 // private metadata key specified by a |key| buffer of length |key_len| and the
-// client data specified by a |client_data| buffer of length |client_data_len|.
-// |*out_value is set to the decrypted value, either zero or one. It returns one
-// on success and zero on error.
+// nonce by a |nonce| buffer of length |nonce_len|. The nonce in
+// |TRUST_TOKEN_experiment_v0| is the client-data field of the SRR. The nonce in
+// |TRUST_TOKEN_experiment_v1| is the token-hash field of the SRR. |*out_value|
+// is set to the decrypted value, either zero or one. It returns one on success
+// and zero on error.
 OPENSSL_EXPORT int TRUST_TOKEN_decode_private_metadata(
     const TRUST_TOKEN_METHOD *method, uint8_t *out_value, const uint8_t *key,
-    size_t key_len, const uint8_t *client_data, size_t client_data_len,
+    size_t key_len, const uint8_t *nonce, size_t nonce_len,
     uint8_t encrypted_bit);