[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.20

1.14      andrew      1: package Palm::Keyring;
                      2:
1.20    ! andrew      3: # $RedRiver: Keyring.pm,v 1.19 2007/01/31 04:17:15 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.19      andrew     28: our $VERSION = 0.92;
1.1       andrew     29:
1.14      andrew     30: sub new {
                     31:     my $classname = shift;
                     32:     my $pass      = shift;
1.1       andrew     33:
1.14      andrew     34:     # Create a generic PDB. No need to rebless it, though.
                     35:     my $self = $classname->SUPER::new(@_);
1.1       andrew     36:
1.14      andrew     37:     $self->{'name'}    = 'Keys-Gtkr';    # Default
                     38:     $self->{'creator'} = 'Gtkr';
                     39:     $self->{'type'}    = 'Gkyr';
                     40:
                     41:     # The PDB is not a resource database by
                     42:     # default, but it's worth emphasizing,
                     43:     # since MemoDB is explicitly not a PRC.
                     44:     $self->{'attributes'}{'resource'} = 0;
1.1       andrew     45:
1.14      andrew     46:     # Initialize the AppInfo block
                     47:     $self->{'appinfo'} = {};
1.1       andrew     48:
1.14      andrew     49:     # Add the standard AppInfo block stuff
                     50:     Palm::StdAppInfo::seed_StdAppInfo( $self->{'appinfo'} );
1.1       andrew     51:
1.14      andrew     52:     # Set the version
                     53:     $self->{'version'} = 4;
1.1       andrew     54:
1.14      andrew     55:     if ( defined $pass ) {
1.16      andrew     56:         $self->Password($pass);
1.14      andrew     57:     }
1.1       andrew     58:
1.14      andrew     59:     return $self;
                     60: }
1.1       andrew     61:
1.14      andrew     62: sub import {
                     63:     Palm::PDB::RegisterPDBHandlers( __PACKAGE__, [ 'Gtkr', 'Gkyr' ], );
                     64:     return 1;
                     65: }
1.1       andrew     66:
1.16      andrew     67: sub ParseRecord {
1.14      andrew     68:     my $self     = shift;
                     69:
1.16      andrew     70:     my $rec = $self->SUPER::ParseRecord(@_);
                     71:
                     72:     # skip the 0 record that holds the password
                     73:     return $rec if ! exists $self->{'records'};
                     74:     return $rec if ! exists $rec->{'data'};
1.14      andrew     75:
1.16      andrew     76:     my ( $name, $encrypted ) = split /$NULL/xm, $rec->{'data'}, 2;
1.1       andrew     77:
1.16      andrew     78:     return $rec if ! $encrypted;
1.19      andrew     79:     delete $rec->{'data'};
                     80:     $rec->{'name'} = $name;
1.16      andrew     81:     $rec->{'encrypted'} = $encrypted;
1.12      andrew     82:
1.16      andrew     83:     return $rec;
1.14      andrew     84: }
1.11      andrew     85:
1.16      andrew     86: sub PackRecord {
                     87:     my $self = shift;
                     88:     my $rec  = shift;
                     89:
                     90:     my $rec0_id = $self->{'records'}->[0]->{'id'};
                     91:
                     92:     if ($rec->{'encrypted'} && ! $rec->{'id'} == $rec0_id) {
1.19      andrew     93:         $rec->{'data'} = join $NULL, $rec->{'name'}, $rec->{'encrypted'};
                     94:         delete $rec->{'name'};
1.16      andrew     95:         delete $rec->{'encrypted'};
                     96:     }
1.1       andrew     97:
1.16      andrew     98:     return $self->SUPER::PackRecord($rec, @_);
1.14      andrew     99: }
1.1       andrew    100:
1.14      andrew    101: sub Encrypt {
                    102:     my $self = shift;
1.16      andrew    103:     my $rec  = shift;
                    104:     my $data = shift;
                    105:     my $pass = shift || $self->{'password'};
                    106:
                    107:     if ( ! $pass) {
                    108:         croak("'password' not set!\n");
                    109:     }
                    110:
                    111:     if ( ! $rec) {
                    112:         croak("Needed parameter 'record' not passed!\n");
                    113:     }
1.14      andrew    114:
1.16      andrew    115:     if ( ! $data) {
                    116:         croak("Needed parameter 'data' not passed!\n");
1.14      andrew    117:     }
                    118:
1.16      andrew    119:     if ( ! $self->Password($pass)) {
                    120:         croak("Incorrect Password!\n");
                    121:     }
1.14      andrew    122:
1.16      andrew    123:     $self->{'digest'} ||= _calc_keys( $pass );
1.14      andrew    124:
1.16      andrew    125:     $data->{'account'}  ||= $EMPTY;
                    126:     $data->{'password'} ||= $EMPTY;
                    127:     $data->{'notes'}    ||= $EMPTY;
1.1       andrew    128:
1.19      andrew    129:     my %Modified;
                    130:     my ($day, $month, $year) = (localtime)[3,4,5];
                    131:     $year -= 4;
                    132:     $month++;
                    133:
                    134:     my $p = $day | ($month << 5) | ($year << 9);
                    135:     my $packeddate = pack 'n', $p;
                    136:
1.16      andrew    137:     my $plaintext = join $NULL,
1.19      andrew    138:         $data->{'account'}, $data->{'password'}, $data->{'notes'}, $packeddate;
1.1       andrew    139:
1.16      andrew    140:     my $encrypted = _crypt3des( $plaintext, $self->{'digest'}, $ENCRYPT );
1.11      andrew    141:
1.16      andrew    142:     return if ! $encrypted;
1.1       andrew    143:
1.19      andrew    144:     $rec->{'attributes'}{'Dirty'} = 1;
                    145:     $rec->{'attributes'}{'dirty'} = 1;
                    146:     $rec->{'name'}    ||= $data->{'name'};
1.16      andrew    147:     $rec->{'encrypted'} = $encrypted;
1.19      andrew    148:
1.14      andrew    149:     return 1;
                    150: }
1.1       andrew    151:
1.14      andrew    152: sub Decrypt {
                    153:     my $self = shift;
1.16      andrew    154:     my $rec  = shift;
                    155:     my $pass = shift || $self->{'password'};
                    156:
                    157:     if ( ! $pass) {
                    158:         croak("'password' not set!\n");
                    159:     }
                    160:
                    161:     if ( ! $rec) {
1.19      andrew    162:         croak("Needed parameter 'record' not passed!\n");
1.16      andrew    163:     }
1.14      andrew    164:
1.16      andrew    165:     if ( ! $self->Password($pass)) {
                    166:         croak("Invalid Password!\n");
1.14      andrew    167:     }
                    168:
1.16      andrew    169:     if ( ! $rec->{'encrypted'} ) {
                    170:         croak("No encrypted content!");
                    171:     }
1.14      andrew    172:
1.16      andrew    173:     $self->{'digest'} ||= _calc_keys( $pass );
1.14      andrew    174:
1.16      andrew    175:     my $decrypted =
                    176:         _crypt3des( $rec->{'encrypted'}, $self->{'digest'}, $DECRYPT );
1.19      andrew    177:     my ( $account, $password, $notes, $packeddate ) = split /$NULL/xm,
1.16      andrew    178:           $decrypted, 4;
1.14      andrew    179:
1.19      andrew    180:     my %Modified;
                    181:     if ($packeddate) {
                    182:         my $u = unpack 'n', $packeddate;
                    183:         my $year  = (($u & 0xFE00) >> 9) + 4; # since 1900
                    184:         my $month = (($u & 0x01E0) >> 5) - 1; # 0-11
                    185:         my $day   = (($u & 0x001F) >> 0);     # 1-31
                    186:
                    187:         %Modified = (
                    188:             year   => $year,
                    189:             month  => $month || 0,
                    190:             day    => $day   || 1,
                    191:         );
                    192:     }
                    193:
1.16      andrew    194:     return {
1.20    ! andrew    195:         name       => $rec->{'name'},
        !           196:         account    => $account,
        !           197:         password   => $password,
        !           198:         notes      => $notes,
        !           199:         lastchange => \%Modified,
1.16      andrew    200:     };
                    201: }
1.14      andrew    202:
1.16      andrew    203: sub Password {
                    204:     my $self = shift;
                    205:     my $pass = shift || $self->{'password'};
                    206:     my $new_pass = shift;
1.14      andrew    207:
1.16      andrew    208:     if (! exists $self->{'records'}) {
                    209:         # Give the PDB the first record that will hold the encrypted password
                    210:         $self->{'records'} = [ $self->new_Record ];
                    211:
                    212:         return $self->_password_update($pass);
                    213:     }
                    214:
                    215:     if ($new_pass) {
                    216:         my @accts = ();
                    217:         foreach my $i (0..$#{ $self->{'records'} }) {
                    218:             if ($i == 0) {
                    219:                 push @accts, undef;
                    220:                 next;
                    221:             }
                    222:             my $acct = $self->Decrypt($self->{'records'}->[$i], $pass);
                    223:             if ( ! $acct ) {
1.19      andrew    224:                 croak("Couldn't decrypt $self->{'records'}->[$i]->{'name'}");
1.16      andrew    225:             }
                    226:             push @accts, $acct;
                    227:         }
1.14      andrew    228:
1.16      andrew    229:         if ( ! $self->_password_update($new_pass)) {
                    230:             croak("Couldn't set new password!");
                    231:         }
                    232:         $pass = $new_pass;
1.1       andrew    233:
1.16      andrew    234:         foreach my $i (0..$#accts) {
                    235:             next if $i == 0;
                    236:             $self->Encrypt($self->{'records'}->[$i], $accts[$i], $pass);
                    237:         }
1.14      andrew    238:     }
1.1       andrew    239:
1.16      andrew    240:     return $self->_password_verify($pass);
1.1       andrew    241: }
                    242:
1.14      andrew    243: sub _calc_keys {
                    244:     my $pass = shift;
                    245:     if (! defined $pass) { croak('No password defined!'); };
                    246:
                    247:     my $digest = md5($pass);
                    248:
                    249:     my ( $key1, $key2 ) = unpack 'a8a8', $digest;
                    250:
                    251:     #--------------------------------------------------
                    252:     # print "key1: $key1: ", length $key1, "\n";
                    253:     # print "key2: $key2: ", length $key2, "\n";
                    254:     #--------------------------------------------------
                    255:
                    256:     $digest = unpack 'H*', $key1 . $key2 . $key1;
                    257:
                    258:     #--------------------------------------------------
                    259:     # print "Digest: ", $digest, "\n";
                    260:     # print length $digest, "\n";
                    261:     #--------------------------------------------------
                    262:
                    263:     return $digest;
1.3       andrew    264: }
                    265:
1.16      andrew    266: sub _password_verify {
1.14      andrew    267:     my $self = shift;
                    268:     my $pass = shift;
                    269:
                    270:     if (! $pass) { croak('No password specified!'); };
1.11      andrew    271:
1.16      andrew    272:     if (defined $self->{'password'} && $pass eq $self->{'password'}) {
                    273:         # already verified this password
                    274:         return 1;
                    275:     }
                    276:
1.14      andrew    277:     # AFAIK the thing we use to test the password is
                    278:     #     always in the first entry
                    279:     my $data = $self->{'records'}->[0]->{'data'};
1.11      andrew    280:
1.14      andrew    281:     #die "No encrypted password in file!" unless defined $data;
1.16      andrew    282:     if ( ! defined $data) { return; };
1.11      andrew    283:
1.14      andrew    284:     $data =~ s/$NULL$//xm;
1.11      andrew    285:
1.14      andrew    286:     my $salt = substr $data, 0, $kSalt_Size;
1.11      andrew    287:
1.14      andrew    288:     my $msg = $salt . $pass;
1.11      andrew    289:
1.14      andrew    290:     $msg .= "\0" x ( $MD5_CBLOCK - length $msg );
1.11      andrew    291:
1.14      andrew    292:     my $digest = md5($msg);
1.11      andrew    293:
1.14      andrew    294:     if ( $data eq $salt . $digest ) {
1.11      andrew    295:
1.14      andrew    296: # May as well generate the keys we need now, since we know the password is right
                    297:         $self->{'digest'} = _calc_keys($pass);
                    298:         if ( $self->{'digest'} ) {
                    299:             $self->{'password'} = $pass;
                    300:             return 1;
                    301:         }
                    302:     }
                    303:     return;
1.6       andrew    304: }
                    305:
1.16      andrew    306: sub _password_update {
1.14      andrew    307:
                    308:     # It is very important to Encrypt after calling this
                    309:     #     (Although it is generally only called by Encrypt)
                    310:     # because otherwise the data will be out of sync with the
                    311:     # password, and that would suck!
                    312:     my $self = shift;
                    313:     my $pass = shift;
                    314:
1.16      andrew    315:     if (! defined $pass) { croak('No password specified!'); };
1.14      andrew    316:
                    317:     my $salt;
                    318:     for ( 1 .. $kSalt_Size ) {
                    319:         $salt .= chr int rand 255;
                    320:     }
                    321:
                    322:     my $msg = $salt . $pass;
1.11      andrew    323:
1.14      andrew    324:     $msg .= "\0" x ( $MD5_CBLOCK - length $msg );
1.11      andrew    325:
1.14      andrew    326:     my $digest = md5($msg);
1.11      andrew    327:
1.14      andrew    328:     my $data = $salt . $digest;    # . "\0";
1.11      andrew    329:
1.14      andrew    330:     # AFAIK the thing we use to test the password is
                    331:     #     always in the first entry
                    332:     $self->{'records'}->[0]->{'data'} = $data;
1.11      andrew    333:
1.14      andrew    334:     $self->{'password'} = $pass;
                    335:     $self->{'digest'}   = _calc_keys( $self->{'password'} );
1.11      andrew    336:
1.14      andrew    337:     return 1;
1.1       andrew    338: }
                    339:
1.14      andrew    340: sub _crypt3des {
                    341:     my ( $plaintext, $passphrase, $flag ) = @_;
                    342:
                    343:     $passphrase   .= $SPACE x ( 16 * 3 );
                    344:     my $cyphertext = $EMPTY;
                    345:
                    346:     my $size = length $plaintext;
1.11      andrew    347:
1.14      andrew    348:     #print "STRING: '$plaintext' - Length: " . (length $plaintext) . "\n";
                    349:
                    350:     my @C;
                    351:     for ( 0 .. 2 ) {
                    352:         $C[$_] =
                    353:           new Crypt::DES( pack 'H*', ( substr $passphrase, 16 * $_, 16 ));
                    354:     }
                    355:
                    356:     for ( 0 .. ( ($size) / 8 ) ) {
                    357:         my $pt = substr $plaintext, $_ * 8, 8;
                    358:
                    359:         #print "PT: '$pt' - Length: " . length($pt) . "\n";
                    360:         if (! length $pt) { next; };
                    361:         if ( (length $pt) < 8 ) {
1.16      andrew    362:             if ($flag == $DECRYPT) { croak('record not 8 byte padded'); };
1.14      andrew    363:             my $len = 8 - (length $pt);
                    364:
                    365:             #print "LENGTH: $len\n";
                    366:             #print "Binary:    '" . unpack("b*", $pt) . "'\n";
                    367:             $pt .= ($NULL x $len);
                    368:
                    369:             #print "PT: '$pt' - Length: " . length($pt) . "\n";
                    370:             #print "Binary:    '" . unpack("b*", $pt) . "'\n";
                    371:         }
                    372:         if ( $flag == $ENCRYPT ) {
                    373:             $pt = $C[0]->encrypt($pt);
                    374:             $pt = $C[1]->decrypt($pt);
                    375:             $pt = $C[2]->encrypt($pt);
                    376:         }
                    377:         else {
                    378:             $pt = $C[0]->decrypt($pt);
                    379:             $pt = $C[1]->encrypt($pt);
                    380:             $pt = $C[2]->decrypt($pt);
                    381:         }
                    382:
                    383:         #print "PT: '$pt' - Length: " . length($pt) . "\n";
                    384:         $cyphertext .= $pt;
                    385:     }
                    386:
                    387:     $cyphertext =~ s/$NULL+$//xm;
1.11      andrew    388:
1.14      andrew    389:     #print "CT: '$cyphertext' - Length: " . length($cyphertext) . "\n";
1.11      andrew    390:
1.14      andrew    391:     return $cyphertext;
                    392: }
1.11      andrew    393:
1.14      andrew    394: 1;
                    395: __END__
1.11      andrew    396:
1.14      andrew    397: =head1 NAME
1.11      andrew    398:
1.14      andrew    399: Palm::Keyring - Handler for Palm Keyring databases.
1.1       andrew    400:
1.14      andrew    401: =head1 DESCRIPTION
1.7       andrew    402:
1.14      andrew    403: The Keyring PDB handler is a helper class for the Palm::PDB package. It
                    404: parses Keyring for Palm OS databases.  See
                    405: L<http://gnukeyring.sourceforge.net/>.
1.1       andrew    406:
1.14      andrew    407: It has the standard Palm::PDB methods with 2 additional public methods.
                    408: Decrypt and Encrypt.
1.1       andrew    409:
1.16      andrew    410: It currently supports the v4 Keyring databases.  The v5 databases from
                    411: the pre-release keyring-2.0 are not supported.
                    412:
                    413: This module doesn't store the decrypted content.  It only keeps it until it
                    414: returns it to you or encrypts it.
1.1       andrew    415:
1.14      andrew    416: =head1 SYNOPSIS
1.1       andrew    417:
1.16      andrew    418:     use Palm::PDB;
                    419:     use Palm::Keyring;
1.17      andrew    420:
                    421:     my $pass = 'password';
1.18      andrew    422:     my $file = 'Keys-Gtkr.pdb';
                    423:     my $pdb  = new Palm::PDB;
1.16      andrew    424:     $pdb->Load($file);
1.17      andrew    425:
                    426:     foreach (0..$#{ $pdb->{'records'} }) {
                    427:         next if $_ = 0; # skip the password record
                    428:         my $rec  = $pdb->{'records'}->[$_];
                    429:         my $acct = $pdb->Decrypt($rec, $pass);
1.19      andrew    430:         print $rec->{'name'}, ' - ', $acct->{'account'}, "\n";
1.16      andrew    431:     }
1.1       andrew    432:
1.14      andrew    433: =head1 SUBROUTINES/METHODS
1.1       andrew    434:
1.14      andrew    435: =head2 new
1.11      andrew    436:
1.16      andrew    437:     $pdb = new Palm::Keyring([$password]);
1.11      andrew    438:
1.14      andrew    439: Create a new PDB, initialized with the various Palm::Keyring fields
                    440: and an empty record list.
1.11      andrew    441:
1.14      andrew    442: Use this method if you're creating a Keyring PDB from scratch otherwise you
1.16      andrew    443: can just use Palm::PDB::new() before calling Load().
1.11      andrew    444:
1.16      andrew    445: =head2 Encrypt
1.11      andrew    446:
1.16      andrew    447:     $pdb->Encrypt($rec, $acct, [$password]);
1.11      andrew    448:
1.16      andrew    449: Encrypts an account into a record, either with the password previously
                    450: used, or with a password that is passed.
1.1       andrew    451:
1.16      andrew    452: $rec is a record from $pdb->{'records'} or a newly generated record.
                    453: $acct is a hashref in the format below.
1.1       andrew    454:
1.16      andrew    455:     my $acct = {
1.20    ! andrew    456:         name       => $rec->{'name'},
        !           457:         account    => $account,
        !           458:         password   => $password,
        !           459:         notes      => $notes,
        !           460:         lastchange => {
        !           461:             year  => 107, # years since 1900
        !           462:             month =>   0, # 0-11, 0 = January, 11 = December
        !           463:             day   =>  30, # 1-31, same as l<localtime/>
        !           464:         },
1.16      andrew    465:     };
1.7       andrew    466:
1.16      andrew    467: =head2 Decrypt
1.1       andrew    468:
1.16      andrew    469:     my $acct = $pdb->Decrypt($rec[, $password]);
1.1       andrew    470:
1.16      andrew    471: Decrypts the record and returns a hashref for the account as described
1.20    ! andrew    472: under Encrypt().
        !           473: However, it ignores the "lastchange" field and generates its own.
        !           474: It also only uses the "name" field if there is not already a $rec->{'name'}.
1.1       andrew    475:
1.16      andrew    476:     foreach (0..$#{ $pdb->{'records'}) {
                    477:         next if $_ == 0;
                    478:         my $rec = $pdb->{'records'}->[$_];
                    479:         my $acct = $pdb->Decrypt($rec[, $password]);
                    480:         # do something with $acct
                    481:     }
1.1       andrew    482:
1.16      andrew    483: =head2 Password
1.1       andrew    484:
1.16      andrew    485:     $pdb->Password([$password[, $new_password]]);
1.1       andrew    486:
1.16      andrew    487: Either sets the password to be used to crypt, or if you pass $new_password,
                    488: changes the password on the database.
1.1       andrew    489:
1.16      andrew    490: If you have created a new $pdb, and you didn't set a password when you
                    491: called new(), you only need to pass one password and it will set that as
                    492: the password.
1.1       andrew    493:
1.16      andrew    494: If nothing is passed, and there has been a password used before,
                    495: it just verifies that the password was correct.
1.1       andrew    496:
1.14      andrew    497: =head1 DEPENDENCIES
1.1       andrew    498:
1.14      andrew    499: Palm::StdAppInfo
1.1       andrew    500:
1.14      andrew    501: Digest::MD5
1.9       andrew    502:
1.14      andrew    503: Crypt::DES
1.4       andrew    504:
1.14      andrew    505: Readonly
1.10      andrew    506:
1.14      andrew    507: =head1 BUGS AND LIMITATIONS
1.1       andrew    508:
1.14      andrew    509: Once this module is uploaded, you can
                    510: Please report any bugs or feature requests to
                    511: C<bug-palm-keyring at rt.cpan.org>, or through the web interface at
                    512: L<http://rt.cpan.org>.  I will be notified, and then you'll automatically be
                    513: notified of progress on your bug as I make changes.
1.1       andrew    514:
                    515: =head1 AUTHOR
                    516:
1.12      andrew    517: Andrew Fresh E<lt>andrew@mad-techies.orgE<gt>
1.1       andrew    518:
1.14      andrew    519: =head1 LICENSE AND COPYRIGHT
                    520:
                    521: Copyright 2004, 2005, 2006, 2007 Andrew Fresh, All Rights Reserved.
                    522:
1.15      andrew    523: This program is free software; you can redistribute it and/or
                    524: modify it under the same terms as Perl itself.
1.14      andrew    525:
1.1       andrew    526: =head1 SEE ALSO
                    527:
                    528: Palm::PDB(3)
                    529:
                    530: Palm::StdAppInfo(3)
1.11      andrew    531:
                    532: The Keyring for Palm OS website:
                    533: L<http://gnukeyring.sourceforge.net/>

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