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

Annotation of palm/Palm-Keyring/lib/Palm/Keyring.pm, Revision 1.15

1.14      andrew      1: package Palm::Keyring;
                      2:
1.15    ! andrew      3: # $RedRiver: Keyring.pm,v 1.14 2007/01/28 22:24:17 andrew Exp $
1.1       andrew      4: #
1.14      andrew      5: # Perl class for dealing with Keyring for Palm OS databases.
1.1       andrew      6: #
                      7: #   This started as Memo.pm, I just made it work for Keyring.
                      8:
                      9: use strict;
1.14      andrew     10: use warnings;
                     11: use Carp;
                     12:
                     13: use base qw/ Palm::StdAppInfo /;
1.1       andrew     14:
                     15: use Digest::MD5 qw(md5);
1.2       andrew     16: use Crypt::DES;
1.14      andrew     17: use Readonly;
                     18:
                     19: Readonly my $ENCRYPT    => 1;
                     20: Readonly my $DECRYPT    => 0;
                     21: Readonly my $MD5_CBLOCK => 64;
                     22: Readonly my $kSalt_Size => 4;
                     23: Readonly my $EMPTY      => q{};
                     24: Readonly my $SPACE      => q{ };
                     25: Readonly my $NULL       => chr 0;
                     26:
                     27: # One liner, to allow MakeMaker to work.
1.15    ! andrew     28: our ($VERSION) = q$Revision: 1.14 $ =~ m{ Revision: \s+ (\S+) }xm;
1.14      andrew     29:
                     30: #@ISA = qw( Palm::StdAppInfo Palm::Raw );
1.1       andrew     31:
1.14      andrew     32: sub new {
                     33:     my $classname = shift;
                     34:     my $pass      = shift;
1.1       andrew     35:
1.14      andrew     36:     # Create a generic PDB. No need to rebless it, though.
                     37:     my $self = $classname->SUPER::new(@_);
1.1       andrew     38:
1.14      andrew     39:     $self->{'name'}    = 'Keys-Gtkr';    # Default
                     40:     $self->{'creator'} = 'Gtkr';
                     41:     $self->{'type'}    = 'Gkyr';
                     42:
                     43:     # The PDB is not a resource database by
                     44:     # default, but it's worth emphasizing,
                     45:     # since MemoDB is explicitly not a PRC.
                     46:     $self->{'attributes'}{'resource'} = 0;
1.1       andrew     47:
1.14      andrew     48:     # Initialize the AppInfo block
                     49:     $self->{'appinfo'} = {};
1.1       andrew     50:
1.14      andrew     51:     # Add the standard AppInfo block stuff
                     52:     Palm::StdAppInfo::seed_StdAppInfo( $self->{'appinfo'} );
1.1       andrew     53:
1.14      andrew     54:     # Set the version
                     55:     $self->{'version'} = 4;
1.1       andrew     56:
1.14      andrew     57:     # Give the PDB the first record that will hold the encrypted password
                     58:     $self->{'records'} = [ $self->new_Record ];
1.1       andrew     59:
1.14      andrew     60:     if ( defined $pass ) {
                     61:         $self->Encrypt($pass);
                     62:     }
1.1       andrew     63:
1.14      andrew     64:     return $self;
                     65: }
1.1       andrew     66:
1.14      andrew     67: sub import {
                     68:     Palm::PDB::RegisterPDBHandlers( __PACKAGE__, [ 'Gtkr', 'Gkyr' ], );
                     69:     return 1;
                     70: }
1.1       andrew     71:
1.14      andrew     72: sub Load {
                     73:     my $self     = shift;
                     74:     my $filename = shift;
                     75:     my $password = shift;
                     76:
                     77:     $self->{'appinfo'} = {};
                     78:     $self->{'records'} = [];
                     79:     $self->SUPER::Load($filename);
                     80:
                     81:     foreach my $rec ( @{ $self->{'records'} } ) {
                     82:         if ( ! exists $rec->{'data'}) { next; };
                     83:         my ( $name, $encrypted ) = split /$NULL/xm, $rec->{'data'}, 2;
                     84:         if ( ! $encrypted ) { next };
                     85:         $rec->{'plaintext'}->{'name'} = $name;
                     86:         $rec->{'encrypted'} = $encrypted;
                     87:     }
1.1       andrew     88:
1.14      andrew     89:     return $self->Decrypt($password) if defined $password;
1.12      andrew     90:
1.14      andrew     91:     return 1;
                     92: }
