use super::{Header, RegisteredHeader, Secret};
use crate::errors::{Error, ValidationError};
use crate::jwa::SignatureAlgorithm;
use crate::serde_custom;
use data_encoding::BASE64URL_NOPAD;
use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize};
fn serialize_header<H: Serialize>(header: &Header<H>) -> Result<Vec<u8>, serde_json::Error> {
serde_json::to_vec(header)
}
fn signing_input(protected_header: &[u8], payload: &[u8]) -> Vec<u8> {
let hlen = BASE64URL_NOPAD.encode_len(protected_header.len());
let plen = BASE64URL_NOPAD.encode_len(payload.len());
let mut r = Vec::with_capacity(hlen + plen + 1);
r.append(&mut BASE64URL_NOPAD.encode(protected_header).into_bytes());
r.push(b'.');
r.append(&mut BASE64URL_NOPAD.encode(payload).into_bytes());
r
}
#[derive(Debug, Clone)]
pub struct Signable {
protected_header_registered: RegisteredHeader,
protected_header_serialized: Vec<u8>,
payload: Vec<u8>,
}
impl Signable {
pub fn new<H: Serialize>(
header: Header<H>,
payload: Vec<u8>,
) -> Result<Self, serde_json::Error> {
let protected_header_serialized = serialize_header(&header)?;
let protected_header_registered = header.registered;
Ok(Self {
protected_header_registered,
protected_header_serialized,
payload,
})
}
pub fn sign(self, secret: Secret) -> Result<SignedData, Error> {
SignedData::sign(self, secret)
}
fn signing_input(&self) -> Vec<u8> {
signing_input(&self.protected_header_serialized, &self.payload)
}
pub fn protected_header_registered(&self) -> &RegisteredHeader {
&self.protected_header_registered
}
pub fn protected_header_serialized(&self) -> &[u8] {
&self.protected_header_serialized
}
pub fn deserialize_protected_header<H: DeserializeOwned>(
&self,
) -> serde_json::Result<Header<H>> {
serde_json::from_slice(&self.protected_header_serialized)
}
pub fn payload(&self) -> &[u8] {
&self.payload
}
pub fn deserialize_json_payload<T: DeserializeOwned>(&self) -> serde_json::Result<T> {
serde_json::from_slice(&self.payload)
}
}
#[derive(Clone)]
pub struct SignedData {
data: Signable,
#[allow(dead_code)]
secret: Secret,
signature: Vec<u8>,
}
impl SignedData {
pub fn sign(data: Signable, secret: Secret) -> Result<Self, Error> {
let signature = data
.protected_header_registered
.algorithm
.sign(&data.signing_input(), &secret)?;
Ok(Self {
data,
secret,
signature,
})
}
pub fn serialize_flattened(&self) -> String {
let payload = self.data.payload.clone();
let protected_header = self.data.protected_header_serialized.clone();
let signature = self.signature.clone();
let s = FlattenedRaw {
payload,
protected_header,
signature,
signatures: (),
unprotected_header: (),
};
serde_json::to_string(&s).expect("Failed to serialize FlattenedRaw to JSON")
}
pub fn verify_flattened(
data: &[u8],
secret: Secret,
algorithm: SignatureAlgorithm,
) -> Result<Self, Error> {
let raw: FlattenedRaw = serde_json::from_slice(data)?;
algorithm
.verify(&raw.signature, &raw.signing_input(), &secret)
.map_err(|_| ValidationError::InvalidSignature)?;
let protected_header_registered: RegisteredHeader =
serde_json::from_slice(&raw.protected_header)?;
if protected_header_registered.algorithm != algorithm {
Err(ValidationError::WrongAlgorithmHeader)?;
}
let data = Signable {
protected_header_registered,
protected_header_serialized: raw.protected_header,
payload: raw.payload,
};
Ok(Self {
data,
secret,
signature: raw.signature,
})
}
pub fn data(&self) -> &Signable {
&self.data
}
}
fn deserialize_reject<'de, D>(_de: D) -> Result<(), D::Error>
where
D: Deserializer<'de>,
{
Err(serde::de::Error::custom("invalid field"))
}
#[derive(Serialize, Deserialize)]
struct FlattenedRaw {
#[serde(rename = "protected", with = "serde_custom::byte_sequence")]
protected_header: Vec<u8>,
#[serde(with = "serde_custom::byte_sequence")]
payload: Vec<u8>,
#[serde(with = "serde_custom::byte_sequence")]
signature: Vec<u8>,
#[serde(default, deserialize_with = "deserialize_reject", skip_serializing)]
#[allow(dead_code)]
signatures: (),
#[serde(
rename = "header",
default,
deserialize_with = "deserialize_reject",
skip_serializing
)]
#[allow(dead_code)]
unprotected_header: (),
}
impl FlattenedRaw {
fn signing_input(&self) -> Vec<u8> {
signing_input(&self.protected_header, &self.payload)
}
}
#[cfg(test)]
mod tests {
use std::str::{self, FromStr};
use serde::{Deserialize, Serialize};
use super::{Header, Secret, Signable, SignedData};
use crate::jwa::SignatureAlgorithm;
use crate::jws::RegisteredHeader;
use crate::{ClaimsSet, CompactJson, CompactPart, Empty, RegisteredClaims, SingleOrMultiple};
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
struct PrivateClaims {
company: String,
department: String,
}
impl CompactJson for PrivateClaims {}
static HS256_PAYLOAD: &str = "{\"protected\":\"eyJhbGciOiJIUz\
I1NiIsInR5cCI6IkpXVCJ9\",\"payload\":\"eyJpc3MiOiJodHRwczovL3\
d3dy5hY21lLmNvbS8iLCJzdWIiOiJKb2huIERvZSIsImF1ZCI6Imh0dHBzOi8vYWNtZ\
S1jdXN0b21lci5jb20vIiwibmJmIjoxMjM0LCJjb21wYW55IjoiQUNNRSIsImRlcGFy\
dG1lbnQiOiJUb2lsZXQgQ2xlYW5pbmcifQ\",\"signature\":\"VFCl2un1Kc17odzOe2Ehf4DVrW\
ddu3U4Ux3GFpOZHtc\"}";
#[test]
fn flattened_jws_round_trip_none() {
let expected_value = not_err!(serde_json::to_value(
"{\"protected\":\"eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0\",\
\"payload\":\"eyJpc3MiOiJodHRwczovL3d3dy5hY21lLmNvbS8iLCJz\
dWIiOiJKb2huIERvZSIsImF1ZCI6Imh0dHBzOi8vYWNtZS1jdXN0b21lci5j\
b20vIiwibmJmIjoxMjM0LCJjb21wYW55IjoiQUNNRSIsImRlcGFydG1lbnQi\
OiJUb2lsZXQgQ2xlYW5pbmcifQ\",\
\"signature\":\"\"}"
));
let expected_claims = ClaimsSet::<PrivateClaims> {
registered: RegisteredClaims {
issuer: Some(not_err!(FromStr::from_str("https://www.acme.com/"))),
subject: Some(not_err!(FromStr::from_str("John Doe"))),
audience: Some(SingleOrMultiple::Single(not_err!(FromStr::from_str(
"https://acme-customer.com/"
)))),
not_before: Some(1234.into()),
..Default::default()
},
private: PrivateClaims {
department: "Toilet Cleaning".to_string(),
company: "ACME".to_string(),
},
};
let expected_jwt = not_err!(SignedData::sign(
not_err!(Signable::new::<Empty>(
From::from(RegisteredHeader {
algorithm: SignatureAlgorithm::None,
..Default::default()
}),
expected_claims.to_bytes().unwrap(),
)),
Secret::None,
));
let token = expected_jwt.serialize_flattened();
assert_eq!(expected_value, not_err!(serde_json::to_value(&token)));
let biscuit: SignedData = not_err!(SignedData::verify_flattened(
token.as_bytes(),
Secret::None,
SignatureAlgorithm::None,
));
let actual_claims: ClaimsSet<PrivateClaims> =
not_err!(biscuit.data().deserialize_json_payload());
assert_eq!(&expected_claims, &actual_claims);
}
#[test]
fn flattened_jws_round_trip_hs256() {
let expected_claims = ClaimsSet::<PrivateClaims> {
registered: RegisteredClaims {
issuer: Some(not_err!(FromStr::from_str("https://www.acme.com/"))),
subject: Some(not_err!(FromStr::from_str("John Doe"))),
audience: Some(SingleOrMultiple::Single(not_err!(FromStr::from_str(
"https://acme-customer.com/"
)))),
not_before: Some(1234.into()),
..Default::default()
},
private: PrivateClaims {
department: "Toilet Cleaning".to_string(),
company: "ACME".to_string(),
},
};
let expected_jwt = not_err!(SignedData::sign(
not_err!(Signable::new(
From::from(RegisteredHeader {
algorithm: SignatureAlgorithm::HS256,
..Default::default()
}),
expected_claims.to_bytes().unwrap(),
)),
Secret::Bytes("secret".to_string().into_bytes())
));
let token = expected_jwt.serialize_flattened();
assert_eq!(
not_err!(serde_json::to_value(HS256_PAYLOAD)),
not_err!(serde_json::to_value(&token))
);
let biscuit = not_err!(SignedData::verify_flattened(
token.as_bytes(),
Secret::Bytes("secret".to_string().into_bytes()),
SignatureAlgorithm::HS256
));
assert_eq!(
&expected_claims,
¬_err!(biscuit.data().deserialize_json_payload())
);
}
#[test]
fn flattened_jws_round_trip_rs256() {
let expected_value = not_err!(serde_json::to_value(
"{\"protected\":\"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9\",\
\"payload\":\"eyJpc3MiOiJodHRwczovL3d3dy5hY21lLmNvbS8iLCJ\
zdWIiOiJKb2huIERvZSIsImF1ZCI6Imh0dHBzOi8vYWNtZS1jdXN0b21lci\
5jb20vIiwibmJmIjoxMjM0LCJjb21wYW55IjoiQUNNRSIsImRlcGFydG1lb\
nQiOiJUb2lsZXQgQ2xlYW5pbmcifQ\",\
\"signature\":\"Gat3NBUTaCyvroil66U0nId4-l6VqbtJYIsM9wRbWo4\
5oYoN-NxYIyl8M-9AlEPseg-4SIuo-A-jccJOWGeWWwy-Een_92wg18II58\
luHz7vAyclw1maJBKHmuj8f2wE_Ky8ir3iTpTGkJQ3IUU9SuU9Fkvajm4jg\
WUtRPpjHm_IqyxV8NkHNyN0p5CqeuRC8sZkOSFkm9b0WnWYRVls1QOjBnN9\
w9zW9wg9DGwj10pqg8hQ5sy-C3J-9q1zJgGDXInkhPLjitO9wzWg4yfVt-C\
JNiHsJT7RY_EN2VmbG8UOjHp8xUPpfqUKyoQttKaQkJHdjP_b47LO4ZKI4U\
ivlA\"}"
));
let expected_claims = ClaimsSet::<PrivateClaims> {
registered: RegisteredClaims {
issuer: Some(not_err!(FromStr::from_str("https://www.acme.com/"))),
subject: Some(not_err!(FromStr::from_str("John Doe"))),
audience: Some(SingleOrMultiple::Single(not_err!(FromStr::from_str(
"https://acme-customer.com/"
)))),
not_before: Some(1234.into()),
..Default::default()
},
private: PrivateClaims {
department: "Toilet Cleaning".to_string(),
company: "ACME".to_string(),
},
};
let private_key =
Secret::rsa_keypair_from_file("test/fixtures/rsa_private_key.der").unwrap();
let expected_jwt = not_err!(SignedData::sign(
not_err!(Signable::new(
From::from(RegisteredHeader {
algorithm: SignatureAlgorithm::RS256,
..Default::default()
}),
expected_claims.to_bytes().unwrap(),
)),
private_key,
));
let token = expected_jwt.serialize_flattened();
assert_eq!(expected_value, not_err!(serde_json::to_value(&token)));
let public_key = Secret::public_key_from_file("test/fixtures/rsa_public_key.der").unwrap();
let biscuit = not_err!(SignedData::verify_flattened(
token.as_bytes(),
public_key,
SignatureAlgorithm::RS256,
));
assert_eq!(
expected_claims,
not_err!(biscuit.data().deserialize_json_payload())
);
}
#[test]
fn flattened_jws_verify_es256() {
use data_encoding::HEXUPPER;
let public_key =
"043727F96AAD416887DD75CC2E333C3D8E06DCDF968B6024579449A2B802EFC891F638C75\
1CF687E6FF9A280E11B7036585E60CA32BB469C3E57998A289E0860A6";
let jwt = "{\
\"payload\":\"eyJ0b2tlbl90eXBlIjoic2VydmljZSIsImlhdCI6MTQ5MjkzODU4OH0\",\
\"protected\":\"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9\",\
\"signature\":\"do_XppIOFthPWlTXL95CIBfgRdyAxbcIsUfM0YxMjCjqvp4ehHFA3I-JasABKzC8CAy4ndhCHsZdpAtKkqZMEA\"}";
let signing_secret = Secret::PublicKey(not_err!(HEXUPPER.decode(public_key.as_bytes())));
let token = not_err!(SignedData::verify_flattened(
jwt.as_bytes(),
signing_secret,
SignatureAlgorithm::ES256
));
let jwt_val: super::FlattenedRaw = not_err!(serde_json::from_str(jwt));
assert_eq!(jwt_val.payload.as_slice(), token.data().payload());
assert_eq!(
jwt_val.protected_header.as_slice(),
token.data().protected_header_serialized()
);
assert_eq!(&jwt_val.signature, &token.signature);
}
#[test]
fn flattened_jws_encode_with_additional_header_fields() {
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
struct CustomHeader {
something: String,
}
let expected_claims = ClaimsSet::<PrivateClaims> {
registered: RegisteredClaims {
issuer: Some(not_err!(FromStr::from_str("https://www.acme.com/"))),
subject: Some(not_err!(FromStr::from_str("John Doe"))),
audience: Some(SingleOrMultiple::Single(not_err!(FromStr::from_str(
"https://acme-customer.com/"
)))),
not_before: Some(1234.into()),
..Default::default()
},
private: PrivateClaims {
department: "Toilet Cleaning".to_string(),
company: "ACME".to_string(),
},
};
let header = Header {
registered: Default::default(),
private: CustomHeader {
something: "foobar".to_string(),
},
};
let expected_jwt = not_err!(SignedData::sign(
not_err!(Signable::new(
header.clone(),
expected_claims.to_bytes().unwrap()
)),
Secret::Bytes("secret".to_string().into_bytes())
));
let token = expected_jwt.serialize_flattened();
let biscuit = not_err!(SignedData::verify_flattened(
token.as_bytes(),
Secret::Bytes("secret".to_string().into_bytes()),
SignatureAlgorithm::HS256,
));
assert_eq!(
&header,
¬_err!(biscuit
.data()
.deserialize_protected_header::<CustomHeader>())
);
}
#[test]
#[should_panic(expected = "InvalidSignature")]
fn flattened_jws_decode_token_invalid_signature_hs256() {
let claims = SignedData::verify_flattened(
"{\"protected\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\",\
\"payload\":\"eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ\",\
\"signature\":\"pKscJVk7-aHxfmQKlaZxh5uhuKhGMAa-1F5IX5mfUwI\"}"
.as_bytes(),
Secret::Bytes("secret".to_string().into_bytes()),
SignatureAlgorithm::HS256,
);
let _ = claims.unwrap();
}
#[test]
#[should_panic(expected = "InvalidSignature")]
fn flattened_jws_decode_token_invalid_signature_rs256() {
let public_key = Secret::public_key_from_file("test/fixtures/rsa_public_key.der").unwrap();
let claims = SignedData::verify_flattened(
"{\"protected\":\"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9\",\
\"payload\":\"eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ\",\
\"signature\":\"pKscJVk7-aHxfmQKlaZxh5uhuKhGMAa-1F5IX5mfUwI\"}"
.as_bytes(),
public_key,
SignatureAlgorithm::RS256,
);
let _ = claims.unwrap();
}
#[test]
#[should_panic(expected = "WrongAlgorithmHeader")]
fn flattened_jws_decode_token_wrong_algorithm() {
let claims = SignedData::verify_flattened(
"{\"protected\":\"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9\",\
\"payload\":\"eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ\",\
\"signature\":\"pKscJVk7-aHxfmQKlaZxh5uhuKhGMAa-1F5IX5mfUwI\"}"
.as_bytes(),
Secret::Bytes("secret".to_string().into_bytes()),
SignatureAlgorithm::HS256,
);
let _ = claims.unwrap();
}
#[test]
#[should_panic(expected = "invalid field")]
fn flattened_jws_verify_reject_multiple_signatures() {
let _ = SignedData::verify_flattened(
br#"{"signatures": [], "protected": "", "payload": "", "signature: ""}"#,
Secret::None,
SignatureAlgorithm::None,
)
.unwrap();
}
#[test]
#[should_panic(expected = "invalid field")]
fn flattened_jws_verify_reject_unprotected_headers() {
let _ = SignedData::verify_flattened(
br#"{"header": "", "protected": "", "payload": "", "signature: ""}"#,
Secret::None,
SignatureAlgorithm::None,
)
.unwrap();
}
}