releasing/cbrtools/perl/Crypt/GPG.pm
changeset 607 378360dbbdba
parent 602 3145852acc89
equal deleted inserted replaced
591:22486c9c7b15 607:378360dbbdba
       
     1 # Copyright (c) 2000-2009 Nokia Corporation and/or its subsidiary(-ies).
       
     2 # All rights reserved.
       
     3 # This component and the accompanying materials are made available
       
     4 # under the terms of the License "Eclipse Public License v1.0"
       
     5 # which accompanies this distribution, and is available
       
     6 # at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
     7 # 
       
     8 # Initial Contributors:
       
     9 # Nokia Corporation - initial contribution.
       
    10 # 
       
    11 # Contributors:
       
    12 # 
       
    13 # Description:
       
    14 # 
       
    15 #
       
    16 # Description:
       
    17 # Crypt::GPG.pm
       
    18 #
       
    19 
       
    20 package Crypt::GPG;
       
    21 
       
    22 use strict;
       
    23 use File::Basename;
       
    24 use IPC::Open2;
       
    25 use IO::Handle;
       
    26 
       
    27 use Crypt;
       
    28 use vars qw(@ISA);
       
    29 @ISA=("Crypt");
       
    30 
       
    31 # Overidden methods from Crypt.pm
       
    32 
       
    33 sub Initialize {
       
    34   my $self = shift;
       
    35 
       
    36   #check to see if the pgp executable exists
       
    37   grep {-x "$_/gpg.exe"} split /;/, $ENV{PATH}
       
    38     or die "Error: The PGP executable \"gpg.exe\" does not exist in users path\n";
       
    39 
       
    40   #call super class method
       
    41   $self->SUPER::Initialize();
       
    42 
       
    43   #check for existence of keyrings and keys
       
    44   $self->CheckKeyRings();
       
    45 }
       
    46 
       
    47 #
       
    48 # Implemented abstract methods from Crypt.pm
       
    49 #
       
    50 
       
    51 sub DoEncrypt {
       
    52   my $self = shift;
       
    53   my $plainText = shift;
       
    54   my $cipherText = shift;
       
    55   my @recipientKeys = @{$_[0]};
       
    56 
       
    57   $self->CheckKeyRings();
       
    58 
       
    59   #build options list
       
    60   my @options = qw(--batch --no-tty --yes --always-trust);
       
    61   push @options, '--status-fd 1';
       
    62   push @options, '-o '.$self->Quoted($cipherText);
       
    63   if ($self->DefaultPath()) {
       
    64     push @options, '--homedir '.$self->Quoted($self->DefaultPath());
       
    65   }
       
    66   foreach my $key (@recipientKeys) {
       
    67     if ($key =~ /^0x([0-9a-fA-F]{8})$/i) {
       
    68       push @options, '-r '.$1;
       
    69     }
       
    70   }
       
    71   my @command = '-e '.$self->Quoted($plainText);
       
    72 
       
    73   # Do encryption. This occasionally fails due to GPG failing to read
       
    74   # the random_seed file when we get a return value of 2. Until we get
       
    75   # a later version of gpg checked as compatible, just retry if this happens.
       
    76   my $retries = 2;
       
    77   my $retval;
       
    78   do {
       
    79       my $cmd = "gpg @options @command";
       
    80       print "Executing command: $cmd\n" if $self->{verbose} > 1;
       
    81       open GPG, "$cmd 2>&1 |" or die "Error: Encrypt command failed.\n";
       
    82       my $error;
       
    83       while (my $line = <GPG>) {
       
    84         if ($self->{verbose} > 1) {
       
    85           print "\t$line";
       
    86         }
       
    87       }
       
    88       close GPG;
       
    89       $retval = $? >> 8;
       
    90       $retries = 0 unless( $retval == 2 );  # Only retry if retval is 2.
       
    91       if( $retval ) {
       
    92         print "WARNING: GPG failure. Error code $retval. ";
       
    93         print "Retrying GPG..." if( $retries > 0 );
       
    94         print "\n";
       
    95       }
       
    96   } while( $retries-- > 0 );
       
    97   die "ERROR: GPG returned error code $retval.\n" if ($retval > 0);
       
    98 }
       
    99 
       
   100 sub DoDecrypt {
       
   101   my $self = shift;
       
   102   my $cipherText = shift;
       
   103   my $plainText = shift;
       
   104   my $passPhrase = shift;
       
   105 
       
   106   $self->CheckKeyRings();
       
   107 
       
   108   #build options list
       
   109   my @options = qw(--batch);
       
   110   push @options, '--status-fd 1';
       
   111   push @options, '--passphrase-fd 0';
       
   112   push @options, '-o '.$self->Quoted($plainText);
       
   113   if ($self->DefaultPath()) {
       
   114     push @options, '--homedir '.$self->Quoted($self->DefaultPath());
       
   115   }
       
   116   my @command = '-d '.$self->Quoted($cipherText);
       
   117 
       
   118   #do decryption reading passphrase from STDIN writing output to log file
       
   119   my $gpgOutput = '/gpg_output.log';
       
   120   my $cmd = "gpg @options @command";
       
   121 
       
   122   # retry 100 times of GPG and opening GPG output
       
   123   my $retries = 100;
       
   124   while ($retries > 0) {
       
   125     print "Executing command: $cmd\n" if $self->{verbose} > 1;
       
   126     if (open GPGIN, "| $cmd 2>NUL 1> $gpgOutput") {
       
   127       print GPGIN "$passPhrase\n";
       
   128       while (my $line  = <GPGIN>) {
       
   129       }
       
   130       close GPGIN;
       
   131 	  
       
   132       #open output of gpg command from file for parsing
       
   133       if (open GPGOUT, "$gpgOutput") {
       
   134         #open output of gpg successfully, then jump out and go ahead
       
   135         last;
       
   136       }
       
   137       else {
       
   138         print "Warning: Cannot open gpg output file, $!\n";
       
   139       }
       
   140     }
       
   141     else {
       
   142       print "Warning: Error: Decrypt command failed, $!\n";
       
   143     }
       
   144     $retries--;
       
   145 
       
   146     # sleep 10 seconds for next try
       
   147     sleep(10);
       
   148   }
       
   149   die "Error: Cannot create or open output log file for $cipherText.\n" if ($retries<=0);
       
   150   
       
   151   my $badPassPhrase =0;
       
   152   my %enc_to;
       
   153   my %no_seckey;
       
   154   my $keyTally = 0;
       
   155   my $useKeyTally = 0; # Fallback for if parsing fails
       
   156   while (my $line = <GPGOUT>) {
       
   157     if ($self->{verbose} > 1) {
       
   158       print "\t$line";
       
   159     }
       
   160     next if ($line =~ /^\s*$/);
       
   161     if ($line =~ /BAD_PASSPHRASE/) {
       
   162       $badPassPhrase = 1;
       
   163     }
       
   164     elsif ($line =~ /GOOD_PASSPHRASE/) {
       
   165       $badPassPhrase = 0;
       
   166     }
       
   167     elsif ($line =~ /ENC_TO/) {
       
   168       if ($line =~ /ENC_TO\s+([\dA-F]*)/) {
       
   169         $enc_to{$1} = $1; # Value is unimportant
       
   170       } else {
       
   171         $useKeyTally = 1;
       
   172       }
       
   173       --$keyTally;
       
   174     }
       
   175     elsif ($line =~ /NO_SECKEY/) {
       
   176       if ($line =~ /NO_SECKEY\s+([\dA-F]*)/) {
       
   177         $no_seckey{$1} = $1; # Value is unimportant
       
   178       } else {
       
   179         $useKeyTally = 1;
       
   180       }
       
   181       --$keyTally;
       
   182     }
       
   183   }
       
   184   close GPGOUT;
       
   185   my $retval = $? >> 8;
       
   186   unlink $gpgOutput;
       
   187 
       
   188   if (!$useKeyTally) {
       
   189     foreach my $key (keys(%no_seckey)) {
       
   190       delete $no_seckey{$key};
       
   191       if (exists $enc_to{$key}) {
       
   192         delete $enc_to{$key};
       
   193       } else {
       
   194         die "Error: Parsing of GPG output failed. Got a NO_SECKEY for no corresponding ENC_TO.\n";
       
   195       }
       
   196     }
       
   197     $keyTally = scalar(keys(%enc_to)); # Number of private keys
       
   198   }
       
   199 
       
   200   #handle specific decryption errors
       
   201   if ($badPassPhrase and $keyTally != 0) {
       
   202     die "Error: Decryption of $cipherText failed. BAD_PASSPHRASE\n";
       
   203   }
       
   204   elsif ($keyTally == 0) {
       
   205     die "Error: Decryption of $cipherText failed. No decrypting key available. NO_SECKEY\n";
       
   206   }
       
   207   elsif ($keyTally < 0) {
       
   208     # Parsing failed, and we got spurious NO_SECKEY messages
       
   209     die "Error: Parsing of GPG output failed. Too many NO_SECKEYs\n";
       
   210   }
       
   211   die "Error code returned by gpg: $retval.\n" if ($retval > 0);
       
   212 }
       
   213 
       
   214 sub GetPublicKeyList {
       
   215   my $self = shift;
       
   216 
       
   217   my @options;
       
   218   if ($self->DefaultPath()) {
       
   219     push @options, '--homedir '.$self->Quoted($self->DefaultPath());
       
   220   }
       
   221   my @command = qw(--list-keys);
       
   222 
       
   223   #list and extract keyids
       
   224   open GPG, "gpg @options @command 2>&1 |" or die "Error: List keys command failed.\n";
       
   225   my @keys;
       
   226   while (my $line = <GPG>) {
       
   227     if ($line =~ /^pub.*?([0-9a-fA-F]{8})\b/i) {
       
   228       push @keys, '0x'.$1;
       
   229     }
       
   230   }
       
   231   close GPG;
       
   232   return \@keys;
       
   233 }
       
   234 
       
   235 sub GetSecretKeyList {
       
   236   my $self = shift;
       
   237 
       
   238   my @options;
       
   239   if ($self->DefaultPath()) {
       
   240     push @options, '--homedir '.$self->Quoted($self->DefaultPath());
       
   241   }
       
   242   my @command = qw(--list-secret-keys);
       
   243 
       
   244   #list and extract keyids
       
   245   open GPG, "gpg @options @command 2>&1 |" or die "Error: List keys command failed.\n";
       
   246   my @keys;
       
   247   while (my $line = <GPG>) {
       
   248     if ($line =~ /^sec.*?([0-9a-fA-F]{8})\b/i) {
       
   249       push @keys, '0x'.$1;
       
   250     }
       
   251   }
       
   252   close GPG;
       
   253   return \@keys;
       
   254 }
       
   255 #
       
   256 # Private
       
   257 #
       
   258 
       
   259 sub CheckKeyRings {
       
   260   my $self = shift;
       
   261 
       
   262   if ($self->DefaultPath) {
       
   263     unless (-e $self->DefaultPath.'/pubring.gpg') {
       
   264       die "Error: PGP Public keyring does not exist\n";
       
   265     }
       
   266     unless (-e $self->DefaultPath.'/secring.gpg') {
       
   267       die "Error: PGP secret keyring does not exist\n";
       
   268     }
       
   269   }
       
   270   unless (@{$self->PublicKeyList}) {
       
   271     die "Error: PGP public keyring is empty\n";
       
   272   }
       
   273   unless (@{$self->SecretKeyList}) {
       
   274     die "Error: PGP secret keyring is empty\n";
       
   275   }
       
   276 }
       
   277 
       
   278 1;
       
   279 
       
   280 __END__
       
   281 
       
   282 =head1 NAME
       
   283 
       
   284 Crypt::GPG.pm - A wrapper over the Gnu Privacy Guard command line PGP tool
       
   285 
       
   286 =head1 DESCRIPTION
       
   287 
       
   288 C<Crypt::GPG> is inherited from the abstract base class C<Crypt>, implementing the abstract methods required for PGP encryption, decryption, etc... by calling Gnu Privacy Guard PGP command line tool (tested with version 1.0.6). For this module to work the PGP executable must have the name C<gpg.exe> and exist in one of the directories defined in the users path.
       
   289 
       
   290 =head1 KNOWN BUGS
       
   291 
       
   292 None
       
   293 
       
   294 =head1 COPYRIGHT
       
   295 
       
   296  Copyright (c) 2000-2009 Nokia Corporation and/or its subsidiary(-ies).
       
   297  All rights reserved.
       
   298  This component and the accompanying materials are made available
       
   299  under the terms of the License "Eclipse Public License v1.0"
       
   300  which accompanies this distribution, and is available
       
   301  at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
   302  
       
   303  Initial Contributors:
       
   304  Nokia Corporation - initial contribution.
       
   305  
       
   306  Contributors:
       
   307  
       
   308  Description:
       
   309  
       
   310 
       
   311 =cut