1.11      andrew     93:
1.14      andrew     94: sub Write {
                     95:     my $self     = shift;
                     96:     my $filename = shift;
                     97:     my $password = shift;
1.1       andrew     98:
1.14      andrew     99:     $self->Encrypt($password) || return;
                    100:     return $self->SUPER::Write($filename);
                    101: }
1.1       andrew    102:
1.14      andrew    103: sub Encrypt {
                    104:     my $self = shift;
                    105:     my $pass = shift;
                    106:
                    107:     if ($pass) {
                    108:         if (
                    109:             !( exists $self->{'records'}->[0]->{'data'}
                    110:                 && $self->_keyring_verify($pass) )
                    111:           )
                    112:         {
                    113:
                    114:             # This would encrypt with a new password.
                    115:             # First decrypting everything with the old password of course.
                    116:             $self->_keyring_update($pass) || return;
                    117:             $self->_keyring_verify($pass) || return;
                    118:         }
                    119:     }
                    120:
                    121:     $self->{'digest'} ||= _calc_keys( $self->{'password'} );
                    122:
                    123:     foreach my $rec ( @{ $self->{'records'} } ) {
                    124:         if (!defined $rec->{'plaintext'}) { next; };
                    125:
                    126:         my $name =
                    127:           defined $rec->{'plaintext'}->{'name'}
                    128:           ? $rec->{'plaintext'}->{'name'}
                    129:           : $EMPTY;
                    130:         my $account =
                    131:           defined $rec->{'plaintext'}->{'account'}
                    132:           ? $rec->{'plaintext'}->{'account'}
                    133:           : $EMPTY;
                    134:         my $password =
                    135:           defined $rec->{'plaintext'}->{'password'}
                    136:           ? $rec->{'plaintext'}->{'password'}
                    137:           : $EMPTY;
                    138:         my $description =
                    139:           defined $rec->{'plaintext'}->{'description'}
                    140:           ? $rec->{'plaintext'}->{'description'}
                    141:           : $EMPTY;
                    142:         my $extra = $EMPTY;
1.1       andrew    143:
1.14      andrew    144:         my $plaintext = join "$NULL", $account, $password, $description, $extra;
1.1       andrew    145:
1.14      andrew    146:         my $encrypted = _crypt3des( $plaintext, $self->{'digest'}, $ENCRYPT );
1.11      andrew    147:
1.14      andrew    148:         $rec->{'data'} = join "$NULL", $name, $encrypted;
                    149:     }
1.1       andrew    150:
1.14      andrew    151:     return 1;
                    152: }
1.1       andrew    153:
1.14      andrew    154: sub Decrypt {
                    155:     my $self = shift;
                    156:     my $pass = shift;
                    157:
                    158:     if ($pass) {
                    159:         $self->_keyring_verify($pass) || return;
                    160:     }
                    161:
                    162:     $self->{'digest'} ||= _calc_keys( $self->{'password'} );
                    163:
                    164:     my $reccount = 0;
                    165:     foreach my $rec ( @{ $self->{'records'} } ) {
                    166:         $reccount++;
                    167:
                    168:         # always skip the first record that has the password in it.
                    169:         next if $reccount <= 1;
                    170:         if ( ! defined $rec->{'data'} ) {
                    171:             warn 'Invalid record ' . ( $reccount - 1 ) . "\n";
                    172:             next;
                    173:         }
                    174:
                    175:         my ( $name, $encrypted ) = split /$NULL/xm, $rec->{'data'}, 2;
                    176:         if (! $encrypted) { next; };
                    177:
                    178:         $rec->{'plaintext'}->{'name'} = $name;
                    179:
                    180:         my $decrypted = _crypt3des( $encrypted, $self->{'digest'}, $DECRYPT );
                    181:         my ( $account, $password, $description, $extra ) = split /$NULL/xm,
                    182:           $decrypted, 4;
                    183:
                    184:         $rec->{'plaintext'}->{'account'} = defined $account ? $account : $EMPTY;
                    185:         $rec->{'plaintext'}->{'password'} =
                    186:           defined $password ? $password : $EMPTY;
                    187:         $rec->{'plaintext'}->{'description'} =
                    188:           defined $description ? $description : $EMPTY;
                    189:
                    190:         #print "Name:      '$name'\n";
                    191:         #print "Encrypted: '$encrypted' - Length: " . length($encrypted) . "\n";
                    192:         #print "    Hex:   '" . unpack("H*", $encrypted) . "'\n";
                    193:         #print "    Binary:'" . unpack("b*", $encrypted) . "'\n";
                    194:         #print "Decrypted: '$decrypted' - Length: " . length($decrypted) . "\n";
                    195:         #print "    Hex:   '" . unpack("H*", $decrypted) . "'\n";
                    196:         #print "    Binary:'" . unpack("b*", $decrypted) . "'\n";
                    197:         #print "\n";
                    198:         #print "Extra: $extra\n";
                    199:         #exit;
                    200:         #--------------------------------------------------
                    201:         # print "Account:     $account\n";
                    202:         # print "Password:    $password\n";
                    203:         # print "Description: $description\n";
                    204:         #--------------------------------------------------
1.1       andrew    205:
1.14      andrew    206:     }
1.1       andrew    207:
1.14      andrew    208:     return 1;
1.1       andrew    209: }
                    210:
