At Linux.Conf.Au this year, I went to the keysigning party, to collect some more signatures for my PGP keys. The keysigning party loosely followed the Zimmermann-Sassamen keysigning protocol, using pre-printed lists of fingerprints, but having each person read out the fingerprint while showing their ID for verification. (Normally the Zimmermann-Sassamen keysigning protocol would have everyone check a master hash of the list, which was shown and the keysigning party and have everyone confirm their master hash matched and their details in the list matched. But the key submission period here was too long to allow many people to print their own lists at home -- submissions closed well after most people had left home.)
Now that I am back home with my checklist of validated PGP fingerprints
I wanted to process the keys I checked and send out the signatures
to the recipients. The LCA2015 keysigning party
recommended caff to signing the keys
afterwards, which is part of the signing-party
package on Debian and
MacPorts. So I attempted to use that for signing the keys.
caff installation
Basic installation is easy:
sudo apt-get install signing-party # Debian, Ubuntu
sudo port install signing-party # MacPorts
but actually getting it to function well on a modern Mac OS X system is
non-trivial for various reasons. Along the way I filed a MacPorts bug
on caff -- after a couple of hours
of debugging "works for me" style perl -- only to later realise that the
problem was a side effect of (a) another problem and (b) the error messages
being hidden (by stderr
redirection which seemed to be the original
problem). But I am getting ahead of myself.
Because signing a bunch of keys (from a keysigning party) requires using your PGP keys a lot, most likely you will want to use gpg-agent to hold temporarily unlocked versions of the keys while you go through the signing process. Otherwise you will have to type the passphrase on your keys a lot. Installation is:
sudo apt-get install gnupg-agent # Debian, Ubuntu
sudo port install gpg-agent # MacPorts
On MacPorts, at least by default, it will also install
pinentry-mac
, a GUI
tool to accept your passphrase, that pops up a always foreground
window that appears on every virtual desktop until you enter your
passphrase... annoying, but the other options are not that much better.
Often gpg-agent
is run at startup, and keeps the keys (until some
timeout) through all sessions -- and MacPorts installs launchd
files
that will permit that, which are disabled by default (and prints instructions
on how to enable them). But it is also possible to run gpg-agent
in
a somewhat ad-hoc fashion, if you are using something like caff
in
a single terminal window -- ie single environment variable context.
To use gpg-agent
in an ad-hoc manner, start a terminal window for
the keysigning and then do:
GPG_TTY=$(tty)
export GPG_TTY
eval $(gpg-agent --daemon)
That will set GPG_TTY
to something like /dev/ttys000
(which
should match the terminal shown in who am i
), and GPG_AGENT_INFO
to something like /tmp/gpg-Ti9zgX/S.gpg-agent:2950:1
, which is a
three part (colon separated)
string of
(1) path to the unix domain socket for communication, PID, and agent
protocol).
Unfortuantely for some reason, at least on OS X with MacPorts, it appears
gpg
or some other tool is unable to understand that the GPG_AGENT_INFO
contains multiple parts, so you get errors like:
can't connect to `/tmp/gpg-Ti9zgX/S.gpg-agent:2950:1': No such file or directory
gpg: can't connect to `/tmp/gpg-Ti9zgX/S.gpg-agent:2950:1': connect failed
and the agent cannot be used :-( I never did figure out why the apparently documented layout did not work, but it can be made to work by throwing away the last two parts with:
GPG_AGENT_INFO=$(echo $GPG_AGENT_INFO | cut -f 1 -d :)
At this point, gpg
is ready to use gpg-agent
to handle the passphrase
prompting for you, so we can move on to setting up caff
.
However do note that it is vital that you set GPG_TTY
, otherwise
[certain actions that caff
attempts to perform will be failed by gpg
]
(https://trac.macports.org/ticket/46601) with the error messages involved
also being hidden. In particular certain commands seem to result in
gpg
reporting:
GPG_TTY must be set in shell (cannot determine automatically)
and then faililng. And caff
runs gpg
in such a way that those error
messages are either (a) completely thrown away (eg, stderr redirected to
/dev/null
, or (b) only shown
if you enable all the debug and tracing options (by editing the caff
source and uncommenting the output statements in debug()
, trace()
and
trace2()
.
Amongst other problems this also means that some commands caff
uses
to locate keys fail, resulting in:
[WARN] No public keys found with list-key E4D3E863 (note that caff uses its own keyring in /Users/ewen/.caff/gnupghome).
[NOTICE] No keys to sign found
even though the keys do actually exist. It took a long time to
figure out that not seting GPG_TTY
was the root cause of this,
in part due to caff
redirecting stderr to /dev/null
(thanks!)
-- and not redirecting stderr
appearing to solve that specific problem (but not others). (More
general problem reported to
Debian, who
seem to be the upstream.)
So do not forget to set GPG_TTY
!
caff configuration
caff
is configured from ~/.caffrc
, and by default will use
~/.caff/
as a working directory, maintaining a completely separate
gpg
home directory (~/.caff/gnupghome
) as well as exported
copies of signed keys in ~/.caff/keys
. There is an example ~/.caffrc
in the documentation, but just running caff
without doing anything will
also write a templated file out.
The reason for using a separate gpg
home directory is never really
explained, but as best I can determine the idea is (a) not to fill up
your main keyring with lots of keys you are mostly not using, and (b) to
avoid including keys/signatures on your main keyring until the recipient
has received them and added them (proving they do also have the email
address listed).
This does mean that if you want to sign keys which
(a) already exist in your main keyring, and (b) do not exist on a keyserver
(or (c) you do not want to download the keys from the keyserver) you will
have to run caff
with an additional argument: --keys-from-gnupg
to
get encourage caff
to get the keys from your main keyring.
The ~/.caffrc
is a perl snippet, which is mostly supposed to set values
in the $CONFIG
hash. This has the advantage that you can use the power
of perl in configuring things, even if it is not the most
readable configuration syntax.
The mandatory settings are:
$CONFIG{'owner'} = 'MY NAME';
$CONFIG{'email'} = 'MY PRIMARY EMAIL';
$CONFIG{'keyid'} = [ qw{KEYID KEYID ... } ];
where those KEYID
s are 16-character keyids rather than the 8-character
ones typically printed on keysigning sheets. It appears the only way
to find them is:
gpg --with-colons --list-key REGULAR_KEY_ID | awk -F : '/^pub/ { print $5; }
(ie, the 5th column in the colon separated output); they do not seem to appear anywhere else in the right format. (It is never explained why those longer keyids are used either, but I assume it is a combination of (a) being more sure of being unique and (b) being what that programmatically parsable output uses.)
The $CONFIG{'keyid'}
value is used to determine which signatures are
your ones and thus should be exported and sent to the recipient. If you
have more than one active key (eg, "work" and "personal") and you
want to create signatures with both, you will probably also want to set:
$CONFIG{'local-user'} = [ qw{KEYID KEYID ... } ];
and you may also want to ensure the emails sent out are encrypted so those keys can read them (if only to help with debugging), by setting:
$CONFIG{'also-encrypt-to'} = [ qw{KEYID KEYID ... } ];
(Those lists can all be the same, or local-user
can be any strict subset
of the keyid
list.)
If you already have the keys you want to sign and do not want caff
trying
to download them, then you will want to set:
$CONFIG{'no-download'} = 1;
(and then make sure you use caff --keys-from-gnupg
to pick up the keys
you already have).
Finally you may want to set:
$CONFIG{'mail'}= 'ask-no';
so that the default is not to email the signatures, at least while you
are testing (the built in default is ask-yes
-- ie, ask, but default
to sending email).
Sending email from caff
Sending email from caff
is problematic for several reasons, but
on modern desktops/laptops the most problematic is that the system
is probably not a full featured Internet mail server itself.
For sending email, caff
just punts to the perl mail modules
(especially
MIME::Entity
,
and
Mail::Mailer
), which would still like to assume that sending email is as simple
as running sendmail
on the system and magic happens. Alas this is
no longer the case for many systems.
Your options at this point are either (a) to set up at least a
minimal mail server on your laptop/desktop which has some way to
get out to a "real" mail server (a smarthost setup), or (b) try to
persuade Mail::Mailer
to do something more modern.
Modern MUA (Mail User Agent)
behaviour is to connect to an outgoing mail server on TCP/587 (Mail
Submission), or perhaps TCP/25 (SMTP) or TCP/465 (SMTPS), and then
do SMTP AUTH to gain permission to send mail out. For security
reasons SMTP AUTH is usually only permitted over a TLS (encrypted)
connection. So getting Mail::Mailer
to do something modern
requires using TLS and SMTP AUTH. It also requires being able to
verify the TLS certificate,
because TLS certificates are finally being verfied by common programs
(in the case of
IO::Socket::SSL
,
this is SSL_Verify_Mode
defaulting to on -- and
Net::SMTP
and
Net::SMTP::SSL
not having an option to override that -- nor any option to pass in CA-related
parameters :-().
Unfortunately Mail::Mailer
and friends do not support the Mail
Submission (TCP/587) port, nor STARTTLS, so some kludges are required.
It appears that something like
Net::SSLGlue::SMTP
might be a useful module for something trying to do better, as it
does support STARTTLS
-- but that does not seem to have been
adopted.
After much experimentation I found it is possible to make Mail::Mailer
behave close enough to a modern MUA by:
Ensuring that the SMTPS service is enabled on the mail server (listening on TCP/465, in always-TLS mode, rather than needing
STARTTLS
)Using the
smtps
mail type, so that an encrypted connection is established and thus SMTP AUTH is offered.Setting the
SSL_CERT_FILE
environment variable to point at the CA certificate used by the mail server, if it is not a widely recognised CA (eg, an organisation-wide CA); conveniently this can be done in~/.caffrc
due to it being a perl fragment, with:# Override SSL certificate location $ENV{SSL_CERT_FILE} = $CONFIG{'caffhome'} . '/naos.co.nz-ca.crt';
Passing authentication username/password arguments to
Mail::Mailer
with a username and password:Auth, ['AUTHUSER', 'AUTHPASS' ],
(unfortunately this requires hard coding the password in the config file; but being a perl fragment you could possibly use a module to, eg, get it from the
~/.netrc
file or a password store)The
From
option is probably also useful, as the auto-guessed from address is unlikely to be widely recognised:From, $CONFIG{'email'},
and you probably want to enable debugging until it is all sorted out with:
Debug, 1
The full config snippet looks like:
$CONFIG{'mailer-send'} = [ 'smtps', Server, 'MAILRELAY',
From, $CONFIG{'email'},
Auth, ['AUTHUSER', 'AUTHPASS' ],
Debug, 1 ];
# Override SSL certificate location
$ENV{SSL_CERT_FILE} = $CONFIG{'caffhome'} . '/naos.co.nz-ca.crt';
which should work as it is treated as an array where the first
argument is the type, and the remainder are key/value pairs (typically
one would use the equivalent syntax sugar of the equals-greater-than
arrow between the key and value for readability -- but the characters
make the blog/RSS feeds sad). See the caff
manpage for examples.
You may also want to set:
$CONFIG{'bcc'} = $CONFIG{'email'}; (or similar)
But (a) the email are saved in
~/.caff/keys/DATE
anyway, and (b) something in theMail::Header
is causing it to end upBCC
whichMail::Mailer
does not recognise as a destination address since it looks forBcc
as a literal key value, so the Bcc does not happen without patchesAs a work around one can edit the
Mail::Mailer
source inwho_to()
, and add a "BCC" variant of the "Bcc" entry, and that causes the Bcc address to be recognised... but theBCC
header is still in the email so it is not a very blind CC...Since the messages are already being saved in a file, and the resulting messages are not actually very readable (see below), there is probably limited benefit in the Bcc option, except when trying to validate that email sending is actually working.
$CONFIG{'mail-template'} = ... (but due to the way that
caff
constructs the email, creating a multi-part MIME message with that content as one part, the signed key as another part, and then encrypting it all, the text is unlikely to be readable to anyone who does not already know what to do -- so using the built in default template is probably as good as anything).
I am not particularly fond of the email formatting (in particular
it causes Thunderbird just to display a message indicating that you
have to install the OpenPGP plugin), but it is "standard" so at
least your emails will be no worse than anyone else's... and not
as messy as, eg, choosing to PGP encrypt the key, and then clearsign
the message (resulting in the PGP encrypted message being mangled
such that you have to do gpg --decyrpt
twice in a pipeline to be
able to unpack the signed key :-( ).
Usage
Having done all of that, in theory you are now ready to actually sign some keys. With your keysigning party check sheet close at hand, and your environment and mail server setup ready to go, run:
caff --keys-from-gnupg -R KEYID
where KEYID
is a key you would like to sign, that you already have
(or can be downloaded if you did not configure caff
to skip downloading).
It will run gpg
repeatedly on your behalf to check the key, and
do the keysigning steps -- either sign everything, or let you pick
key uids to sign. If you have more than one key in local-user
,
then it will run gpg
once to sign with each key. You will need
your key unlocked for each time you want to sign something, but by
using gpg-agent
the number of actual prompts will be limited as the
unlock credentials will be cached for a short period. After signing
with each key, enter "save" at the gpg
prompt to save the signatures
and move on to the next step.
After the signing stage is complete, unless mail
is set to no
it will
look for "recent" (defaults to "today") signatures, and either
automatically send them or offer to send them. If you choose to
send them, it will extract your signatures (ie signatures by one
of the keys listed in $CONFIG{keyid}
) and send them individually
to each email address (with the idea that the recipient only gets
the benefit of the keys for addresses where they have working email
reception).
Once you get it working caff
makes it reasonably quick to process the
key checklist from a keysigning party. Unfortunately its implementation
is pretty fragile, and it makes a lot of undocumented assumptions (and
goes out of its way to hide the error messages so it is not obvious when
its assumptions are not being met). Hopefully the above will help others
avoid tripping over some of those unmet assumptions.
All of which is a reminder that GPG is "damn near unusable" :-(
Update, 2015-01-29: After some more debugging it turns out the
GPG_TTY
-not-set issue was
self-inflicted,
ironically by a wrapper script I wrote years ago that attempted
to auto-set GPG_TTY
from stdin or stderr -- and failed with the error
I was seeing if stdin and stderr were redirected and GPG_TTY
was
not set. So that Macports bug got (justifiably) marked invalid. For
now I've moved the wrapper aside. But I think with the benefit of
hindsight the wrapper should probably have run the gpg
command
anyway, even if it could not set GPG_TTY
sensibly, since that
at least had some chance of working (lots of gpg
actions do not
actually need the GPG_TTY
set -- just those that maniuplate the
private keys) whereas exiting with an error when GPG_TTY
was
not set is always going to hard fail. Thus exchanging one obscure
error for another obscure error. (And in the meantime it sounds
like gpg
itself has gone out of its way to make the errors less
obscure in the GPG_TTY
-needed-but-not-set case.)