| version 1.9, 2006/11/10 17:49:51 | 
version 1.11, 2007/01/27 23:59:29 | 
 | 
 | 
|  #   This started as Memo.pm, I just made it work for Keyring. | 
 #   This started as Memo.pm, I just made it work for Keyring. | 
|  # | 
 # | 
|  # $Id$ | 
 # $Id$ | 
|  # $RedRiver: Keyring.pm,v 1.8 2006/11/10 17:31:38 andrew Exp $ | 
 # $RedRiver: Keyring.pm,v 1.10 2006/12/06 18:45:42 andrew Exp $ | 
|   | 
  | 
|  use strict; | 
 use strict; | 
|  package Palm::Keyring; | 
 package Palm::Keyring; | 
 | 
 | 
|   | 
  | 
|  =head1 SYNOPSIS | 
 =head1 SYNOPSIS | 
|   | 
  | 
|      use Palm::Keyring; | 
         use Palm::Keyring; | 
|          $pdb->Decrypt('mypassword'); | 
         $pdb->Load($file); | 
|   | 
         $pdb->Decrypt($assword); | 
|   | 
  | 
|  =head1 DESCRIPTION | 
 =head1 DESCRIPTION | 
|   | 
  | 
|  The Keyring PDB handler is a helper class for the Palm::PDB package. It | 
 The Keyring PDB handler is a helper class for the Palm::PDB package. It | 
|  parses Keyring databases.  See | 
 parses Keyring for Palm OS databases.  See | 
|  L<http://gnukeyring.sourceforge.net/>. | 
 L<http://gnukeyring.sourceforge.net/>. | 
|   | 
  | 
|  It is just the standard Palm::Raw with 2 additional public methods.  Decrypt and Encrypt. | 
 It has the standard Palm::Raw with 2 additional public methods. | 
|   | 
 Decrypt and Encrypt. | 
|   | 
  | 
|  =cut | 
 =cut | 
|   | 
  | 
|  =head2 new | 
 =head2 new | 
|   | 
  | 
|    $pdb = new Palm::Keyring ('password'); | 
         $pdb = new Palm::Keyring($password); | 
|   | 
  | 
|  Create a new PDB, initialized with the various Palm::Keyring fields | 
 Create a new PDB, initialized with the various Palm::Keyring fields | 
|  and an empty record list. | 
 and an empty record list. | 
 | 
 | 
|  Use this method if you're creating a Keyring PDB from scratch. | 
 Use this method if you're creating a Keyring PDB from scratch. | 
|   | 
  | 
|  =cut | 
 =cut | 
|  #' | 
  | 
|  sub new | 
 sub new | 
|  { | 
 { | 
|          my $classname   = shift; | 
         my $classname   = shift; | 
 | 
 | 
|          $self->{version} = 4; | 
         $self->{version} = 4; | 
|   | 
  | 
|          # Give the PDB the first record that will hold the encrypted password | 
         # Give the PDB the first record that will hold the encrypted password | 
|          $self->{records} = [ { | 
         $self->{records} = [ $self->new_Record ]; | 
|                  'category'   => 0, | 
  | 
|                  'attributes' => { | 
  | 
|                          'private' => 1, | 
  | 
|                          'Secret'  => 1, | 
  | 
|                          'Dirty'   => 1, | 
  | 
|                          'dirty'   => 1, | 
  | 
|                  }, | 
  | 
|          }, ]; | 
  | 
|   | 
  | 
|          if ($pass) { | 
         if (defined $pass) { | 
|                  $self->Encrypt($pass); | 
                 $self->Encrypt($pass); | 
|          } | 
         } | 
|   | 
  | 
 | 
 | 
|                  ); | 
                 ); | 
|  } | 
 } | 
|   | 
  | 
|   | 
 =pod | 
|   | 
  | 
|   | 
 =head2 Load | 
|   | 
  | 
|   | 
         $pdb->Load($filename[, $password]); | 
|   | 
  | 
|   | 
 Overrides the standard Palm::Raw Load() to add | 
|   | 
 $record->{'plaintext'}->{'name'} and | 
|   | 
 $record->{'encrypted'} fields. | 
|   | 
 $record->{'plaintext'}->{'name'} holds the name of the record, | 
|   | 
 $record->{'encrypted'} is the encrypted information in the PDB. | 
|   | 
  | 
|   | 
 It also takes an additional optional parameter, which is the password to use to | 
|   | 
 decrypt the database. | 
