1use std::fmt;
16#[cfg(unix)]
17use std::fs;
18#[cfg(unix)]
19use std::fs::File;
20#[cfg(unix)]
21use std::io::Read;
22#[cfg(unix)]
23use std::io::Write;
24use std::path::Path;
25use std::path::PathBuf;
26#[cfg(unix)]
27use std::sync::Mutex;
28
29#[cfg(any(unix, test))]
30use aes_gcm::aead::Aead;
31#[cfg(any(unix, test))]
32use aes_gcm::aead::Generate;
33#[cfg(any(unix, test))]
34use aes_gcm::aead::KeyInit;
35#[cfg(any(unix, test))]
36use aes_gcm::aead::Nonce;
37#[cfg(any(unix, test))]
38use aes_gcm::aead::Payload;
39#[cfg(any(unix, test))]
40use aes_gcm::Aes256Gcm;
41use rocketmq_security_api::SecretAccess;
42use rocketmq_security_api::SecretMaterial;
43use rocketmq_security_api::SecretName;
44use rocketmq_security_api::SecretPersistence;
45use rocketmq_security_api::SecretProvider;
46use rocketmq_security_api::SecretProviderCapabilities;
47use rocketmq_security_api::SecretProviderId;
48use rocketmq_security_api::SecretVersion;
49use rocketmq_security_api::SecretVersioning;
50#[cfg(unix)]
51use rocketmq_security_api::SecurityContractViolation;
52use rocketmq_security_api::SecurityOperation;
53use rocketmq_security_api::SecurityProviderError;
54use rocketmq_security_api::SecurityProviderFailure;
55use rocketmq_security_api::VersionedSecret;
56
57use crate::AuthFailureKind;
58use crate::AuthOperation;
59use crate::AuthServiceError;
60use crate::AuthServiceResult;
61
62#[cfg(any(unix, test))]
63const ENVELOPE_MAGIC: &[u8; 8] = b"RMQSEC01";
64#[cfg(any(unix, test))]
65const NONCE_LENGTH: usize = 12;
66#[cfg(any(unix, test))]
67const LENGTH_FIELD: usize = 8;
68#[cfg(any(unix, test))]
69const ENVELOPE_PREFIX_LENGTH: usize = ENVELOPE_MAGIC.len() + NONCE_LENGTH + LENGTH_FIELD;
70#[cfg(any(unix, test))]
71const MAX_SECRET_LENGTH: usize = 1024 * 1024;
72#[cfg(any(unix, test))]
73const AUTH_TAG_LENGTH: usize = 16;
74
75pub struct EncryptedFileSecretProvider {
77 id: SecretProviderId,
78 #[cfg(unix)]
79 root: PathBuf,
80 #[cfg(unix)]
81 key: SecretMaterial,
82 #[cfg(unix)]
83 write_lock: Mutex<()>,
84}
85
86impl EncryptedFileSecretProvider {
87 pub fn new(id: SecretProviderId, root: impl Into<PathBuf>, key: SecretMaterial) -> AuthServiceResult<Self> {
94 if key.len() != 32 {
95 return Err(AuthServiceError::new(
96 AuthOperation::LoadSecret,
97 AuthFailureKind::InvalidInput,
98 ));
99 }
100 let root = root.into();
101 prepare_root(&root).map_err(auth_provider_error)?;
102 Ok(Self {
103 id,
104 #[cfg(unix)]
105 root,
106 #[cfg(unix)]
107 key,
108 #[cfg(unix)]
109 write_lock: Mutex::new(()),
110 })
111 }
112
113 #[cfg(unix)]
114 fn read_version(
115 &self,
116 name: &SecretName,
117 version: SecretVersion,
118 ) -> Result<VersionedSecret, SecurityProviderError> {
119 let path = version_path(&self.root.join(name.as_str()), version);
120 let envelope = read_restricted_file(&path)?;
121 let plaintext = decrypt_envelope(&self.key, name, version, &envelope)?;
122 let material = SecretMaterial::new(plaintext).map_err(|source| {
123 SecurityProviderError::caused_by(
124 SecurityProviderFailure::InvalidData,
125 SecurityOperation::ReadSecret,
126 source,
127 )
128 })?;
129 Ok(VersionedSecret::new(material, Some(version)))
130 }
131
132 #[cfg(unix)]
133 fn write_version(
134 &self,
135 name: &SecretName,
136 material: &SecretMaterial,
137 expected_version: Option<SecretVersion>,
138 ) -> Result<SecretVersion, SecurityProviderError> {
139 if material.len() > MAX_SECRET_LENGTH {
140 return Err(provider_error(
141 SecurityProviderFailure::InvalidData,
142 SecurityOperation::WriteSecret,
143 ));
144 }
145 let directory = self.root.join(name.as_str());
146 prepare_secret_directory(&directory)?;
147 let current = latest_version(&directory)?;
148 let next = match (current, expected_version) {
149 (None, None) => SecretVersion::new(1),
150 (Some(current), Some(expected)) if current == expected => SecretVersion::new(
151 current
152 .get()
153 .checked_add(1)
154 .ok_or_else(|| provider_error(SecurityProviderFailure::Conflict, SecurityOperation::WriteSecret))?,
155 ),
156 _ => {
157 return Err(provider_error(
158 SecurityProviderFailure::Conflict,
159 SecurityOperation::WriteSecret,
160 ));
161 }
162 };
163 let envelope = encrypt_envelope(&self.key, name, next, material.expose_secret())?;
164 publish_version(&directory, next, &envelope)?;
165 Ok(next)
166 }
167}
168
169impl fmt::Debug for EncryptedFileSecretProvider {
170 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171 f.debug_struct("EncryptedFileSecretProvider")
172 .field("id", &self.id)
173 .field("root", &"[REDACTED]")
174 .field("key", &"[REDACTED]")
175 .finish()
176 }
177}
178
179impl SecretProvider for EncryptedFileSecretProvider {
180 fn id(&self) -> &SecretProviderId {
181 &self.id
182 }
183
184 fn capabilities(&self) -> SecretProviderCapabilities {
185 SecretProviderCapabilities::new(
186 SecretAccess::ReadWrite,
187 SecretPersistence::EncryptedLocalStorage,
188 SecretVersioning::Optimistic,
189 )
190 }
191
192 fn read(&self, name: &SecretName) -> Result<VersionedSecret, SecurityProviderError> {
193 #[cfg(windows)]
194 {
195 let _ = name;
196 Err(provider_error(
197 SecurityProviderFailure::Unsupported,
198 SecurityOperation::ReadSecret,
199 ))
200 }
201 #[cfg(unix)]
202 {
203 let directory = self.root.join(name.as_str());
204 check_restricted_directory(&directory)?;
205 let version = latest_version(&directory)?
206 .ok_or_else(|| provider_error(SecurityProviderFailure::NotFound, SecurityOperation::ReadSecret))?;
207 self.read_version(name, version)
208 }
209 }
210
211 fn write(
212 &self,
213 name: &SecretName,
214 material: SecretMaterial,
215 expected_version: Option<SecretVersion>,
216 ) -> Result<SecretVersion, SecurityProviderError> {
217 #[cfg(windows)]
218 {
219 let _ = (name, material, expected_version);
220 Err(provider_error(
221 SecurityProviderFailure::Unsupported,
222 SecurityOperation::WriteSecret,
223 ))
224 }
225 #[cfg(unix)]
226 {
227 let _guard = self
228 .write_lock
229 .lock()
230 .map_err(|_| provider_error(SecurityProviderFailure::Unavailable, SecurityOperation::WriteSecret))?;
231 self.write_version(name, &material, expected_version)
232 }
233 }
234}
235
236#[cfg(windows)]
237fn prepare_root(_root: &Path) -> Result<(), SecurityProviderError> {
238 Err(provider_error(
239 SecurityProviderFailure::Unsupported,
240 SecurityOperation::InspectSecret,
241 ))
242}
243
244#[cfg(unix)]
245fn prepare_root(root: &Path) -> Result<(), SecurityProviderError> {
246 use std::os::unix::fs::DirBuilderExt;
247
248 if !root.exists() {
249 let mut builder = fs::DirBuilder::new();
250 builder.recursive(true).mode(0o700);
251 builder.create(root).map_err(|source| {
252 SecurityProviderError::caused_by(
253 SecurityProviderFailure::Unavailable,
254 SecurityOperation::InspectSecret,
255 source,
256 )
257 })?;
258 }
259 check_restricted_directory(root)
260}
261
262#[cfg(unix)]
263fn prepare_secret_directory(directory: &Path) -> Result<(), SecurityProviderError> {
264 use std::os::unix::fs::DirBuilderExt;
265
266 if !directory.exists() {
267 let mut builder = fs::DirBuilder::new();
268 builder.mode(0o700);
269 match builder.create(directory) {
270 Ok(()) => {}
271 Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => {}
272 Err(source) => {
273 return Err(SecurityProviderError::caused_by(
274 SecurityProviderFailure::Unavailable,
275 SecurityOperation::WriteSecret,
276 source,
277 ));
278 }
279 }
280 }
281 check_restricted_directory(directory)
282}
283
284#[cfg(unix)]
285fn check_restricted_directory(path: &Path) -> Result<(), SecurityProviderError> {
286 use std::os::unix::fs::PermissionsExt;
287
288 let metadata = fs::symlink_metadata(path).map_err(|source| {
289 SecurityProviderError::caused_by(
290 SecurityProviderFailure::NotFound,
291 SecurityOperation::InspectSecret,
292 source,
293 )
294 })?;
295 if metadata.file_type().is_symlink() || !metadata.is_dir() {
296 return Err(SecurityProviderError::contract(
297 SecurityOperation::InspectSecret,
298 SecurityContractViolation::SecretStoragePermissionsInsecure,
299 ));
300 }
301 if metadata.permissions().mode() & 0o077 != 0 {
302 return Err(SecurityProviderError::contract(
303 SecurityOperation::InspectSecret,
304 SecurityContractViolation::SecretStoragePermissionsInsecure,
305 ));
306 }
307 Ok(())
308}
309
310#[cfg(unix)]
311fn latest_version(directory: &Path) -> Result<Option<SecretVersion>, SecurityProviderError> {
312 let entries = fs::read_dir(directory).map_err(|source| {
313 SecurityProviderError::caused_by(
314 SecurityProviderFailure::NotFound,
315 SecurityOperation::InspectSecret,
316 source,
317 )
318 })?;
319 let mut latest = None;
320 for entry in entries {
321 let entry = entry.map_err(|source| {
322 SecurityProviderError::caused_by(
323 SecurityProviderFailure::Unavailable,
324 SecurityOperation::InspectSecret,
325 source,
326 )
327 })?;
328 let file_name = entry.file_name();
329 let Some(file_name) = file_name.to_str() else {
330 return Err(provider_error(
331 SecurityProviderFailure::InvalidData,
332 SecurityOperation::InspectSecret,
333 ));
334 };
335 let Some(raw_version) = file_name.strip_suffix(".secret") else {
336 if file_name.ends_with(".tmp") {
337 continue;
338 }
339 return Err(provider_error(
340 SecurityProviderFailure::InvalidData,
341 SecurityOperation::InspectSecret,
342 ));
343 };
344 if raw_version.len() != 20 || !raw_version.bytes().all(|byte| byte.is_ascii_digit()) {
345 return Err(provider_error(
346 SecurityProviderFailure::InvalidData,
347 SecurityOperation::InspectSecret,
348 ));
349 }
350 let version = raw_version.parse::<u64>().map_err(|source| {
351 SecurityProviderError::caused_by(
352 SecurityProviderFailure::InvalidData,
353 SecurityOperation::InspectSecret,
354 source,
355 )
356 })?;
357 if version == 0 {
358 return Err(provider_error(
359 SecurityProviderFailure::InvalidData,
360 SecurityOperation::InspectSecret,
361 ));
362 }
363 let metadata = entry.metadata().map_err(|source| {
364 SecurityProviderError::caused_by(
365 SecurityProviderFailure::Unavailable,
366 SecurityOperation::InspectSecret,
367 source,
368 )
369 })?;
370 if !metadata.is_file() {
371 return Err(provider_error(
372 SecurityProviderFailure::InvalidData,
373 SecurityOperation::InspectSecret,
374 ));
375 }
376 let version = SecretVersion::new(version);
377 latest = Some(latest.map_or(version, |current: SecretVersion| current.max(version)));
378 }
379 Ok(latest)
380}
381
382#[cfg(unix)]
383fn version_path(directory: &Path, version: SecretVersion) -> PathBuf {
384 directory.join(format!("{:020}.secret", version.get()))
385}
386
387#[cfg(unix)]
388fn read_restricted_file(path: &Path) -> Result<Vec<u8>, SecurityProviderError> {
389 use std::os::unix::fs::PermissionsExt;
390
391 let link_metadata = fs::symlink_metadata(path).map_err(|source| {
392 SecurityProviderError::caused_by(SecurityProviderFailure::NotFound, SecurityOperation::ReadSecret, source)
393 })?;
394 if link_metadata.file_type().is_symlink() {
395 return Err(SecurityProviderError::contract(
396 SecurityOperation::ReadSecret,
397 SecurityContractViolation::SecretStoragePermissionsInsecure,
398 ));
399 }
400 let mut file = File::open(path).map_err(|source| {
401 SecurityProviderError::caused_by(
402 SecurityProviderFailure::Unavailable,
403 SecurityOperation::ReadSecret,
404 source,
405 )
406 })?;
407 let metadata = file.metadata().map_err(|source| {
408 SecurityProviderError::caused_by(
409 SecurityProviderFailure::Unavailable,
410 SecurityOperation::ReadSecret,
411 source,
412 )
413 })?;
414 if !metadata.is_file() || metadata.permissions().mode() & 0o077 != 0 {
415 return Err(SecurityProviderError::contract(
416 SecurityOperation::ReadSecret,
417 SecurityContractViolation::SecretStoragePermissionsInsecure,
418 ));
419 }
420 let maximum = ENVELOPE_PREFIX_LENGTH + MAX_SECRET_LENGTH + AUTH_TAG_LENGTH;
421 if metadata.len() > maximum as u64 {
422 return Err(provider_error(
423 SecurityProviderFailure::InvalidData,
424 SecurityOperation::ReadSecret,
425 ));
426 }
427 let mut envelope = Vec::with_capacity(metadata.len() as usize);
428 (&mut file)
429 .take(maximum as u64 + 1)
430 .read_to_end(&mut envelope)
431 .map_err(|source| {
432 SecurityProviderError::caused_by(
433 SecurityProviderFailure::Unavailable,
434 SecurityOperation::ReadSecret,
435 source,
436 )
437 })?;
438 if envelope.len() > maximum {
439 return Err(provider_error(
440 SecurityProviderFailure::InvalidData,
441 SecurityOperation::ReadSecret,
442 ));
443 }
444 Ok(envelope)
445}
446
447#[cfg(any(unix, test))]
448fn associated_data(name: &SecretName, version: SecretVersion) -> Vec<u8> {
449 let mut data = Vec::with_capacity(32 + name.as_str().len());
450 data.extend_from_slice(b"rocketmq-secret-envelope-v1\0");
451 data.extend_from_slice(name.as_str().as_bytes());
452 data.push(0);
453 data.extend_from_slice(&version.get().to_be_bytes());
454 data
455}
456
457#[cfg(any(unix, test))]
458fn encrypt_envelope(
459 key: &SecretMaterial,
460 name: &SecretName,
461 version: SecretVersion,
462 plaintext: &[u8],
463) -> Result<Vec<u8>, SecurityProviderError> {
464 let cipher = Aes256Gcm::new_from_slice(key.expose_secret()).map_err(|source| {
465 SecurityProviderError::caused_by(
466 SecurityProviderFailure::InvalidData,
467 SecurityOperation::EncryptSecret,
468 source,
469 )
470 })?;
471 let nonce = Nonce::<Aes256Gcm>::generate();
472 let aad = associated_data(name, version);
473 let ciphertext = cipher
474 .encrypt(
475 &nonce,
476 Payload {
477 msg: plaintext,
478 aad: &aad,
479 },
480 )
481 .map_err(|source| {
482 SecurityProviderError::caused_by(
483 SecurityProviderFailure::OperationFailed,
484 SecurityOperation::EncryptSecret,
485 source,
486 )
487 })?;
488 let mut envelope = Vec::with_capacity(ENVELOPE_PREFIX_LENGTH + ciphertext.len());
489 envelope.extend_from_slice(ENVELOPE_MAGIC);
490 envelope.extend_from_slice(&nonce);
491 envelope.extend_from_slice(&(ciphertext.len() as u64).to_be_bytes());
492 envelope.extend_from_slice(&ciphertext);
493 Ok(envelope)
494}
495
496#[cfg(any(unix, test))]
497fn decrypt_envelope(
498 key: &SecretMaterial,
499 name: &SecretName,
500 version: SecretVersion,
501 envelope: &[u8],
502) -> Result<Vec<u8>, SecurityProviderError> {
503 if envelope.len() < ENVELOPE_PREFIX_LENGTH + AUTH_TAG_LENGTH || &envelope[..ENVELOPE_MAGIC.len()] != ENVELOPE_MAGIC
504 {
505 return Err(provider_error(
506 SecurityProviderFailure::InvalidData,
507 SecurityOperation::DecryptSecret,
508 ));
509 }
510 let nonce_start = ENVELOPE_MAGIC.len();
511 let nonce_end = nonce_start + NONCE_LENGTH;
512 let length_end = nonce_end + LENGTH_FIELD;
513 let nonce = Nonce::<Aes256Gcm>::try_from(&envelope[nonce_start..nonce_end])
514 .map_err(|_| provider_error(SecurityProviderFailure::InvalidData, SecurityOperation::DecryptSecret))?;
515 let encoded_length = u64::from_be_bytes(
516 envelope[nonce_end..length_end]
517 .try_into()
518 .map_err(|_| provider_error(SecurityProviderFailure::InvalidData, SecurityOperation::DecryptSecret))?,
519 );
520 let ciphertext = &envelope[length_end..];
521 if encoded_length != ciphertext.len() as u64 || ciphertext.len() > MAX_SECRET_LENGTH + AUTH_TAG_LENGTH {
522 return Err(provider_error(
523 SecurityProviderFailure::InvalidData,
524 SecurityOperation::DecryptSecret,
525 ));
526 }
527 let cipher = Aes256Gcm::new_from_slice(key.expose_secret()).map_err(|source| {
528 SecurityProviderError::caused_by(
529 SecurityProviderFailure::InvalidData,
530 SecurityOperation::DecryptSecret,
531 source,
532 )
533 })?;
534 let aad = associated_data(name, version);
535 cipher
536 .decrypt(
537 &nonce,
538 Payload {
539 msg: ciphertext,
540 aad: &aad,
541 },
542 )
543 .map_err(|source| {
544 SecurityProviderError::caused_by(
545 SecurityProviderFailure::InvalidData,
546 SecurityOperation::DecryptSecret,
547 source,
548 )
549 })
550}
551
552#[cfg(unix)]
553fn publish_version(directory: &Path, version: SecretVersion, envelope: &[u8]) -> Result<(), SecurityProviderError> {
554 use std::os::unix::fs::OpenOptionsExt;
555 use std::os::unix::fs::PermissionsExt;
556
557 let nonce_start = ENVELOPE_MAGIC.len();
558 let nonce_end = nonce_start + NONCE_LENGTH;
559 let temporary_path = directory.join(format!(
560 ".{:020}-{}.tmp",
561 version.get(),
562 hex::encode(&envelope[nonce_start..nonce_end])
563 ));
564 let final_path = version_path(directory, version);
565 let mut options = fs::OpenOptions::new();
566 options.write(true).create_new(true).mode(0o600);
567 let mut file = options.open(&temporary_path).map_err(|source| {
568 SecurityProviderError::caused_by(
569 SecurityProviderFailure::Unavailable,
570 SecurityOperation::WriteSecret,
571 source,
572 )
573 })?;
574 let write_result = (|| {
575 file.write_all(envelope).map_err(|source| {
576 SecurityProviderError::caused_by(
577 SecurityProviderFailure::Unavailable,
578 SecurityOperation::WriteSecret,
579 source,
580 )
581 })?;
582 file.sync_all().map_err(|source| {
583 SecurityProviderError::caused_by(
584 SecurityProviderFailure::Unavailable,
585 SecurityOperation::SynchronizeSecret,
586 source,
587 )
588 })?;
589 file.set_permissions(fs::Permissions::from_mode(0o400))
590 .map_err(|source| {
591 SecurityProviderError::caused_by(
592 SecurityProviderFailure::Unavailable,
593 SecurityOperation::WriteSecret,
594 source,
595 )
596 })?;
597 fs::hard_link(&temporary_path, &final_path).map_err(|source| {
598 if source.kind() == std::io::ErrorKind::AlreadyExists {
599 SecurityProviderError::caused_by(
600 SecurityProviderFailure::Conflict,
601 SecurityOperation::WriteSecret,
602 source,
603 )
604 } else {
605 SecurityProviderError::caused_by(
606 SecurityProviderFailure::Unavailable,
607 SecurityOperation::WriteSecret,
608 source,
609 )
610 }
611 })?;
612 sync_directory(directory)
613 })();
614 drop(file);
615 let _ = fs::remove_file(&temporary_path);
616 write_result
617}
618
619#[cfg(unix)]
620fn sync_directory(directory: &Path) -> Result<(), SecurityProviderError> {
621 File::open(directory)
622 .and_then(|file| file.sync_all())
623 .map_err(|source| {
624 SecurityProviderError::caused_by(
625 SecurityProviderFailure::Unavailable,
626 SecurityOperation::SynchronizeSecret,
627 source,
628 )
629 })
630}
631
632fn provider_error(kind: SecurityProviderFailure, operation: SecurityOperation) -> SecurityProviderError {
633 SecurityProviderError::new(kind, operation)
634}
635
636fn auth_provider_error(source: SecurityProviderError) -> AuthServiceError {
637 AuthServiceError::provider(AuthOperation::LoadSecret, source)
638}
639
640#[cfg(test)]
641mod tests {
642 use std::error::Error;
643
644 use super::*;
645
646 fn material(bytes: &[u8]) -> SecretMaterial {
647 SecretMaterial::new(bytes.to_vec()).unwrap()
648 }
649
650 #[test]
651 fn envelope_authenticates_name_version_and_ciphertext() {
652 let key = material(&[9; 32]);
653 let name = SecretName::new("broker-admin-key").unwrap();
654 let version = SecretVersion::new(7);
655 let mut envelope = encrypt_envelope(&key, &name, version, b"not-written-in-clear").unwrap();
656 assert!(!envelope.windows(20).any(|window| window == b"not-written-in-clear"));
657 assert_eq!(
658 decrypt_envelope(&key, &name, version, &envelope).unwrap(),
659 b"not-written-in-clear"
660 );
661
662 envelope[ENVELOPE_PREFIX_LENGTH] ^= 1;
663 assert_eq!(
664 decrypt_envelope(&key, &name, version, &envelope).unwrap_err().kind(),
665 SecurityProviderFailure::InvalidData
666 );
667 let other_name = SecretName::new("other-key").unwrap();
668 assert_eq!(
669 decrypt_envelope(&key, &other_name, version, &envelope)
670 .unwrap_err()
671 .kind(),
672 SecurityProviderFailure::InvalidData
673 );
674 }
675
676 #[test]
677 fn cipher_failures_retain_typed_sources_without_rendering_them() {
678 let name = SecretName::new("broker-admin-key").unwrap();
679 let version = SecretVersion::new(7);
680 let invalid_key = material(&[9; 31]);
681 let key_error = encrypt_envelope(&invalid_key, &name, version, b"secret")
682 .expect_err("an invalid AES-256 key length must fail");
683 assert_eq!(key_error.kind(), SecurityProviderFailure::InvalidData);
684 assert_eq!(key_error.operation(), SecurityOperation::EncryptSecret);
685 assert!(Error::source(&key_error)
686 .and_then(|source| source.downcast_ref::<aes_gcm::aead::common::InvalidLength>())
687 .is_some());
688
689 let key = material(&[9; 32]);
690 let mut envelope = encrypt_envelope(&key, &name, version, b"secret").unwrap();
691 envelope[ENVELOPE_PREFIX_LENGTH] ^= 1;
692 let decrypt_error = decrypt_envelope(&key, &name, version, &envelope)
693 .expect_err("modified ciphertext must fail authentication");
694 assert_eq!(decrypt_error.kind(), SecurityProviderFailure::InvalidData);
695 assert_eq!(decrypt_error.operation(), SecurityOperation::DecryptSecret);
696 assert!(Error::source(&decrypt_error)
697 .and_then(|source| source.downcast_ref::<aes_gcm::Error>())
698 .is_some());
699
700 for error in [&key_error, &decrypt_error] {
701 assert!(!error.to_string().contains("InvalidLength"));
702 assert!(!format!("{error:?}").contains("aead::Error"));
703 }
704 }
705}