use base64::{Engine as _, prelude::BASE64_URL_SAFE_NO_PAD};
use rama_core::error::BoxErrorExt as _;
use rama_core::error::{BoxError, ErrorContext as _};
use rama_utils::{fmt::format_with_capacity, macros::generate_set_and_with};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
fn dotted_pair(first: &str, second: &str) -> String {
format_with_capacity(
first.len() + second.len() + 1,
format_args!("{first}.{second}"),
)
}
fn dotted_triple(first: &str, second: &str, third: &str) -> String {
format_with_capacity(
first.len() + second.len() + third.len() + 2,
format_args!("{first}.{second}.{third}"),
)
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Empty {}
pub const NO_PAYLOAD: Option<&'static Empty> = None;
pub const EMPTY_PAYLOAD: Option<&'static Empty> = Some(&Empty {});
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct JWSBuilder {
protected_headers: Headers,
unprotected_headers: Headers,
payload: String,
}
#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Headers(Option<Map<String, Value>>);
impl Headers {
generate_set_and_with! {
pub fn header(
mut self,
name: String,
value: impl Serialize,
) -> Result<Self, BoxError> {
let headers = self.0.get_or_insert_default();
let value = serde_json::to_value(value).context("convert to value")?;
headers.insert(name, value);
Ok(self)
}
}
generate_set_and_with! {
pub fn headers(mut self, headers: impl Serialize) -> Result<Self, BoxError> {
let headers =
serde_json::to_value(headers).context("convert headers to serde json value")?;
let mut headers = match headers {
Value::Object(map) => map,
_ => Err(BoxError::from_static_str(
"Can only set multiple headers if input is key value object",
))?,
};
match &mut self.0 {
Some(existing_headers) => existing_headers.append(&mut headers),
None => self.0 = Some(headers),
};
Ok(self)
}
}
fn as_encoded_string(&self) -> Result<String, BoxError> {
let encoded = match &self.0 {
Some(headers) => {
let headers = serde_json::to_vec(headers).context("convert to bytes")?;
BASE64_URL_SAFE_NO_PAD.encode(headers)
}
None => String::new(),
};
Ok(encoded)
}
fn is_none(&self) -> bool {
self.0.is_none()
}
fn is_some(&self) -> bool {
self.0.is_some()
}
pub fn decode<'de, 'a: 'de, T>(&'a self) -> Result<T, BoxError>
where
T: Deserialize<'de>,
{
match &self.0 {
Some(headers) => Ok(T::deserialize(headers).context("deserialize headers into T")?),
None => Err(BoxError::from_static_str(
"headers are None, deserialize not supported",
)),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ChainedJWSBuilder {
signatures: Vec<Signature>,
payload: String,
protected_headers: Headers,
unprotected_headers: Headers,
}
impl JWSBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
generate_set_and_with! {
pub fn payload(mut self, payload: impl AsRef<[u8]>) -> Self {
let payload = BASE64_URL_SAFE_NO_PAD.encode(payload);
self.payload = payload;
self
}
}
generate_set_and_with! {
pub fn protected_header(
mut self,
name: String,
value: impl Serialize,
) -> Result<Self, BoxError> {
self.protected_headers.try_set_header(name, value)?;
Ok(self)
}
}
generate_set_and_with! {
pub fn protected_headers(mut self, headers: impl Serialize) -> Result<Self, BoxError> {
self.protected_headers.try_set_headers(headers)?;
Ok(self)
}
}
pub fn protected_headers_mut(&mut self) -> &mut Headers {
&mut self.protected_headers
}
generate_set_and_with! {
pub fn unprotected_header(
mut self,
name: String,
value: impl Serialize,
) -> Result<Self, BoxError> {
self.unprotected_headers.try_set_header(name, value)?;
Ok(self)
}
}
generate_set_and_with! {
pub fn unprotected_headers(mut self, headers: impl Serialize) -> Result<Self, BoxError> {
self.unprotected_headers.try_set_headers(headers)?;
Ok(self)
}
}
pub fn unprotected_headers_mut(&mut self) -> &mut Headers {
&mut self.unprotected_headers
}
pub fn build_compact(mut self, signer: &impl Signer) -> Result<JWSCompact, BoxError> {
if self.unprotected_headers.is_some() {
return Err(BoxError::from_static_str(
"Compact jws does not support unprotected headers",
));
}
signer
.set_headers(&mut self.protected_headers, &mut self.unprotected_headers)
.context("signer set headers")?;
let protected = self.protected_headers.as_encoded_string()?;
let mut signing_input = dotted_pair(&protected, &self.payload);
let signature = signer
.sign(&signing_input)
.context("signer sign protected data")?;
let signature = BASE64_URL_SAFE_NO_PAD.encode(signature.as_ref());
signing_input.reserve(1 + signature.len());
signing_input.push('.');
signing_input.push_str(&signature);
Ok(JWSCompact(signing_input))
}
pub fn build_flattened(mut self, signer: &impl Signer) -> Result<JWSFlattened, BoxError> {
signer
.set_headers(&mut self.protected_headers, &mut self.unprotected_headers)
.context("signer set headers")?;
let protected = self.protected_headers.as_encoded_string()?;
let signing_input = dotted_pair(&protected, &self.payload);
let signature = signer
.sign(&signing_input)
.context("signer sign protected data")?;
let signature = BASE64_URL_SAFE_NO_PAD.encode(signature.as_ref());
Ok(JWSFlattened {
signature: Signature {
protected,
unprotected: self.unprotected_headers,
signature,
},
payload: self.payload,
})
}
pub fn build_jws(mut self, signer: &impl Signer) -> Result<JWS, BoxError> {
signer
.set_headers(&mut self.protected_headers, &mut self.unprotected_headers)
.context("signer set headers")?;
let protected = self.protected_headers.as_encoded_string()?;
let signing_input = dotted_pair(&protected, &self.payload);
let signature = signer
.sign(&signing_input)
.context("signer sign protected data")?;
let signature = BASE64_URL_SAFE_NO_PAD.encode(signature.as_ref());
let signature = Signature {
protected,
signature,
unprotected: self.unprotected_headers,
};
Ok(JWS {
signatures: vec![signature],
payload: self.payload,
})
}
pub fn add_signature(mut self, signer: &impl Signer) -> Result<ChainedJWSBuilder, BoxError> {
signer
.set_headers(&mut self.protected_headers, &mut self.unprotected_headers)
.context("signer set headers")?;
let protected = self.protected_headers.as_encoded_string()?;
let signing_input = dotted_pair(&protected, &self.payload);
let signature = signer
.sign(&signing_input)
.context("signer sign protected data")?;
let signature = BASE64_URL_SAFE_NO_PAD.encode(signature.as_ref());
let signature = Signature {
protected,
signature,
unprotected: self.unprotected_headers,
};
Ok(ChainedJWSBuilder {
signatures: vec![signature],
protected_headers: Default::default(),
unprotected_headers: Default::default(),
payload: self.payload,
})
}
}
impl ChainedJWSBuilder {
generate_set_and_with! {
pub fn protected_header(
mut self,
name: String,
value: impl Serialize,
) -> Result<Self, BoxError> {
self.protected_headers.try_set_header(name, value)?;
Ok(self)
}
}
generate_set_and_with! {
pub fn protected_headers(mut self, headers: impl Serialize) -> Result<Self, BoxError> {
self.protected_headers.try_set_headers(headers)?;
Ok(self)
}
}
pub fn protected_headers_mut(&mut self) -> &mut Headers {
&mut self.protected_headers
}
generate_set_and_with! {
pub fn unprotected_header(
mut self,
name: String,
value: impl Serialize,
) -> Result<Self, BoxError> {
self.unprotected_headers.try_set_header(name, value)?;
Ok(self)
}
}
generate_set_and_with! {
pub fn with_unprotected_headers(
mut self,
headers: impl Serialize,
) -> Result<Self, BoxError> {
self.unprotected_headers.try_set_headers(headers)?;
Ok(self)
}
}
pub fn unprotected_headers_mut(&mut self) -> &mut Headers {
&mut self.unprotected_headers
}
pub fn add_signature(mut self, signer: &impl Signer) -> Result<Self, BoxError> {
signer
.set_headers(&mut self.protected_headers, &mut self.unprotected_headers)
.context("signer set headers")?;
let protected = self.protected_headers.as_encoded_string()?;
let signing_input = dotted_pair(&protected, &self.payload);
let signature = signer
.sign(&signing_input)
.context("signer sign protected data")?;
let signature = BASE64_URL_SAFE_NO_PAD.encode(signature.as_ref());
let signature = Signature {
protected,
signature,
unprotected: self.unprotected_headers,
};
self.signatures.push(signature);
Ok(Self {
signatures: self.signatures,
protected_headers: Default::default(),
unprotected_headers: Default::default(),
payload: self.payload,
})
}
pub fn build(mut self, signer: &impl Signer) -> Result<JWS, BoxError> {
signer
.set_headers(&mut self.protected_headers, &mut self.unprotected_headers)
.context("signer set headers")?;
let protected = self.protected_headers.as_encoded_string()?;
let signing_input = dotted_pair(&protected, &self.payload);
let signature = signer
.sign(&signing_input)
.context("signer sign protected data")?;
let signature = BASE64_URL_SAFE_NO_PAD.encode(signature.as_ref());
let signature = Signature {
protected,
signature,
unprotected: self.unprotected_headers,
};
self.signatures.push(signature);
Ok(JWS {
payload: self.payload,
signatures: self.signatures,
})
}
}
pub trait Signer {
type Signature: AsRef<[u8]>;
type Error: Into<BoxError>;
fn set_headers(
&self,
protected_headers: &mut Headers,
unprotected_headers: &mut Headers,
) -> Result<(), Self::Error>;
fn sign(&self, data: &str) -> Result<Self::Signature, Self::Error>;
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct JWSCompact(String);
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct JWSFlattened {
#[serde(default = "Default::default")]
payload: String,
#[serde(flatten)]
signature: Signature,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct JWS {
payload: String,
signatures: Vec<Signature>,
}
impl JWSCompact {
#[must_use]
pub fn builder() -> JWSBuilder {
JWSBuilder::new()
}
}
impl JWS {
#[must_use]
pub fn builder() -> JWSBuilder {
JWSBuilder::new()
}
pub fn decode<V: Verifier>(self, verifier: &V) -> Result<(DecodedJWS, V::Output), BoxError> {
let mut signatures = Vec::with_capacity(self.signatures.len());
for signature in self.signatures.into_iter() {
let protected = BASE64_URL_SAFE_NO_PAD
.decode(&signature.protected)
.context("decode protected header")?;
let protected = serde_json::from_slice::<Headers>(&protected)
.context("deserialize protected headers")?;
let decoded_signature = BASE64_URL_SAFE_NO_PAD
.decode(signature.signature)
.context("decode signature")?;
let decoded_signature = DecodedSignature {
protected,
signature: decoded_signature,
unprotected: signature.unprotected,
};
let to_verify = ToVerifySignature {
decoded_signature,
signed_data: dotted_pair(&signature.protected, &self.payload),
};
signatures.push(to_verify);
}
let payload = BASE64_URL_SAFE_NO_PAD
.decode(&self.payload)
.context("decode payload")?;
let verifier_output = verifier
.verify(&payload, &signatures)
.context("signer verify signatures")?;
let signatures = signatures
.into_iter()
.map(|sig| sig.decoded_signature)
.collect();
Ok((
DecodedJWS {
signatures,
payload,
},
verifier_output,
))
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
struct Signature {
#[serde(skip_serializing_if = "String::is_empty")]
#[serde(default = "Default::default")]
protected: String,
#[serde(skip_serializing_if = "Headers::is_none")]
#[serde(rename = "headers", default = "Default::default")]
unprotected: Headers,
#[serde(skip_serializing_if = "String::is_empty")]
#[serde(default = "Default::default")]
signature: String,
}
impl JWSFlattened {
#[must_use]
pub fn builder() -> JWSBuilder {
JWSBuilder::new()
}
pub fn as_compact(&self) -> Result<String, BoxError> {
if self.signature.unprotected.is_some() {
return Err(BoxError::from_static_str(
"JWSCompact does not support unprotected headers",
));
};
Ok(dotted_triple(
&self.signature.protected,
&self.payload,
&self.signature.signature,
))
}
pub fn decode<V: Verifier>(
self,
verifier: &V,
) -> Result<(DecodedJWSFlattened, V::Output), BoxError> {
let protected = BASE64_URL_SAFE_NO_PAD
.decode(&self.signature.protected)
.context("decode protected header")?;
let protected = serde_json::from_slice::<Headers>(&protected)
.context("deserialize protected headers")?;
let decoded_signature = BASE64_URL_SAFE_NO_PAD
.decode(self.signature.signature)
.context("decode signature")?;
let decoded_signature = DecodedSignature {
protected,
signature: decoded_signature,
unprotected: self.signature.unprotected,
};
let to_verify = ToVerifySignature {
decoded_signature,
signed_data: dotted_pair(&self.signature.protected, &self.payload),
};
let payload = BASE64_URL_SAFE_NO_PAD
.decode(&self.payload)
.context("decode payload")?;
let verify_output = verifier
.verify(&payload, std::slice::from_ref(&to_verify))
.context("signer verify signature")?;
let signature = to_verify.decoded_signature;
Ok((DecodedJWSFlattened { signature, payload }, verify_output))
}
}
#[derive(Debug)]
pub struct DecodedJWSFlattened {
payload: Vec<u8>,
signature: DecodedSignature,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DecodedJWS {
payload: Vec<u8>,
signatures: Vec<DecodedSignature>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DecodedSignature {
protected: Headers,
unprotected: Headers,
signature: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ToVerifySignature {
signed_data: String,
decoded_signature: DecodedSignature,
}
impl ToVerifySignature {
#[must_use]
pub fn signed_data(&self) -> &str {
&self.signed_data
}
#[must_use]
pub fn decoded_signature(&self) -> &DecodedSignature {
&self.decoded_signature
}
}
impl DecodedSignature {
#[must_use]
pub fn protected_headers(&self) -> &Headers {
&self.protected
}
pub fn decode_protected_headers<'de, 'a: 'de, T: Deserialize<'de>>(
&'a self,
) -> Result<T, BoxError> {
self.protected.decode()
}
#[must_use]
pub fn unprotected_headers(&self) -> &Headers {
&self.unprotected
}
pub fn decode_unprotected_headers<'de, 'a: 'de, T: Deserialize<'de>>(
&'a self,
) -> Result<T, BoxError> {
self.unprotected.decode()
}
#[must_use]
pub fn signature(&self) -> &[u8] {
self.signature.as_slice()
}
}
impl DecodedJWS {
#[must_use]
pub fn signatures(&self) -> &[DecodedSignature] {
self.signatures.as_slice()
}
#[must_use]
pub fn payload(&self) -> &[u8] {
&self.payload
}
}
impl DecodedJWSFlattened {
#[must_use]
pub fn protected_headers(&self) -> &Headers {
self.signature.protected_headers()
}
pub fn decode_protected_headers<'de, 'a: 'de, T: Deserialize<'de>>(
&'a self,
) -> Result<T, BoxError> {
self.signature.decode_protected_headers()
}
#[must_use]
pub fn unprotected_headers(&self) -> &Headers {
self.signature.unprotected_headers()
}
pub fn decode_unprotected_headers<'de, 'a: 'de, T: Deserialize<'de>>(
&'a self,
) -> Result<T, BoxError> {
self.signature.decode_unprotected_headers()
}
#[must_use]
pub fn signature(&self) -> &[u8] {
self.signature.signature()
}
#[must_use]
pub fn payload(&self) -> &[u8] {
&self.payload
}
}
pub trait Verifier {
type Error: Into<BoxError>;
type Output;
fn verify(
&self,
payload: &[u8],
signatures: &[ToVerifySignature],
) -> Result<Self::Output, Self::Error>;
}
#[cfg(test)]
mod tests {
use tokio_test::assert_err;
use super::*;
#[test]
fn dotted_helpers_reserve_exact_capacity() {
let pair = dotted_pair("protect", "pay");
assert_eq!(pair, "protect.pay");
assert_eq!(pair.capacity(), pair.len());
let triple = dotted_triple("protect", "pay", "xyz");
assert_eq!(triple, "protect.pay.xyz");
assert_eq!(triple.capacity(), triple.len());
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
struct AcmeProtected<'a> {
alg: Option<&'a str>,
nonce: &'a str,
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
struct Random<'a> {
data: &'a str,
}
struct DummyKey;
impl Signer for DummyKey {
type Signature = Vec<u8>;
type Error = BoxError;
fn sign(&self, data: &str) -> Result<Self::Signature, BoxError> {
let mut out = data.as_bytes().to_vec();
out.push(33);
Ok(out)
}
fn set_headers(
&self,
protected_headers: &mut Headers,
_unprotected_headers: &mut Headers,
) -> Result<(), BoxError> {
protected_headers.try_set_header("alg".to_owned(), "test_algo".to_owned())?;
Ok(())
}
}
impl Verifier for DummyKey {
type Error = BoxError;
type Output = ();
fn verify(
&self,
_payload: &[u8],
to_verify_sigs: &[ToVerifySignature],
) -> Result<(), BoxError> {
let to_verify = &to_verify_sigs[0];
let original = to_verify.signed_data.as_bytes();
let signature = to_verify.decoded_signature.signature();
if original.len() + 1 != signature.len() {
Err(BoxError::from_static_str(
"signature should add single u8 to original slice",
))
} else if original[..] != signature[..original.len()] {
Err(BoxError::from_static_str("original data should be equal"))
} else if signature[signature.len() - 1] != 33 {
Err(BoxError::from_static_str(
"last element in signature should be 33",
))
} else {
Ok(())
}
}
}
#[test]
fn can_serialize_and_deserialize() {
let nonce = "random".to_owned();
let protected = AcmeProtected {
nonce: &nonce,
alg: None,
};
let something = "something_random".to_owned();
let header = Random { data: &something };
let payload = "something".to_owned();
let signer_and_verifier = DummyKey;
let jws = JWSBuilder::new()
.with_payload(payload.clone())
.try_with_protected_headers(protected.clone())
.unwrap()
.try_with_unprotected_headers(header.clone())
.unwrap()
.build_flattened(&signer_and_verifier)
.unwrap();
let serialized = serde_json::to_string(&jws).unwrap();
let jws_received = serde_json::from_str::<JWSFlattened>(&serialized).unwrap();
let mut expected_protected = protected.clone();
expected_protected.alg = Some("test_algo");
assert_eq!(jws.signature.protected, jws_received.signature.protected);
assert_eq!(
jws.signature.unprotected,
jws_received.signature.unprotected
);
assert_eq!(jws.payload, jws_received.payload);
let (decoded_jws, _) = jws_received.decode(&signer_and_verifier).unwrap();
let received_payload = String::from_utf8(decoded_jws.payload().to_vec()).unwrap();
let received_protected = decoded_jws
.decode_protected_headers::<AcmeProtected>()
.unwrap();
let received_header = decoded_jws.decode_unprotected_headers::<Random>().unwrap();
assert_eq!(payload, received_payload);
assert_eq!(expected_protected, received_protected);
assert_eq!(header, received_header);
}
#[test]
fn empty_vs_none() {
let signer = DummyKey;
let protected = AcmeProtected {
nonce: "somthing",
alg: None,
};
let jws = JWSFlattened::builder()
.try_with_protected_headers(protected.clone())
.unwrap()
.build_flattened(&signer)
.unwrap();
assert_eq!(jws.payload, "".to_owned());
let jws = JWSFlattened::builder()
.with_payload(serde_json::to_vec(&EMPTY_PAYLOAD).unwrap())
.try_with_protected_headers(protected.clone())
.unwrap()
.build_flattened(&signer)
.unwrap();
assert_eq!(jws.payload, "e30".to_owned());
}
#[test]
fn tampering_should_be_detected() {
let nonce = "random".to_owned();
let protected = AcmeProtected {
nonce: &nonce,
alg: None,
};
let payload = "something".to_owned();
let signer_and_verifier = DummyKey;
let jws = JWSFlattened::builder()
.with_payload(payload)
.try_with_protected_headers(protected.clone())
.unwrap()
.build_flattened(&signer_and_verifier)
.unwrap();
let serialized = serde_json::to_string(&jws).unwrap();
let server = move |serialized: String| {
let received =
serde_json::from_str::<JWSFlattened>(&serialized).context("decode jws")?;
let _decoded = received.decode(&signer_and_verifier)?;
Ok::<_, BoxError>(())
};
for i in 0..serialized.len() - 1 {
let mut serialized: String = serialized.clone();
serialized.insert(i, 't');
assert_err!(server(serialized), "failed at {i}");
}
}
#[test]
fn can_create_jws() {
let nonce = "random".to_owned();
let something = "something_random".to_owned();
let header = Random { data: &something };
let payload = "something".to_owned();
let signer_and_verifier = DummyKey;
let jws = JWSBuilder::new()
.with_payload(payload.clone())
.try_with_protected_header("nonce".to_owned(), &nonce)
.unwrap()
.try_with_unprotected_headers(header.clone())
.unwrap()
.build_jws(&signer_and_verifier)
.unwrap();
let serialized = serde_json::to_string(&jws).unwrap();
let received = serde_json::from_str::<JWS>(&serialized).unwrap();
let (decoded, _) = received.decode(&signer_and_verifier).unwrap();
let decoded_payload = String::from_utf8(decoded.payload().to_vec()).unwrap();
assert_eq!(payload, decoded_payload);
}
#[test]
fn can_create_multi_signature_jws() {
let nonce = "random".to_owned();
let protected = AcmeProtected {
nonce: &nonce,
alg: None,
};
let something = "something_random".to_owned();
let header = Random { data: &something };
let payload = "something".to_owned();
let signer_and_verifier = DummyKey;
let builder = JWSBuilder::new()
.with_payload(payload.clone())
.try_with_protected_headers(protected.clone())
.unwrap()
.try_with_unprotected_headers(header.clone())
.unwrap();
struct SecondSigner;
impl Signer for SecondSigner {
type Signature = Vec<u8>;
type Error = BoxError;
fn sign(&self, data: &str) -> Result<Self::Signature, BoxError> {
Ok(data.as_bytes().to_owned())
}
fn set_headers(
&self,
protected_headers: &mut Headers,
_unprotected_headers: &mut Headers,
) -> Result<(), BoxError> {
protected_headers.try_set_header("data".to_owned(), "very protected")?;
Ok(())
}
}
let second_signer = SecondSigner;
let jws = builder
.add_signature(&signer_and_verifier)
.unwrap()
.try_with_unprotected_header("second".to_owned(), "something second")
.unwrap()
.try_with_protected_header("app specific".to_owned(), "will not be used by verifier")
.unwrap()
.build(&second_signer)
.unwrap();
let serialized = serde_json::to_string(&jws).unwrap();
#[derive(Debug, Deserialize)]
struct SecondProtectedHeader {
data: String,
}
struct MultiVerifier {
dummy_verifier: DummyKey,
}
impl Verifier for MultiVerifier {
type Error = BoxError;
type Output = ();
fn verify(
&self,
payload: &[u8],
to_verify_sigs: &[ToVerifySignature],
) -> Result<(), BoxError> {
self.dummy_verifier.verify(payload, to_verify_sigs)?;
let second = &to_verify_sigs[1];
let protected_header = second
.decoded_signature
.decode_protected_headers::<SecondProtectedHeader>()?;
if protected_header.data.as_str() == "very protected" {
Ok(())
} else {
Err(BoxError::from_static_str(
"received unexpected second protected header",
))
}
}
}
let multi_verifier = MultiVerifier {
dummy_verifier: signer_and_verifier,
};
let received = serde_json::from_str::<JWS>(&serialized).unwrap();
let (decoded, _) = received.decode(&multi_verifier).unwrap();
let decoded_payload = String::from_utf8(decoded.payload().to_vec()).unwrap();
assert_eq!(payload, decoded_payload);
}
}