|   | 
  | 
|   | 
 See Decrypt() for the additional fields that are available after decryption. | 
|   | 
  | 
|   | 
 =cut | 
|   | 
  | 
|  sub Load | 
 sub Load | 
|  { | 
 { | 
|          my $self = shift; | 
         my $self     = shift; | 
|          $self->SUPER::Load(@_); | 
         my $filename = shift; | 
|   | 
         my $password = shift; | 
|   | 
  | 
|   | 
         $self->{'appinfo'} = {}; | 
|   | 
         $self->{'records'} = []; | 
|   | 
         $self->SUPER::Load($filename); | 
|   | 
  | 
|          foreach my $record (@{ $self->{records} }) { | 
         foreach my $record (@{ $self->{records} }) { | 
|                  next unless exists $record->{data}; | 
                 next unless exists $record->{data}; | 
|                  my ($name, $encrypted) = split /\000/, $record->{data}, 2; | 
                 my ($name, $encrypted) = split /\000/, $record->{data}, 2; | 
 | 
 | 
|                  $record->{plaintext}->{name} = $name; | 
                 $record->{plaintext}->{name} = $name; | 
|          $record->{encrypted} = $encrypted; | 
         $record->{encrypted} = $encrypted; | 
|          } | 
         } | 
|   | 
  | 
|   | 
         return $self->Decrypt($password) if defined $password; | 
|   | 
  | 
|          1; | 
         1; | 
|  } | 
 } | 
|   | 
  | 
|   | 
 =pod | 
|   | 
  | 
|   | 
 =head2 Write | 
|   | 
  | 
|   | 
         $pdb->Write($filename[, $password]); | 
|   | 
  | 
|   | 
 Just like the Palm::Raw::Write() but encrypts everything before saving. | 
|   | 
  | 
|   | 
 Also takes an optional password to encrypt with a new password, not needed | 
|   | 
 unless you are changing the password. | 
|   | 
  | 
|   | 
 =cut | 
|   | 
  | 
|  sub Write | 
 sub Write | 
|  { | 
 { | 
|          my $self = shift; | 
         my $self = shift; | 
|          $self->Encrypt() || return undef; | 
         my $filename = shift; | 
|          return $self->SUPER::Load(@_); | 
         my $password = shift; | 
|   | 
  | 
|   | 
         $self->Encrypt($password) || return undef; | 
|   | 
         return $self->SUPER::Write($filename); | 
|  } | 
 } | 
|   | 
  | 
|   | 
 =pod | 
|   | 
  | 
|   | 
 =head2 Encrypt | 
|   | 
  | 
|   | 
         $pdb->Encrypt([$password]); | 
|   | 
  | 
|   | 
 Encrypts the PDB, either with the password used to decrypt or create it, or | 
|   | 
 optionally with a password that is passed. | 
|   | 
  | 
|   | 
 See Decrypt() for an what plaintext fields are available to be encrypted. | 
|   | 
  | 
|   | 
 =cut | 
|   | 
  | 
|  sub Encrypt | 
 sub Encrypt | 
|  { | 
 { | 
|          my $self = shift; | 
         my $self = shift; | 
|          my $pass = shift; | 
         my $pass = shift; | 
|   | 
  | 
|   | 
  | 
|          if ($pass) { | 
         if ($pass) { | 
|                  unless ($self->_keyring_verify($pass) ) { | 
                 unless (exists $self->{'records'}->[0]->{'data'} && | 
|   | 
                     $self->_keyring_verify($pass) ) { | 
|                          # This would encrypt with a new password. | 
                         # This would encrypt with a new password. | 
|                          # First decrypting everything with the old password of course. | 
                         # First decrypting everything with the old password of course. | 
|                          $self->_keyring_update($pass) || return undef; | 
                         $self->_keyring_update($pass) || return undef; | 
 | 
 | 
|                  $record->{data} = join("\000", $name, $encrypted); | 
                 $record->{data} = join("\000", $name, $encrypted); | 
|          } | 
         } | 
|   | 
  | 
|          return 1; | 
         1; | 
|  } | 
 } | 
|   | 
  | 
|   | 
 =head2 Decrypt | 
|   | 
  | 
|   | 
         $pdb->Decrypt([$password]); | 
|   | 
  | 
|   | 
 Decrypts the PDB and fills out the rest of the fields available in | 
|   | 
 $record->{'plaintext'}. | 
|   | 
  | 
|   | 
 The plaintext should now be this, before encryption or after decryption: | 
|   | 
  | 
|   | 
         $record->{'plaintext'} = { | 
|   | 
                 name        => $name, | 
|   | 
                 account     => $account, | 
|   | 
                 password    => $account_password, | 
|   | 
                 description => $description, | 
|   | 
         }; | 
|   | 
  | 
|   | 
 =cut | 
|   | 
  | 
|  sub Decrypt | 
 sub Decrypt | 
|  { | 
 { | 
|          my $self = shift; | 
         my $self = shift; | 
 | 
 | 
|   | 
  | 
|          } | 
         } | 
|   | 
  | 
|          return 1; | 
         1; | 
|  } | 
 } | 
