biscuit_auth/token/
third_party.rs

1/*
2 * Copyright (c) 2019 Geoffroy Couprie <contact@geoffroycouprie.com> and Contributors to the Eclipse Foundation.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5use std::cmp::max;
6
7use prost::Message;
8
9use crate::{
10    builder::BlockBuilder,
11    crypto::generate_external_signature_payload_v1,
12    datalog::SymbolTable,
13    error,
14    format::{convert::token_block_to_proto_block, schema, SerializedBiscuit},
15    KeyPair, PrivateKey,
16};
17
18use super::THIRD_PARTY_SIGNATURE_VERSION;
19
20/// Third party block request
21#[derive(PartialEq, Debug)]
22pub struct ThirdPartyRequest {
23    pub(crate) previous_signature: Vec<u8>,
24}
25
26impl ThirdPartyRequest {
27    pub(crate) fn from_container(
28        container: &SerializedBiscuit,
29    ) -> Result<ThirdPartyRequest, error::Token> {
30        if container.proof.is_sealed() {
31            return Err(error::Token::AppendOnSealed);
32        }
33
34        let previous_signature = container
35            .blocks
36            .last()
37            .unwrap_or(&container.authority)
38            .signature
39            .to_bytes()
40            .to_vec();
41        Ok(ThirdPartyRequest { previous_signature })
42    }
43
44    pub fn serialize(&self) -> Result<Vec<u8>, error::Token> {
45        let previous_signature = self.previous_signature.clone();
46
47        let request = schema::ThirdPartyBlockRequest {
48            legacy_previous_key: None,
49            legacy_public_keys: Vec::new(),
50            previous_signature,
51        };
52        let mut v = Vec::new();
53
54        request.encode(&mut v).map(|_| v).map_err(|e| {
55            error::Token::Format(error::Format::SerializationError(format!(
56                "serialization error: {:?}",
57                e
58            )))
59        })
60    }
61
62    pub fn serialize_base64(&self) -> Result<String, error::Token> {
63        Ok(base64::encode_config(self.serialize()?, base64::URL_SAFE))
64    }
65
66    pub fn deserialize(slice: &[u8]) -> Result<Self, error::Token> {
67        let data = schema::ThirdPartyBlockRequest::decode(slice).map_err(|e| {
68            error::Format::DeserializationError(format!("deserialization error: {:?}", e))
69        })?;
70
71        if !data.legacy_public_keys.is_empty() {
72            return Err(error::Token::Format(error::Format::DeserializationError(
73                "public keys were provided in third-party block request".to_owned(),
74            )));
75        }
76
77        if data.legacy_previous_key.is_some() {
78            return Err(error::Token::Format(error::Format::DeserializationError(
79                "previous public key was provided in third-party block request".to_owned(),
80            )));
81        }
82
83        let previous_signature = data.previous_signature.to_vec();
84
85        Ok(ThirdPartyRequest { previous_signature })
86    }
87
88    pub fn deserialize_base64<T>(slice: T) -> Result<Self, error::Token>
89    where
90        T: AsRef<[u8]>,
91    {
92        let decoded = base64::decode_config(slice, base64::URL_SAFE)?;
93        Self::deserialize(&decoded)
94    }
95
96    /// Creates a [`ThirdPartyBlock`] signed with the third party service's [`PrivateKey`]
97    pub fn create_block(
98        self,
99        private_key: &PrivateKey,
100        block_builder: BlockBuilder,
101    ) -> Result<ThirdPartyBlock, error::Token> {
102        let symbols = SymbolTable::new();
103        let mut block = block_builder.build(symbols);
104        block.version = max(super::DATALOG_3_2, block.version);
105
106        let mut payload = Vec::new();
107        token_block_to_proto_block(&block)
108            .encode(&mut payload)
109            .map_err(|e| {
110                error::Format::SerializationError(format!("serialization error: {:?}", e))
111            })?;
112
113        let signed_payload = generate_external_signature_payload_v1(
114            &payload,
115            &self.previous_signature,
116            THIRD_PARTY_SIGNATURE_VERSION,
117        );
118
119        let keypair = KeyPair::from(private_key);
120        let signature = keypair.sign(&signed_payload)?;
121
122        let public_key = keypair.public();
123        let content = schema::ThirdPartyBlockContents {
124            payload,
125            external_signature: schema::ExternalSignature {
126                signature: signature.to_bytes().to_vec(),
127                public_key: public_key.to_proto(),
128            },
129        };
130
131        Ok(ThirdPartyBlock(content))
132    }
133}
134
135/// Signed third party block content
136///
137/// this must be integrated with the token that created the [`ThirdPartyRequest`]
138/// using [`Biscuit::append_third_party`](crate::Biscuit::append_third_party)
139#[derive(Clone, Debug)]
140pub struct ThirdPartyBlock(pub(crate) schema::ThirdPartyBlockContents);
141
142impl ThirdPartyBlock {
143    pub fn serialize(&self) -> Result<Vec<u8>, error::Token> {
144        let mut buffer = vec![];
145        self.0.encode(&mut buffer).map(|_| buffer).map_err(|e| {
146            error::Token::Format(error::Format::SerializationError(format!(
147                "serialization error: {:?}",
148                e
149            )))
150        })
151    }
152
153    pub fn serialize_base64(&self) -> Result<String, error::Token> {
154        Ok(base64::encode_config(self.serialize()?, base64::URL_SAFE))
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161
162    #[test]
163    fn third_party_request_roundtrip() {
164        let mut rng: rand::rngs::StdRng = rand::SeedableRng::seed_from_u64(0);
165        let root = KeyPair::new_with_rng(crate::builder::Algorithm::Ed25519, &mut rng);
166        let biscuit1 = crate::Biscuit::builder()
167            .fact("right(\"file1\", \"read\")")
168            .unwrap()
169            .fact("right(\"file2\", \"read\")")
170            .unwrap()
171            .fact("right(\"file1\", \"write\")")
172            .unwrap()
173            .build_with_rng(&root, crate::token::default_symbol_table(), &mut rng)
174            .unwrap();
175        let req = biscuit1.third_party_request().unwrap();
176        let serialized_req = req.serialize().unwrap();
177        let parsed_req = ThirdPartyRequest::deserialize(&serialized_req).unwrap();
178
179        assert_eq!(req, parsed_req);
180    }
181}