Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
9a55b73
Update README to have links to implemented specification
thibmeu May 27, 2026
92a90b3
justfile: remove zsh dependency, use default sh shell
thibmeu May 27, 2026
42a8904
Add Privacy Pass test vectors
thibmeu May 27, 2026
2855cc6
Validate base64url padding
thibmeu May 27, 2026
45e87d9
Fix token challenge encoding
thibmeu May 27, 2026
257779b
Use compressed P-384 encoding
thibmeu May 27, 2026
8eaba3f
Fix VOPRF finalization
thibmeu May 27, 2026
a1f1f83
Parse multiple PrivateToken challenges
thibmeu May 27, 2026
634c009
Handle empty vector strings
thibmeu May 27, 2026
30a4f4a
Assert token response vector parts
thibmeu May 27, 2026
7cfa5b5
Fix batched request parsing
thibmeu May 27, 2026
8749285
Accept RSA-PSS token keys
thibmeu May 27, 2026
443975e
Require RSASSA-PSS token keys
thibmeu May 27, 2026
85f63b7
Use OpenSSL PSS encoding
thibmeu May 27, 2026
0a8b493
Use Montgomery RSA blinding
thibmeu May 27, 2026
efbb564
Use constant-time RSA blind signing
thibmeu May 27, 2026
ac72119
Validate RSASSA-PSS parameters
thibmeu May 27, 2026
d59f1ec
Validate blind RSA representatives
thibmeu May 27, 2026
66c7fc7
Cleanse RSA blinding factors
thibmeu May 27, 2026
2308d7e
Check RSA parameter allocation
thibmeu May 27, 2026
5af286f
Cleanse RSA private exponent
thibmeu May 27, 2026
d773663
Validate component RSA keys
thibmeu May 27, 2026
b2e20ca
Ignore build-example/ folder
thibmeu May 27, 2026
50b7057
Fix token parsing
thibmeu May 27, 2026
16995d5
Harden VOPRF verification
thibmeu May 27, 2026
608e4be
Fix HTTP auth parsing
thibmeu May 27, 2026
5a22eb9
Validate token provider origin
thibmeu May 27, 2026
a15175d
Fix build config
thibmeu May 27, 2026
cdd5768
Add crypto vector tests
thibmeu May 27, 2026
3cf51be
Limit Blind RSA profile
thibmeu May 27, 2026
856bb7e
Store blinding inverse in standard form, convert to Montgomery in fin…
suhasHere May 27, 2026
b75337d
Add range validation and error checking in blind RSA operations
suhasHere May 27, 2026
c6ff122
Add DLEQ scalar range validation and NULL safety in VOPRF
suhasHere May 27, 2026
8e7413f
Fix MSVC deprecation warning for OpenSSL 3.0 RSA APIs
suhasHere May 28, 2026
a761135
Fix crypto validation regressions
thibmeu May 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ jobs:
run: |
cmake -B build \
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \
-DOPENSSL_ROOT_DIR="$(brew --prefix openssl@3)" \
-DPRIVACY_PASS_BUILD_TESTS=ON \
-DPRIVACY_PASS_BUILD_BENCHMARKS=ON \
-DPRIVACY_PASS_BUILD_MOQ=ON
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Build directories
build/
build-examples/
cmake-build-*/

# IDE
Expand Down
14 changes: 13 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function(privacy_pass_set_warnings target)
endfunction()

# Dependencies
find_package(OpenSSL REQUIRED)
find_package(OpenSSL 3.0 REQUIRED)

include(FetchContent)

Expand Down Expand Up @@ -58,6 +58,14 @@ if(PRIVACY_PASS_BUILD_TESTS)
GIT_TAG v2.4.12
)
FetchContent_MakeAvailable(doctest)

set(JSON_BuildTests OFF CACHE INTERNAL "")
FetchContent_Declare(
nlohmann_json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.3
)
FetchContent_MakeAvailable(nlohmann_json)
endif()

