diff --git a/Cargo.lock b/Cargo.lock index 9232c336f..bfbf3cda5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1919,6 +1919,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint 0.4.3", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.2.6" @@ -1930,6 +1944,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg 1.0.1", + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint-dig" version = "0.6.1" @@ -1949,6 +1974,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "num-complex" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fbc387afefefd5e9e39493299f3069e14a140dd34dc19b4c1c1a8fddb6a790" +dependencies = [ + "num-traits", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -1970,6 +2004,18 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +dependencies = [ + "autocfg 1.0.1", + "num-bigint 0.4.3", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.14" @@ -2024,7 +2070,7 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openpgp-dsm" -version = "0.2.1-beta" +version = "0.3.0-beta" dependencies = [ "anyhow", "bindgen", @@ -2035,6 +2081,7 @@ dependencies = [ "ipnetwork", "lalrpop", "log 0.4.14", + "num", "rpassword", "sdkms", "semver", @@ -3019,6 +3066,7 @@ dependencies = [ "ripemd160", "rpassword", "rsa", + "serde", "sha-1 0.9.8", "sha1collisiondetection", "sha2", @@ -3205,7 +3253,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "692ca13de57ce0613a363c8c2f1de925adebc81b04c923ac60c5488bb44abe4b" dependencies = [ "chrono", - "num-bigint", + "num-bigint 0.2.6", "num-traits", ] @@ -4173,12 +4221,12 @@ dependencies = [ [[package]] name = "yasna" -version = "0.3.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de7bff972b4f2a06c85f6d8454b09df153af7e3a4ec2aac81db1b105b684ddb" +checksum = "346d34a236c9d3e5f3b9b74563f238f955bbd05fa0b8b4efa53c130c43982f4c" dependencies = [ "bit-vec", - "num-bigint", + "num-bigint 0.4.3", ] [[package]] diff --git a/openpgp-dsm/Cargo.toml b/openpgp-dsm/Cargo.toml index 3f76ac3f9..0aa2c103d 100644 --- a/openpgp-dsm/Cargo.toml +++ b/openpgp-dsm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "openpgp-dsm" -version = "0.2.1-beta" +version = "0.3.0-beta" authors = ["zugzwang "] edition = "2018" @@ -14,6 +14,7 @@ hyper-native-tls = "0.3.0" ipnetwork = "0.17" http = "0.2.4" log = "0.4.14" +num = "0.4.0" rpassword = "5.0" spki = "0.4.0" sequoia-openpgp = { path = "../openpgp", default-features = false } @@ -23,7 +24,7 @@ serde = "1.0" serde_derive = "1.0" serde_json = "1.0" uuid = "0.7.4" -yasna = { version = "0.3.2", features = ["num-bigint", "bit-vec"] } +yasna = { version = "0.5.0", features = ["num-bigint", "bit-vec"] } [features] default = ["sequoia-openpgp/default"] diff --git a/openpgp-dsm/src/der.rs b/openpgp-dsm/src/der.rs index 579e7bc9b..ee60f020b 100644 --- a/openpgp-dsm/src/der.rs +++ b/openpgp-dsm/src/der.rs @@ -40,14 +40,14 @@ pub mod parse { // publicExponent INTEGER -- e // } // - yasna::parse_der(&pk.subject_public_key, |reader| { - Ok(reader.read_sequence(|reader| { + yasna::parse_der(pk.subject_public_key, |reader| { + reader.read_sequence(|reader| { let n = reader.next().read_biguint()?.to_bytes_be(); let e = reader.next().read_u32()?.to_be_bytes().to_vec(); Ok(RsaPub { n, e }) - })?) + }) }) - .map_err(|e| e.into()) + .map_err(|e| anyhow::anyhow!("ASN1 error: {:?}", e)) } pub fn rsa_private_edpq(buf: &[u8]) -> super::Result { @@ -65,7 +65,7 @@ pub mod parse { // coefficient INTEGER -- (inverse of q) mod p // } // - Ok(yasna::parse_der(&buf, |reader| { + yasna::parse_der(buf, |reader| { reader.read_sequence(|reader| { let _version = reader.next().read_u32()?; let _n = reader.next().read_biguint()?; @@ -79,7 +79,7 @@ pub mod parse { Ok(RsaPriv { e, d, p, q }) }) - })?) + }).map_err(|e| anyhow::anyhow!("ASN1 error: {:?}", e)) } pub fn ec_point_x_y(buf: &[u8]) -> super::Result<(Vec, Vec)> { @@ -116,25 +116,25 @@ pub mod parse { } pub fn ecdsa_r_s(buf: &[u8]) -> super::Result<(Vec, Vec)> { - Ok(yasna::parse_der(&buf, |reader| { + yasna::parse_der(buf, |reader| { reader.read_sequence(|reader| { let r = reader.next().read_biguint()?.to_bytes_be(); let s = reader.next().read_biguint()?.to_bytes_be(); Ok((r, s)) }) - })?) + }).map_err(|e| anyhow::anyhow!("ASN1 error: {:?}", e)) } pub fn ec_priv_scalar(buf: &[u8]) -> super::Result> { - Ok(yasna::parse_der(&buf, |reader| { - Ok(reader.read_sequence(|reader| { + yasna::parse_der(buf, |reader| { + reader.read_sequence(|reader| { let _version = reader.next().read_u32()?; let priv_key = reader.next().read_bytes()?; let _oid = reader.next().read_tagged_der()?; let _pk = reader.next().read_tagged_der()?; Ok(priv_key) - })?) - })?) + }) + }).map_err(|e| anyhow::anyhow!("ASN1 error: {:?}", e)) } } @@ -145,14 +145,101 @@ pub mod serialize { use yasna::models::ObjectIdentifier as Oid; use sequoia_openpgp::crypto::mpi; + use num::bigint::BigUint; + + pub fn rsa_private( + n: &mpi::MPI, + e: &mpi::MPI, + d: &mpi::ProtectedMPI, + p: &mpi::ProtectedMPI, + q: &mpi::ProtectedMPI, + u: &mpi::ProtectedMPI, + ) -> Vec { + let (n, e, d, p, q, u) = ( + BigUint::from_bytes_be(n.value()), + BigUint::from_bytes_be(e.value()), + BigUint::from_bytes_be(d.value()), + BigUint::from_bytes_be(p.value()), + BigUint::from_bytes_be(q.value()), + BigUint::from_bytes_be(u.value()), + ); + + // + // Note that `u` is defined in RFC4880bis 5.6.1 as p⁻¹ (mod q), + // whereas RFC8017 A.1.2 defines the CRT coefficient as q⁻¹ (mod p). + // We can conciliate both views noting that we know `u` such that + // + // up + qk = 1 + // k = - (up - 1)/q (mod p) + // k = p - ((up - 1)/q (mod p)) + // + let minus_k = ((u * p.clone() - 1u32) / q.clone()) % p.clone(); + let coeff = (p.clone() - minus_k) % p.clone(); // always well-defined + let e1 = d.clone() % (p.clone() - 1u32); + let e2 = d.clone() % (q.clone() - 1u32); + yasna::construct_der(|w| { + w.write_sequence(|w| { + w.next().write_u32(0); + for mpi in [&n, &e, &d, &p, &q, &e1, &e2, &coeff] { + w.next().write_biguint(mpi); + } + }) + }) + } + + pub fn ec_private_25519(curve: &Curve, x: Vec) -> Result> { + let opaque_octet_string = yasna::construct_der(|w| { + w.write_bytes(&x); + }); + + let oid = curve_oid(curve)?; + + // RFC8410 + Ok(yasna::construct_der(|w|{ + w.write_sequence(|w| { + w.next().write_u32(0); + w.next().write_sequence(|w| { + w.next().write_oid(&oid); + }); + w.next().write_bytes(&opaque_octet_string); + }); + })) + } + + pub fn ec_private( + curve: &Curve, + scalar: &mpi::ProtectedMPI, + ) -> Result> { + let mut d = scalar.value().to_vec(); + // For X25519, x is LITTLE ENDIAN! + if curve == &Curve::Cv25519 { + d.reverse(); + return ec_private_25519(curve, d); + } + if curve == &Curve::Ed25519 { + return ec_private_25519(curve, d); + } + + let oid = curve_oid(curve)?; + + // RFC5915 + Ok(yasna::construct_der(|w|{ + w.write_sequence(|w| { + w.next().write_u32(1); + w.next().write_bytes(&d); + w.next().write_tagged(yasna::Tag::context(0), |w| { + w.write_oid(&oid); + }); + // We ignore the public key serialization + }); + })) + } pub fn spki_ecdh(curve: &Curve, e: &mpi::MPI) -> Vec { match curve { Curve::Cv25519 => { let x = e.value(); - if x.len() == 0 { - unreachable!(); - } + assert!(!x.is_empty()); let x = x[1..].to_vec(); @@ -173,7 +260,7 @@ pub mod serialize { // let nist_oid = Oid::from_slice(&[1, 2, 840, 10045, 2, 1]); - let named_curve = curve_oid(&curve).expect("bad curve OID"); + let named_curve = curve_oid(curve).expect("bad curve OID"); let alg_id = yasna::construct_der(|writer| { writer.write_sequence(|writer| { @@ -182,7 +269,7 @@ pub mod serialize { }); }); - let subj_public_key = BitVec::from_bytes(&e.value()); + let subj_public_key = BitVec::from_bytes(e.value()); yasna::construct_der(|writer| { writer.write_sequence(|writer| { writer.next().write_der(&alg_id); @@ -199,7 +286,8 @@ pub mod serialize { Curve::NistP384 => Oid::from_slice(&[1, 3, 132, 0, 34]), Curve::NistP521 => Oid::from_slice(&[1, 3, 132, 0, 35]), Curve::Cv25519 => Oid::from_slice(&[1, 3, 101, 110]), - curve @ _ => { + Curve::Ed25519 => Oid::from_slice(&[1, 3, 101, 112]), + curve => { return Err(anyhow::anyhow!("unsupported curve {}", curve)); } }; diff --git a/openpgp-dsm/src/lib.rs b/openpgp-dsm/src/lib.rs index 381c81333..51c2c6c73 100644 --- a/openpgp-dsm/src/lib.rs +++ b/openpgp-dsm/src/lib.rs @@ -23,8 +23,8 @@ use std::borrow::Cow; use std::collections::HashMap; use std::convert::TryFrom; use std::env; -use std::io::Read; use std::fs::File; +use std::io::Read; use std::net::IpAddr; use std::str::FromStr; use std::sync::Arc; @@ -34,8 +34,8 @@ use anyhow::{Context, Error, Result}; use http::uri::Uri; use hyper::client::{Client as HyperClient, ProxyConfig}; use hyper::net::HttpsConnector; +use hyper_native_tls::native_tls::{Identity, TlsConnector}; use hyper_native_tls::NativeTlsClient; -use hyper_native_tls::native_tls::{TlsConnector, Identity}; use ipnetwork::IpNetwork; use log::info; use sdkms::api_model::Algorithm::Rsa; @@ -46,13 +46,19 @@ use sdkms::api_model::{ RsaOptions, RsaSignaturePaddingPolicy, RsaSignaturePolicy, SignRequest, SignResponse, Sobject, SobjectDescriptor, SobjectRequest, }; -use sdkms::{SdkmsClient as DsmClient, Error as DsmError, PendingApproval}; use sdkms::operations::Operation; +use sdkms::{Error as DsmError, PendingApproval, SdkmsClient as DsmClient}; use semver::{Version, VersionReq}; +use sequoia_openpgp::cert::ValidCert; use sequoia_openpgp::crypto::mem::Protected; -use sequoia_openpgp::crypto::{ecdh, mpi, Decryptor, SessionKey, Signer}; +use sequoia_openpgp::crypto::mpi::{ + Ciphertext as MpiCiphertext, ProtectedMPI, PublicKey as MpiPublic, + SecretKeyMaterial as MpiSecret, Signature as MpiSignature, MPI, +}; +use sequoia_openpgp::crypto::{ecdh, Decryptor, SessionKey, Signer}; use sequoia_openpgp::packet::key::{ - Key4, PrimaryRole, PublicParts, SubordinateRole, UnspecifiedRole, + Key4, KeyRole as SequoiaKeyRole, PrimaryRole, PublicParts, SecretParts, + SubordinateRole, UnspecifiedRole, }; use sequoia_openpgp::packet::prelude::SecretKeyMaterial; use sequoia_openpgp::packet::signature::SignatureBuilder; @@ -60,10 +66,10 @@ use sequoia_openpgp::packet::{Key, UserID}; use sequoia_openpgp::serialize::SerializeInto; use sequoia_openpgp::types::{ Curve as SequoiaCurve, Features, HashAlgorithm, KeyFlags, - PublicKeyAlgorithm, SignatureType, SymmetricAlgorithm, + PublicKeyAlgorithm, SignatureType, SymmetricAlgorithm, Timestamp, }; use sequoia_openpgp::{Cert, Packet}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use uuid::Uuid; mod der; @@ -167,7 +173,7 @@ impl Auth { .context("bad app UUID")?; Ok(Auth::Cert(uuid, p12_id)) } - (None, None) => return Err(Error::msg("no auth credentials found")), + (None, None) => Err(Error::msg("no auth credentials found")), } } } @@ -204,14 +210,14 @@ impl > + Display> OperateOrAskApproval for DsmClien &self, pa: &PendingApproval, desc: S ) -> Result { let id = pa.request_id(); - while pa.status(&self)? == ApprovalStatus::Pending { + while pa.status(self)? == ApprovalStatus::Pending { println!( "Approval request {} ({}) pending. Press Enter to check status", id, desc ); std::io::stdin().read_line(&mut String::new())?; } - match pa.result(&self) { + match pa.result(self) { Ok(output) => { println!("Approval request {} approved", id); output.map_err(|e|e.into()) @@ -320,7 +326,7 @@ impl Credentials { .with_hyper_client(Arc::new(hyper_client)) .build() .context("could not initiate a DSM client")?; - cli.authenticate_with_api_key(&api_key)? + cli.authenticate_with_api_key(api_key)? }, Auth::Cert(app_uuid, identity) => { let tls_conn = TlsConnector::builder() @@ -332,7 +338,7 @@ impl Credentials { .with_hyper_client(Arc::new(hyper_client)) .build() .context("could not initiate a DSM client")?; - cli.authenticate_with_cert(Some(&app_uuid))? + cli.authenticate_with_cert(Some(app_uuid))? } }; @@ -357,17 +363,17 @@ enum Role { } impl DsmAgent { - /// Returns a DsmAgent with signing capabilities, corresponding to the given - /// key name. - pub fn new_signer(credentials: Credentials, key_name: &str) -> Result { + /// Returns a DsmAgent with certifying capabilities, corresponding to the + /// primary key (flag "C"). + fn new_certifier(credentials: Credentials, key_name: &str) -> Result { let dsm_client = credentials.dsm_client()?; let descriptor = SobjectDescriptor::Name(key_name.to_string()); - let sobject = dsm_client + let prim_sob = dsm_client .get_sobject(None, &descriptor) .context(format!("could not get primary key {}", key_name))?; - let key = PublicKey::from_sobject(sobject, KeyRole::Primary)?; - + // Initialize Signer with primary key + let key = PublicKey::from_sobject(prim_sob, KeyRole::Primary)?; Ok(DsmAgent { credentials, descriptor, @@ -376,35 +382,91 @@ impl DsmAgent { }) } - /// Returns a DsmAgent with decryption capabilities, corresponding to the - /// given key name. - pub fn new_decryptor(credentials: Credentials, key_name: &str) -> Result { + /// Returns a DsmAgent with signing capabilities, corresponding to the first + /// key with key flag "S" found in DSM. + pub fn new_signer(credentials: Credentials, key_name: &str) -> Result { let dsm_client = credentials.dsm_client()?; + // Check if primary is "S" let descriptor = SobjectDescriptor::Name(key_name.to_string()); - let sobject = dsm_client + let prim_sob = dsm_client .get_sobject(None, &descriptor) .context(format!("could not get primary key {}", key_name))?; - - if let Some(KeyLinks { subkeys, .. }) = sobject.links { - if subkeys.len() == 0 { - return Err(Error::msg("No subkeys found in DSM")); + if let Some(flags) = KeyMetadata::from_sobject(&prim_sob)?.key_flags { + if flags.for_signing() { + // Initialize Signer with primary key + let key = PublicKey::from_sobject(prim_sob, KeyRole::Primary)?; + return Ok(DsmAgent { + credentials, + descriptor, + public: key.sequoia_key.context("key is not loaded")?, + role: Role::Signer, + }); } - let uid = subkeys[0]; + } + + // Loop through subkeys + let KeyLinks { subkeys, .. } + = prim_sob.links.ok_or(Error::msg("no subkeys found"))?; + for uid in subkeys { let descriptor = SobjectDescriptor::Kid(uid); - let sobject = dsm_client - .get_sobject(None, &descriptor) - .context("could not get subkey".to_string())?; - let key = PublicKey::from_sobject(sobject, KeyRole::Subkey)?; - Ok(DsmAgent { - credentials, - descriptor, - public: key.sequoia_key.context("key is not loaded")?, - role: Role::Decryptor, - }) - } else { - Err(Error::msg("was not able to get decryption subkey")) + let sub_sob = dsm_client + .get_sobject(None, &descriptor)?; + if let Some(flags) = KeyMetadata::from_sobject(&sub_sob)?.key_flags { + if flags.for_signing() { + // Initialize Signer with subkey + let key = PublicKey::from_sobject(sub_sob, KeyRole::Subkey)?; + return Ok(DsmAgent { + credentials, + descriptor, + public: key.sequoia_key.context("key is not loaded")?, + role: Role::Signer, + }) + } + } } + + Err(anyhow::anyhow!(format!("Found no suitable signing key in DSM"))) + } + + /// Returns several DsmAgents with decryption capabilities, corresponding to + /// all subkeys with key flag "Et" or "Er" found in DSM. + /// We assume that the primary key is ONLY "C" or "CS", so it will never be + /// used as a decryptor. + /// + /// NOTE: From RFC4880bis "[...] it is a thorny issue to determine what is + /// "communications" and what is "storage". This decision is left wholly up + /// to the implementation". + pub fn new_decryptors(credentials: Credentials, key_name: &str) -> Result> { + let mut decryptors = Vec::::new(); + + let dsm_client = credentials.dsm_client()?; + let prim_descriptor = SobjectDescriptor::Name(key_name.to_string()); + let prim_sobject = dsm_client + .get_sobject(None, &prim_descriptor) + .context(format!("could not get primary key {}", key_name))?; + + if let Some(KeyLinks { subkeys, .. }) = prim_sobject.links { + for uid in subkeys { + let descriptor = SobjectDescriptor::Kid(uid); + let sobject = dsm_client + .get_sobject(None, &descriptor) + .context("could not get subkey".to_string())?; + if let Some(kf) = KeyMetadata::from_sobject(&sobject)?.key_flags { + if kf.for_storage_encryption() || kf.for_transport_encryption() { + let key = PublicKey::from_sobject(sobject, KeyRole::Subkey)?; + decryptors.push(DsmAgent { + credentials: credentials.clone(), + descriptor, + public: key.sequoia_key.context("key is not loaded")?, + role: Role::Decryptor, + }); + } + } + } + } + + Ok(decryptors) } } @@ -427,13 +489,24 @@ enum KeyRole { Subkey, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Default)] struct KeyMetadata { - certificate: String, + sq_dsm_version: String, + fingerprint: String, + #[serde(skip_serializing_if = "Option::is_none")] + key_flags: Option, + #[serde(skip_serializing_if = "Option::is_none")] + certificate: Option, + #[serde(skip_serializing_if = "Option::is_none")] + external_creation_timestamp: Option, + #[serde(skip_serializing_if = "Option::is_none")] + hash_algo: Option, + #[serde(skip_serializing_if = "Option::is_none")] + symm_algo: Option, } impl KeyMetadata { - fn from_primary_sobject(sob: &Sobject) -> Result { + fn from_sobject(sob: &Sobject) -> Result { match &sob.custom_metadata { Some(dict) => { if !dict.contains_key(DSM_LABEL_PGP) { @@ -443,9 +516,17 @@ impl KeyMetadata { serde_json::from_str(&dict[DSM_LABEL_PGP])?; Ok(key_md) } - None => Err(anyhow::anyhow!("no custom metadata found")), + None => Err(anyhow::anyhow!("no metadata found on {:?}", sob.kid)) } } + + fn to_custom_metadata(&self) -> Result> { + let key_json = serde_json::to_string(&self)?; + let mut custom_metadata = HashMap::::new(); + custom_metadata.insert(DSM_LABEL_PGP.to_string(), key_json); + + Ok(custom_metadata) + } } /// Generates an OpenPGP key with secrets stored in DSM. At the OpenPGP @@ -468,6 +549,11 @@ pub fn generate_key( exportable: bool, credentials: Credentials, ) -> Result<()> { + + // Hash and symmetric algorithms for signatures/encryption + let hash_algo = HashAlgorithm::SHA512; + let symm_algo = SymmetricAlgorithm::AES256; + // User ID let uid: UserID = match user_id { Some(id) => id.into(), @@ -530,15 +616,17 @@ pub fn generate_key( .clone() .context("unloaded primary key")? .into(); + let prim_fingerprint = prim.fingerprint().to_hex(); + let prim_id = prim.keyid().to_hex(); let prim_creation_time = prim.creation_time(); - let mut prim_signer = DsmAgent::new_signer(credentials, &key_name)?; + let mut prim_signer = DsmAgent::new_certifier(credentials, key_name)?; - let primary_flags = KeyFlags::empty().set_certification().set_signing(); + let prim_flags = KeyFlags::empty().set_certification().set_signing(); let prim_sig_builder = SignatureBuilder::new(SignatureType::DirectKey) .set_features(Features::sequoia())? - .set_key_flags(primary_flags.clone())? + .set_key_flags(prim_flags.clone())? .set_key_validity_period(validity_period)? .set_signature_creation_time(prim_creation_time)? .set_preferred_symmetric_algorithms(vec![ @@ -549,7 +637,7 @@ pub fn generate_key( HashAlgorithm::SHA512, HashAlgorithm::SHA256, ])? - .set_hash_algo(HashAlgorithm::SHA512); + .set_hash_algo(hash_algo); // A direct key signature is always over the primary key. let prim_sig = prim_sig_builder.sign_direct_key(&mut prim_signer, None)?; @@ -564,7 +652,7 @@ pub fn generate_key( let builder = SignatureBuilder::new(SignatureType::PositiveCertification) .set_primary_userid(true)? .set_features(Features::sequoia())? - .set_key_flags(primary_flags)? + .set_key_flags(prim_flags.clone())? .set_key_validity_period(validity_period)? .set_signature_creation_time(prim_creation_time)? .set_preferred_symmetric_algorithms(vec![ @@ -595,33 +683,68 @@ pub fn generate_key( let builder = SignatureBuilder::new(SignatureType::SubkeyBinding) .set_key_validity_period(validity_period)? - .set_hash_algo(HashAlgorithm::SHA512) + .set_hash_algo(hash_algo) .set_signature_creation_time(subkey_creation_time)? - .set_key_flags(subkey_flags)?; + .set_key_flags(subkey_flags.clone())?; + let subkey_fingerprint = subkey_public.fingerprint().to_hex(); + let subkey_id = subkey_public.keyid().to_hex(); let signature = subkey_public.bind(&mut prim_signer, &cert, builder)?; cert = cert .insert_packets(vec![Packet::from(subkey_public), signature.into()])?; - info!("store certificate in DSM"); - let armored = String::from_utf8(cert.armored().to_vec()?)?; - let key_md = KeyMetadata { - certificate: armored, - }; - - let key_json = serde_json::to_string(&key_md)?; - let mut metadata = HashMap::::new(); - metadata.insert(DSM_LABEL_PGP.to_string(), key_json); - - let update_req = SobjectRequest { - custom_metadata: Some(metadata), - ..Default::default() - }; + info!("Generation: store primary metadata"); + { + let primary_desc = format!( + "PGP primary, {}, {}", prim_id, prim_flags.human_readable() + ); + let prim_metadata = KeyMetadata { + sq_dsm_version: SQ_DSM_VERSION.to_string(), + fingerprint: prim_fingerprint, + key_flags: Some(prim_flags), + certificate: Some(String::from_utf8(cert.armored().to_vec()?)?), + ..Default::default() + }.to_custom_metadata()?; + let update_req = SobjectRequest { + description: Some(primary_desc), + custom_metadata: Some(prim_metadata), + ..Default::default() + }; + dsm_client.__update_sobject( + &primary.uid()?, &update_req, "store PGP certificate as metadata" + )?; + } - dsm_client.__update_sobject( - &primary.uid()?, &update_req, "store PGP certificate as metadata" - )?; + info!("Generation: rename subkey and store metadata"); + { + let subkey_name = format!( + "{} {}", key_name, subkey_id + ); + let subkey_desc = format!( + "PGP subkey of {}, {}", prim_id, subkey_flags.human_readable() + ); + let key_json = serde_json::to_string(&KeyMetadata { + sq_dsm_version: SQ_DSM_VERSION.to_string(), + fingerprint: subkey_fingerprint, + key_flags: Some(subkey_flags), + certificate: None, + external_creation_timestamp: None, + hash_algo: Some(hash_algo), + symm_algo: Some(symm_algo), + })?; + let mut sub_metadata = HashMap::::new(); + sub_metadata.insert(DSM_LABEL_PGP.to_string(), key_json); + let update_req = SobjectRequest { + name: Some(subkey_name), + description: Some(subkey_desc), + custom_metadata: Some(sub_metadata), + ..Default::default() + }; + dsm_client.__update_sobject( + &subkey.uid()?, &update_req, "store PGP certificate as metadata" + )?; + } Ok(()) } @@ -633,23 +756,14 @@ pub fn extract_cert(key_name: &str, cred: Credentials) -> Result { info!("dsm extract_cert"); let dsm_client = cred.dsm_client()?; - let metadata = { - let sobject = dsm_client - .get_sobject(None, &SobjectDescriptor::Name(key_name.to_string())) - .context(format!("could not get primary key {}", key_name))?; - - match sobject.custom_metadata { - None => return Err(Error::msg("metadata not found".to_string())), - Some(dict) => dict, - } - }; - if !metadata.contains_key(DSM_LABEL_PGP) { - return Err(Error::msg("malformed metadata in DSM".to_string())); - } - - let key_md: KeyMetadata = serde_json::from_str(&metadata[DSM_LABEL_PGP])?; + let sobject = dsm_client + .get_sobject(None, &SobjectDescriptor::Name(key_name.to_string())) + .context(format!("could not get primary key {}", key_name))?; - Ok(Cert::from_str(&key_md.certificate)?) + Cert::from_str( + &KeyMetadata::from_sobject(&sobject)?.certificate + .ok_or(anyhow::anyhow!("no certificate in DSM custom metadata"))? + ) } pub fn extract_tsk_from_dsm(key_name: &str, cred: Credentials) -> Result { @@ -665,7 +779,7 @@ pub fn extract_tsk_from_dsm(key_name: &str, cred: Credentials) -> Result { "export primary key", ) .context(format!("could not export primary secret {}", key_name))?; - let key_md = KeyMetadata::from_primary_sobject(&prim_sob)?; + let key_md = KeyMetadata::from_sobject(&prim_sob)?; let packet = secret_packet_from_sobject(&prim_sob, KeyRole::Primary)?; packets.push(packet); @@ -687,7 +801,10 @@ pub fn extract_tsk_from_dsm(key_name: &str, cred: Credentials) -> Result { // Merge with the known public certificate let priv_cert = Cert::try_from(packets)?; - let cert = Cert::from_str(&key_md.certificate)?; + let cert = Cert::from_str( + &key_md.certificate + .ok_or(anyhow::anyhow!("no certificate in DSM custom metadata"))? + )?; let merged = cert .merge_public_and_secret(priv_cert) .context("Could not merge public and private certificates")?; @@ -695,6 +812,244 @@ pub fn extract_tsk_from_dsm(key_name: &str, cred: Credentials) -> Result { Ok(merged) } +/// Imports a given Transferable Secret Key to DSM. +pub fn import_tsk_to_dsm( + tsk: ValidCert, + key_name: &str, + cred: Credentials, + exportable: bool, +) -> Result<()> { + + fn import_constructed_sobject( + cred: &Credentials, + name: String, + desc: String, + ops: KeyOperations, + metadata: &mut KeyMetadata, + mpis: &MpiPublic, + hazmat: &MpiSecret, + ) -> Result { + let req = match (mpis, hazmat) { + (MpiPublic::RSA{ e, n }, MpiSecret::RSA { d, p, q, u }) => { + let value = der::serialize::rsa_private(n, e, &d, &p, &q, &u); + let key_size = n.bits() as u32; + let rsa_opts = if ops.contains(KeyOperations::SIGN) { + let sig_policy = RsaSignaturePolicy { + padding: Some(RsaSignaturePaddingPolicy::Pkcs1V15 {}), + }; + Some(RsaOptions { + signature_policy: vec![sig_policy], + ..Default::default() + }) + } else if ops.contains(KeyOperations::DECRYPT) { + let enc_policy = RsaEncryptionPolicy { + padding: Some(RsaEncryptionPaddingPolicy::Pkcs1V15 {}), + }; + Some(RsaOptions { + encryption_policy: vec![enc_policy], + ..Default::default() + }) + } else { + None + }; + + SobjectRequest { + name: Some(name.clone()), + custom_metadata: Some(metadata.to_custom_metadata()?), + description: Some(desc), + obj_type: Some(ObjectType::Rsa), + key_ops: Some(ops), + key_size: Some(key_size), + rsa: rsa_opts, + value: Some(value.into()), + ..Default::default() + } + }, + (MpiPublic::EdDSA { curve, .. }, MpiSecret::EdDSA { scalar }) => { + let value = der::serialize::ec_private(&curve, &scalar)?; + SobjectRequest { + name: Some(name.clone()), + custom_metadata: Some(metadata.to_custom_metadata()?), + description: Some(desc), + obj_type: Some(ObjectType::Ec), + key_ops: Some(ops), + value: Some(value.into()), + ..Default::default() + } + }, + (MpiPublic::ECDSA { curve, .. }, MpiSecret::ECDSA { scalar }) => { + let value = der::serialize::ec_private(&curve, &scalar)?; + SobjectRequest { + name: Some(name.clone()), + custom_metadata: Some(metadata.to_custom_metadata()?), + description: Some(desc), + obj_type: Some(ObjectType::Ec), + key_ops: Some(ops), + value: Some(value.into()), + ..Default::default() + } + }, + (MpiPublic::ECDH { curve, q: _, hash, sym }, MpiSecret::ECDH { scalar }) => { + let value = der::serialize::ec_private(&curve, &scalar)?; + metadata.hash_algo = Some(*hash); + metadata.symm_algo = Some(*sym); + SobjectRequest { + name: Some(name.clone()), + custom_metadata: Some(metadata.to_custom_metadata()?), + description: Some(desc), + obj_type: Some(ObjectType::Ec), + key_ops: Some(ops), + value: Some(value.into()), + ..Default::default() + } + }, + _ => unimplemented!("public key algorithm") + }; + + Ok( + cred.dsm_client()? + .import_sobject(&req) + .context(format!("could not import secret {}", name))? + .kid + .ok_or(anyhow::anyhow!("no UUID returned from DSM"))? + ) + } + + fn get_hazardous_material(key: &Key) + -> MpiSecret { + // HAZMAT: Decrypt MPIs from protected memory and form DER + if let SecretKeyMaterial::Unencrypted(mpis) = key.secret() { + mpis.map::<_, MpiSecret>(|crypt| {crypt.clone()}) + } else { + unreachable!("mpis are encrypted") + } + } + + fn get_operations( + key_flags: Option, + pk_algo: PublicKeyAlgorithm, + exportable: bool, + ) -> KeyOperations { + let mut ops = KeyOperations::APPMANAGEABLE; + + if exportable { + ops |= KeyOperations::EXPORT; + } + + if let Some(f) = key_flags { + if f.for_signing() | f.for_certification() { + ops |= KeyOperations::SIGN; + } + + if f.for_transport_encryption() | f.for_storage_encryption() { + if pk_algo == PublicKeyAlgorithm::ECDH { + ops |= KeyOperations::AGREEKEY; + } else { + ops |= KeyOperations::DECRYPT; + } + } + } + + ops + } + + let prim_key = tsk.primary_key(); + let primary = prim_key.key().clone().parts_into_secret()?; + + let creation_time = Timestamp::try_from(primary.creation_time())?; + let prim_flags = tsk.primary_key() + .key_flags() + .ok_or_else(|| anyhow::anyhow!("Bad input: primary has no key flags"))?; + let prim_id = prim_key.keyid().to_hex(); + let prim_name = key_name.to_string(); + let prim_desc = format!( + "PGP primary, {}, {}", prim_id, prim_flags.human_readable() + ); + + let armored = String::from_utf8(tsk.cert().armored().to_vec()?)?; + let mut prim_metadata = KeyMetadata { + sq_dsm_version: SQ_DSM_VERSION.to_string(), + fingerprint: prim_key.fingerprint().to_hex(), + key_flags: Some(prim_flags), + certificate: Some(armored), + external_creation_timestamp: Some(creation_time.into()), + ..Default::default() + }; + + let prim_ops = get_operations( + prim_key.key_flags(), + prim_key.pk_algo(), + exportable + ); + + let prim_hazmat = get_hazardous_material(&primary); + + let prim_uuid = import_constructed_sobject( + &cred, + prim_name, + prim_desc, + prim_ops, + &mut prim_metadata, + &primary.mpis(), + &prim_hazmat, + )?; + + for subkey in tsk.keys().subkeys().unencrypted_secret() { + let creation_time = Timestamp::try_from(subkey.creation_time())?; + let subkey_flags = subkey.key_flags().unwrap_or(KeyFlags::empty()); + let subkey_id = subkey.keyid().to_hex(); + let subkey_name = format!( + "{} {}/{}", key_name, prim_id, subkey_id, + ).to_string(); + let subkey_desc = format!( + "PGP subkey, {}", subkey_flags.human_readable() + ); + + let mut subkey_md = KeyMetadata { + certificate: None, + sq_dsm_version: SQ_DSM_VERSION.to_string(), + external_creation_timestamp: Some(creation_time.into()), + fingerprint: subkey.fingerprint().to_hex(), + key_flags: Some(subkey_flags), + ..Default::default() + }; + + let subkey_ops = get_operations( + subkey.key_flags(), + subkey.pk_algo(), + exportable + ); + + let subkey_hazmat = get_hazardous_material(&subkey); + + info!("import subkey {}", subkey_name); + let subkey_uuid = import_constructed_sobject( + &cred, + subkey_name.clone(), + subkey_desc, + subkey_ops, + &mut subkey_md, + &subkey.mpis(), + &subkey_hazmat, + )?; + + info!("bind subkey {} to primary key in DSM", subkey_name); + let link_req = SobjectRequest { + links: Some(KeyLinks { + parent: Some(prim_uuid), + ..Default::default() + }), + ..Default::default() + }; + + cred.dsm_client()?.__update_sobject( + &subkey_uuid, &link_req, "bind subkey to primary key" + )?; + } + + Ok(()) +} + impl PublicKey { fn create( client: &DsmClient, @@ -703,8 +1058,6 @@ impl PublicKey { algo: &SupportedPkAlgo, exportable: bool, ) -> Result { - let description = Some("Created with sq-dsm".to_string()); - let mut sobject_request = match (&role, algo) { (KeyRole::Primary, SupportedPkAlgo::Rsa(key_size)) => { let sig_policy = RsaSignaturePolicy { @@ -717,7 +1070,6 @@ impl PublicKey { SobjectRequest { name: Some(name), - description, obj_type: Some(ObjectType::Rsa), key_ops: Some( KeyOperations::SIGN | KeyOperations::APPMANAGEABLE, @@ -738,7 +1090,6 @@ impl PublicKey { SobjectRequest { name: Some(name + " (PGP: decryption subkey)"), - description, obj_type: Some(ObjectType::Rsa), key_ops: Some( KeyOperations::DECRYPT | KeyOperations::APPMANAGEABLE, @@ -750,7 +1101,6 @@ impl PublicKey { } (KeyRole::Primary, SupportedPkAlgo::Curve25519) => SobjectRequest { name: Some(name), - description, obj_type: Some(ObjectType::Ec), key_ops: Some( KeyOperations::SIGN | KeyOperations::APPMANAGEABLE, @@ -763,7 +1113,6 @@ impl PublicKey { SobjectRequest { name: Some(name), - description, obj_type: Some(ObjectType::Ec), key_ops: Some( KeyOperations::AGREEKEY | KeyOperations::APPMANAGEABLE, @@ -774,7 +1123,6 @@ impl PublicKey { } (KeyRole::Primary, SupportedPkAlgo::Ec(curve)) => SobjectRequest { name: Some(name), - description, obj_type: Some(ObjectType::Ec), key_ops: Some( KeyOperations::SIGN | KeyOperations::APPMANAGEABLE, @@ -784,7 +1132,6 @@ impl PublicKey { }, (KeyRole::Subkey, SupportedPkAlgo::Ec(curve)) => SobjectRequest { name: Some(name + " (PGP: decryption subkey)"), - description, obj_type: Some(ObjectType::Ec), key_ops: Some( KeyOperations::AGREEKEY | KeyOperations::APPMANAGEABLE, @@ -806,37 +1153,45 @@ impl PublicKey { } fn from_sobject(sob: Sobject, role: KeyRole) -> Result { + let default_hash = HashAlgorithm::SHA512; + let default_symm = SymmetricAlgorithm::AES256; let descriptor = SobjectDescriptor::Kid(sob.kid.context("no kid")?); - let time: SystemTime = sob.created_at.to_datetime().into(); + + // Newly created Sobjects don't have metadata yet + let md = KeyMetadata::from_sobject(&sob) + .unwrap_or(KeyMetadata::default()); + let time: SystemTime = if let Some(secs) = md.external_creation_timestamp { + Timestamp::from(secs).into() + } else { + sob.created_at.to_datetime().into() + }; + let raw_pk = sob.pub_key.context("public bits of sobject missing")?; let (pk_algo, pk_material, role) = match sob.obj_type { ObjectType::Ec => match sob.elliptic_curve { Some(ApiCurve::Ed25519) => { - if role == KeyRole::Subkey { - return Err(Error::msg("signing subkeys unsupported")); - } let pk_algo = PublicKeyAlgorithm::EdDSA; let curve = SequoiaCurve::Ed25519; // Strip the leading OID - let point = mpi::MPI::new_compressed_point(&raw_pk[12..]); + let point = MPI::new_compressed_point(&raw_pk[12..]); - let ec_pk = mpi::PublicKey::EdDSA { curve, q: point }; - (pk_algo, ec_pk, KeyRole::Primary) + let ec_pk = MpiPublic::EdDSA { curve, q: point }; + (pk_algo, ec_pk, role) } Some(ApiCurve::X25519) => { let pk_algo = PublicKeyAlgorithm::ECDH; let curve = SequoiaCurve::Cv25519; // Strip the leading OID - let point = mpi::MPI::new_compressed_point(&raw_pk[12..]); + let point = MPI::new_compressed_point(&raw_pk[12..]); - let ec_pk = mpi::PublicKey::ECDH { + let ec_pk = MpiPublic::ECDH { curve, q: point, - hash: HashAlgorithm::SHA512, - sym: SymmetricAlgorithm::AES256, + hash: md.hash_algo.unwrap_or(default_hash), + sym: md.symm_algo.unwrap_or(default_symm), }; (pk_algo, ec_pk, KeyRole::Subkey) @@ -849,19 +1204,19 @@ impl PublicKey { let bits_field = curve.bits() .ok_or_else(|| Error::msg("bad curve"))?; - let point = mpi::MPI::new_point(&x, &y, bits_field); + let point = MPI::new_point(&x, &y, bits_field); let (pk_algo, ec_pk) = match role { KeyRole::Primary => ( PublicKeyAlgorithm::ECDSA, - mpi::PublicKey::ECDSA { curve, q: point }, + MpiPublic::ECDSA { curve, q: point }, ), KeyRole::Subkey => ( PublicKeyAlgorithm::ECDH, - mpi::PublicKey::ECDH { + MpiPublic::ECDH { curve, q: point, - hash: HashAlgorithm::SHA512, - sym: SymmetricAlgorithm::AES256, + hash: md.hash_algo.unwrap_or(default_hash), + sym: md.symm_algo.unwrap_or(default_symm), }, ), }; @@ -879,17 +1234,15 @@ impl PublicKey { }, ObjectType::Rsa => { let pk = der::parse::rsa_n_e(&raw_pk)?; - let pk_material = mpi::PublicKey::RSA { + let pk_material = MpiPublic::RSA { e: pk.e.into(), n: pk.n.into() }; let pk_algo = PublicKeyAlgorithm::RSAEncryptSign; (pk_algo, pk_material, role) - } - t @ _ => { - return Err(Error::msg(format!("unknown object : {:?}", t))); - } + }, + t => { return Err(Error::msg(format!("unknown object: {:?}", t))); } }; let key = Key::V4( @@ -919,7 +1272,7 @@ impl Signer for DsmAgent { &mut self, hash_algo: HashAlgorithm, digest: &[u8], - ) -> Result { + ) -> Result { if self.role != Role::Signer { return Err(Error::msg("bad role for DSM agent")); } @@ -930,9 +1283,7 @@ impl Signer for DsmAgent { HashAlgorithm::SHA1 => DigestAlgorithm::Sha1, HashAlgorithm::SHA512 => DigestAlgorithm::Sha512, HashAlgorithm::SHA256 => DigestAlgorithm::Sha256, - hash @ _ => { - panic!("unimplemented: {}", hash); - } + hash => panic!("unimplemented: {}", hash), }; match self.public.pk_algo() { @@ -949,7 +1300,7 @@ impl Signer for DsmAgent { .context("bad response for signature request")?; let plain: Vec = sign_resp.signature.into(); - Ok(mpi::Signature::RSA { s: plain.into() }) + Ok(MpiSignature::RSA { s: plain.into() }) } PublicKeyAlgorithm::EdDSA => { let sign_req = SignRequest { @@ -964,9 +1315,9 @@ impl Signer for DsmAgent { .context("bad response for signature request")?; let plain: Vec = sign_resp.signature.into(); - Ok(mpi::Signature::EdDSA { - r: mpi::MPI::new(&plain[..32]), - s: mpi::MPI::new(&plain[32..]), + Ok(MpiSignature::EdDSA { + r: MPI::new(&plain[..32]), + s: MPI::new(&plain[32..]), }) } PublicKeyAlgorithm::ECDSA => { @@ -985,14 +1336,12 @@ impl Signer for DsmAgent { let (r, s) = der::parse::ecdsa_r_s(&plain) .context("could not decode ECDSA der")?; - Ok(mpi::Signature::ECDSA { + Ok(MpiSignature::ECDSA { r: r.to_vec().into(), s: s.to_vec().into(), }) } - algo @ _ => { - return Err(Error::msg(format!("unknown algo: {}", algo))); - } + algo => Err(Error::msg(format!("unknown algo: {}", algo))) } } } @@ -1002,7 +1351,7 @@ impl Decryptor for DsmAgent { fn decrypt( &mut self, - ciphertext: &mpi::Ciphertext, + ciphertext: &MpiCiphertext, _plaintext_len: Option, ) -> Result { if self.role != Role::Decryptor { @@ -1013,7 +1362,7 @@ impl Decryptor for DsmAgent { .context("could not initialize the http client")?; match ciphertext { - mpi::Ciphertext::RSA { c } => { + MpiCiphertext::RSA { c } => { let decrypt_req = DecryptRequest { cipher: c.value().to_vec().into(), alg: Some(Rsa), @@ -1030,9 +1379,9 @@ impl Decryptor for DsmAgent { .plain.to_vec().into() ) } - mpi::Ciphertext::ECDH { e, .. } => { + MpiCiphertext::ECDH { e, .. } => { let curve = match &self.public.mpis() { - mpi::PublicKey::ECDH { curve, .. } => curve, + MpiPublic::ECDH { curve, .. } => curve, _ => panic!("inconsistent pk algo"), }; @@ -1072,7 +1421,7 @@ impl Decryptor for DsmAgent { name: None, group_id: None, key_type: ObjectType::Secret, - key_size: curve_key_size(&curve).context("size")?, + key_size: curve_key_size(curve).context("size")?, enabled: true, description: None, custom_metadata: None, @@ -1111,7 +1460,14 @@ fn secret_packet_from_sobject( sobject: &Sobject, role: KeyRole, ) -> Result { - let time: SystemTime = sobject.created_at.to_datetime().into(); + let md = KeyMetadata::from_sobject(sobject)?; + + let time: SystemTime = if let Some(secs) = md.external_creation_timestamp { + Timestamp::from(secs).into() + } else { + sobject.created_at.to_datetime().into() + }; + let raw_secret = sobject.value.as_ref() .context("secret bits missing in Sobject")?; let raw_public = sobject.pub_key.as_ref() @@ -1146,15 +1502,12 @@ fn secret_packet_from_sobject( } let secret = &raw_secret[16..]; match role { - KeyRole::Primary => Ok(Key::V4( - Key4::<_, PrimaryRole>::import_secret_cv25519( - secret, None, None, time, - )?, - ) - .into()), + KeyRole::Primary => { + Err(anyhow::anyhow!("primary keys can't decrypt")) + }, KeyRole::Subkey => Ok(Key::V4( Key4::<_, SubordinateRole>::import_secret_cv25519( - secret, None, None, time, + secret, md.hash_algo, md.symm_algo, time, )?, ) .into()), @@ -1167,28 +1520,28 @@ fn secret_packet_from_sobject( let curve = sequoia_curve_from_api_curve(curve)?; let bits_field = curve.bits() .ok_or_else(|| Error::msg("bad curve"))?; - let (x, y) = der::parse::ec_point_x_y(&raw_public)?; - let point = mpi::MPI::new_point(&x, &y, bits_field); + let (x, y) = der::parse::ec_point_x_y(raw_public)?; + let point = MPI::new_point(&x, &y, bits_field); // Secret - let scalar: mpi::ProtectedMPI = der::parse::ec_priv_scalar( - &raw_secret - )?.into(); + let scalar: ProtectedMPI = der::parse::ec_priv_scalar( + raw_secret + )?.into(); let algo: PublicKeyAlgorithm; let secret: SecretKeyMaterial; - let public: mpi::PublicKey; + let public: MpiPublic; if is_signer { algo = PublicKeyAlgorithm::ECDSA; - secret = mpi::SecretKeyMaterial::ECDSA { scalar }.into(); - public = mpi::PublicKey::ECDSA { curve, q: point }; + secret = MpiSecret::ECDSA { scalar }.into(); + public = MpiPublic::ECDSA { curve, q: point }; } else { algo = PublicKeyAlgorithm::ECDH; - secret = mpi::SecretKeyMaterial::ECDH { scalar }.into(); - public = mpi::PublicKey::ECDH { + secret = MpiSecret::ECDH { scalar }.into(); + public = MpiPublic::ECDH { curve, + hash: md.hash_algo.unwrap_or(HashAlgorithm::SHA512), + sym: md.symm_algo.unwrap_or(SymmetricAlgorithm::AES256), q: point, - hash: HashAlgorithm::SHA512, - sym: SymmetricAlgorithm::AES256, }; }; match role { @@ -1209,7 +1562,7 @@ fn secret_packet_from_sobject( _ => unimplemented!(), }, ObjectType::Rsa => { - let sk = der::parse::rsa_private_edpq(&raw_secret)?; + let sk = der::parse::rsa_private_edpq(raw_secret)?; match role { KeyRole::Primary => Ok(Key::V4( Key4::<_, PrimaryRole>::import_secret_rsa_unchecked_e( @@ -1250,7 +1603,7 @@ impl NoProxy { pub fn parse(no_proxy: &str) -> Self { Self( no_proxy - .split(",") + .split(',') .filter_map(|no_proxy| no_proxy.parse().ok()) .collect(), ) @@ -1304,8 +1657,8 @@ impl FromStr for NoProxyEntry { fn from_str(s: &str) -> ::std::result::Result { // split host and port from no_proxy - let mut no_proxy_splits = s.trim().splitn(2, ":"); - let no_proxy_host = no_proxy_splits.next().ok_or_else(|| ())?; + let mut no_proxy_splits = s.trim().splitn(2, ':'); + let no_proxy_host = no_proxy_splits.next().ok_or(())?; let no_proxy_port = no_proxy_splits.next().and_then(|port| port.parse().ok()); @@ -1315,7 +1668,7 @@ impl FromStr for NoProxyEntry { Ok(NoProxyEntry { ipnetwork: IpNetwork::from_str(no_proxy_host).ok(), split_hostname: no_proxy_host - .split(".") + .split('.') .map(String::from) .collect(), port: no_proxy_port, @@ -1349,10 +1702,10 @@ fn maybe_proxied(endpoint: &str, ssl: NativeTlsClient) -> Result { } let https_conn = HttpsConnector::new(ssl); - if let Some((proxy_host, proxy_port)) = decide_proxy_from_env(&endpoint) { + if let Some((proxy_host, proxy_port)) = decide_proxy_from_env(endpoint) { Ok(HyperClient::with_proxy_config(ProxyConfig::new( "http", - proxy_host.to_string(), + proxy_host, proxy_port, https_conn, NativeTlsClient::new()?, @@ -1368,7 +1721,7 @@ fn curve_key_size(curve: &SequoiaCurve) -> Result { SequoiaCurve::NistP256 => Ok(256), SequoiaCurve::NistP384 => Ok(384), SequoiaCurve::NistP521 => Ok(521), - curve @ _ => Err(Error::msg(format!("unsupported curve {}", curve))), + curve => Err(Error::msg(format!("unsupported curve {}", curve))), } } @@ -1390,7 +1743,7 @@ fn api_curve_from_sequoia_curve(curve: SequoiaCurve) -> Result { SequoiaCurve::NistP256 => Ok(ApiCurve::NistP256), SequoiaCurve::NistP384 => Ok(ApiCurve::NistP384), SequoiaCurve::NistP521 => Ok(ApiCurve::NistP521), - curve @ _ => Err(Error::msg(format!("unsupported curve {}", curve))), + curve => Err(Error::msg(format!("unsupported curve {}", curve))), } } @@ -1429,3 +1782,36 @@ fn try_unlock_p12(cert_file: String) -> Result { } } } + +trait HumanReadable { + fn human_readable(&self) -> String; +} + +impl HumanReadable for KeyFlags { + fn human_readable(&self) -> String { + let mut s = Vec::new(); + if self.for_certification() { + s.push("Certification"); + } + if self.for_signing() { + s.push("Signing"); + } + match (self.for_transport_encryption(), self.for_storage_encryption()) { + (true, true) => s.push("Transport and Storage Encryption"), + (true, false) => s.push("Transport Encryption"), + (false, true) => s.push("Storage Encryption"), + _ => {} + } + if self.is_split_key() { + s.push("Split Key"); + } + if self.for_authentication() { + s.push("Authentication"); + } + if self.is_group_key() { + s.push("Group Key"); + } + + s.join(", ") + } +} diff --git a/openpgp/Cargo.toml b/openpgp/Cargo.toml index bf6fe9169..420a4a9bb 100644 --- a/openpgp/Cargo.toml +++ b/openpgp/Cargo.toml @@ -41,6 +41,7 @@ memsec = { version = ">=0.5", default-features = false } nettle = { version = "7.0.2", optional = true } regex = "1" regex-syntax = "0.6" +serde = { version = "1.0", features = ["derive"] } sha1collisiondetection = { version = "0.2.3", default-features = false, features = ["std"] } thiserror = "1.0.2" xxhash-rust = { version = "0.8", features = ["xxh3"] } diff --git a/openpgp/src/types/bitfield.rs b/openpgp/src/types/bitfield.rs index 9fce02c36..61b8d09ef 100644 --- a/openpgp/src/types/bitfield.rs +++ b/openpgp/src/types/bitfield.rs @@ -1,5 +1,7 @@ +use serde::{Deserialize, Serialize}; + /// A bitfield. -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub(crate) struct Bitfield { pub(crate) raw: Vec, } diff --git a/openpgp/src/types/key_flags.rs b/openpgp/src/types/key_flags.rs index 933df1eab..176cce83b 100644 --- a/openpgp/src/types/key_flags.rs +++ b/openpgp/src/types/key_flags.rs @@ -1,6 +1,8 @@ use std::fmt; use std::ops::{BitAnd, BitOr}; +use serde::{Deserialize, Serialize}; + #[cfg(test)] use quickcheck::{Arbitrary, Gen}; @@ -47,7 +49,7 @@ use crate::types::Bitfield; /// } /// # Ok(()) } /// ``` -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct KeyFlags(Bitfield); assert_send_and_sync!(KeyFlags); diff --git a/openpgp/src/types/mod.rs b/openpgp/src/types/mod.rs index ba50cccde..90f7f0b65 100644 --- a/openpgp/src/types/mod.rs +++ b/openpgp/src/types/mod.rs @@ -46,6 +46,8 @@ use std::fmt; use std::str::FromStr; use std::result; +use serde::{Deserialize, Serialize}; + #[cfg(test)] use quickcheck::{Arbitrary, Gen}; @@ -533,7 +535,7 @@ impl Arbitrary for Curve { /// # Ok(()) } /// ``` #[non_exhaustive] -#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug, PartialOrd, Ord)] +#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug, PartialOrd, Ord, Serialize, Deserialize)] pub enum SymmetricAlgorithm { /// Null encryption. Unencrypted, @@ -929,7 +931,7 @@ impl Arbitrary for CompressionAlgorithm { /// /// [Section 9.4 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-9.4 #[non_exhaustive] -#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug, PartialOrd, Ord)] +#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug, PartialOrd, Ord, Serialize, Deserialize)] pub enum HashAlgorithm { /// Rivest et.al. message digest 5. MD5, diff --git a/sq/Makefile b/sq/Makefile index 968630f80..8a8573fdd 100644 --- a/sq/Makefile +++ b/sq/Makefile @@ -36,7 +36,9 @@ install: build-release $(MAKE) -C../store install test-dsm: - cargo run -- --version + cargo build + ./tests/dsm/knownkeys_import_dsm.sh + ./tests/dsm/generate_gpg_import_dsm_auto.tcl ./tests/dsm/key_expiration.sh -c rsa2k ./tests/dsm/key_expiration.sh -c rsa3k ./tests/dsm/key_expiration.sh -c rsa4k diff --git a/sq/src/commands/decrypt.rs b/sq/src/commands/decrypt.rs index 7e203a67f..836b860a8 100644 --- a/sq/src/commands/decrypt.rs +++ b/sq/src/commands/decrypt.rs @@ -219,10 +219,12 @@ impl<'a> DecryptionHelper for Helper<'a> { { for dsm_key in &self.dsm_keys_presecrets { for pkesk in pkesks { - let decryptor = DsmAgent::new_decryptor(dsm_key.0.clone(), &dsm_key.1)?; - if let Some(fp) = self.try_decrypt(pkesk, sym_algo, Box::new(decryptor), + for decryptor in DsmAgent::new_decryptors(dsm_key.0.clone(), &dsm_key.1)? { + // TODO: This could be parallelized + if let Some(fp) = self.try_decrypt(pkesk, sym_algo, Box::new(decryptor), &mut decrypt) { - return Ok(fp); + return Ok(fp); + } } } } diff --git a/sq/src/commands/key.rs b/sq/src/commands/key.rs index dda4209ef..cdabb3705 100644 --- a/sq/src/commands/key.rs +++ b/sq/src/commands/key.rs @@ -30,6 +30,7 @@ pub fn dispatch(config: Config, m: &clap::ArgMatches) -> Result<()> { match m.subcommand() { ("generate", Some(m)) => generate(config, m)?, ("export", Some(m)) => generate(config, m)?, + ("dsm-import", Some(m)) => dsm_import(config, m)?, ("password", Some(m)) => password(config, m)?, ("extract-cert", Some(m)) => extract_cert(config, m)?, ("extract-dsm-secret", Some(m)) => extract_dsm(config, m)?, @@ -81,14 +82,18 @@ fn generate(config: Config, m: &ArgMatches) -> Result<()> { m.value_of("client-cert"), m.value_of("app-uuid"), )?; - return dsm::generate_key( + println!("Generating keys inside inside Fortanix DSM. This might take a while..."); + 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)?, - ); + )?; + println!("OK"); + + return Ok(()) } // Cipher Suite @@ -294,6 +299,31 @@ fn _password(config: Config, m: &ArgMatches, key: Cert) -> Result<()> { Ok(()) } +// Unlocks a cert with a passphrase +fn _unlock(key: Cert) -> Result { + if ! key.is_tsk() { + return Err(anyhow::anyhow!("Input is not a Transferable Secret Key")); + } + + // Decrypt all secrets. + let passwords = &mut Vec::new(); + let mut decrypted: Vec = vec![decrypt_key( + key.primary_key().key().clone().parts_into_secret()?, + passwords, + )? + .into()]; + for ka in key.keys().subkeys().secret() { + decrypted.push(decrypt_key( + ka.key().clone().parts_into_secret()?, + passwords)?.into()); + } + let key = key.insert_packets(decrypted)?; + assert_eq!(key.keys().secret().count(), + key.keys().unencrypted_secret().count()); + + Ok(key) +} + fn extract_cert(config: Config, m: &ArgMatches) -> Result<()> { let mut output = config.create_or_stdout_safe(m.value_of("output"))?; @@ -322,6 +352,25 @@ fn extract_cert(config: Config, m: &ArgMatches) -> Result<()> { Ok(()) } +fn dsm_import(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 input = open_or_stdin(m.value_of("input"))?; + let key = _unlock(Cert::from_reader(input)?)?; + let valid_key = key.with_policy(&config.policy, None)?; + + match m.value_of("dsm-key") { + Some(key_name) => dsm::import_tsk_to_dsm( + valid_key, key_name, dsm_auth, m.is_present("dsm-exportable"), + ), + None => unreachable!("name is compulsory") + } +} + fn extract_dsm(config: Config, m: &ArgMatches) -> Result<()> { let dsm_secret = dsm::Auth::from_options_or_env( m.value_of("api-key"), diff --git a/sq/src/sq-usage.rs b/sq/src/sq-usage.rs index fc2b4176b..eb7f4ffe7 100644 --- a/sq/src/sq-usage.rs +++ b/sq/src/sq-usage.rs @@ -396,6 +396,9 @@ //! password Changes password protecting secrets //! extract-cert Converts a key to a cert //! extract-dsm-secret Extracts a secret key from Fortanix DSM +//! dsm-import +//! Imports a Transferable Secret Key into Fortanix DSM +//! //! attest-certifications Attests to third-party certifications //! adopt Binds keys from one certificate to another //! help @@ -643,6 +646,51 @@ //! Writes to FILE or stdout if omitted //! ``` //! +//! ### Subcommand key dsm-import +//! +//! ```text +//! Imports a Transferable Secret Key info Fortanix DSM +//! +//! This command unlocks the TSK (if encrypted), and imports it into Fortanix DSM +//! for secure storage and usage. +//! +//! USAGE: +//! sq key dsm-import [FLAGS] [OPTIONS] --dsm-key +//! +//! FLAGS: +//! --dsm-exportable +//! (DANGER) Configure the key to be exportable from DSM +//! +//! -h, --help +//! Prints help information +//! +//! -V, --version +//! Prints version information +//! +//! +//! 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 +//! +//! --input +//! Reads from FILE or stdin if omitted +//! +//! +//! EXAMPLES: +//! +//! # Import the key into DSM +//! $ sq-dsm key dsm-import --dsm-key="Imported by sq-dsm" < my_priv_key.asc +//! ``` +//! //! ### Subcommand key attest-certifications //! //! ```text diff --git a/sq/src/sq_cli.rs b/sq/src/sq_cli.rs index 53cb8c523..fd3ff1dfa 100644 --- a/sq/src/sq_cli.rs +++ b/sq/src/sq_cli.rs @@ -796,6 +796,43 @@ command exfiltrates secrets from DSM and outputs a Key. .short("B").long("binary") .help("Emits binary data")) ) + .subcommand(SubCommand::with_name("dsm-import") + .display_order(112) + .about("Imports a Transferable Secret Key into Fortanix DSM") + .long_about( +"Imports a Transferable Secret Key info Fortanix DSM + +This command unlocks the TSK (if encrypted), and imports it into Fortanix DSM for secure storage and usage. +") + .after_help( + "EXAMPLES: + +# Import the key into DSM +$ sq-dsm key dsm-import --dsm-key=\"Imported by sq-dsm\" < my_priv_key.asc +") + .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) + .help("Name of the DSM key")) + .arg(Arg::with_name("dsm-exportable") + .long("dsm-exportable") + .help("(DANGER) Configure the key to be exportable from DSM")) + .arg(Arg::with_name("input") + .long("input").value_name("FILE") + .help("Reads from FILE or stdin if omitted")) + ) .subcommand( SubCommand::with_name("adopt") .display_order(800) diff --git a/sq/tests/data/knownkeys/alice-lovelace-encryption-subkey-signing-subkey-priv.pgp b/sq/tests/data/knownkeys/alice-lovelace-encryption-subkey-signing-subkey-priv.pgp new file mode 100644 index 000000000..a51312c18 --- /dev/null +++ b/sq/tests/data/knownkeys/alice-lovelace-encryption-subkey-signing-subkey-priv.pgp @@ -0,0 +1,34 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Comment: 5CCB BA06 74EA 5162 615E 36E9 80E5 ADE9 43CA 0DC3 +Comment: Alice Lovelace + +xVgEX+EpIRYJKwYBBAHaRw8BAQdAk2lV4viRCsrrlI7oZiAFrDW15Ub501ffvU7S +6yW72T0AAP98f2/23DyuAfXHregxl+xGYN6o6fi77Vi8dYa9wiIb2hSFwsALBB8W +CgB9BYJf4SkhAwsJBwkQgOWt6UPKDcNHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMu +c2VxdW9pYS1wZ3Aub3Jn2zij0tETOdhNjV1/3sgGfKSJvhvxhFFtOKv5oVCjCg8D +FQoIApsBAh4BFiEEXMu6BnTqUWJhXjbpgOWt6UPKDcMAABzeAQCVmqRLJsxJGNMJ +h/YMWE13a0A7aUyPGz9/KZwPo3SPDgD8CQ68/y4TKiJ7mq1uXFe7Zy05FaMHsvvi +Eu0c7pfbsgLNIkFsaWNlIExvdmVsYWNlIDxhbGljZUBleGFtcGxlLm9yZz7CwA4E +ExYKAIAFgl/hKSEDCwkHCRCA5a3pQ8oNw0cUAAAAAAAeACBzYWx0QG5vdGF0aW9u +cy5zZXF1b2lhLXBncC5vcmeT5w4uORuaHCWWIK+rzraqwjpRPwebVp6RwmPGxQZ1 +CgMVCggCmQECmwECHgEWIQRcy7oGdOpRYmFeNumA5a3pQ8oNwwAA1cwBAJXM7Y/7 +eu1CSEZnHpGDzPU6sG1vW1DMmrGrfpOC6MaaAQC9g7cgz3lw+0JIIRALUnza1ldV +Lbo1RvwxCXimdRJLDsdYBF/hKSEWCSsGAQQB2kcPAQEHQD94W151HvcrTgi/kxaj ++GXZVR/Y88YApOIW/4wBFbggAAEA1RuFSSulQ5yhBMbbiSjdtdpBzcqy/mS0/AgQ +JHkJYZMQH8LAwgQYFgoBNAWCX+EpIQkQgOWt6UPKDcNHFAAAAAAAHgAgc2FsdEBu +b3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3Jnjj9o9zLfMD6kI28uKxXnh+OtMtHxobKf +KotBWcbP934CmwICHgG+oAQZFgoAbwWCX+EpIQkQ3EJ5dpXWJOVHFAAAAAAAHgAg +c2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3JnhKRR+9OiWWt5iSsaCcLw3ubD +IOfv9HDCSwEdRnCy0/YWIQRqOx7HYjNivAZudavcQnl2ldYk5QAACdUA/2iU3Zj9 +XUaSD5JglQGEKErfKMYYUZpPOaxTUNotkvLcAP4g47bG6RQPs2KE9ofZyt2vRU+o +gML+lxdpIyjq4lDUCBYhBFzLugZ06lFiYV426YDlrelDyg3DAAC3jAD/UTXGuANz +bSSWSwezqCWkuDVF1gGCmptlybiSoQkmbfgA/1KKZ2EMfFn31bDfl5xcLv/ZpL/P +QgNNKImcXhHo+7gOx10EX+EpIRIKKwYBBAGXVQEFAQEHQJb42VFMY8HDOjz6YID9 +2VlIXRDiriB4y9/kSmbIFW1dAwEICQAA/300m8woegh+4aanj0Jj7Mg1TCAjrIsW +2Wd4iLyRVcEIEBHCwAMEGBYKAHUFgl/hKSEJEIDlrelDyg3DRxQAAAAAAB4AIHNh +bHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZ6N5upvK+V2YoTHp4L4PBhl2gGMt +yj1Y9/Vr9A/Ua87TApsMAh4BFiEEXMu6BnTqUWJhXjbpgOWt6UPKDcMAALb2APsF +i7eiPmR16BxyGGlNbr7oGm1zN8YCHj2wmO2L2sS8CQEAxlzlmpOg1617ktMjBU8i +rxOum0Q1TX/9zb7N8K95jg4= +=m6EI +-----END PGP PRIVATE KEY BLOCK----- diff --git a/sq/tests/data/knownkeys/carol-encryption-subkey-signing-subkey-priv.pgp b/sq/tests/data/knownkeys/carol-encryption-subkey-signing-subkey-priv.pgp new file mode 100644 index 000000000..3a7ad221f --- /dev/null +++ b/sq/tests/data/knownkeys/carol-encryption-subkey-signing-subkey-priv.pgp @@ -0,0 +1,34 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Comment: 0B17 34A8 2726 A5D1 D5AC 1568 1EC1 4781 FD88 09B4 +Comment: Carol + +xVgEX+E3kBYJKwYBBAHaRw8BAQdASs0ILN2aEvj/twvb1vo7wKT9ABHU6o4ujRX/ +0fFZjpsAAP4xwTpSP8Xe+lSjjGoGoalMi/mO6P1UHK5pgpPga/GS+xM9wsALBB8W +CgB9BYJf4TeQAwsJBwkQHsFHgf2ICbRHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMu +c2VxdW9pYS1wZ3Aub3JnuxssSC5qyTYMvQCsI1mlRJdJXqozDMWspEHgO6obGNED +FQoIApsBAh4BFiEECxc0qCcmpdHVrBVoHsFHgf2ICbQAAPmBAQC6ZDOJwf/bP5ji +5uHYGe+m+AfGaPMVTFh6FQjRqBD16wEA6botbClQofBaSx3Rru3XfY3c/cnUi5Jd +dCZbSbQr0AvNGUNhcm9sIDxjYXJvbEBleGFtcGxlLm9yZz7CwA4EExYKAIAFgl/h +N5ADCwkHCRAewUeB/YgJtEcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lh +LXBncC5vcmdY79mcCoWanc8hEICEfhzTTNkR9+wPApKRCcz/9coeFQMVCggCmQEC +mwECHgEWIQQLFzSoJyal0dWsFWgewUeB/YgJtAAAC/cA/1y6eWTbR+DNQOPs2fp/ +W4GY8FSuSn0z8IxCy2fPksRpAP92e/0CBkKjaSkaM9r8dKsKpT/krw7WtdAy8WJM +mHMHBcdYBF/hN5AWCSsGAQQB2kcPAQEHQGP9UaE+6d8Xd/oqtuAe0O4RVH5mXfWh +ycHiEsMO+JpSAAD/VgdJfyRSjqdCgp5YK/U8+URcYpATf6B5nHmOc/AqZe0PncLA +wgQYFgoBNAWCX+E3kAkQHsFHgf2ICbRHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMu +c2VxdW9pYS1wZ3Aub3JnN4Uv4Qds5xBXfPiQV///S+yksEfVpNda9iLGWasIwGMC +mwICHgG+oAQZFgoAbwWCX+E3kAkQBdi56tuSqMFHFAAAAAAAHgAgc2FsdEBub3Rh +dGlvbnMuc2VxdW9pYS1wZ3Aub3Jnk4tn1OpEZd6L6U71mZYgeOSg2WYtQ1bN8hhf +aqm7xF0WIQQ9VqQkPVzDRWON+xkF2Lnq25KowQAAZ4kA/i1OTj9naXJQT1mSlLki +Vt3M/BF/o5V9lBPOTBHurb+rAQCRJgWhfbGanDzENHjAMjxaJFuEpQifVrWvZaFx +GMsvARYhBAsXNKgnJqXR1awVaB7BR4H9iAm0AAA6mAEAloE+iTDr12OLD4m4+d3/ +C8NPedm4Wh8m517WN3lM138BAIbB3YtdYwa9M73lqvjTX7yYwwF1DwkCHrpWFFHf +nFoLx10EX+E3kBIKKwYBBAGXVQEFAQEHQJqfwagmR2vflwcoPIFgoGK7zRNNpRVo +GiRvGWwpdek7AwEICQAA/0S6DsVF5rOBKrt5K6JWa6X8uY8zAHtCOeY1no2Srcxg +EN7CwAMEGBYKAHUFgl/hN5AJEB7BR4H9iAm0RxQAAAAAAB4AIHNhbHRAbm90YXRp +b25zLnNlcXVvaWEtcGdwLm9yZ2f6a257Ki8vnzBBjR4YfcGcp12EFVbPXgctYDwP +nP2qApsMAh4BFiEECxc0qCcmpdHVrBVoHsFHgf2ICbQAAFouAP96l5VD9Q+OS+fJ +g65UnJkGM0iJaykDLIHX9XL789CGcgD/RgIL13421jrt1CZm96/KtabLOSUpNeu3 +xzJTFjzE9wU= +=/Mna +-----END PGP PRIVATE KEY BLOCK----- diff --git a/sq/tests/data/knownkeys/cv25519.pgp b/sq/tests/data/knownkeys/cv25519.pgp new file mode 100644 index 000000000..fe2f78ff2 --- /dev/null +++ b/sq/tests/data/knownkeys/cv25519.pgp @@ -0,0 +1,34 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Comment: F2FA 3ED8 D2F7 5D82 DF83 7FAB B379 184A BFD4 3A70 +Comment: zugzwang + +xVgEYlin0RYJKwYBBAHaRw8BAQdAToA9w3C8tdNnKf/50o3ijqrYob0sGOmQCEpH +BvfddhAAAP9QyuGmokv/RyQ0y9VXsfKAUlab1DpC3dJg6YqQv/RVqRObwsARBB8W +CgCDBYJiWKfRBYkFpI+9AwsJBwkQs3kYSr/UOnBHFAAAAAAAHgAgc2FsdEBub3Rh +dGlvbnMuc2VxdW9pYS1wZ3Aub3JnQkbO4d35p1pKB67Yqr5xGplutESGn+eLwuqo +pnCxMeADFQoIApsBAh4BFiEE8vo+2NL3XYLfg3+rs3kYSr/UOnAAALk8AP9Bky0u +pu3O+jMr3GfXC2wlyPbHzK1v6VNpOI7wvcByPwEA0Cwyl+9AqegWOsObwDfOi/mU +wnwVGH3h0EsvDgybwgvNGHp1Z3p3YW5nIDx6dWdAendhbmcuY29tPsLAFAQTFgoA +hgWCYlin0QWJBaSPvQMLCQcJELN5GEq/1DpwRxQAAAAAAB4AIHNhbHRAbm90YXRp +b25zLnNlcXVvaWEtcGdwLm9yZ8kIh4zb9P/wj71l/fhsvp5+9I+kAeTGjaT3YX2M +16i9AxUKCAKZAQKbAQIeARYhBPL6PtjS912C34N/q7N5GEq/1DpwAAD/MgD/fRqi +BxX5l+Falw9R831Oh3th6nRq4aN7EsBU43cEngoBANDCXtfCqjRm/BXbBjoTBFGo +GcasdX8KmeGiYH0CZc4Gx1gEYlin0RYJKwYBBAHaRw8BAQdAEWPbcfXvDW31qHcN +hnQjb9DCs1uWm8L+aQGzZhqWIVAAAP9GBDnLuRgk1/RUu+ckSyCBFVstkrgqMtmg +rz+GwOPnFw/kwsDFBBgWCgE3BYJiWKfRBYkFpI+9CRCzeRhKv9Q6cEcUAAAAAAAe +ACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmeTjpbRHCzVyMCFI3k7TSCu +Oc7eOdelZsIYH/CFYNtE0AKbAr6gBBkWCgBvBYJiWKfRCRAoY33El34IPUcUAAAA +AAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmctbobKOTDFr/NKegkB +lbrgk3mLyLXtExT/ABq3YnOzuBYhBGOYZfullsCGZ08wRChjfcSXfgg9AACmYAD+ +KJMEQgbEizH2KBmbFVtoUbHU+P9JNkuDo9qlMdJDZRcA/iaWe9Zc+GqB8ZfJY6gZ +cWuQ8zTnFe4cDFwOvqjhvm8IFiEE8vo+2NL3XYLfg3+rs3kYSr/UOnAAALIaAQDw +ap+OgwkkGYdXb92qHVtwB3dSSIyxJhUdsCuBwd0ehwD+LpLhxQr3jOuwZ4FIoZFb +m0Kw5VRJwmRI/g2z0+aSPQHHXQRiWKfREgorBgEEAZdVAQUBAQdAGrOzD9Wxf58m +8Xee6n9c5uq5H05O6t3OkjnwfH1ckh8DAQgJAAD/VI+w4u/ygkxHBdGq6j/K1vJm +i6WA6G9DXaUqdicnV4gSJMLABgQYFgoAeAWCYlin0QWJBaSPvQkQs3kYSr/UOnBH +FAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3JnhOQrVlVE1gtU +6ilHHtNdd9ojAMzBxJvnmWugTDj+2ocCmwwWIQTy+j7Y0vddgt+Df6uzeRhKv9Q6 +cAAAffQBAK52Q42LpZ272rxbCwVl+0R35LUSUUBMV2FRS6LwVMZ/AQCqxPiYdLV8 +vz1NzVDFkZKhtrFHu/oVTlI2kj++56FIDQ== +=CiYB +-----END PGP PRIVATE KEY BLOCK----- diff --git a/sq/tests/data/knownkeys/gpg_cv25519.pgp b/sq/tests/data/knownkeys/gpg_cv25519.pgp new file mode 100644 index 000000000..250119c5e Binary files /dev/null and b/sq/tests/data/knownkeys/gpg_cv25519.pgp differ diff --git a/sq/tests/data/knownkeys/gpg_p256.pgp b/sq/tests/data/knownkeys/gpg_p256.pgp new file mode 100644 index 000000000..58dad4f06 Binary files /dev/null and b/sq/tests/data/knownkeys/gpg_p256.pgp differ diff --git a/sq/tests/data/knownkeys/gpg_rsa2k_4k.pgp b/sq/tests/data/knownkeys/gpg_rsa2k_4k.pgp new file mode 100644 index 000000000..b827d3885 Binary files /dev/null and b/sq/tests/data/knownkeys/gpg_rsa2k_4k.pgp differ diff --git a/sq/tests/data/knownkeys/rsa3k.pgp b/sq/tests/data/knownkeys/rsa3k.pgp new file mode 100644 index 000000000..0af5d80fd --- /dev/null +++ b/sq/tests/data/knownkeys/rsa3k.pgp @@ -0,0 +1,147 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Comment: 1DD7 23AA F899 5B9D D4CE AAE3 10D9 3EEF C236 E800 +Comment: zugzwang + +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 +=8Zcd +-----END PGP PRIVATE KEY BLOCK----- diff --git a/sq/tests/data/knownkeys/sop_bouncy.pgp b/sq/tests/data/knownkeys/sop_bouncy.pgp new file mode 100644 index 000000000..fc0ccc00a --- /dev/null +++ b/sq/tests/data/knownkeys/sop_bouncy.pgp @@ -0,0 +1,25 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: PGPainless +Comment: 48E5 E739 6DD7 BA74 206D 512F EE0E B060 0D8E AE7A +Comment: Bernadette + +lFgEYlU9RBYJKwYBBAHaRw8BAQdAy0K6F229CdwAiw4J7p8M+jHa3mnF1TAio9+0 +2z045nUAAQCwgpU79i/gJfR15d19mcZLyhKi/aKZU7L5CpEciijCyBImtBpCZXJu +YWRldHRlIDxiQGV4YW1wbGUub3JnPoiPBBMWCgBBBQJiVT1ECZDuDrBgDY6uehah +BEjl5zlt17p0IG1RL+4OsGANjq56Ap4BApsBBZYCAwEABIsJCAcFlQoJCAsCmQEA +AKnbAQCgUqgf2V19pkCMVRbD2lfBSzFsO70edNhXeJuvBTNaiwD/crijyFua1Q0g +JhyY3ZFrLnycKg7BP70h1FXguiswUg6cXQRiVT1EEgorBgEEAZdVAQUBAQdApza+ +MKMpUEIo3dWfrmUsT8WI8eTz41BFuYJGLLK5+VoDAQgHAAD/T5elUlsLxF5x8W/Y +OZea9dLKRtZcZ0dl2QBaIcIjZOgRGIh1BBgWCgAdBQJiVT1EAp4BApsMBZYCAwEA +BIsJCAcFlQoJCAsACgkQ7g6wYA2OrnptagD/eopgzp6HNGgBRkn4rHY2dkQ0V92m +aFI46Qev2VFrYBEBAM5f/d5MjCSp8jToBDKkrd6ZTIQLFYQMmb5CN4CHi7wCnFgE +YlU9RBYJKwYBBAHaRw8BAQdAUYHz/DpQWx70GxY5uQjp/26GN6yvXUYiep17KyTr +6T0AAP9XgUxzY8258UzIwhXB0f4GSNzQRZGLdA6mFQWnspATFhCaiNUEGBYKAH0F +AmJVPUQCngECmwIFlgIDAQAEiwkIBwWVCgkIC18gBBkWCgAGBQJiVT1EAAoJEEeG +PbnLJkDuU40A/32JOSTAXvev5G9jd12RHuSt6sqVFfisySQ4s7w9bPMdAQCydsOA +UFB8HTWUMxoGIULO6b3+LomGcUYtOFFIC0paCgAKCRDuDrBgDY6uemyEAQDSnIDr +pD1x58qtIeOooWFIDLGFmiqFn3TfqLZrqg1o9AEA8A/lbBV7riuK1UP6trB9Q/80 +e3NXEUVztOfdkb0gAAc= +=i2nl +-----END PGP PRIVATE KEY BLOCK----- + diff --git a/sq/tests/data/knownkeys/sop_ecdh.pgp b/sq/tests/data/knownkeys/sop_ecdh.pgp new file mode 100644 index 000000000..bcdcb5093 --- /dev/null +++ b/sq/tests/data/knownkeys/sop_ecdh.pgp @@ -0,0 +1,17 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +xVgEXEcE6RYJKwYBBAHaRw8BAQdArjWwk3FAqyiFbFBKT4TzXcVBqPTB3gmzlC/U +b7O1u10AAP9XBeW6lzGOLx7zHH9AsUDUTb2pggYGMzd0P3ulJ2AfvQ4RzSZBbGlj +ZSBMb3ZlbGFjZSA8YWxpY2VAb3BlbnBncC5leGFtcGxlPsKQBBMWCAA4AhsDBQsJ +CAcCBhUKCQgLAgQWAgMBAh4BAheAFiEE64W7X6M6deFelE5j8jFVDE9H444FAl2l +nzoACgkQ8jFVDE9H447pKwD6A5xwUqIDprBzrHfahrImaYEZzncqb25vkLV2arYf +a78A/R3AwtLQvjxwLDuzk4dUtUwvUYibL2sAHwj2kGaHnfICx10EXEcE6RIKKwYB +BAGXVQEFAQEHQEL/BiGtq0k84Km1wqQw2DIikVYrQrMttN8d7BPfnr4iAwEIBwAA +/3/xFPG6U17rhTuq+07gmEvaFYKfxRB6sgAYiW6TMTpQEK7CwAAEGBYKAHIFglxH +BOkJEPIxVQxPR+OORxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdw +Lm9yZ6fxIVB5TfAwfoDWDE/457ZVsdaLlpjjHyxj9RdJJ5SVAhsMFiEE64W7X6M6 +deFelE5j8jFVDE9H444AAA6JAQCsuXwSYUSTrtio9R2Rt4VtFJDh8iy1AohvLpfT +zilJywEAyi7xxTMQnnW0kF0C0hk7dKtSEwPJdsq2oaqO8YlFnAA= +=Yo59 +-----END PGP PRIVATE KEY BLOCK----- + diff --git a/sq/tests/data/knownkeys/sop_ecdh_aes128_sha256.pgp b/sq/tests/data/knownkeys/sop_ecdh_aes128_sha256.pgp new file mode 100644 index 000000000..2f590ada6 --- /dev/null +++ b/sq/tests/data/knownkeys/sop_ecdh_aes128_sha256.pgp @@ -0,0 +1,17 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +xVgEXEcE6RYJKwYBBAHaRw8BAQdArjWwk3FAqyiFbFBKT4TzXcVBqPTB3gmzlC/U +b7O1u10AAP9XBeW6lzGOLx7zHH9AsUDUTb2pggYGMzd0P3ulJ2AfvQ4RzSZBbGlj +ZSBMb3ZlbGFjZSA8YWxpY2VAb3BlbnBncC5leGFtcGxlPsKQBBMWCAA4AhsDBQsJ +CAcCBhUKCQgLAgQWAgMBAh4BAheAFiEE64W7X6M6deFelE5j8jFVDE9H444FAl2l +nzoACgkQ8jFVDE9H447pKwD6A5xwUqIDprBzrHfahrImaYEZzncqb25vkLV2arYf +a78A/R3AwtLQvjxwLDuzk4dUtUwvUYibL2sAHwj2kGaHnfICx10EXEcE6RIKKwYB +BAGXVQEFAQEHQEL/BiGtq0k84Km1wqQw2DIikVYrQrMttN8d7BPfnr4iAwEIBwAA +/3/xFPG6U17rhTuq+07gmEvaFYKfxRB6sgAYiW6TMTpQEK7CwAAEGBYKAHIFglxH +BOkJEPIxVQxPR+OORxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdw +Lm9yZwxNY4g21lyZn9/WSUVKhfdPPMwW1iWRYYtYycOnJrBlAhsMFiEE64W7X6M6 +deFelE5j8jFVDE9H444AAKhCAP9eqN9V87dcbnrLGBt7BiNQAGL1U2JHtZH9aqSt +tGPZnQD9FJRH76/lnZF6pWzYMEefBCAZr9IHg9ih802TuMggVwE= +=bqKd +-----END PGP PRIVATE KEY BLOCK----- + diff --git a/sq/tests/data/knownkeys/sop_ecdh_aes128_sha384.pgp b/sq/tests/data/knownkeys/sop_ecdh_aes128_sha384.pgp new file mode 100644 index 000000000..380d507c1 --- /dev/null +++ b/sq/tests/data/knownkeys/sop_ecdh_aes128_sha384.pgp @@ -0,0 +1,17 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +xVgEXEcE6RYJKwYBBAHaRw8BAQdArjWwk3FAqyiFbFBKT4TzXcVBqPTB3gmzlC/U +b7O1u10AAP9XBeW6lzGOLx7zHH9AsUDUTb2pggYGMzd0P3ulJ2AfvQ4RzSZBbGlj +ZSBMb3ZlbGFjZSA8YWxpY2VAb3BlbnBncC5leGFtcGxlPsKQBBMWCAA4AhsDBQsJ +CAcCBhUKCQgLAgQWAgMBAh4BAheAFiEE64W7X6M6deFelE5j8jFVDE9H444FAl2l +nzoACgkQ8jFVDE9H447pKwD6A5xwUqIDprBzrHfahrImaYEZzncqb25vkLV2arYf +a78A/R3AwtLQvjxwLDuzk4dUtUwvUYibL2sAHwj2kGaHnfICx10EXEcE6RIKKwYB +BAGXVQEFAQEHQEL/BiGtq0k84Km1wqQw2DIikVYrQrMttN8d7BPfnr4iAwEJBwAA +/3/xFPG6U17rhTuq+07gmEvaFYKfxRB6sgAYiW6TMTpQEK7CwAAEGBYKAHIFglxH +BOkJEPIxVQxPR+OORxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdw +Lm9yZy3kbqpY1VOEjYd3U44LP7mjS2DH4wyKuhIOLlH7rsi3AhsMFiEE64W7X6M6 +deFelE5j8jFVDE9H444AABiyAP4sLkx41+GXTNgDYs/bKc3/Fk3dU8LCl4rcc8L5 +Jb/OpAEAkqhM9EFE629jStOuZPAtbtC12VbPxobUx7pDovEoOQU= +=s8tk +-----END PGP PRIVATE KEY BLOCK----- + diff --git a/sq/tests/data/knownkeys/sop_ecdh_aes128_sha512.pgp b/sq/tests/data/knownkeys/sop_ecdh_aes128_sha512.pgp new file mode 100644 index 000000000..c5a047248 --- /dev/null +++ b/sq/tests/data/knownkeys/sop_ecdh_aes128_sha512.pgp @@ -0,0 +1,17 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +xVgEXEcE6RYJKwYBBAHaRw8BAQdArjWwk3FAqyiFbFBKT4TzXcVBqPTB3gmzlC/U +b7O1u10AAP9XBeW6lzGOLx7zHH9AsUDUTb2pggYGMzd0P3ulJ2AfvQ4RzSZBbGlj +ZSBMb3ZlbGFjZSA8YWxpY2VAb3BlbnBncC5leGFtcGxlPsKQBBMWCAA4AhsDBQsJ +CAcCBhUKCQgLAgQWAgMBAh4BAheAFiEE64W7X6M6deFelE5j8jFVDE9H444FAl2l +nzoACgkQ8jFVDE9H447pKwD6A5xwUqIDprBzrHfahrImaYEZzncqb25vkLV2arYf +a78A/R3AwtLQvjxwLDuzk4dUtUwvUYibL2sAHwj2kGaHnfICx10EXEcE6RIKKwYB +BAGXVQEFAQEHQEL/BiGtq0k84Km1wqQw2DIikVYrQrMttN8d7BPfnr4iAwEKBwAA +/3/xFPG6U17rhTuq+07gmEvaFYKfxRB6sgAYiW6TMTpQEK7CwAAEGBYKAHIFglxH +BOkJEPIxVQxPR+OORxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdw +Lm9yZ03QSejUmqlDpePNdCClHdFXVEsPNRQEbAwmcNg6bvwrAhsMFiEE64W7X6M6 +deFelE5j8jFVDE9H444AAGYFAQCJlmZuk7vHa1rgS5e5I8juajEtr0kJe5MsbcVq +hn1lCwEA+T7F34XtSPkkDRjjAuq41pHqOAsmpmBESdwdqBUPlgk= +=+sx3 +-----END PGP PRIVATE KEY BLOCK----- + diff --git a/sq/tests/data/knownkeys/sop_ecdh_aes192_sha224.pgp b/sq/tests/data/knownkeys/sop_ecdh_aes192_sha224.pgp new file mode 100644 index 000000000..25dd42fc9 --- /dev/null +++ b/sq/tests/data/knownkeys/sop_ecdh_aes192_sha224.pgp @@ -0,0 +1,17 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +xVgEXEcE6RYJKwYBBAHaRw8BAQdArjWwk3FAqyiFbFBKT4TzXcVBqPTB3gmzlC/U +b7O1u10AAP9XBeW6lzGOLx7zHH9AsUDUTb2pggYGMzd0P3ulJ2AfvQ4RzSZBbGlj +ZSBMb3ZlbGFjZSA8YWxpY2VAb3BlbnBncC5leGFtcGxlPsKQBBMWCAA4AhsDBQsJ +CAcCBhUKCQgLAgQWAgMBAh4BAheAFiEE64W7X6M6deFelE5j8jFVDE9H444FAl2l +nzoACgkQ8jFVDE9H447pKwD6A5xwUqIDprBzrHfahrImaYEZzncqb25vkLV2arYf +a78A/R3AwtLQvjxwLDuzk4dUtUwvUYibL2sAHwj2kGaHnfICx10EXEcE6RIKKwYB +BAGXVQEFAQEHQEL/BiGtq0k84Km1wqQw2DIikVYrQrMttN8d7BPfnr4iAwELCAAA +/3/xFPG6U17rhTuq+07gmEvaFYKfxRB6sgAYiW6TMTpQEK7CwAAEGBYKAHIFglxH +BOkJEPIxVQxPR+OORxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdw +Lm9yZ4akPRxuu99sIhAMG2vL/QRvSnyrigMGwTkO+J+1U/HjAhsMFiEE64W7X6M6 +deFelE5j8jFVDE9H444AAPVcAQDJMmi/LBWt8GSvvIHr6+AI1uQYC2eX3vsLDu+r +XcpEDQD+KlffiNovVXohx01b7dYXlPQsfgpn+dbh+dLDsLCplwY= +=X+WR +-----END PGP PRIVATE KEY BLOCK----- + diff --git a/sq/tests/data/knownkeys/sop_openpgpjs.pgp b/sq/tests/data/knownkeys/sop_openpgpjs.pgp new file mode 100644 index 000000000..1d59274b7 --- /dev/null +++ b/sq/tests/data/knownkeys/sop_openpgpjs.pgp @@ -0,0 +1,64 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: OpenPGP.js v4.10.10 +Comment: https://openpgpjs.org + +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 +F0BL +=Y1ew +-----END PGP PRIVATE KEY BLOCK----- + diff --git a/sq/tests/data/knownkeys/sop_sq_bob.pgp b/sq/tests/data/knownkeys/sop_sq_bob.pgp new file mode 100644 index 000000000..8a81274e7 --- /dev/null +++ b/sq/tests/data/knownkeys/sop_sq_bob.pgp @@ -0,0 +1,83 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Comment: Bob's OpenPGP Transferable Secret Key + +lQVYBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv +/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz +/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/ +5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3 +X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv +9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0 +qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb +SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb +vLIwa3T4CyshfT0AEQEAAQAL/RZqbJW2IqQDCnJi4Ozm++gPqBPiX1RhTWSjwxfM +cJKUZfzLj414rMKm6Jh1cwwGY9jekROhB9WmwaaKT8HtcIgrZNAlYzANGRCM4TLK +3VskxfSwKKna8l+s+mZglqbAjUg3wmFuf9Tj2xcUZYmyRm1DEmcN2ZzpvRtHgX7z +Wn1mAKUlSDJZSQks0zjuMNbupcpyJokdlkUg2+wBznBOTKzgMxVNC9b2g5/tMPUs +hGGWmF1UH+7AHMTaS6dlmr2ZBIyogdnfUqdNg5sZwsxSNrbglKP4sqe7X61uEAIQ +bD7rT3LonLbhkrj3I8wilUD8usIwt5IecoHhd9HziqZjRCc1BUBkboUEoyedbDV4 +i4qfsFZ6CEWoLuD5pW7dEp0M+WeuHXO164Rc+LnH6i1VQrpb1Okl4qO6ejIpIjBI +1t3GshtUu/mwGBBxs60KBX5g77mFQ9lLCRj8lSYqOsHRKBhUp4qM869VA+fD0BRP +fqPT0I9IH4Oa/A3jYJcg622GwQYA1LhnP208Waf6PkQSJ6kyr8ymY1yVh9VBE/g6 +fRDYA+pkqKnw9wfH2Qho3ysAA+OmVOX8Hldg+Pc0Zs0e5pCavb0En8iFLvTA0Q2E +LR5rLue9uD7aFuKFU/VdcddY9Ww/vo4k5p/tVGp7F8RYCFn9rSjIWbfvvZi1q5Tx ++akoZbga+4qQ4WYzB/obdX6SCmi6BndcQ1QdjCCQU6gpYx0MddVERbIp9+2SXDyL +hpxjSyz+RGsZi/9UAshT4txP4+MZBgDfK3ZqtW+h2/eMRxkANqOJpxSjMyLO/FXN +WxzTDYeWtHNYiAlOwlQZEPOydZFty9IVzzNFQCIUCGjQ/nNyhw7adSgUk3+BXEx/ +MyJPYY0BYuhLxLYcrfQ9nrhaVKxRJj25SVHj2ASsiwGJRZW4CC3uw40OYxfKEvNC +mer/VxM3kg8qqGf9KUzJ1dVdAvjyx2Hz6jY2qWCyRQ6IMjWHyd43C4r3jxooYKUC +YnstRQyb/gCSKahveSEjo07CiXMr88UGALwzEr3npFAsPW3osGaFLj49y1oRe11E +he9gCHFm+fuzbXrWmdPjYU5/ZdqdojzDqfu4ThfnipknpVUM1o6MQqkjM896FHm8 +zbKVFSMhEP6DPHSCexMFrrSgN03PdwHTO6iBaIBBFqmGY01tmJ03SxvSpiBPON9P +NVvy/6UZFedTq8A07OUAxO62YUSNtT5pmK2vzs3SAZJmbFbMh+NN204TRI72GlqT +t5hcfkuv8hrmwPS/ZR6q312mKQ6w/1pqO9qitCFCb2IgQmFiYmFnZSA8Ym9iQG9w +ZW5wZ3AuZXhhbXBsZT6JAc4EEwEKADgCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgEC +F4AWIQTRpm4aI7GCyZgPeIz7/MgqAV5zMAUCXaWe+gAKCRD7/MgqAV5zMG9sC/9U +2T3RrqEbw533FPNfEflhEVRIZ8gDXKM8hU6cqqEzCmzZT6xYTe6sv4y+PJBGXJFX +yhj0g6FDkSyboM5litOcTupURObVqMgA/Y4UKERznm4fzzH9qek85c4ljtLyNufe +doL2pp3vkGtn7eD0QFRaLLmnxPKQ/TlZKdLE1G3u8Uot8QHicaR6GnAdc5UXQJE3 +BiV7jZuDyWmZ1cUNwJkKL6oRtp+ZNDOQCrLNLecKHcgCqrpjSQG5oouba1I1Q6Vl +sP44dhA1nkmLHtxlTOzpeHj4jnk1FaXmyasurrrI5CgU/L2Oi39DGKTH/A/cywDN +4ZplIQ9zR8enkbXquUZvFDe+Xz+6xRXtb5MwQyWODB3nHw85HocLwRoIN9WdQEI+ +L8a/56AuOwhs8llkSuiITjR7r9SgKJC2WlAHl7E8lhJ3VDW3ELC56KH308d6mwOG +ZRAqIAKzM1T5FGjMBhq7ZV0eqdEntBh3EcOIfj2M8rg1MzJv+0mHZOIjByawikad +BVgEXaWc8gEMANYwv1xsYyunXYK0X1vY/rP1NNPvhLyLIE7NpK90YNBj+xS1ldGD +bUdZqZeef2xJe8gMQg05DoD1DF3GipZ0Ies65beh+d5hegb7N4pzh0LzrBrVNHar +29b5ExdI7i4iYD5TO6Vr/qTUOiAN/byqELEzAb+L+b2DVz/RoCm4PIp1DU9ewcc2 +WB38Ofqut3nLYA5tqJ9XvAiEQme+qAVcM3ZFcaMt4I4dXhDZZNg+D9LiTWcxdUPB +leu8iwDRjAgyAhPzpFp+nWoqWA81uIiULWD1Fj+IVoY3ZvgivoYOiEFBJ9lbb4te +g9m5UT/AaVDTWuHzbspVlbiVe+qyB77C2daWzNyx6UYBPLOo4r0t0c91kbNE5lgj +Z7xz6los0N1U8vq91EFSeQJoSQ62XWavYmlCLmdNT6BNfgh4icLsT7Vr1QMX9jzn +JtTPxdXytSdHvpSpULsqJ016l0dtmONcK3z9mj5N5z0k1tg1AH970TGYOe2aUcSx +IRDMXDOPyzEfjwARAQABAAv9F2CwsjS+Sjh1M1vegJbZjei4gF1HHpEM0K0PSXsp +SfVvpR4AoSJ4He6CXSMWg0ot8XKtDuZoV9jnJaES5UL9pMAD7JwIOqZm/DYVJM5h +OASCh1c356/wSbFbzRHPtUdZO9Q30WFNJM5pHbCJPjtNoRmRGkf71RxtvHBzy7np +Ga+W6U/NVKHw0i0CYwMI0YlKDakYW3Pm+QL+gHZFvngGweTod0f9l2VLLAmeQR/c ++EZs7lNumhuZ8mXcwhUc9JQIhOkpO+wreDysEFkAcsKbkQP3UDUsA1gFx9pbMzT0 +tr1oZq2a4QBtxShHzP/ph7KLpN+6qtjks3xB/yjTgaGmtrwM8tSe0wD1RwXS+/1o +BHpXTnQ7TfeOGUAu4KCoOQLv6ELpKWbRBLWuiPwMdbGpvVFALO8+kvKAg9/r+/ny +zM2GQHY+J3Jh5JxPiJnHfXNZjIKLbFbIPdSKNyJBuazXW8xIa//mEHMI5OcvsZBK +clAIp7LXzjEjKXIwHwDcTn9pBgDpdOKTHOtJ3JUKx0rWVsDH6wq6iKV/FTVSY5jl +zN+puOEsskF1Lfxn9JsJihAVO3yNsp6RvkKtyNlFazaCVKtDAmkjoh60XNxcNRqr +gCnwdpbgdHP6v/hvZY54ZaJjz6L2e8unNEkYLxDt8cmAyGPgH2XgL7giHIp9jrsQ +aS381gnYwNX6wE1aEikgtY91nqJjwPlibF9avSyYQoMtEqM/1UjTjB2KdD/MitK5 +fP0VpvuXpNYZedmyq4UOMwdkiNMGAOrfmOeT0olgLrTMT5H97Cn3Yxbk13uXHNu/ +ZUZZNe8s+QtuLfUlKAJtLEUutN33TlWQY522FV0m17S+b80xJib3yZVJteVurrh5 +HSWHAM+zghQAvCesg5CLXa2dNMkTCmZKgCBvfDLZuZbjFwnwCI6u/NhOY9egKuUf +SA/je/RXaT8m5VxLYMxwqQXKApzD87fv0tLPlVIEvjEsaf992tFEFSNPcG1l/jpd +5AVXw6kKuf85UkJtYR1x2MkQDrqY1QX/XMw00kt8y9kMZUre19aCArcmor+hDhRJ +E3Gt4QJrD9z/bICESw4b4z2DbgD/Xz9IXsA/r9cKiM1h5QMtXvuhyfVeM01enhxM +GbOH3gjqqGNKysx0UODGEwr6AV9hAd8RWXMchJLaExK9J5SRawSg671ObAU24SdY +vMQ9Z4kAQ2+1ReUZzf3ogSMRZtMT+d18gT6L90/y+APZIaoArLPhebIAGq39HLmJ +26x3z0WAgrpA1kNsjXEXkoiZGPLKIGoe3hqJAbYEGAEKACAWIQTRpm4aI7GCyZgP +eIz7/MgqAV5zMAUCXaWc8gIbDAAKCRD7/MgqAV5zMOn/C/9ugt+HZIwX308zI+QX +c5vDLReuzmJ3ieE0DMO/uNSC+K1XEioSIZP91HeZJ2kbT9nn9fuReuoff0T0Dief +rbwcIQQHFFkrqSp1K3VWmUGp2JrUsXFVdjy/fkBIjTd7c5boWljv/6wAsSfiv2V0 +JSM8EFU6TYXxswGjFVfc6X97tJNeIrXL+mpSmPPqy2bztcCCHkWS5lNLWQw+R7Vg +71Fe6yBSNVrqC2/imYG2J9zlowjx1XU63Wdgqp2Wxt0l8OmsB/W80S1fRF5G4SDH +s9HXglXXqPsBRZJYfP+VStm9L5P/sKjCcX6WtZR7yS6G8zj/X767MLK/djANvpPd +NVniEke6hM3CNBXYPAMhQBMWhCulcoz+0lxi8L34rMN+Dsbma96psdUrn7uLaB91 +6we0CTfF8qqm7BsVAgalon/UUiuMY80U3ueoj3okiSTiHIjD/YtpXSPioC8nMng7 +xqAY9Bwizt4FWgXuLm1a4+So4V9j1TRCXd12Uc2l2RNmgDE= +=miES +-----END PGP PRIVATE KEY BLOCK----- + diff --git a/sq/tests/dsm/extract_dsm_import_gpg.sh b/sq/tests/dsm/extract_dsm_import_gpg.sh index 4a503535b..a82a195d1 100755 --- a/sq/tests/dsm/extract_dsm_import_gpg.sh +++ b/sq/tests/dsm/extract_dsm_import_gpg.sh @@ -15,7 +15,7 @@ create_tmp_dir gpg_homedir gpg="gpg --homedir=$gpg_homedir --trust-model always --pinentry-mode loopback" -trap 'erase_tmp_dirs' EXIT +trap 'erase_tmp_dir $data && erase_tmp_dir $gpg_homedir' EXIT # Test files message=$data/message.txt diff --git a/sq/tests/dsm/extract_dsm_import_gpg_auto.tcl b/sq/tests/dsm/extract_dsm_import_gpg_auto.tcl index ba2cdb9fb..a0c85c536 100755 --- a/sq/tests/dsm/extract_dsm_import_gpg_auto.tcl +++ b/sq/tests/dsm/extract_dsm_import_gpg_auto.tcl @@ -20,4 +20,8 @@ send "$pass\n" expect "Enter password" send "$pass\n" -interact +expect { + "SUCCESS" {exit 0;} +} + +exit 1; diff --git a/sq/tests/dsm/generate_gpg_import_dsm.sh b/sq/tests/dsm/generate_gpg_import_dsm.sh new file mode 100755 index 000000000..55243b730 --- /dev/null +++ b/sq/tests/dsm/generate_gpg_import_dsm.sh @@ -0,0 +1,79 @@ +#!/bin/bash -e + +sq="" + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +source $SCRIPT_DIR/common.sh + +data="" +create_tmp_dir data +echo "Data dir: $data" + +gpg_homedir="" +create_tmp_dir gpg_homedir +echo "GPG Home: $gpg_homedir" + +gpg="gpg --homedir=$gpg_homedir --trust-model always --pinentry-mode loopback" + +trap 'erase_tmp_dir $data && erase_tmp_dir $gpg_homedir' EXIT + +# Test files +message=$data/message.txt +decrypted_with_dsm=$data/decrypted_with_dsm.asc +alice_public=$data/alice.asc +alice_local_priv=$data/alice_local_priv.asc +alice_extracted_priv=$data/alice_extracted_priv.asc +alice_extracted_pub=$data/alice_extracted_pub.asc +signed_local=$data/message.signed_local.asc +signed_remote=$data/message.signed_remote.asc +signed_gpg=$data/message.signed_gpg.gpg + +random=$(head /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w "10" | head -n 1) +alice_key_name="test-gpg-import-alice-$random" + +echo "Enter test passphrase" +read -r test_passphrase + +comm "sq --version" +$sq --version +comm "gpg --version" +$gpg --version + +comm "Generate and export gpg key (Alice)" +$gpg --passphrase="$test_passphrase" --yes --quick-gen-key "Alice (generated with gpg) " +$gpg --export-secret-keys --passphrase="$test_passphrase" > "$alice_local_priv" +$gpg --export > "$alice_public" + +comm "Import gpg key into DSM" +$sq key dsm-import --dsm-key="$alice_key_name" --dsm-exportable < "$alice_local_priv" + +printf "Y el verso cae al alma como al pasto el rocío.\n" > "$message" + +## Signature roundtrips + +comm "sign with (i) local key (ii) remote key (iii) gpg key" +$sq sign --signer-key="$alice_local_priv" "$message" > "$signed_local" +my_cat "$signed_local" +$sq sign --dsm-key="$alice_key_name" "$message" > "$signed_remote" +my_cat "$signed_remote" +$gpg --sign --passphrase="$test_passphrase" --output="$signed_gpg" "$message" +my_cat "$signed_gpg" + +comm "verify with sq and gpg" +$gpg --verify "$signed_remote" +$gpg --verify "$signed_local" +$gpg --verify "$signed_gpg" +$sq verify --signer-cert="$alice_public" "$signed_remote" +$sq verify --signer-cert="$alice_public" "$signed_local" +$sq verify --signer-cert="$alice_public" "$signed_gpg" + +comm "encrypt - decrypt with sq-dsm" +$sq key extract-cert --dsm-key="$alice_key_name" > "$alice_extracted_pub" +$sq encrypt --recipient-cert="$alice_public" < "$message" | $sq decrypt --dsm-key="$alice_key_name" > "$decrypted_with_dsm" +diff "$message" "$decrypted_with_dsm" + +# Import to gpg +$sq key extract-dsm-secret --dsm-key="$alice_key_name" > "$alice_extracted_priv" +$gpg --import "$alice_extracted_priv" + +echo "SUCCESS" diff --git a/sq/tests/dsm/generate_gpg_import_dsm_auto.tcl b/sq/tests/dsm/generate_gpg_import_dsm_auto.tcl new file mode 100755 index 000000000..46f34dbe4 --- /dev/null +++ b/sq/tests/dsm/generate_gpg_import_dsm_auto.tcl @@ -0,0 +1,31 @@ +#!/usr/bin/expect + +set test_script "./tests/dsm/generate_gpg_import_dsm.sh" +set verbosity [lindex $argv 3]; +set pass "my-test-passphrase" + +spawn "$test_script" -v "$verbosity" + +expect "Enter test passphrase" +send "$pass\n" + +expect "Enter password*" +send "$pass\n" + +expect "Please enter password to decrypt*" +send "$pass\n" + +expect "Please enter password to decrypt*" +send "$pass\n" + +expect "New password*" +send "$pass\n" + +expect "Repeat new password*" +send "$pass\n" + +expect { + "SUCCESS" {exit 0;} +} + +exit 1; diff --git a/sq/tests/dsm/key_expiration.sh b/sq/tests/dsm/key_expiration.sh index 37ef23276..ee95fc178 100755 --- a/sq/tests/dsm/key_expiration.sh +++ b/sq/tests/dsm/key_expiration.sh @@ -55,3 +55,5 @@ check_time "$alice_one_day" "Expiration time.* UTC (creation time + P1D)" comm "generate a key that expires in 2039, and check certificate" $sq key generate --dsm-key="$alice_2039" --userid="Alice 2039 " --cipher-suite="$cipher_suite" --expires="$future_2039" check_time "$alice_2039" "Expiration time: 2039" + +echo "SUCCESS" diff --git a/sq/tests/dsm/knownkeys_import_dsm.sh b/sq/tests/dsm/knownkeys_import_dsm.sh new file mode 100755 index 000000000..c08c66ddc --- /dev/null +++ b/sq/tests/dsm/knownkeys_import_dsm.sh @@ -0,0 +1,51 @@ +#!/bin/bash -e + +sq="" + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +source $SCRIPT_DIR/common.sh + +data="" +create_tmp_dir data +echo "Data dir: $data" + +trap 'erase_tmp_dir $data' EXIT + +knownkeys="$SCRIPT_DIR/../data/knownkeys" + +# Test files +message=$data/message.txt +alice_public=$data/alice.asc +alice_public_extracted=$data/alice_public_extracted.asc + +comm "sq --version" +$sq --version + +printf "Y el verso cae al alma como al pasto el rocío.\n" > "$message" + +for f in "$knownkeys"/*; do + comm "Knownkey:" + echo "$f" + + comm "Key name:" + random=$(head /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w "10" | head -n 1) + key_name="knownkey-$random" + echo "$key_name" + + comm "Import into DSM:" + $sq key dsm-import --dsm-key="$key_name" < "$f" + + comm "Compare certificates" + $sq key extract-cert --dsm-key="$key_name" > "$alice_public_extracted" + $sq key extract-cert < "$f" > "$alice_public" + diff "$alice_public" "$alice_public_extracted" + + comm "Signature roundtrip" + $sq sign --dsm-key="$key_name" < "$message" | $sq verify --signer-cert="$alice_public" + + comm "Encryption roundtrip" + $sq encrypt --recipient-cert="$alice_public_extracted" < "$message" | $sq decrypt --dsm-key="$key_name" + +done + +echo "SUCCESS" diff --git a/sq/tests/dsm/sq_roundtrips.sh b/sq/tests/dsm/sq_roundtrips.sh index c8845944a..49a3eebf5 100755 --- a/sq/tests/dsm/sq_roundtrips.sh +++ b/sq/tests/dsm/sq_roundtrips.sh @@ -75,3 +75,5 @@ comm "decrypt" $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" + +echo "SUCCESS"