bc_envelope/seal.rs
1#![cfg(all(feature = "signature", feature = "recipient"))]
2
3//! # Envelope Sealing and Unsealing
4//!
5//! This module provides convenience functions for combining signing and
6//! encryption operations in a single step, creating secure, authenticated
7//! envelopes.
8//!
9//! ## Sealing
10//!
11//! Sealing an envelope:
12//! 1. Signs the envelope with the sender's private key
13//! 2. Encrypts the signed envelope to the recipient's public key
14//!
15//! This creates a secure container where:
16//! - The recipient can verify who sent the envelope (authentication)
17//! - Only the intended recipient can decrypt the content (confidentiality)
18//! - The signature ensures the content hasn't been modified (integrity)
19//!
20//! ## Unsealing
21//!
22//! Unsealing performs these operations in reverse:
23//! 1. Decrypts the envelope using the recipient's private key
24//! 2. Verifies the signature using the sender's public key
25
26use anyhow::Result;
27use bc_components::{Decrypter, Encrypter, Signer, SigningOptions, Verifier};
28
29use crate::Envelope;
30
31impl Envelope {
32 /// Seals an envelope by signing it with the sender's key and then
33 /// encrypting it to the recipient.
34 ///
35 /// This is a convenience method that combines signing and encryption in one
36 /// step.
37 ///
38 /// # Arguments
39 ///
40 /// * `sender` - The private key used to sign the envelope
41 /// * `recipient` - The public key used to encrypt the envelope
42 ///
43 /// # Returns
44 ///
45 /// A new envelope that has been signed and encrypted
46 ///
47 /// # Example
48 ///
49 /// ```
50 /// # #[cfg(all(feature = "signature", feature = "recipient"))]
51 /// # {
52 /// use bc_components::{EncapsulationScheme, SignatureScheme};
53 /// use bc_envelope::prelude::*;
54 ///
55 /// // Create an envelope
56 /// let envelope = Envelope::new("Confidential message");
57 ///
58 /// // Generate keys for sender and recipient using specific schemes
59 /// let (sender_private, _) = SignatureScheme::Ed25519.keypair();
60 /// let (_, recipient_public) = EncapsulationScheme::X25519.keypair();
61 ///
62 /// // Seal the envelope
63 /// let sealed_envelope = envelope.seal(&sender_private, &recipient_public);
64 /// # }
65 /// ```
66 pub fn seal(
67 &self,
68 sender: &dyn Signer,
69 recipient: &dyn Encrypter,
70 ) -> Envelope {
71 self.sign(sender).encrypt_to_recipient(recipient)
72 }
73
74 /// Seals an envelope by signing it with the sender's key and then
75 /// encrypting it to the recipient, with optional signing options.
76 ///
77 /// This is a convenience method that combines signing and encryption in one
78 /// step.
79 ///
80 /// # Arguments
81 ///
82 /// * `sender` - The private key used to sign the envelope
83 /// * `recipient` - The public key used to encrypt the envelope
84 /// * `options` - Optional signing options to control how the signature is
85 /// created
86 ///
87 /// # Returns
88 ///
89 /// A new envelope that has been signed and encrypted
90 ///
91 /// # Example
92 ///
93 /// ```
94 /// # #[cfg(all(feature = "signature", feature = "recipient"))]
95 /// # fn main() -> Result<(), anyhow::Error> {
96 /// use bc_components::{EncapsulationScheme, SignatureScheme, SigningOptions};
97 /// use bc_envelope::prelude::*;
98 ///
99 /// // Create an envelope
100 /// let envelope = Envelope::new("Confidential message");
101 ///
102 /// // Generate keys for sender and recipient using specific schemes
103 /// let (sender_private, _) = SignatureScheme::Ed25519.keypair();
104 /// let (_, recipient_public) = EncapsulationScheme::X25519.keypair();
105 ///
106 /// // Create signing options for SSH
107 /// let options = SigningOptions::Ssh {
108 /// namespace: "test".to_string(),
109 /// hash_alg: ssh_key::HashAlg::Sha512,
110 /// };
111 ///
112 /// // Seal the envelope with options
113 /// let sealed_envelope =
114 /// envelope.seal_opt(&sender_private, &recipient_public, Some(options));
115 /// # Ok(())
116 /// # }
117 /// ```
118 pub fn seal_opt(
119 &self,
120 sender: &dyn Signer,
121 recipient: &dyn Encrypter,
122 options: Option<SigningOptions>,
123 ) -> Envelope {
124 self.sign_opt(sender, options)
125 .encrypt_to_recipient(recipient)
126 }
127
128 /// Unseals an envelope by decrypting it with the recipient's private key
129 /// and then verifying the signature using the sender's public key.
130 ///
131 /// This is a convenience method that combines decryption and signature
132 /// verification in one step.
133 ///
134 /// # Arguments
135 ///
136 /// * `sender` - The public key used to verify the signature
137 /// * `recipient` - The private key used to decrypt the envelope
138 ///
139 /// # Returns
140 ///
141 /// A Result containing the unsealed envelope if successful, or an error if
142 /// decryption or signature verification fails
143 ///
144 /// # Example
145 ///
146 /// ```
147 /// # #[cfg(all(feature = "signature", feature = "recipient"))]
148 /// # fn main() -> Result<(), anyhow::Error> {
149 /// use bc_components::{EncapsulationScheme, SignatureScheme};
150 /// use bc_envelope::prelude::*;
151 ///
152 /// // Create an envelope
153 /// let envelope = Envelope::new("Confidential message");
154 ///
155 /// // Generate keys for sender and recipient using specific schemes
156 /// let (sender_private, sender_public) = SignatureScheme::Ed25519.keypair();
157 /// let (recipient_private, recipient_public) =
158 /// EncapsulationScheme::X25519.keypair();
159 ///
160 /// // Seal the envelope
161 /// let sealed_envelope = envelope.seal(&sender_private, &recipient_public);
162 ///
163 /// // Unseal the envelope using the recipient's private key
164 /// let unsealed_envelope =
165 /// sealed_envelope.unseal(&sender_public, &recipient_private)?;
166 ///
167 /// // Verify we got back the original message
168 /// let message: String = unsealed_envelope.extract_subject()?;
169 /// assert_eq!(message, "Confidential message");
170 /// # Ok(())
171 /// # }
172 /// ```
173 pub fn unseal(
174 &self,
175 sender: &dyn Verifier,
176 recipient: &dyn Decrypter,
177 ) -> Result<Envelope> {
178 self.decrypt_to_recipient(recipient)?.verify(sender)
179 }
180}
181
182#[cfg(all(test, feature = "signature", feature = "recipient"))]
183mod tests {
184 use anyhow::Result;
185 use bc_components::{EncapsulationScheme, SignatureScheme, SigningOptions};
186
187 use super::*;
188
189 #[test]
190 fn test_seal_and_unseal() -> Result<()> {
191 // Create a test envelope
192
193 let message = "Top secret message";
194 let original_envelope = Envelope::new(message);
195
196 // Generate keys for sender and recipient using established schemes
197 let (sender_private, sender_public) =
198 SignatureScheme::Ed25519.keypair();
199 let (recipient_private, recipient_public) =
200 EncapsulationScheme::X25519.keypair();
201
202 // Step 1: Seal the envelope
203 let sealed_envelope =
204 original_envelope.seal(&sender_private, &recipient_public);
205
206 // Verify the envelope is encrypted
207 assert!(sealed_envelope.is_subject_encrypted());
208
209 // Step 2: Unseal the envelope
210 let unsealed_envelope =
211 sealed_envelope.unseal(&sender_public, &recipient_private)?;
212
213 // Verify we got back the original message
214 let extracted_message: String = unsealed_envelope.extract_subject()?;
215 assert_eq!(extracted_message, message);
216
217 Ok(())
218 }
219
220 #[test]
221 #[cfg(all(feature = "signature", feature = "recipient"))]
222 fn test_seal_opt_with_options() -> Result<()> {
223 // Create a test envelope
224 let message = "Confidential data";
225 let original_envelope = Envelope::new(message);
226
227 // Generate keys for sender and recipient
228 let (sender_private, sender_public) =
229 SignatureScheme::Ed25519.keypair();
230 let (recipient_private, recipient_public) =
231 EncapsulationScheme::X25519.keypair();
232
233 // Create signing options
234 let options = SigningOptions::Ssh {
235 namespace: "test".to_string(),
236 hash_alg: ssh_key::HashAlg::Sha512,
237 };
238
239 // Seal the envelope with options
240 let sealed_envelope = original_envelope.seal_opt(
241 &sender_private,
242 &recipient_public,
243 Some(options),
244 );
245
246 // Verify the envelope is encrypted
247 assert!(sealed_envelope.is_subject_encrypted());
248
249 // Unseal the envelope
250 let unsealed_envelope =
251 sealed_envelope.unseal(&sender_public, &recipient_private)?;
252
253 // Verify we got back the original message
254 let extracted_message: String = unsealed_envelope.extract_subject()?;
255 assert_eq!(extracted_message, message);
256
257 Ok(())
258 }
259}