///|
/// A collection of public and private keys with id lookup, mirroring the
/// gopenpgp KeyRing role.
pub struct KeyRing {
priv public_keys : Array[PublicKey]
priv private_keys : Array[PrivateKey]
} derive(Debug)
///|
/// Create an empty key ring.
pub fn KeyRing::new() -> KeyRing {
KeyRing::{ public_keys: [], private_keys: [], }
}
///|
/// Add a public key (available to encryption/verification lookups).
pub fn KeyRing::add_public(self : KeyRing, key : PublicKey) -> Unit {
self.public_keys.push(key)
}
///|
/// Add a private key (available to decryption lookups).
pub fn KeyRing::add_private(self : KeyRing, key : PrivateKey) -> Unit {
self.private_keys.push(key)
}
///|
/// Number of public keys in the ring.
pub fn KeyRing::public_count(self : KeyRing) -> Int32 {
self.public_keys.length()
}
///|
/// Number of private keys in the ring.
pub fn KeyRing::private_count(self : KeyRing) -> Int32 {
self.private_keys.length()
}
///|
/// All public key packets (primary + subkeys) of a key, with their packets.
fn all_public_packets(key : PublicKey) -> Array[@packet.PublicKeyPacket] {
let out = [key.primary]
for c in key.subkeys {
out.push(c.key)
}
out
}
///|
/// True when the packet is an encryption-capable component of `key` that
/// is still usable at `now` (key/subkey not revoked, flags permit —
/// absent flags count as capable —, nothing expired). Each subkey is
/// judged by its own binding (N2).
fn key_packet_encryption_usable_at(
key : PublicKey,
packet : @packet.PublicKeyPacket,
now : Int64,
) -> Bool raise {
if key.is_revoked(now) {
return false
}
for c in key.subkeys {
if c.key.key_id().equal(packet.key_id()) {
return !subkey_revoked_at(c.revocation, key.primary, c.key, now) &&
component_usable(c.key, Some(c.binding), FLAG_ENCRYPT, now)
}
}
key.primary.algo().is_rsa() &&
component_usable(key.primary, Some(key.self_signature()), FLAG_ENCRYPT, now)
}
///|
/// True when the packet is a component of `key` that may verify at `now`
/// (not revoked, sign/certify flags permit, nothing expired).
fn key_packet_verify_usable_at(
key : PublicKey,
packet : @packet.PublicKeyPacket,
now : Int64,
) -> Bool raise {
if key.is_revoked(now) {
return false
}
if packet.key_id().equal(key.primary.key_id()) {
return component_usable(
key.primary,
Some(key.self_signature()),
FLAG_SIGN_OR_CERTIFY,
now,
)
}
for c in key.subkeys {
if component_usable(c.key, Some(c.binding), FLAG_SIGN_OR_CERTIFY, now) {
return true
}
}
false
}
///|
/// Find an encryption-capable public key packet by 8-octet key id that is
/// still usable at `now` (lifecycle-checked: revocation, key flags and
/// expiry — a key without key flags counts as capable; `now = 0` skips
/// the time checks). All public-key algorithms participate; errors from
/// key-id computation propagate (F15): the keys were parsed on the way
/// in, so a failure here is an invariant violation, not a lookup miss.
pub fn KeyRing::find_encryption_key(
self : KeyRing,
key_id : BytesView,
now~ : Int64 = 0L,
) -> @packet.PublicKeyPacket? raise {
for key in self.public_keys {
for p in all_public_packets(key) {
let id = p.key_id()
if id.equal(key_id.to_owned()) &&
key_packet_encryption_usable_at(key, p, now) {
return Some(p)
}
}
}
None
}
///|
/// Find the unlocked private key packet matching a PKESK key id.
/// Deliberately NOT lifecycle-gated: decrypting mail that arrived before
/// a key expired must keep working (mirrors gopenpgp, whose decryption
/// path does not check expiry either).
pub fn KeyRing::find_decryption_key(
self : KeyRing,
key_id : BytesView,
) -> @packet.SecretKeyPacket? raise {
for key in self.private_keys {
let candidates : Array[@packet.SecretKeyPacket] = [key.primary]
for c in key.subkeys {
candidates.push(c.secret)
}
for sk in candidates {
let id = sk.public.key_id()
if id.equal(key_id.to_owned()) && sk.s2k_usage == 0 {
return Some(sk)
}
}
}
None
}
///|
/// Find a transferable key that can verify a signature from `key_id` and
/// is still usable at `now` (revocation, sign/certify flags, expiry).
pub fn KeyRing::find_verification_key(
self : KeyRing,
key_id : BytesView,
now~ : Int64 = 0L,
) -> PublicKey? raise {
for key in self.public_keys {
for p in all_public_packets(key) {
let id = p.key_id()
if id.equal(key_id.to_owned()) && key_packet_verify_usable_at(key, p, now) {
return Some(key)
}
}
}
None
}
///|
pub extend KeyRing with @moonbitlang/core/debug.Debug::{to_repr}