///|
/// Key lifecycle (M1): expiry, revocation and capability checks aligned
/// with gopenpgp's `Key.IsExpired / IsRevoked / CanEncrypt / CanVerify`.
///
/// Every function takes `now` as seconds since the Unix epoch; `now = 0`
/// disables all time checks โ the non-native targets have no clock
/// (`@primitives.unix_now` yields 0 there), so behavior degrades to the
/// pre-lifecycle structural rules instead of spuriously failing.
///|
/// Key flag bits (RFC 9580 ยง5.2.3.29). The capability masks used here
/// span octet 0 only, where all the classic capabilities live.
const FLAG_CERTIFY : Int32 = 0x01
///|
const FLAG_SIGN : Int32 = 0x02
///|
const FLAG_ENCRYPT_COMM : Int32 = 0x04
///|
const FLAG_ENCRYPT_STORAGE : Int32 = 0x08
///|
const FLAG_ENCRYPT : Int32 = FLAG_ENCRYPT_COMM | FLAG_ENCRYPT_STORAGE
///|
const FLAG_SIGN_OR_CERTIFY : Int32 = FLAG_SIGN | FLAG_CERTIFY
///|
/// True when a key packet expired at `now`: the governing signature's
/// Key Expiration Time is nonzero and `now` lies after the key's creation
/// time plus that many seconds (go-crypto's `keyExpired` semantics: the
/// lifetime counts from the key packet's creation, not the signature's).
fn packet_expired_at(
packet : @packet.PublicKeyPacket,
governing_sig : @packet.SignaturePacket?,
now : Int64,
) -> Bool {
if now == 0L {
return false
}
let lifetime = match governing_sig {
Some(s) => s.key_expiration_time()
None => 0
}
if lifetime == 0 {
return false
}
now > (packet.creation_time + lifetime).to_int64()
}
///|
/// True when a signature has itself expired at `now`: its Signature
/// Expiration Time is nonzero and `now` lies after its creation time plus
/// that many seconds. An expired governing signature leaves the component
/// it certifies without a current statement of capability.
fn sig_expired_at(sig : @packet.SignaturePacket, now : Int64) -> Bool {
if now == 0L {
return false
}
let lifetime = sig.sig_expiration_time()
if lifetime == 0 {
return false
}
match sig.creation_time() {
Some(created) => now > (created + lifetime).to_int64()
None => false
}
}
///|
fn sig_expired_opt(sig : @packet.SignaturePacket?, now : Int64) -> Bool {
match sig {
Some(s) => sig_expired_at(s, now)
None => false
}
}
///|
/// True when any stored primary-key revocation verifies over the key and
/// has already taken effect at `now`. A revocation whose cryptographic
/// check fails or errors is ignored โ an invalid revocation must not be
/// able to disable a key. `now = 0` still validates the signature but
/// ignores effective-date questions.
fn primary_revoked_at(
revocations : Array[@packet.SignaturePacket],
primary : @packet.PublicKeyPacket,
now : Int64,
) -> Bool {
for sig in revocations {
let verified = verify_key_signature(sig, primary, primary, direct=true) catch {
_ => false
}
if revocation_effective_at(sig, now) && verified {
return true
}
}
false
}
///|
/// True when a subkey revocation verifies over (primary, subkey) and has
/// taken effect at `now`.
fn subkey_revoked_at(
sig : @packet.SignaturePacket?,
primary : @packet.PublicKeyPacket,
subkey : @packet.PublicKeyPacket,
now : Int64,
) -> Bool {
match sig {
Some(s) => {
let verified = verify_key_signature(s, primary, subkey) catch {
_ => false
}
if revocation_effective_at(s, now) && verified {
return true
}
}
None => ()
}
false
}
///|
/// A future-dated revocation has not taken effect yet; undated ones are
/// treated as immediately effective. `now = 0` (no time) lets every
/// stored revocation through to the cryptographic check.
fn revocation_effective_at(sig : @packet.SignaturePacket, now : Int64) -> Bool {
if now == 0L {
return true
}
match sig.creation_time() {
Some(created) => created <= now.to_int()
None => true
}
}
///|
/// Key flags octet 0 from a signature's hashed area; None when the
/// subpacket is absent, which means "assume every capability" (the
/// legacy rule gopenpgp encodes as `FlagsValid = false`).
fn governing_flags(sig : @packet.SignaturePacket?) -> Int32? {
match sig {
Some(s) =>
match s.key_flags() {
Some(b) => if b.length() > 0 { Some(b[0].to_int()) } else { None }
None => None
}
None => None
}
}
///|
/// Usable for `capability` at `now`: the governing signature's flags
/// permit it (or are absent), the packet has not expired, and the
/// governing signature has not itself expired.
fn component_usable(
packet : @packet.PublicKeyPacket,
governing_sig : @packet.SignaturePacket?,
capability : Int32,
now : Int64,
) -> Bool {
let flag_ok = match governing_flags(governing_sig) {
Some(f) => (f & capability) != 0
None => true
}
flag_ok &&
!packet_expired_at(packet, governing_sig, now) &&
!sig_expired_opt(governing_sig, now)
}
///|
/// True when the primary key expired at `now`, per the Key Expiration
/// Time of its certification (or, failing that, its Direct Key
/// signature). Subkey expiry is judged separately inside `can_encrypt`.
pub fn PublicKey::is_expired(self : PublicKey, now : Int64) -> Bool {
if packet_expired_at(self.primary, Some(self.self_signature()), now) {
return true
}
match self.direct_signature {
Some(d) => packet_expired_at(self.primary, Some(d), now)
None => false
}
}
///|
/// True when a cryptographically valid, already-effective self-revocation
/// of the primary key is on record. Subkey revocations only affect
/// `can_encrypt` (they say nothing about the primary).
pub fn PublicKey::is_revoked(self : PublicKey, now : Int64) -> Bool {
primary_revoked_at(self.revocations, self.primary, now)
}
///|
/// The encryption-capable packet to use at `now`: the last usable
/// encryption subkey in wire order (each judged by its own binding โ
/// flags, expiry, revocation), otherwise the primary for RSA. None when
/// nothing on the key may encrypt at `now`.
pub fn PublicKey::encryption_packet_at(
self : PublicKey,
now : Int64,
) -> @packet.PublicKeyPacket? {
let mut chosen : @packet.PublicKeyPacket? = None
for c in self.subkeys {
if !subkey_revoked_at(c.revocation, self.primary, c.key, now) &&
component_usable(c.key, Some(c.binding), FLAG_ENCRYPT, now) {
chosen = Some(c.key)
}
}
match chosen {
Some(k) => return Some(k)
None => ()
}
if self.primary.algo().is_rsa() &&
component_usable(
self.primary,
Some(self.self_signature()),
FLAG_ENCRYPT,
now,
) {
return Some(self.primary)
}
None
}
///|
/// True when, at `now`, the key has an encryption-capable component:
/// neither the key nor the subkey is revoked, some component carries the
/// encryption key flags (absence of the subpacket counts as capable) and
/// that component has not expired.
pub fn PublicKey::can_encrypt(self : PublicKey, now : Int64) -> Bool {
if self.is_revoked(now) {
return false
}
self.encryption_packet_at(now) is Some(_)
}
///|
/// True when, at `now`, some component may sign or certify: not revoked,
/// primary (or any subkey) flags permit signing/certifying, and the
/// governing signatures have not expired.
pub fn PublicKey::can_verify(self : PublicKey, now : Int64) -> Bool {
if self.is_revoked(now) {
return false
}
if component_usable(
self.primary,
Some(self.self_signature()),
FLAG_SIGN_OR_CERTIFY,
now,
) {
return true
}
for c in self.subkeys {
if component_usable(c.key, Some(c.binding), FLAG_SIGN_OR_CERTIFY, now) {
return true
}
}
false
}
///|
/// See `PublicKey::is_expired`. Delegates to the public half.
pub fn PrivateKey::is_expired(self : PrivateKey, now : Int64) -> Bool {
self.to_public().is_expired(now)
}
///|
/// See `PublicKey::is_revoked`. Delegates to the public half.
pub fn PrivateKey::is_revoked(self : PrivateKey, now : Int64) -> Bool {
self.to_public().is_revoked(now)
}
///|
/// See `PublicKey::can_encrypt`. Delegates to the public half.
pub fn PrivateKey::can_encrypt(self : PrivateKey, now : Int64) -> Bool {
self.to_public().can_encrypt(now)
}
///|
/// See `PublicKey::can_verify`. Delegates to the public half.
pub fn PrivateKey::can_verify(self : PrivateKey, now : Int64) -> Bool {
self.to_public().can_verify(now)
}