int TRUST_TOKEN_ISSUER_redeem()

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