Add raw redeem API. Change-Id: I70225ad7f95fa1dbaeecb830b17e4cde34d1bd0a Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/43444 Commit-Queue: Steven Valdez <svaldez@google.com> Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/crypto/trust_token/trust_token.c b/crypto/trust_token/trust_token.c index a4891d8..3334fba 100644 --- a/crypto/trust_token/trust_token.c +++ b/crypto/trust_token/trust_token.c
@@ -303,7 +303,7 @@ !CBB_add_bytes(&token_inner, token->data, token->len) || !CBB_add_u16_length_prefixed(&request, &inner) || !CBB_add_bytes(&inner, data, data_len) || - !CBB_add_u64(&request, time) || + (ctx->method->has_srr && !CBB_add_u64(&request, time)) || !CBB_finish(&request, out, out_len)) { OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE); CBB_cleanup(&request); @@ -518,6 +518,72 @@ return ret; } + +int TRUST_TOKEN_ISSUER_redeem_raw(const TRUST_TOKEN_ISSUER *ctx, + uint32_t *out_public, uint8_t *out_private, + TRUST_TOKEN **out_token, + uint8_t **out_client_data, + size_t *out_client_data_len, + const uint8_t *request, size_t request_len) { + CBS request_cbs, token_cbs; + CBS_init(&request_cbs, request, request_len); + if (!CBS_get_u16_length_prefixed(&request_cbs, &token_cbs)) { + OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_ERROR); + return 0; + } + + uint32_t public_metadata = 0; + uint8_t private_metadata = 0; + + // 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); + return 0; + } + + const struct trust_token_issuer_key_st *key = + trust_token_issuer_get_key(ctx, public_metadata); + uint8_t nonce[TRUST_TOKEN_NONCE_SIZE]; + if (key == NULL || + !ctx->method->read(&key->key, nonce, &private_metadata, + CBS_data(&token_cbs), CBS_len(&token_cbs))) { + OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_INVALID_TOKEN); + return 0; + } + + CBS client_data; + if (!CBS_get_u16_length_prefixed(&request_cbs, &client_data) || + (ctx->method->has_srr && !CBS_skip(&request_cbs, 8)) || + CBS_len(&request_cbs) != 0) { + OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_ERROR); + return 0; + } + + uint8_t *client_data_buf = NULL; + size_t client_data_len = 0; + if (!CBS_stow(&client_data, &client_data_buf, &client_data_len)) { + OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE); + goto err; + } + + TRUST_TOKEN *token = TRUST_TOKEN_new(nonce, TRUST_TOKEN_NONCE_SIZE); + if (token == NULL) { + OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE); + goto err; + } + *out_public = public_metadata; + *out_private = private_metadata; + *out_token = token; + *out_client_data = client_data_buf; + *out_client_data_len = client_data_len; + + return 1; + +err: + OPENSSL_free(client_data_buf); + return 0; +} + // https://tools.ietf.org/html/rfc7049#section-2.1 static int add_cbor_int_with_type(CBB *cbb, uint8_t major_type, uint64_t value) { @@ -622,9 +688,9 @@ } CBS client_data; - uint64_t redemption_time; + uint64_t redemption_time = 0; if (!CBS_get_u16_length_prefixed(&request_cbs, &client_data) || - !CBS_get_u64(&request_cbs, &redemption_time)) { + (ctx->method->has_srr && !CBS_get_u64(&request_cbs, &redemption_time))) { OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_ERROR); goto err; } @@ -643,6 +709,19 @@ // The SRR is constructed as per the format described in // https://docs.google.com/document/d/1TNnya6B8pyomDK2F1R9CL3dY10OAmqWlnCxsWyOBDVQ/edit#heading=h.7mkzvhpqb8l5 + // The V2 protocol is intended to be used with + // |TRUST_TOKEN_ISSUER_redeem_raw|. However, we temporarily support it with + // |TRUST_TOKEN_ISSUER_redeem| to ease the transition for existing issuer + // callers. Those callers' consumers currently expect an expiry-timestamp + // field, so we fill in a placeholder value. + // + // TODO(svaldez): After the existing issues have migrated to + // |TRUST_TOKEN_ISSUER_redeem_raw| remove this logic. + uint64_t expiry_time = 0; + if (ctx->method->has_srr) { + expiry_time = redemption_time + lifetime; + } + static const char kClientDataLabel[] = "client-data"; static const char kExpiryTimestampLabel[] = "expiry-timestamp"; static const char kMetadataLabel[] = "metadata"; @@ -673,7 +752,7 @@ !CBB_add_bytes(&srr, CBS_data(&client_data), CBS_len(&client_data)) || !add_cbor_text(&srr, kExpiryTimestampLabel, strlen(kExpiryTimestampLabel)) || - !add_cbor_int(&srr, redemption_time + lifetime) || + !add_cbor_int(&srr, expiry_time) || !CBB_finish(&srr, &srr_buf, &srr_len)) { OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE); goto err;
diff --git a/crypto/trust_token/trust_token_test.cc b/crypto/trust_token/trust_token_test.cc index b6d080f..f9f183d 100644 --- a/crypto/trust_token/trust_token_test.cc +++ b/crypto/trust_token/trust_token_test.cc
@@ -325,7 +325,7 @@ for (TRUST_TOKEN *token : tokens.get()) { const uint8_t kClientData[] = "\x70TEST CLIENT DATA"; - uint64_t kRedemptionTime = 13374242; + uint64_t kRedemptionTime = (method()->has_srr ? 13374242 : 0); uint8_t *redeem_msg = NULL, *redeem_resp = NULL; ASSERT_TRUE(TRUST_TOKEN_CLIENT_begin_redemption( @@ -366,7 +366,7 @@ for (TRUST_TOKEN *token : tokens.get()) { const uint8_t kClientData[] = "\x70TEST CLIENT DATA"; - uint64_t kRedemptionTime = 13374242; + uint64_t kRedemptionTime = 0; uint8_t *redeem_msg = NULL, *redeem_resp = NULL; ASSERT_TRUE(TRUST_TOKEN_CLIENT_begin_redemption( @@ -499,9 +499,9 @@ for (TRUST_TOKEN *token : tokens.get()) { const uint8_t kClientData[] = "\x70TEST CLIENT DATA"; - uint64_t kRedemptionTime = 13374242; + uint64_t kRedemptionTime = (method()->has_srr ? 13374242 : 0); - const uint8_t kExpectedSRR[] = + const uint8_t kExpectedSRRV1[] = "\xa4\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" @@ -511,6 +511,23 @@ "\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 kExpectedSRRV2[] = + "\xa4\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" + "\x00"; + + const uint8_t *expected_srr = kExpectedSRRV1; + size_t expected_srr_len = sizeof(kExpectedSRRV1) - 1; + if (!method()->has_srr) { + expected_srr = kExpectedSRRV2; + expected_srr_len = sizeof(kExpectedSRRV2) - 1; + } + uint8_t *redeem_msg = NULL, *redeem_resp = NULL; ASSERT_TRUE(TRUST_TOKEN_CLIENT_begin_redemption( client.get(), &redeem_msg, &msg_len, token, kClientData, @@ -540,22 +557,21 @@ if (!method()->has_srr) { size_t b64_len; - ASSERT_TRUE(EVP_EncodedLength(&b64_len, sizeof(kExpectedSRR) - 1)); + ASSERT_TRUE(EVP_EncodedLength(&b64_len, expected_srr_len)); b64_len -= 1; - const char kSRRHeader[] = "body=:"; ASSERT_LT(sizeof(kSRRHeader) - 1 + b64_len, srr_len); ASSERT_EQ(Bytes(kSRRHeader, sizeof(kSRRHeader) - 1), Bytes(srr, sizeof(kSRRHeader) - 1)); uint8_t *decoded_srr = - (uint8_t *)OPENSSL_malloc(sizeof(kExpectedSRR) + 1); + (uint8_t *)OPENSSL_malloc(expected_srr_len + 2); ASSERT_TRUE(decoded_srr); - ASSERT_LT( - int(sizeof(kExpectedSRR) - 1), + ASSERT_LE( + int(expected_srr_len), EVP_DecodeBlock(decoded_srr, srr + sizeof(kSRRHeader) - 1, b64_len)); srr = decoded_srr; - srr_len = sizeof(kExpectedSRR) - 1; + srr_len = expected_srr_len; free_srr.reset(srr); } @@ -584,11 +600,63 @@ // Clear out the token hash. OPENSSL_memset(srr + 41, 0, sizeof(token_hash)); - ASSERT_EQ(Bytes(kExpectedSRR, sizeof(kExpectedSRR) - 1), + ASSERT_EQ(Bytes(expected_srr, expected_srr_len), Bytes(srr, srr_len)); } } +TEST_P(TrustTokenMetadataTest, RawSetAndGetMetadata) { + 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); + size_t tokens_issued; + bool result = TRUST_TOKEN_ISSUER_issue( + issuer.get(), &issue_resp, &resp_len, &tokens_issued, issue_msg, msg_len, + public_metadata(), private_metadata(), /*max_issuance=*/1); + if (!method()->has_private_metadata && private_metadata()) { + ASSERT_FALSE(result); + return; + } + ASSERT_TRUE(result); + 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)); + ASSERT_TRUE(tokens); + EXPECT_EQ(1u, sk_TRUST_TOKEN_num(tokens.get())); + + for (TRUST_TOKEN *token : tokens.get()) { + const uint8_t kClientData[] = "\x70TEST CLIENT DATA"; + uint64_t kRedemptionTime = (method()->has_srr ? 13374242 : 0); + + uint8_t *redeem_msg = NULL; + ASSERT_TRUE(TRUST_TOKEN_CLIENT_begin_redemption( + client.get(), &redeem_msg, &msg_len, token, kClientData, + sizeof(kClientData) - 1, kRedemptionTime)); + bssl::UniquePtr<uint8_t> free_redeem_msg(redeem_msg); + uint32_t public_value; + uint8_t private_value; + TRUST_TOKEN *rtoken; + uint8_t *client_data; + size_t client_data_len; + ASSERT_TRUE(TRUST_TOKEN_ISSUER_redeem_raw( + issuer.get(), &public_value, &private_value, &rtoken, + &client_data, &client_data_len, redeem_msg, msg_len)); + bssl::UniquePtr<uint8_t> free_client_data(client_data); + bssl::UniquePtr<TRUST_TOKEN> free_rtoken(rtoken); + + ASSERT_EQ(Bytes(kClientData, sizeof(kClientData) - 1), + Bytes(client_data, client_data_len)); + ASSERT_EQ(public_value, static_cast<uint32_t>(public_metadata())); + ASSERT_EQ(private_value, private_metadata()); + } +} + TEST_P(TrustTokenMetadataTest, TooManyRequests) { if (!method()->has_private_metadata && private_metadata()) { return;
diff --git a/include/openssl/trust_token.h b/include/openssl/trust_token.h index 7146995..d9247f7 100644 --- a/include/openssl/trust_token.h +++ b/include/openssl/trust_token.h
@@ -42,14 +42,10 @@ // TRUST_TOKEN_experiment_v2_voprf is an experimental Trust Tokens protocol // using VOPRFs and P-384 with up to 6 keys, without RR verification. -// -// This version is incomplete and should not be used. OPENSSL_EXPORT const TRUST_TOKEN_METHOD *TRUST_TOKEN_experiment_v2_voprf(void); // TRUST_TOKEN_experiment_v2_pmb is an experimental Trust Tokens protocol using // PMBTokens and P-384 with up to 3 keys, without RR verification. -// -// This version is incomplete and should not be used. OPENSSL_EXPORT const TRUST_TOKEN_METHOD *TRUST_TOKEN_experiment_v2_pmb(void); // trust_token_st represents a single-use token for the Trust Token protocol. @@ -150,9 +146,9 @@ // |token| and receive a signature over |data| and serializes the request into // a newly-allocated buffer, setting |*out| to that buffer and |*out_len| to // its length. |time| is the number of seconds since the UNIX epoch and used to -// verify the validity of the issuer's response. The caller takes ownership of -// the buffer and must call |OPENSSL_free| when done. It returns one on success -// or zero on error. +// verify the validity of the issuer's response in TrustTokenV1 and ignored in +// other versions. The caller takes ownership of the buffer and must call +// |OPENSSL_free| when done. It returns one on success or zero on error. OPENSSL_EXPORT int TRUST_TOKEN_CLIENT_begin_redemption( TRUST_TOKEN_CLIENT *ctx, uint8_t **out, size_t *out_len, const TRUST_TOKEN *token, const uint8_t *data, size_t data_len, @@ -228,16 +224,16 @@ uint32_t public_metadata, uint8_t private_metadata, size_t max_issuance); // TRUST_TOKEN_ISSUER_redeem ingests a |request| for token redemption and -// verifies the token. If the token is valid, a SRR is produced with a lifetime +// verifies the token. If the token is valid, a RR is produced with a lifetime // of |lifetime| (in seconds), signing over the requested data from the request // and the value of the token, storing the result into a newly-allocated buffer // and setting |*out| to that buffer and |*out_len| to its length. The extracted // |TRUST_TOKEN| is stored into a newly-allocated buffer and stored in // |*out_token|. The extracted client data is stored into a newly-allocated -// buffer and stored in |*out_client_data|. The extracted redemption time is -// stored in |*out_redemption_time|. The caller takes ownership of each output -// buffer and must call |OPENSSL_free| when done. It returns one on success or -// zero on error. +// buffer and stored in |*out_client_data|. In TrustTokenV1, the extracted +// redemption time is stored in |*out_redemption_time|. The caller takes +// ownership of each output 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| seen globally before // returning the SRR to the client. If the value has been reused, the caller @@ -249,6 +245,24 @@ size_t *out_client_data_len, uint64_t *out_redemption_time, const uint8_t *request, size_t request_len, uint64_t lifetime); +// TRUST_TOKEN_ISSUER_redeem_raw ingests a |request| for token redemption and +// verifies the token. The public metadata is stored in |*out_public|. The +// private metadata (if any) is stored in |*out_private|. The extracted +// |TRUST_TOKEN| is stored into a newly-allocated buffer and stored in +// |*out_token|. The extracted client data is stored into a newly-allocated +// buffer and stored in |*out_client_data|. The caller takes ownership of each +// output 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| seen globally before +// returning a response to the client. If the value has been reused, the caller +// must report an error to the client. Returning a response with replayed values +// allows an attacker to double-spend tokens. +OPENSSL_EXPORT int TRUST_TOKEN_ISSUER_redeem_raw( + const TRUST_TOKEN_ISSUER *ctx, uint32_t *out_public, uint8_t *out_private, + TRUST_TOKEN **out_token, uint8_t **out_client_data, + size_t *out_client_data_len, const uint8_t *request, size_t request_len); + // TRUST_TOKEN_decode_private_metadata decodes |encrypted_bit| using the // private metadata key specified by a |key| buffer of length |key_len| and the // nonce by a |nonce| buffer of length |nonce_len|. The nonce in