Request for comments on GnuPG usage

Zygo Blaxell
Thu, 24 Feb 2000 07:15:14 GMT

Signed message created at Thu Feb 24 02:15:02 2000 by zblaxell@washu

Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain

I'm designing a variety of systems that use GnuPG-signed control messages.
These systems will mostly replace SSH-based interactive connections with
SMTP-based non-interactive ones.  The goal is to provide secure message
transport between machines when those machines do not necessarily have
reliable full-time internet connectivity with each other.

I have attached a prototype message authenticator at the bottom of
this message.  The authenticator reads a file on stdin which contains
an OpenPGP message, authenticates it as described below, then invokes
another process and passes the plaintext (or dearmored text) of the
message through a pipe.

Perl junkies can skip to the end and read the code directly.

My comments are below; please correct any assertion you believe to be
wrong.  There's also some notes about workarounds for various GnuPG

Here is what my prototype tries to do:

	0.  Assume that the prototype authenticator has a secret key in
	    "shell-authorization-ring" which it can use to decrypt messages,
	    and that it has access to a public key ring which contains at
	    least the set of public keys that will be accepted as authorized
	    messages.  The latter can be replaced by a public key server.
	    .gnupg/options is set up for whatever keyservers, RSA extensions,
	    and other glue may be required.

	1.  Interpret message on stdin using GnuPG.  Temporary files are
	    used to work around GnuPG pipe handling--I find that when I
	    use gpg with stdin and stdout as pipes, the data is always
	    corrupted.	Three temporary files are created:  the input,
	    the plaintext output of GnuPG (either decrypted or merely
	    dearmored), and the status-fd output.

	    A non-standard secret keyring is used because the secret key
	    must have no passphrase for this to work, and GnuPG will
	    spew annoying "Secret key <id> is not protected" warnings
	    _every_ time it is invoked if there are such keys in the
	    private keyring.  There seems to be no way to turn this off,
	    at least not in 1.0.1.

	2.  Examine the --status-fd output to find the fingerprint
	    of the public key used to generate a valid signature, and the
	    timestamp of that signature.=20=20

	3.  Check the fingerprint of the public key used in the signature
	    against a table of fingerprints of authorized signing
	    keys ($HOME/.mail-shell-authorized in the prototype).
	    Authorization fails if the fingerprint of the key signing
	    the message is not listed here.

	4.  Check the signature timestamp to see if this is a replay of
	    a previous message ($HOME/.mail-shell-seq in the prototype).
	    Authorization fails if this is the case.

	5.  If all has gone well this far, accept the output of gpg as
	    the authorized message, and act on it (feed it to a shell
	    in the prototype).

