blob: 7d466d28b2f1abff57e1bf3d45a0fc788efe63e4 [file] [log] [blame]
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "anonymous_tokens/cpp/privacy_pass/token_encodings.h"
#include <sys/types.h>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <string>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/ascii.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_split.h"
#include "absl/time/time.h"
#include "absl/types/span.h"
#include "anonymous_tokens/cpp/shared/status_utils.h"
#include <openssl/base.h>
#include <openssl/bytestring.h>
#include <openssl/mem.h>
namespace anonymous_tokens {
namespace {
absl::StatusOr<std::string> EncodeTokenStructHelper(
const uint16_t& token_type, const std::string& token_key_id,
const std::string& nonce, const std::string& context,
const std::optional<std::string> authenticator) {
// Main CryptoByteBuilder object cbb which will be passed to CBB_finish to
// finalize the output string.
bssl::ScopedCBB cbb;
// initial_capacity only serves as a hint.
if (!CBB_init(cbb.get(), /*initial_capacity=*/98)) {
return absl::InternalError("CBB_init() failed.");
}
// Add token_type to cbb.
if (!CBB_add_u16(cbb.get(), token_type) ||
// Add nonce to cbb.
!CBB_add_bytes(cbb.get(), reinterpret_cast<const uint8_t*>(nonce.data()),
nonce.size()) ||
// Add context string to cbb.
!CBB_add_bytes(cbb.get(),
reinterpret_cast<const uint8_t*>(context.data()),
context.size()) ||
// Add token_key_id to cbb.
!CBB_add_bytes(cbb.get(),
reinterpret_cast<const uint8_t*>(token_key_id.data()),
token_key_id.size())) {
return absl::InvalidArgumentError(
"Could not construct cbb with given inputs.");
}
// Add authenticator to cbb.
if (authenticator.has_value() &&
!CBB_add_bytes(cbb.get(),
reinterpret_cast<const uint8_t*>(authenticator->data()),
authenticator->size())) {
return absl::InvalidArgumentError("Could not add authenticator to cbb.");
}
uint8_t* encoded_output;
size_t encoded_output_len;
if (!CBB_finish(cbb.get(), &encoded_output, &encoded_output_len)) {
return absl::InvalidArgumentError(
"Failed to generate token / token input encoding");
}
std::string encoded_output_str(reinterpret_cast<const char*>(encoded_output),
encoded_output_len);
// Free memory.
OPENSSL_free(encoded_output);
return encoded_output_str;
}
// `extensions_cbs` may contain one or more encoded extensions in a row.
// This function only decodes the first extension from `extensions_cbs`. After
// this function is called `extensions_cbs` will point to the next extension
// after the one returned by decodeExtensions. If an error is returned,
// `extensions_cbs` may be in a partially read state - do not rely on it to
// parse more extensions.
absl::StatusOr<Extension> decodeExtension(CBS* extensions_cbs) {
Extension ext;
if (!CBS_get_u16(extensions_cbs, &ext.extension_type)) {
return absl::InvalidArgumentError("failed to read next type.");
}
CBS extension_cbs;
if (!CBS_get_u16_length_prefixed(extensions_cbs, &extension_cbs)) {
return absl::InvalidArgumentError("failed to read extension value.");
}
ext.extension_value.resize(CBS_len(&extension_cbs));
if (!CBS_copy_bytes(&extension_cbs,
reinterpret_cast<uint8_t*>(ext.extension_value.data()),
CBS_len(&extension_cbs))) {
return absl::InvalidArgumentError("failed to read Extension value.");
}
return ext;
}
} // namespace
absl::StatusOr<std::string> AuthenticatorInput(const Token& token) {
return EncodeTokenStructHelper(token.token_type, token.token_key_id,
token.nonce, token.context, std::nullopt);
}
absl::StatusOr<std::string> MarshalToken(const Token& token) {
return EncodeTokenStructHelper(token.token_type, token.token_key_id,
token.nonce, token.context,
token.authenticator);
}
absl::StatusOr<Token> UnmarshalToken(std::string token) {
Token out;
out.nonce.resize(32);
out.context.resize(32);
out.token_key_id.resize(32);
out.authenticator.resize(256);
CBS cbs;
CBS_init(&cbs, reinterpret_cast<const uint8_t*>(token.data()), token.size());
if (!CBS_get_u16(&cbs, &out.token_type)) {
return absl::InvalidArgumentError("failed to read token type");
}
if (out.token_type != 0xDA7A) {
return absl::InvalidArgumentError("unsupported token type");
}
if (!CBS_copy_bytes(&cbs, reinterpret_cast<uint8_t*>(out.nonce.data()),
out.nonce.size())) {
return absl::InvalidArgumentError("failed to read nonce");
}
if (!CBS_copy_bytes(&cbs, reinterpret_cast<uint8_t*>(out.context.data()),
out.context.size())) {
return absl::InvalidArgumentError("failed to read context");
}
if (!CBS_copy_bytes(&cbs, reinterpret_cast<uint8_t*>(out.token_key_id.data()),
out.token_key_id.size())) {
return absl::InvalidArgumentError("failed to read token_key_id");
}
if (!CBS_copy_bytes(&cbs,
reinterpret_cast<uint8_t*>(out.authenticator.data()),
out.authenticator.size())) {
return absl::InvalidArgumentError("failed to read authenticator");
}
if (CBS_len(&cbs) != 0) {
return absl::InvalidArgumentError("token had extra bytes");
}
return out;
}
absl::StatusOr<std::string> EncodeExtension(const Extension& extension) {
// Main CryptoByteBuilder object cbb which will be passed to CBB_finish to
// finalize the output string.
bssl::ScopedCBB cbb;
// Temporary cbb struct object to fill with value bytes.
CBB extension_value;
// initial_capacity only serves as a hint.
if (!CBB_init(cbb.get(), /*initial_capacity=*/4)) {
return absl::InternalError("CBB_init() failed.");
}
// Add extension type using only 2 bytes.
if (!CBB_add_u16(cbb.get(), extension.extension_type) ||
// Add extension value but prefix it with its length which should fit in 2
// bytes.
!CBB_add_u16_length_prefixed(cbb.get(), &extension_value) ||
!CBB_add_bytes(
&extension_value,
reinterpret_cast<const uint8_t*>(extension.extension_value.data()),
extension.extension_value.size())) {
return absl::InvalidArgumentError("Failed to populate cbb.");
}
uint8_t* encoded_ext;
size_t encoded_ext_len;
if (!CBB_finish(cbb.get(), &encoded_ext, &encoded_ext_len)) {
return absl::InvalidArgumentError("Failed to generate extension encoding");
}
std::string encoded_ext_str(reinterpret_cast<const char*>(encoded_ext),
encoded_ext_len);
// Free memory.
OPENSSL_free(encoded_ext);
return encoded_ext_str;
}
absl::StatusOr<std::string> EncodeExtensions(const Extensions& extensions) {
// Main CryptoByteBuilder object cbb which will be passed to CBB_finish to
// finalize the output string.
bssl::ScopedCBB cbb;
// Temporary cbb struct object to fill with encoded extensions bytes.
CBB extensions_list;
// initial_capacity only serves as a hint.
if (!CBB_init(cbb.get(), /*initial_capacity=*/6)) {
return absl::InternalError("CBB_init() failed.");
}
// Size in bytes for the extensions list must fit in 2 bytes.
if (!CBB_add_u16_length_prefixed(cbb.get(), &extensions_list)) {
return absl::InvalidArgumentError(
"Call to CBB_add_u16_length_prefixed erred.");
}
// Add all encoded extensions to the temporary cbb.
for (const Extension& ext : extensions.extensions) {
ANON_TOKENS_ASSIGN_OR_RETURN(std::string encoded_ext, EncodeExtension(ext));
if (!CBB_add_bytes(&extensions_list,
reinterpret_cast<const uint8_t*>(encoded_ext.data()),
encoded_ext.size())) {
return absl::InvalidArgumentError(
"Could not add encoded extension to cbb.");
}
}
uint8_t* encoded_extensions;
size_t encoded_extensions_len;
if (!CBB_finish(cbb.get(), &encoded_extensions, &encoded_extensions_len)) {
return absl::InvalidArgumentError(
"Failed to generate encoded extensions list.");
}
std::string encoded_extensions_str(
reinterpret_cast<const char*>(encoded_extensions),
encoded_extensions_len);
// Free memory.
OPENSSL_free(encoded_extensions);
return encoded_extensions_str;
}
absl::StatusOr<ExpirationTimestamp> ExpirationTimestamp::FromExtension(
const Extension& ext) {
if (ext.extension_type != 0x0001) {
return absl::InvalidArgumentError(
absl::StrCat("Extension of wrong type: ", ext.extension_type));
}
ExpirationTimestamp ts;
CBS cbs;
CBS_init(&cbs, reinterpret_cast<const uint8_t*>(ext.extension_value.data()),
ext.extension_value.size());
if (!CBS_get_u64(&cbs, &ts.timestamp_precision)) {
return absl::InvalidArgumentError("failed to read timestamp_precision");
}
if (!CBS_get_u64(&cbs, &ts.timestamp)) {
return absl::InvalidArgumentError("failed to read timestamp");
}
return ts;
}
absl::StatusOr<Extension> ExpirationTimestamp::AsExtension() const {
bssl::ScopedCBB cbb;
if (!CBB_init(cbb.get(), sizeof(ExpirationTimestamp))) {
return absl::InternalError("CBB_init() failed.");
}
if (!CBB_add_u64(cbb.get(), timestamp_precision)) {
return absl::InternalError("Failed to add timestamp_precision to cbb.");
}
if (!CBB_add_u64(cbb.get(), timestamp)) {
return absl::InternalError("Failed to add timestamp to cbb.");
}
uint8_t* wire_format;
size_t wire_format_len;
if (!CBB_finish(cbb.get(), &wire_format, &wire_format_len)) {
return absl::InternalError("Failed to generate wire format.");
}
std::string wire_format_str(reinterpret_cast<const char*>(wire_format),
wire_format_len);
OPENSSL_free(wire_format);
return Extension{
.extension_type = 0x0001,
.extension_value = wire_format_str,
};
}
absl::StatusOr<Extension> GeoHint::AsExtension() const {
bssl::ScopedCBB cbb;
if (!CBB_init(cbb.get(), sizeof(uint16_t) + geo_hint.size())) {
return absl::InternalError("CBB_init() failed.");
}
// Temporary cbb struct object to fill with geo_hint bytes.
CBB geo_hint_cbb;
if (!CBB_add_u16_length_prefixed(cbb.get(), &geo_hint_cbb) ||
!CBB_add_bytes(&geo_hint_cbb,
reinterpret_cast<const uint8_t*>(geo_hint.data()),
geo_hint.size())) {
return absl::InternalError("Failed to add geohint to cbb.");
}
uint8_t* wire_format;
size_t wire_format_len;
if (!CBB_finish(cbb.get(), &wire_format, &wire_format_len)) {
return absl::InternalError("Failed to generate wire format.");
}
std::string wire_format_str(reinterpret_cast<const char*>(wire_format),
wire_format_len);
OPENSSL_free(wire_format);
return Extension{
.extension_type = 0x0002,
.extension_value = wire_format_str,
};
}
absl::StatusOr<GeoHint> GeoHint::FromExtension(const Extension& ext) {
if (ext.extension_type != 0x0002) {
return absl::InvalidArgumentError(absl::StrCat(
"[GeoHint] Extension of wrong type: ", ext.extension_type));
}
GeoHint gh;
CBS cbs;
CBS_init(&cbs, reinterpret_cast<const uint8_t*>(ext.extension_value.data()),
ext.extension_value.size());
CBS geohint_cbs;
if (!CBS_get_u16_length_prefixed(&cbs, &geohint_cbs)) {
return absl::InvalidArgumentError(
"[GeoHint] failed to read geohint length");
}
gh.geo_hint.resize(CBS_len(&geohint_cbs));
if (!CBS_copy_bytes(&geohint_cbs,
reinterpret_cast<uint8_t*>(gh.geo_hint.data()),
gh.geo_hint.size())) {
return absl::InvalidArgumentError("[GeoHint] failed to read geohint data");
}
const std::vector<std::string_view> split = absl::StrSplit(gh.geo_hint, ',');
if (split.size() != 3) {
return absl::InvalidArgumentError(
"[GeoHint] geo_hint must be exactly 3 parts.");
}
for (const std::string_view part : split) {
if (absl::AsciiStrToUpper(part) != part) {
return absl::InvalidArgumentError(
"[GeoHint] all geo_hint parts must be UPPERCASE.");
}
}
gh.country_code = split[0];
gh.region = split[1];
gh.city = split[2];
return gh;
}
absl::StatusOr<Extension> ServiceType::AsExtension() const {
bssl::ScopedCBB cbb;
if (!CBB_init(cbb.get(), sizeof(uint8_t))) {
return absl::InternalError("CBB_init() failed.");
}
if (!CBB_add_u8(cbb.get(), service_type_id)) {
return absl::InternalError("Failed to add service_type_id to cbb.");
}
uint8_t* wire_format;
size_t wire_format_len;
if (!CBB_finish(cbb.get(), &wire_format, &wire_format_len)) {
return absl::InternalError("Failed to generate wire format.");
}
std::string wire_format_str(reinterpret_cast<const char*>(wire_format),
wire_format_len);
OPENSSL_free(wire_format);
return Extension{.extension_type = 0xF001,
.extension_value = wire_format_str};
}
absl::StatusOr<ServiceType> ServiceType::FromExtension(const Extension& ext) {
if (ext.extension_type != 0xF001) {
return absl::InvalidArgumentError(absl::StrCat(
"[ServiceType] extension of wrong type: ", ext.extension_type));
}
ServiceType st;
CBS cbs;
CBS_init(&cbs, reinterpret_cast<const uint8_t*>(ext.extension_value.data()),
ext.extension_value.size());
if (!CBS_get_u8(&cbs, &st.service_type_id)) {
return absl::InvalidArgumentError(
"[ServiceType] failed to read len from extension");
}
switch (st.service_type_id) {
case kChromeIpBlinding:
st.service_type = "chromeipblinding";
break;
default:
return absl::InvalidArgumentError(
"[ServiceType] unknown service_type_id");
}
return st;
}
absl::StatusOr<Extension> DebugMode::AsExtension() const {
bssl::ScopedCBB cbb;
if (!CBB_init(cbb.get(), sizeof(uint8_t))) {
return absl::InternalError("CBB_init() failed.");
}
if (!CBB_add_u8(cbb.get(), mode)) {
return absl::InternalError("Failed to add mode to cbb.");
}
uint8_t* wire_format;
size_t wire_format_len;
if (!CBB_finish(cbb.get(), &wire_format, &wire_format_len)) {
return absl::InternalError("Failed to generate wire format.");
}
std::string wire_format_str(reinterpret_cast<const char*>(wire_format),
wire_format_len);
OPENSSL_free(wire_format);
return Extension{.extension_type = 0xF002,
.extension_value = wire_format_str};
}
absl::StatusOr<DebugMode> DebugMode::FromExtension(const Extension& ext) {
if (ext.extension_type != 0xF002) {
return absl::InvalidArgumentError(absl::StrCat(
"[DebugMode] extension of wrong type: ", ext.extension_type));
}
DebugMode dm;
CBS cbs;
CBS_init(&cbs, reinterpret_cast<const uint8_t*>(ext.extension_value.data()),
ext.extension_value.size());
if (!CBS_get_u8(&cbs, &dm.mode)) {
return absl::InvalidArgumentError(
"[DebugMode] failed to read len from extension");
}
if (dm.mode != kProd && dm.mode != kDebug) {
return absl::InvalidArgumentError(
absl::StrCat("[DebugMode] invalid mode: ", dm.mode));
}
return dm;
}
absl::StatusOr<Extension> ProxyLayer::AsExtension() const {
bssl::ScopedCBB cbb;
if (!CBB_init(cbb.get(), sizeof(uint8_t))) {
return absl::InternalError("CBB_init() failed.");
}
if (!CBB_add_u8(cbb.get(), layer)) {
return absl::InternalError("Failed to add layer to cbb.");
}
uint8_t* wire_format;
size_t wire_format_len;
if (!CBB_finish(cbb.get(), &wire_format, &wire_format_len)) {
return absl::InternalError("Failed to generate wire format.");
}
std::string wire_format_str(reinterpret_cast<const char*>(wire_format),
wire_format_len);
OPENSSL_free(wire_format);
return Extension{.extension_type = 0xF003,
.extension_value = wire_format_str};
}
absl::StatusOr<ProxyLayer> ProxyLayer::FromExtension(const Extension& ext) {
if (ext.extension_type != 0xF003) {
return absl::InvalidArgumentError(absl::StrCat(
"[ProxyLayer] extension of wrong type: ", ext.extension_type));
}
ProxyLayer pl;
CBS cbs;
CBS_init(&cbs, reinterpret_cast<const uint8_t*>(ext.extension_value.data()),
ext.extension_value.size());
if (!CBS_get_u8(&cbs, &pl.layer)) {
return absl::InvalidArgumentError(
"[ProxyLayer] failed to read len from extension");
}
if (pl.layer != kProxyA && pl.layer != kProxyB) {
return absl::InvalidArgumentError(
absl::StrCat("[ProxyLayer] invalid layer: ", pl.layer));
}
return pl;
}
absl::StatusOr<Extensions> DecodeExtensions(
absl::string_view encoded_extensions) {
CBS cbs;
CBS_init(&cbs, reinterpret_cast<const uint8_t*>(encoded_extensions.data()),
encoded_extensions.size());
CBS extensions_cbs;
if (!CBS_get_u16_length_prefixed(&cbs, &extensions_cbs)) {
return absl::InvalidArgumentError("failed to read extensions.");
}
if (CBS_len(&extensions_cbs) == 0) {
return absl::InvalidArgumentError("At least one extension is required.");
}
if (CBS_len(&cbs) != 0) {
return absl::InvalidArgumentError("no data after extensions is allowed.");
}
Extensions extensions;
while (CBS_len(&extensions_cbs) > 0) {
ANON_TOKENS_ASSIGN_OR_RETURN(const Extension ext,
decodeExtension(&extensions_cbs));
extensions.extensions.push_back(ext);
}
return extensions;
}
absl::StatusOr<std::string> MarshalTokenChallenge(
const TokenChallenge& token_challenge) {
// Main CryptoByteBuilder object cbb which will be passed to CBB_finish to
// finalize the output string.
bssl::ScopedCBB cbb;
// initial_capacity only serves as a hint.
if (!CBB_init(cbb.get(), /*initial_capacity=*/98)) {
return absl::InternalError("CBB_init() failed.");
}
// Add token_type to cbb.
if (!CBB_add_u16(cbb.get(), token_challenge.token_type)) {
return absl::InvalidArgumentError("Could not add token_type to cbb.");
}
// Add issuer_name to cbb using temporary cbb struct object issuer_name_cbb.
CBB issuer_name_cbb;
if (!CBB_add_u16_length_prefixed(cbb.get(), &issuer_name_cbb) ||
!CBB_add_bytes(
&issuer_name_cbb,
reinterpret_cast<const uint8_t*>(token_challenge.issuer_name.data()),
token_challenge.issuer_name.size())) {
return absl::InvalidArgumentError("Could not add issuer_name to cbb.");
}
uint8_t* marshaled_challenge;
size_t marshaled_challenge_len;
if (!CBB_finish(cbb.get(), &marshaled_challenge, &marshaled_challenge_len)) {
return absl::InvalidArgumentError("Failed to marshal token challenge");
}
std::string marshaled_challenge_str(
reinterpret_cast<const char*>(marshaled_challenge),
marshaled_challenge_len);
// Free memory.
OPENSSL_free(marshaled_challenge);
return marshaled_challenge_str;
}
absl::StatusOr<std::string> MarshalTokenRequest(
const TokenRequest& token_request) {
// Main CryptoByteBuilder object cbb which will be passed to CBB_finish to
// finalize the output string.
bssl::ScopedCBB cbb;
// initial_capacity only serves as a hint.
if (!CBB_init(cbb.get(),
/*initial_capacity=*/kDA7AMarshaledTokenRequestSizeInBytes)) {
return absl::InternalError("CBB_init() failed.");
}
// Add token_type to cbb.
if (!CBB_add_u16(cbb.get(), token_request.token_type) ||
// Add truncated_token_key_id to cbb.
!CBB_add_u8(cbb.get(), token_request.truncated_token_key_id) ||
// Add blinded_token_request string to cbb.
!CBB_add_bytes(cbb.get(),
reinterpret_cast<const uint8_t*>(
token_request.blinded_token_request.data()),
token_request.blinded_token_request.size())) {
return absl::InvalidArgumentError(
"Could not construct cbb with given inputs.");
}
uint8_t* encoded_output;
size_t encoded_output_len;
if (!CBB_finish(cbb.get(), &encoded_output, &encoded_output_len)) {
return absl::InvalidArgumentError(
"Failed to generate token request encoding");
}
std::string encoded_output_str(reinterpret_cast<const char*>(encoded_output),
encoded_output_len);
// Free memory.
OPENSSL_free(encoded_output);
return encoded_output_str;
}
absl::StatusOr<TokenRequest> UnmarshalTokenRequest(
absl::string_view token_request) {
TokenRequest out;
out.blinded_token_request.resize(kDA7ABlindedTokenRequestSizeInBytes);
CBS cbs;
CBS_init(&cbs, reinterpret_cast<const uint8_t*>(token_request.data()),
token_request.size());
if (!CBS_get_u16(&cbs, &out.token_type)) {
return absl::InvalidArgumentError("failed to read token type");
}
if (out.token_type != 0xDA7A) {
return absl::InvalidArgumentError("unsupported token type");
}
if (!CBS_get_u8(&cbs, &out.truncated_token_key_id)) {
return absl::InvalidArgumentError("failed to read truncated_token_key_id");
}
if (!CBS_copy_bytes(
&cbs, reinterpret_cast<uint8_t*>(out.blinded_token_request.data()),
out.blinded_token_request.size())) {
return absl::InvalidArgumentError("failed to read blinded_token_request");
}
if (CBS_len(&cbs) != 0) {
return absl::InvalidArgumentError("token request had extra bytes");
}
return out;
}
absl::StatusOr<std::string> MarshalExtendedTokenRequest(
const ExtendedTokenRequest& extended_token_request) {
// Main CryptoByteBuilder object cbb which will be passed to CBB_finish to
// finalize the output string.
bssl::ScopedCBB cbb;
// Initial_capacity only serves as a hint. Note that
// extended_token_request.request would occupy 259 bytes as the token type is
// DA7A and we will add some additional bytes as buffer for the extensions.
if (!CBB_init(
cbb.get(),
/*initial_capacity=*/kDA7AMarshaledTokenRequestSizeInBytes + 41)) {
return absl::InternalError("CBB_init() failed.");
}
// Marshal TokenRequest structure.
ANON_TOKENS_ASSIGN_OR_RETURN(
std::string encoded_request,
MarshalTokenRequest(extended_token_request.request));
// Marshal Extensions structure.
ANON_TOKENS_ASSIGN_OR_RETURN(
std::string encoded_extensions,
EncodeExtensions(extended_token_request.extensions));
// Add encoded_request to cbb.
if (!CBB_add_bytes(cbb.get(),
reinterpret_cast<const uint8_t*>(encoded_request.data()),
encoded_request.size()) ||
// Add encoded_extensions to cbb.
!CBB_add_bytes(
cbb.get(),
reinterpret_cast<const uint8_t*>(encoded_extensions.data()),
encoded_extensions.size())) {
return absl::InvalidArgumentError(
"Could not construct cbb with given inputs.");
}
uint8_t* encoded_output;
size_t encoded_output_len;
if (!CBB_finish(cbb.get(), &encoded_output, &encoded_output_len)) {
return absl::InvalidArgumentError(
"Failed to generate token request encoding");
}
std::string encoded_output_str(reinterpret_cast<const char*>(encoded_output),
encoded_output_len);
// Free memory.
OPENSSL_free(encoded_output);
return encoded_output_str;
}
absl::StatusOr<ExtendedTokenRequest> UnmarshalExtendedTokenRequest(
absl::string_view extended_token_request) {
CBS cbs;
CBS_init(&cbs,
reinterpret_cast<const uint8_t*>(extended_token_request.data()),
extended_token_request.size());
std::string encoded_token_request;
encoded_token_request.resize(kDA7AMarshaledTokenRequestSizeInBytes);
if (!CBS_copy_bytes(&cbs,
reinterpret_cast<uint8_t*>(encoded_token_request.data()),
encoded_token_request.size())) {
return absl::InvalidArgumentError("failed to read encoded_token_request");
}
std::string encoded_extensions;
encoded_extensions.resize(CBS_len(&cbs));
if (!CBS_copy_bytes(&cbs,
reinterpret_cast<uint8_t*>(encoded_extensions.data()),
encoded_extensions.size())) {
return absl::InvalidArgumentError("failed to read encoded_extensions");
}
ExtendedTokenRequest out;
ANON_TOKENS_ASSIGN_OR_RETURN(out.request,
UnmarshalTokenRequest(encoded_token_request));
ANON_TOKENS_ASSIGN_OR_RETURN(out.extensions,
DecodeExtensions(encoded_extensions));
return out;
}
absl::Status ValidateExtensionsOrderAndValues(
const Extensions& extensions, absl::Span<uint16_t> expected_types,
absl::Time now) {
if (expected_types.size() != extensions.extensions.size()) {
return absl::InvalidArgumentError(
absl::StrFormat("Expected %d type, got %d", expected_types.size(),
extensions.extensions.size()));
}
for (int i = 0; i < expected_types.size(); i++) {
if (expected_types[i] != extensions.extensions[i].extension_type) {
return absl::InvalidArgumentError(absl::StrFormat(
"Expected %x type at index %d, got %x", expected_types[i], i,
extensions.extensions[i].extension_type));
}
}
return ValidateExtensionsValues(extensions, now);
}
absl::Status ValidateExtensionsValues(const Extensions& extensions,
absl::Time now) {
for (const Extension& ext : extensions.extensions) {
switch (ext.extension_type) {
case 0x0001: {
absl::StatusOr<ExpirationTimestamp> expiration_timestamp =
ExpirationTimestamp::FromExtension(ext);
if (!expiration_timestamp.ok()) {
return expiration_timestamp.status();
}
if (expiration_timestamp->timestamp % kFifteenMinutesInSeconds != 0) {
return absl::InvalidArgumentError(
"Expiration timestamp is not rounded");
}
absl::Time timestamp =
absl::FromUnixSeconds(expiration_timestamp->timestamp);
if (timestamp < now || timestamp > now + absl::Hours(kOneWeekToHours)) {
return absl::InvalidArgumentError(
"Expiration timestamp is out of range");
}
break;
}
case 0x0002: {
absl::StatusOr<GeoHint> geo_hint = GeoHint::FromExtension(ext);
if (!geo_hint.ok()) {
return geo_hint.status();
}
if (geo_hint->country_code.length() != kAlpha2CountryCodeLength) {
return absl::InvalidArgumentError("Country code is not 2 characters");
}
for (const char& c : geo_hint->country_code) {
if (!absl::ascii_isupper(c)) {
return absl::InvalidArgumentError("Country code is not uppercase");
}
}
for (const char& c : geo_hint->region) {
if (!absl::ascii_isupper(c) && !absl::ascii_ispunct(c)) {
return absl::InvalidArgumentError("Region is not uppercase");
}
}
break;
}
case 0xF001: {
absl::StatusOr<ServiceType> service_type =
ServiceType::FromExtension(ext);
if (!service_type.ok()) {
return service_type.status();
}
break;
}
case 0xF002: {
absl::StatusOr<DebugMode> debug_mode = DebugMode::FromExtension(ext);
if (!debug_mode.ok()) {
return debug_mode.status();
}
break;
}
case 0xF003: {
absl::StatusOr<ProxyLayer> proxy_layer = ProxyLayer::FromExtension(ext);
if (!proxy_layer.ok()) {
return proxy_layer.status();
}
break;
}
default: {
return absl::InvalidArgumentError("Unsupported extension type");
}
}
}
return absl::OkStatus();
}
} // namespace anonymous_tokens