diff --git a/Cargo.lock b/Cargo.lock index 04c54784..9232c336 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2024,7 +2024,7 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openpgp-dsm" -version = "0.1.1" +version = "0.2.1-beta" dependencies = [ "anyhow", "bindgen", diff --git a/openpgp-dsm/Cargo.toml b/openpgp-dsm/Cargo.toml index e75059e4..3f76ac3f 100644 --- a/openpgp-dsm/Cargo.toml +++ b/openpgp-dsm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "openpgp-dsm" -version = "0.1.1" +version = "0.2.1-beta" authors = ["zugzwang "] edition = "2018" diff --git a/openpgp-dsm/src/lib.rs b/openpgp-dsm/src/lib.rs index 66424c42..381c8133 100644 --- a/openpgp-dsm/src/lib.rs +++ b/openpgp-dsm/src/lib.rs @@ -80,24 +80,98 @@ pub struct DsmAgent { role: Role, } -const DSM_LABEL_PGP: &str = "sq_dsm"; -const ENV_API_KEY: &str = "FORTANIX_API_KEY"; -const ENV_API_ENDPOINT: &str = "FORTANIX_API_ENDPOINT"; -const ENV_APP_UUID: &str = "FORTANIX_APP_UUID"; -const ENV_HTTP_PROXY: &str = "http_proxy"; -const ENV_NO_PROXY: &str = "no_proxy"; -const ENV_P12: &str = "FORTANIX_PKCS12_ID"; -const MIN_DSM_VERSION: &str = "4.2.0"; +/// The version of this crate. +pub const SQ_DSM_VERSION: &str = env!("CARGO_PKG_VERSION"); +const DSM_LABEL_PGP: &str = "sq_dsm"; +const ENV_API_KEY: &str = "FORTANIX_API_KEY"; +const ENV_API_ENDPOINT: &str = "FORTANIX_API_ENDPOINT"; +const ENV_APP_UUID: &str = "FORTANIX_APP_UUID"; +const ENV_HTTP_PROXY: &str = "http_proxy"; +const ENV_NO_PROXY: &str = "no_proxy"; +const ENV_P12: &str = "FORTANIX_PKCS12_ID"; +const MIN_DSM_VERSION: &str = "4.2.0"; // As seen on sdkms-client-rust/blob/master/examples/approval_request.rs -const OP_APPROVAL_MSG: &str = "This operation requires approval"; +const OP_APPROVAL_MSG: &str = "This operation requires approval"; #[derive(Clone)] -enum Auth { +pub enum Auth { ApiKey(String), // App UUID and PKCS12 identity Cert(Uuid, Identity), } +impl Auth { + pub fn from_options_or_env( + cli_api_key: Option<&str>, + cli_client_cert: Option<&str>, + cli_app_uuid: Option<&str>, + ) -> Result { + // Try API key + let api_key = match (cli_api_key, env::var(ENV_API_KEY).ok()) { + (Some(api_key), None) => Some(api_key.to_string()), + (None, Some(api_key)) => Some(api_key), + (Some(api_key), Some(_)) => { + println!( + "API key both in parameters and env; ignoring env" + ); + Some(api_key.to_string()) + }, + (None, None) => None, + }; + + // Try client cert + let cert_based = { + let client_cert = match (cli_client_cert, env::var(ENV_P12).ok()) { + (Some(cert), None) => Some(cert.to_string()), + (None, Some(cert)) => Some(cert), + (Some(cert), Some(_)) => { + println!( + "P12 cert both in parameters and env; ignoring env" + ); + Some(cert.to_string()) + }, + (None, None) => None, + }; + + let app_uuid = match (cli_app_uuid, env::var(ENV_APP_UUID).ok()) { + (Some(id), None) => Some(id.to_string()), + (None, Some(id)) => Some(id), + (Some(id), Some(_)) => { + println!( + "APP UUID both in parameters and env; ignoring env" + ); + Some(id.to_string()) + }, + (None, None) => None, + }; + + match (client_cert, app_uuid) { + (Some(cert), Some(uuid)) => Some((cert, uuid)), + _ => None, + } + }; + + match (api_key, cert_based) { + (Some(api_key), None) => Ok(Auth::ApiKey(api_key)), + (Some(api_key), Some(_)) => { + println!( + "Multiple auth methods found. Using API key" + ); + + Ok(Auth::ApiKey(api_key)) + }, + (None, Some((client_cert, app_uuid))) => { + let p12_id = try_unlock_p12(client_cert)?; + + let uuid = Uuid::parse_str(&app_uuid) + .context("bad app UUID")?; + Ok(Auth::Cert(uuid, p12_id)) + } + (None, None) => return Err(Error::msg("no auth credentials found")), + } + } +} + #[derive(Clone)] pub struct Credentials { api_endpoint: String, @@ -225,65 +299,9 @@ impl > + Display> OperateOrAskApproval for DsmClien } impl Credentials { - pub fn new_from_env() -> Result { + pub fn new(auth: Auth) -> Result { let api_endpoint = env::var(ENV_API_ENDPOINT) - .with_context(|| format!("{} env var absent", ENV_API_ENDPOINT))?; - - let auth = match ( - env::var(ENV_API_KEY).ok(), - env::var(ENV_P12).ok(), - ) { - (Some(api_key), other) => { - if other.is_some() { - println!("Both {}, {} are set, using API key auth", - ENV_API_KEY, ENV_P12); - } - Auth::ApiKey(api_key) - }, - (None, Some(cert_file)) => { - let app_uuid = Uuid::parse_str( - &env::var(ENV_APP_UUID).context( - format!("Need {} for cert-based auth", ENV_APP_UUID))? - ).context("bad app UUID")?; - let mut cert_stream = File::open(cert_file.clone()) - .context(format!("opening {}", cert_file))?; - let mut cert = Vec::new(); - cert_stream.read_to_end(&mut cert) - .context(format!("reading {}", cert_file))?; - // Try to unlock certificate without password - let mut first = true; - let id = if let Ok(id) = Identity::from_pkcs12(&cert, "") { - id - } else { - loop { - // Prompt the user for PKCS12 password - match rpassword::read_password_from_tty( - Some( - &format!( - "{}Enter password to unlock {}: ", - if first { "" } else { "Invalid password. " }, - cert_file)) - ) { - Ok(p) => { - first = false; - if let Ok(id) = Identity::from_pkcs12(&cert, &p) { - break id; - } - }, - Err(err) => { - return Err(Error::msg(format!( - "While reading password: {}", err) - )); - } - } - } - }; - Auth::Cert(app_uuid, id) - } - (None, None) => return Err(Error::msg(format!( - "at least one of {}, {} env var is needed", - ENV_API_KEY, ENV_P12))), - }; + .with_context(|| format!("{} absent", ENV_API_ENDPOINT))?; Ok(Self { api_endpoint, auth }) } @@ -448,6 +466,7 @@ pub fn generate_key( user_id: Option<&str>, algo: Option<&str>, exportable: bool, + credentials: Credentials, ) -> Result<()> { // User ID let uid: UserID = match user_id { @@ -467,7 +486,6 @@ pub fn generate_key( _ => unreachable!("argument has a default value"), }; - let credentials = Credentials::new_from_env()?; let dsm_client = credentials.dsm_client()?; info!("create primary key"); @@ -611,10 +629,9 @@ pub fn generate_key( /// Extracts the certificate of the corresponding PGP key. Note that this /// certificate, created at key-generation time, is stored in the custom /// metadata of the Security Object representing the primary key. -pub fn extract_cert(key_name: &str) -> Result { +pub fn extract_cert(key_name: &str, cred: Credentials) -> Result { info!("dsm extract_cert"); - let credentials = Credentials::new_from_env()?; - let dsm_client = credentials.dsm_client()?; + let dsm_client = cred.dsm_client()?; let metadata = { let sobject = dsm_client @@ -635,10 +652,9 @@ pub fn extract_cert(key_name: &str) -> Result { Ok(Cert::from_str(&key_md.certificate)?) } -pub fn extract_tsk_from_dsm(key_name: &str) -> Result { +pub fn extract_tsk_from_dsm(key_name: &str, cred: Credentials) -> Result { // Extract all secrets as packets - let credentials = Credentials::new_from_env()?; - let dsm_client = credentials.dsm_client()?; + let dsm_client = cred.dsm_client()?; let mut packets = Vec::::with_capacity(2); @@ -1377,3 +1393,39 @@ fn api_curve_from_sequoia_curve(curve: SequoiaCurve) -> Result { curve @ _ => Err(Error::msg(format!("unsupported curve {}", curve))), } } + +fn try_unlock_p12(cert_file: String) -> Result { + let mut cert_stream = File::open(cert_file.clone()) + .context(format!("opening {}", cert_file))?; + let mut cert = Vec::new(); + cert_stream.read_to_end(&mut cert) + .context(format!("reading {}", cert_file))?; + // Try to unlock certificate without password first + let mut first = true; + if let Ok(id) = Identity::from_pkcs12(&cert, "") { + Ok(id) + } else { + loop { + // Prompt the user for PKCS12 password + match rpassword::read_password_from_tty( + Some( + &format!( + "{}Enter password to unlock {}: ", + if first { "" } else { "Invalid password. " }, + cert_file)) + ) { + Ok(p) => { + first = false; + if let Ok(id) = Identity::from_pkcs12(&cert, &p) { + break Ok(id) + } + }, + Err(err) => { + return Err(Error::msg(format!( + "While reading password: {}", err) + )); + } + } + } + } +} diff --git a/sq/Cargo.toml b/sq/Cargo.toml index 4020ed4d..d9c2bd30 100644 --- a/sq/Cargo.toml +++ b/sq/Cargo.toml @@ -45,6 +45,7 @@ env_logger = "0.9.0" clap = "2.33" sequoia-openpgp = { path = "../openpgp", version = "1.0.0", default-features = false } subplot-build = "0.1.0" +openpgp-dsm = { path = "../openpgp-dsm", default-features = false } [dev-dependencies] assert_cli = "0.6" diff --git a/sq/Makefile b/sq/Makefile index 3b2530af..968630f8 100644 --- a/sq/Makefile +++ b/sq/Makefile @@ -37,24 +37,25 @@ install: build-release test-dsm: cargo run -- --version - ./tests/dsm/key_expiration.sh --rsa2k - ./tests/dsm/key_expiration.sh --rsa3k - ./tests/dsm/key_expiration.sh --rsa4k - ./tests/dsm/key_expiration.sh --p256 - ./tests/dsm/key_expiration.sh --p384 - ./tests/dsm/key_expiration.sh --p521 - ./tests/dsm/key_expiration.sh --cv25519 - ./tests/dsm/extract_dsm_import_gpg_auto.tcl --rsa2k - ./tests/dsm/extract_dsm_import_gpg_auto.tcl --rsa3k - ./tests/dsm/extract_dsm_import_gpg_auto.tcl --rsa4k - ./tests/dsm/extract_dsm_import_gpg_auto.tcl --p256 - ./tests/dsm/extract_dsm_import_gpg_auto.tcl --p384 - ./tests/dsm/extract_dsm_import_gpg_auto.tcl --p521 - ./tests/dsm/extract_dsm_import_gpg_auto.tcl --cv25519 - ./tests/dsm/sq_roundtrips.sh --rsa2k - ./tests/dsm/sq_roundtrips.sh --rsa3k - ./tests/dsm/sq_roundtrips.sh --rsa4k - ./tests/dsm/sq_roundtrips.sh --p256 - ./tests/dsm/sq_roundtrips.sh --p384 - ./tests/dsm/sq_roundtrips.sh --p521 - ./tests/dsm/sq_roundtrips.sh --cv25519 + ./tests/dsm/key_expiration.sh -c rsa2k + ./tests/dsm/key_expiration.sh -c rsa3k + ./tests/dsm/key_expiration.sh -c rsa4k + ./tests/dsm/key_expiration.sh -c nistp256 + ./tests/dsm/key_expiration.sh -c nistp384 + ./tests/dsm/key_expiration.sh -c nistp521 + ./tests/dsm/key_expiration.sh -c cv25519 + ./tests/dsm/extract_dsm_import_gpg_auto.tcl -c rsa2k + ./tests/dsm/extract_dsm_import_gpg_auto.tcl -c rsa3k + ./tests/dsm/extract_dsm_import_gpg_auto.tcl -c rsa4k + ./tests/dsm/extract_dsm_import_gpg_auto.tcl -c nistp256 + ./tests/dsm/extract_dsm_import_gpg_auto.tcl -c nistp384 + ./tests/dsm/extract_dsm_import_gpg_auto.tcl -c nistp521 + ./tests/dsm/extract_dsm_import_gpg_auto.tcl -c cv25519 + ./tests/dsm/sq_roundtrips.sh -x -c cv25519 # Passing api-key + ./tests/dsm/sq_roundtrips.sh -c rsa2k + ./tests/dsm/sq_roundtrips.sh -c rsa3k + ./tests/dsm/sq_roundtrips.sh -c rsa4k + ./tests/dsm/sq_roundtrips.sh -c nistp256 + ./tests/dsm/sq_roundtrips.sh -c nistp384 + ./tests/dsm/sq_roundtrips.sh -c nistp521 + ./tests/dsm/sq_roundtrips.sh -c cv25519 diff --git a/sq/src/commands/key.rs b/sq/src/commands/key.rs index 6666288d..dda4209e 100644 --- a/sq/src/commands/key.rs +++ b/sq/src/commands/key.rs @@ -75,12 +75,19 @@ fn generate(config: Config, m: &ArgMatches) -> Result<()> { builder = builder.set_validity_period(d); if let Some(dsm_key_name) = m.value_of("dsm-key") { + // Fortanix DSM + let dsm_secret = dsm::Auth::from_options_or_env( + m.value_of("api-key"), + m.value_of("client-cert"), + m.value_of("app-uuid"), + )?; return dsm::generate_key( dsm_key_name, d, m.value_of("userid"), m.value_of("cipher-suite"), m.is_present("dsm-exportable"), + dsm::Credentials::new(dsm_secret)?, ); } @@ -292,7 +299,14 @@ fn extract_cert(config: Config, m: &ArgMatches) -> Result<()> { let cert = match m.value_of("dsm-key") { Some(key_name) => { - dsm::extract_cert(key_name)? + // Fortanix DSM + let dsm_secret = dsm::Auth::from_options_or_env( + m.value_of("api-key"), + m.value_of("client-cert"), + m.value_of("app-uuid"), + )?; + let dsm_auth = dsm::Credentials::new(dsm_secret)?; + dsm::extract_cert(key_name, dsm_auth)? } None => { let input = open_or_stdin(m.value_of("input"))?; @@ -309,8 +323,14 @@ fn extract_cert(config: Config, m: &ArgMatches) -> Result<()> { } fn extract_dsm(config: Config, m: &ArgMatches) -> Result<()> { + let dsm_secret = dsm::Auth::from_options_or_env( + m.value_of("api-key"), + m.value_of("client-cert"), + m.value_of("app-uuid"), + )?; + let dsm_auth = dsm::Credentials::new(dsm_secret)?; let key = match m.value_of("dsm-key") { - Some(key_name) => dsm::extract_tsk_from_dsm(key_name)?, + Some(key_name) => dsm::extract_tsk_from_dsm(key_name, dsm_auth)?, None => unreachable!("name is compulsory") }; diff --git a/sq/src/secrets.rs b/sq/src/secrets.rs index 606e696a..ced4c506 100644 --- a/sq/src/secrets.rs +++ b/sq/src/secrets.rs @@ -9,6 +9,7 @@ use sequoia_openpgp::packet::Key; use sequoia_openpgp::types::HashAlgorithm; pub use openpgp_dsm::Credentials; +pub use openpgp_dsm::Auth; use openpgp_dsm::DsmAgent; /// A Secret can be a private key loaded from memory, or stored externally. It diff --git a/sq/src/sq-usage.rs b/sq/src/sq-usage.rs index 51e609c3..fc2b4176 100644 --- a/sq/src/sq-usage.rs +++ b/sq/src/sq-usage.rs @@ -83,6 +83,15 @@ //! back to using the one that expired last //! //! OPTIONS: +//! --api-key +//! Authenticates to Fortanix DSM using the given API key +//! +//! --app-uuid +//! Authenticates to Fortanix DSM with the given App (cert-based +//! authentication) +//! --client-cert +//! Authenticates to Fortanix DSM with the given client certificate +//! //! --compression //! Selects compression scheme to use [default: pad] [possible values: //! none, pad, zip, zlib, bzip2] @@ -169,6 +178,15 @@ //! //! //! OPTIONS: +//! --api-key +//! Authenticates to Fortanix DSM using the given API key +//! +//! --app-uuid +//! Authenticates to Fortanix DSM with the given App (cert-based +//! authentication) +//! --client-cert +//! Authenticates to Fortanix DSM with the given client certificate +//! //! --dsm-key //! Decrypts with secrets stored inside the Fortanix Self-Defending Key- //! Management System @@ -240,6 +258,15 @@ //! //! //! OPTIONS: +//! --api-key +//! Authenticates to Fortanix DSM using the given API key +//! +//! --app-uuid +//! Authenticates to Fortanix DSM with the given App (cert-based +//! authentication) +//! --client-cert +//! Authenticates to Fortanix DSM with the given client certificate +//! //! --dsm-key //! Signs the message with the Fortanix DSM key //! @@ -420,6 +447,12 @@ //! //! //! OPTIONS: +//! --api-key +//! Authenticates to Fortanix DSM using the given API key +//! +//! --app-uuid +//! Authenticates to Fortanix DSM with the given App (cert-based +//! authentication) //! --can-encrypt //! Adds an encryption-capable subkey. Encryption-capable subkeys can be //! marked as suitable for transport encryption, storage encryption, or @@ -429,6 +462,9 @@ //! Selects the cryptographic algorithms for the key [default: cv25519] //! [possible values: rsa2k, rsa3k, rsa4k, cv25519, nistp256, nistp384, //! nistp521] +//! --client-cert +//! Authenticates to Fortanix DSM with the given client certificate +//! //! --dsm-key //! Generate secrets inside Fortanix DSM with the given name //! @@ -538,9 +574,18 @@ //! //! //! OPTIONS: +//! --api-key +//! Authenticates to Fortanix DSM using the given API key +//! +//! --app-uuid +//! Authenticates to Fortanix DSM with the given App (cert-based +//! authentication) +//! --client-cert +//! Authenticates to Fortanix DSM with the given client certificate +//! //! --dsm-key -//! Extracts the certificate from the Fortanix Self-Defending Key- -//! Management System +//! Extracts the certificate from Fortanix DSM +//! //! -o, --output //! Writes to FILE or stdout if omitted //! @@ -582,6 +627,15 @@ //! //! //! OPTIONS: +//! --api-key +//! Authenticates to Fortanix DSM using the given API key +//! +//! --app-uuid +//! Authenticates to Fortanix DSM with the given App (cert-based +//! authentication) +//! --client-cert +//! Authenticates to Fortanix DSM with the given client certificate +//! //! --dsm-key //! Name of the DSM key //! diff --git a/sq/src/sq.rs b/sq/src/sq.rs index b47a1029..94cabbd9 100644 --- a/sq/src/sq.rs +++ b/sq/src/sq.rs @@ -28,7 +28,7 @@ mod sq_cli; mod commands; mod secrets; -use secrets::{Credentials, PreSecret}; +use secrets::{Auth, Credentials, PreSecret}; fn open_or_stdin(f: Option<&str>) -> Result>> { @@ -436,8 +436,14 @@ fn main() -> Result<()> { .map(load_keys) .unwrap_or_else(|| Ok(vec![]))?; if let Some(name) = m.value_of("dsm-key") { - let auth = Credentials::new_from_env()?; - secrets.push(PreSecret::Dsm(auth, name.to_string())); + // Fortanix DSM + let dsm_secret = Auth::from_options_or_env( + m.value_of("api-key"), + m.value_of("client-cert"), + m.value_of("app-uuid"), + )?; + let dsm_auth = Credentials::new(dsm_secret)?; + secrets.push(PreSecret::Dsm(dsm_auth, name.to_string())); } let private_key_store = m.value_of("private-key-store"); commands::decrypt(config, private_key_store, @@ -477,9 +483,15 @@ fn main() -> Result<()> { }; let private_key_store = m.value_of("private-key-store"); if let Some(name) = m.value_of("signer-dsm-key") { - let auth = Credentials::new_from_env()?; + // Fortanix DSM + let dsm_secret = Auth::from_options_or_env( + m.value_of("api-key"), + m.value_of("client-cert"), + m.value_of("app-uuid"), + )?; + let dsm_auth = Credentials::new(dsm_secret)?; additional_secrets - .push(secrets::PreSecret::Dsm(auth, name.to_string())); + .push(secrets::PreSecret::Dsm(dsm_auth, name.to_string())); } commands::encrypt(commands::EncryptOpts { policy, @@ -537,8 +549,14 @@ fn main() -> Result<()> { } if let Some(name) = m.value_of("dsm-key") { - let auth = Credentials::new_from_env()?; - secrets.push(secrets::PreSecret::Dsm(auth, name.to_string())); + // Fortanix DSM + let dsm_secret = Auth::from_options_or_env( + m.value_of("api-key"), + m.value_of("client-cert"), + m.value_of("app-uuid"), + )?; + let dsm_auth = Credentials::new(dsm_secret)?; + secrets.push(secrets::PreSecret::Dsm(dsm_auth, name.to_string())); } if let Some(merge) = m.value_of("merge") { let output = config.create_or_stdout_pgp(output, binary, @@ -679,8 +697,14 @@ fn main() -> Result<()> { .map(load_keys) .unwrap_or_else(|| Ok(vec![]))?; if let Some(name) = m.value_of("dsm-key") { - let auth = Credentials::new_from_env()?; - secrets.push(PreSecret::Dsm(auth, name.to_string())); + // Fortanix DSM + let dsm_secret = Auth::from_options_or_env( + m.value_of("api-key"), + m.value_of("client-cert"), + m.value_of("app-uuid"), + )?; + let dsm_auth = Credentials::new(dsm_secret)?; + secrets.push(PreSecret::Dsm(dsm_auth, name.to_string())); } commands::decrypt::decrypt_unwrap( config, diff --git a/sq/src/sq_cli.rs b/sq/src/sq_cli.rs index ed87d6d5..53cb8c52 100644 --- a/sq/src/sq_cli.rs +++ b/sq/src/sq_cli.rs @@ -25,9 +25,10 @@ pub fn configure( feature_autocrypt: bool, ) -> App<'static, 'static> { let version = Box::leak( - format!("{} (sequoia-openpgp {})", + format!("{} (sequoia-openpgp {}) (sq-dsm {})", env!("CARGO_PKG_VERSION"), - sequoia_openpgp::VERSION) + sequoia_openpgp::VERSION, + openpgp_dsm::SQ_DSM_VERSION) .into_boxed_str()) as &str; let app = app @@ -136,6 +137,18 @@ $ sq decrypt ciphertext.pgp .arg(Arg::with_name("hex") .short("x").long("hex") .help("Prints a hexdump (implies --dump)")) + .arg(Arg::with_name("api-key") + .long("api-key").value_name("API-KEY") + .help("Authenticates to Fortanix DSM using the given \ + API key")) + .arg(Arg::with_name("client-cert") + .long("client-cert").value_name("P12-FILE") + .help("Authenticates to Fortanix DSM with the given client \ + certificate")) + .arg(Arg::with_name("app-uuid") + .long("app-uuid").value_name("APP-UUID") + .help("Authenticates to Fortanix DSM with the given App \ + (cert-based authentication)")) .arg(Arg::with_name("dsm-key") .long("dsm-key").value_name("DSM-KEY-NAME") .help("Decrypts with secrets stored inside the \ @@ -185,6 +198,18 @@ $ sq encrypt --symmetric message.txt .arg(Arg::with_name("private-key-store") .long("private-key-store").value_name("KEY_STORE") .help("Provides parameters for private key store")) + .arg(Arg::with_name("api-key") + .long("api-key").value_name("API-KEY") + .help("Authenticates to Fortanix DSM using the given \ + API key")) + .arg(Arg::with_name("client-cert") + .long("client-cert").value_name("P12-FILE") + .help("Authenticates to Fortanix DSM with the given client \ + certificate")) + .arg(Arg::with_name("app-uuid") + .long("app-uuid").value_name("APP-UUID") + .help("Authenticates to Fortanix DSM with the given App \ + (cert-based authentication)")) .arg(Arg::with_name("signer-dsm-key") .long("signer-dsm-key").value_name("DSM-KEY-NAME") .help("Signs the message with a key stored in Fortanix \ @@ -297,6 +322,18 @@ $ sq sign --detached --signer-key juliet.pgp message.txt .long("signer-key").value_name("KEY") .multiple(true).number_of_values(1) .help("Signs using KEY")) + .arg(Arg::with_name("api-key") + .long("api-key").value_name("API-KEY") + .help("Authenticates to Fortanix DSM using the given \ + API key")) + .arg(Arg::with_name("client-cert") + .long("client-cert").value_name("P12-FILE") + .help("Authenticates to Fortanix DSM with the given client \ + certificate")) + .arg(Arg::with_name("app-uuid") + .long("app-uuid").value_name("APP-UUID") + .help("Authenticates to Fortanix DSM with the given App \ + (cert-based authentication)")) .arg(Arg::with_name("dsm-key") .long("dsm-key").value_name("DSM-KEY-NAME") .help("Signs the message with the Fortanix DSM key")) @@ -556,6 +593,18 @@ $ sq key generate --userid \"\" --userid \"Juliet Capulet\" .arg(Arg::with_name("with-password") .long("with-password") .help("Protects the key with a password")) + .arg(Arg::with_name("api-key") + .long("api-key").value_name("API-KEY") + .help("Authenticates to Fortanix DSM using the given \ + API key")) + .arg(Arg::with_name("client-cert") + .long("client-cert").value_name("P12-FILE") + .help("Authenticates to Fortanix DSM with the given client \ + certificate")) + .arg(Arg::with_name("app-uuid") + .long("app-uuid").value_name("APP-UUID") + .help("Authenticates to Fortanix DSM with the given App \ + (cert-based authentication)")) .arg(Arg::with_name("dsm-exportable") .long("dsm-exportable") .help("(DANGER) Configure the key to be exportable from DSM")) @@ -698,10 +747,22 @@ $ sq key extract-cert --output juliet.cert.pgp juliet.key.pgp .arg(Arg::with_name("binary") .short("B").long("binary") .help("Emits binary data")) + .arg(Arg::with_name("api-key") + .long("api-key").value_name("API-KEY") + .help("Authenticates to Fortanix DSM using the \ + given API key")) + .arg(Arg::with_name("client-cert") + .long("client-cert").value_name("P12-FILE") + .help("Authenticates to Fortanix DSM with the given \ + client certificate")) + .arg(Arg::with_name("app-uuid") + .long("app-uuid").value_name("APP-UUID") + .help("Authenticates to Fortanix DSM with the given App \ + (cert-based authentication)")) .arg(Arg::with_name("dsm-key") .long("dsm-key").value_name("DSM-KEY-NAME") - .help("Extracts the certificate from the Fortanix \ - Self-Defending Key-Management System")) + .help("Extracts the certificate from Fortanix \ + DSM")) ) .subcommand(SubCommand::with_name("extract-dsm-secret") .display_order(111) @@ -712,6 +773,18 @@ $ sq key extract-cert --output juliet.cert.pgp juliet.key.pgp Is a Fortanix DSM key was generated using the `--dsm-exportable` flag, this command exfiltrates secrets from DSM and outputs a Key. ") + .arg(Arg::with_name("api-key") + .long("api-key").value_name("API-KEY") + .help("Authenticates to Fortanix DSM using the \ + given API key")) + .arg(Arg::with_name("client-cert") + .long("client-cert").value_name("P12-FILE") + .help("Authenticates to Fortanix DSM with the given client \ + certificate")) + .arg(Arg::with_name("app-uuid") + .long("app-uuid").value_name("APP-UUID") + .help("Authenticates to Fortanix DSM with the given App \ + (cert-based authentication)")) .arg(Arg::with_name("dsm-key") .long("dsm-key").value_name("DSM-KEY-NAME") .required(true) diff --git a/sq/tests/dsm/common.sh b/sq/tests/dsm/common.sh new file mode 100755 index 00000000..26108b31 --- /dev/null +++ b/sq/tests/dsm/common.sh @@ -0,0 +1,56 @@ +#!/bin/bash -e + +# Script options: +# +# -c CIPHERSUITE: Select ciphersuite +# -x : Set CLI auth via the api-key flag +# -v VERBOSITY : Select verbosity level + +export sq="../target/debug/sq" + +# tmp directory, erased on exit +create_tmp_dir() { + eval "$1"="$(mktemp -d)" +} + +erase_tmp_dir() { + rm -rf "$1" +} + +comm() { + printf "~~~ %s ~~~\n" "$1" +} + +my_cat() { + if [[ "$verbosity" -eq 1 ]]; then + head -n4 "$1" + echo " [TRUNCATED OUTPUT]" + fi + if [[ "$verbosity" -eq 2 ]]; then + cat "$1" + fi +} + +export cipher_suite="" +export cli_auth=false # If false, api-key is passed to the CLI +export verbosity=0 + +while getopts :xc:v: opt; do + case $opt in + x) cli_auth=true ;; + c) cipher_suite="$OPTARG";; + v) verbosity="$OPTARG";; + :) echo "Missing argument for option -$OPTARG"; exit 1;; + \?) echo "Unknown option -$OPTARG"; exit 1;; + esac +done + +if [ -z "$FORTANIX_API_ENDPOINT" ]; then + echo "FORTANIX_API_ENDPOINT unset" + exit 1 +fi +if [ -z "$FORTANIX_API_KEY" ]; then + echo "FORTANIX_API_KEY unset" + exit 1 +fi + diff --git a/sq/tests/dsm/extract_dsm_import_gpg.sh b/sq/tests/dsm/extract_dsm_import_gpg.sh index 69fd76d9..4a503535 100755 --- a/sq/tests/dsm/extract_dsm_import_gpg.sh +++ b/sq/tests/dsm/extract_dsm_import_gpg.sh @@ -1,45 +1,11 @@ #!/bin/bash -e -sq="../target/debug/sq" - -case "$1" in - --p256) cipher_suite="nistp256";; - --p384) cipher_suite="nistp384";; - --p521) cipher_suite="nistp521";; - --cv25519) cipher_suite="cv25519";; - --rsa2k) cipher_suite="rsa2k";; - --rsa3k) cipher_suite="rsa3k";; - --rsa4k) cipher_suite="rsa4k";; - -*) echo "unknown option: $1" >&2; exit 1;; -esac - -case "$3" in - 1|2) verbosity=$3;; - *) verbosity=0 -esac - -# tmp directory, erased on exit -create_tmp_dir() { - eval "$1"="$(mktemp -d)" -} - -erase_tmp_dirs() { - rm -rf "$data" "$gpg_homedir" -} - -comm() { - printf "~~~ %s ~~~\n" "$1" -} - -my_cat() { - if [[ "$verbosity" -eq 1 ]]; then - head -n4 "$1" - echo " [TRUNCATED OUTPUT]" - fi - if [[ "$verbosity" -eq 2 ]]; then - cat "$1" - fi -} +sq="" +cipher_suite="" + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +# shellcheck source=./common.sh +source $SCRIPT_DIR/common.sh data="" create_tmp_dir data diff --git a/sq/tests/dsm/extract_dsm_import_gpg_auto.tcl b/sq/tests/dsm/extract_dsm_import_gpg_auto.tcl index 6f8c3025..ba2cdb9f 100755 --- a/sq/tests/dsm/extract_dsm_import_gpg_auto.tcl +++ b/sq/tests/dsm/extract_dsm_import_gpg_auto.tcl @@ -1,11 +1,11 @@ #!/usr/bin/expect set test_script "./tests/dsm/extract_dsm_import_gpg.sh" -set ciphersuite [lindex $argv 0]; -set verbosity [lindex $argv 2]; +set ciphersuite [lindex $argv 1]; +set verbosity [lindex $argv 3]; set pass "my-test-passphrase" -spawn "$test_script" "$ciphersuite" -v "$verbosity" +spawn "$test_script" -c "$ciphersuite" -v "$verbosity" expect "Enter test passphrase" send "$pass\n" diff --git a/sq/tests/dsm/key_expiration.sh b/sq/tests/dsm/key_expiration.sh index bd567dc0..37ef2327 100755 --- a/sq/tests/dsm/key_expiration.sh +++ b/sq/tests/dsm/key_expiration.sh @@ -1,45 +1,11 @@ #!/bin/bash -e -sq="../target/debug/sq" # Generated by e.g. "cargo run" +sq="" +cipher_suite="" -case "$1" in - --p256) cipher_suite="nistp256";; - --p384) cipher_suite="nistp384";; - --p521) cipher_suite="nistp521";; - --cv25519) cipher_suite="cv25519";; - --rsa2k) cipher_suite="rsa2k";; - --rsa3k) cipher_suite="rsa3k";; - --rsa4k) cipher_suite="rsa4k";; - *) echo "unknown option: $1" >&2; exit 1;; -esac - -case "$3" in - 1|2) verbosity=$3;; - *) verbosity=0 -esac - -# tmp directory, erased on exit -create_tmp_dir() { - eval "$1"="$(mktemp -d)" -} - -erase_tmp_dir() { - rm -rf "$1" -} - -comm() { - printf "~~~ %s ~~~\n" "$1" -} - -my_cat() { - if [[ "$verbosity" -eq 1 ]]; then - head -n4 "$1" - echo " [TRUNCATED OUTPUT]" - fi - if [[ "$verbosity" -eq 2 ]]; then - cat "$1" - fi -} +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +# shellcheck source=./common.sh +source $SCRIPT_DIR/common.sh data="" create_tmp_dir data diff --git a/sq/tests/dsm/sq_roundtrips.sh b/sq/tests/dsm/sq_roundtrips.sh index e3422189..c8845944 100755 --- a/sq/tests/dsm/sq_roundtrips.sh +++ b/sq/tests/dsm/sq_roundtrips.sh @@ -1,45 +1,17 @@ #!/bin/bash -e -sq="../target/debug/sq" - -case "$1" in - --p256) cipher_suite="nistp256";; - --p384) cipher_suite="nistp384";; - --p521) cipher_suite="nistp521";; - --cv25519) cipher_suite="cv25519";; - --rsa2k) cipher_suite="rsa2k";; - --rsa3k) cipher_suite="rsa3k";; - --rsa4k) cipher_suite="rsa4k";; - *) echo "unknown option: $1" >&2; exit 1;; -esac - -case "$3" in - 1|2) verbosity=$3;; - *) verbosity=0 -esac - -# tmp directory, erased on exit -create_tmp_dir() { - eval "$1"="$(mktemp -d)" -} - -erase_tmp_dir() { - rm -rf "$1" -} - -comm() { - printf "~~~ %s ~~~\n" "$1" -} - -my_cat() { - if [[ "$verbosity" -eq 1 ]]; then - head -n4 "$1" - echo " [TRUNCATED OUTPUT]" - fi - if [[ "$verbosity" -eq 2 ]]; then - cat "$1" - fi -} +sq="" +cipher_suite="" +cli_auth=false # If false, api-key is passed to the CLI +apikey= + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +# shellcheck source=./common.sh +source $SCRIPT_DIR/common.sh + +if [ "$cli_auth" = true ] ; then + apikey="--api-key=$FORTANIX_API_KEY" +fi data="" create_tmp_dir data @@ -63,24 +35,24 @@ alice_key_name="test-sq-roundtrip-alice-$random" bob_key_name="test-sq-roundtrip-bob-$random" comm "generate-keys (Alice with $cipher_suite, Bob with default)" -$sq key generate --dsm-key="$alice_key_name" --userid="Alice Павловна Вишневская " --cipher-suite="$cipher_suite" -$sq key generate --dsm-key="$bob_key_name" --userid="Bob Сергeeвич Прокoфьев " -$sq key generate --userid="Bob Сергeeвич Прокoфьев " --export="$bob_local_priv" +$sq key generate $apikey --dsm-key="$alice_key_name" --userid="Alice Павловна Вишневская " --cipher-suite="$cipher_suite" +$sq key generate $apikey --dsm-key="$bob_key_name" --userid="Bob Сергeeвич Прокoфьев " +$sq key generate $apikey --userid="Bob Сергeeвич Прокoфьев " --export="$bob_local_priv" comm "certificate Alice" -$sq key extract-cert --dsm-key="$alice_key_name" > "$alice_public" +$sq key extract-cert $apikey --dsm-key="$alice_key_name" > "$alice_public" my_cat "$alice_public" comm "certificate Bob SDKMS" -$sq key extract-cert --dsm-key="$bob_key_name" > "$bob_dsm" +$sq key extract-cert $apikey --dsm-key="$bob_key_name" > "$bob_dsm" my_cat "$bob_dsm" comm "certificate Bob Local" -$sq key extract-cert "$bob_local_priv" > "$bob_local_pub" +$sq key extract-cert $apikey "$bob_local_priv" > "$bob_local_pub" my_cat "$bob_local_pub" printf "Y el verso cae al alma como al pasto el rocío.\n" > "$message" comm "sign" -$sq sign --dsm-key="$alice_key_name" "$message" > "$signed" +$sq sign $apikey --dsm-key="$alice_key_name" "$message" > "$signed" my_cat "$signed" comm "verify" @@ -91,15 +63,15 @@ $sq encrypt --recipient-cert "$alice_public" "$message" --output "$encrypted_nos my_cat "$encrypted_nosign" comm "decrypt" -$sq decrypt --dsm-key="$alice_key_name" "$encrypted_nosign" --output "$decrypted_nosign" +$sq decrypt $apikey --dsm-key="$alice_key_name" "$encrypted_nosign" --output "$decrypted_nosign" diff "$message" "$decrypted_nosign" comm "encrypt to Alice, sign with both Bob keys" -$sq encrypt --signer-dsm-key="$bob_key_name" --signer-key="$bob_local_priv" --recipient-cert "$alice_public" "$message" --output "$encrypted_signed" +$sq encrypt $apikey --signer-dsm-key="$bob_key_name" --signer-key="$bob_local_priv" --recipient-cert "$alice_public" "$message" --output "$encrypted_signed" my_cat "$encrypted_signed" comm "decrypt" -$sq decrypt --signer-cert="$bob_dsm" --signer-cert="$bob_local_pub" --dsm-key="$alice_key_name" "$encrypted_signed" --output "$decrypted_signed" +$sq decrypt $apikey --signer-cert="$bob_dsm" --signer-cert="$bob_local_pub" --dsm-key="$alice_key_name" "$encrypted_signed" --output "$decrypted_signed" diff "$message" "$decrypted_signed"