#!/usr/bin/env perl # Author: Rob Park # License: GNU General Public License # $Log: gpg-ringmgr,v $ # Revision 1.12 2003/02/02 02:46:38 feztaa # Ownertrust data is no longer lost when a key is moved. # # Revision 1.11 2003/02/01 11:00:03 feztaa # Fixed secret key handling! You can now safely use this script # to manage your secret keys, as well as public keys. # # Revision 1.10 2003/01/31 23:35:02 feztaa # When moving secret keys, gpg now no longer asks for confirmation. # There seems to be some brokenness with moving secret keys, though, # so maybe avoid it if possible, for now. # # Revision 1.9 2003/01/31 00:49:04 feztaa # Touched up the documentation again. # # Revision 1.8 2003/01/31 00:40:21 feztaa # Improved the quality of the included documentation. # # Revision 1.7 2003/01/31 00:18:42 feztaa # You can now move keys from a keyring that isn't defined in your # GPG options file. use strict; use Getopt::Long; use Pod::Usage; # Help the user if they haven't told me to do anything pod2usage(1) unless (@ARGV); # Parse the commandline arguments. The values of this hash are the defaults if # the option isn't specified; you might want to edit them. my %args = ( "help" => undef, "man" => undef, "debug" => undef, "from-ring" => "misc", "to-ring" => undef, "secret-keys" => undef, "no-opts-args" => "--options /dev/null --no-greeting --no-secmem-warning --show-keyring --no-default-keyring", "key-id" => [ ], "<>" => \&addkeys ); GetOptions(\%args, 'help|?', 'man', 'debug', 'from-ring=s', 'to-ring=s', 'secret-keys', 'key-id=s', '<>') or pod2usage(2); # Give the user help if they asked for help pod2usage(1) if $args{help}; # Give the user a manpage if they asked for that pod2usage(-exitstatus => 0, -verbose => 2) if $args{man}; # The following line of code allows you to specify this: # --key-id 12345678,23456789,34567890 # Instead of just this: # --key-id 12345678 --key-id 23456789 --key-id 34567890 @{$args{"key-id"}} = split(",", join(",", @{$args{"key-id"}})); # If the from and to rings are the same, the key just gets deleted. This won't let it happen. die "Can't move a key to the ring it's already on!" if ($args{'from-ring'} eq $args{'to-ring'}); # We probably don't want to send the keys to nowhere, so die if to-ring isn't set. die "Can't send keys into oblivion!" unless ($args{'to-ring'}); # Enclose the from and to rings with "ring." and ".gpg". Don't forget that # 'pub' or 'sec' will also be prepended to create the actual filename for the # keyring when it is used. $args{"from-ring"} =~ s/^(.*)$/ring.$1.gpg/; $args{"to-ring"} =~ s/^(.*)$/ring.$1.gpg/; # If the keyring to copy from doesn't exist, it can't have any keys on it, so we should # probably just give up... die "Public keyring to move keys from doesn't exist!" unless (-f $ENV{HOME} . "/.gnupg/pub$args{'from-ring'}"); if ($args{'secret-keys'}) { die "Secret keyring to move keys from doesn't exist!" unless (-f $ENV{HOME} . "/.gnupg/sec$args{'from-ring'}"); } # Forgot to prepent the key ID with '--key-id'? No problem! sub addkeys { push @{$args{'key-id'}}, $_[-1]; } # Sanitize the keys given for (@{$args{"key-id"}}) { s#.*/##; s#0x##; } # Remove invalid keys from the list of keys to move @{$args{"key-id"}} = grep { ! system("gpg --keyring pub$args{'from-ring'} --list-keys $_ >/dev/null 2>&1") } @{$args{"key-id"}}; # If there were no key ids specified, let the user know what went wrong. die "No valid keys specified!" unless (@{$args{'key-id'}}); # Print some info to aid debugging... if ($args{debug}) { require Data::Dumper; local $Data::Dumper::Indent = 1; local $Data::Dumper::Deepcopy = 1; local $Data::Dumper::Sortkeys = 1; print "Debug info:\n\n", Data::Dumper->Dump([\%args], [qw(*args)]); } # If the keyring that we're moving keys to doesn't exist, create it. if (!grep { m/(pub|sec)$args{'to-ring'}$/ } glob("~/.gnupg/*")) { print "Destination keyring not found, creating!\n"; print "You'll have to edit gpg's config file and add this keyring to it if\n"; print "you want to be able to use it easily in the future.\n\n"; for (qw(pub sec)) { system("touch ~/.gnupg/$_$args{'to-ring'}"); } } # Ownertrust data is lost when a key is moved, this preserves it. my $trust = `gpg --export-ownertrust`; # Work our magic on all of the keys specified. for (@{$args{"key-id"}}) { my ($delsec, $delpub); if ($args{"secret-keys"}) { print "Moving secret key $_ from sec$args{'from-ring'} to sec$args{'to-ring'} ... \n\n"; # Export the secret key, and import it on the destination keyring. system("gpg --secret-keyring sec$args{'from-ring'} --export-secret-key $_ | gpg $args{'no-opts-args'} --secret-keyring sec$args{'to-ring'} --quiet --import"); $delsec = ! $?; # If $? is true, something went wrong. If $delsec is true, everything is ok. } print "Moving public key $_ from pub$args{'from-ring'} to pub$args{'to-ring'} ... \n\n"; # Export the public key, and import it on the destination keyring. system("gpg --keyring pub$args{'from-ring'} --export $_ | gpg $args{'no-opts-args'} --keyring pub$args{'to-ring'} --quiet --import"); $delpub = ! $?; # If $? is true, something went wrong. If $delpub is true, everything is ok. if ($args{"secret-keys"}) { # We need the fingerprint to delete the secret key without prompting the user my $print = `gpg --fingerprint $_`; $print =~ m/Key fingerprint = ([0-9A-F ]{50})/; $print = $1; $print =~ s/\s//g; chomp $print; # If everything went ok, delete the secret key from it's source $delsec && system("gpg $args{'no-opts-args'} --secret-keyring sec$args{'from-ring'} --batch --yes --delete-secret-key $print"); # Verify that the move worked by showing which keyring the secret key is now on. print "Secret key $_ is now on the following keyring:\n\n"; system("gpg --show-keyring --secret-keyring sec$args{'to-ring'} --list-secret-keys $_"); } # If everything went ok, delete the public key from it's source $delpub && system("gpg $args{'no-opts-args'} --keyring pub$args{'from-ring'} --batch --yes --delete-key $_"); # Verify that the move worked by showing which keyring the public key is now on. print "Public key $_ is now on the following keyring:\n\n"; system("gpg --show-keyring --keyring pub$args{'to-ring'} --list-keys $_"); } # Restore the ownertrust data open TRUST, "| gpg --import-ownertrust"; print TRUST $trust; close TRUST; __END__ =head1 NAME gpg-ringmgr - easily move gpg keys from one keyring to another =head1 SYNOPSIS gpg-ringmgr [options] -t -k [ ...] =head1 OPTIONS =over 8 =item B<-h, --help> Print some basic help. =item B<-m, --man> Be more helpful, with usage examples. =item B<-f, --from-ring> Which ring to move the key from. Your keyrings should be in the form of C, where C is defined by this option. Defaults to C. =item B<-t, --to-ring> Same as B<--from-ring>, except it defines where the key is moved I. The default value is undefined, which means that you I specify this argument! =item B<-s, --secret-keys> Causes script to also manipulate your secret keys, as well as public keys. Without this option, the script only touches public keys. =item B<-k, --key-id> A comma separated list of key IDs that you want to move from one ring to another. This option can be specified more than once. =item B<-n, --no-opts-args> This option defines the options that are passed to GPG in order to make it ignore the contents of it's options file. You probably shouldn't change this unless you know what you're doing. =item B<-d, --debug> uses the Data::Dumper module to display some debugging info. You probably shouldn't use this option. =back =head1 EXAMPLES =over 8 =item B This will move that key from the keyring at C<~/.gnupg/pubring.misc.gpg> to the keyring at C<~/.gnupg/pubring.business.gpg>. =item B Since C<--from-ring misc> is default behavior, this is exactly the same as the previous example. =item B This moves those three keys from the keyring at C<~/.gnupg/pubring.work.gpg> to the keyring at C<~/.gnupg/pubring.play.gpg>. =item B Demonstrating that the C<--key-id> argument itself is optional, even though the key ids themselves aren't. =item B Moves the public key with that ID from C<~/.gnupg/pubring.personal.gpg> to C<~/.gnupg/pubring.impersonal.gpg>, and the secret key with that ID from C<~/.gnupg/secring.personal.gpg> to C<~/.gnupg/secring.impersonal.gpg>. =back =head1 NOTES If you specify a keyring with B<--to-ring> that doesn't exist, it will be created for you automatically. =head1 VERSION $Id: gpg-ringmgr,v 1.12 2003/02/02 02:46:38 feztaa Exp $ =head1 AUTHOR Rob Park . Original bash version hacked together by David T-G . =head1 SEE ALSO gpg(1) =cut