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