[PATCH] Allow for multiple space-separated key IDs for encryption

Alex Vandiver alexmv at bestpractical.com
Wed Apr 28 19:54:23 UTC 2010


---

I find this less clean in some ways, as it means duplicate
GPG-Recipient: lines in our metaheader, which we read into a hash,
losing information -- information which we happily don't actually
need, because we never actually use the header's values, just its
presence or absence.

It also means we're still serializing the list of keys by joining on
spaces, just only in the inventory key now.

 lib/Brackup/Backup.pm           |   21 ++++++++++++---------
 lib/Brackup/Manual/Overview.pod |    4 ++++
 lib/Brackup/PositionedChunk.pm  |    4 ++--
 lib/Brackup/Root.pm             |   18 +++++++++++-------
 lib/Brackup/StoredChunk.pm      |    2 +-
 5 files changed, 30 insertions(+), 19 deletions(-)

diff --git a/lib/Brackup/Backup.pm b/lib/Brackup/Backup.pm
index 35bf579..a8aef64 100644
--- a/lib/Brackup/Backup.pm
+++ b/lib/Brackup/Backup.pm
@@ -40,7 +40,7 @@ sub backup {
 
     my $stats  = Brackup::BackupStats->new;
 
-    my $gpg_rcpt = $self->{root}->gpg_rcpt;
+    my @gpg_rcpts = $self->{root}->gpg_rcpts;
 
     my $n_kb         = 0.0; # num:  kb of all files in root
     my $n_files      = 0;   # int:  # of files in root
@@ -96,7 +96,7 @@ sub backup {
 
     my $gpg_iter;
     my $gpg_pm;   # gpg ProcessManager
-    if ($gpg_rcpt) {
+    if (@gpg_rcpts) {
         ($chunk_iterator, $gpg_iter) = $chunk_iterator->mux_into(2);
         $gpg_pm = Brackup::GPGProcManager->new($gpg_iter, $target);
     }
@@ -108,7 +108,7 @@ sub backup {
                                              '.' . basename($backup_file) . 'XXXXX',
                                              DIR => dirname($backup_file),
         );
-        if (! $gpg_rcpt) {
+        unless (@gpg_rcpts) {
             if (eval { require IO::Compress::Gzip }) {
                 close $metafh;
                 $metafh = IO::Compress::Gzip->new($meta_filename)
@@ -212,7 +212,7 @@ sub backup {
             $schunk = Brackup::StoredChunk->new($pchunk);
 
             # encrypt it
-            if ($gpg_rcpt) {
+            if (@gpg_rcpts) {
                 $schunk->set_encrypted_chunkref($gpg_pm->enc_chunkref_of($pchunk));
             }
 
@@ -288,11 +288,16 @@ sub backup {
         my $is_encrypted = 0;
 
         # store the metafile, encrypted, on the target
-        if ($gpg_rcpt) {
+        if (@gpg_rcpts) {
             my $encfile = $backup_file . ".enc";
+            my @recipients = map {("--recipient", $_)} @gpg_rcpts;
             system($self->{root}->gpg_path, $self->{root}->gpg_args,
+                   @recipients,
                    "--trust-model=always",
-                   "--recipient", $gpg_rcpt, "--encrypt", "--output=$encfile", "--yes", $backup_file)
+                   "--encrypt",
+                   "--output=$encfile",
+                   "--yes",
+                   $backup_file)
                 and die "Failed to run gpg while encryping metafile: $!\n";
             open ($store_fh, $encfile) or die "Failed to open encrypted metafile '$encfile': $!\n";
             $store_filename = $encfile;
@@ -361,9 +366,7 @@ sub backup_header {
     $ret .= "TargetName: " . $self->{target}->name . "\n";
     $ret .= "DefaultFileMode: " . $self->default_file_mode . "\n";
     $ret .= "DefaultDirMode: " . $self->default_directory_mode . "\n";
-    if (my $rcpt = $self->{root}->gpg_rcpt) {
-        $ret .= "GPG-Recipient: $rcpt\n";
-    }
+    $ret .= "GPG-Recipient: $_\n" for $self->{root}->gpg_rcpts;
     $ret .= "\n";
     return $ret;
 }
diff --git a/lib/Brackup/Manual/Overview.pod b/lib/Brackup/Manual/Overview.pod
index ad51941..3cb2f32 100644
--- a/lib/Brackup/Manual/Overview.pod
+++ b/lib/Brackup/Manual/Overview.pod
@@ -114,6 +114,10 @@ on paper.  (Worst case you can type it back in, or use OCR.)  Export with:
 
   $ gpg --export-secret-keys --armor
 
+You can encrypt to multiple keys by providing multiple
+C<gpg_recipient> lines; any of the keys provided will be able to
+decrypt the backups.
+
 =head1 Restores
 
 To do a restore, you'll need your *.brackup file handy.  If you lost
diff --git a/lib/Brackup/PositionedChunk.pm b/lib/Brackup/PositionedChunk.pm
index f99e5ab..f6a7603 100644
--- a/lib/Brackup/PositionedChunk.pm
+++ b/lib/Brackup/PositionedChunk.pm
@@ -111,8 +111,8 @@ sub raw_chunkref {
 sub inventory_key {
     my $self = shift;
     my $key = $self->raw_digest;
-    if (my $rcpt = $self->root->gpg_rcpt) {
-        $key .= ";to=$rcpt";
+    if (my @rcpts = $self->root->gpg_rcpts) {
+        $key .= ";to=@rcpts";
     } else {
         $key .= ";raw";
     }
diff --git a/lib/Brackup/Root.pm b/lib/Brackup/Root.pm
index edcc4cd..7456f4d 100644
--- a/lib/Brackup/Root.pm
+++ b/lib/Brackup/Root.pm
@@ -18,7 +18,7 @@ sub new {
 
     $self->{dir}        = $conf->path_value('path');
     $self->{gpg_path}   = $conf->value('gpg_path') || "gpg";
-    $self->{gpg_rcpt}   = $conf->value('gpg_recipient');
+    $self->{gpg_rcpts}  = [ $conf->values('gpg_recipient') ];
     $self->{chunk_size} = $conf->byte_value('chunk_size');
     $self->{ignore}     = [];
 
@@ -53,9 +53,9 @@ sub gpg_args {
     return @{ $self->{gpg_args} };
 }
 
-sub gpg_rcpt {
+sub gpg_rcpts {
     my $self = shift;
-    return $self->{gpg_rcpt};
+    return @{ $self->{gpg_rcpts} };
 }
 
 # returns Brackup::DigestCache object
@@ -119,7 +119,7 @@ sub foreach_file {
                 # GC: seems to work fine as of at least gpg 1.4.5, so commenting out
                 # gpg seems to barf on files ending in whitespace, blowing
                 # stuff up, so we just skip them instead...
-                #if ($self->gpg_rcpt && $path =~ /\s+$/) {
+                #if ($self->gpg_rcpts && $path =~ /\s+$/) {
                 #    warn "Skipping file ending in whitespace: <$path>\n";
                 #    next;
                 #}
@@ -203,15 +203,16 @@ sub du_stats {
 # given filehandle to data, returns encrypted data
 sub encrypt {
     my ($self, $data_fh, $outfn) = @_;
-    my $gpg_rcpt = $self->gpg_rcpt
+    my @gpg_rcpts = $self->gpg_rcpts
         or Carp::confess("Encryption not setup for this root");
 
     my $cout = Symbol::gensym();
     my $cin = Symbol::gensym();
 
+    my @recipients = map {("--recipient", $_)} @gpg_rcpts;
     my $pid = IPC::Open2::open2($cout, $cin,
         $self->gpg_path, $self->gpg_args,
-        "--recipient", $gpg_rcpt,
+        @recipients,
         "--trust-model=always",
         "--batch",
         "--encrypt",
@@ -264,7 +265,10 @@ The directory to backup (recursively)
 
 =item B<gpg_recipient>
 
-The public key signature to encrypt data with.  See L<Brackup::Manual::Overview/"Using encryption">.
+The public key signature to encrypt data with.  See
+L<Brackup::Manual::Overview/"Using encryption">.  This option can be
+provided multiple times; any of the provided keys will be able to
+decrypt the backups.
 
 =item B<chunk_size>
 
diff --git a/lib/Brackup/StoredChunk.pm b/lib/Brackup/StoredChunk.pm
index 9b70e46..d3f4141 100644
--- a/lib/Brackup/StoredChunk.pm
+++ b/lib/Brackup/StoredChunk.pm
@@ -96,7 +96,7 @@ sub root {
 # returns true if encrypted, false otherwise
 sub encrypted {
     my $self = shift;
-    return $self->root->gpg_rcpt ? 1 : 0;
+    return $self->root->gpg_rcpts ? 1 : 0;
 }
 
 sub compressed {
-- 
1.7.0.5



More information about the brackup mailing list