|   | 
  | 
|  sub _calc_keys | 
 sub _calc_keys | 
 | 
 | 
|          my $pass = shift; | 
         my $pass = shift; | 
|   | 
  | 
|          die "No password specified!" unless $pass; | 
         die "No password specified!" unless $pass; | 
|          $self->{password} = $pass; | 
  | 
|   | 
  | 
|          # AFAIK the thing we use to test the password is | 
         # AFAIK the thing we use to test the password is | 
|          #     always in the first entry | 
         #     always in the first entry | 
|          my $data = $self->{records}->[1]->{data}; | 
         my $data = $self->{records}->[0]->{data}; | 
|          #die "No encrypted password in file!" unless defined $data; | 
         #die "No encrypted password in file!" unless defined $data; | 
|          return undef unless defined $data; | 
         return undef unless defined $data; | 
|   | 
  | 
 | 
 | 
|   | 
  | 
|          if ($data eq $salt . $digest) { | 
         if ($data eq $salt . $digest) { | 
|                  # May as well generate the keys we need now, since we know the password is right | 
                 # May as well generate the keys we need now, since we know the password is right | 
|                  $self->{digest} = _calc_keys($self->{password}); | 
                 $self->{digest} = _calc_keys($pass); | 
|                  if ($self->{digest}) { | 
                 if ($self->{digest}) { | 
|   | 
                         $self->{password} = $pass; | 
|                          return 1; | 
                         return 1; | 
|                  } else { | 
                 } else { | 
|                          return undef; | 
                         return undef; | 
 | 
 | 
|          die "No password specified!" unless $pass; | 
         die "No password specified!" unless $pass; | 
|   | 
  | 
|          # if the database already has a password in it | 
         # if the database already has a password in it | 
|          if ($self->{records}->[1]->{data}) { | 
         if ($self->{records}->[0]->{data}) { | 
|                  # Make sure everything is decrypted before we update the keyring | 
                 # Make sure everything is decrypted before we update the keyring | 
|                  $self->Decrypt() || return undef; | 
                 $self->Decrypt() || return undef; | 
|          } | 
         } | 
 | 
 | 
|   | 
  | 
|          # AFAIK the thing we use to test the password is | 
         # AFAIK the thing we use to test the password is | 
|          #     always in the first entry | 
         #     always in the first entry | 
|          $self->{records}->[1]->{data} = $data; | 
         $self->{records}->[0]->{data} = $data; | 
|   | 
  | 
|          $self->{password} = $pass; | 
         $self->{password} = $pass; | 
|          $self->{digest}   = _calc_keys($self->{password}); | 
         $self->{digest}   = _calc_keys($self->{password}); | 
 | 
 | 
|          return 1; | 
         return 1; | 
|  } | 
 } | 
|   | 
  | 
|   | 
  | 
|  # XXX It looks like they are using des_ecb2_encrypt so I dunno if that is different | 
  | 
|  sub _crypt3des { | 
 sub _crypt3des { | 
|          my ( $plaintext, $passphrase, $flag ) = @_; | 
         my ( $plaintext, $passphrase, $flag ) = @_; | 
|   | 
         my $NULL = chr(0); | 
|   | 
  | 
|          $passphrase .= ' ' x (16*3); | 
         $passphrase .= ' ' x (16*3); | 
|          my $cyphertext = ""; | 
         my $cyphertext = ""; | 
 | 
 | 
|                  $C[$_] = new Crypt::DES( pack( "H*", substr($passphrase, 16*$_, 16 ))); | 
                 $C[$_] = new Crypt::DES( pack( "H*", substr($passphrase, 16*$_, 16 ))); | 
|          } | 
         } | 
|   | 
  | 
|   | 
         for ( 0 .. (($size)/8)) { | 
|  # XXX From Crypt::TripleDES | 
  | 
|  # http://search.cpan.org/src/VIPUL/Crypt-TripleDES-0.24/lib/Crypt/TripleDES.pm | 
  | 
|  # | 
  | 
|  #    for ( 0 .. (($size)/8) -1 ) { | 
  | 
|  #     my $pt = substr( $plaintext, $_*8, 8 ); | 
  | 
|  #        $pt = Crypt::PPDES::des_ecb_encrypt( $flag ? $keyvecs{0} : $keyvecs{2}, $flag, $pt ); | 
  | 
|  #        $pt = Crypt::PPDES::des_ecb_encrypt( $keyvecs{1}, (not $flag), $pt ); | 
  | 
|  #        $pt = Crypt::PPDES::des_ecb_encrypt( $flag ? $keyvecs{2} : $keyvecs{0}, $flag, $pt ); | 
  | 
|  #        $cyphertext .= $pt; | 
  | 
|  #    } | 
  | 
|   | 
  | 
|          for ( 0 .. (($size)/8) - 1) { | 
  | 
|                  my $pt = substr( $plaintext, $_*8, 8 ); | 
                 my $pt = substr( $plaintext, $_*8, 8 ); | 
|                  #print "PT: '$pt' - Length: " . length($pt) . "\n"; | 
                 #print "PT: '$pt' - Length: " . length($pt) . "\n"; | 
|   | 
                 next unless length($pt); | 
|                  if (length($pt) < 8) { | 
                 if (length($pt) < 8) { | 
|                          die "record not 8 byte padded" if  $flag == DECRYPT; | 
                         die "record not 8 byte padded" if  $flag == DECRYPT; | 
|                          my $len = 8 - length($pt); | 
                         my $len = 8 - length($pt); | 
|                          #print "LENGTH: $len\n"; | 
                         #print "LENGTH: $len\n"; | 
|                          #print "Binary:    '" . unpack("b*", $pt) . "'\n"; | 
                         #print "Binary:    '" . unpack("b*", $pt) . "'\n"; | 
|                          $pt .= (chr(0) x $len);# . $pt; | 
                         $pt .= ($NULL x $len); | 
|                          #print "Binary:    '" . unpack("b*", $pt) . "'\n"; | 
  | 
|                          #print "PT: '$pt' - Length: " . length($pt) . "\n"; | 
                         #print "PT: '$pt' - Length: " . length($pt) . "\n"; | 
|   | 
                         #print "Binary:    '" . unpack("b*", $pt) . "'\n"; | 
|                  } | 
                 } | 
|                  $pt = $C[0]->decrypt( $pt ); | 
                 if ($flag == ENCRYPT) { | 
|                  $pt = $C[1]->encrypt( $pt ); | 
                         $pt = $C[0]->encrypt( $pt ); | 
|                  $pt = $C[2]->decrypt( $pt ); | 
                         $pt = $C[1]->decrypt( $pt ); | 
|   | 
                         $pt = $C[2]->encrypt( $pt ); | 
|   | 
                 } else { | 
|   | 
                         $pt = $C[0]->decrypt( $pt ); | 
|   | 
                         $pt = $C[1]->encrypt( $pt ); | 
|   | 
                         $pt = $C[2]->decrypt( $pt ); | 
|   | 
                 } | 
|                  #print "PT: '$pt' - Length: " . length($pt) . "\n"; | 
                 #print "PT: '$pt' - Length: " . length($pt) . "\n"; | 
|                  $cyphertext .= $pt; | 
                 $cyphertext .= $pt; | 
|          } | 
         } | 
|   | 
  | 
|          return substr ( $cyphertext, 0, $size ); | 
         $cyphertext =~ s/$NULL+$//; | 
|   | 
         #print "CT: '$cyphertext' - Length: " . length($cyphertext) . "\n"; | 
|   | 
  | 
|   | 
         return $cyphertext; | 
|  } | 
 } | 
|   | 
  | 
|  1; | 
 1; | 
 | 
 | 
|  Palm::PDB(3) | 
 Palm::PDB(3) | 
|   | 
  | 
|  Palm::StdAppInfo(3) | 
 Palm::StdAppInfo(3) | 
|   | 
  | 
|   | 
 The Keyring for Palm OS website: | 
|   | 
 L<http://gnukeyring.sourceforge.net/> | 
|   | 
  | 
|  =cut | 
 =cut |