Skip to main content

native_ossl/
cipher.rs

1//! `CipherAlg` — `EVP_CIPHER` algorithm descriptor.
2//!
3//! Phase 3.2 delivers `CipherAlg`; Phase 4.2 extends this module with
4//! `CipherCtx<Dir>`, `AeadEncryptCtx`, and `AeadDecryptCtx`.
5
6use crate::error::ErrorStack;
7use native_ossl_sys as sys;
8use std::ffi::CStr;
9use std::sync::Arc;
10
11// ── CipherAlg — algorithm descriptor ─────────────────────────────────────────
12
13/// An OpenSSL symmetric cipher algorithm descriptor (`EVP_CIPHER*`).
14///
15/// Fetched once and reused.  Implements `Clone` via `EVP_CIPHER_up_ref`.
16pub struct CipherAlg {
17    ptr: *mut sys::EVP_CIPHER,
18    /// Keeps the library context alive while this descriptor is in use.
19    lib_ctx: Option<Arc<crate::lib_ctx::LibCtx>>,
20}
21
22impl CipherAlg {
23    /// Fetch a cipher algorithm from the global default library context.
24    ///
25    /// # Errors
26    ///
27    /// Returns `Err` if the algorithm is not available.
28    pub fn fetch(name: &CStr, props: Option<&CStr>) -> Result<Self, ErrorStack> {
29        let props_ptr = props.map_or(std::ptr::null(), CStr::as_ptr);
30        let ptr = unsafe {
31            sys::EVP_CIPHER_fetch(std::ptr::null_mut(), name.as_ptr(), props_ptr)
32        };
33        if ptr.is_null() {
34            return Err(ErrorStack::drain());
35        }
36        Ok(CipherAlg { ptr, lib_ctx: None })
37    }
38
39    /// Fetch a cipher algorithm from an explicit library context.
40    ///
41    /// # Errors
42    pub fn fetch_in(
43        ctx: &Arc<crate::lib_ctx::LibCtx>,
44        name: &CStr,
45        props: Option<&CStr>,
46    ) -> Result<Self, ErrorStack> {
47        let props_ptr = props.map_or(std::ptr::null(), CStr::as_ptr);
48        let ptr = unsafe {
49            sys::EVP_CIPHER_fetch(ctx.as_ptr(), name.as_ptr(), props_ptr)
50        };
51        if ptr.is_null() {
52            return Err(ErrorStack::drain());
53        }
54        Ok(CipherAlg {
55            ptr,
56            lib_ctx: Some(Arc::clone(ctx)),
57        })
58    }
59
60    /// Expected key length in bytes (e.g. 32 for AES-256).
61    #[must_use]
62    pub fn key_len(&self) -> usize {
63        usize::try_from(unsafe { sys::EVP_CIPHER_get_key_length(self.ptr) }).unwrap_or(0)
64    }
65
66    /// Expected IV length in bytes (0 for ECB mode).
67    #[must_use]
68    pub fn iv_len(&self) -> usize {
69        usize::try_from(unsafe { sys::EVP_CIPHER_get_iv_length(self.ptr) }).unwrap_or(0)
70    }
71
72    /// Block size in bytes (1 for stream ciphers; 16 for AES).
73    #[must_use]
74    pub fn block_size(&self) -> usize {
75        usize::try_from(unsafe { sys::EVP_CIPHER_get_block_size(self.ptr) }).unwrap_or(0)
76    }
77
78    /// OpenSSL flags for this cipher (e.g. `EVP_CIPH_FLAG_AEAD_CIPHER`).
79    #[must_use]
80    pub fn flags(&self) -> u64 {
81        unsafe { sys::EVP_CIPHER_get_flags(self.ptr) }
82    }
83
84    /// Return `true` if this is an AEAD cipher (GCM, CCM, ChaCha20-Poly1305).
85    #[must_use]
86    pub fn is_aead(&self) -> bool {
87        // EVP_CIPH_FLAG_AEAD_CIPHER = 0x0020_0000
88        (self.flags() & 0x0020_0000) != 0
89    }
90
91    /// Return the raw `EVP_CIPHER*` pointer.  Valid for the lifetime of `self`.
92    #[must_use]
93    pub fn as_ptr(&self) -> *const sys::EVP_CIPHER {
94        self.ptr
95    }
96}
97
98impl Clone for CipherAlg {
99    fn clone(&self) -> Self {
100        unsafe { sys::EVP_CIPHER_up_ref(self.ptr) };
101        CipherAlg {
102            ptr: self.ptr,
103            lib_ctx: self.lib_ctx.clone(),
104        }
105    }
106}
107
108impl Drop for CipherAlg {
109    fn drop(&mut self) {
110        unsafe { sys::EVP_CIPHER_free(self.ptr) };
111    }
112}
113
114// SAFETY: `EVP_CIPHER*` is reference-counted and immutable after fetch.
115unsafe impl Send for CipherAlg {}
116unsafe impl Sync for CipherAlg {}
117
118// ── Direction markers (Phase 4.2) ─────────────────────────────────────────────
119
120/// Marker type for encrypt direction.
121pub struct Encrypt;
122/// Marker type for decrypt direction.
123pub struct Decrypt;
124
125mod sealed {
126    pub trait Direction {}
127    impl Direction for super::Encrypt {}
128    impl Direction for super::Decrypt {}
129}
130
131// ── CipherCtx<Dir> — stateful context ────────────────────────────────────────
132
133/// Stateful symmetric cipher context.
134///
135/// `Dir` is either [`Encrypt`] or [`Decrypt`]; the type parameter prevents
136/// accidentally calling encrypt operations on a decrypt context and vice versa.
137///
138/// `!Clone` — cipher contexts have no `up_ref`; use a fresh context per message.
139pub struct CipherCtx<Dir> {
140    ptr: *mut sys::EVP_CIPHER_CTX,
141    _dir: std::marker::PhantomData<Dir>,
142}
143
144impl<Dir: sealed::Direction> CipherCtx<Dir> {
145    /// Feed `input` into the cipher; write plaintext/ciphertext to `output`.
146    ///
147    /// `output` must be at least `input.len() + block_size - 1` bytes.
148    /// Returns the number of bytes written.
149    ///
150    /// # Errors
151    pub fn update(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize, ErrorStack>
152    where
153        Dir: IsEncrypt,
154    {
155        // SAFETY: self.ptr is a valid EVP_CIPHER_CTX initialised in CipherAlg::encrypt/decrypt.
156        unsafe { Dir::do_update(self.ptr, input, output) }
157    }
158
159    /// Feed `input` through the cipher and return the output as a `Vec<u8>`.
160    ///
161    /// Allocates `input.len() + block_size` bytes, calls [`Self::update`], then
162    /// truncates to the actual number of bytes written.  Use this when the caller
163    /// cannot easily compute the output size in advance.
164    ///
165    /// # Errors
166    pub fn update_to_vec(&mut self, input: &[u8]) -> Result<Vec<u8>, ErrorStack>
167    where
168        Dir: IsEncrypt,
169    {
170        let block_size = usize::try_from(unsafe {
171            sys::EVP_CIPHER_CTX_get_block_size(self.ptr)
172        })
173        .unwrap_or(0);
174        let max = input.len() + block_size;
175        let mut out = vec![0u8; max];
176        let n = self.update(input, &mut out)?;
177        out.truncate(n);
178        Ok(out)
179    }
180
181    /// Finalise the operation (flush padding / verify auth tag).
182    ///
183    /// # Errors
184    pub fn finalize(&mut self, output: &mut [u8]) -> Result<usize, ErrorStack>
185    where
186        Dir: IsEncrypt,
187    {
188        // SAFETY: same as update.
189        unsafe { Dir::do_finalize(self.ptr, output) }
190    }
191
192    /// Set dynamic parameters on the context (e.g. GCM tag length).
193    ///
194    /// # Errors
195    pub fn set_params(&mut self, params: &crate::params::Params<'_>) -> Result<(), ErrorStack> {
196        crate::ossl_call!(sys::EVP_CIPHER_CTX_set_params(self.ptr, params.as_ptr()))
197    }
198
199    /// Return the raw `EVP_CIPHER_CTX*` pointer.  Returns a mutable pointer
200    /// because most OpenSSL EVP functions require `EVP_CIPHER_CTX*` even for
201    /// logically read-only operations.
202    #[must_use]
203    pub fn as_ptr(&self) -> *mut sys::EVP_CIPHER_CTX {
204        self.ptr
205    }
206}
207
208impl<Dir> Drop for CipherCtx<Dir> {
209    fn drop(&mut self) {
210        unsafe { sys::EVP_CIPHER_CTX_free(self.ptr) };
211    }
212}
213
214unsafe impl<Dir: sealed::Direction> Send for CipherCtx<Dir> {}
215
216/// Helper trait that routes to the correct `EVP_Encrypt*` or `EVP_Decrypt*` functions.
217///
218/// Sealed: only `Encrypt` and `Decrypt` implement this.
219pub trait IsEncrypt: sealed::Direction {
220    /// Feed data into the cipher and write output.
221    ///
222    /// # Errors
223    ///
224    /// Returns `Err` if the underlying EVP update call fails.
225    ///
226    /// # Safety
227    ///
228    /// `ctx` must be a valid, initialised `EVP_CIPHER_CTX*`.
229    unsafe fn do_update(
230        ctx: *mut sys::EVP_CIPHER_CTX,
231        input: &[u8],
232        output: &mut [u8],
233    ) -> Result<usize, ErrorStack>;
234
235    /// Flush final block and write output.
236    ///
237    /// # Errors
238    ///
239    /// Returns `Err` if finalisation fails (e.g. auth tag mismatch for AEAD).
240    ///
241    /// # Safety
242    ///
243    /// `ctx` must be a valid, initialised `EVP_CIPHER_CTX*`.
244    unsafe fn do_finalize(
245        ctx: *mut sys::EVP_CIPHER_CTX,
246        output: &mut [u8],
247    ) -> Result<usize, ErrorStack>;
248}
249
250impl IsEncrypt for Encrypt {
251    unsafe fn do_update(
252        ctx: *mut sys::EVP_CIPHER_CTX,
253        input: &[u8],
254        output: &mut [u8],
255    ) -> Result<usize, ErrorStack> {
256        let inl = i32::try_from(input.len()).map_err(|_| ErrorStack::drain())?;
257        let mut outl: i32 = 0;
258        crate::ossl_call!(sys::EVP_EncryptUpdate(
259            ctx,
260            output.as_mut_ptr(),
261            std::ptr::addr_of_mut!(outl),
262            input.as_ptr(),
263            inl
264        ))?;
265        Ok(usize::try_from(outl).unwrap_or(0))
266    }
267
268    unsafe fn do_finalize(
269        ctx: *mut sys::EVP_CIPHER_CTX,
270        output: &mut [u8],
271    ) -> Result<usize, ErrorStack> {
272        let mut outl: i32 = 0;
273        crate::ossl_call!(sys::EVP_EncryptFinal_ex(
274            ctx,
275            output.as_mut_ptr(),
276            std::ptr::addr_of_mut!(outl)
277        ))?;
278        Ok(usize::try_from(outl).unwrap_or(0))
279    }
280}
281
282impl IsEncrypt for Decrypt {
283    unsafe fn do_update(
284        ctx: *mut sys::EVP_CIPHER_CTX,
285        input: &[u8],
286        output: &mut [u8],
287    ) -> Result<usize, ErrorStack> {
288        let inl = i32::try_from(input.len()).map_err(|_| ErrorStack::drain())?;
289        let mut outl: i32 = 0;
290        crate::ossl_call!(sys::EVP_DecryptUpdate(
291            ctx,
292            output.as_mut_ptr(),
293            std::ptr::addr_of_mut!(outl),
294            input.as_ptr(),
295            inl
296        ))?;
297        Ok(usize::try_from(outl).unwrap_or(0))
298    }
299
300    unsafe fn do_finalize(
301        ctx: *mut sys::EVP_CIPHER_CTX,
302        output: &mut [u8],
303    ) -> Result<usize, ErrorStack> {
304        let mut outl: i32 = 0;
305        crate::ossl_call!(sys::EVP_DecryptFinal_ex(
306            ctx,
307            output.as_mut_ptr(),
308            std::ptr::addr_of_mut!(outl)
309        ))?;
310        Ok(usize::try_from(outl).unwrap_or(0))
311    }
312}
313
314impl CipherAlg {
315    /// Create an encryption context.
316    ///
317    /// **`key` and `iv` are copied** into OpenSSL's internal state by
318    /// `EVP_EncryptInit_ex2` (key scheduling + zeroization on free).
319    ///
320    /// # Errors
321    pub fn encrypt(
322        &self,
323        key: &[u8],
324        iv: &[u8],
325        params: Option<&crate::params::Params<'_>>,
326    ) -> Result<CipherCtx<Encrypt>, ErrorStack> {
327        let ctx_ptr = unsafe { sys::EVP_CIPHER_CTX_new() };
328        if ctx_ptr.is_null() {
329            return Err(ErrorStack::drain());
330        }
331        let params_ptr = params.map_or(crate::params::null_params(), crate::params::Params::as_ptr);
332        crate::ossl_call!(sys::EVP_EncryptInit_ex2(
333            ctx_ptr,
334            self.ptr,
335            key.as_ptr(),
336            iv.as_ptr(),
337            params_ptr
338        ))
339        .map_err(|e| {
340            unsafe { sys::EVP_CIPHER_CTX_free(ctx_ptr) };
341            e
342        })?;
343        Ok(CipherCtx {
344            ptr: ctx_ptr,
345            _dir: std::marker::PhantomData,
346        })
347    }
348
349    /// Create a decryption context.
350    ///
351    /// Same key/IV copy semantics as `encrypt`.
352    ///
353    /// # Errors
354    pub fn decrypt(
355        &self,
356        key: &[u8],
357        iv: &[u8],
358        params: Option<&crate::params::Params<'_>>,
359    ) -> Result<CipherCtx<Decrypt>, ErrorStack> {
360        let ctx_ptr = unsafe { sys::EVP_CIPHER_CTX_new() };
361        if ctx_ptr.is_null() {
362            return Err(ErrorStack::drain());
363        }
364        let params_ptr = params.map_or(crate::params::null_params(), crate::params::Params::as_ptr);
365        crate::ossl_call!(sys::EVP_DecryptInit_ex2(
366            ctx_ptr,
367            self.ptr,
368            key.as_ptr(),
369            iv.as_ptr(),
370            params_ptr
371        ))
372        .map_err(|e| {
373            unsafe { sys::EVP_CIPHER_CTX_free(ctx_ptr) };
374            e
375        })?;
376        Ok(CipherCtx {
377            ptr: ctx_ptr,
378            _dir: std::marker::PhantomData,
379        })
380    }
381}
382
383// ── AEAD types (Phase 4.2) ────────────────────────────────────────────────────
384
385/// AEAD encryption context (GCM, CCM, ChaCha20-Poly1305).
386pub struct AeadEncryptCtx(CipherCtx<Encrypt>);
387
388impl AeadEncryptCtx {
389    /// Create an AEAD encryption context.
390    ///
391    /// # Panics
392    ///
393    /// Panics if `alg` is not an AEAD cipher (`EVP_CIPH_FLAG_AEAD_CIPHER` not set).
394    ///
395    /// # Errors
396    pub fn new(
397        alg: &CipherAlg,
398        key: &[u8],
399        iv: &[u8],
400        params: Option<&crate::params::Params<'_>>,
401    ) -> Result<Self, ErrorStack> {
402        assert!(alg.is_aead(), "CipherAlg is not an AEAD algorithm");
403        Ok(AeadEncryptCtx(alg.encrypt(key, iv, params)?))
404    }
405
406    /// Set additional authenticated data (AAD).  Call before first `update`.
407    ///
408    /// # Panics
409    ///
410    /// Panics if `aad.len() > i32::MAX` (effectively impossible in practice).
411    ///
412    /// # Errors
413    pub fn set_aad(&mut self, aad: &[u8]) -> Result<(), ErrorStack> {
414        // AAD is fed via EVP_EncryptUpdate with output = NULL.
415        let alen = i32::try_from(aad.len()).expect("AAD too large for EVP_EncryptUpdate");
416        let mut outl: i32 = 0;
417        crate::ossl_call!(sys::EVP_EncryptUpdate(
418            self.0.ptr,
419            std::ptr::null_mut(),
420            std::ptr::addr_of_mut!(outl),
421            aad.as_ptr(),
422            alen
423        ))
424    }
425
426    /// Feed `input` into the AEAD cipher; write to `output`.
427    ///
428    /// # Errors
429    pub fn update(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize, ErrorStack> {
430        self.0.update(input, output)
431    }
432
433    /// Flush any remaining bytes.  Must be called before `tag`.
434    ///
435    /// # Errors
436    pub fn finalize(&mut self, output: &mut [u8]) -> Result<usize, ErrorStack> {
437        self.0.finalize(output)
438    }
439
440    /// Retrieve the authentication tag after `finalize`.
441    ///
442    /// `tag` must be 16 bytes for GCM (`EVP_CTRL_GCM_GET_TAG = 16`).
443    ///
444    /// # Panics
445    ///
446    /// Panics if `tag.len() > i32::MAX`.
447    ///
448    /// # Errors
449    pub fn tag(&self, tag: &mut [u8]) -> Result<(), ErrorStack> {
450        // EVP_CTRL_GCM_GET_TAG = 16
451        let tlen = i32::try_from(tag.len()).expect("tag slice too large");
452        let rc = unsafe {
453            sys::EVP_CIPHER_CTX_ctrl(
454                self.0.ptr,
455                16, // EVP_CTRL_GCM_GET_TAG
456                tlen,
457                tag.as_mut_ptr().cast(),
458            )
459        };
460        if rc != 1 {
461            return Err(ErrorStack::drain());
462        }
463        Ok(())
464    }
465}
466
467/// AEAD decryption context.
468pub struct AeadDecryptCtx(CipherCtx<Decrypt>);
469
470impl AeadDecryptCtx {
471    /// Create an AEAD decryption context.
472    ///
473    /// # Panics
474    ///
475    /// Panics if `alg` is not an AEAD cipher.
476    ///
477    /// # Errors
478    pub fn new(
479        alg: &CipherAlg,
480        key: &[u8],
481        iv: &[u8],
482        params: Option<&crate::params::Params<'_>>,
483    ) -> Result<Self, ErrorStack> {
484        assert!(alg.is_aead(), "CipherAlg is not an AEAD algorithm");
485        Ok(AeadDecryptCtx(alg.decrypt(key, iv, params)?))
486    }
487
488    /// Set AAD before first `update`.
489    ///
490    /// # Panics
491    ///
492    /// Panics if `aad.len() > i32::MAX`.
493    ///
494    /// # Errors
495    pub fn set_aad(&mut self, aad: &[u8]) -> Result<(), ErrorStack> {
496        let alen = i32::try_from(aad.len()).expect("AAD too large for EVP_DecryptUpdate");
497        let mut outl: i32 = 0;
498        crate::ossl_call!(sys::EVP_DecryptUpdate(
499            self.0.ptr,
500            std::ptr::null_mut(),
501            std::ptr::addr_of_mut!(outl),
502            aad.as_ptr(),
503            alen
504        ))
505    }
506
507    /// Feed `input` into the cipher; write to `output`.
508    ///
509    /// # Errors
510    pub fn update(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize, ErrorStack> {
511        self.0.update(input, output)
512    }
513
514    /// Set the expected authentication tag before `finalize`.
515    ///
516    /// # Panics
517    ///
518    /// Panics if `tag.len() > i32::MAX`.
519    ///
520    /// # Errors
521    pub fn set_tag(&mut self, tag: &[u8]) -> Result<(), ErrorStack> {
522        // EVP_CTRL_GCM_SET_TAG = 17
523        let tlen = i32::try_from(tag.len()).expect("tag slice too large");
524        let rc = unsafe {
525            sys::EVP_CIPHER_CTX_ctrl(
526                self.0.ptr,
527                17, // EVP_CTRL_GCM_SET_TAG
528                tlen,
529                // OpenSSL API is not const-correct here; cast away const.
530                tag.as_ptr().cast_mut().cast(),
531            )
532        };
533        if rc != 1 {
534            return Err(ErrorStack::drain());
535        }
536        Ok(())
537    }
538
539    /// Finalise — returns `Err` if authentication tag verification fails.
540    ///
541    /// # Errors
542    pub fn finalize(&mut self, output: &mut [u8]) -> Result<usize, ErrorStack> {
543        self.0.finalize(output)
544    }
545}
546
547// ── Tests ─────────────────────────────────────────────────────────────────────
548
549#[cfg(test)]
550mod tests {
551    use super::*;
552
553    #[test]
554    fn fetch_aes_256_gcm_properties() {
555        let alg = CipherAlg::fetch(c"AES-256-GCM", None).unwrap();
556        assert_eq!(alg.key_len(), 32);
557        assert_eq!(alg.iv_len(), 12);
558        assert_eq!(alg.block_size(), 1);
559        assert!(alg.is_aead());
560    }
561
562    #[test]
563    fn fetch_aes_256_cbc_properties() {
564        let alg = CipherAlg::fetch(c"AES-256-CBC", None).unwrap();
565        assert_eq!(alg.key_len(), 32);
566        assert_eq!(alg.iv_len(), 16);
567        assert_eq!(alg.block_size(), 16);
568        assert!(!alg.is_aead());
569    }
570
571    #[test]
572    fn fetch_nonexistent_fails() {
573        assert!(CipherAlg::fetch(c"NONEXISTENT_CIPHER_XYZ", None).is_err());
574    }
575
576    #[test]
577    fn clone_then_drop_both() {
578        let alg = CipherAlg::fetch(c"AES-256-GCM", None).unwrap();
579        let alg2 = alg.clone();
580        drop(alg);
581        drop(alg2);
582    }
583
584    /// AES-256-CBC round-trip encrypt + decrypt.
585    #[test]
586    fn aes_256_cbc_round_trip() {
587        let alg = CipherAlg::fetch(c"AES-256-CBC", None).unwrap();
588        let key = [0x42u8; 32];
589        let iv = [0x24u8; 16];
590        let plaintext = b"Hello, cipher world!";
591
592        // Encrypt.
593        let mut enc = alg.encrypt(&key, &iv, None).unwrap();
594        let mut ciphertext = vec![0u8; plaintext.len() + alg.block_size()];
595        let n = enc.update(plaintext, &mut ciphertext).unwrap();
596        let m = enc.finalize(&mut ciphertext[n..]).unwrap();
597        ciphertext.truncate(n + m);
598
599        // Decrypt.
600        let mut dec = alg.decrypt(&key, &iv, None).unwrap();
601        let mut recovered = vec![0u8; ciphertext.len() + alg.block_size()];
602        let n2 = dec.update(&ciphertext, &mut recovered).unwrap();
603        let m2 = dec.finalize(&mut recovered[n2..]).unwrap();
604        recovered.truncate(n2 + m2);
605
606        assert_eq!(recovered, plaintext);
607    }
608
609    /// AES-256-GCM AEAD: encrypt → tag → decrypt → verify; tag corruption → Err.
610    #[test]
611    fn aes_256_gcm_round_trip_and_tag_failure() {
612        let alg = CipherAlg::fetch(c"AES-256-GCM", None).unwrap();
613        let key = [0x11u8; 32];
614        let iv = [0x22u8; 12];
615        let aad = b"additional data";
616        let plaintext = b"secret message!";
617
618        // Encrypt.
619        let mut enc = AeadEncryptCtx::new(&alg, &key, &iv, None).unwrap();
620        enc.set_aad(aad).unwrap();
621        let mut ciphertext = vec![0u8; plaintext.len()];
622        let n = enc.update(plaintext, &mut ciphertext).unwrap();
623        enc.finalize(&mut ciphertext[n..]).unwrap();
624        let mut tag = [0u8; 16];
625        enc.tag(&mut tag).unwrap();
626
627        // Decrypt with correct tag — must succeed.
628        let mut dec = AeadDecryptCtx::new(&alg, &key, &iv, None).unwrap();
629        dec.set_aad(aad).unwrap();
630        let mut recovered = vec![0u8; ciphertext.len()];
631        let n2 = dec.update(&ciphertext, &mut recovered).unwrap();
632        dec.set_tag(&tag).unwrap();
633        dec.finalize(&mut recovered[n2..]).unwrap();
634        assert_eq!(&recovered[..n2], plaintext);
635
636        // Decrypt with corrupted tag — must fail.
637        let mut bad_tag = tag;
638        bad_tag[0] ^= 0xff;
639        let mut dec2 = AeadDecryptCtx::new(&alg, &key, &iv, None).unwrap();
640        dec2.set_aad(aad).unwrap();
641        let mut dummy = vec![0u8; ciphertext.len()];
642        dec2.update(&ciphertext, &mut dummy).unwrap();
643        dec2.set_tag(&bad_tag).unwrap();
644        assert!(dec2.finalize(&mut dummy).is_err());
645    }
646}