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