diff --git a/Cargo.lock b/Cargo.lock index cd5167ae6e511b08385ad35ced2cdc4fc70e47ca..028b93702fe5ac45effcea620fdafde8de5b7e61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1746,6 +1746,7 @@ dependencies = [ "hex", "log", "md-5", + "nethsm-backup", "nethsm-sdk-rs", "nethsm-tests", "p224", diff --git a/Cargo.toml b/Cargo.toml index 5b47045f679288ccc79bb7a6939f58d17038e4a2..1551e230a8ce9cb07d22459656d249ba608a6d8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ chrono = "0.4.38" clap = { version = "4.5.19", features = ["derive", "env"] } ed25519-dalek = "2.1.1" nethsm = { path = "nethsm", version = "0.7.1" } +nethsm-backup = { path = "nethsm-backup" } nethsm-config = { path = "nethsm-config", version = "0.2.1" } rand = "0.8.5" rsa = "0.9.6" diff --git a/nethsm/Cargo.toml b/nethsm/Cargo.toml index b0b88a2f259e556d2985b9505042dc0854b87c66..9c08fb345754073343d53fe04487b61d60d39eea 100644 --- a/nethsm/Cargo.toml +++ b/nethsm/Cargo.toml @@ -20,6 +20,7 @@ email_address = "0.2.9" hex = { version = "0.4.3", features = ["serde"] } log = "0.4.22" md-5 = "0.10.6" +nethsm-backup.workspace = true nethsm-sdk-rs = "1.1.1" p224 = { version = "0.13.2", features = ["pem", "pkcs8"] } p256 = { version = "0.13.2", features = ["pem", "pkcs8"] } diff --git a/nethsm/src/lib.rs b/nethsm/src/lib.rs index 6bb7e4502ac3de45d5d79d77ed5821e9881bbab0..7869e3064a4066f7604c0a26472430fe49cee289 100644 --- a/nethsm/src/lib.rs +++ b/nethsm/src/lib.rs @@ -88,6 +88,7 @@ use std::cell::RefCell; use std::collections::HashMap; use std::fmt::Display; +use std::io::Read; use std::net::Ipv4Addr; use std::str::FromStr; use std::sync::Arc; @@ -371,6 +372,71 @@ impl FromStr for Url { } } +/// Validates a [backup]. +/// +/// Parses a previously created backup file. If `passphrase` is +/// [`Some`], additionally decrypts the backup and verifies the +/// encrypted backup version number. +/// +/// # Errors +/// +/// Returns an [`nethsm_backup::Error`] if validating a [backup] fails: +/// * the magic number is missing in the file +/// * the version number is unknown +/// * the provided passphrase is incorrect +/// +/// # Examples +/// +/// ```no_run +/// use nethsm::{validate_backup, ConnectionSecurity, Credentials, NetHsm, Passphrase}; +/// +/// # fn main() -> testresult::TestResult { +/// // create a connection with a user in the Backup role +/// let nethsm = NetHsm::new( +/// "https://example.org/api/v1".try_into()?, +/// ConnectionSecurity::Unsafe, +/// Some(Credentials::new( +/// "backup1".parse()?, +/// Some(Passphrase::new("passphrase".to_string())), +/// )), +/// None, +/// None, +/// )?; +/// +/// // create a backup and write it to file +/// std::fs::write("nethsm.bkp", nethsm.backup()?)?; +/// +/// // check for consistency only +/// validate_backup(&mut std::fs::File::open("nethsm.bkp")?, None)?; +/// +/// // check for correct passphrase by decrypting and validating the encrypted backup version +/// validate_backup( +/// &mut std::fs::File::open("nethsm.bkp")?, +/// Passphrase::new("a sample password".into()), +/// )?; +/// # Ok(()) +/// # } +/// ``` +/// [backup]: https://docs.nitrokey.com/nethsm/administration#backup +pub fn validate_backup( + reader: &mut impl Read, + passphrase: impl Into<Option<Passphrase>>, +) -> Result<(), nethsm_backup::Error> { + let passphrase = passphrase.into(); + let backup = nethsm_backup::Backup::parse(reader)?; + if let Some(passphrase) = passphrase { + let decryptor = backup.decrypt(passphrase.expose_borrowed().as_bytes())?; + let version = decryptor.version()?; + if version.len() != 1 || version[0] != 0 { + return Err(nethsm_backup::Error::BadVersion { + highest_supported_version: 0, + backup_version: version, + }); + } + } + Ok(()) +} + /// A network connection to a NetHSM. /// /// Defines a network configuration for the connection and a list of user [`Credentials`] that can