[BACK]Return to Keyring.pm CVS log [TXT][DIR] Up to [local] / palm / Palm-Keyring / lib / Palm

Diff for /palm/Palm-Keyring/lib/Palm/Keyring.pm between version 1.14 and 1.17

version 1.14, 2007/01/28 22:24:17 version 1.17, 2007/01/30 05:16:16
Line 1 
Line 1 
 package Palm::Keyring;  package Palm::Keyring;
   
 # $RedRiver: Keyring.pm,v 1.13 2007/01/28 18:13:28 andrew Exp $  # $RedRiver: Keyring.pm,v 1.16 2007/01/30 04:59:55 andrew Exp $
 #  #
 # Perl class for dealing with Keyring for Palm OS databases.  # Perl class for dealing with Keyring for Palm OS databases.
 #  #
Line 25 
Line 25 
 Readonly my $NULL       => chr 0;  Readonly my $NULL       => chr 0;
   
 # One liner, to allow MakeMaker to work.  # One liner, to allow MakeMaker to work.
 our ($VERSION) = q$Revision$ =~ m{ Revision: \s+ (\S+) }xm;  our $VERSION = 0.91;
   
 #@ISA = qw( Palm::StdAppInfo Palm::Raw );  
   
 sub new {  sub new {
     my $classname = shift;      my $classname = shift;
     my $pass      = shift;      my $pass      = shift;
Line 54 
Line 52 
     # Set the version      # Set the version
     $self->{'version'} = 4;      $self->{'version'} = 4;
   
     # Give the PDB the first record that will hold the encrypted password  
     $self->{'records'} = [ $self->new_Record ];  
   
     if ( defined $pass ) {      if ( defined $pass ) {
         $self->Encrypt($pass);          $self->Password($pass);
     }      }
   
     return $self;      return $self;
Line 69 
Line 64 
     return 1;      return 1;
 }  }
   
 sub Load {  sub ParseRecord {
     my $self     = shift;      my $self     = shift;
     my $filename = shift;  
     my $password = shift;  
   
     $self->{'appinfo'} = {};      my $rec = $self->SUPER::ParseRecord(@_);
     $self->{'records'} = [];  
     $self->SUPER::Load($filename);  
   
     foreach my $rec ( @{ $self->{'records'} } ) {      # skip the 0 record that holds the password
         if ( ! exists $rec->{'data'}) { next; };      return $rec if ! exists $self->{'records'};
         my ( $name, $encrypted ) = split /$NULL/xm, $rec->{'data'}, 2;  
         if ( ! $encrypted ) { next };  
         $rec->{'plaintext'}->{'name'} = $name;  
         $rec->{'encrypted'} = $encrypted;  
     }  
   
     return $self->Decrypt($password) if defined $password;      # skip records with no data (There shouldn't be any)
       return $rec if ! exists $rec->{'data'};
   
     return 1;      my ( $name, $encrypted ) = split /$NULL/xm, $rec->{'data'}, 2;
   
       return $rec if ! $encrypted;
       $rec->{'data'} = $name;
       $rec->{'encrypted'} = $encrypted;
   
       return $rec;
 }  }
   
 sub Write {  sub PackRecord {
     my $self     = shift;      my $self = shift;
     my $filename = shift;      my $rec  = shift;
     my $password = shift;  
   
     $self->Encrypt($password) || return;      my $rec0_id = $self->{'records'}->[0]->{'id'};
     return $self->SUPER::Write($filename);  
       if ($rec->{'encrypted'} && ! $rec->{'id'} == $rec0_id) {
           $rec->{'data'} = join $NULL, $rec->{'data'}, $rec->{'encrypted'};
           delete $rec->{'encrypted'};
       }
   
       return $self->SUPER::PackRecord($rec, @_);
 }  }
   
 sub Encrypt {  sub Encrypt {
     my $self = shift;      my $self = shift;
     my $pass = shift;      my $rec  = shift;
       my $data = shift;
       my $pass = shift || $self->{'password'};
   
     if ($pass) {      if ( ! $pass) {
         if (          croak("'password' not set!\n");
             !( exists $self->{'records'}->[0]->{'data'}      }
                 && $self->_keyring_verify($pass) )  
           )  
         {  
   
             # This would encrypt with a new password.      if ( ! $rec) {
             # First decrypting everything with the old password of course.          croak("Needed parameter 'record' not passed!\n");
             $self->_keyring_update($pass) || return;  
             $self->_keyring_verify($pass) || return;  
         }  
     }      }
   
     $self->{'digest'} ||= _calc_keys( $self->{'password'} );      if ( ! $data) {
           croak("Needed parameter 'data' not passed!\n");
       }
   
     foreach my $rec ( @{ $self->{'records'} } ) {      if ( ! $self->Password($pass)) {
         if (!defined $rec->{'plaintext'}) { next; };          croak("Incorrect Password!\n");
       }
   
         my $name =      $self->{'digest'} ||= _calc_keys( $pass );
           defined $rec->{'plaintext'}->{'name'}  
           ? $rec->{'plaintext'}->{'name'}  
           : $EMPTY;  
         my $account =  
           defined $rec->{'plaintext'}->{'account'}  
           ? $rec->{'plaintext'}->{'account'}  
           : $EMPTY;  
         my $password =  
           defined $rec->{'plaintext'}->{'password'}  
           ? $rec->{'plaintext'}->{'password'}  
           : $EMPTY;  
         my $description =  
           defined $rec->{'plaintext'}->{'description'}  
           ? $rec->{'plaintext'}->{'description'}  
           : $EMPTY;  
         my $extra = $EMPTY;  
   
         my $plaintext = join "$NULL", $account, $password, $description, $extra;      $data->{'account'}  ||= $EMPTY;
       $data->{'password'} ||= $EMPTY;
       $data->{'notes'}    ||= $EMPTY;
   
         my $encrypted = _crypt3des( $plaintext, $self->{'digest'}, $ENCRYPT );      my $plaintext = join $NULL,
           $data->{'account'}, $data->{'password'}, $data->{'notes'};
   
         $rec->{'data'} = join "$NULL", $name, $encrypted;      my $encrypted = _crypt3des( $plaintext, $self->{'digest'}, $ENCRYPT );
     }  
   
       return if ! $encrypted;
   
       $rec->{'data'} ||= $data->{'name'};
       $rec->{'encrypted'} = $encrypted;
     return 1;      return 1;
 }  }
   
 sub Decrypt {  sub Decrypt {
     my $self = shift;      my $self = shift;
     my $pass = shift;      my $rec  = shift;
       my $pass = shift || $self->{'password'};
   
     if ($pass) {      if ( ! $pass) {
         $self->_keyring_verify($pass) || return;          croak("'password' not set!\n");
     }      }
   
     $self->{'digest'} ||= _calc_keys( $self->{'password'} );      if ( ! $rec) {
           carp("Needed parameter 'record' not passed!\n");
           return;
       }
   
     my $reccount = 0;      if ( ! $self->Password($pass)) {
     foreach my $rec ( @{ $self->{'records'} } ) {          croak("Invalid Password!\n");
         $reccount++;      }
   
         # always skip the first record that has the password in it.      if ( ! $rec->{'encrypted'} ) {
         next if $reccount <= 1;          croak("No encrypted content!");
         if ( ! defined $rec->{'data'} ) {      }
             warn 'Invalid record ' . ( $reccount - 1 ) . "\n";  
             next;  
         }  
   
         my ( $name, $encrypted ) = split /$NULL/xm, $rec->{'data'}, 2;      $self->{'digest'} ||= _calc_keys( $pass );
         if (! $encrypted) { next; };  
   
         $rec->{'plaintext'}->{'name'} = $name;      my $decrypted =
           _crypt3des( $rec->{'encrypted'}, $self->{'digest'}, $DECRYPT );
         my $decrypted = _crypt3des( $encrypted, $self->{'digest'}, $DECRYPT );      my ( $account, $password, $notes, $extra ) = split /$NULL/xm,
         my ( $account, $password, $description, $extra ) = split /$NULL/xm,  
           $decrypted, 4;            $decrypted, 4;
   
         $rec->{'plaintext'}->{'account'} = defined $account ? $account : $EMPTY;      return {
         $rec->{'plaintext'}->{'password'} =          account  => $account,
           defined $password ? $password : $EMPTY;          password => $password,
         $rec->{'plaintext'}->{'description'} =          notes    => $notes,
           defined $description ? $description : $EMPTY;      };
   }
   
         #print "Name:      '$name'\n";  sub Password {
         #print "Encrypted: '$encrypted' - Length: " . length($encrypted) . "\n";      my $self = shift;
         #print "    Hex:   '" . unpack("H*", $encrypted) . "'\n";      my $pass = shift || $self->{'password'};
         #print "    Binary:'" . unpack("b*", $encrypted) . "'\n";      my $new_pass = shift;
         #print "Decrypted: '$decrypted' - Length: " . length($decrypted) . "\n";  
         #print "    Hex:   '" . unpack("H*", $decrypted) . "'\n";  
         #print "    Binary:'" . unpack("b*", $decrypted) . "'\n";  
         #print "\n";  
         #print "Extra: $extra\n";  
         #exit;  
         #--------------------------------------------------  
         # print "Account:     $account\n";  
         # print "Password:    $password\n";  
         # print "Description: $description\n";  
         #--------------------------------------------------  
   
       if (! exists $self->{'records'}) {
           # Give the PDB the first record that will hold the encrypted password
           $self->{'records'} = [ $self->new_Record ];
   
           return $self->_password_update($pass);
     }      }
   
     return 1;      if ($new_pass) {
           my @accts = ();
           foreach my $i (0..$#{ $self->{'records'} }) {
               if ($i == 0) {
                   push @accts, undef;
                   next;
               }
               my $acct = $self->Decrypt($self->{'records'}->[$i], $pass);
               if ( ! $acct ) {
                   croak("Couldn't decrypt $self->{'records'}->[$i]->{'data'}");
               }
               push @accts, $acct;
           }
   
           if ( ! $self->_password_update($new_pass)) {
               croak("Couldn't set new password!");
           }
           $pass = $new_pass;
   
           foreach my $i (0..$#accts) {
               next if $i == 0;
               $self->Encrypt($self->{'records'}->[$i], $accts[$i], $pass);
           }
       }
   
       return $self->_password_verify($pass);
 }  }
   
 sub _calc_keys {  sub _calc_keys {
Line 231 
Line 237 
     return $digest;      return $digest;
 }  }
   
 sub _keyring_verify {  sub _password_verify {
     my $self = shift;      my $self = shift;
     my $pass = shift;      my $pass = shift;
   
     if (! $pass) { croak('No password specified!'); };      if (! $pass) { croak('No password specified!'); };
   
       if (defined $self->{'password'} && $pass eq $self->{'password'}) {
           # already verified this password
           return 1;
       }
   
     # 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'}->[0]->{'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;
     if (! defined $data) { return; };      if ( ! defined $data) { return; };
   
     $data =~ s/$NULL$//xm;      $data =~ s/$NULL$//xm;
   
Line 266 
Line 277 
     return;      return;
 }  }
   
 sub _keyring_update {  sub _password_update {
   
     # It is very important to Encrypt after calling this      # It is very important to Encrypt after calling this
     #     (Although it is generally only called by Encrypt)      #     (Although it is generally only called by Encrypt)
Line 275 
Line 286 
     my $self = shift;      my $self = shift;
     my $pass = shift;      my $pass = shift;
   
     if (! $pass) { croak('No password specified!'); };      if (! defined $pass) { croak('No password specified!'); };
   
     # if the database already has a password in it  
     if ( $self->{'records'}->[0]->{'data'} ) {  
   
         # Make sure everything is decrypted before we update the keyring  
         $self->Decrypt() || return;  
     }  
   
     my $salt;      my $salt;
     for ( 1 .. $kSalt_Size ) {      for ( 1 .. $kSalt_Size ) {
         $salt .= chr int rand 255;          $salt .= chr int rand 255;
Line 329 
Line 333 
         #print "PT: '$pt' - Length: " . length($pt) . "\n";          #print "PT: '$pt' - Length: " . length($pt) . "\n";
         if (! length $pt) { next; };          if (! length $pt) { next; };
         if ( (length $pt) < 8 ) {          if ( (length $pt) < 8 ) {
                         if ($flag == $DECRYPT) { croak('record not 8 byte padded'); };              if ($flag == $DECRYPT) { croak('record not 8 byte padded'); };
             my $len = 8 - (length $pt);              my $len = 8 - (length $pt);
   
             #print "LENGTH: $len\n";              #print "LENGTH: $len\n";
Line 377 
Line 381 
 It has the standard Palm::PDB methods with 2 additional public methods.  It has the standard Palm::PDB methods with 2 additional public methods.
 Decrypt and Encrypt.  Decrypt and Encrypt.
   
 It currently supports the v4 Keyring databases.  The v5 databases from the pre-release keyring-2.0 are not supported.  It currently supports the v4 Keyring databases.  The v5 databases from
   the pre-release keyring-2.0 are not supported.
   
   This module doesn't store the decrypted content.  It only keeps it until it
   returns it to you or encrypts it.
   
 =head1 SYNOPSIS  =head1 SYNOPSIS
   
         use Palm::PDB;      use Palm::PDB;
         use Palm::Keyring;      use Palm::Keyring;
         my $pdb = new Palm::PDB;  
         $pdb->Load($file);      my $pass = 'password';
         foreach my $rec (@{ $pdb->{'records'} }) {      my $pdb = new Palm::PDB;
                 print "$rec->{'plaintext'}->{'name'}\n";      $pdb->Load($file);
         }  
         $pdb->Decrypt($password);      foreach (0..$#{ $pdb->{'records'} }) {
         # do something with the decrypted parts          next if $_ = 0; # skip the password record
           my $rec  = $pdb->{'records'}->[$_];
           my $acct = $pdb->Decrypt($rec, $pass);
           print $rec->{'data'}, ' - ', $acct->{'account'}, "\n";
       }
   
 =head1 SUBROUTINES/METHODS  =head1 SUBROUTINES/METHODS
   
 =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 otherwise you  Use this method if you're creating a Keyring PDB from scratch otherwise you
 can just use Palm::PDB::new().  can just use Palm::PDB::new() before calling Load().
   
 =head2 Load  =head2 Encrypt
   
         $pdb->Load($filename[, $password]);      $pdb->Encrypt($rec, $acct, [$password]);
   
 Overrides the standard Palm::Raw Load() to add  Encrypts an account into a record, either with the password previously
 $rec->{'plaintext'}->{'name'} and  used, or with a password that is passed.
 $rec->{'encrypted'} fields.  
 $rec->{'plaintext'}->{'name'} holds the name of the record,  
 $rec->{'encrypted'} is the encrypted information in the PDB.  
   
 It also takes an additional optional parameter, which is the password to use to  $rec is a record from $pdb->{'records'} or a newly generated record.
 decrypt the database.  $acct is a hashref in the format below.
   
 See Decrypt() for the additional fields that are available after decryption.      my $acct = {
           account  => $account,
           password => $password,
           notes    => $notes,
       };
   
 =head2 Write  =head2 Decrypt
   
         $pdb->Write($filename[, $password]);      my $acct = $pdb->Decrypt($rec[, $password]);
   
 Just like the Palm::Raw::Write() but encrypts everything before saving.  Decrypts the record and returns a hashref for the account as described
   under Encrypt();
   
 Also takes an optional password to encrypt with a new password, not needed      foreach (0..$#{ $pdb->{'records'}) {
 unless you are changing the password.          next if $_ == 0;
           my $rec = $pdb->{'records'}->[$_];
           my $acct = $pdb->Decrypt($rec[, $password]);
           # do something with $acct
       }
   
 =head2 Encrypt  =head2 Password
   
         $pdb->Encrypt([$password]);      $pdb->Password([$password[, $new_password]]);
   
 Encrypts the PDB, either with the password used to decrypt or create it, or  Either sets the password to be used to crypt, or if you pass $new_password,
 optionally with a password that is passed.  changes the password on the database.
   
 See Decrypt() for an what plaintext fields are available to be encrypted.  If you have created a new $pdb, and you didn't set a password when you
   called new(), you only need to pass one password and it will set that as
   the password.
   
 =head2 Decrypt  If nothing is passed, and there has been a password used before,
   it just verifies that the password was correct.
   
         $pdb->Decrypt([$password]);  
   
 Decrypts the PDB and fills out the rest of the fields available in  
 $rec->{'plaintext'}.  
   
 The plaintext should now be this, before encryption or after decryption:  
   
         $rec->{'plaintext'} = {  
                 name        => $name,  
                 account     => $account,  
                 password    => $account_password,  
                 description => $description,  
         };  
   
 =head1 DEPENDENCIES  =head1 DEPENDENCIES
   
 Palm::StdAppInfo  Palm::StdAppInfo
Line 476 
Line 483 
   
 =head1 LICENSE AND COPYRIGHT  =head1 LICENSE AND COPYRIGHT
   
 You may distribute this file under the terms of perl itself  
 as specified in the LICENSE file.  
   
 Copyright 2004, 2005, 2006, 2007 Andrew Fresh, All Rights Reserved.  Copyright 2004, 2005, 2006, 2007 Andrew Fresh, All Rights Reserved.
   
 This program is free software; you can redistribute it and/or modify it  This program is free software; you can redistribute it and/or
 under the same terms as Perl itself.  modify it under the same terms as Perl itself.
   
 =head1 SEE ALSO  =head1 SEE ALSO
   

Legend:
Removed from v.1.14  
changed lines
  Added in v.1.17

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>