in Sources/CNIOBoringSSL/crypto/trust_token/trust_token.c [643:847]
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,
size_t *out_client_data_len,
uint64_t *out_redemption_time,
const uint8_t *request, size_t request_len,
uint64_t lifetime) {
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;
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);
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;
}
int ok = 0;
CBB response, srr;
uint8_t *srr_buf = NULL, *sig_buf = NULL, *client_data_buf = NULL;
size_t srr_len = 0, sig_len = 0, client_data_len = 0;
EVP_MD_CTX md_ctx;
EVP_MD_CTX_init(&md_ctx);
CBB_zero(&srr);
if (!CBB_init(&response, 0)) {
OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
goto err;
}
CBS client_data;
uint64_t redemption_time = 0;
if (!CBS_get_u16_length_prefixed(&request_cbs, &client_data) ||
(ctx->method->has_srr && !CBS_get_u64(&request_cbs, &redemption_time))) {
OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_DECODE_ERROR);
goto err;
}
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 = get_metadata_obfuscator(
ctx->metadata_key, ctx->metadata_key_len, token_hash, sizeof(token_hash));
// 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";
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(kTokenHashLabel));
assert(strlen(kTokenHashLabel) < strlen(kClientDataLabel));
assert(strlen(kClientDataLabel) < strlen(kExpiryTimestampLabel));
assert(strlen(kPublicLabel) < strlen(kPrivateLabel));
size_t map_entries = 4;
if (!CBB_init(&srr, 0) ||
!add_cbor_map(&srr, map_entries) || // SRR map
!add_cbor_text(&srr, kMetadataLabel, strlen(kMetadataLabel)) ||
!add_cbor_map(&srr, 2) || // Metadata map
!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, kTokenHashLabel, strlen(kTokenHashLabel)) ||
!add_cbor_bytes(&srr, token_hash, sizeof(token_hash)) ||
!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)) ||
!add_cbor_int(&srr, expiry_time) ||
!CBB_finish(&srr, &srr_buf, &srr_len)) {
OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
goto err;
}
if (!EVP_DigestSignInit(&md_ctx, NULL, NULL, NULL, ctx->srr_key) ||
!EVP_DigestSign(&md_ctx, NULL, &sig_len, srr_buf, srr_len)) {
OPENSSL_PUT_ERROR(TRUST_TOKEN, TRUST_TOKEN_R_SRR_SIGNATURE_ERROR);
goto err;
}
// Merge SRR and Signature into single string.
// TODO(svaldez): Expose API to construct this from the caller.
if (!ctx->method->has_srr) {
static const char kSRRHeader[] = "body=:";
static const char kSRRSplit[] = ":, signature=:";
static const char kSRREnd[] = ":";
size_t srr_b64_len, sig_b64_len;
if (!EVP_EncodedLength(&srr_b64_len, srr_len) ||
!EVP_EncodedLength(&sig_b64_len, sig_len)) {
goto err;
}
sig_buf = OPENSSL_malloc(sig_len);
uint8_t *srr_b64_buf = OPENSSL_malloc(srr_b64_len);
uint8_t *sig_b64_buf = OPENSSL_malloc(sig_b64_len);
if (!sig_buf ||
!srr_b64_buf ||
!sig_b64_buf ||
!EVP_DigestSign(&md_ctx, sig_buf, &sig_len, srr_buf, srr_len) ||
!CBB_add_bytes(&response, (const uint8_t *)kSRRHeader,
strlen(kSRRHeader)) ||
!CBB_add_bytes(&response, srr_b64_buf,
EVP_EncodeBlock(srr_b64_buf, srr_buf, srr_len)) ||
!CBB_add_bytes(&response, (const uint8_t *)kSRRSplit,
strlen(kSRRSplit)) ||
!CBB_add_bytes(&response, sig_b64_buf,
EVP_EncodeBlock(sig_b64_buf, sig_buf, sig_len)) ||
!CBB_add_bytes(&response, (const uint8_t *)kSRREnd, strlen(kSRREnd))) {
OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
OPENSSL_free(srr_b64_buf);
OPENSSL_free(sig_b64_buf);
goto err;
}
OPENSSL_free(srr_b64_buf);
OPENSSL_free(sig_b64_buf);
} else {
CBB child;
uint8_t *ptr;
if (!CBB_add_u16_length_prefixed(&response, &child) ||
!CBB_add_bytes(&child, srr_buf, srr_len) ||
!CBB_add_u16_length_prefixed(&response, &child) ||
!CBB_reserve(&child, &ptr, sig_len) ||
!EVP_DigestSign(&md_ctx, ptr, &sig_len, srr_buf, srr_len) ||
!CBB_did_write(&child, sig_len) ||
!CBB_flush(&response)) {
OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_MALLOC_FAILURE);
goto err;
}
}
if (!CBS_stow(&client_data, &client_data_buf, &client_data_len) ||
!CBB_finish(&response, out, out_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_token = token;
*out_client_data = client_data_buf;
*out_client_data_len = client_data_len;
*out_redemption_time = redemption_time;
ok = 1;
err:
CBB_cleanup(&response);
CBB_cleanup(&srr);
OPENSSL_free(srr_buf);
OPENSSL_free(sig_buf);
EVP_MD_CTX_cleanup(&md_ctx);
if (!ok) {
OPENSSL_free(client_data_buf);
}
return ok;
}