1.14      andrew    211: sub _calc_keys {
                    212:     my $pass = shift;
                    213:     if (! defined $pass) { croak('No password defined!'); };
                    214:
                    215:     my $digest = md5($pass);
                    216:
                    217:     my ( $key1, $key2 ) = unpack 'a8a8', $digest;
                    218:
                    219:     #--------------------------------------------------
                    220:     # print "key1: $key1: ", length $key1, "\n";
                    221:     # print "key2: $key2: ", length $key2, "\n";
                    222:     #--------------------------------------------------
                    223:
                    224:     $digest = unpack 'H*', $key1 . $key2 . $key1;
                    225:
                    226:     #--------------------------------------------------
                    227:     # print "Digest: ", $digest, "\n";
                    228:     # print length $digest, "\n";
                    229:     #--------------------------------------------------
                    230:
                    231:     return $digest;
1.3       andrew    232: }
                    233:
1.14      andrew    234: sub _keyring_verify {
                    235:     my $self = shift;
                    236:     my $pass = shift;
                    237:
                    238:     if (! $pass) { croak('No password specified!'); };
1.11      andrew    239:
1.14      andrew    240:     # AFAIK the thing we use to test the password is
                    241:     #     always in the first entry
                    242:     my $data = $self->{'records'}->[0]->{'data'};
1.11      andrew    243:
1.14      andrew    244:     #die "No encrypted password in file!" unless defined $data;
                    245:     if (! defined $data) { return; };
1.11      andrew    246:
1.14      andrew    247:     $data =~ s/$NULL$//xm;
1.11      andrew    248:
1.14      andrew    249:     my $salt = substr $data, 0, $kSalt_Size;
1.11      andrew    250:
1.14      andrew    251:     my $msg = $salt . $pass;
1.11      andrew    252:
1.14      andrew    253:     $msg .= "\0" x ( $MD5_CBLOCK - length $msg );
1.11      andrew    254:
1.14      andrew    255:     my $digest = md5($msg);
1.11      andrew    256:
1.14      andrew    257:     if ( $data eq $salt . $digest ) {
1.11      andrew    258:
1.14      andrew    259: # May as well generate the keys we need now, since we know the password is right
                    260:         $self->{'digest'} = _calc_keys($pass);
                    261:         if ( $self->{'digest'} ) {
                    262:             $self->{'password'} = $pass;
                    263:             return 1;
                    264:         }
                    265:     }
                    266:     return;
1.6       andrew    267: }
                    268:
1.14      andrew    269: sub _keyring_update {
                    270:
                    271:     # It is very important to Encrypt after calling this
                    272:     #     (Although it is generally only called by Encrypt)
                    273:     # because otherwise the data will be out of sync with the
                    274:     # password, and that would suck!
                    275:     my $self = shift;
                    276:     my $pass = shift;
                    277:
                    278:     if (! $pass) { croak('No password specified!'); };
1.11      andrew    279:
1.14      andrew    280:     # if the database already has a password in it
                    281:     if ( $self->{'records'}->[0]->{'data'} ) {
                    282:
                    283:         # Make sure everything is decrypted before we update the keyring
                    284:         $self->Decrypt() || return;
                    285:     }
                    286:
                    287:     my $salt;
                    288:     for ( 1 .. $kSalt_Size ) {
                    289:         $salt .= chr int rand 255;
                    290:     }
                    291:
                    292:     my $msg = $salt . $pass;
1.11      andrew    293:
1.14      andrew    294:     $msg .= "\0" x ( $MD5_CBLOCK - length $msg );
1.11      andrew    295:
1.14      andrew    296:     my $digest = md5($msg);
1.11      andrew    297:
1.14      andrew    298:     my $data = $salt . $digest;    # . "\0";
1.11      andrew    299:
1.14      andrew    300:     # AFAIK the thing we use to test the password is
                    301:     #     always in the first entry
                    302:     $self->{'records'}->[0]->{'data'} = $data;
1.11      andrew    303:
1.14      andrew    304:     $self->{'password'} = $pass;
                    305:     $self->{'digest'}   = _calc_keys( $self->{'password'} );
1.11      andrew    306:
1.14      andrew    307:     return 1;
1.1       andrew    308: }
                    309:
