warg_protocol/
proto_envelope.rs

1use super::registry::RegistryIndex;
2use anyhow::Error;
3use base64::{engine::general_purpose::STANDARD, Engine};
4use prost::Message;
5use serde::{Deserialize, Serialize};
6use serde_with::{base64::Base64, serde_as};
7use std::fmt;
8use thiserror::Error;
9use warg_crypto::{hash::AnyHashError, signing, Decode, Signable};
10use warg_protobuf::protocol as protobuf;
11
12/// The ProtoEnvelope with the published registry log index.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct PublishedProtoEnvelope<Contents> {
15    /// The wrapped ProtoEnvelope
16    pub envelope: ProtoEnvelope<Contents>,
17    /// The published registry log index for the record
18    pub registry_index: RegistryIndex,
19}
20
21/// The envelope struct is used to keep around the original
22/// bytes that the content was serialized into in case
23/// the serialization is not canonical.
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct ProtoEnvelope<Contents> {
26    /// The content represented by content_bytes
27    contents: Contents,
28    /// The serialized representation of the content
29    content_bytes: Vec<u8>,
30    /// The hash of the key that signed this envelope
31    key_id: signing::KeyID,
32    /// The signature for the content_bytes
33    signature: signing::Signature,
34}
35
36impl<Contents> ProtoEnvelope<Contents> {
37    /// Create an envelope for some contents using a signature.
38    pub fn signed_contents(
39        private_key: &signing::PrivateKey,
40        contents: Contents,
41    ) -> Result<Self, signing::SignatureError>
42    where
43        Contents: Signable,
44    {
45        let content_bytes: Vec<u8> = contents.encode();
46
47        let key_id = private_key.public_key().fingerprint();
48        let signature = contents.sign(private_key)?;
49        Ok(ProtoEnvelope {
50            contents,
51            content_bytes,
52            key_id,
53            signature,
54        })
55    }
56
57    /// Get the byte representation of the envelope contents.
58    pub fn content_bytes(&self) -> &[u8] {
59        &self.content_bytes
60    }
61
62    pub fn key_id(&self) -> &signing::KeyID {
63        &self.key_id
64    }
65
66    pub fn signature(&self) -> &signing::Signature {
67        &self.signature
68    }
69
70    /// Get the representation of the entire envelope as a byte vector.
71    /// This is the logical inverse of `Envelope::from_bytes`.
72    pub fn to_protobuf(&self) -> Vec<u8> {
73        let proto_envelope = protobuf::Envelope {
74            contents: self.content_bytes.clone(),
75            key_id: self.key_id.to_string(),
76            signature: self.signature.to_string(),
77        };
78        proto_envelope.encode_to_vec()
79    }
80
81    /// Create an entire envelope from a byte vector.
82    /// This is the logical inverse of `Envelope::as_bytes`.
83    pub fn from_protobuf(bytes: &[u8]) -> Result<Self, ParseEnvelopeError>
84    where
85        Contents: Decode,
86    {
87        // Parse outer envelope
88        let envelope = protobuf::Envelope::decode(bytes)?;
89        let contents = Contents::decode(&envelope.contents)?;
90
91        // Read key ID and signature
92        let key_id = envelope.key_id.into();
93        let signature = envelope.signature.parse()?;
94
95        Ok(ProtoEnvelope {
96            contents,
97            content_bytes: envelope.contents,
98            key_id,
99            signature,
100        })
101    }
102}
103
104impl<Content> AsRef<Content> for ProtoEnvelope<Content> {
105    fn as_ref(&self) -> &Content {
106        &self.contents
107    }
108}
109
110/// Errors that occur in the process of parsing an envelope from bytes
111#[derive(Error, Debug)]
112pub enum ParseEnvelopeError {
113    #[error("failed to parse the outer envelope protobuf message")]
114    ProtobufEnvelope(#[from] prost::DecodeError),
115
116    #[error("failed to parse envelope contents from bytes")]
117    Contents(#[from] Error),
118
119    #[error("failed to parse envelope key id")]
120    KeyID(#[from] AnyHashError),
121
122    #[error("failed to parse envelope signature")]
123    Signature(#[from] signing::SignatureParseError),
124}
125
126#[serde_as]
127#[derive(Clone, Serialize, Deserialize)]
128#[serde(rename_all = "camelCase")]
129pub struct ProtoEnvelopeBody {
130    /// The serialized representation of the content
131    #[serde_as(as = "Base64")]
132    content_bytes: Vec<u8>,
133    /// The hash of the key that signed this envelope
134    key_id: signing::KeyID,
135    /// The signature for the content_bytes
136    signature: signing::Signature,
137}
138
139impl<Content> TryFrom<ProtoEnvelopeBody> for ProtoEnvelope<Content>
140where
141    Content: Decode,
142{
143    type Error = Error;
144
145    fn try_from(value: ProtoEnvelopeBody) -> Result<Self, Self::Error> {
146        let contents = Content::decode(&value.content_bytes)?;
147        let envelope = ProtoEnvelope {
148            contents,
149            content_bytes: value.content_bytes,
150            key_id: value.key_id,
151            signature: value.signature,
152        };
153        Ok(envelope)
154    }
155}
156
157impl<Content> From<ProtoEnvelope<Content>> for ProtoEnvelopeBody {
158    fn from(value: ProtoEnvelope<Content>) -> Self {
159        ProtoEnvelopeBody {
160            content_bytes: value.content_bytes,
161            key_id: value.key_id,
162            signature: value.signature,
163        }
164    }
165}
166
167impl fmt::Debug for ProtoEnvelopeBody {
168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169        f.debug_struct("ProtoEnvelopeBody")
170            .field("content_bytes", &STANDARD.encode(&self.content_bytes))
171            .field("key_id", &self.key_id)
172            .field("signature", &self.signature)
173            .finish()
174    }
175}
176
177#[serde_as]
178#[derive(Clone, Serialize, Deserialize)]
179#[serde(rename_all = "camelCase")]
180pub struct PublishedProtoEnvelopeBody {
181    /// The ProtoEnvelopeBody flattened
182    #[serde(flatten)]
183    pub envelope: ProtoEnvelopeBody,
184    /// The index of the published record in the registry log
185    pub registry_index: RegistryIndex,
186}
187
188impl<Content> TryFrom<PublishedProtoEnvelopeBody> for PublishedProtoEnvelope<Content>
189where
190    Content: Decode,
191{
192    type Error = Error;
193
194    fn try_from(value: PublishedProtoEnvelopeBody) -> Result<Self, Self::Error> {
195        Ok(PublishedProtoEnvelope {
196            envelope: ProtoEnvelope::<Content>::try_from(value.envelope)?,
197            registry_index: value.registry_index,
198        })
199    }
200}
201
202impl<Content> From<PublishedProtoEnvelope<Content>> for PublishedProtoEnvelopeBody {
203    fn from(value: PublishedProtoEnvelope<Content>) -> Self {
204        PublishedProtoEnvelopeBody {
205            envelope: ProtoEnvelopeBody::from(value.envelope),
206            registry_index: value.registry_index,
207        }
208    }
209}
210
211impl fmt::Debug for PublishedProtoEnvelopeBody {
212    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213        f.debug_struct("PublishedProtoEnvelopeBody")
214            .field(
215                "content_bytes",
216                &STANDARD.encode(&self.envelope.content_bytes),
217            )
218            .field("key_id", &self.envelope.key_id)
219            .field("signature", &self.envelope.signature)
220            .field("registry_index", &self.registry_index)
221            .finish()
222    }
223}