hessra_token/
verify.rs

1extern crate biscuit_auth as biscuit;
2
3use biscuit::macros::{authorizer, check};
4use biscuit::{Algorithm, Biscuit, PublicKey};
5use chrono::Utc;
6use serde::Deserialize;
7
8use crate::error::TokenError;
9
10fn build_base_authorizer(
11    subject: String,
12    resource: String,
13) -> Result<biscuit::AuthorizerBuilder, TokenError> {
14    let now = Utc::now().timestamp();
15
16    let authz = authorizer!(
17        r#"
18            time({now});
19            resource({resource});
20            subject({subject});
21            operation("read");
22            operation("write");
23            allow if subject($sub), resource($res), operation($op), right($sub, $res, $op);
24        "#
25    );
26    Ok(authz)
27}
28
29fn verify_raw_biscuit(
30    biscuit: Biscuit,
31    subject: String,
32    resource: String,
33) -> Result<(), TokenError> {
34    let authz = build_base_authorizer(subject, resource)?;
35    if authz.build(&biscuit)?.authorize().is_ok() {
36        Ok(())
37    } else {
38        Err(TokenError::authorization_error(
39            "Token does not grant required access rights",
40        ))
41    }
42}
43
44/// Verifies a Biscuit authorization token locally without contacting the authorization server.
45///
46/// This function performs local verification of a Biscuit token using the provided public key.
47/// It validates that the token grants access to the specified resource for the given subject.
48///
49/// # Arguments
50///
51/// * `token` - The binary Biscuit token bytes (typically decoded from Base64)
52/// * `public_key` - The public key used to verify the token signature
53/// * `subject` - The subject (user) identifier to verify authorization for
54/// * `resource` - The resource identifier to verify authorization against
55///
56/// # Returns
57///
58/// * `Ok(())` - If the token is valid and grants access to the resource
59/// * `Err(TokenError)` - If verification fails for any reason
60///
61/// # Errors
62///
63/// Returns an error if:
64/// - The token is malformed or cannot be parsed
65/// - The token signature is invalid
66/// - The token does not grant the required access rights
67/// - The token has expired or other authorization checks fail
68pub fn verify_biscuit_local(
69    token: Vec<u8>,
70    public_key: PublicKey,
71    subject: String,
72    resource: String,
73) -> Result<(), TokenError> {
74    let biscuit = Biscuit::from(&token, public_key)?;
75    verify_raw_biscuit(biscuit, subject, resource)
76}
77
78/// Verifies a Biscuit authorization token locally without contacting the authorization server.
79///
80/// This function performs local verification of a Biscuit token using the provided public key.
81/// It validates that the token grants access to the specified resource for the given subject.
82///
83/// # Arguments
84///
85/// * `token` - The base64-encoded Biscuit token string
86/// * `public_key` - The public key used to verify the token signature
87/// * `subject` - The subject (user) identifier to verify authorization for
88/// * `resource` - The resource identifier to verify authorization against
89///
90/// # Returns
91///
92/// * `Ok(())` - If the token is valid and grants access to the resource
93/// * `Err(TokenError)` - If verification fails for any reason
94///
95/// # Errors
96///
97/// Returns an error if:
98/// - The token is malformed or cannot be parsed
99/// - The token signature is invalid
100/// - The token does not grant the required access rights
101/// - The token has expired or other authorization checks fail
102pub fn verify_token_local(
103    token: &str,
104    public_key: PublicKey,
105    subject: &str,
106    resource: &str,
107) -> Result<(), TokenError> {
108    let biscuit = Biscuit::from_base64(token, public_key)?;
109    verify_raw_biscuit(biscuit, subject.to_string(), resource.to_string())
110}
111
112/// Takes a public key encoded as a string in the format "ed25519/..." or "secp256r1/..."
113/// and returns a PublicKey.
114pub fn biscuit_key_from_string(key: String) -> Result<PublicKey, TokenError> {
115    println!("Key: {:?}", key);
116    // first split the string on the /
117    let parts = key.split('/').collect::<Vec<&str>>();
118    if parts.len() != 2 {
119        return Err(TokenError::invalid_key_format(
120            "Key must be in format 'algorithm/hexkey'",
121        ));
122    }
123
124    let alg = match parts[0] {
125        "ed25519" => Algorithm::Ed25519,
126        "secp256r1" => Algorithm::Secp256r1,
127        _ => {
128            return Err(TokenError::invalid_key_format(
129                "Unsupported algorithm, must be ed25519 or secp256r1",
130            ))
131        }
132    };
133
134    // decode the key from hex
135    let key_bytes = hex::decode(parts[1])?;
136
137    // construct the public key
138    let key = PublicKey::from_bytes(&key_bytes, alg)
139        .map_err(|e| TokenError::invalid_key_format(e.to_string()))?;
140
141    Ok(key)
142}
143
144#[derive(Debug, Deserialize, Clone)]
145pub struct ServiceNode {
146    pub component: String,
147    pub public_key: String,
148}
149
150fn verify_raw_service_chain_biscuit(
151    biscuit: Biscuit,
152    subject: String,
153    resource: String,
154    service_nodes: Vec<ServiceNode>,
155    component: Option<String>,
156) -> Result<(), TokenError> {
157    let mut authz = build_base_authorizer(subject, resource.clone())?;
158
159    let mut component_found = false;
160    if component.is_none() {
161        component_found = true;
162    }
163    for service_node in service_nodes {
164        if let Some(ref component) = component {
165            if component == &service_node.component {
166                component_found = true;
167                break;
168            }
169        }
170        let service = resource.clone();
171        let node_name = service_node.component;
172        let node_key = biscuit_key_from_string(service_node.public_key)?;
173        authz = authz.check(check!(
174            r#"
175                check if node({service}, {node_name}) trusting authority, {node_key};
176            "#
177        ))?;
178    }
179
180    if let Some(ref component) = component {
181        if !component_found {
182            return Err(TokenError::authorization_error(format!(
183                "Token does not grant required access rights. missing {}",
184                component.clone()
185            )));
186        }
187    }
188
189    if authz.build(&biscuit)?.authorize().is_ok() {
190        Ok(())
191    } else {
192        Err(TokenError::authorization_error(
193            "Token does not grant required access rights",
194        ))
195    }
196}
197
198pub fn verify_service_chain_biscuit_local(
199    token: Vec<u8>,
200    public_key: PublicKey,
201    subject: String,
202    resource: String,
203    service_nodes: Vec<ServiceNode>,
204    component: Option<String>,
205) -> Result<(), TokenError> {
206    let biscuit = Biscuit::from(&token, public_key).map_err(TokenError::biscuit_error)?;
207    verify_raw_service_chain_biscuit(biscuit, subject, resource, service_nodes, component)
208}
209
210pub fn verify_service_chain_token_local(
211    token: &str,
212    public_key: PublicKey,
213    subject: &str,
214    resource: &str,
215    service_nodes: Vec<ServiceNode>,
216    component: Option<String>,
217) -> Result<(), TokenError> {
218    let biscuit = Biscuit::from_base64(token, public_key).map_err(TokenError::biscuit_error)?;
219    verify_raw_service_chain_biscuit(
220        biscuit,
221        subject.to_string(),
222        resource.to_string(),
223        service_nodes,
224        component,
225    )
226}