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    let parts = key.split('/').collect::<Vec<&str>>();
126    if parts.len() != 2 {
127        return Err(TokenError::invalid_key_format(
128            "Key must be in format 'algorithm/hexkey'",
129        ));
130    }
131
132    let alg = match parts[0] {
133        "ed25519" => Algorithm::Ed25519,
134        "secp256r1" => Algorithm::Secp256r1,
135        _ => {
136            return Err(TokenError::invalid_key_format(
137                "Unsupported algorithm, must be ed25519 or secp256r1",
138            ))
139        }
140    };
141
142    // decode the key from hex
143    let key_bytes = hex::decode(parts[1])?;
144
145    // construct the public key
146    let key = PublicKey::from_bytes(&key_bytes, alg)
147        .map_err(|e| TokenError::invalid_key_format(e.to_string()))?;
148
149    Ok(key)
150}
151
152#[derive(Debug, Deserialize, Clone)]
153pub struct ServiceNode {
154    pub component: String,
155    pub public_key: String,
156}
157
158fn verify_raw_service_chain_biscuit(
159    biscuit: Biscuit,
160    subject: String,
161    resource: String,
162    operation: String,
163    service_nodes: Vec<ServiceNode>,
164    component: Option<String>,
165) -> Result<(), TokenError> {
166    let mut authz = build_base_authorizer(subject, resource.clone(), operation)?;
167
168    let mut component_found = false;
169    if component.is_none() {
170        component_found = true;
171    }
172    for service_node in service_nodes {
173        if let Some(ref component) = component {
174            if component == &service_node.component {
175                component_found = true;
176                break;
177            }
178        }
179        let service = resource.clone();
180        let node_name = service_node.component;
181        let node_key = biscuit_key_from_string(service_node.public_key)?;
182        authz = authz.check(check!(
183            r#"
184                check if node({service}, {node_name}) trusting authority, {node_key};
185            "#
186        ))?;
187    }
188
189    if let Some(ref component) = component {
190        if !component_found {
191            return Err(TokenError::authorization_error(format!(
192                "Token does not grant required access rights. missing {}",
193                component.clone()
194            )));
195        }
196    }
197
198    if authz.build(&biscuit)?.authorize().is_ok() {
199        Ok(())
200    } else {
201        Err(TokenError::authorization_error(
202            "Token does not grant required access rights",
203        ))
204    }
205}
206
207pub fn verify_service_chain_biscuit_local(
208    token: Vec<u8>,
209    public_key: PublicKey,
210    subject: String,
211    resource: String,
212    operation: String,
213    service_nodes: Vec<ServiceNode>,
214    component: Option<String>,
215) -> Result<(), TokenError> {
216    let biscuit = Biscuit::from(&token, public_key).map_err(TokenError::biscuit_error)?;
217    verify_raw_service_chain_biscuit(
218        biscuit,
219        subject,
220        resource,
221        operation,
222        service_nodes,
223        component,
224    )
225}
226
227pub fn verify_service_chain_token_local(
228    token: &str,
229    public_key: PublicKey,
230    subject: &str,
231    resource: &str,
232    operation: &str,
233    service_nodes: Vec<ServiceNode>,
234    component: Option<String>,
235) -> Result<(), TokenError> {
236    let biscuit = Biscuit::from_base64(token, public_key).map_err(TokenError::biscuit_error)?;
237    verify_raw_service_chain_biscuit(
238        biscuit,
239        subject.to_string(),
240        resource.to_string(),
241        operation.to_string(),
242        service_nodes,
243        component,
244    )
245}