From 5a38fdacf2ade07f1a5003799d84ca6d0e2eb40c Mon Sep 17 00:00:00 2001 From: zugzwang Date: Fri, 27 May 2022 09:00:53 +0200 Subject: [PATCH] Support key import (#59) Now, Fortanix DSM can represent arbitrary OpenPGP Transferable Secret Keys. Such keys can come from other implementations such the local GnuPG keyring, etc. Because nothing is known about the input key, the corresponding structure of Sobjects has been enhanced to represent it in fair generality. More fields have been added as custom metadata (fingerprint of each subkeys, external creation timestamps, ECDH additional algorithms, etc). Consequently, next release (0.3.0) is not backwards compatible with previous versions. The input key is accepted for import under the following assumptions: - The primary key is always C or CS, - there is at least one Et or Er subkey, - if the primary key is C, then there is at least one S subkey, and - the given cipher suites and algorithms are supported by Fortanix DSM. --- Cargo.lock | 58 +- openpgp-dsm/Cargo.toml | 5 +- openpgp-dsm/src/der.rs | 124 ++- openpgp-dsm/src/lib.rs | 706 ++++++++++++++---- openpgp/Cargo.toml | 1 + openpgp/src/types/bitfield.rs | 4 +- openpgp/src/types/key_flags.rs | 4 +- openpgp/src/types/mod.rs | 6 +- sq/Makefile | 4 +- sq/src/commands/decrypt.rs | 8 +- sq/src/commands/key.rs | 53 +- sq/src/sq-usage.rs | 48 ++ sq/src/sq_cli.rs | 37 + ...-encryption-subkey-signing-subkey-priv.pgp | 34 + ...-encryption-subkey-signing-subkey-priv.pgp | 34 + sq/tests/data/knownkeys/cv25519.pgp | 34 + sq/tests/data/knownkeys/gpg_cv25519.pgp | Bin 0 -> 511 bytes sq/tests/data/knownkeys/gpg_p256.pgp | Bin 0 -> 570 bytes sq/tests/data/knownkeys/gpg_rsa2k_4k.pgp | Bin 0 -> 3471 bytes sq/tests/data/knownkeys/rsa3k.pgp | 147 ++++ sq/tests/data/knownkeys/sop_bouncy.pgp | 25 + sq/tests/data/knownkeys/sop_ecdh.pgp | 17 + .../data/knownkeys/sop_ecdh_aes128_sha256.pgp | 17 + .../data/knownkeys/sop_ecdh_aes128_sha384.pgp | 17 + .../data/knownkeys/sop_ecdh_aes128_sha512.pgp | 17 + .../data/knownkeys/sop_ecdh_aes192_sha224.pgp | 17 + sq/tests/data/knownkeys/sop_openpgpjs.pgp | 64 ++ sq/tests/data/knownkeys/sop_sq_bob.pgp | 83 ++ sq/tests/dsm/extract_dsm_import_gpg.sh | 2 +- sq/tests/dsm/extract_dsm_import_gpg_auto.tcl | 6 +- sq/tests/dsm/generate_gpg_import_dsm.sh | 79 ++ sq/tests/dsm/generate_gpg_import_dsm_auto.tcl | 31 + sq/tests/dsm/key_expiration.sh | 2 + sq/tests/dsm/knownkeys_import_dsm.sh | 51 ++ sq/tests/dsm/sq_roundtrips.sh | 2 + 35 files changed, 1540 insertions(+), 197 deletions(-) create mode 100644 sq/tests/data/knownkeys/alice-lovelace-encryption-subkey-signing-subkey-priv.pgp create mode 100644 sq/tests/data/knownkeys/carol-encryption-subkey-signing-subkey-priv.pgp create mode 100644 sq/tests/data/knownkeys/cv25519.pgp create mode 100644 sq/tests/data/knownkeys/gpg_cv25519.pgp create mode 100644 sq/tests/data/knownkeys/gpg_p256.pgp create mode 100644 sq/tests/data/knownkeys/gpg_rsa2k_4k.pgp create mode 100644 sq/tests/data/knownkeys/rsa3k.pgp create mode 100644 sq/tests/data/knownkeys/sop_bouncy.pgp create mode 100644 sq/tests/data/knownkeys/sop_ecdh.pgp create mode 100644 sq/tests/data/knownkeys/sop_ecdh_aes128_sha256.pgp create mode 100644 sq/tests/data/knownkeys/sop_ecdh_aes128_sha384.pgp create mode 100644 sq/tests/data/knownkeys/sop_ecdh_aes128_sha512.pgp create mode 100644 sq/tests/data/knownkeys/sop_ecdh_aes192_sha224.pgp create mode 100644 sq/tests/data/knownkeys/sop_openpgpjs.pgp create mode 100644 sq/tests/data/knownkeys/sop_sq_bob.pgp create mode 100755 sq/tests/dsm/generate_gpg_import_dsm.sh create mode 100755 sq/tests/dsm/generate_gpg_import_dsm_auto.tcl create mode 100755 sq/tests/dsm/knownkeys_import_dsm.sh 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 0000000000000000000000000000000000000000..250119c5e4bc0f328275b49f2eca0650fa763135 GIT binary patch literal 511 zcmbOd!IBhlcCQ$xHX9=g<1Kf7Mn-mrSMKNib+4?8tx^)&RJHet;F6@M;KXdsoITSm z9@P03U1wnUAMQ}d`tGK`Y~XgapRKoj4|z&03ZH#9QN(uM|MJqkiR=QLTXdpI)2qr8 z^U@VG(o^$NixNvxQxuXa719gRbrhV-j7&`pEj1NvDjX{GD(pI@u?UNCFxZJHvY5JV z{lsm%dBOEq1+Qy08{Y`0)Wotffn3QX&CJTlna0?_%FW5a&cr6l#mT|V#3IJT%*Z6i z$Rystz{M#5)55~b$gp43wC2%e=9QnAUs%N?Gx7XW3D*dlVw_O4e%^oa=(xiS|7;e` z=dzTseLCUFeBR~D=d>Pf)972Qq?FaMPk8p@TV^bCV!`1f#03tY>7k6Qpa5F7L{M|) zs<}6glt1jyZj~^HR-4B;TV_UfCT14XyE(2eO4`~kdPjFlMgDyu9eyV_l({JGfTTjr z1+i;)bGGlg(Q};f|GCNL*Ea5czj|hsc$uo-m(VTR3heC{h4>g4{uy2Txy<0N!hhGc zkR?jZ2R82Ly&RNm8Z<91_z!Ha^RJSZ)RVqSgN^tx3Edn+l)lEzjLNc{HXeRPF8_|ZI!E^ z*s_bize@hhW$K=0Q+xIEdWQ$IXLI(8yV(Aeh*y?W&0}WdU}s=t_{z9z($e~$Ga~FR zuUT@}SirYs=OWczp&y@VtG_mMIO!vBx}%Oo0vIT21OkNz5-7-?H(SZb&@gL;L8eso zzjaIFE9AwZ`ESVnO6cTxy*n$~>DjEDm-iSMCggu`LiKsq%BOvooUd=c`@H^Q P;lwX-mjv8zRi^>~k6YzS literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b827d38852ac930ce4f3f2012b7bc4b6ebd3f02a GIT binary patch literal 3471 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 literal 0 HcmV?d00001 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 + +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 +=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"