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);