=================================================================== RCS file: /cvs/palm/Palm-Keyring/lib/Palm/Keyring.pm,v retrieving revision 1.8 retrieving revision 1.12 diff -u -r1.8 -r1.12 --- palm/Palm-Keyring/lib/Palm/Keyring.pm 2006/11/10 17:31:38 1.8 +++ palm/Palm-Keyring/lib/Palm/Keyring.pm 2007/01/28 00:18:46 1.12 @@ -8,8 +8,7 @@ # # This started as Memo.pm, I just made it work for Keyring. # -# $Id: Keyring.pm,v 1.8 2006/11/10 17:31:38 andrew Exp $ -# $RedRiver: Keyring.pm,v 1.7 2006/11/10 16:45:42 andrew Exp $ +# $RedRiver: Keyring.pm,v 1.11 2007/01/27 23:59:29 andrew Exp $ use strict; package Palm::Keyring; @@ -27,7 +26,7 @@ # One liner, to allow MakeMaker to work. -$VERSION = do { my @r = (q$Revision: 1.8 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; +$VERSION = do { my @r = (q$Revision: 1.12 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; @ISA = qw( Palm::StdAppInfo Palm::Raw ); @@ -37,29 +36,41 @@ =head1 SYNOPSIS - use Palm::Keyring; - $pdb->Decrypt('mypassword'); + use Palm::PDB; + use Palm::Keyring; + my $pdb = new Palm::PDB; + $pdb->Load($file); + foreach my $record (@{ $pdb->{'records'} }) { + print "$record->{'plaintext'}->{'name'}\n"; + } + $pdb->Decrypt($password); + # do something with the decrypted parts =head1 DESCRIPTION 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. -It is just the standard Palm::Raw with 2 additional public methods. Decrypt and Encrypt. +It has the standard Palm::PDB methods with 2 additional public methods. +Decrypt and Encrypt. +It currently supports the v4 Keyring databases. The v5 databases from the pre-release keyring-2.0 are not supported. + =cut + =head2 new - $pdb = new Palm::Keyring ('password'); + $pdb = new Palm::Keyring([$password]); Create a new PDB, initialized with the various Palm::Keyring fields 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 otherwise you +can just use Palm::PDB::new(). =cut -#' + sub new { my $classname = shift; @@ -86,17 +97,9 @@ $self->{version} = 4; # Give the PDB the first record that will hold the encrypted password - $self->{records} = [ { - 'category' => 0, - 'attributes' => { - 'private' => 1, - 'Secret' => 1, - 'Dirty' => 1, - 'dirty' => 1, - }, - }, ]; + $self->{records} = [ $self->new_Record ]; - if ($pass) { + if (defined $pass) { $self->Encrypt($pass); } @@ -110,11 +113,35 @@ ); } +=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 { - my $self = shift; - $self->SUPER::Load(@_); + my $self = shift; + my $filename = shift; + my $password = shift; + $self->{'appinfo'} = {}; + $self->{'records'} = []; + $self->SUPER::Load($filename); + foreach my $record (@{ $self->{records} }) { next unless exists $record->{data}; my ($name, $encrypted) = split /\000/, $record->{data}, 2; @@ -122,24 +149,56 @@ $record->{plaintext}->{name} = $name; $record->{encrypted} = $encrypted; } + + return $self->Decrypt($password) if defined $password; + 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 { my $self = shift; - $self->Encrypt() || return undef; - return $self->SUPER::Load(@_); + my $filename = shift; + 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 { my $self = shift; my $pass = shift; - if ($pass) { - unless ($self->_keyring_verify($pass) ) { + unless (exists $self->{'records'}->[0]->{'data'} && + $self->_keyring_verify($pass) ) { # This would encrypt with a new password. # First decrypting everything with the old password of course. $self->_keyring_update($pass) || return undef; @@ -169,9 +228,27 @@ $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 { my $self = shift; @@ -220,7 +297,7 @@ } - return 1; + 1; } sub _calc_keys @@ -251,11 +328,10 @@ my $pass = shift; die "No password specified!" unless $pass; - $self->{password} = $pass; # AFAIK the thing we use to test the password is # 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; return undef unless defined $data; @@ -271,8 +347,9 @@ if ($data eq $salt . $digest) { # 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}) { + $self->{password} = $pass; return 1; } else { return undef; @@ -294,7 +371,7 @@ die "No password specified!" unless $pass; # 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 $self->Decrypt() || return undef; } @@ -314,7 +391,7 @@ # AFAIK the thing we use to test the password is # always in the first entry - $self->{records}->[1]->{data} = $data; + $self->{records}->[0]->{data} = $data; $self->{password} = $pass; $self->{digest} = _calc_keys($self->{password}); @@ -322,10 +399,9 @@ return 1; } - -# XXX It looks like they are using des_ecb2_encrypt so I dunno if that is different sub _crypt3des { my ( $plaintext, $passphrase, $flag ) = @_; + my $NULL = chr(0); $passphrase .= ' ' x (16*3); my $cyphertext = ""; @@ -338,26 +414,36 @@ $C[$_] = new Crypt::DES( pack( "H*", substr($passphrase, 16*$_, 16 ))); } - for ( 0 .. (($size)/8) - 1) { + for ( 0 .. (($size)/8)) { my $pt = substr( $plaintext, $_*8, 8 ); #print "PT: '$pt' - Length: " . length($pt) . "\n"; + next unless length($pt); if (length($pt) < 8) { die "record not 8 byte padded" if $flag == DECRYPT; my $len = 8 - length($pt); #print "LENGTH: $len\n"; #print "Binary: '" . unpack("b*", $pt) . "'\n"; - $pt .= (chr(0) x $len);# . $pt; - #print "Binary: '" . unpack("b*", $pt) . "'\n"; + $pt .= ($NULL x $len); #print "PT: '$pt' - Length: " . length($pt) . "\n"; + #print "Binary: '" . unpack("b*", $pt) . "'\n"; } - $pt = $C[0]->decrypt( $pt ); - $pt = $C[1]->encrypt( $pt ); - $pt = $C[2]->decrypt( $pt ); + if ($flag == ENCRYPT) { + $pt = $C[0]->encrypt( $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"; $cyphertext .= $pt; } - return substr ( $cyphertext, 0, $size ); + $cyphertext =~ s/$NULL+$//; + #print "CT: '$cyphertext' - Length: " . length($cyphertext) . "\n"; + + return $cyphertext; } 1; @@ -365,12 +451,15 @@ =head1 AUTHOR -Andrew Fresh Eandrew@mad-techies.org +Andrew Fresh Eandrew@mad-techies.orgE =head1 SEE ALSO Palm::PDB(3) Palm::StdAppInfo(3) + +The Keyring for Palm OS website: +L =cut