hessra_token_authz/
verify.rs

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