Skip to content

Commit f2b11b7

Browse files
DavidSchinazicopybara-github
authored andcommitted
Add ability to check tokens from private_tokens tool
PiperOrigin-RevId: 864604897
1 parent 29926d4 commit f2b11b7

File tree

3 files changed

+119
-24
lines changed

3 files changed

+119
-24
lines changed

quiche/quic/masque/private_tokens.cc

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include <memory>
88
#include <string>
9+
#include <vector>
910

1011
#include "absl/status/status.h"
1112
#include "absl/status/statusor.h"
@@ -179,6 +180,12 @@ absl::Status ValidateToken(absl::string_view base64_public_key,
179180
if (!absl::WebSafeBase64Unescape(base64_public_key, &der_public_key)) {
180181
return absl::InvalidArgumentError("Failed to decode the base64 public key");
181182
}
183+
std::string token_key_id(SHA256_DIGEST_LENGTH, '\0');
184+
if (SHA256(reinterpret_cast<const uint8_t*>(der_public_key.data()),
185+
der_public_key.size(),
186+
reinterpret_cast<uint8_t*>(token_key_id.data())) == nullptr) {
187+
return absl::InternalError("Failed to compute token_key_id");
188+
}
182189
QUICHE_ASSIGN_OR_RETURN(
183190
bssl::UniquePtr<RSA> public_key,
184191
AT::RsaSsaPssPublicKeyFromDerEncoding(der_public_key));
@@ -188,7 +195,28 @@ absl::Status ValidateToken(absl::string_view base64_public_key,
188195
}
189196

190197
QUICHE_ASSIGN_OR_RETURN(AT::Token token, AT::UnmarshalToken(binary_token));
198+
if (token.token_key_id != token_key_id) {
199+
return absl::InvalidArgumentError(absl::StrCat(
200+
"Token key ID ", absl::BytesToHexString(token.token_key_id),
201+
" does not match the public key ID ",
202+
absl::BytesToHexString(token_key_id)));
203+
}
191204
return AT::PrivacyPassRsaBssaClient::Verify(token, *public_key);
192205
}
193206

207+
absl::Status TokenValidatesFromAtLeastOneKey(
208+
const std::vector<std::string>& base64_public_keys,
209+
absl::string_view base64_token) {
210+
absl::Status last_error =
211+
absl::InvalidArgumentError("No public keys provided");
212+
for (const std::string& base64_public_key : base64_public_keys) {
213+
absl::Status status = ValidateToken(base64_public_key, base64_token);
214+
if (status.ok()) {
215+
return absl::OkStatus();
216+
}
217+
last_error = status;
218+
}
219+
return last_error;
220+
}
221+
194222
} // namespace quic

quiche/quic/masque/private_tokens.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#define QUICHE_QUIC_MASQUE_PRIVATE_TOKENS_H_
77

88
#include <string>
9+
#include <vector>
910

1011
#include "absl/status/status.h"
1112
#include "absl/status/statusor.h"
@@ -40,6 +41,11 @@ absl::StatusOr<std::string> CreateTokenLocally(RSA* private_key,
4041
absl::Status ValidateToken(absl::string_view base64_public_key,
4142
absl::string_view base64_token);
4243

44+
// Checks the token against all keys using ValidateToken above.
45+
absl::Status TokenValidatesFromAtLeastOneKey(
46+
const std::vector<std::string>& base64_public_keys,
47+
absl::string_view base64_token);
48+
4349
} // namespace quic
4450

4551
#endif // QUICHE_QUIC_MASQUE_PRIVATE_TOKENS_H_

quiche/quic/masque/private_tokens_bin.cc

Lines changed: 85 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,61 +2,122 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
#include <algorithm>
56
#include <string>
67
#include <vector>
78

89
#include "absl/status/status.h"
910
#include "absl/strings/str_cat.h"
11+
#include "absl/strings/str_split.h"
1012
#include "openssl/base.h"
1113
#include "openssl/rsa.h"
1214
#include "quiche/quic/masque/private_tokens.h"
1315
#include "quiche/common/platform/api/quiche_command_line_flags.h"
1416
#include "quiche/common/platform/api/quiche_logging.h"
1517
#include "quiche/common/quiche_status_utils.h"
1618

19+
// This tool exists to help test out private tokens as defined in RFCs 9577 and
20+
// 9578.
21+
22+
// To generate a config based on existing keys in PEM files:
23+
// blaze run //quiche/quic/masque:private_tokens -- --alsologtostderr
24+
// --private_key_file=/path/to/private_key.pem
25+
// --public_key_file==/path/to/public_key.pem
26+
27+
// To test a token against a given public key in base64 format:
28+
// blaze run //quiche/quic/masque:private_tokens -- --alsologtostderr
29+
// --encoded_public_key="$PUBLIC_KEY" --token="$TOKEN"
30+
31+
// To test out whether a token matches an issuer URL:
32+
// blaze run //quiche/quic/masque:private_tokens -- --alsologtostderr
33+
// --token="$TOKEN" --encoded_public_key="$(
34+
// curl --silent -H "Accept: application/private-token-issuer-directory"
35+
// "$ISSUER_URL" | jq -r '.["token-keys"] | map(.["token-key"]) | join(",")')"
36+
1737
DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, private_key_file, "",
1838
"Path to the PEM-encoded RSA private key.");
1939

2040
DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, public_key_file, "",
2141
"Path to the PEM-encoded RSA public key.");
2242