Here are some limitations or risks of the prototype that I know about:

	1.  I really feel uncomfortable about relying on GnuPG to know
	    what to do when it is fed arbitrary input without a command
	    such as "--decrypt" or "--verify".  However, I've generated
	    a number of test messages including secret and public keys,
	    and gpg doesn't do anything I wouldn't want it to do on any
	    test case I've tried.=20=20

	    Is 'gpg' without any command intended to always do the most
	    harmless thing appropriate?  Did I miss something?

	2.  --status-fd is cool.  ;-)=20=20

	    I don't use the TRUST_* results from GnuPG.  Whoever set
	    up the message authenticator must appropriately verify the
	    fingerprint of every public key that will be used to sign
	    authorized messages.=20=20

	    The "--always-trust" suppresses GnuPG's public key validity
	    checking, which otherwise causes failures in many cases
	    (GnuPG tries to prompt for a "really use this key", even in
	    the presence of --batch and --yes, which causes a failure
	    when there is no user to prompt).=20=20

	3.  It would be nice if we had exactly one authorized key
	    fingerprint, and any public key signed by that one true
	    authorized key (modulo revocations) was also authorized.
	    I'm not sure how to do this, but it would be cool.
	    I think I start with=20

		gpg --with-colons --with-fingerprint --fast-list-mode \
		    --list-sigs 0xKEY-ID-HERE

	    and do a tree search until I hit a key that I like, or
	    if I trust GnuPG's trustdb, just simply:

		gpg --with-colons --with-fingerprint --list-sigs 0xKEY-ID-HERE

	    and read the first two lines; the trust value is the second
	    field of the first line.  I assume that GnuPG prevents=20
	    user ID's with newlines in them, otherwise key fingerprints
	    could be spoofed.

	    OTOH, I like to KISS, and just use my own key-to-authority
	    mapping mechanism.	The key can be used as a user ID in a
	    system with multiple access modes, which makes validating
	    the key less useful since the external program that uses the
	    authenticator must know what privileges are associated with
	    all authorized signing key fingerprints anyway.

	    This prototype has a list of fingerprints that it will
	    accept, and simply rejects any GnuPG status-fd output unless
	    it contains a VALIDSIG line and one of the desired
	    fingerprints.  It's simple.  It's predictable.  It's easier
	    to explain than the trustdb model.

	4.  The prototype just checks that the timestamp is greater than
	    any previously received timestamp, which works well in
	    the case of only one authorized key holder.  This can be
	    trivially extended to store separate last received timestamps
	    from each authorized signature key.

	    This does have a significant drawback in that if messages
	    arrive out of order, the earlier messages will be discarded.
	    It also has a less significant drawback in that only one
	    message can be authenticated per second.  My hardware can't
	    generate more than one signed message per second per signing
	    key, so I don't consider this to be a problem.

	    Maintaining a database of "SIG_ID"'s could solve this, but
	    requires storing every SIG_ID ever received for the entire
	    lifetime of the signing key.=20=20

	    SIG_ID combined with timestamp could be used to put an upper
	    bound on the storage space used for SIG_ID's.  Perhaps the
	    reply test should be "SIG_ID not yet received and signature
	    timestamp within last N days" with a policy of expiring
	    SIG_ID's in the receiver database after 2N days.

	5.  Hopefully nothing weird happens with clearsigned text
	    messages, such as a change in line ending format.=20=20

I'm assuming that all of the following vulnerabilities are non-problems:

	- denial of service:  it takes several seconds to process each
	  incoming message on the P133-class systems I'll be using, so
	  it would be easy to feed megabytes of junk data in 200-byte
	  messages to the system and kill it.

	- attacks outside of the authentication system:  Tempest
	  monitoring, cracking root on the same machine, stealing the
	  hardware, storage of decrypted messages, etc.

	- evil authorized message signers:  of course, if there are
	  vulnerabilities in the receiver of authenticated messages,
	  (e.g. the message receiver is the stdin of an unrestricted
	  Bourne shell with read/write access to the .gnupg directory
	  ;-), these vulnerabilities could be exploited by authorized
	  message signers.  My concern ends when I am assured that
	  those vulnerabilities cannot be exploited by _un_authorized
	  message signers.

	- there is no management facility:  it would be nice, but
	  not essential, if it could handle importing keys and revocation
	  certificates, especially if combined with the "all authorized
	  keys are signed by the one true key" authentication mechanism;
	  however, for small-scale operations, it's easier to maintain
	  a list of fingerprints outside of GnuPG.

GnuPG prevents forgery, modification, and (if encryption is used)
disclosure of messages.  The signature database in the authenticator
prevents attacks based on replaying previous valid messages.
The fingerprint database in the authenticator provides trust and
authorization data.=20=20

That's all the attacks I can think of.

Am I missing anything?

Prototype perl script follows:

#!/usr/bin/perl -w
use strict;

print STDERR '$Id: mail-shell,v 1.1 2000/02/21 14:42:20 cvs Exp $', "\n";

# Set up work area

my $tmp =3D "/tmp/ms-$$";
my $pid =3D $$;
mkdir($tmp, 0700) or die "mkdir: $tmp: $!";

