| version 1.31, 2007/02/19 02:55:35 | version 1.39, 2007/02/23 03:24:09 | 
|  |  | 
| package Palm::Keyring; | package Palm::Keyring; | 
| # $RedRiver: Keyring.pm,v 1.30 2007/02/19 01:37:10 andrew Exp $ | # $RedRiver: Keyring.pm,v 1.38 2007/02/23 02:54:49 andrew Exp $ | 
| ######################################################################## | ######################################################################## | 
| # Keyring.pm *** Perl class for Keyring for Palm OS databases. | # Keyring.pm *** Perl class for Keyring for Palm OS databases. | 
| # | # | 
|  |  | 
| use warnings; | use warnings; | 
|  |  | 
| use Carp; | use Carp; | 
| use Data::Dumper; |  | 
|  |  | 
| use base qw/ Palm::StdAppInfo /; | use base qw/ Palm::StdAppInfo /; | 
|  |  | 
| use Digest::HMAC_SHA1 qw(hmac_sha1); |  | 
| use Digest::SHA1 qw(sha1); |  | 
| use Crypt::CBC; |  | 
|  |  | 
| use Digest::MD5 qw(md5); |  | 
| use Crypt::DES; |  | 
|  |  | 
| my $ENCRYPT    = 1; | my $ENCRYPT    = 1; | 
| my $DECRYPT    = 0; | my $DECRYPT    = 0; | 
| my $MD5_CBLOCK = 64; | my $MD5_CBLOCK = 64; | 
|  |  | 
| my $NULL       = chr 0; | my $NULL       = chr 0; | 
|  |  | 
| my @CRYPTS = ( | my @CRYPTS = ( | 
| { # None | { | 
|  | alias     => 'None', | 
| name      => 'None', | name      => 'None', | 
| keylen    => 8, | keylen    => 8, | 
| blocksize => 1, | blocksize => 1, | 
| default_iter => 500, | default_iter => 500, | 
| }, | }, | 
| { # DES-EDE3 | { | 
|  | alias     => 'DES-EDE3', | 
| name      => 'DES_EDE3', | name      => 'DES_EDE3', | 
| keylen    => 24, | keylen    => 24, | 
| blocksize =>  8, | blocksize =>  8, | 
| DES_odd_parity => 1, | DES_odd_parity => 1, | 
| default_iter => 1000, | default_iter => 1000, | 
| }, | }, | 
| { # AES128 | { | 
|  | alias     => 'AES128', | 
| name      => 'Rijndael', | name      => 'Rijndael', | 
| keylen    => 16, | keylen    => 16, | 
| blocksize => 16, | blocksize => 16, | 
| default_iter => 100, | default_iter => 100, | 
| }, | }, | 
| { # AES256 | { | 
|  | alias     => 'AES256', | 
| name      => 'Rijndael', | name      => 'Rijndael', | 
| keylen    => 32, | keylen    => 32, | 
| blocksize => 16, | blocksize => 16, | 
|  |  | 
| # Set defaults | # Set defaults | 
| if ($self->{version} == 5) { | if ($self->{version} == 5) { | 
| $self->{options}->{cipher} ||= 0; # 'None' | $self->{options}->{cipher} ||= 0; # 'None' | 
| $self->{options}->{iterations} ||= | my $c = crypts($self->{options}->{cipher}) | 
| $CRYPTS[ $self->{options}->{cipher} ]{default_iter}; | or croak('Unknown cipher ' . $self->{options}->{cipher}); | 
|  | $self->{options}->{iterations} ||= $c->{default_iter}; | 
| $self->{appinfo}->{cipher} ||= $self->{options}->{cipher}; | $self->{appinfo}->{cipher} ||= $self->{options}->{cipher}; | 
| $self->{appinfo}->{iter}   ||= $self->{options}->{iterations}; | $self->{appinfo}->{iter}   ||= $self->{options}->{iterations}; | 
| }; | }; | 
|  |  | 
| if ( defined $options->{password} ) { | if ( defined $options->{password} ) { | 
|  |  | 
| return 1; | return 1; | 
| } | } | 
|  |  | 
|  | # Accessors | 
|  |  | 
|  | sub crypts | 
|  | { | 
|  | my $crypt = shift; | 
|  | if (! defined $crypt || ! length $crypt) { | 
|  | return; | 
|  | } elsif ($crypt =~ /\D/) { | 
|  | foreach my $c (@CRYPTS) { | 
|  | if ($c->{alias} eq $crypt) { | 
|  | return $c; | 
|  | } | 
|  | } | 
|  | # didn't find it. | 
|  | return; | 
|  | } else { | 
|  | return $CRYPTS[$crypt]; | 
|  | } | 
|  | } | 
|  |  | 
| # ParseRecord | # ParseRecord | 
|  |  | 
| sub ParseRecord | sub ParseRecord | 
|  |  | 
| delete $rec->{data}; | delete $rec->{data}; | 
|  |  | 
| } elsif ($self->{version} == 5) { | } elsif ($self->{version} == 5) { | 
| my $blocksize = $CRYPTS[ $self->{appinfo}->{cipher} ]{blocksize}; | my $c = crypts( $self->{appinfo}->{cipher} ) | 
|  | or croak('Unknown cipher ' . $self->{appinfo}->{cipher}); | 
|  | my $blocksize = $c->{blocksize}; | 
| my ($field, $extra) = _parse_field($rec->{data}); | my ($field, $extra) = _parse_field($rec->{data}); | 
| my $ivec      = substr $extra, 0, $blocksize; | delete $rec->{data}; | 
| my $encrypted = substr $extra, $blocksize; |  | 
|  |  | 
| $rec->{name}      = $field->{data}; | $rec->{name}      = $field->{data}; | 
| $rec->{ivec}      = $ivec; | $rec->{ivec}      = substr $extra, 0, $blocksize; | 
| $rec->{encrypted} = $encrypted; | $rec->{encrypted} = substr $extra, $blocksize; | 
|  |  | 
| } else { | } else { | 
| die 'Unsupported Version'; | die 'Unsupported Version'; | 
|  |  | 
| } | } | 
|  |  | 
| } elsif ($self->{version} == 5) { | } elsif ($self->{version} == 5) { | 
| my $field = { | my $field; | 
| 'label_id' => 1, | if ($rec->{name}) { | 
| 'data'     => $rec->{name}, | $field = { | 
| 'font'     => 0, | 'label_id' => 1, | 
| }; | 'data'     => $rec->{name}, | 
| my $packed .= _pack_field($field); | 'font'     => 0, | 
|  | }; | 
|  | } else { | 
|  | $field = { | 
|  | 'label_id' => $EMPTY, | 
|  | 'data'     => $EMPTY, | 
|  | 'font'     => 0, | 
|  | }; | 
|  | } | 
|  | my $packed = _pack_field($field); | 
|  |  | 
| $rec->{data} = join '', $packed, $rec->{ivec}, $rec->{encrypted}; | $rec->{data} = join '', $packed, $rec->{ivec}, $rec->{encrypted}; | 
|  |  | 
|  |  | 
|  |  | 
| my $unpackstr | my $unpackstr | 
| = ("C1" x 8)  # 8 uint8s in an array for the salt | = ("C1" x 8)  # 8 uint8s in an array for the salt | 
| . ("S1" x 2)  # the iter (uint16) and the cipher (uint16) | . ("n1" x 2)  # the iter (uint16) and the cipher (uint16) | 
| . ("C1" x 8); # and finally 8 more uint8s for the hash | . ("C1" x 8); # and finally 8 more uint8s for the hash | 
|  |  | 
| my (@salt, $iter, $cipher, @hash); | my (@salt, $iter, $cipher, @hash); | 
|  |  | 
|  |  | 
| my $packstr | my $packstr | 
| = ("C1" x 8)  # 8 uint8s in an array for the salt | = ("C1" x 8)  # 8 uint8s in an array for the salt | 
| . ("S1" x 2)  # the iter (uint16) and the cipher (uint16) | . ("n1" x 2)  # the iter (uint16) and the cipher (uint16) | 
| . ("C1" x 8); # and finally 8 more uint8s for the hash | . ("C1" x 8); # and finally 8 more uint8s for the hash | 
|  |  | 
| my @salt = map { hex $_ } $appinfo->{salt} =~ /../gxm; | my @salt = map { hex $_ } $appinfo->{salt} =~ /../gxm; | 
|  |  | 
| my $rec  = shift; | my $rec  = shift; | 
| my $data = shift; | my $data = shift; | 
| my $pass = shift || $self->{password}; | my $pass = shift || $self->{password}; | 
|  | my $ivec = shift; | 
|  |  | 
| if ( ! $pass && ! $self->{appinfo}->{key}) { | if ( ! $pass && ! $self->{appinfo}->{key}) { | 
| croak("password not set!\n"); | croak("password not set!\n"); | 
|  |  | 
| $rec->{name}    ||= $data->{name}; | $rec->{name}    ||= $data->{name}; | 
|  |  | 
| } elsif ($self->{version} == 5) { | } elsif ($self->{version} == 5) { | 
| my @recs = ($data, $acct); | my @accts = ($data, $acct); | 
| my $name; |  | 
| if ($self->{options}->{v4compatible}) { | if ($self->{options}->{v4compatible}) { | 
| $rec->{name} ||= $data->{name}; | $rec->{name} ||= $data->{name}; | 
| foreach my $rec (@recs) { | foreach my $a (@accts) { | 
| my @fields; | my @fields; | 
| foreach my $k (sort keys %{ $rec }) { | foreach my $k (sort keys %{ $a }) { | 
| my $field = { | my $field = { | 
| label    => $k, | label    => $k, | 
| font     => 0, | font     => 0, | 
| data     => $rec->{$k}, | data     => $a->{$k}, | 
| }; | }; | 
| push @fields, $field; | push @fields, $field; | 
| } | } | 
| $rec = \@fields; | $a = \@fields; | 
| } | } | 
| } | } | 
|  |  | 
| my $ivec; |  | 
| ($encrypted, $ivec) = _encrypt_v5( | ($encrypted, $ivec) = _encrypt_v5( | 
| @recs, | @accts, | 
| $self->{appinfo}->{key}, | $self->{appinfo}->{key}, | 
| $self->{appinfo}->{cipher}, | $self->{appinfo}->{cipher}, | 
|  | $ivec, | 
| ); | ); | 
| if ($ivec) { | if (defined $ivec) { | 
| $rec->{ivec} = $ivec; | $rec->{ivec} = $ivec; | 
| } | } | 
|  |  | 
|  |  | 
| my $old    = shift; | my $old    = shift; | 
| my $key    = shift; | my $key    = shift; | 
| my $cipher = shift; | my $cipher = shift; | 
| my $length = $CRYPTS[ $cipher ]{blocksize}; | my $ivec   = shift; | 
| my $ivec   = shift || pack("C*",map {rand(256)} 1..$length); | my $c = crypts($cipher) or croak('Unknown cipher ' . $cipher); | 
|  |  | 
| my $keylen      = $CRYPTS[ $cipher ]{keylen}; | if (! defined $ivec) { | 
| my $cipher_name = $CRYPTS[ $cipher ]{name}; | $ivec = pack("C*",map {rand(256)} 1..$c->{blocksize}); | 
|  | } | 
|  |  | 
| my $changed = 0; | my $changed = 0; | 
| my $need_newdate = 1; | my $need_newdate = 1; | 
| my $date_index; | my $date_index; | 
| for (my $i = 0; $i < @{ $new }; $i++) { | for (my $i = 0; $i < @{ $new }; $i++) { | 
| if ( | if ( | 
| (exists $new->[$i]->{label_id} && $new->[$i]->{label_id} == 3) || | ($new->[$i]->{label_id} && $new->[$i]->{label_id} == 3) || | 
| (exists $new->[$i]->{label}    && $new->[$i]->{label}    eq 'lastchange') | ($new->[$i]->{label} && $new->[$i]->{label} eq 'lastchange') | 
| ) { | ) { | 
| $date_index   = $i; | $date_index   = $i; | 
| if ( $old && $#{ $new } == $#{ $old } && ( | if ( $old && $#{ $new } == $#{ $old } && ( | 
| $new->[$i]->{data}->{day}   != $old->[$i]->{data}->{day}   || | $new->[$i]{data}{day}   != $old->[$i]{data}{day}   || | 
| $new->[$i]->{data}->{month} != $old->[$i]->{data}->{month} || | $new->[$i]{data}{month} != $old->[$i]{data}{month} || | 
| $new->[$i]->{data}->{year}  != $old->[$i]->{data}->{year} | $new->[$i]{data}{year}  != $old->[$i]{data}{year} | 
| )) { | )) { | 
| $changed      = 1; | $changed      = 1; | 
| $need_newdate = 0; | $need_newdate = 0; | 
| last; |  | 
| } | } | 
|  |  | 
| } elsif ($old && $#{ $new } == $#{ $old }) { | } elsif ($old && $#{ $new } == $#{ $old }) { | 
|  |  | 
| foreach my $field (@{ $new }) { | foreach my $field (@{ $new }) { | 
| $decrypted .= _pack_field($field); | $decrypted .= _pack_field($field); | 
| } | } | 
|  |  | 
| my $encrypted; | my $encrypted; | 
| if ($cipher_name eq 'None') { | if ($c->{name} eq 'None') { | 
| # do nothing | # do nothing | 
| $encrypted = $decrypted; | $encrypted = $decrypted; | 
|  |  | 
| } elsif ($cipher_name eq 'DES_EDE3' or $cipher_name eq 'Rijndael') { | } elsif ($c->{name} eq 'DES_EDE3' or $c->{name} eq 'Rijndael') { | 
| my $c = Crypt::CBC->new( | require Crypt::CBC; | 
| -literal_key => 1, | my $cbc = Crypt::CBC->new( | 
| -key         => $key, | -key         => $key, | 
|  | -literal_key => 1, | 
| -iv          => $ivec, | -iv          => $ivec, | 
| -cipher      => $cipher_name, | -cipher      => $c->{name}, | 
| -keysize     => $keylen, | -keysize     => $c->{keylen}, | 
|  | -blocksize   => $c->{blocksize}, | 
| -header      => 'none', | -header      => 'none', | 
| -padding     => 'oneandzeroes', | -padding     => 'oneandzeroes', | 
| ); | ); | 
|  |  | 
| croak("Unable to set up encryption!"); | croak("Unable to set up encryption!"); | 
| } | } | 
|  |  | 
| $encrypted = $c->encrypt($decrypted); | $encrypted = $cbc->encrypt($decrypted); | 
|  |  | 
| } else { | } else { | 
| die "Unsupported Version"; | die "Unsupported Version"; | 
|  |  | 
|  |  | 
| sub _decrypt_v5 | sub _decrypt_v5 | 
| { | { | 
|  |  | 
| my $encrypted = shift; | my $encrypted = shift; | 
| my $key       = shift; | my $key       = shift; | 
| my $cipher    = shift; | my $cipher    = shift; | 
| my $ivec      = shift; | my $ivec      = shift; | 
|  |  | 
| my $keylen       = $CRYPTS[ $cipher ]{keylen}; | my $c = crypts($cipher) or croak('Unknown cipher ' . $cipher); | 
| my $cipher_name  = $CRYPTS[ $cipher ]{name}; |  | 
|  |  | 
| my $decrypted; | my $decrypted; | 
|  |  | 
| if ($cipher_name eq 'None') { | if ($c->{name} eq 'None') { | 
| # do nothing | # do nothing | 
| $decrypted = $encrypted; | $decrypted = $encrypted; | 
|  |  | 
| } elsif ($cipher_name eq 'DES_EDE3' or $cipher_name eq 'Rijndael') { | } elsif ($c->{name} eq 'DES_EDE3' or $c->{name} eq 'Rijndael') { | 
| my $c = Crypt::CBC->new( | require Crypt::CBC; | 
| -literal_key => 1, | my $cbc = Crypt::CBC->new( | 
| -key         => $key, | -key         => $key, | 
|  | -literal_key => 1, | 
| -iv          => $ivec, | -iv          => $ivec, | 
| -cipher      => $cipher_name, | -cipher      => $c->{name}, | 
| -keysize     => $keylen, | -keysize     => $c->{keylen}, | 
|  | -blocksize   => $c->{blocksize}, | 
| -header      => 'none', | -header      => 'none', | 
| -padding     => 'oneandzeroes', | -padding     => 'oneandzeroes', | 
| ); | ); | 
|  |  | 
| if (! $c) { | if (! $c) { | 
| croak("Unable to set up encryption!"); | croak("Unable to set up encryption!"); | 
| } | } | 
| $encrypted .= $NULL x $keylen; # pad out a keylen | my $len = $c->{blocksize} - length($encrypted) % $c->{blocksize}; | 
| $decrypted  = $c->decrypt($encrypted); | $encrypted .= $NULL x $len; | 
|  | $decrypted  = $cbc->decrypt($encrypted); | 
|  |  | 
| } else { | } else { | 
| die "Unsupported Version"; | die "Unsupported Version"; | 
|  |  | 
| } | } | 
| } | } | 
| } elsif ($self->{version} == 5) { | } elsif ($self->{version} == 5) { | 
| return _password_verify_v5($pass, $self->{appinfo}); | return _password_verify_v5($self->{appinfo}, $pass); | 
| } else { | } else { | 
| # XXX unsupported version | # XXX unsupported version | 
| } | } | 
|  |  | 
|  |  | 
| sub _password_verify_v4 | sub _password_verify_v4 | 
| { | { | 
|  | require Digest::MD5; | 
|  | import Digest::MD5 qw(md5); | 
|  |  | 
| my $pass = shift; | my $pass = shift; | 
| my $data = shift; | my $data = shift; | 
|  |  | 
|  |  | 
|  |  | 
| my $digest = md5($msg); | my $digest = md5($msg); | 
|  |  | 
| if (! $data eq $salt . $digest ) { | if ($data ne $salt . $digest ) { | 
| return; | return; | 
| } | } | 
|  |  | 
|  |  | 
|  |  | 
| sub _password_verify_v5 | sub _password_verify_v5 | 
| { | { | 
| my $pass    = shift; |  | 
| my $appinfo = shift; | my $appinfo = shift; | 
|  | my $pass    = shift; | 
|  |  | 
| my $salt = pack("H*", $appinfo->{salt}); | my $salt = pack("H*", $appinfo->{salt}); | 
|  |  | 
|  | my $c = crypts($appinfo->{cipher}) | 
|  | or croak('Unknown cipher ' . $appinfo->{cipher}); | 
| my ($key, $hash) = _calc_key_v5( | my ($key, $hash) = _calc_key_v5( | 
| $pass, $salt, $appinfo->{iter}, | $pass, $salt, $appinfo->{iter}, | 
| $CRYPTS[ $appinfo->{cipher} ]{keylen}, | $c->{keylen}, | 
| $CRYPTS[ $appinfo->{cipher} ]{DES_odd_parity}, | $c->{DES_odd_parity}, | 
| ); | ); | 
|  |  | 
|  | #print "Iter: '" . $appinfo->{iter} . "'\n"; | 
| #print "Key:  '". unpack("H*", $key) . "'\n"; | #print "Key:  '". unpack("H*", $key) . "'\n"; | 
|  | #print "Salt: '". unpack("H*", $salt) . "'\n"; | 
| #print "Hash: '". $hash . "'\n"; | #print "Hash: '". $hash . "'\n"; | 
| #print "Hash: '". $appinfo->{masterhash} . "'\n"; | #print "Hash: '". $appinfo->{masterhash} . "'\n"; | 
|  |  | 
|  |  | 
|  |  | 
| sub _password_update_v4 | sub _password_update_v4 | 
| { | { | 
|  | require Digest::MD5; | 
|  | import Digest::MD5 qw(md5); | 
|  |  | 
| my $pass = shift; | my $pass = shift; | 
|  |  | 
| if (! defined $pass) { croak('No password specified!'); }; | if (! defined $pass) { croak('No password specified!'); }; | 
|  |  | 
| my $length  = 8; | my $length  = 8; | 
| my $salt    = shift || pack("C*",map {rand(256)} 1..$length); | my $salt    = shift || pack("C*",map {rand(256)} 1..$length); | 
|  |  | 
|  | my $c = crypts($cipher) or croak('Unknown cipher ' . $cipher); | 
| my ($key, $hash) = _calc_key_v5( | my ($key, $hash) = _calc_key_v5( | 
| $pass, $salt, $iter, | $pass, $salt, $iter, | 
| $CRYPTS[ $cipher ]->{keylen}, | $c->{keylen}, | 
| $CRYPTS[ $cipher ]->{DES_odd_parity}, | $c->{DES_odd_parity}, | 
| ); | ); | 
|  |  | 
| $appinfo->{salt}           = unpack "H*", $salt; | $appinfo->{salt}           = unpack "H*", $salt; | 
| $appinfo->{iter}           = $iter; | $appinfo->{iter}           = $iter; | 
| $appinfo->{cipher}         = $cipher; | $appinfo->{cipher}         = $cipher; | 
|  |  | 
| $appinfo->{key}            = $key; |  | 
| $appinfo->{masterhash}     = $hash; | $appinfo->{masterhash}     = $hash; | 
|  | $appinfo->{key}            = $key; | 
|  |  | 
| return $key; | return $key; | 
| } | } | 
|  |  | 
|  | # Helpers | 
|  |  | 
| sub _calc_keys | sub _calc_keys | 
| { | { | 
|  |  | 
| { | { | 
| my ($pass, $salt, $iter, $keylen, $dop) = @_; | my ($pass, $salt, $iter, $keylen, $dop) = @_; | 
|  |  | 
|  | require Digest::HMAC_SHA1; | 
|  | import  Digest::HMAC_SHA1 qw(hmac_sha1); | 
|  | require Digest::SHA1; | 
|  | import  Digest::SHA1 qw(sha1); | 
|  |  | 
| my $key = _pbkdf2( $pass, $salt, $iter, $keylen, \&hmac_sha1 ); | my $key = _pbkdf2( $pass, $salt, $iter, $keylen, \&hmac_sha1 ); | 
| if ($dop) { $key = DES_odd_parity($key); } | if ($dop) { $key = DES_odd_parity($key); } | 
|  |  | 
|  |  | 
|  |  | 
| sub _crypt3des | sub _crypt3des | 
| { | { | 
|  | require Crypt::DES; | 
|  |  | 
| my ( $plaintext, $passphrase, $flag ) = @_; | my ( $plaintext, $passphrase, $flag ) = @_; | 
|  |  | 
| $passphrase   .= $SPACE x ( 16 * 3 ); | $passphrase   .= $SPACE x ( 16 * 3 ); | 
|  |  | 
| $labels[3]   = 'lastchange'; | $labels[3]   = 'lastchange'; | 
| $labels[255] = 'notes'; | $labels[255] = 'notes'; | 
|  |  | 
| my ($len) = unpack "S1", $field; | my ($len) = unpack "n1", $field; | 
| if ($len + 4 > length $field) { | if ($len + 4 > length $field) { | 
| return undef, $field; | return undef, $field; | 
| } | } | 
| my $unpackstr = "S1 C1 C1 A$len"; | my $unpackstr = "x2 C1 C1 A$len"; | 
| if ($len % 2 && $len + 4 < length $field) { | my $offset    =   2 +1 +1 +$len; | 
|  | if ($len % 2) { # && $len + 4 < length $field) { | 
| # trim the 0/1 byte padding for next even address. | # trim the 0/1 byte padding for next even address. | 
|  | $offset++; | 
| $unpackstr .= ' x' | $unpackstr .= ' x' | 
| } | } | 
| $unpackstr .= ' A*'; |  | 
|  |  | 
| my (undef, $label, $font, $data, $leftover) | my ($label, $font, $data) = unpack $unpackstr, $field; | 
| = unpack $unpackstr, $field; | my $leftover = substr $field, $offset; | 
|  |  | 
| if ($label == 3) { | if ($label && $label == 3) { | 
| $data = _parse_keyring_date($data); | $data = _parse_keyring_date($data); | 
| } | } | 
| return { | return { | 
|  |  | 
| notes      => 255, | notes      => 255, | 
| ); | ); | 
|  |  | 
| my $label = $field->{label_id} || $labels{ $field->{label} }; | my $packed; | 
| my $font  = $field->{font}     || 0; | if (defined $field) { | 
| my $data  = $field->{data}     || ''; | my $label = $field->{label_id} || 0; | 
|  | if (defined $field->{label} && ! $label) { | 
|  | $label = $labels{ $field->{label} }; | 
|  | } | 
|  | my $font  = $field->{font} || 0; | 
|  | my $data  = defined $field->{data} ? $field->{data} : $EMPTY; | 
|  |  | 
| if ($label == 3) { | if ($label && $label == 3) { | 
| $data = _pack_keyring_date($data); | $data = _pack_keyring_date($data); | 
| } | } | 
| my $len = length $data; | my $len = length $data; | 
| my $packstr = "S1 C1 C1 A*"; | my $packstr = "n1 C1 C1 A*"; | 
|  |  | 
| my $packed = pack $packstr, ($len, $label, $font, $data); | $packed = pack $packstr, ($len, $label, $font, $data); | 
|  |  | 
| if ($len % 2) { | if ($len % 2) { | 
| # add byte padding for next even address. | # add byte padding for next even address. | 
| $packed .= $NULL; | $packed .= $NULL; | 
|  | } | 
|  | } else { | 
|  | my $packstr = "n1 C1 C1 x1"; | 
|  | $packed = pack $packstr, 0, 0, 0; | 
| } | } | 
|  |  | 
| return $packed; | return $packed; | 
|  |  | 
| 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. | It currently supports the v4 Keyring databases as well as | 
| The pre-release v5 databases are mostly supported.  There are definitely some | the pre-release v5 databases.  I am not completely happy with the interface | 
| bugs,  For example, t/keyring5.t sometimes fails.  I am not sure why yet. | for accessing the v5 database, so any suggestions on improvements on | 
|  | the interface are appreciated. | 
|  |  | 
| This module doesn't store the decrypted content.  It only keeps it until it | This module doesn't store the decrypted content.  It only keeps it until it | 
| returns it to you or encrypts it. | returns it to you or encrypts it. | 
|  |  | 
| next if $_ == 0 && $pdb->{version} == 4; | next if $_ == 0 && $pdb->{version} == 4; | 
| my $rec  = $pdb->{records}->[$_]; | my $rec  = $pdb->{records}->[$_]; | 
| my $acct = $pdb->Decrypt($rec, $pass); | my $acct = $pdb->Decrypt($rec, $pass); | 
| print $rec->{name}, ' - ', $acct->{account}, "\n"; | print $rec->{name}, ' - '; | 
|  | if ($pdb->{version} == 4 || $pdb->{options}->{v4compatible}) { | 
|  | print ' - ', $acct->{account}; | 
|  | } else { | 
|  | foreach my $a (@{ $acct }) { | 
|  | if ($a->{label} eq 'account') { | 
|  | print ' - ',  $a->{data}; | 
|  | last; | 
|  | } | 
|  | } | 
|  | } | 
|  | print "\n"; | 
| } | } | 
|  |  | 
| =head1 SUBROUTINES/METHODS | =head1 SUBROUTINES/METHODS | 
|  |  | 
| $pdb = new Palm::Keyring({ key1 => value1,  key2 => value2 }); | $pdb = new Palm::Keyring({ key1 => value1,  key2 => value2 }); | 
| $pdb = new Palm::Keyring( -key1 => value1, -key2 => value2); | $pdb = new Palm::Keyring( -key1 => value1, -key2 => value2); | 
|  |  | 
| =head3 Supported options are: | =over | 
|  |  | 
|  | =item Supported options | 
|  |  | 
| =over | =over | 
|  |  | 
| =item password | =item password | 
|  |  | 
|  |  | 
| =item cipher | =item cipher | 
|  |  | 
| The cipher to use.  0, 1, 2 or 3. | The cipher to use.  Either the number or the name. | 
|  |  | 
| 0 => None | 0 => None | 
| 1 => DES_EDE3 | 1 => DES_EDE3 | 
|  |  | 
|  |  | 
| The number of iterations to encrypt with. | The number of iterations to encrypt with. | 
|  |  | 
|  | =item options | 
|  |  | 
|  | A hashref of the options that are set | 
|  |  | 
| =back | =back | 
|  |  | 
|  | =back | 
|  |  | 
|  | For v5 databases there are some additional appinfo fields set. | 
|  |  | 
|  | $pdb->{appinfo} = { | 
|  | # normal appinfo stuff described in L<Palm::StdAppInfo> | 
|  | cipher     => The index number of the cipher being used | 
|  | iter       => Number of iterations for the cipher | 
|  | }; | 
|  |  | 
|  | =head2 crypt | 
|  |  | 
|  | Pass in the alias of the crypt to use, or the index. | 
|  |  | 
|  | These only make sense for v5 databases. | 
|  |  | 
|  | This is a function, not a method. | 
|  | $cipher can be 0, 1, 2, 3, None, DES_EDE3, AES128 or AES256. | 
|  |  | 
|  | my $c = Palm::Keyring::crypt($cipher); | 
|  |  | 
|  | $c is now: | 
|  |  | 
|  | $c = { | 
|  | alias     => (None|DES_EDE3|AES128|AES256), | 
|  | name      => (None|DES_EDE3|Rijndael), | 
|  | keylen    => <key length of the ciphe>, | 
|  | blocksize => <block size of the cipher>, | 
|  | default_iter => <default iterations for the cipher>, | 
|  | }; | 
|  |  | 
| =head2 Encrypt | =head2 Encrypt | 
|  |  | 
| $pdb->Encrypt($rec, $acct[, $password]); | $pdb->Encrypt($rec, $acct[, $password[, $ivec]]); | 
|  |  | 
| Encrypts an account into a record, either with the password previously | Encrypts an account into a record, either with the password previously | 
| used, or with a password that is passed. | used, or with a password that is passed. | 
|  |  | 
|  | $ivec is the initialization vector to use to encrypt the record.  This is | 
|  | not used by v4 databases.  Normally this is not passed and is generated | 
|  | randomly. | 
|  |  | 
| $rec is a record from $pdb->{records} or a new_Record(). | $rec is a record from $pdb->{records} or a new_Record(). | 
| The v4 $acct is a hashref in the format below. | The v4 $acct is a hashref in the format below. | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
| The account name is stored in $rec->{name} for both v4 and v5 databases. | The account name is stored in $rec->{name} for both v4 and v5 databases. | 
| It is not returned in the decrypted information for v5. | It is not returned in the decrypted information for v5. | 
|  |  | 
| $rec->{name} = 'account name'; | $rec->{name} = 'account name'; | 
|  |  | 
|  |  | 
| the password. | the password. | 
|  |  | 
| If nothing is passed, it forgets the password that it was remembering. | If nothing is passed, it forgets the password that it was remembering. | 
|  |  | 
|  | After a successful password verification the following fields are set | 
|  |  | 
|  | For v4 | 
|  |  | 
|  | $pdb->{digest}   = the calculated digest used from the key; | 
|  | $pdb->{password} = the password that was passed in; | 
|  |  | 
|  | For v5 | 
|  |  | 
|  | $pdb->{appinfo} = { | 
|  | # As described under new() with these additional fields | 
|  | cipher     => The index number of the cipher being used | 
|  | iter       => Number of iterations for the cipher | 
|  | key        => The key that is calculated from the password | 
|  | and salt and is used to decrypt the records. | 
|  | masterhash => the hash of the key that is stored in the | 
|  | database.  Either set when Loading the database | 
|  | or when setting a new password. | 
|  | salt       => the salt that is either read out of the database | 
|  | or calculated when setting a new password. | 
|  | }; | 
|  |  | 
| =head1 DEPENDENCIES | =head1 DEPENDENCIES | 
|  |  |