43+
DEFINE_QUICHE_COMMAND_LINE_FLAG(
44+
std::string, encoded_public_key, "",
45+
"Base64-encoded public key to use for token validation. Multiple entries "
46+
"may be passed in by separating them with commas.");
47+
48+
DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, token, "", "Token to validate.");
49+
2350
namespace quic {
2451
namespace {
2552

2653
absl::Status RunPrivateTokens(int argc, char* argv[]) {
27-
const char* usage =
28-
"Usage: private_tokens --private_key_file=<private-key-file> "
29-
"--public_key_file=<public-key-file>";
54+
const char* usage = "Usage: private_tokens";
3055
std::vector<std::string> params =
3156
quiche::QuicheParseCommandLineFlags(usage, argc, argv);
57+
const std::string private_key_file =
58+
quiche::GetQuicheCommandLineFlag(FLAGS_private_key_file);
59+
const std::string public_key_file =
60+
quiche::GetQuicheCommandLineFlag(FLAGS_public_key_file);
61+
const std::string encoded_public_key_from_flags =
62+
quiche::GetQuicheCommandLineFlag(FLAGS_encoded_public_key);
63+
std::vector<std::string> encoded_public_keys = absl::StrSplit(
64+
encoded_public_key_from_flags, ',', absl::SkipWhitespace());
65+
const std::string token_from_flags =
66+
quiche::GetQuicheCommandLineFlag(FLAGS_token);
3267

33-
QUICHE_ASSIGN_OR_RETURN(bssl::UniquePtr<RSA> private_key,
34-
ParseRsaPrivateKey(quiche::GetQuicheCommandLineFlag(
35-
FLAGS_private_key_file)));
36-
QUICHE_ASSIGN_OR_RETURN(bssl::UniquePtr<RSA> public_key,
37-
ParseRsaPublicKey(quiche::GetQuicheCommandLineFlag(
38-
FLAGS_public_key_file)));
39-
QUICHE_ASSIGN_OR_RETURN(std::string encoded_public_key,
40-
EncodePrivacyPassPublicKey(public_key.get()));
68+
bssl::UniquePtr<RSA> public_key;
69+
std::string encoded_public_key;
70+
if (!public_key_file.empty()) {
71+
QUICHE_ASSIGN_OR_RETURN(public_key, ParseRsaPublicKey(public_key_file));
72+
QUICHE_ASSIGN_OR_RETURN(encoded_public_key,
73+
EncodePrivacyPassPublicKey(public_key.get()));
74+
if (!encoded_public_keys.empty()) {
75+
if (std::find(encoded_public_keys.begin(), encoded_public_keys.end(),
76+
encoded_public_key) == encoded_public_keys.end()) {
77+
return absl::InvalidArgumentError(
78+
"Public key from --public_key_file does not match "
79+
"--encoded_public_key");
80+
}
81+
} else {
82+
encoded_public_keys.push_back(encoded_public_key);
4183

42-
std::string issuer_config = absl::StrCat(
43-
"{\n \"issuer-request-uri\": \"https://issuer.example.net/request\",\n",
44-
" \"token-keys\": [\n {\n \"token-type\": 2,\n",
45-
" \"token-key\": \"", encoded_public_key, "\",\n }\n ]\n}");
84+
std::string issuer_config = absl::StrCat(
85+
"{\n \"issuer-request-uri\": "
86+
"\"https://issuer.example.net/request\",\n",
87+
" \"token-keys\": [\n {\n \"token-type\": 2,\n",
88+
" \"token-key\": \"", encoded_public_key, "\",\n }\n ]\n}");
4689

47-
QUICHE_LOG(INFO) << "The issuer config could look like:\n" << issuer_config;
90+
QUICHE_LOG(INFO) << "The issuer config could look like:\n"
91+
<< issuer_config;
92+
}
93+
}
4894

49-
QUICHE_ASSIGN_OR_RETURN(
50-
std::string token,
51-
CreateTokenLocally(private_key.get(), public_key.get()));
95+
if (!token_from_flags.empty()) {
96+
QUICHE_RETURN_IF_ERROR(
97+
TokenValidatesFromAtLeastOneKey(encoded_public_keys, token_from_flags));
98+
QUICHE_LOG(INFO) << "Validated token from --token";
99+
}
100+
if (!private_key_file.empty()) {
101+
QUICHE_ASSIGN_OR_RETURN(bssl::UniquePtr<RSA> private_key,
102+
ParseRsaPrivateKey(private_key_file));
103+
if (public_key == nullptr) {
104+
return absl::InvalidArgumentError(
105+
"--public_key_file is required when --private_key_file is set.");
106+
}
107+
QUICHE_ASSIGN_OR_RETURN(
108+
std::string generated_token,
109+
CreateTokenLocally(private_key.get(), public_key.get()));
52110

53-
std::string auth_header =
54-
absl::StrCat("Authorization: PrivateToken token=\"", token, "\"");
111+
std::string auth_header = absl::StrCat(
112+
"Authorization: PrivateToken token=\"", generated_token, "\"");
55113

56-
QUICHE_LOG(INFO) << "The auth header would look like:\n" << auth_header;
114+
QUICHE_LOG(INFO) << "The generated auth header would look like:\n"
115+
<< auth_header;
57116

58-
QUICHE_RETURN_IF_ERROR(ValidateToken(encoded_public_key, token));
59-
QUICHE_LOG(INFO) << "Token validation succeeded";
117+
QUICHE_RETURN_IF_ERROR(
118+
TokenValidatesFromAtLeastOneKey(encoded_public_keys, generated_token));
119+
QUICHE_LOG(INFO) << "Validated locally-generated token";
120+
}
60121
return absl::OkStatus();
61122
}
62123

0 commit comments

Comments
 (0)