From da9ead0ca60e8d52d1d4612464c28af2328886d2 Mon Sep 17 00:00:00 2001 From: donmor Date: Tue, 14 May 2024 10:01:18 +0800 Subject: [PATCH 01/12] Update mount.rs --- src/commands/mount.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/commands/mount.rs b/src/commands/mount.rs index 9414c77f2..b092661ed 100644 --- a/src/commands/mount.rs +++ b/src/commands/mount.rs @@ -312,6 +312,16 @@ fn devs_str_sbs_from_device( } } +fn parse_passphrase_file_from_mount_options(options: impl AsRef) -> Option { + options + .as_ref() + .split(",") + .fold(None, |_, next| match next { + x if x.starts_with("passphrase_file") => Some(PathBuf::from(x.split("=").nth(1).unwrap().to_string())), + _ => None, + }) +} + fn cmd_mount_inner(opt: Cli) -> anyhow::Result<()> { // Grab the udev information once let udev_info = udev_bcachefs_info()?; @@ -359,6 +369,18 @@ fn cmd_mount_inner(opt: Cli) -> anyhow::Result<()> { true } } + } else if let Some(passphrase_file) = parse_passphrase_file_from_mount_options(&opt.options) { + match key::read_from_passphrase_file(&block_devices_to_mount[0], passphrase_file.as_path()) { + Ok(()) => { + // Decryption succeeded + false + } + Err(err) => { + // Decryption failed, fall back to unlock_policy + error!("Failed to decrypt using passphrase_file: {}", err); + true + } + } } else { // No passphrase_file specified, fall back to unlock_policy true From 3e4bf884c0cfc235a1f923efd6f19472dae137d4 Mon Sep 17 00:00:00 2001 From: donmor Date: Tue, 14 May 2024 10:05:44 +0800 Subject: [PATCH 02/12] Update bcachefs.8 --- bcachefs.8 | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/bcachefs.8 b/bcachefs.8 index 8ff51a7da..7f1c89189 100644 --- a/bcachefs.8 +++ b/bcachefs.8 @@ -380,6 +380,12 @@ is the path where the filesystem should be mounted. If not set, then the filesys but all steps preceding mounting the filesystem (e.g. asking for passphrase) will still be performed. .Pp the options are as follows: .Bl -tag -width Ds +.It Fl f , Fl -passphrase-file Ns = Ns Ar passphrase_file +Path to passphrase/key file +.sp +Precedes key_location/unlock_policy: if the filesystem can be decrypted by the specified passphrase +file, it is decrypted. (i.e. Regardless if "fail" is specified for key_location/unlock_policy.) +.El .It Fl o Ar options Mount options provided as a comma-separated list. See user guide for complete list. .Bl -tag -width Ds -compact @@ -393,6 +399,8 @@ Run fsck during mount Fix errors without asking during fsck .It Cm read_only Mount in read only mode +.It Cm passphrase_file Ns = Ns Ar passphrase_file +Path to passphrase/key file .It Cm version_upgrade .El .It Fl k , Fl -key-location Ns = Ns ( Cm fail | wait | ask ) @@ -408,6 +416,7 @@ prompt the user for password. .El .It Fl c , Fl -colorize Ns = Ns ( Cm true | false ) Force color on/off. Default: auto-detect TTY +.El .It Fl v Be verbose. Can be specified more than once. .El From 9cf9e1b3fd97f29c890603d8843e3310ddcd6e6d Mon Sep 17 00:00:00 2001 From: Roland Vet Date: Fri, 17 May 2024 16:15:45 +0200 Subject: [PATCH 03/12] Refactor master key unlocking Biggest bonus is the simplification of the if-else-if-else flow. Instead we look for the Ok(()) returned from key::unlock_master_key_using_pasphrase_file and return early if we find it. Signed-off-by: Roland Vet --- src/commands/mount.rs | 63 ++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/src/commands/mount.rs b/src/commands/mount.rs index b092661ed..70cd9c775 100644 --- a/src/commands/mount.rs +++ b/src/commands/mount.rs @@ -354,41 +354,9 @@ fn cmd_mount_inner(opt: Cli) -> anyhow::Result<()> { if block_devices_to_mount.len() == 0 { Err(anyhow::anyhow!("No device found from specified parameters"))?; } - // Check if the filesystem's master key is encrypted + // Check if the filesystem is encrypted and the master key is locked if unsafe { bcachefs::bch2_sb_is_encrypted_and_locked(block_devices_to_mount[0].sb) } { - // First by password_file, if available - let fallback_to_unlock_policy = if let Some(passphrase_file) = &opt.passphrase_file { - match key::read_from_passphrase_file(&block_devices_to_mount[0], passphrase_file.as_path()) { - Ok(()) => { - // Decryption succeeded - false - } - Err(err) => { - // Decryption failed, fall back to unlock_policy - error!("Failed to decrypt using passphrase_file: {}", err); - true - } - } - } else if let Some(passphrase_file) = parse_passphrase_file_from_mount_options(&opt.options) { - match key::read_from_passphrase_file(&block_devices_to_mount[0], passphrase_file.as_path()) { - Ok(()) => { - // Decryption succeeded - false - } - Err(err) => { - // Decryption failed, fall back to unlock_policy - error!("Failed to decrypt using passphrase_file: {}", err); - true - } - } - } else { - // No passphrase_file specified, fall back to unlock_policy - true - }; - // If decryption by key_file was unsuccesful, prompt for passphrase (or follow key_policy) - if fallback_to_unlock_policy { - key::apply_key_unlocking_policy(&block_devices_to_mount[0], opt.unlock_policy)?; - }; + attempt_unlock_master_key(block_devices_to_mount, &opt.passphrase_file, &opt.options, opt.unlock_policy)?; } if let Some(mountpoint) = opt.mountpoint { @@ -411,6 +379,33 @@ fn cmd_mount_inner(opt: Cli) -> anyhow::Result<()> { Ok(()) } +fn attempt_unlock_master_key(block_devices_to_mount: Vec, opt_passphrase_file: &Option, options: &String, unlock_policy: UnlockPolicy) -> Result<(), anyhow::Error> { + // Unlock by passphrase_file specified by cli + if let Some(passphrase_file) = opt_passphrase_file{ + debug!("Attempting to unlock the master key with the passphrase file specified by cli"); + match key::read_from_passphrase_file(&block_devices_to_mount[0], passphrase_file.as_path()) { + Ok(()) => return Ok(()), // Unlock succeeded, return early. + Err(err) => { + // Unlock failed, print error and continue. + error!("Failed to decrypt using passphrase_file: {}", err); + } + } + } + // Unlock by passphrase_file specified by mount options + if let Some(passphrase_file) = &parse_passphrase_file_from_mount_options(options) { + debug!("Attempting to unlock the master key with the passphrase_file specified in the mount options"); + match key::read_from_passphrase_file(&block_devices_to_mount[0], passphrase_file.as_path()) { + Ok(()) => return Ok(()), // Unlock succeeded, return early. + Err(err) => { + // Unlock failed, print error and continue. + error!("Failed to decrypt using passphrase_file: {}", err); + } + } + } + // Fallback to the key_policy + key::apply_key_unlocking_policy(&block_devices_to_mount[0], unlock_policy) +} + pub fn mount(mut argv: Vec, symlink_cmd: Option<&str>) -> i32 { // If the bcachefs tool is being called as "bcachefs mount dev ..." (as opposed to via a // symlink like "/usr/sbin/mount.bcachefs dev ...", then we need to pop the 0th argument From 661da2f8858c4b7e9ce9c2b66ea223eadbec0ae5 Mon Sep 17 00:00:00 2001 From: Roland Vet Date: Fri, 17 May 2024 22:09:36 +0200 Subject: [PATCH 04/12] Rename read_from_passphrase_file to unlock_master_key_using_passphrase_file Signed-off-by: Roland Vet --- src/commands/mount.rs | 4 ++-- src/key.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/commands/mount.rs b/src/commands/mount.rs index 70cd9c775..13c61a0be 100644 --- a/src/commands/mount.rs +++ b/src/commands/mount.rs @@ -383,7 +383,7 @@ fn attempt_unlock_master_key(block_devices_to_mount: Vec, opt_pas // Unlock by passphrase_file specified by cli if let Some(passphrase_file) = opt_passphrase_file{ debug!("Attempting to unlock the master key with the passphrase file specified by cli"); - match key::read_from_passphrase_file(&block_devices_to_mount[0], passphrase_file.as_path()) { + match key::unlock_master_key_using_passphrase_file(&block_devices_to_mount[0], passphrase_file.as_path()) { Ok(()) => return Ok(()), // Unlock succeeded, return early. Err(err) => { // Unlock failed, print error and continue. @@ -394,7 +394,7 @@ fn attempt_unlock_master_key(block_devices_to_mount: Vec, opt_pas // Unlock by passphrase_file specified by mount options if let Some(passphrase_file) = &parse_passphrase_file_from_mount_options(options) { debug!("Attempting to unlock the master key with the passphrase_file specified in the mount options"); - match key::read_from_passphrase_file(&block_devices_to_mount[0], passphrase_file.as_path()) { + match key::unlock_master_key_using_passphrase_file(&block_devices_to_mount[0], passphrase_file.as_path()) { Ok(()) => return Ok(()), // Unlock succeeded, return early. Err(err) => { // Unlock failed, print error and continue. diff --git a/src/key.rs b/src/key.rs index d0018805d..c46c7e343 100644 --- a/src/key.rs +++ b/src/key.rs @@ -150,7 +150,7 @@ fn unlock_master_key(sb: &bch_sb_handle, passphrase: &String) -> anyhow::Result< } } -pub fn read_from_passphrase_file(block_device: &bch_sb_handle, passphrase_file: &std::path::Path) -> anyhow::Result<()> { +pub fn unlock_master_key_using_passphrase_file(block_device: &bch_sb_handle, passphrase_file: &std::path::Path) -> anyhow::Result<()> { // Attempts to unlock the master key by password_file // Return true if unlock was successful, false otherwise info!("Attempting to unlock master key for filesystem {}, using password from file {}", block_device.sb().uuid(), passphrase_file.display()); From 0e5d899c6d6f8942bd656475e8111c3a44379427 Mon Sep 17 00:00:00 2001 From: Roland Vet Date: Thu, 16 May 2024 21:24:15 +0200 Subject: [PATCH 05/12] Fix comments in unlock_master_key_using_passphrase_file --- src/key.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/key.rs b/src/key.rs index c46c7e343..36baa1b35 100644 --- a/src/key.rs +++ b/src/key.rs @@ -151,10 +151,10 @@ fn unlock_master_key(sb: &bch_sb_handle, passphrase: &String) -> anyhow::Result< } pub fn unlock_master_key_using_passphrase_file(block_device: &bch_sb_handle, passphrase_file: &std::path::Path) -> anyhow::Result<()> { - // Attempts to unlock the master key by password_file - // Return true if unlock was successful, false otherwise - info!("Attempting to unlock master key for filesystem {}, using password from file {}", block_device.sb().uuid(), passphrase_file.display()); - // Read the contents of the password_file into a string + // Attempts to unlock the master key by passphrase_file + // Return OK() if unlock was successful, Err() otherwise + info!("Attempting to unlock master key for filesystem {}, using passphrase from file {}", block_device.sb().uuid(), passphrase_file.display()); + // Read the contents of the passphrase_file into a string let passphrase = fs::read_to_string(passphrase_file)?; // Call decrypt_master_key with the read string unlock_master_key(block_device, &passphrase) From 236ec6696474af9ed3367d6b10794f49f467717c Mon Sep 17 00:00:00 2001 From: donmor Date: Tue, 14 May 2024 10:01:18 +0800 Subject: [PATCH 06/12] Update mount.rs Add -o passphrase_file= mount option. This allows unlocking the volume in fstab. --- src/commands/mount.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/commands/mount.rs b/src/commands/mount.rs index 9414c77f2..b092661ed 100644 --- a/src/commands/mount.rs +++ b/src/commands/mount.rs @@ -312,6 +312,16 @@ fn devs_str_sbs_from_device( } } +fn parse_passphrase_file_from_mount_options(options: impl AsRef) -> Option { + options + .as_ref() + .split(",") + .fold(None, |_, next| match next { + x if x.starts_with("passphrase_file") => Some(PathBuf::from(x.split("=").nth(1).unwrap().to_string())), + _ => None, + }) +} + fn cmd_mount_inner(opt: Cli) -> anyhow::Result<()> { // Grab the udev information once let udev_info = udev_bcachefs_info()?; @@ -359,6 +369,18 @@ fn cmd_mount_inner(opt: Cli) -> anyhow::Result<()> { true } } + } else if let Some(passphrase_file) = parse_passphrase_file_from_mount_options(&opt.options) { + match key::read_from_passphrase_file(&block_devices_to_mount[0], passphrase_file.as_path()) { + Ok(()) => { + // Decryption succeeded + false + } + Err(err) => { + // Decryption failed, fall back to unlock_policy + error!("Failed to decrypt using passphrase_file: {}", err); + true + } + } } else { // No passphrase_file specified, fall back to unlock_policy true From a66759a9203c5985a95fc746d6985c5caee8e309 Mon Sep 17 00:00:00 2001 From: donmor Date: Tue, 14 May 2024 10:05:44 +0800 Subject: [PATCH 07/12] Update bcachefs.8 Update help message, adding descriptions for -o passphrase_file mounting option. --- bcachefs.8 | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/bcachefs.8 b/bcachefs.8 index 8ff51a7da..7f1c89189 100644 --- a/bcachefs.8 +++ b/bcachefs.8 @@ -380,6 +380,12 @@ is the path where the filesystem should be mounted. If not set, then the filesys but all steps preceding mounting the filesystem (e.g. asking for passphrase) will still be performed. .Pp the options are as follows: .Bl -tag -width Ds +.It Fl f , Fl -passphrase-file Ns = Ns Ar passphrase_file +Path to passphrase/key file +.sp +Precedes key_location/unlock_policy: if the filesystem can be decrypted by the specified passphrase +file, it is decrypted. (i.e. Regardless if "fail" is specified for key_location/unlock_policy.) +.El .It Fl o Ar options Mount options provided as a comma-separated list. See user guide for complete list. .Bl -tag -width Ds -compact @@ -393,6 +399,8 @@ Run fsck during mount Fix errors without asking during fsck .It Cm read_only Mount in read only mode +.It Cm passphrase_file Ns = Ns Ar passphrase_file +Path to passphrase/key file .It Cm version_upgrade .El .It Fl k , Fl -key-location Ns = Ns ( Cm fail | wait | ask ) @@ -408,6 +416,7 @@ prompt the user for password. .El .It Fl c , Fl -colorize Ns = Ns ( Cm true | false ) Force color on/off. Default: auto-detect TTY +.El .It Fl v Be verbose. Can be specified more than once. .El From 0fbc0f965e68438a1eefa0e6ed2e84df0d6863f1 Mon Sep 17 00:00:00 2001 From: donmor Date: Sat, 18 May 2024 16:54:43 +0800 Subject: [PATCH 08/12] Update mount.rs Do if-else simplification in another way, making less changes to the original --- src/commands/mount.rs | 50 +++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/src/commands/mount.rs b/src/commands/mount.rs index 13c61a0be..822dbc09f 100644 --- a/src/commands/mount.rs +++ b/src/commands/mount.rs @@ -356,7 +356,24 @@ fn cmd_mount_inner(opt: Cli) -> anyhow::Result<()> { } // Check if the filesystem is encrypted and the master key is locked if unsafe { bcachefs::bch2_sb_is_encrypted_and_locked(block_devices_to_mount[0].sb) } { - attempt_unlock_master_key(block_devices_to_mount, &opt.passphrase_file, &opt.options, opt.unlock_policy)?; + // First by password_file, if available + let fallback_to_unlock_policy = if let Some() = &opt.passphrase_file { + // Unlock by passphrase_file specified by cli + debug!("Attempting to unlock the master key with the passphrase file specified by cli"); + attempt_unlock_master_key_with_passphrase_file(&block_devices_to_mount[0], passphrase_file) + } else if let Some(passphrase_file) = &parse_passphrase_file_from_mount_options(&opt.options) { + // Unlock by passphrase_file specified by mount options + debug!("Attempting to unlock the master key with the passphrase_file specified in the mount options"); + attempt_unlock_master_key_with_passphrase_file(&block_devices_to_mount[0], passphrase_file) + } else { + // No passphrase_file specified, fall back to unlock_policy + true + }; + // If decryption by key_file was unsuccesful, prompt for passphrase (or follow key_policy) + if fallback_to_unlock_policy { + key::apply_key_unlocking_policy(&block_devices_to_mount[0], opt.unlock_policy)?; + }; + } if let Some(mountpoint) = opt.mountpoint { @@ -379,31 +396,18 @@ fn cmd_mount_inner(opt: Cli) -> anyhow::Result<()> { Ok(()) } -fn attempt_unlock_master_key(block_devices_to_mount: Vec, opt_passphrase_file: &Option, options: &String, unlock_policy: UnlockPolicy) -> Result<(), anyhow::Error> { - // Unlock by passphrase_file specified by cli - if let Some(passphrase_file) = opt_passphrase_file{ - debug!("Attempting to unlock the master key with the passphrase file specified by cli"); - match key::unlock_master_key_using_passphrase_file(&block_devices_to_mount[0], passphrase_file.as_path()) { - Ok(()) => return Ok(()), // Unlock succeeded, return early. - Err(err) => { - // Unlock failed, print error and continue. - error!("Failed to decrypt using passphrase_file: {}", err); - } +fn attempt_unlock_master_key_with_passphrase_file(block_device: bch_sb_handle, passphrase_file: PathBuf) -> bool { + match key::unlock_master_key_using_passphrase_file(block_device, passphrase_file.as_path()) { + Ok(()) => { + // Decryption succeeded + false } - } - // Unlock by passphrase_file specified by mount options - if let Some(passphrase_file) = &parse_passphrase_file_from_mount_options(options) { - debug!("Attempting to unlock the master key with the passphrase_file specified in the mount options"); - match key::unlock_master_key_using_passphrase_file(&block_devices_to_mount[0], passphrase_file.as_path()) { - Ok(()) => return Ok(()), // Unlock succeeded, return early. - Err(err) => { - // Unlock failed, print error and continue. - error!("Failed to decrypt using passphrase_file: {}", err); - } + Err(err) => { + // Decryption failed, fall back to unlock_policy + error!("Failed to decrypt using passphrase_file: {}", err); + true } } - // Fallback to the key_policy - key::apply_key_unlocking_policy(&block_devices_to_mount[0], unlock_policy) } pub fn mount(mut argv: Vec, symlink_cmd: Option<&str>) -> i32 { From bb82fe1c8018de3c645510c4dddaa93fb4e710ca Mon Sep 17 00:00:00 2001 From: donmor Date: Sat, 18 May 2024 17:01:25 +0800 Subject: [PATCH 09/12] Update mount.rs formatting --- src/commands/mount.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/commands/mount.rs b/src/commands/mount.rs index 822dbc09f..c24f61787 100644 --- a/src/commands/mount.rs +++ b/src/commands/mount.rs @@ -373,7 +373,6 @@ fn cmd_mount_inner(opt: Cli) -> anyhow::Result<()> { if fallback_to_unlock_policy { key::apply_key_unlocking_policy(&block_devices_to_mount[0], opt.unlock_policy)?; }; - } if let Some(mountpoint) = opt.mountpoint { From c0756a920f475666f8d74382ed8ca0445838edc9 Mon Sep 17 00:00:00 2001 From: donmor Date: Sat, 18 May 2024 22:54:19 +0800 Subject: [PATCH 10/12] Update mount.rs fix missing param --- src/commands/mount.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/mount.rs b/src/commands/mount.rs index c24f61787..2e2e84d07 100644 --- a/src/commands/mount.rs +++ b/src/commands/mount.rs @@ -357,7 +357,7 @@ fn cmd_mount_inner(opt: Cli) -> anyhow::Result<()> { // Check if the filesystem is encrypted and the master key is locked if unsafe { bcachefs::bch2_sb_is_encrypted_and_locked(block_devices_to_mount[0].sb) } { // First by password_file, if available - let fallback_to_unlock_policy = if let Some() = &opt.passphrase_file { + let fallback_to_unlock_policy = if let Some(passphrase_file) = &opt.passphrase_file { // Unlock by passphrase_file specified by cli debug!("Attempting to unlock the master key with the passphrase file specified by cli"); attempt_unlock_master_key_with_passphrase_file(&block_devices_to_mount[0], passphrase_file) From c33f40959ac0e4379c0c6b2a20074f809a046c1b Mon Sep 17 00:00:00 2001 From: donmor Date: Mon, 27 May 2024 13:09:08 +0800 Subject: [PATCH 11/12] Update mount.rs Solve conflicts --- src/commands/mount.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/commands/mount.rs b/src/commands/mount.rs index 2e2e84d07..c4e6e735b 100644 --- a/src/commands/mount.rs +++ b/src/commands/mount.rs @@ -354,8 +354,16 @@ fn cmd_mount_inner(opt: Cli) -> anyhow::Result<()> { if block_devices_to_mount.len() == 0 { Err(anyhow::anyhow!("No device found from specified parameters"))?; } - // Check if the filesystem is encrypted and the master key is locked - if unsafe { bcachefs::bch2_sb_is_encrypted_and_locked(block_devices_to_mount[0].sb) } { + let key_name = CString::new(format!( + "bcachefs:{}", + block_devices_to_mount[0].sb().uuid() + )) + .unwrap(); + + // Check if the filesystem's master key is encrypted and we don't have a key + if unsafe { bcachefs::bch2_sb_is_encrypted_and_locked(block_devices_to_mount[0].sb) } + && !key::check_for_key(&key_name)? + { // First by password_file, if available let fallback_to_unlock_policy = if let Some(passphrase_file) = &opt.passphrase_file { // Unlock by passphrase_file specified by cli From d3e26ce0e8bc89fd07b7b4974e69ecd1b17271ca Mon Sep 17 00:00:00 2001 From: donmor Date: Mon, 27 May 2024 13:54:05 +0800 Subject: [PATCH 12/12] Update mount.rs Fix incorrect args --- src/commands/mount.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/commands/mount.rs b/src/commands/mount.rs index fddf67e0c..25e702696 100644 --- a/src/commands/mount.rs +++ b/src/commands/mount.rs @@ -371,11 +371,11 @@ fn cmd_mount_inner(opt: Cli) -> anyhow::Result<()> { && !key::check_for_key(&key_name)? { // First by password_file, if available - let fallback_to_unlock_policy = if let Some(passphrase_file) = &opt.passphrase_file { + let fallback_to_unlock_policy = if let Some(passphrase_file) = opt.passphrase_file { // Unlock by passphrase_file specified by cli debug!("Attempting to unlock the master key with the passphrase file specified by cli"); attempt_unlock_master_key_with_passphrase_file(&block_devices_to_mount[0], passphrase_file) - } else if let Some(passphrase_file) = &parse_passphrase_file_from_mount_options(&opt.options) { + } else if let Some(passphrase_file) = parse_passphrase_file_from_mount_options(&opt.options) { // Unlock by passphrase_file specified by mount options debug!("Attempting to unlock the master key with the passphrase_file specified in the mount options"); attempt_unlock_master_key_with_passphrase_file(&block_devices_to_mount[0], passphrase_file) @@ -409,7 +409,7 @@ fn cmd_mount_inner(opt: Cli) -> anyhow::Result<()> { Ok(()) } -fn attempt_unlock_master_key_with_passphrase_file(block_device: bch_sb_handle, passphrase_file: PathBuf) -> bool { +fn attempt_unlock_master_key_with_passphrase_file(block_device: &bch_sb_handle, passphrase_file: PathBuf) -> bool { match key::read_from_passphrase_file(block_device, passphrase_file.as_path()) { Ok(()) => { // Decryption succeeded