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