keyfork/keyfork-derive-openpgp/src/lib.rs

122 lines
4.7 KiB
Rust
Raw Normal View History

use std::time::{Duration, SystemTime, SystemTimeError};
use ed25519_dalek::SigningKey;
use keyfork_derive_util::{
request::{DerivationResponse, TryFromDerivationResponseError},
DerivationIndex, ExtendedPrivateKey, PrivateKey,
};
use sequoia_openpgp::{
packet::{
key::{Key4, PrimaryRole, SubordinateRole},
signature::SignatureBuilder,
Key, UserID,
},
types::{KeyFlags, SignatureType},
Cert, Packet,
};
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("{0}")]
Anyhow(#[from] anyhow::Error),
#[error("Incorrect derived data: {0}")]
IncorrectDerivedData(#[from] TryFromDerivationResponseError),
#[error("Could not create derivation index: {0}")]
Index(#[from] keyfork_derive_util::index::Error),
#[error("Could not perform operation against private key: {0}")]
PrivateKey(#[from] keyfork_derive_util::extended_key::private_key::Error),
#[error("Invalid system time: {0}")]
SystemTime(#[from] SystemTimeError),
#[error("First key in certificate must have certify capability")]
NotCert,
#[error("Index out of bounds: {0}")]
IndexOutOfBounds(#[from] std::num::TryFromIntError),
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
pub fn derive(data: DerivationResponse, keys: &[KeyFlags], userid: UserID) -> Result<Cert> {
let primary_key_flags = match keys.get(0) {
Some(kf) if kf.for_certification() => kf,
_ => return Err(Error::NotCert),
};
let epoch = SystemTime::UNIX_EPOCH + Duration::from_secs(1);
let one_day = SystemTime::now() + Duration::from_secs(60 * 60 * 24);
// Create certificate with initial key and signature
let xprv = ExtendedPrivateKey::<SigningKey>::try_from(data)?;
let derived_primary_key = xprv.derive_child(&DerivationIndex::new(0, true)?)?;
let primary_key = Key::from(Key4::<_, PrimaryRole>::import_secret_ed25519(
&PrivateKey::to_bytes(derived_primary_key.private_key()),
epoch,
)?);
let cert = Cert::from_packets(vec![Packet::SecretKey(primary_key.clone())].into_iter())?;
// Sign and attach primary key and primary userid
let builder = SignatureBuilder::new(SignatureType::PositiveCertification)
.set_key_validity_period(one_day.duration_since(epoch)?)?
.set_signature_creation_time(epoch)?
.set_key_flags(primary_key_flags.clone())?;
let binding = userid.bind(&mut primary_key.clone().into_keypair()?, &cert, builder)?;
let cert = cert.insert_packets(vec![Packet::from(userid.clone()), binding.into()])?;
let policy = sequoia_openpgp::policy::StandardPolicy::new();
// Set certificate expiration to one day
let mut keypair = primary_key.clone().into_keypair()?;
let signatures = cert.set_expiration_time(&policy, None, &mut keypair, Some(one_day))?;
let cert = cert.insert_packets(signatures)?;
let mut cert = cert;
for (index, subkey_flags) in keys.iter().enumerate().skip(1) {
// Generate subkey
let index = u32::try_from(index)?;
let derived_key = xprv.derive_child(&DerivationIndex::new(index, true)?)?;
let subkey = Key::from(
Key4::<_, SubordinateRole>::import_secret_ed25519(
&PrivateKey::to_bytes(derived_key.private_key()),
epoch,
)
.unwrap(),
);
// As per OpenPGP spec, signing keys must backsig the primary key
let builder = if subkey_flags.for_signing() {
SignatureBuilder::new(SignatureType::SubkeyBinding)
.set_key_flags(subkey_flags.clone())?
.set_signature_creation_time(epoch)?
.set_key_validity_period(one_day.duration_since(epoch)?)?
.set_embedded_signature(
SignatureBuilder::new(SignatureType::PrimaryKeyBinding)
.set_signature_creation_time(epoch)?
.sign_primary_key_binding(
&mut subkey.clone().into_keypair()?,
&primary_key,
&subkey,
)?,
)?
} else {
SignatureBuilder::new(SignatureType::SubkeyBinding)
.set_key_flags(subkey_flags.clone())?
.set_signature_creation_time(epoch)?
.set_key_validity_period(one_day.duration_since(epoch)?)?
};
// Sign subkey with primary key and attach to cert
let binding =
builder.sign_subkey_binding(&mut primary_key.clone().into_keypair()?, None, &subkey)?;
cert = cert.insert_packets(vec![
Packet::SecretSubkey(subkey.clone()),
Packet::Signature(binding),
])?;
}
Ok(cert)
}