#!/usr/bin/env perl
# Author: Rob Park <
[email protected]>
# 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 <GPG Keyring> -k <GPG Key ID> [<Key ID> ...]
=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<pubring.x.gpg>, where C<x> is defined by this option. Defaults to C<misc>.
=item B<-t, --to-ring>
Same as B<--from-ring>, except it defines where the key is moved I<to>. The
default value is undefined, which means that you I<must> 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<gpg-ringmgr --from-ring misc --to-ring business --key-id 12345678>
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<gpg-ringmgr --to-ring business --key-id 12345678>
Since C<--from-ring misc> is default behavior, this is exactly the same as the
previous example.
=item B<gpg-ringmgr -f work -t play -k 12345678,23456789,34567890>
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<gpg-ringmgr -t example 12345678 23456789 34567890>
Demonstrating that the C<--key-id> argument itself is optional, even though the
key ids themselves aren't.
=item B<gpg-ringmgr -f personal -t impersonal -s -k 12345678>
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 <
[email protected]>. Original bash version hacked together by David
T-G <
[email protected]>.
=head1 SEE ALSO
gpg(1)
=cut