use super::TransportError;
use crate::approval::SignedApproval;
use crate::crypto::Signature;
use crate::sdk::authority::OwnedReceivedAuthorization;
use crate::sdk::AuthorizedCall;
use crate::warrant::Warrant;
use serde_json::Value;
pub type TenuoMeta = OwnedReceivedAuthorization;
pub fn encode_meta(
chain: &[Warrant],
signature: &Signature,
approvals: &[SignedApproval],
) -> Result<Value, TransportError> {
crate::meta_envelope::encode_meta(chain, &signature.to_bytes(), approvals)
.map(|meta| meta.to_json())
.map_err(meta_error)
}
fn meta_error(err: crate::meta_envelope::MetaError) -> TransportError {
match err {
crate::meta_envelope::MetaError::MissingField(name) => TransportError::MissingField(name),
crate::meta_envelope::MetaError::TooManyApprovals => TransportError::TooManyApprovals,
crate::meta_envelope::MetaError::InvalidSignature => TransportError::InvalidSignature,
crate::meta_envelope::MetaError::PayloadTooLarge => TransportError::PayloadTooLarge,
_ => TransportError::InvalidEncoding,
}
}
pub fn sign_meta(
authority: &crate::sdk::PresentedAuthority,
call: &crate::sdk::Call<'_>,
approvals: &[SignedApproval],
timestamp: i64,
window_secs: i64,
) -> Result<Value, TransportError> {
let signature = authority
.prove(call, timestamp, window_secs)
.map_err(|_| TransportError::ProofFailed)?;
encode_meta(authority.chain(), &signature, approvals)
}
pub fn encode_meta_from_authorized(call: &AuthorizedCall<'_>) -> Result<Value, TransportError> {
encode_meta(call.chain(), call.pop_signature(), call.approvals())
}
pub fn decode_meta(meta: &Value) -> Result<TenuoMeta, TransportError> {
let decoded = crate::meta_envelope::decode_meta(meta).map_err(meta_error)?;
OwnedReceivedAuthorization::new(decoded.warrants, decoded.signature, decoded.approvals)
.map_err(Into::into)
}
pub fn strip_tenuo(meta: &mut Value) {
if let Some(object) = meta.as_object_mut() {
object.remove("tenuo");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decoder_matches_core_conformance() {
let suite: Value = serde_json::from_str(include_str!(
"../../../../tests/vectors/tenuo-meta-conformance.json"
))
.unwrap();
let mut valid = suite["delegated"].clone();
for approvals in [
Value::Null,
serde_json::json!([]),
valid["approvals"].clone(),
] {
valid["approvals"] = approvals;
assert!(decode_meta(&valid).is_ok());
assert!(crate::meta_envelope::decode_meta(&valid).is_ok());
}
for case in suite["invalid_envelopes"].as_array().unwrap() {
let mut invalid = suite["delegated"].clone();
for (key, value) in case.as_object().unwrap() {
if key != "error" {
invalid[key] = value.clone();
}
}
assert!(decode_meta(&invalid).is_err());
assert!(crate::meta_envelope::decode_meta(&invalid).is_err());
}
valid.as_object_mut().unwrap().remove("signature");
assert_eq!(
decode_meta(&valid).err(),
Some(TransportError::MissingField("signature"))
);
}
#[test]
fn signed_meta_verifies_at_a_guard() {
use crate::sdk::{Call, Guard, LocalSigner, PresentedAuthority, RevocationMode};
use crate::{ConstraintSet, SigningKey};
use std::sync::Arc;
let issuer = SigningKey::generate();
let holder = SigningKey::generate();
let warrant = Warrant::builder()
.capability("read", ConstraintSet::new())
.holder(holder.public_key())
.ttl(std::time::Duration::from_secs(60))
.build(&issuer)
.unwrap();
let authority =
PresentedAuthority::new(vec![warrant], Arc::new(LocalSigner::new(holder))).unwrap();
let arguments = serde_json::json!({"path": "/a"});
let call = Call::try_from_json("read", &arguments).unwrap();
let meta = sign_meta(
&authority,
&call,
&[],
chrono::Utc::now().timestamp(),
crate::planes::DEFAULT_POP_WINDOW_SECS,
)
.unwrap();
let mut authorizer = crate::Authorizer::new();
authorizer.add_trusted_root(issuer.public_key());
let guard = Guard::builder()
.authorizer(authorizer)
.revocation(RevocationMode::TtlOnly {
max_lifetime: std::time::Duration::from_secs(3600),
})
.build()
.unwrap();
let received = decode_meta(&meta).unwrap();
assert!(guard
.check_received(&received.as_received().unwrap(), &call)
.is_ok());
let other = serde_json::json!({"path": "/b"});
let other = Call::try_from_json("read", &other).unwrap();
assert!(guard
.check_received(&received.as_received().unwrap(), &other)
.is_err());
}
#[test]
fn integral_float_proofs_agree_across_sdk_and_core() {
use crate::sdk::{Call, Guard, LocalSigner, PresentedAuthority, RevocationMode};
use crate::{ConstraintSet, SigningKey};
use std::sync::Arc;
for (value, text) in [
(serde_json::json!({"n": 1.0}), r#"{"n":1.0}"#),
(serde_json::json!({"n": 1}), r#"{"n":1.0}"#),
(serde_json::json!({"n": 1.0}), r#"{"n":1}"#),
(serde_json::json!({"n": 1.5}), r#"{"n":1.5}"#),
] {
let issuer = SigningKey::generate();
let holder = SigningKey::generate();
let warrant = Warrant::builder()
.capability("calc", ConstraintSet::new())
.holder(holder.public_key())
.ttl(std::time::Duration::from_secs(60))
.build(&issuer)
.unwrap();
let ts = chrono::Utc::now().timestamp();
let call = Call::try_from_json("calc", &value).unwrap();
let authority = PresentedAuthority::new(
vec![warrant.clone()],
Arc::new(LocalSigner::new(holder.clone())),
)
.unwrap();
let meta = sign_meta(
&authority,
&call,
&[],
ts,
crate::planes::DEFAULT_POP_WINDOW_SECS,
)
.unwrap();
assert!(
crate::meta_envelope::verify_meta_pop(
meta["warrant"].as_str().unwrap(),
meta["signature"].as_str().unwrap(),
"calc",
text,
ts,
)
.unwrap(),
"core rejected a Rust SDK proof for {value} / {text}"
);
let core = crate::meta_envelope::sign_meta(&[warrant], &holder, "calc", text, ts, &[])
.unwrap()
.to_json();
let mut authorizer = crate::Authorizer::new();
authorizer.add_trusted_root(issuer.public_key());
let guard = Guard::builder()
.authorizer(authorizer)
.revocation(RevocationMode::TtlOnly {
max_lifetime: std::time::Duration::from_secs(3600),
})
.build()
.unwrap();
let received = decode_meta(&core).unwrap();
assert!(
guard
.check_received(&received.as_received().unwrap(), &call)
.is_ok(),
"Rust Guard rejected a core proof for {text} / {value}"
);
}
}
}