From 639a6782bd104582c333ccab7728eefb078d8cf8 Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Fri, 22 Jan 2021 09:38:38 +0100 Subject: [PATCH] paperkey: move code to src/tools/paperkey.rs --- src/bin/proxmox_backup_client/key.rs | 225 +----------------------- src/tools.rs | 1 + src/tools/paperkey.rs | 250 +++++++++++++++++++++++++++ 3 files changed, 256 insertions(+), 220 deletions(-) create mode 100644 src/tools/paperkey.rs diff --git a/src/bin/proxmox_backup_client/key.rs b/src/bin/proxmox_backup_client/key.rs index bbc60141..ef3123bb 100644 --- a/src/bin/proxmox_backup_client/key.rs +++ b/src/bin/proxmox_backup_client/key.rs @@ -1,9 +1,6 @@ use std::path::PathBuf; -use std::io::Write; -use std::process::{Stdio, Command}; use anyhow::{bail, format_err, Error}; -use serde::{Deserialize, Serialize}; use serde_json::Value; use proxmox::api::api; @@ -20,6 +17,10 @@ use proxmox::sys::linux::tty; use proxmox::tools::fs::{file_get_contents, replace_file, CreateOptions}; use proxmox_backup::{ + tools::paperkey::{ + PaperkeyFormat, + generate_paper_key, + }, api2::types::{ PASSWORD_HINT_SCHEMA, KeyInfo, @@ -32,17 +33,6 @@ use proxmox_backup::{ tools, }; -#[api()] -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -/// Paperkey output format -pub enum PaperkeyFormat { - /// Format as Utf8 text. Includes QR codes as ascii-art. - Text, - /// Format as Html. Includes QR codes as png images. - Html, -} - pub const DEFAULT_ENCRYPTION_KEY_FILE_NAME: &str = "encryption-key.json"; pub const MASTER_PUBKEY_FILE_NAME: &str = "master-public.pem"; @@ -464,46 +454,7 @@ fn paper_key( let data = file_get_contents(&path)?; let data = String::from_utf8(data)?; - let (data, is_private_key) = if data.starts_with("-----BEGIN ENCRYPTED PRIVATE KEY-----\n") { - let lines: Vec = data - .lines() - .map(|s| s.trim_end()) - .filter(|s| !s.is_empty()) - .map(String::from) - .collect(); - - if !lines[lines.len()-1].starts_with("-----END ENCRYPTED PRIVATE KEY-----") { - bail!("unexpected key format"); - } - - if lines.len() < 20 { - bail!("unexpected key format"); - } - - (lines, true) - } else { - match serde_json::from_str::(&data) { - Ok(key_config) => { - let lines = serde_json::to_string_pretty(&key_config)? - .lines() - .map(String::from) - .collect(); - - (lines, false) - }, - Err(err) => { - eprintln!("Couldn't parse '{:?}' as KeyConfig - {}", path, err); - bail!("Neither a PEM-formatted private key, nor a PBS key file."); - }, - } - }; - - let format = output_format.unwrap_or(PaperkeyFormat::Html); - - match format { - PaperkeyFormat::Html => paperkey_html(&data, subject, is_private_key), - PaperkeyFormat::Text => paperkey_text(&data, subject, is_private_key), - } + generate_paper_key(std::io::stdout(), &data, subject, output_format) } pub fn cli() -> CliCommandMap { @@ -545,169 +496,3 @@ pub fn cli() -> CliCommandMap { .insert("show", key_show_cmd_def) .insert("paperkey", paper_key_cmd_def) } - -fn paperkey_html(lines: &[String], subject: Option, is_private: bool) -> Result<(), Error> { - - let img_size_pt = 500; - - println!(""); - println!(""); - println!(""); - println!(""); - println!(""); - println!("Proxmox Backup Paperkey"); - println!(""); - - println!(""); - - println!(""); - - if let Some(subject) = subject { - println!("

Subject: {}

", subject); - } - - if is_private { - const BLOCK_SIZE: usize = 20; - let blocks = (lines.len() + BLOCK_SIZE -1)/BLOCK_SIZE; - - for i in 0..blocks { - let start = i*BLOCK_SIZE; - let mut end = start + BLOCK_SIZE; - if end > lines.len() { - end = lines.len(); - } - let data = &lines[start..end]; - - println!("
"); - println!("

"); - - for l in start..end { - println!("{:02}: {}", l, lines[l]); - } - - println!("

"); - - let qr_code = generate_qr_code("svg", data)?; - let qr_code = base64::encode_config(&qr_code, base64::STANDARD_NO_PAD); - - println!("
"); - println!("", qr_code); - println!("
"); - println!("
"); - } - - println!(""); - println!(""); - return Ok(()); - } - - println!("
"); - - println!("

"); - - println!("-----BEGIN PROXMOX BACKUP KEY-----"); - - for line in lines { - println!("{}", line); - } - - println!("-----END PROXMOX BACKUP KEY-----"); - - println!("

"); - - let qr_code = generate_qr_code("svg", lines)?; - let qr_code = base64::encode_config(&qr_code, base64::STANDARD_NO_PAD); - - println!("
"); - println!("", qr_code); - println!("
"); - - println!("
"); - - println!(""); - println!(""); - - Ok(()) -} - -fn paperkey_text(lines: &[String], subject: Option, is_private: bool) -> Result<(), Error> { - - if let Some(subject) = subject { - println!("Subject: {}\n", subject); - } - - if is_private { - const BLOCK_SIZE: usize = 5; - let blocks = (lines.len() + BLOCK_SIZE -1)/BLOCK_SIZE; - - for i in 0..blocks { - let start = i*BLOCK_SIZE; - let mut end = start + BLOCK_SIZE; - if end > lines.len() { - end = lines.len(); - } - let data = &lines[start..end]; - - for l in start..end { - println!("{:-2}: {}", l, lines[l]); - } - let qr_code = generate_qr_code("utf8i", data)?; - let qr_code = String::from_utf8(qr_code) - .map_err(|_| format_err!("Failed to read qr code (got non-utf8 data)"))?; - println!("{}", qr_code); - println!("{}", char::from(12u8)); // page break - - } - return Ok(()); - } - - println!("-----BEGIN PROXMOX BACKUP KEY-----"); - for line in lines { - println!("{}", line); - } - println!("-----END PROXMOX BACKUP KEY-----"); - - let qr_code = generate_qr_code("utf8i", &lines)?; - let qr_code = String::from_utf8(qr_code) - .map_err(|_| format_err!("Failed to read qr code (got non-utf8 data)"))?; - - println!("{}", qr_code); - - Ok(()) -} - -fn generate_qr_code(output_type: &str, lines: &[String]) -> Result, Error> { - let mut child = Command::new("qrencode") - .args(&["-t", output_type, "-m0", "-s1", "-lm", "--output", "-"]) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn()?; - - { - let stdin = child.stdin.as_mut() - .ok_or_else(|| format_err!("Failed to open stdin"))?; - let data = lines.join("\n"); - stdin.write_all(data.as_bytes()) - .map_err(|_| format_err!("Failed to write to stdin"))?; - } - - let output = child.wait_with_output() - .map_err(|_| format_err!("Failed to read stdout"))?; - - let output = crate::tools::command_output(output, None)?; - - Ok(output) -} diff --git a/src/tools.rs b/src/tools.rs index bce60d00..cc782da2 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -43,6 +43,7 @@ pub mod ticket; pub mod xattr; pub mod zip; pub mod sgutils2; +pub mod paperkey; pub mod parallel_handler; pub use parallel_handler::ParallelHandler; diff --git a/src/tools/paperkey.rs b/src/tools/paperkey.rs new file mode 100644 index 00000000..030275cc --- /dev/null +++ b/src/tools/paperkey.rs @@ -0,0 +1,250 @@ +use std::io::Write; +use std::process::{Stdio, Command}; + +use anyhow::{bail, format_err, Error}; +use serde::{Deserialize, Serialize}; + +use proxmox::api::api; + +use crate::backup::KeyConfig; + +#[api()] +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +/// Paperkey output format +pub enum PaperkeyFormat { + /// Format as Utf8 text. Includes QR codes as ascii-art. + Text, + /// Format as Html. Includes QR codes as SVG images. + Html, +} + +/// Generate a paper key (html or utf8 text) +/// +/// This function takes an encryption key (either RSA private key +/// text, or `KeyConfig` json), and generates a printable text or html +/// page, including a scanable QR code to recover the key. +pub fn generate_paper_key( + output: W, + data: &str, + subject: Option, + output_format: Option, +) -> Result<(), Error> { + + let (data, is_private_key) = if data.starts_with("-----BEGIN ENCRYPTED PRIVATE KEY-----\n") { + let lines: Vec = data + .lines() + .map(|s| s.trim_end()) + .filter(|s| !s.is_empty()) + .map(String::from) + .collect(); + + if !lines[lines.len()-1].starts_with("-----END ENCRYPTED PRIVATE KEY-----") { + bail!("unexpected key format"); + } + + if lines.len() < 20 { + bail!("unexpected key format"); + } + + (lines, true) + } else { + match serde_json::from_str::(&data) { + Ok(key_config) => { + let lines = serde_json::to_string_pretty(&key_config)? + .lines() + .map(String::from) + .collect(); + + (lines, false) + }, + Err(err) => { + eprintln!("Couldn't parse data as KeyConfig - {}", err); + bail!("Neither a PEM-formatted private key, nor a PBS key file."); + }, + } + }; + + let format = output_format.unwrap_or(PaperkeyFormat::Html); + + match format { + PaperkeyFormat::Html => paperkey_html(output, &data, subject, is_private_key), + PaperkeyFormat::Text => paperkey_text(output, &data, subject, is_private_key), + } +} + +fn paperkey_html( + mut output: W, + lines: &[String], + subject: Option, + is_private: bool, +) -> Result<(), Error> { + + let img_size_pt = 500; + + writeln!(output, "")?; + writeln!(output, "")?; + writeln!(output, "")?; + writeln!(output, "")?; + writeln!(output, "")?; + writeln!(output, "Proxmox Backup Paperkey")?; + writeln!(output, "")?; + + writeln!(output, "")?; + + writeln!(output, "")?; + + if let Some(subject) = subject { + writeln!(output, "

Subject: {}

", subject)?; + } + + if is_private { + const BLOCK_SIZE: usize = 20; + let blocks = (lines.len() + BLOCK_SIZE -1)/BLOCK_SIZE; + + for i in 0..blocks { + let start = i*BLOCK_SIZE; + let mut end = start + BLOCK_SIZE; + if end > lines.len() { + end = lines.len(); + } + let data = &lines[start..end]; + + writeln!(output, "
")?; + writeln!(output, "

")?; + + for l in start..end { + writeln!(output, "{:02}: {}", l, lines[l])?; + } + + writeln!(output, "

")?; + + let qr_code = generate_qr_code("svg", data)?; + let qr_code = base64::encode_config(&qr_code, base64::STANDARD_NO_PAD); + + writeln!(output, "
")?; + writeln!(output, "", qr_code)?; + writeln!(output, "
")?; + writeln!(output, "
")?; + } + + writeln!(output, "")?; + writeln!(output, "")?; + return Ok(()); + } + + writeln!(output, "
")?; + + writeln!(output, "

")?; + + writeln!(output, "-----BEGIN PROXMOX BACKUP KEY-----")?; + + for line in lines { + writeln!(output, "{}", line)?; + } + + writeln!(output, "-----END PROXMOX BACKUP KEY-----")?; + + writeln!(output, "

")?; + + let qr_code = generate_qr_code("svg", lines)?; + let qr_code = base64::encode_config(&qr_code, base64::STANDARD_NO_PAD); + + writeln!(output, "
")?; + writeln!(output, "", qr_code)?; + writeln!(output, "
")?; + + writeln!(output, "
")?; + + writeln!(output, "")?; + writeln!(output, "")?; + + Ok(()) +} + +fn paperkey_text( + mut output: W, + lines: &[String], + subject: Option, + is_private: bool, +) -> Result<(), Error> { + + if let Some(subject) = subject { + writeln!(output, "Subject: {}\n", subject)?; + } + + if is_private { + const BLOCK_SIZE: usize = 5; + let blocks = (lines.len() + BLOCK_SIZE -1)/BLOCK_SIZE; + + for i in 0..blocks { + let start = i*BLOCK_SIZE; + let mut end = start + BLOCK_SIZE; + if end > lines.len() { + end = lines.len(); + } + let data = &lines[start..end]; + + for l in start..end { + writeln!(output, "{:-2}: {}", l, lines[l])?; + } + let qr_code = generate_qr_code("utf8i", data)?; + let qr_code = String::from_utf8(qr_code) + .map_err(|_| format_err!("Failed to read qr code (got non-utf8 data)"))?; + writeln!(output, "{}", qr_code)?; + writeln!(output, "{}", char::from(12u8))?; // page break + + } + return Ok(()); + } + + writeln!(output, "-----BEGIN PROXMOX BACKUP KEY-----")?; + for line in lines { + writeln!(output, "{}", line)?; + } + writeln!(output, "-----END PROXMOX BACKUP KEY-----")?; + + let qr_code = generate_qr_code("utf8i", &lines)?; + let qr_code = String::from_utf8(qr_code) + .map_err(|_| format_err!("Failed to read qr code (got non-utf8 data)"))?; + + writeln!(output, "{}", qr_code)?; + + Ok(()) +} + +fn generate_qr_code(output_type: &str, lines: &[String]) -> Result, Error> { + let mut child = Command::new("qrencode") + .args(&["-t", output_type, "-m0", "-s1", "-lm", "--output", "-"]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn()?; + + { + let stdin = child.stdin.as_mut() + .ok_or_else(|| format_err!("Failed to open stdin"))?; + let data = lines.join("\n"); + stdin.write_all(data.as_bytes()) + .map_err(|_| format_err!("Failed to write to stdin"))?; + } + + let output = child.wait_with_output() + .map_err(|_| format_err!("Failed to read stdout"))?; + + let output = crate::tools::command_output(output, None)?; + + Ok(output) +}