#!/usr/bin/perl -T # $RedRiver: keyring.t,v 1.24 2008/09/19 05:53:08 andrew Exp $ use strict; use warnings; use Test::More tests => 333; use Data::Dumper; BEGIN { use_ok('Palm::PDB'); use_ok('Palm::Keyring'); } my $file = 'Keys-test.pdb'; my $password = '12345'; my $new_password = '54321'; my @o = ( { version => 4, password => $password, }, { version => 5, password => $password, cipher => 1, }, ); my $acct = { 0 => { label => 'name', label_id => 0, data => 'test3', font => 0, }, 1 => { label => 'account', label_id => 1, data => 'atestaccount', font => 0, }, 2 => { label => 'password', label_id => 2, data => $password, font => 0, }, 3 => { label => 'lastchange', label_id => 3, data => { day => 2, month => 2, year => 99, }, font => 0, }, 255 => { label => 'notes', label_id => 255, data => 'now that really roxorZ!', font => 0, }, }; my %unchanging_date = %{ $acct->{3}->{data} }; my $bad_version = 999; my $bad_cipher = 999; my %crypt_1_details = ( 'default_iter' => 1000, 'keylen' => 24, 'blocksize' => 8, 'name' => 'DES_EDE3', 'alias' => 'DES-EDE3', 'DES_odd_parity' => 1 ); my $bad_label = 999; my $bad_label_name = 'not_a_label_name'; my %label_1_details = ( id => 1, name => 'account', ); my %label_not_found_details = ( id => $bad_label, name => undef, ); # Crypts is_deeply( Palm::Keyring::crypts(1), \%crypt_1_details, 'Got crypt 1' ); is_deeply( Palm::Keyring::crypts('DES-EDE3'), \%crypt_1_details, 'Got crypt DES-EDE3' ); is( Palm::Keyring::crypts(), undef, "Didn't get crypt empty cipher" ); is( Palm::Keyring::crypts($bad_cipher), undef, "Didn't get crypt $bad_cipher" ); # Bad Cipher eval { Palm::Keyring->new( { version => 5, cipher => $bad_cipher } ) }; like( $@, qr/^Unknown \s cipher \s $bad_cipher/xms, "Failed to create keyring with cipher $bad_cipher" ); # Labels is_deeply( Palm::Keyring::labels(1), \%label_1_details, 'Got label 1' ); is_deeply( Palm::Keyring::labels('account'), \%label_1_details, 'Got label account' ); is( Palm::Keyring::labels(), undef, "Didn't get label empty label" ); is_deeply( Palm::Keyring::labels($bad_label), \%label_not_found_details, "Got default label for $bad_label" ); is( Palm::Keyring::labels($bad_label_name), undef, "Didn't get label for $bad_label_name" ); # encrypt/decrypt (v4) my %acctv4 = ( account => 'account name', password => $password, notes => 'these are notes', lastchange => undef, ); my $digestv4; ok( $digestv4 = Palm::Keyring::_calc_keys( $password ), '_calc_keys' ); my $encryptedv4; ok( $encryptedv4 = Palm::Keyring::_encrypt_v4( \%acctv4, {}, $digestv4 ), '_encrypt_v4'); my $plaintextv4; ok( $plaintextv4 = Palm::Keyring::_decrypt_v4( $encryptedv4, $digestv4 ), '_decrypt_v4'); $plaintextv4->{lastchange} = undef; is_deeply( $plaintextv4, \%acctv4, 'Is what we got back, what we sent in?' ); my $NULL = chr(0); $plaintextv4 = join $NULL, $acctv4{account}, $acctv4{password}, $acctv4{notes}; ok( $encryptedv4 = Palm::Keyring::_crypt3des( $plaintextv4, $digestv4, 1 ), 'encrypt without date' ); # 1 is encrypt ok( $plaintextv4 = Palm::Keyring::_decrypt_v4( $encryptedv4, $digestv4 ), '_decrypt_v4'); $plaintextv4->{'lastchange'} = undef; is_deeply( $plaintextv4, \%acctv4, 'Is what we got back, what we sent in?' ); # Password eval{ Palm::Keyring::_password_verify_v4() }; like( $@, qr/^No \s password \s specified!/xms, '_password_verify_v4 with no password' ); eval{ Palm::Keyring::_password_verify_v4($password) }; like( $@, qr/^No \s encrypted \s password \s in \s file!/xms, '_password_verify_v4 with no password' ); my $pdb; eval { $pdb = new Palm::Keyring( -file => 't/Keys-invalid_version.pdb' ) }; like( $@, qr/^Unsupported \s Version \s $bad_version/xms, 'Couldn\'t load pdb with invalid version' ); eval { $pdb = new Palm::Keyring( -file => 't/Keys-invalid_cipher.pdb' ) }; like( $@, qr/^Unknown \s cipher \s $bad_version/xms, 'Couldn\'t load pdb with Unknown Cipher' ); eval { $pdb = new Palm::Keyring( -file => 't/Keys-invalid_appinfo.pdb' ) }; like( $@, qr/^Corrupt \s appinfo\? \s no \s {other}/xms, 'Couldn\'t load pdb with invalid appinfo' ); eval{ $pdb = new Palm::Keyring( -file => 't/Keys-no_data.pdb', -password => $new_password ) }; like( $@, qr/^Incorrect \s Password/xms, 'Couldn\'t load Palm::Keyring file with no data and wrong password' ); ok( $pdb = new Palm::Keyring( -file => 't/Keys-no_data.pdb' ), 'Loaded Palm::Keyring file with no data' ); my $record; ok( $record = $pdb->append_Record(), 'Append Record' ); my $ivec = pack("C*", 1..8); ok( $pdb->Encrypt( $record, $password, $acct, $ivec), 'Encrypt account into record (with custom ivec)' ); $acct->{254} = { label => 'misc', label_id => 254, data => 'This doesn\'t even really existx', font => 0, }; ok( $pdb->Encrypt( $record, $password, $acct ), 'Encrypt account into record (with custom field)' ); delete $acct->{254}; delete $record->{plaintext}; ok( $pdb->PackRecord($record), 'Pack Proper Record'); ok( $record = $pdb->ParseRecord(%{ $record }), 'Parse Proper Packed'); my $record2; ok( $record2 = $pdb->append_Record(), 'Append Record' ); eval{ $pdb->PackRecord($record2) }; like( $@, qr/^No \s encrypted \s data \s in \s record/xms, 'Pack Empty Record' ); $pdb->{appinfo}->{cipher} = 'TESTING'; eval{ $pdb->Decrypt( $record ) }; like( $@, qr/^Unsupported \s Crypt \s Testing/xms, 'Couldn\'t Decrypt with unsupported Crypt' ); my $encrypted = delete $record->{encrypted}; eval{ $pdb->Encrypt( $record ) }; like( $@, qr/^Unsupported \s Crypt \s Testing/xms, 'Couldn\'t Encrypt with unsupported Crypt' ); $record->{encrypted} = $encrypted; $pdb->{appinfo}->{cipher} = $bad_cipher; eval{ $pdb->Decrypt( $record ) }; like( $@, qr/^Unknown \s cipher \s $bad_cipher/xms, 'Couldn\'t Decrypt with unsupported cipher' ); $encrypted = delete $record->{encrypted}; eval{ $pdb->Encrypt( $record ) }; like( $@, qr/^Unknown \s cipher \s $bad_cipher/xms, 'Couldn\'t Encrypt with unsupported cipher' ); $record->{encrypted} = $encrypted; $record2->{encrypted} = {}; delete $record2->{ivec}; eval{ $pdb->PackRecord($record2) }; like( $@, qr/^No \s ivec/xms, 'Pack Empty Record with encrypted, but no ivec' ); $pdb->{version} = 4; delete $record2->{encrypted}; delete $record2->{data}; eval{ $pdb->PackRecord($record2) }; like( $@, qr/^No \s data \s in \s record \s to \s pack/xms, 'Pack Empty Record with no data' ); $pdb->{version} = $bad_version; eval{ $pdb->Decrypt( $record ) }; like( $@, qr/^Unsupported \s Version \s $bad_version/xms, 'Couldn\'t Decrypt with unsupported version' ); delete $record->{encrypted}; eval{ $pdb->Encrypt( $record, undef, $acct ) }; like( $@, qr/^Unsupported \s Version \s $bad_version/xms, 'Couldn\'t Encrypt with unsupported version' ); eval { $pdb->Write($file) }; like( $@, qr/^Unsupported \s Version \s $bad_version/xms, 'Couldn\'t Write file with unsupported version' ); eval{ $pdb->PackRecord($record) }; like( $@, qr/^Unsupported \s Version \s $bad_version/xms, 'Couldn\'t PackRecord with Invalid Version' ); $record2->{data} = q{nothing}; eval{ $pdb->ParseRecord(%{ $record2 }) }; like( $@, qr/^Unsupported \s Version \s $bad_version/xms, 'Couldn\'t ParseRecord with Invalid Version' ); eval{ $pdb->Password( $new_password ) }; like( $@, qr/^Unsupported \s Version \s $bad_version/xms, 'Couldn\'t Password with Invalid Version' ); $pdb = undef; $record = undef; $record2 = undef; unlink $file; foreach my $options (@o) { foreach my $config_type ( 'hashref', 'cgi-style', 'list' ) { my $pdb; my $record; my $decrypted; %{ $acct->{3}->{data} } = %unchanging_date; my $rec_num = 0; my $Num_Tests_Left = 25; SKIP: { if ( defined $options->{cipher} && $options->{cipher} > 0 ) { my $crypt = Palm::Keyring::crypts( $options->{cipher} ); skip 'Crypt::CBC not installed', $Num_Tests_Left unless eval "require Crypt::CBC"; if ($crypt) { skip 'Crypt::' . $crypt->{name} . ' not installed', $Num_Tests_Left unless eval "require Crypt::$crypt->{name}"; } else { skip 'Unknown Crypt: ' . $options->{cipher}, $Num_Tests_Left; } } if ( $options->{version} == 4 ) { skip 'Crypt::DES not installed', $Num_Tests_Left unless eval "require Crypt::DES "; skip 'Digest::MD5 not installed', $Num_Tests_Left unless eval "require Digest::MD5 "; } elsif ( $options->{version} == 5 ) { skip 'Digest::HMAC_SHA1 not installed', $Num_Tests_Left unless eval "require Digest::HMAC_SHA1 "; } my @options = ($options); if ( $config_type eq 'cgi-style' ) { @options = ( '-version' => $options->{version}, '-password' => $options->{password}, ); if ( $options->{cipher} ) { push @options, '-cipher', $options->{cipher}; } } elsif ( $config_type eq 'list' ) { @options = ( $options->{password}, $options->{version} ); if ( $options->{cipher} ) { push @options, $options->{cipher}; } } ok( $pdb = new Palm::Keyring(@options), 'new Palm::Keyring v' . $options->{version} ); ok( $pdb->Write($file), 'Write "empty" file' ); #exit if $pdb->{version} == 5; ok( $record = $pdb->append_Record(), 'Append Record' ); ok( $pdb->Password(), 'Clear Password' ); eval{ $pdb->Encrypt() }; like( $@, qr/^Needed \s parameter \s \[record\] \s not \s passed!/xms, 'Encrypt account into record without record' ); eval{ $pdb->Encrypt( $record ) }; like( $@, qr/^Password \s not \s set!/xms, 'Encrypt account into record without password' ); eval{ $pdb->Encrypt( $record, $password ) }; like( $@, qr/^Needed \s parameter \s \[plaintext\] \s not \s passed!/xms, 'Encrypt account into record without account' ); eval{ $pdb->Encrypt( $record, $new_password, $acct ) }; like( $@, qr/^Incorrect \s Password/xms, 'Encrypt account into record with wrong password' ); ok( $pdb->Encrypt( $record, $password, $acct ), 'Encrypt account into record' ); ok( $pdb->Encrypt( $record, $password, $acct ), 'Encrypt account into record (with no changes)'); ok( $decrypted = $pdb->Decrypt( $pdb->{records}->[$rec_num] ), 'Decrypt record' ); is( $decrypted->{2}->{data}, $password, 'Got password' ); ok( $pdb->Write($file), 'Write file' ); $pdb = undef; ok( $pdb = new Palm::Keyring(), 'new Palm::Keyring' ); ok( $pdb->Load($file), 'Load File' ); eval{ $pdb->Decrypt( ) }; like( $@, qr/^Needed \s parameter \s \[record\] \s not \s passed!/xms, 'Decrypt with no record' ); eval{ $pdb->Decrypt( $pdb->{records}->[$rec_num] ) }; like( $@, qr/^Password \s not \s set!/xms, 'Decrypt with no password set' ); eval{ $pdb->Decrypt( $pdb->{records}->[$rec_num], $new_password ) }; like( $@, qr/^Incorrect \s Password/xms, 'Decypt with invalid password' ); eval{ $pdb->Password($new_password) }; like( $@, qr/^Incorrect \s Password/xms, 'Verify Incorrect Password' ); eval{ $pdb->Decrypt( {} ) }; like( $@, qr/^No \s encrypted \s content!/xms, 'Decrypt with empty record' ); ok( $pdb->Password($password), 'Verify Password' ); ok( $decrypted = $pdb->Decrypt( $pdb->{records}->[$rec_num] ), 'Decrypt record' ); is( $decrypted->{2}->{data}, $password, 'Got password' ); is_deeply( $decrypted, $acct, 'Account Matches' ); my $old_date = $decrypted->{3}->{data}; ok( $pdb->Password( $password, $new_password ), 'Change PDB Password' ); ok( $decrypted = $pdb->Decrypt( $pdb->{'records'}->[$rec_num] ), 'Decrypt with new password' ); my $new_date = $decrypted->{3}->{data}; is_deeply( $old_date, $new_date, 'Date didn\'t change' ); $decrypted->{2}->{data} = $new_password; $pdb->{records}->[$rec_num]->{plaintext} = $decrypted; ok( $pdb->Encrypt( $pdb->{'records'}->[$rec_num] ), 'Encrypt record (new password)' ); ok( $decrypted = $pdb->Decrypt( $pdb->{'records'}->[$rec_num] ), 'Decrypt new record' ); is( $decrypted->{2}->{data}, $new_password, 'Got new password' ); $new_date = $decrypted->{3}->{data}; my $od = join '/', map { $old_date->{$_} } sort keys %{$old_date}; my $nd = join '/', map { $new_date->{$_} } sort keys %{$new_date}; isnt( $od, $nd, 'Date changed' ); %{ $new_date } = %unchanging_date; $new_date->{year} = 1999; $decrypted->{3}->{data} = $new_date; ok( $pdb->Encrypt( $pdb->{'records'}->[$rec_num], undef, $decrypted ), 'Encrypt record (new date)' ); ok( $decrypted = $pdb->Decrypt( $pdb->{'records'}->[$rec_num] ), 'Decrypt new record' ); $new_date = $decrypted->{3}->{data}; $od = $nd; $nd = join '/', map { $new_date->{$_} } sort keys %{$new_date}; my $ud = join '/', map { $unchanging_date{$_} } sort keys %unchanging_date; isnt( $od, $nd, 'Date changed (from what it used to be)' ); is( $ud, $nd, 'Date changed (to what we set)' ); delete $decrypted->{3}; ok( $pdb->Encrypt( $pdb->{'records'}->[$rec_num], undef, $decrypted ), 'Encrypt record (no date)' ); ok( $decrypted = $pdb->Decrypt( $pdb->{'records'}->[$rec_num] ), 'Decrypt new record' ); $new_date = $decrypted->{3}->{data}; is( ref $new_date, 'HASH', 'Got a hashref date' ); my $last_decrypted = $decrypted; $decrypted = {}; ok( $pdb->Password(), 'Forget password' ); eval { $decrypted = $pdb->Decrypt( $pdb->{'records'}->[$rec_num] ); }; ok( $@, 'Don\'t decrypt' ); isnt( $decrypted->{password}, $new_password, 'Didn\'t get new password' ); ok( $pdb->Unlock($new_password), 'Unlock' ); my @plaintext = map { $_->{plaintext} } @{ $pdb->{records} }; is_deeply( $plaintext[0], $last_decrypted, 'Account Matches' ); ok( $pdb->Lock(), 'Lock' ); my $cleared_decrypted = {}; $cleared_decrypted->{0} = $last_decrypted->{0}; @plaintext = map { $_->{plaintext} } @{ $pdb->{records} }; is_deeply( $plaintext[0], $cleared_decrypted, 'Cleared records' ); $pdb->{records}->[0]->{data} = undef; ok( $pdb->Write($file), 'Write file without data' ); ok( $pdb->Load($file), 'Load File without data' ); ok( unlink($file), 'Remove test pdb v' . $options->{version} ); } } } 1;