# Destroy work area when we're done with it.

my $badness =3D 1;

	$pid =3D=3D $$ && system('rm', '-rf', $tmp);

	# Perl has strange ideas about what the exit status of the
	# process should be if there are END blocks.
	# This makes perl think the code is buggy, which is good enough
	# for propagating a non-zero exit status to the caller,
	# but generates an annoying warning message.


# Capture incoming message data.

system("cat > $tmp/in") and die "cat: exit status $?";

# Analyze data with gpg.

my $rv =3D system("gpg \\
		 --batch \\
		 --yes \\
		 --secret-keyring shell-authorization-ring \\
		 --status-fd 3 \\
		 --output $tmp/out \\
		 $tmp/in 3> $tmp/status");

# If gpg doesn't like it, then we don't either.

if ($rv) {
	die "Message decryption/verify failed.";

# OK, so GPG has analyzed it.  Now we look at the results of that analysis.

open(STATUS, "<$tmp/status") or die "No status information from gpg:  $!";

my ($signature, $sig_date, $sig_time);

while (<STATUS>) {
	($signature, $sig_date, $sig_time) =3D ($1, $2, $3) if m/^\[GNUPG:\] VALID=
SIG ([A-Fa-f\d]+) (\d\d\d\d-\d\d-\d\d) (\d+)\r?\n$/os;

close(STATUS) or die "Error reading status information from ggp:  $!";

die "No valid signature detected in gpg status information" unless defined(=

print STDERR "Found signature data:  $signature, $sig_date, $sig_time\n";

# OK, so GPG has verified a signature.  Now we check to see if we
# trust the owner of the signature's key.

open(AUTHORIZED_KEYS, "<$ENV{HOME}/.mail-shell-authorized") or die "No auth=
orized key fingerprints list: $!";
my $authorized =3D 0;
	next if /^\s*#/os;
	s/[^A-Fa-f0-9]+/ /gos;
	s/.* //gos;
	next unless length($_) >=3D 32;
	$authorized =3D 1 if $_ eq $signature;
close(AUTHORIZED_KEYS) or die "Error reading authorized keys file: $!";

die "Signature valid, but not authorized" unless $authorized;

# At this point we are sure that the message is signed, and we like the
# person whose key it was signed with.

# Trivial protection against repeated message attacks.
# Yes, this does mean you can send at most one message per second,
# and they must appear in order.  Darn.

open(SEQUENCE, "<$ENV{HOME}/.mail-shell-seq")=20
	or die "You must set the message sequence number, e.g. by 'echo 1 > \$HOME=

my $sequence =3D <SEQUENCE>;
$sequence +=3D 0;

die "Zero or negative sequence number is probably an error and therefore no=
t allowed."=20
	unless $sequence > 0;


die "Message sequence number repeated (got $sig_time, last message was $seq=
	if $sig_time <=3D $sequence;

open(SEQUENCE, ">$ENV{HOME}/.mail-shell-seq.tmp.$$") or die "open new seque=
nce number: $!";
print SEQUENCE $sig_time or die "write new sequence number: $!";
close(SEQUENCE) or die "close new sequence number: $!";
rename("$ENV{HOME}/.mail-shell-seq.tmp.$$", "$ENV{HOME}/.mail-shell-seq") o=
r die "rename: new to old sequence number: $!";

# OK, now we are sure that this is an original message, not a repeat.

system('sh', '-x', "$tmp/out") and die "Command returned exit status $?";
$badness =3D 0;

OpenPGP email preferred at <>.
OpenPGP key available on and other fine keyservers.
OpenPGP fingerprint:  2B32 546D 21A5 0DB2 20C8  AF10 1D4A 610E 6972 2DEE=20

Content-Type: application/pgp-signature
Content-Disposition: inline

Version: GnuPG v1.0.1 (GNU/Linux)
Comment: For info see