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