Getting Alerted on Expiring GPG Keys

GnuPG is a powerful cryptographic tool that enjoys widespread support by F/LOSS mail user agents. However, due to a lack of conventional public key management it is plagued by key-related issues. One such issue is the expiry of GnuPG keys, which often goes unnoticed by affected messaging partners until such time when it actually disturbs messaging between them.

The way user agents handle key expiry situations can roughly be described as follows:

(i) If the GnuPG key of a sender of a message is expired, user agents can not regard it as valid, and the attempt to sign the message using the sender’s key will fail. Once the GnuPG key of a user has expired the user will realize this situation without an external alerting mechanism only when trying to sign a message.

(ii) If the GnuPG key of an intended recipient of a message is expired, user agents can not regard it as valid; the attempt to encrypt a message using an expired key of a recipient will fail. Also, the sending of an encrypted message to multiple recipients, where a subset of the recipients feature expired public keys, can be blocked. Other users will realize this situation once they try to send an encrypted message to the user in question.

In the proposed setup specific GnuPG keys (specified using a list of key IDs) stored in ~/.gnupg are scanned periodically. The scan is repeated daily, at 11 A.M. local time (which can be changed to whatever systemd timers support). The tolerated remaining validity will be set to „more than 30 days“ (this, too, can easily be set to a different value).

The check is performed by a systemd user service unit called check-gnupg-key-expiry.service, this unit notifies the user by means of notify-send(1) which generates desktop notifications. The service is periodically run by a systemd user timer unit check-gnupg-key-expiry.timer. The service’s „worker script“ is stored in /usr/local/bin/check-gnupg-key-expiry; it is implemented in Perl with the „GnuPG::Interface“ library which on Debian-based systems can be installed as follows:

apt install libgnupg-interface-perl

The worker script /usr/local/bin/check-gnupg-key-expiry contains:

#!/usr/bin/env perl
# Usage: check-gnupg-key-expiry [KEY_ID ...]

use strict;
use warnings;
use GnuPG::Interface;

# Configuration

my $delta_days = 30;
my $now = time();
my $delta = 3600*24*$delta_days;
my @warnings = ();
my $rv = 0;

# Arguments

my @key_ids = @ARGV;

# Main Program

my $gnupg = GnuPG::Interface->new();
my @keys = $gnupg->get_public_keys(@key_ids);

foreach my $key (@keys) {
    if(defined($key->expiration_date)) {
        if($key->expiration_date - $now <= $delta) {
            push @warnings, {
                id => $key->hex_id,
                user => $key->user_ids->[0]->as_string,
                expire => $key->expiration_date_string
            };

            $rv = 1;
        }
    }
}

foreach my $warning (@warnings) {
    print
        "EXPIRATION WARNING: ".
        "Key-ID: ".$warning->{id}." ".
        "(User \"".$warning->{user}."\"): ".
        $warning->{expire}."\n";
}

exit $rv;

Calling the script without arguments will inspect all public keys in the invoking user’s GnuPG keyring and exit with a return value of 1 if one or more of these keys will expire in 30 days or less. Each argument to the script will be taken as a key ID in the user’s keyring and will restrict the expiry check to those keys. Since my personal public key’s ID is 72B289BFC0ABBF39135D72529AABA177548ABF0F, to check if my personal public key will soon expire, i would run the script as follows:

check-gnupg-key-expiry 72B289BFC0ABBF39135D72529AABA177548ABF0F

An environment file in directory ~/.config/environment.d specifies the key ID (or space-separated list of IDs) as an environment variable:

# ~/.config/environment.d/check-gnupg-key-expiry.conf
CHECK_GNUPG_KEY_EXPIRY_KEY_IDS="72B289BFC0ABBF39135D72529AABA177548ABF0F"

I then create the systemd user service unit, which will use notify-send(1) to emit a critical warning if the key is found to expire soon:

# ~/.config/systemd/user/check-gnupg-key-expiry.service
[Unit]
Description=Check for expiring GPG Keys
[Service]
ExecStart=check-gnupg-key-expiry $CHECK_GNUPG_KEY_EXPIRY_KEY_IDS
ExecStopPost=/bin/sh -c '\
    if [ "$$EXIT_STATUS" -ne 0 ] ; then \
        /usr/bin/notify-send \
            --urgency=critical \
            "Found expiring GPG key(s)." \
            "See journalctl --user-unit check-gnupg-key-expiry.service for details." ; \
    fi \
    '

The service is to be executed everyday at 11 A.M. by means of a systemd user timer unit:

# ~/.config/systemd/user/check-gnupg-key-expiry.timer
[Unit]
Description=Perform daily check for expiring GnuPG key(s)
[Timer]
OnCalendar=*-*-* 11:00:00
[Install]
WantedBy=default.target

Logged in as the user in question, I mark the timer unit to be started by default and activate it immediately:

systemctl --user daemon-reload
systemctl --user enable --now check-gnupg-key-expiry.timer