[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

gpg changesets (was Re: Bits from the DPL: DSA and buildds and DAM, oh my!)



Anthony Towns wrote:
> That's a technical issue, however -- one that seems like it should be
> emminently solvable. Ensuring that any such solution is written in a way
> that encourages auditability (of the code, of the input and of the output)
> is an important part though, and I don't know that anyone even has a good
> understanding of how that would be achieved. That probably means someone
> needs to try to implement it, and then we can see what doesn't work.

Perhaps something like this:

* Each change to the gpg keyring is stored in a separate changeset file.
  Changesets can reflect any set of changes to the keyring. Changesets
  can also include arbitrary metadata.
* Changesets are never removed or modified, only new ones added.
* There's an ordering of the changesets. This ordering is stored in an
  index file.
* The index file is only appended to, to add new changesets.
* Changesets can be generated using some simple tool. This might be
  $EDITOR, or it might compare two keyrings.
* Changesets can be fully examined to see what change they make before
  applying them.
* Changesets can be applied by a tool that drives gnupg to make the
  changes.
* To accept a changeset, keyring-maint gpg signs it, adds it to the index
  file, and gpg signs that.
* keyring-maint can verify at any time that all changesets in a
  directory are gpg signed by trusted parties (ie, himself, or the previous
  keyring-maint(s)), and that the index file is signed by a trusted
  party.
* A simple tool can build the keyring. It has two modes, for full rebuild
  and incremental build:
  1. Start with an empty keyring, and apply each changeset in turn in the 
     order given in the index file. Record the last applied changeset.
  2. Start with a populated keyring, apply each changeset after the
     last applied one, and record the last applied changeset.

What format to use for the changesets is an interesting question.
Perhaps something like this, which is an example changeset to modify my
current key, and add a new key:


Changed-By: Joey Hess <joeyh@debian.org>
Comment: Removing an old email address.
Action: edit-key 788A3F4C
Data:
  uid 3
  deluid
  y
  save

Changed-By: Joey Hess <joeyh@debian.org>
Comment: Joey also wants to have two keys in the keyring, here's the new one.
Action: import
Data:
  -----BEGIN PGP PUBLIC KEY BLOCK-----
  Version: GnuPG v1.4.6 (GNU/Linux)
  
  mQGiBEXfns0RBACaKfCKEA7O+K10XSkxmVj/CxQovoHoQlplw/2PJdvfvPRXtQVB
  P6RYl7dShpZ1hT9kbcxuTbriGPjA10WP0kbg36TJ4xbddk4oXJVjpVt51AaP9Ac3
  bdWd2LeCnizUyazUWoZl+ulzenFod1J8drwPs6MGvKUMZEYMKprhgnaQxwCg4AcS
  mpUu3iA/3kDs9GGB7HUPH5sEAJR0N5GH+Kk6D36fsV13KtijYVK9sAygKXfcCW13
  nXvssNNcrR9aBCel7ZkPtIGM4tW6kNXJXUyzIfFmKHVKYbSjSeKSMAiaDNfIOwBi
  XA9dgFcPCVQ/Dz+zAs+qo/imnqBohrFOMsBgcP52lZzCYBv7GjGNk9dPjYEyrqul
  olYAA/4kJj+LHmBMCyrOFvTOxuiMbazPAbxOQtyGS4W9Kq0/zNZUIjXd0mG68w0z
  bTbBk2xmlll+4HSc/wbSdsm/ehbSreyp7q8nzq0fK1AQ7t/e2MPunt+ayanB9KXl
  08aI5zx93LDatAd1RIYzc1u/R+tk3ks8ViC3+sdWfo1lOkhlcrQYc29zb3NvcyA8
  c3Nvc29Aa2l0aXRpdHQ+iGAEExECACAFAkXfns0CGwMGCwkIBwMCBBUCCAMEFgID
  AQIeAQIXgAAKCRD/fMTh/I6hNFi/AJ9Oc2ZxjnQhPJ4lANYQEMqaVa/8ogCdFiXm
  zibrof8Wb+Sn8wXgg0XnYQa5Ag0ERd+e8RAIAJQd/dve6IqN0UnCeTd85IFzMmMG
  9ExrZx/Nm4MczYcQRC9KsiBus1UFY6XImq1Z9F13B8pr0qE3YaBBA7c979fy9qDf
  bJ9D1jixhAU1vLasH9pTkwBN2Hvd5/Nm1wlMv06Kzsceb3EmgCHF716dmlYzia+0
  v9cVdpZrc6G3jfDSvLGmNbmIHmXJM5Mia+k7SzJdKSjjcun94YzeIiM4iRGj3S88
  3wtll9Mr08lexYuu3xC7SK2bM0PZaC2E3qshLhXLmjaoS9m9zinhg43g8WCXE0c6
  X/ePf9dxIgxaF/TkReOpgZ+W6okkyyDVjGdC8ldoyKBpe20YV9x96V+77qsAAwUH
  /j1MC9Qp44y/KYthuoUxTffTeGdAkSsCsoGPiilLRE5HB3KiWhrr9oPq/6hvM+Bk
  92mCdh1FVsIOmLkSrQ54y2kA3Iu8MdvHveu6nrhQIHPY+6AJVvkN/zL8h1H/+igu
  sCqQyyXph+cXRK8ZMwke+fkp7TqOMtXyjCn9i5RekI/S9xfbWW2zFKc9QXsyyfc3
  bm2pTtaNQEkpgfBFSNrrHmgTH8ES/lOoinnfKrVz8me9iO/JK/hh6I7XigIsGbtH
  hLmwzyOD9IvD+QEDSmPWOTO0K3efrrSBS/aFtAYlKtx5creJgdcfobpD0Ugvlykq
  y2mr6UrU3ucQJ3/hdVlqR+eISQQYEQIACQUCRd+e8QIbDAAKCRD/fMTh/I6hNL/M
  AKC+/JPLSXz2wauck429Zv5pVN7geACfZLznlUiaJuGuMMLUs+T4bDej03s=
  =TNPZ
  -----END PGP PUBLIC KEY BLOCK-----

Changed-By: Joey Hess <joeyh@debian.org>
Comment: On second thought, that new key is no good
Action: delete-key FC8EA134
Data: y


Note that this is a relative changeset: its action depends on the
keyring it's run on, since it deletes uid 3 of 788A3F4C. Which points to
the need for the review tool. Luckily, it's really easy[1] to write
(attached as changeset-review). (A simple modification of that also gives
the changeset apply tool.) Here's its output for the changeset above:


joey@kodama:~>cp input.gpg TESTRING.gpg
joey@kodama:~>./changeset-review ~/TESTRING.gpg c
gpg --edit-key 788A3F4C
>> uid 3
gpg (GnuPG) 1.4.6; Copyright (C) 2006 Free Software Foundation, Inc.
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions. See the file COPYING for details.


gpg: please do a --check-trustdb
pub  1024D/788A3F4C  created: 1999-09-08  expires: never       usage: SC  
                     trust: unknown       validity: full
sub  2048g/1950ED18  created: 1999-09-08  expires: never       usage: E   
[  full  ] (1). Joey Hess <joeyh@debian.org>
[  full  ] (2)  Joey Hess <joey@kitenet.net>
[  full  ] (3)  Joey Hess <joeyh@master.debian.org>
[  full  ] (4)  Joey Hess <joey@mooix.net>
Please note that the shown key validity is not necessarily correct
unless you restart the program.


pub  1024D/788A3F4C  created: 1999-09-08  expires: never       usage: SC  
                     trust: unknown       validity: full
sub  2048g/1950ED18  created: 1999-09-08  expires: never       usage: E   
[  full  ] (1). Joey Hess <joeyh@debian.org>
[  full  ] (2)  Joey Hess <joey@kitenet.net>
[  full  ] (3)* Joey Hess <joeyh@master.debian.org>
[  full  ] (4)  Joey Hess <joey@mooix.net>

>> deluid
>> y

pub  1024D/788A3F4C  created: 1999-09-08  expires: never       usage: SC  
                     trust: unknown       validity: full
sub  2048g/1950ED18  created: 1999-09-08  expires: never       usage: E   
[  full  ] (1). Joey Hess <joeyh@debian.org>
[  full  ] (2)  Joey Hess <joey@kitenet.net>
[  full  ] (3)  Joey Hess <joey@mooix.net>

>> save
gpg operation complete

gpg --import 
gpg: key FC8EA134: public key "sososos <ssoso@kitititt>" imported
gpg: Total number processed: 1
gpg:               imported: 1
gpg operation complete

gpg --delete-key FC8EA134
>> y
gpg (GnuPG) 1.4.6; Copyright (C) 2006 Free Software Foundation, Inc.
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions. See the file COPYING for details.


pub  1024D/FC8EA134 2007-02-24 sososos <ssoso@kitititt>

gpg operation complete

joey@kodama:~>cmp input.gpg TESTRING.gpg 
joey@kodama:~>


One easy way to create the changesets is to have some templates for
common operations such as adding a key, removing a key, changing a key,
etc, and just fill in the details. A program that watches what is done
in gpg and generates a changeset would be a neat alternative.

If this seems approximately right I will put a bit more time into it,
to get an actual usable demo system based on the full keyring data.

-- 
see shy jo

[1] Well, ok, it actually took me 3 hours..
#!/usr/bin/perl
# Given a keyring and a changeset, shows what gpg would do if it
# applied the changeset to the keyring. The keyring is not modified.
use warnings;
use strict;
use File::Temp;

my @allowed_actions=qw(import edit-key delete-key);
my @gpgopts=qw(--command-fd 0 --no-auto-check-trustdb --no-default-keyring);

my $keyring=shift || usage();
my $changeset=shift || usage();

my $testring=$keyring.".tmp.$$";
if (-e $testring) {
	die "$testring exists";
}
system("cp", $keyring, $testring) == 0 || die "copy failed";

push @gpgopts, "--keyring", $testring;

my %fields;
my $field;
open(CHANGESET, "<", $changeset) || die "$changeset: $!";
while (<CHANGESET>) {
	chomp;
	if (/^([^\s]+):(?:\s+(.*))?/) {
		$field=lc $1;
		if (defined $2) {
			$fields{$field}=$2;
		}
		else {
			$fields{$field}='';
		}
	}
	elsif (/^\s+\.$/ && defined $field) {
		$fields{$field}.="\n";
	}
	elsif (/^\s+(.*)/ && defined $field) {
		$fields{$field}.="\n" if length $fields{$field};
		$fields{$field}.=$1;
	}
	elsif ($_ eq "") {
		process() if defined $field;
		%fields=();
	}
	else {
		die "parse error on line $. of $changeset";
	}
}
close CHANGESET;
process() if defined $field;

sub process {
	if (! exists $fields{action}) {
		die "$changeset missing action field";
	}
	my @action=split(' ', $fields{action});
	my $command=shift @action;
	if (! grep { $_ eq $command } @allowed_actions) {
		die "$changeset contains disallowed action \"$command\"";
	}
	if (! exists $fields{data}) {
		die "$changeset missing data field";
	}

	my $pid = open(GPG, "|-");
	$SIG{PIPE} = sub { die "whoops, pipe broke" };
	if (! $pid) {
		print "gpg --$command @action\n";
		exec("gpg", @gpgopts, "--$command", @action) ||
			die("failed to run gpg");
	}
	$|=1;
	GPG->autoflush(1);
	foreach my $line (split("\n", $fields{data})) {
		print ">> $line\n" if $command ne 'import';
		print GPG "$line\n" || die "failed talking to gpg";
		sleep 1 if $command ne 'import';  # makes output clearer
	}
	close GPG || die "gpg exited nonzero";
	print "gpg operation complete\n\n";
}

unlink $testring;
unlink $testring."~";# gpg backup file

sub usage {
	die "Usage: changeset-review keyring changeset\n"; 
}

Attachment: signature.asc
Description: Digital signature


Reply to: