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

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

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