1.14      andrew    310: sub _crypt3des {
                    311:     my ( $plaintext, $passphrase, $flag ) = @_;
                    312:
                    313:     $passphrase   .= $SPACE x ( 16 * 3 );
                    314:     my $cyphertext = $EMPTY;
                    315:
                    316:     my $size = length $plaintext;
1.11      andrew    317:
1.14      andrew    318:     #print "STRING: '$plaintext' - Length: " . (length $plaintext) . "\n";
                    319:
                    320:     my @C;
                    321:     for ( 0 .. 2 ) {
                    322:         $C[$_] =
                    323:           new Crypt::DES( pack 'H*', ( substr $passphrase, 16 * $_, 16 ));
                    324:     }
                    325:
                    326:     for ( 0 .. ( ($size) / 8 ) ) {
                    327:         my $pt = substr $plaintext, $_ * 8, 8;
                    328:
                    329:         #print "PT: '$pt' - Length: " . length($pt) . "\n";
                    330:         if (! length $pt) { next; };
                    331:         if ( (length $pt) < 8 ) {
                    332:                        if ($flag == $DECRYPT) { croak('record not 8 byte padded'); };
                    333:             my $len = 8 - (length $pt);
                    334:
                    335:             #print "LENGTH: $len\n";
                    336:             #print "Binary:    '" . unpack("b*", $pt) . "'\n";
                    337:             $pt .= ($NULL x $len);
                    338:
                    339:             #print "PT: '$pt' - Length: " . length($pt) . "\n";
                    340:             #print "Binary:    '" . unpack("b*", $pt) . "'\n";
                    341:         }
                    342:         if ( $flag == $ENCRYPT ) {
                    343:             $pt = $C[0]->encrypt($pt);
                    344:             $pt = $C[1]->decrypt($pt);
                    345:             $pt = $C[2]->encrypt($pt);
                    346:         }
                    347:         else {
                    348:             $pt = $C[0]->decrypt($pt);
                    349:             $pt = $C[1]->encrypt($pt);
                    350:             $pt = $C[2]->decrypt($pt);
                    351:         }
                    352:
                    353:         #print "PT: '$pt' - Length: " . length($pt) . "\n";
                    354:         $cyphertext .= $pt;
                    355:     }
                    356:
                    357:     $cyphertext =~ s/$NULL+$//xm;
1.11      andrew    358:
1.14      andrew    359:     #print "CT: '$cyphertext' - Length: " . length($cyphertext) . "\n";
1.11      andrew    360:
1.14      andrew    361:     return $cyphertext;
                    362: }
1.11      andrew    363:
1.14      andrew    364: 1;
                    365: __END__
1.11      andrew    366:
1.14      andrew    367: =head1 NAME
1.11      andrew    368:
1.14      andrew    369: Palm::Keyring - Handler for Palm Keyring databases.
1.1       andrew    370:
1.14      andrew    371: =head1 DESCRIPTION
1.7       andrew    372:
1.14      andrew    373: The Keyring PDB handler is a helper class for the Palm::PDB package. It
                    374: parses Keyring for Palm OS databases.  See
                    375: L<http://gnukeyring.sourceforge.net/>.
1.1       andrew    376:
1.14      andrew    377: It has the standard Palm::PDB methods with 2 additional public methods.
                    378: Decrypt and Encrypt.
1.1       andrew    379:
1.14      andrew    380: It currently supports the v4 Keyring databases.  The v5 databases from the pre-release keyring-2.0 are not supported.
1.1       andrew    381:
1.14      andrew    382: =head1 SYNOPSIS
1.1       andrew    383:
1.14      andrew    384:        use Palm::PDB;
                    385:        use Palm::Keyring;
                    386:        my $pdb = new Palm::PDB;
                    387:        $pdb->Load($file);
                    388:        foreach my $rec (@{ $pdb->{'records'} }) {
                    389:                print "$rec->{'plaintext'}->{'name'}\n";
1.1       andrew    390:        }
1.14      andrew    391:        $pdb->Decrypt($password);
                    392:        # do something with the decrypted parts
