Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Public Metadata Issuance #25

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ categories = ["cryptography", "privacy"]
async-trait = "0.1.56"
base64 = "0.21.0"
generic-array = "0.14.5"
hkdf = "0.12.3"
rand = "0.8.5"
serde = "1"
sha2 = "0.10.2"
Expand All @@ -32,6 +33,7 @@ p384 = { version = "0.13.0", default-features = false, features = [
"voprf",
] }
blind-rsa-signatures = "0.15.0"
num-bigint-dig = "0.8"
http = "0.2.8"
typenum = "1.15.0"
nom = "7"
Expand Down
121 changes: 106 additions & 15 deletions src/auth/authorize.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
//! This module contains the authorization logic for redemption phase of the
//! protocol.

use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::{engine::general_purpose::URL_SAFE, Engine as _};
use generic_array::{ArrayLength, GenericArray};
use http::{header::HeaderName, HeaderValue};
use nom::branch::alt;
use nom::sequence::delimited;
use nom::{
bytes::complete::{tag, tag_no_case},
multi::{many1, separated_list1},
Expand Down Expand Up @@ -148,6 +151,30 @@ pub fn build_authorization_header<Nk: ArrayLength<u8>>(
Ok((header_name, header_value))
}

/// Builds a `Authorize` header according to the following scheme:
///
/// `PrivateToken token=... extensions=...`
///
/// # Errors
/// Returns an error if the token is not valid.
pub fn build_authorization_header_ext<Nk: ArrayLength<u8>>(
token: &Token<Nk>,
extensions: &[u8],
) -> Result<(HeaderName, HeaderValue), BuildError> {
let value = format!(
"PrivateToken token={} extensions={}",
URL_SAFE.encode(
token
.tls_serialize_detached()
.map_err(|_| BuildError::InvalidToken)?
),
URL_SAFE.encode(extensions),
);
let header_name = http::header::AUTHORIZATION;
let header_value = HeaderValue::from_str(&value).map_err(|_| BuildError::InvalidToken)?;
Ok((header_name, header_value))
}

/// Building error for the `Authorization` header values
#[derive(Error, Debug)]
pub enum BuildError {
Expand All @@ -167,10 +194,24 @@ pub fn parse_authorization_header<Nk: ArrayLength<u8>>(
) -> Result<Token<Nk>, ParseError> {
let s = value.to_str().map_err(|_| ParseError::InvalidInput)?;
let tokens = parse_header_value(s)?;
let token = tokens[0].clone();
let token = tokens[0].0.clone();
Ok(token)
}

/// Parses an `Authorization` header according to the following scheme:
///
/// `PrivateToken token=... [extensions=...]`
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// `PrivateToken token=... [extensions=...]`
/// `PrivateToken token=... [, extensions=...]`

///
/// # Errors
/// Returns an error if the header value is not valid.
pub fn parse_authorization_header_ext<Nk: ArrayLength<u8>>(
value: &HeaderValue,
) -> Result<(Token<Nk>, Option<Vec<u8>>), ParseError> {
let s = value.to_str().map_err(|_| ParseError::InvalidInput)?;
let mut tokens = parse_header_value(s)?;
Ok(tokens.pop().unwrap())
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should return an error instead

}

/// Parsing error for the `WWW-Authenticate` header values
#[derive(Error, Debug)]
pub enum ParseError {
Expand All @@ -189,7 +230,10 @@ fn parse_key_value(input: &str) -> IResult<&str, (&str, &str)> {
let (input, _) = tag("=")(input)?;
let (input, _) = opt_spaces(input)?;
let (input, value) = match key.to_lowercase().as_str() {
"token" => base64_char(input)?,
"token" | "extensions" => {
// Values may or may not be delimited with quotes.
alt((delimited(tag("\""), base64_char, tag("\"")), base64_char))(input)?
}
_ => {
return Err(nom::Err::Failure(nom::error::make_error(
input,
Expand All @@ -200,13 +244,14 @@ fn parse_key_value(input: &str) -> IResult<&str, (&str, &str)> {
Ok((input, (key, value)))
}

fn parse_private_token(input: &str) -> IResult<&str, &str> {
fn parse_private_token(input: &str) -> IResult<&str, (&str, Option<&str>)> {
let (input, _) = opt_spaces(input)?;
let (input, _) = tag_no_case("PrivateToken")(input)?;
let (input, _) = many1(space)(input)?;
let (input, key_values) = separated_list1(tag(","), parse_key_value)(input)?;
let (input, key_values) = separated_list1(tag(" "), parse_key_value)(input)?;

let mut token = None;
let mut extensions = None;
let err = nom::Err::Failure(nom::error::make_error(input, nom::error::ErrorKind::Tag));

for (key, value) in key_values {
Expand All @@ -217,33 +262,50 @@ fn parse_private_token(input: &str) -> IResult<&str, &str> {
}
token = Some(value)
}
"extensions" => {
if extensions.is_some() {
return Err(err);
}
extensions = Some(value)
}
_ => return Err(err),
}
}
let token = token.ok_or(err)?;

Ok((input, token))
Ok((input, (token, extensions)))
}

fn parse_private_tokens(input: &str) -> IResult<&str, Vec<&str>> {
fn parse_private_tokens(input: &str) -> IResult<&str, Vec<(&str, Option<&str>)>> {
separated_list1(tag(","), parse_private_token)(input)
}

fn parse_header_value<Nk: ArrayLength<u8>>(input: &str) -> Result<Vec<Token<Nk>>, ParseError> {
fn parse_header_value<Nk: ArrayLength<u8>>(
input: &str,
) -> Result<Vec<(Token<Nk>, Option<Vec<u8>>)>, ParseError> {
let (output, tokens) = parse_private_tokens(input).map_err(|_| ParseError::InvalidInput)?;
if !output.is_empty() {
return Err(ParseError::InvalidInput);
}
let tokens = tokens
.into_iter()
.map(|token_value| {
Token::tls_deserialize(
&mut URL_SAFE
.decode(token_value)
.map_err(|_| ParseError::InvalidToken)?
.as_slice(),
)
.map_err(|_| ParseError::InvalidToken)
.map(|(token_value, extensions_value)| {
let ext = extensions_value.and_then(|x| {
URL_SAFE_NO_PAD
.decode(x)
.or_else(|_| URL_SAFE.decode(x))
.ok()
});
Ok((
Token::tls_deserialize(
&mut URL_SAFE
.decode(token_value)
.map_err(|_| ParseError::InvalidToken)?
.as_slice(),
)
.map_err(|_| ParseError::InvalidToken)?,
ext,
))
})
.collect::<Result<Vec<_>, _>>()?;
Ok(tokens)
Expand Down Expand Up @@ -275,3 +337,32 @@ fn builder_parser_test() {
assert_eq!(token.token_key_id(), &token_key_id);
assert_eq!(token.authenticator(), &authenticator);
}

#[test]
fn builder_parser_extensions_test() {
use generic_array::typenum::U32;

let nonce = [1u8; 32];
let challenge_digest = [2u8; 32];
let token_key_id = [3u8; 32];
let authenticator = [4u8; 32];
let token = Token::<U32>::new(
TokenType::Private,
nonce,
challenge_digest,
token_key_id,
GenericArray::clone_from_slice(&authenticator),
);
let extensions = b"hello world";
let (header_name, header_value) = build_authorization_header_ext(&token, extensions).unwrap();

assert_eq!(header_name, http::header::AUTHORIZATION);

let (token, maybe_extensions) = parse_authorization_header_ext::<U32>(&header_value).unwrap();
assert_eq!(token.token_type(), TokenType::Private);
assert_eq!(token.nonce(), nonce);
assert_eq!(token.challenge_digest(), &challenge_digest);
assert_eq!(token.token_key_id(), &token_key_id);
assert_eq!(token.authenticator(), &authenticator);
assert_eq!(maybe_extensions, Some(extensions.to_vec()));
}
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ pub enum TokenType {
Public = 2,
/// Batched token
Batched = 0xF91A,
/// Publicly verifiable token with public metadata
PublicMetadata = 0xDA7A,
/// Privately verifiable token with public metadata
PrivateMetadata = 0xDA7B,
}

/// Token key ID
Expand Down
59 changes: 45 additions & 14 deletions src/public_tokens/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,25 @@ use thiserror::Error;

use crate::{
auth::{authenticate::TokenChallenge, authorize::Token},
ChallengeDigest, KeyId, TokenInput, TokenType,
ChallengeDigest, KeyId, TokenInput,
};

use super::{key_id_to_token_key_id, public_key_to_key_id, Nonce, TokenRequest, TokenResponse, NK};
use super::{
key_id_to_token_key_id, public_key_to_key_id, Nonce, TokenProtocol, TokenRequest,
TokenResponse, NK,
};

/// Client-side state that is kept between the token requests and token responses.
#[derive(Debug)]
pub struct TokenState {
blinding_result: BlindingResult,
token_input: TokenInput,
challenge_digest: ChallengeDigest,
/// A prepared version of token_input
prepared_input: Vec<u8>,
/// An augmented public key if used by the issuance protocol.
/// If none, the client's public key should be used.
public_key: Option<PublicKey>,
}

/// Errors that can occur when issuing token requests.
Expand Down Expand Up @@ -55,14 +63,15 @@ impl Client {
Self { key_id, public_key }
}

/// Issue a token request.
/// Issue a token request using the specified Privacy Pass issuance protocol.
///
/// # Errors
/// Returns an error if the challenge is invalid.
pub fn issue_token_request<R: RngCore + CryptoRng>(
pub fn issue_token_request_protocol<R: RngCore + CryptoRng>(
&mut self,
rng: &mut R,
challenge: TokenChallenge,
protocol: TokenProtocol,
) -> Result<(TokenRequest, TokenState), IssueTokenRequestError> {
let mut nonce: Nonce = [0u8; 32];
rng.fill_bytes(&mut nonce);
Expand All @@ -76,20 +85,24 @@ impl Client {
// token_input = concat(0x0002, nonce, challenge_digest, token_key_id)
// blinded_msg, blind_inv = rsabssa_blind(pkI, token_input)

let token_input = TokenInput::new(TokenType::Public, nonce, challenge_digest, self.key_id);
let token_input =
TokenInput::new(protocol.token_type(), nonce, challenge_digest, self.key_id);
let prepared_input = protocol.prepare_message(token_input.serialize());
let public_key = protocol.augment_public_key(&self.public_key);

let options = Options::default();
let blinding_result = self
.public_key
.blind(rng, token_input.serialize(), false, &options)
let blinding_result = public_key
.as_ref()
.unwrap_or(&self.public_key)
.blind(rng, &prepared_input, false, &options)
.map_err(|_| IssueTokenRequestError::BlindingError)?;

debug_assert!(blinding_result.blind_msg.len() == NK);
let mut blinded_msg = [0u8; NK];
blinded_msg.copy_from_slice(blinding_result.blind_msg.as_slice());

let token_request = TokenRequest {
token_type: TokenType::Public,
token_type: protocol.token_type(),
token_key_id: key_id_to_token_key_id(&self.key_id),
blinded_msg,
};
Expand All @@ -98,10 +111,24 @@ impl Client {
blinding_result,
token_input,
challenge_digest,
prepared_input,
public_key,
};
Ok((token_request, token_state))
}

/// Issue a token request.
///
/// # Errors
/// Returns an error if the challenge is invalid.
pub fn issue_token_request<R: RngCore + CryptoRng>(
&mut self,
rng: &mut R,
challenge: TokenChallenge,
) -> Result<(TokenRequest, TokenState), IssueTokenRequestError> {
self.issue_token_request_protocol(rng, challenge, TokenProtocol::Basic)
}

/// Issue a token.
///
/// # Errors
Expand All @@ -112,23 +139,27 @@ impl Client {
token_state: &TokenState,
) -> Result<Token<U256>, IssueTokenError> {
// authenticator = rsabssa_finalize(pkI, nonce, blind_sig, blind_inv)
let token_input = token_state.token_input.serialize();
let options = Options::default();
let blind_sig = BlindSignature(token_response.blind_sig.to_vec());
let signature = self
.public_key
let public_key = token_state.public_key.as_ref().unwrap_or(&self.public_key);
let signature = public_key
.finalize(
&blind_sig,
&token_state.blinding_result.secret,
None,
token_input,
&token_state.prepared_input,
&options,
)
.map_err(|_| IssueTokenError::InvalidTokenResponse)?;

debug_assert!(signature
.verify(public_key, None, &token_state.prepared_input, &options,)
.is_ok());

let authenticator: GenericArray<u8, U256> =
GenericArray::clone_from_slice(&signature[0..256]);
Ok(Token::new(
TokenType::Public,
token_state.token_input.token_type,
token_state.token_input.nonce,
token_state.challenge_digest,
token_state.token_input.key_id,
Expand Down
Loading