Annotation of palm/Palm-Keyring/lib/Palm/Keyring.pm, Revision 1.14
1.14 ! andrew 1: package Palm::Keyring;
! 2:
! 3: # $RedRiver: Keyring.pm,v 1.13 2007/01/28 18:13:28 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.
! 28: our ($VERSION) = q$Revision: 1.13 $ =~ m{ Revision: \s+ (\S+) }xm;
! 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: You may distribute this file under the terms of perl itself
! 480: as specified in the LICENSE file.
! 481:
! 482: Copyright 2004, 2005, 2006, 2007 Andrew Fresh, All Rights Reserved.
! 483:
! 484: This program is free software; you can redistribute it and/or modify it
! 485: under the same terms as Perl itself.
! 486:
1.1 andrew 487: =head1 SEE ALSO
488:
489: Palm::PDB(3)
490:
491: Palm::StdAppInfo(3)
1.11 andrew 492:
493: The Keyring for Palm OS website:
494: L<http://gnukeyring.sourceforge.net/>
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>