=================================================================== RCS file: /cvs/palm/Palm-Keyring/lib/Palm/Keyring.pm,v retrieving revision 1.1 retrieving revision 1.11 diff -u -r1.1 -r1.11 --- palm/Palm-Keyring/lib/Palm/Keyring.pm 2006/01/26 20:54:19 1.1 +++ palm/Palm-Keyring/lib/Palm/Keyring.pm 2007/01/27 23:59:29 1.11 @@ -8,8 +8,8 @@ # # This started as Memo.pm, I just made it work for Keyring. # -# $Id: Keyring.pm,v 1.1 2006/01/26 20:54:19 andrew Exp $ -# $RedRiver$ +# $Id: Keyring.pm,v 1.11 2007/01/27 23:59:29 andrew Exp $ +# $RedRiver: Keyring.pm,v 1.10 2006/12/06 18:45:42 andrew Exp $ use strict; package Palm::Keyring; @@ -18,7 +18,7 @@ use vars qw( $VERSION @ISA ); use Digest::MD5 qw(md5); -use Crypt::TripleDES; +use Crypt::DES; use constant ENCRYPT => 1; use constant DECRYPT => 0; @@ -27,7 +27,7 @@ # One liner, to allow MakeMaker to work. -$VERSION = do { my @r = (q$Revision: 1.1 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; +$VERSION = do { my @r = (q$Revision: 1.11 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; @ISA = qw( Palm::StdAppInfo Palm::Raw ); @@ -37,21 +37,24 @@ =head1 SYNOPSIS - use Palm::Keyring; - $pdb->Decrypt('mypassword'); + use Palm::Keyring; + $pdb->Load($file); + $pdb->Decrypt($assword); =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::Raw with 2 additional public methods. +Decrypt and Encrypt. =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. @@ -59,23 +62,22 @@ Use this method if you're creating a Keyring PDB from scratch. =cut -#' + sub new { my $classname = shift; my $pass = shift; + # Create a generic PDB. No need to rebless it, though. my $self = $classname->SUPER::new(@_); - # Create a generic PDB. No need to rebless it, - # though. $self->{name} = "Keys-Gtkr"; # Default $self->{creator} = "Gtkr"; $self->{type} = "Gkyr"; + # The PDB is not a resource database by + # default, but it's worth emphasizing, + # since MemoDB is explicitly not a PRC. $self->{attributes}{resource} = 0; - # The PDB is not a resource database by - # default, but it's worth emphasizing, - # since MemoDB is explicitly not a PRC. # Initialize the AppInfo block $self->{appinfo} = {}; @@ -87,19 +89,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); } @@ -113,13 +105,92 @@ ); } +=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; + 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; + next unless $encrypted; + $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; + 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; @@ -127,31 +198,49 @@ } } - my $seen_enc_pass = 0; - foreach my $record (@{ $self->{records} }) { - unless ($seen_enc_pass) { - $seen_enc_pass = 1; - next; - } + $self->{digest} ||= _calc_keys($self->{password}); + foreach my $record (@{ $self->{records} }) { next unless defined $record->{plaintext}; - my $name = defined $record->{plaintext}->{name} ? $record->{plaintext}->{name} : ''; - my $account = defined $record->{plaintext}->{account} ? $record->{plaintext}->{account} : ''; - my $password = defined $record->{plaintext}->{password} ? $record->{plaintext}->{password} : ''; - my $description = defined $record->{plaintext}->{description} ? $record->{plaintext}->{description} : ''; + my $name = defined $record->{plaintext}->{name} ? + $record->{plaintext}->{name} : ''; + my $account = defined $record->{plaintext}->{account} ? + $record->{plaintext}->{account} : ''; + my $password = defined $record->{plaintext}->{password} ? + $record->{plaintext}->{password} : ''; + my $description = defined $record->{plaintext}->{description} ? + $record->{plaintext}->{description} : ''; my $extra = ''; - my $plaintext = join("\0", $account, $password, $description, $extra); + my $plaintext = join("\000", $account, $password, $description, $extra); - my $encrypted = $self->_crypt($plaintext, ENCRYPT); + my $encrypted = _crypt3des($plaintext, $self->{digest}, ENCRYPT); - $record->{data} = join("\0", $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 { my $self = shift; @@ -161,28 +250,37 @@ $self->_keyring_verify($pass) || return undef; } - my $seen_enc_pass = 0; - foreach my $record (@{ $self->{records} }) { - unless ($seen_enc_pass) { - # need to skip the first record because it is the encrypted password - $seen_enc_pass = 1; - next; - } + $self->{digest} ||= _calc_keys($self->{password}); + foreach my $record (@{ $self->{records} }) { next unless defined $record->{data}; - my ($name, $encrypted) = split /\0/, $record->{data}; + my ($name, $encrypted) = split /\000/, $record->{data}, 2; + next unless $encrypted; + $record->{plaintext}->{name} = $name; - my $decrypted = $self->_crypt($encrypted, DECRYPT); + my $decrypted = _crypt3des($encrypted, $self->{digest}, DECRYPT); my ($account, $password, $description, $extra) - = split /\0/, $decrypted, 4; + = split /\000/, $decrypted, 4; - $record->{plaintext}->{account} = defined $account ? $account : ''; - $record->{plaintext}->{password} = defined $password ? $password : ''; - $record->{plaintext}->{description} = defined $description ? $description : ''; + $record->{plaintext}->{account} = defined $account ? + $account : ''; + $record->{plaintext}->{password} = defined $password ? + $password : ''; + $record->{plaintext}->{description} = defined $description ? + $description : ''; + #print "Name: '$name'\n"; + #print "Encrypted: '$encrypted' - Length: " . length($encrypted) . "\n"; + #print " Hex: '" . unpack("H*", $encrypted) . "'\n"; + #print " Binary:'" . unpack("b*", $encrypted) . "'\n"; + #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"; @@ -191,32 +289,12 @@ } - return 1; + 1; } -sub _crypt -{ - my $self = shift; - my $original = shift; - my $flag = shift; - - my $digest = $self->{digest} || $self->_calc_keys(); - #print "DIGEST: $digest\n"; - - my $des = new Crypt::TripleDES; - - if ($flag == ENCRYPT) { - return $des->encrypt3($original, $digest); - } else { - return $des->decrypt3($original, $digest); - } -} - sub _calc_keys { - my $self = shift; - - my $pass = $self->{'password'}; + my $pass = shift; die "No password defined!" unless defined $pass; my $digest = md5($pass); @@ -233,7 +311,6 @@ # print length $digest, "\n"; #-------------------------------------------------- - $self->{digest} = $digest; return $digest; } @@ -242,8 +319,7 @@ my $self = shift; my $pass = shift; - die "No password specified!" unless defined $pass; - $self->{password} = $pass; + die "No password specified!" unless $pass; # AFAIK the thing we use to test the password is # always in the first entry @@ -263,7 +339,9 @@ if ($data eq $salt . $digest) { # May as well generate the keys we need now, since we know the password is right - if ($self->_calc_keys()) { + $self->{digest} = _calc_keys($pass); + if ($self->{digest}) { + $self->{password} = $pass; return 1; } else { return undef; @@ -282,7 +360,7 @@ my $self = shift; my $pass = shift; - die "No password specified!" unless defined $pass; + die "No password specified!" unless $pass; # if the database already has a password in it if ($self->{records}->[0]->{data}) { @@ -308,12 +386,58 @@ $self->{records}->[0]->{data} = $data; $self->{password} = $pass; - $self->_calc_keys(); + $self->{digest} = _calc_keys($self->{password}); return 1; } +sub _crypt3des { + my ( $plaintext, $passphrase, $flag ) = @_; + my $NULL = chr(0); + $passphrase .= ' ' x (16*3); + my $cyphertext = ""; + + my $size = length ( $plaintext ); + #print "STRING: '$plaintext' - Length: " . length($plaintext) . "\n"; + + my @C; + for ( 0..2 ) { + $C[$_] = new Crypt::DES( pack( "H*", substr($passphrase, 16*$_, 16 ))); + } + + 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 .= ($NULL x $len); + #print "PT: '$pt' - Length: " . length($pt) . "\n"; + #print "Binary: '" . unpack("b*", $pt) . "'\n"; + } + 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; + } + + $cyphertext =~ s/$NULL+$//; + #print "CT: '$cyphertext' - Length: " . length($cyphertext) . "\n"; + + return $cyphertext; +} + 1; __END__ @@ -326,5 +450,8 @@ Palm::PDB(3) Palm::StdAppInfo(3) + +The Keyring for Palm OS website: +L =cut