Skip to content

Commit 686c79f

Browse files
committed
feat: add RcanChain and PEM encoding
1 parent ee64cf6 commit 686c79f

File tree

5 files changed

+398
-17
lines changed

5 files changed

+398
-17
lines changed

Cargo.lock

Lines changed: 173 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,16 @@ hex = "0.4.3"
1919
postcard = { version = "1.1.1", features = ["use-std"] }
2020
rand = "0.8.5"
2121
serde = { version = "1.0.217", features = ["derive"] }
22+
ssh-encoding = { version = "0.2.0", features = ["pem", "std"], optional = true }
23+
thiserror = "2"
24+
tokio = { version = "1", optional = true, features = ["fs"] }
2225

2326
[dev-dependencies]
2427
bitfields = "0.12.4"
2528
rand_chacha = "0.3.1"
2629
testresult = "0.4.1"
30+
31+
[features]
32+
default = []
33+
fs = ["pem", "dep:tokio"]
34+
pem = ["dep:ssh-encoding"]

src/chain.rs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
use std::time::Duration;
2+
3+
use anyhow::{bail, ensure, Context, Result};
4+
use ed25519_dalek::{SigningKey, VerifyingKey};
5+
use serde::{de::DeserializeOwned, Deserialize, Serialize};
6+
7+
use crate::{Authorizer, Capability, Expires, Rcan, VERSION};
8+
9+
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
10+
pub struct RcanChain<C>(Vec<Rcan<C>>);
11+
12+
impl<C> Default for RcanChain<C> {
13+
fn default() -> Self {
14+
Self(Vec::new())
15+
}
16+
}
17+
18+
impl<C: Capability> RcanChain<C> {
19+
pub fn is_empty(&self) -> bool {
20+
self.0.is_empty()
21+
}
22+
23+
pub fn len(&self) -> usize {
24+
self.0.len()
25+
}
26+
27+
pub fn from_rcan(rcan: Rcan<C>) -> Self {
28+
Self(vec![rcan])
29+
}
30+
31+
pub fn iter(&self) -> impl Iterator<Item = &'_ Rcan<C>> + '_ {
32+
self.into_iter()
33+
}
34+
35+
pub fn first_issuer(&self) -> Result<&VerifyingKey> {
36+
self.0
37+
.first()
38+
.map(|rcan| rcan.issuer())
39+
.context("rcan chain is empty")
40+
}
41+
42+
pub fn final_audience(&self) -> Result<&VerifyingKey> {
43+
self.0
44+
.last()
45+
.map(|rcan| rcan.audience())
46+
.context("rcan chain is empty")
47+
}
48+
49+
pub fn final_capability(&self) -> Result<&C> {
50+
self.0
51+
.last()
52+
.map(|rcan| rcan.capability())
53+
.context("rcan chain is empty")
54+
}
55+
56+
pub fn verify_chain(&self) -> Result<()> {
57+
self.check_invocation_from(
58+
*self.first_issuer()?,
59+
*self.final_audience()?,
60+
self.final_capability()?,
61+
)
62+
}
63+
64+
pub fn check_invocation_from(
65+
&self,
66+
root_issuer: VerifyingKey,
67+
invoker: VerifyingKey,
68+
capability: &C,
69+
) -> Result<()> {
70+
if self.first_issuer()? != &root_issuer {
71+
bail!("invocation failed: root issuer does not match");
72+
}
73+
Authorizer::new(root_issuer)
74+
.check_invocation_from(invoker, capability, self)
75+
.context("not authorized")?;
76+
Ok(())
77+
}
78+
79+
pub fn with_delegation(
80+
&self,
81+
issuer: &SigningKey,
82+
audience: VerifyingKey,
83+
capability: C,
84+
max_age: Duration,
85+
) -> Result<Self>
86+
where
87+
C: Clone,
88+
{
89+
let root_issuer = self.first_issuer()?;
90+
self.check_invocation_from(*root_issuer, issuer.verifying_key(), &capability)?;
91+
92+
let can = Rcan::delegating_builder(&issuer, audience, *root_issuer, capability)
93+
.sign(Expires::valid_for(max_age));
94+
let mut next_chain = self.0.clone();
95+
next_chain.push(can);
96+
Ok(Self(next_chain))
97+
}
98+
99+
pub fn encode(&self) -> Vec<u8> {
100+
postcard::to_extend(self, vec![VERSION]).expect("vec")
101+
}
102+
103+
pub fn encoded_len(&self) -> usize {
104+
postcard::experimental::serialized_size(self).unwrap() + 1
105+
}
106+
107+
pub fn decode(bytes: &[u8]) -> Result<Self>
108+
where
109+
C: DeserializeOwned,
110+
{
111+
let Some(version) = bytes.first() else {
112+
bail!("cannot decode, token is empty");
113+
};
114+
ensure!(*version == VERSION, "invalid version: {}", version);
115+
let out: Self = postcard::from_bytes(&bytes[1..]).context("decoding")?;
116+
Ok(out)
117+
}
118+
}
119+
120+
impl<'a, C> IntoIterator for &'a RcanChain<C> {
121+
type Item = &'a Rcan<C>;
122+
123+
type IntoIter = std::slice::Iter<'a, Rcan<C>>;
124+
125+
fn into_iter(self) -> Self::IntoIter {
126+
self.0[..].into_iter()
127+
}
128+
}

0 commit comments

Comments
 (0)