# Fetch Google Benchmark if building benchmarks
Expand Down Expand Up @@ -244,6 +252,10 @@ if(PRIVACY_PASS_BUILD_TESTS)
target_link_libraries(privacy_pass_tests PRIVATE
privacy_pass
doctest::doctest
nlohmann_json::nlohmann_json
)
target_compile_definitions(privacy_pass_tests PRIVATE
PRIVACY_PASS_TEST_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/tests/test_data"
)
if(PRIVACY_PASS_BUILD_MOQ)
target_link_libraries(privacy_pass_tests PRIVATE privacy_pass_moq)
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
| macOS | [![macOS](https://github.com/Quicr/privacypass-cpp/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/Quicr/privacypass-cpp/actions/workflows/ci.yml?query=branch%3Amain) |
| Windows | [![Windows](https://github.com/Quicr/privacypass-cpp/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/Quicr/privacypass-cpp/actions/workflows/ci.yml?query=branch%3Amain) |

C++ implementation of the Privacy Pass protocol (RFC 9576-9578) with optional MOQ extension support.
C++ implementation of the Privacy Pass protocol ([RFC9576](https://datatracker.ietf.org/doc/html/rfc9576), [RFC9577](https://datatracker.ietf.org/doc/html/rfc9577), [RFC9578](https://datatracker.ietf.org/doc/html/rfc9578)) with optional MOQ extension ([draft-ietf-moq-privacy-pass-auth-02](https://datatracker.ietf.org/doc/html/draft-ietf-moq-privacy-pass-auth-02)) support.

## Features

- Blind RSA (Token Type 0x0002) and VOPRF P-384 (Token Type 0x0001)
- Token challenges, requests, responses, and redemption
- Optional MOQ extension (draft-ietf-moq-privacy-pass-auth)
- Optional MOQ extension ([draft-ietf-moq-privacy-pass-auth-02](https://datatracker.ietf.org/doc/html/draft-ietf-moq-privacy-pass-auth-02))
- Modern C++23 with `std::expected` error handling

## Building
Expand Down
2 changes: 1 addition & 1 deletion examples/moq_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ void print_hex(const std::string& label, ByteView data, size_t max_bytes = 16) {
// Example MOQ Client built on TokenProvider
class MoqClient {
public:
MoqClient() : provider_(TokenProviderConfig{.max_cached_tokens = 10}) {}
MoqClient() : provider_(TokenProviderConfig{.max_cached_tokens = 10, .origin_name = "origin.example.com"}) {}

void add_issuer(const std::string& name, const PublicKey& key) {
provider_.add_issuer_key(name, key);
Expand Down
1 change: 1 addition & 0 deletions include/privacy_pass/core/serialization.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ class ByteWriter {
namespace base64url {

[[nodiscard]] std::string encode(ByteView data);
[[nodiscard]] std::string encode_padded(ByteView data);
[[nodiscard]] Result<Bytes> decode(std::string_view encoded);

} // namespace base64url
Expand Down
2 changes: 2 additions & 0 deletions include/privacy_pass/core/token_provider.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <functional>
#include <memory>
#include <optional>
#include <string>

namespace privacy_pass {

Expand All @@ -28,6 +29,7 @@ struct TokenProviderConfig {
size_t max_cached_tokens{100};
std::chrono::seconds token_prefetch_threshold{60};
bool allow_token_reuse{false}; // For testing only
std::string origin_name;
};

// Callback for async token fetching
Expand Down
2 changes: 1 addition & 1 deletion include/privacy_pass/core/token_request.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace privacy_pass {
struct TokenRequest {
TokenType token_type;
uint8_t truncated_token_key_id; // LSB of full key ID
Bytes blinded_msg; // Nk bytes for RSA, 97 bytes for VOPRF P-384
Bytes blinded_msg; // Nk bytes for RSA, 49 bytes for VOPRF P-384

// Create a token request
static TokenRequest create(
Expand Down
2 changes: 1 addition & 1 deletion include/privacy_pass/core/token_response.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ struct BlindRsaTokenResponse {

// VOPRF token response (P-384)
struct VoprfTokenResponse {
Bytes evaluate_msg; // Ne = 97 bytes (compressed P-384 point)
Bytes evaluate_msg; // Ne = 49 bytes (compressed P-384 point)
Bytes evaluate_proof; // 2*Ns = 96 bytes (DLEQ proof)

[[nodiscard]] Result<Bytes> serialize() const;
Expand Down
2 changes: 1 addition & 1 deletion include/privacy_pass/core/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ struct TokenTypeInfo {
static constexpr TokenTypeInfo for_type(TokenType t) noexcept {
switch (t) {
case TokenType::VOPRF_P384_SHA384:
return {t, 48, 32, 97, false};
return {t, 48, 32, 49, false};
case TokenType::BLIND_RSA:
return {t, 256, 32, 256, true};
case TokenType::VOPRF_RISTRETTO255_SHA512:
Expand Down
2 changes: 1 addition & 1 deletion include/privacy_pass/crypto/voprf.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace privacy_pass::crypto {
// Supports P-384 with SHA-384

// P-384 constants
constexpr size_t P384_ELEMENT_SIZE = 97; // Uncompressed point (1 + 48 + 48)
constexpr size_t P384_ELEMENT_SIZE = 49; // Compressed point (1 + 48)
constexpr size_t P384_SCALAR_SIZE = 48;
constexpr size_t P384_OUTPUT_SIZE = 48;
constexpr size_t P384_PROOF_SIZE = 96; // 2 * scalar size
Expand Down
6 changes: 5 additions & 1 deletion include/privacy_pass/http/auth_scheme.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@

#include <optional>
#include <string>
#include <vector>

namespace privacy_pass::http {

// WWW-Authenticate header parameters
struct ChallengeHeader {
std::string challenge; // Base64url-encoded TokenChallenge
std::string token_key; // Base64url-encoded issuer public key
std::optional<std::string> token_key; // Base64url-encoded issuer public key
std::optional<uint32_t> max_age; // Optional max-age in seconds

// Format as WWW-Authenticate header value
Expand All @@ -24,6 +25,9 @@ struct ChallengeHeader {
// Parse from WWW-Authenticate header value
[[nodiscard]] static Result<ChallengeHeader> parse(std::string_view header);

// Parse all PrivateToken challenges from a WWW-Authenticate header value
[[nodiscard]] static Result<std::vector<ChallengeHeader>> parse_all(std::string_view header);

// Decode the challenge
[[nodiscard]] Result<TokenChallenge> decode_challenge() const;

Expand Down
4 changes: 1 addition & 3 deletions justfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# Privacy Pass C++ - Build Commands

set shell := ["zsh", "-cu"]

default:
@just --list

Expand Down Expand Up @@ -57,7 +55,7 @@ format-check:

# Static analysis
lint:
clang-tidy src/**/*.cpp -- -I include -std=c++20
find src -name '*.cpp' -exec clang-tidy {} -- -I include -std=c++23 \;

# Generate compile_commands.json for IDE support
compile-commands: configure
Expand Down
7 changes: 2 additions & 5 deletions src/core/origin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -442,11 +442,8 @@ Result<bool> Origin::verify(
"No RSA keys configured"});

case TokenType::VOPRF_P384_SHA384:
if (impl_->private_origin) {
return impl_->private_origin->validate_structure(token, expected_challenge);
}
return std::unexpected(Error{ErrorCode::ISSUER_UNKNOWN,
"No VOPRF keys configured"});
return std::unexpected(Error{ErrorCode::UNSUPPORTED_TOKEN_TYPE,
"VOPRF tokens require issuer private-key verification"});

default:
return std::unexpected(Error{ErrorCode::UNSUPPORTED_TOKEN_TYPE,
Expand Down
16 changes: 15 additions & 1 deletion src/core/serialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,28 @@ std::string encode(ByteView data) {
return result;
}

std::string encode_padded(ByteView data) {
std::string result = encode(data);
while ((result.size() % 4) != 0) {
result.push_back('=');
}
return result;
}

Result<Bytes> decode(std::string_view encoded) {
if (encoded.empty()) {
return Bytes{};
}

// Remove padding if present
size_t padding = 0;
while (!encoded.empty() && encoded.back() == '=') {
encoded.remove_suffix(1);
++padding;
}

if (padding > 2 || encoded.find('=') != std::string_view::npos || encoded.size() % 4 == 1) {
return std::unexpected(Error{ErrorCode::INVALID_BASE64,
"Invalid base64url padding"});
}

if (encoded.empty()) {
Expand Down
4 changes: 4 additions & 0 deletions src/core/token.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ Result<Token> Token::deserialize(ByteView data) {
}
token.authenticator.assign(auth->begin(), auth->end());

if (!reader.empty()) {
return std::unexpected(Error{ErrorCode::MALFORMED_DATA, "Trailing Token data"});
}

return token;
}

Expand Down
Loading
Loading