1.1       andrew    393:
1.14      andrew    394: =head1 SUBROUTINES/METHODS
1.1       andrew    395:
1.14      andrew    396: =head2 new
1.11      andrew    397:
1.14      andrew    398:        $pdb = new Palm::Keyring([$password]);
1.11      andrew    399:
1.14      andrew    400: Create a new PDB, initialized with the various Palm::Keyring fields
                    401: and an empty record list.
1.11      andrew    402:
1.14      andrew    403: Use this method if you're creating a Keyring PDB from scratch otherwise you
                    404: can just use Palm::PDB::new().
1.11      andrew    405:
1.14      andrew    406: =head2 Load
1.11      andrew    407:
1.14      andrew    408:        $pdb->Load($filename[, $password]);
1.11      andrew    409:
1.14      andrew    410: Overrides the standard Palm::Raw Load() to add
                    411: $rec->{'plaintext'}->{'name'} and
                    412: $rec->{'encrypted'} fields.
                    413: $rec->{'plaintext'}->{'name'} holds the name of the record,
                    414: $rec->{'encrypted'} is the encrypted information in the PDB.
1.1       andrew    415:
1.14      andrew    416: It also takes an additional optional parameter, which is the password to use to
                    417: decrypt the database.
1.1       andrew    418:
1.14      andrew    419: See Decrypt() for the additional fields that are available after decryption.
1.7       andrew    420:
1.14      andrew    421: =head2 Write
1.1       andrew    422:
1.14      andrew    423:        $pdb->Write($filename[, $password]);
1.1       andrew    424:
1.14      andrew    425: Just like the Palm::Raw::Write() but encrypts everything before saving.
1.1       andrew    426:
1.14      andrew    427: Also takes an optional password to encrypt with a new password, not needed
                    428: unless you are changing the password.
1.1       andrew    429:
1.14      andrew    430: =head2 Encrypt
1.1       andrew    431:
1.14      andrew    432:        $pdb->Encrypt([$password]);
1.1       andrew    433:
1.14      andrew    434: Encrypts the PDB, either with the password used to decrypt or create it, or
                    435: optionally with a password that is passed.
1.1       andrew    436:
1.14      andrew    437: See Decrypt() for an what plaintext fields are available to be encrypted.
1.1       andrew    438:
1.14      andrew    439: =head2 Decrypt
1.1       andrew    440:
1.14      andrew    441:        $pdb->Decrypt([$password]);
1.1       andrew    442:
1.14      andrew    443: Decrypts the PDB and fills out the rest of the fields available in
                    444: $rec->{'plaintext'}.
1.1       andrew    445:
1.14      andrew    446: The plaintext should now be this, before encryption or after decryption:
1.1       andrew    447:
1.14      andrew    448:        $rec->{'plaintext'} = {
                    449:                name        => $name,
                    450:                account     => $account,
                    451:                password    => $account_password,
                    452:                description => $description,
                    453:        };
1.1       andrew    454:
1.14      andrew    455: =head1 DEPENDENCIES
1.1       andrew    456:
1.14      andrew    457: Palm::StdAppInfo
1.1       andrew    458:
1.14      andrew    459: Digest::MD5
1.9       andrew    460:
1.14      andrew    461: Crypt::DES
1.4       andrew    462:
1.14      andrew    463: Readonly
1.10      andrew    464:
1.14      andrew    465: =head1 BUGS AND LIMITATIONS
1.1       andrew    466:
1.14      andrew    467: Once this module is uploaded, you can
                    468: Please report any bugs or feature requests to
                    469: C<bug-palm-keyring at rt.cpan.org>, or through the web interface at
                    470: L<http://rt.cpan.org>.  I will be notified, and then you'll automatically be
                    471: notified of progress on your bug as I make changes.
1.1       andrew    472:
                    473: =head1 AUTHOR
                    474:
1.12      andrew    475: Andrew Fresh E<lt>andrew@mad-techies.orgE<gt>
1.1       andrew    476:
1.14      andrew    477: =head1 LICENSE AND COPYRIGHT
                    478:
                    479: Copyright 2004, 2005, 2006, 2007 Andrew Fresh, All Rights Reserved.
                    480:
1.15    ! andrew    481: This program is free software; you can redistribute it and/or
        !           482: modify it under the same terms as Perl itself.
1.14      andrew    483:
1.1       andrew    484: =head1 SEE ALSO
                    485:
                    486: Palm::PDB(3)
                    487:
                    488: Palm::StdAppInfo(3)
1.11      andrew    489:
                    490: The Keyring for Palm OS website:
                    491: L<http://gnukeyring.sourceforge.net/>

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