diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2023-02-13 20:22:06 +0100 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2024-03-13 10:59:48 +0100 |
commit | 710996262e5dc3e39649e210eb58c5a93e5ab2d9 (patch) | |
tree | b567d39745b85cc91837abd18e7882049a842268 | |
parent | a3c0e1737d921de757c957bc9d831d85d446c4a1 (diff) |
openpgp: Implement v6 key packet support.
todo:
- encrypt/decrypt secrets
-rw-r--r-- | openpgp/src/cert/builder.rs | 123 | ||||
-rw-r--r-- | openpgp/src/cert/builder/key.rs | 4 | ||||
-rw-r--r-- | openpgp/src/crypto/hash.rs | 56 | ||||
-rw-r--r-- | openpgp/src/crypto/key.rs | 77 | ||||
-rw-r--r-- | openpgp/src/crypto/mod.rs | 1 | ||||
-rw-r--r-- | openpgp/src/packet/key.rs | 61 | ||||
-rw-r--r-- | openpgp/src/packet/key/conversions.rs | 1 | ||||
-rw-r--r-- | openpgp/src/packet/key/v6.rs | 545 | ||||
-rw-r--r-- | openpgp/src/packet/mod.rs | 92 | ||||
-rw-r--r-- | openpgp/src/packet/prelude.rs | 1 | ||||
-rw-r--r-- | openpgp/src/parse.rs | 240 | ||||
-rw-r--r-- | openpgp/src/serialize.rs | 122 | ||||
-rw-r--r-- | openpgp/src/serialize/cert.rs | 10 |
13 files changed, 1318 insertions, 15 deletions
diff --git a/openpgp/src/cert/builder.rs b/openpgp/src/cert/builder.rs index 0d9f77b1..51b3ea4b 100644 --- a/openpgp/src/cert/builder.rs +++ b/openpgp/src/cert/builder.rs @@ -5,7 +5,8 @@ use crate::packet; use crate::packet::{ Key, key::Key4, - key::KeyRole, + key::Key6, + key::UnspecifiedRole, key::SecretKey as KeySecretKey, key::SecretParts as KeySecretParts, }; @@ -140,10 +141,22 @@ impl CipherSuite { Ok(()) } - fn generate_key<K, R>(self, flags: K) - -> Result<Key<KeySecretParts, R>> - where R: KeyRole, - K: AsRef<KeyFlags>, + fn generate_key<K>(self, flags: K, version: u8) + -> Result<Key<KeySecretParts, UnspecifiedRole>> + where K: AsRef<KeyFlags>, + { + match version { + 4 => Ok(self.generate_v4_key(flags)?.into()), + 6 => Ok(self.generate_v6_key(flags)?.into()), + n => Err(Error::InvalidArgument( + format!("Generating OpenPGP v{} keys not supported", n) + ).into()), + } + } + + fn generate_v4_key<K>(self, flags: K) + -> Result<Key4<KeySecretParts, UnspecifiedRole>> + where K: AsRef<KeyFlags>, { use crate::types::Curve; @@ -188,7 +201,58 @@ impl CipherSuite { .into()), } }, - }.map(|key| key.into()) + } + } + + fn generate_v6_key<K>(self, flags: K) + -> Result<Key6<KeySecretParts, UnspecifiedRole>> + where K: AsRef<KeyFlags>, + { + use crate::types::Curve; + + let flags = flags.as_ref(); + let sign = flags.for_certification() || flags.for_signing() + || flags.for_authentication(); + let encrypt = flags.for_transport_encryption() + || flags.for_storage_encryption(); + + match self { + CipherSuite::RSA2k => + Key4::generate_rsa(2048), + CipherSuite::RSA3k => + Key4::generate_rsa(3072), + CipherSuite::RSA4k => + Key4::generate_rsa(4096), + CipherSuite::Cv25519 | + CipherSuite::P256 | CipherSuite::P384 | CipherSuite::P521 => { + let curve = match self { + CipherSuite::Cv25519 if sign => Curve::Ed25519, + CipherSuite::Cv25519 if encrypt => Curve::Cv25519, + CipherSuite::Cv25519 => { + return Err(Error::InvalidOperation( + "No key flags set".into()) + .into()); + } + CipherSuite::P256 => Curve::NistP256, + CipherSuite::P384 => Curve::NistP384, + CipherSuite::P521 => Curve::NistP521, + _ => unreachable!(), + }; + + match (sign, encrypt) { + (true, false) => Key4::generate_ecc(true, curve), + (false, true) => Key4::generate_ecc(false, curve), + (true, true) => + Err(Error::InvalidOperation( + "Can't use key for encryption and signing".into()) + .into()), + (false, false) => + Err(Error::InvalidOperation( + "No key flags set".into()) + .into()), + } + }, + }.map(Key6::from_common) } } @@ -269,6 +333,7 @@ assert_send_and_sync!(KeyBlueprint); pub struct CertBuilder<'a> { creation_time: Option<std::time::SystemTime>, ciphersuite: CipherSuite, + version: u8, primary: KeyBlueprint, subkeys: Vec<(Option<SignatureBuilder>, KeyBlueprint)>, userids: Vec<(Option<SignatureBuilder>, packet::UserID)>, @@ -322,6 +387,7 @@ impl CertBuilder<'_> { CertBuilder { creation_time: None, ciphersuite: CipherSuite::default(), + version: 4, primary: KeyBlueprint{ flags: KeyFlags::empty().set_certification(), validity: None, @@ -552,6 +618,44 @@ impl CertBuilder<'_> { self } + /// Sets the OpenPGP version to generate keys for. + /// + /// Supported are version 4 and version 6 keys. By default, we + /// generate OpenPGP v4 keys. + /// + /// # Examples + /// + /// ``` + /// use sequoia_openpgp as openpgp; + /// use openpgp::cert::prelude::*; + /// use openpgp::types::PublicKeyAlgorithm; + /// + /// # fn main() -> openpgp::Result<()> { + /// let (key, _) = + /// CertBuilder::general_purpose(None, Some("alice@example.org")) + /// .generate()?; + /// assert_eq!(key.primary_key().version(), 4); + /// + /// let (key, _) = + /// CertBuilder::general_purpose(None, Some("alice@example.org")) + /// .set_version(6)? + /// .generate()?; + /// assert_eq!(key.primary_key().version(), 6); + /// # Ok(()) + /// # } + /// ``` + pub fn set_version(mut self, version: u8) -> Result<Self> { + match version { + 4 => (), + 6 => (), + n => return Err(Error::InvalidArgument( + format!("Generating OpenPGP v{} keys not supported", n) + ).into()), + } + self.version = version; + Ok(self) + } + /// Adds a User ID. /// /// Adds a User ID to the certificate. The first User ID that is @@ -1495,7 +1599,8 @@ impl CertBuilder<'_> { let flags = &blueprint.flags; let mut subkey = blueprint.ciphersuite .unwrap_or(self.ciphersuite) - .generate_key(flags)?; + .generate_key(flags, self.version)? + .role_into_subordinate(); subkey.set_creation_time(creation_time)?; let sig = template.unwrap_or_else( @@ -1552,7 +1657,9 @@ impl CertBuilder<'_> { { let mut key = self.primary.ciphersuite .unwrap_or(self.ciphersuite) - .generate_key(KeyFlags::empty().set_certification())?; + .generate_key(KeyFlags::empty().set_certification(), + self.version)? + .role_into_primary(); key.set_creation_time(creation_time)?; let sig = SignatureBuilder::new(SignatureType::DirectKey); let sig = Self::signature_common( diff --git a/openpgp/src/cert/builder/key.rs b/openpgp/src/cert/builder/key.rs index 4d211750..1619c82a 100644 --- a/openpgp/src/cert/builder/key.rs +++ b/openpgp/src/cert/builder/key.rs @@ -143,8 +143,10 @@ impl KeyBuilder { /// NTP is widely used, empirically it seems that some virtual /// machines have laggy clocks. pub fn subkey(self, vc: ValidCert) -> Result<SubkeyBuilder<'_>> { + let version = vc.primary_key().version(); let mut key: Key<key::SecretParts, key::SubordinateRole> - = self.cipher_suite.generate_key(&self.flags)?; + = self.cipher_suite.generate_key(&self.flags, version)? + .role_into_subordinate(); let ct = self.creation_time.unwrap_or_else(|| { crate::now() - Duration::new(SIG_BACKDATE_BY, 0) }); diff --git a/openpgp/src/crypto/hash.rs b/openpgp/src/crypto/hash.rs index 7bd9a9e1..f1ef2098 100644 --- a/openpgp/src/crypto/hash.rs +++ b/openpgp/src/crypto/hash.rs @@ -38,7 +38,7 @@ use crate::packet::Key; use crate::packet::UserID; use crate::packet::UserAttribute; use crate::packet::key; -use crate::packet::key::Key4; +use crate::packet::key::{Key4, Key6}; use crate::packet::Signature; use crate::packet::signature::{self, Signature3, Signature4}; use crate::Result; @@ -384,6 +384,18 @@ impl Hash for UserAttribute { } } +impl<P, R> Hash for Key<P, R> + where P: key::KeyParts, + R: key::KeyRole, +{ + fn hash(&self, hash: &mut dyn Digest) { + match self { + Key::V4(k) => k.hash(hash), + Key::V6(k) => k.hash(hash), + } + } +} + impl<P, R> Hash for Key4<P, R> where P: key::KeyParts, R: key::KeyRole, @@ -423,6 +435,48 @@ impl<P, R> Hash for Key4<P, R> } } +impl<P, R> Hash for Key6<P, R> + where P: key::KeyParts, + R: key::KeyRole, +{ + fn hash(&self, hash: &mut dyn Digest) { + use crate::serialize::MarshalInto; + + // We hash 15 bytes plus the MPIs. But, the len doesn't + // include the tag (1 byte) or the length (4 bytes). + let len = (15 - 5) + self.mpis().serialized_len() as u32; + + let mut header: Vec<u8> = Vec::with_capacity(9); + + // Tag. + header.push(0x9b); + + // Length (4 bytes, big endian). + header.extend_from_slice(&len.to_be_bytes()); + + // Version. + header.push(6); + + // Creation time. + let creation_time: u32 = + Timestamp::try_from(self.creation_time()) + .unwrap_or_else(|_| Timestamp::from(0)) + .into(); + header.extend_from_slice(&creation_time.to_be_bytes()); + + // Algorithm. + header.push(self.pk_algo().into()); + + // Length of all MPIs. + header.extend_from_slice( + &(self.mpis().serialized_len() as u32).to_be_bytes()); + hash.update(&header[..]); + + // MPIs. + self.mpis().hash(hash); + } +} + impl Hash for Signature { fn hash(&self, hash: &mut dyn Digest) { match self { diff --git a/openpgp/src/crypto/key.rs b/openpgp/src/crypto/key.rs new file mode 100644 index 00000000..fd3f9eb0 --- /dev/null +++ b/openpgp/src/crypto/key.rs @@ -0,0 +1,77 @@ +//! Common secret key related operations. + +use std::time::SystemTime; + +use crate::{ + Result, + packet::key::{self, Key4, Key6, SecretParts}, + types::{ + Curve, + HashAlgorithm, + SymmetricAlgorithm, + }, +}; + +impl<R> Key6<SecretParts, R> + where R: key::KeyRole, +{ + + /// Creates a new OpenPGP secret key packet for an existing X25519 key. + /// + /// The ECDH key will use hash algorithm `hash` and symmetric + /// algorithm `sym`. If one or both are `None` secure defaults + /// will be used. The key will have it's creation date set to + /// `ctime` or the current time if `None` is given. + pub fn import_secret_cv25519<H, S, T>(private_key: &[u8], + hash: H, sym: S, ctime: T) + -> Result<Self> where H: Into<Option<HashAlgorithm>>, + S: Into<Option<SymmetricAlgorithm>>, + T: Into<Option<SystemTime>> + { + Key4::import_secret_cv25519(private_key, hash, sym, ctime) + .map(Key6::from_common) + } + + /// Creates a new OpenPGP secret key packet for an existing Ed25519 key. + /// + /// The ECDH key will use hash algorithm `hash` and symmetric + /// algorithm `sym`. If one or both are `None` secure defaults + /// will be used. The key will have it's creation date set to + /// `ctime` or the current time if `None` is given. + pub fn import_secret_ed25519<T>(private_key: &[u8], ctime: T) + -> Result<Self> where T: Into<Option<SystemTime>> + { + Key4::import_secret_ed25519(private_key, ctime) + .map(Key6::from_common) + } + + /// Creates a new OpenPGP public key packet for an existing RSA key. + /// + /// The RSA key will use public exponent `e` and modulo `n`. The key will + /// have it's creation date set to `ctime` or the current time if `None` + /// is given. + #[allow(clippy::many_single_char_names)] + pub fn import_secret_rsa<T>(d: &[u8], p: &[u8], q: &[u8], ctime: T) + -> Result<Self> where T: Into<Option<SystemTime>> + { + Key4::import_secret_rsa(d, p, q, ctime) + .map(Key6::from_common) + } + + /// Generates a new RSA key with a public modulos of size `bits`. + pub fn generate_rsa(bits: usize) -> Result<Self> { + Key4::generate_rsa(bits) + .map(Key6::from_common) + } + + /// Generates a new ECC key over `curve`. + /// + /// If `for_signing` is false a ECDH key, if it's true either a + /// EdDSA or ECDSA key is generated. Giving `for_signing == true` and + /// `curve == Cv25519` will produce an error. Likewise + /// `for_signing == false` and `curve == Ed25519` will produce an error. + pub fn generate_ecc(for_signing: bool, curve: Curve) -> Result<Self> { + Key4::generate_ecc(for_signing, curve) + .map(Key6::from_common) + } +} diff --git a/openpgp/src/crypto/mod.rs b/openpgp/src/crypto/mod.rs index 1c136102..a3b085aa 100644 --- a/openpgp/src/crypto/mod.rs +++ b/openpgp/src/crypto/mod.rs @@ -34,6 +34,7 @@ pub use self::asymmetric::{Signer, Decryptor, KeyPair}; pub(crate) mod backend; pub mod ecdh; pub mod hash; +mod key; pub mod mem; pub mod mpi; mod s2k; diff --git a/openpgp/src/packet/key.rs b/openpgp/src/packet/key.rs index f7287f5d..89a02ad4 100644 --- a/openpgp/src/packet/key.rs +++ b/openpgp/src/packet/key.rs @@ -98,7 +98,11 @@ use crate::PublicKeyAlgorithm; use crate::seal; use crate::SymmetricAlgorithm; use crate::HashAlgorithm; -use crate::types::{Curve, Timestamp}; +use crate::types::{ + AEADAlgorithm, + Curve, + Timestamp, +}; use crate::crypto::S2K; use crate::Result; use crate::crypto::Password; @@ -109,6 +113,8 @@ use crate::KeyHandle; use crate::policy::HashAlgoSecurity; mod conversions; +mod v6; +pub use v6::Key6; /// A marker trait that captures whether a `Key` definitely contains /// secret key material. @@ -1789,6 +1795,8 @@ pub struct Encrypted { s2k: S2K, /// Symmetric algorithm used to encrypt the secret key material. algo: SymmetricAlgorithm, + /// AEAD algorithm and IV used to encrypt the secret key material. + aead: Option<(AEADAlgorithm, Box<[u8]>)>, /// Checksum method. checksum: Option<mpi::SecretKeyChecksum>, /// Encrypted MPIs prefixed with the IV. @@ -1850,13 +1858,30 @@ impl Encrypted { } /// Creates a new encrypted key object. + pub fn new_aead(s2k: S2K, + sym_algo: SymmetricAlgorithm, + aead_algo: AEADAlgorithm, + aead_iv: Box<[u8]>, + ciphertext: Box<[u8]>) + -> Self + { + Encrypted { + s2k, + algo: sym_algo, + aead: Some((aead_algo, aead_iv)), + checksum: None, + ciphertext: Ok(ciphertext), + } + } + + /// Creates a new encrypted key object. pub(crate) fn new_raw(s2k: S2K, algo: SymmetricAlgorithm, checksum: Option<mpi::SecretKeyChecksum>, ciphertext: std::result::Result<Box<[u8]>, Box<[u8]>>) -> Self { - Encrypted { s2k, algo, checksum, ciphertext } + Encrypted { s2k, algo, aead: None, checksum, ciphertext } } /// Returns the key derivation mechanism. @@ -1870,6 +1895,17 @@ impl Encrypted { self.algo } + /// Returns the AEAD algorithm used to encrypt the secret key + /// material. + pub fn aead_algo(&self) -> Option<AEADAlgorithm> { + self.aead.as_ref().map(|(a, _iv)| *a) + } + + /// Returns the AEAD IV used to encrypt the secret key material. + pub fn aead_iv(&self) -> Option<&[u8]> { + self.aead.as_ref().map(|(_a, iv)| &iv[..]) + } + /// Returns the checksum method used to protect the encrypted /// secret key material, if any. pub fn checksum(&self) -> Option<mpi::SecretKeyChecksum> { @@ -1937,9 +1973,14 @@ impl<P, R> Arbitrary for super::Key<P, R> where P: KeyParts, P: Clone, R: KeyRole, R: Clone, Key4<P, R>: Arbitrary, + Key6<P, R>: Arbitrary, { fn arbitrary(g: &mut Gen) -> Self { - Key4::arbitrary(g).into() + if <bool>::arbitrary(g) { + Key4::arbitrary(g).into() + } else { + Key6::arbitrary(g).into() + } } } @@ -2762,7 +2803,21 @@ FwPoSAbbsLkNS/iNN2MDGAVYvezYn2QZ } } } + Ok(()) + } + #[test] + fn v6_key_fingerprint() -> Result<()> { + let p = Packet::from_bytes("-----BEGIN PGP ARMORED FILE----- + +xjcGY4d/4xYAAAAtCSsGAQQB2kcPAQEHQPlNp7tI1gph5WdwamWH0DMZmbudiRoI +JC6thFQ9+JWj +=SgmS +-----END PGP ARMORED FILE-----")?; + let k: &Key<PublicParts, PrimaryRole> = p.downcast_ref().unwrap(); + assert_eq!(k.fingerprint().to_string(), + "4EADF309C6BC874AE04702451548F93F\ + 96FA7A01D0A33B5AF7D4E379E0F9F8EE".to_string()); Ok(()) } } diff --git a/openpgp/src/packet/key/conversions.rs b/openpgp/src/packet/key/conversions.rs index babdc192..7b479578 100644 --- a/openpgp/src/packet/key/conversions.rs +++ b/openpgp/src/packet/key/conversions.rs @@ -527,6 +527,7 @@ macro_rules! create_conversions { create_conversions!(Key<>); create_conversions!(Key4<>); +create_conversions!(Key6<>); create_conversions!(KeyBundle<>); // A hack, since the type has to be an ident, which means that we diff --git a/openpgp/src/packet/key/v6.rs b/openpgp/src/packet/key/v6.rs new file mode 100644 index 00000000..f0711e50 --- /dev/null +++ b/openpgp/src/packet/key/v6.rs @@ -0,0 +1,545 @@ +//! OpenPGP v6 key packet. + +use std::fmt; +use std::cmp::Ordering; +use std::hash::Hasher; +use std::time; + +#[cfg(test)] +use quickcheck::{Arbitrary, Gen}; + +use crate::crypto::{mpi, hash::{Hash, Digest}}; +use crate::packet::key::{ + KeyParts, + KeyRole, + PublicParts, + SecretParts, + UnspecifiedParts, +}; +use crate::packet::prelude::*; +use crate::PublicKeyAlgorithm; +use crate::SymmetricAlgorithm; +use crate::HashAlgorithm; +use crate::types::Timestamp; +use crate::Result; +use crate::crypto::Password; +use crate::KeyID; +use crate::Fingerprint; +use crate::KeyHandle; +use crate::policy::HashAlgoSecurity; + +/// Holds a public key, public subkey, private key or private subkey +/// packet. +/// +/// Use [`Key6::generate_rsa`] or [`Key6::generate_ecc`] to create a +/// new key. +/// +/// Existing key material can be turned into an OpenPGP key using +/// [`Key6::new`], [`Key6::with_secret`], [`Key6::import_public_cv25519`], +/// [`Key6::import_public_ed25519`], [`Key6::import_public_rsa`], +/// [`Key6::import_secret_cv25519`], [`Key6::import_secret_ed25519`], +/// and [`Key6::import_secret_rsa`]. +/// +/// Whether you create a new key or import existing key material, you +/// still need to create a binding signature, and, for signing keys, a +/// back signature before integrating the key into a certificate. +/// +/// Normally, you won't directly use `Key6`, but [`Key`], which is a +/// relatively thin wrapper around `Key6`. +/// +/// See [Section 5.5 of RFC 4880] and [the documentation for `Key`] +/// for more details. +/// +/// [Section 5.5 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.5 +/// [the documentation for `Key`]: super::Key +/// [`Key`]: super::Key +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct Key6<P: KeyParts, R: KeyRole> { + pub(crate) common: Key4<P, R>, +} + +impl<P, R> fmt::Debug for Key6<P, R> +where P: KeyParts, + R: KeyRole, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Key6") + .field("fingerprint", &self.fingerprint()) + .field("creation_time", &self.creation_time()) + .field("pk_algo", &self.pk_algo()) + .field("mpis", &self.mpis()) + .field("secret", &self.optional_secret()) + .finish() + } +} + +impl<P, R> fmt::Display for Key6<P, R> +where P: KeyParts, + R: KeyRole, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.fingerprint()) + } +} + +impl<P, R> Key6<P, R> +where P: KeyParts, + R: KeyRole, +{ + /// The security requirements of the hash algorithm for + /// self-signatures. + /// + /// A cryptographic hash algorithm usually has [three security + /// properties]: pre-image resistance, second pre-image + /// resistance, and collision resistance. If an attacker can + /// influence the signed data, then the hash algorithm needs to + /// have both second pre-image resistance, and collision + /// resistance. If not, second pre-image resistance is + /// sufficient. + /// + /// [three security properties]: https://en.wikipedia.org/wiki/Cryptographic_hash_function#Properties + /// + /// In general, an attacker may be able to influence third-party + /// signatures. But direct key signatures, and binding signatures + /// are only over data fully determined by signer. And, an + /// attacker's control over self signatures over User IDs is + /// limited due to their structure. + /// + /// These observations can be used to extend the life of a hash + /// algorithm after its collision resistance has been partially + /// compromised, but not completely broken. For more details, + /// please refer to the documentation for [HashAlgoSecurity]. + /// + /// [HashAlgoSecurity]: crate::policy::HashAlgoSecurity + pub fn hash_algo_security(&self) -> HashAlgoSecurity { + HashAlgoSecurity::SecondPreImageResistance + } + + /// Compares the public bits of two keys. + /// + /// This returns `Ordering::Equal` if the public MPIs, creation + /// time, and algorithm of the two `Key6`s match. This does not + /// consider the packets' encodings, packets' tags or their secret + /// key material. + pub fn public_cmp<PB, RB>(&self, b: &Key6<PB, RB>) -> Ordering + where PB: KeyParts, + RB: KeyRole, + { + self.mpis().cmp(b.mpis()) + .then_with(|| self.creation_time().cmp(&b.creation_time())) + .then_with(|| self.pk_algo().cmp(&b.pk_algo())) + } + + /// Tests whether two keys are equal modulo their secret key + /// material. + /// + /// This returns true if the public MPIs, creation time and + /// algorithm of the two `Key6`s match. This does not consider + /// the packets' encodings, packets' tags or their secret key + /// material. + pub fn public_eq<PB, RB>(&self, b: &Key6<PB, RB>) -> bool + where PB: KeyParts, + RB: KeyRole, + { + self.public_cmp(b) == Ordering::Equal + } + + /// Hashes everything but any secret key material into state. + /// + /// This is an alternate implementation of [`Hash`], which never + /// hashes the secret key material. + /// + /// [`Hash`]: std::hash::Hash + pub fn public_hash<H>(&self, state: &mut H) + where H: Hasher + { + self.common.public_hash(state); + } +} + +impl<P, R> Key6<P, R> +where + P: KeyParts, + R: KeyRole, +{ + /// Creates a v6 key from a v4 key. Used internally in + /// constructors. + pub(crate) fn from_common(common: Key4<P, R>) -> Self { + Key6 { common } + } + + /// Creates an OpenPGP public key from the specified key material. + /// + /// This is an internal version for parse.rs that avoids going + /// through SystemTime. + pub(crate) fn make<T>(creation_time: T, + pk_algo: PublicKeyAlgorithm, + mpis: mpi::PublicKey, + secret: Option<SecretKeyMaterial>) + -> Result<Self> + where + T: Into<Timestamp>, + { + Ok(Key6 { + common: Key4::make(creation_time, pk_algo, mpis, secret)?, + }) + } +} + +impl<R> Key6<key::PublicParts, R> +where R: KeyRole, +{ + /// Creates an OpenPGP public key from the specified key material. + pub fn new<T>(creation_time: T, pk_algo: PublicKeyAlgorithm, + mpis: mpi::PublicKey) + -> Result<Self> + where T: Into<time::SystemTime> + { + Ok(Key6 { + common: Key4::new(creation_time, pk_algo, mpis)?, + }) + } + + /// Creates an OpenPGP public key packet from existing X25519 key + /// material. + /// + /// The ECDH key will use hash algorithm `hash` and symmetric + /// algorithm `sym`. If one or both are `None` secure defaults + /// will be used. The key will have its creation date set to |