mirror of
https://codeberg.org/scip/Crypt--PWSafe3.git
synced 2025-12-16 20:21:01 +01:00
first commit
This commit is contained in:
18
CHANGELOG
Normal file
18
CHANGELOG
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
1.03
|
||||||
|
after saving we do not mv the tmp file but copying
|
||||||
|
it, because mv sometimes doesn't work with files the
|
||||||
|
current user is not the owner but has write permissions
|
||||||
|
while cp works on such files. so now we cp and unlink
|
||||||
|
the tmpfile after saving.
|
||||||
|
|
||||||
|
1.02
|
||||||
|
doc fix in ::Record (group separator is . not /)
|
||||||
|
added Shell.pm to Makefile.PL dependencies
|
||||||
|
|
||||||
|
|
||||||
|
1.01
|
||||||
|
bug fix in t/run.t
|
||||||
|
|
||||||
|
|
||||||
|
1.00
|
||||||
|
initial version
|
||||||
12
MANIFEST
Normal file
12
MANIFEST
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
lib/Crypt/PWSafe3/Databaseformat.pm
|
||||||
|
lib/Crypt/PWSafe3/Field.pm
|
||||||
|
lib/Crypt/PWSafe3/HeaderField.pm
|
||||||
|
lib/Crypt/PWSafe3/Record.pm
|
||||||
|
lib/Crypt/PWSafe3/SHA256.pm
|
||||||
|
lib/Crypt/PWSafe3.pm
|
||||||
|
Makefile.PL
|
||||||
|
MANIFEST
|
||||||
|
README
|
||||||
|
t/run.t
|
||||||
|
t/tom.psafe3
|
||||||
|
CHANGELOG
|
||||||
22
Makefile.PL
Normal file
22
Makefile.PL
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
require 5.004;
|
||||||
|
use ExtUtils::MakeMaker;
|
||||||
|
|
||||||
|
WriteMakefile(
|
||||||
|
'NAME' => 'Crypt::PWSafe3',
|
||||||
|
'VERSION_FROM' => 'lib/Crypt/PWSafe3.pm',
|
||||||
|
'PREREQ_PM' => { 'Digest::HMAC' => 1.00,
|
||||||
|
'Digest::SHA' => 1.00,
|
||||||
|
'Crypt::CBC' => 2.30,
|
||||||
|
'Crypt::ECB' => 1.45,
|
||||||
|
'Crypt::Twofish' => 2.14,
|
||||||
|
'Crypt::Random' => 1.25,
|
||||||
|
'Data::UUID' => 1.217,
|
||||||
|
'Shell' => 0.5,
|
||||||
|
},
|
||||||
|
'AUTHOR' => 'Thomas Linden <tlinden@cpan.org>',
|
||||||
|
'clean' => {
|
||||||
|
FILES => 't/*.out *~ */*~ */*/*~ */*/*/*~'
|
||||||
|
},
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
52
README
Normal file
52
README
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
NAME
|
||||||
|
Crypt::PWSafe3 - Read and write Passwordsafe v3 files
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
|
||||||
|
use Crypt::PWSafe3;
|
||||||
|
my $vault = new Crypt::PWSafe3(file => 'filename.psafe3',
|
||||||
|
password => 'somesecret');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Crypt::PWSafe3 provides read and write access to password
|
||||||
|
database files created by Password Safe V3 (and up) available
|
||||||
|
at http://passwordsafe.sf.net.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
INSTALLATION
|
||||||
|
|
||||||
|
to install, type:
|
||||||
|
perl Makefile.PL
|
||||||
|
make
|
||||||
|
make test
|
||||||
|
make install
|
||||||
|
|
||||||
|
to read the complete documentation, type:
|
||||||
|
perldoc Crypt::PWSafe3
|
||||||
|
perldoc Crypt::PWSafe3::Record
|
||||||
|
|
||||||
|
|
||||||
|
COPYRIGHT
|
||||||
|
Crypt::PWSafe3
|
||||||
|
Copyright (c) 2011 by T. Linden <tlinden@cpan.org>
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it
|
||||||
|
and/or modify it under the same terms as Perl itself.
|
||||||
|
|
||||||
|
HOMEPAGE
|
||||||
|
|
||||||
|
The homepage of Config::General is located at:
|
||||||
|
|
||||||
|
http://www.daemon.de/crypt-pwsafe3/.
|
||||||
|
|
||||||
|
|
||||||
|
AUTHOR
|
||||||
|
T. Linden <tlinden |AT| cpan.org>
|
||||||
|
|
||||||
|
|
||||||
|
VERSION
|
||||||
|
1.03
|
||||||
912
lib/Crypt/PWSafe3.pm
Normal file
912
lib/Crypt/PWSafe3.pm
Normal file
@@ -0,0 +1,912 @@
|
|||||||
|
|
||||||
|
# http://passwordsafe.svn.sourceforge.net/viewvc/passwordsafe/trunk/pwsafe/pwsafe/docs/formatV3.txt?revision=2139
|
||||||
|
|
||||||
|
package Crypt::PWSafe3;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
|
||||||
|
use Carp::Heavy;
|
||||||
|
use Carp;
|
||||||
|
|
||||||
|
use Crypt::CBC;
|
||||||
|
use Crypt::ECB;
|
||||||
|
use Crypt::Twofish;
|
||||||
|
use Digest::HMAC;
|
||||||
|
use Digest::SHA;
|
||||||
|
use Crypt::Random qw( makerandom );
|
||||||
|
use Data::UUID;
|
||||||
|
use Shell qw(mv cp);
|
||||||
|
use File::Spec;
|
||||||
|
use FileHandle;
|
||||||
|
use Data::Dumper;
|
||||||
|
use Exporter ();
|
||||||
|
use vars qw(@ISA @EXPORT);
|
||||||
|
|
||||||
|
$Crypt::PWSafe3::VERSION = '1.03';
|
||||||
|
|
||||||
|
use Crypt::PWSafe3::Field;
|
||||||
|
use Crypt::PWSafe3::HeaderField;
|
||||||
|
use Crypt::PWSafe3::Record;
|
||||||
|
use Crypt::PWSafe3::SHA256;
|
||||||
|
|
||||||
|
my @fields = qw(tag salt iter shaps b1 b2 b3 b4 keyk file program
|
||||||
|
keyl iv hmac header strechedpw password whoami);
|
||||||
|
foreach my $field (@fields) {
|
||||||
|
eval qq(
|
||||||
|
*Crypt::PWSafe3::$field = sub {
|
||||||
|
my(\$this, \$arg) = \@_;
|
||||||
|
if (\$arg) {
|
||||||
|
return \$this->{$field} = \$arg;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return \$this->{$field};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub new {
|
||||||
|
#
|
||||||
|
# new vault object
|
||||||
|
my($this, %param) = @_;
|
||||||
|
my $class = ref($this) || $this;
|
||||||
|
my $self = \%param; # file, password, whoami, program
|
||||||
|
bless($self, $class);
|
||||||
|
|
||||||
|
# sanity checks
|
||||||
|
if (! exists $self->{whoami}) {
|
||||||
|
$self->{whoami} = $ENV{USER};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! exists $self->{program}) {
|
||||||
|
$self->{program} = $0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! exists $self->{password}) {
|
||||||
|
croak 'Parameter password is required';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! exists $self->{file}) {
|
||||||
|
$self->{file} = '';
|
||||||
|
$self->create();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (! -s $self->{file}) {
|
||||||
|
$self->create();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$self->read();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$self->{modified} = 0;
|
||||||
|
|
||||||
|
return $self;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub stretchpw {
|
||||||
|
#
|
||||||
|
# generate the streched password hash
|
||||||
|
#
|
||||||
|
# algorithm is described here:
|
||||||
|
# [KEYSTRETCH Section 4.1] http://www.schneier.com/paper-low-entropy.pdf
|
||||||
|
my ($this, $passwd) = @_;
|
||||||
|
my $sha = new Digest::SHA('SHA-256');
|
||||||
|
$sha->reset();
|
||||||
|
$sha->add( ( $passwd, $this->salt) );
|
||||||
|
my $stretched = $sha->digest();
|
||||||
|
foreach (1 .. $this->iter) {
|
||||||
|
$sha->reset();
|
||||||
|
$sha->add( ( $stretched) );
|
||||||
|
$stretched = $sha->digest();
|
||||||
|
}
|
||||||
|
$passwd = $this->random(64);
|
||||||
|
return $stretched;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub create {
|
||||||
|
#
|
||||||
|
# create an empty vault without writing to disk
|
||||||
|
my($this) = @_;
|
||||||
|
|
||||||
|
# default header fields
|
||||||
|
$this->tag('PWS3');
|
||||||
|
$this->salt($this->random(32));
|
||||||
|
$this->iter(2048);
|
||||||
|
|
||||||
|
# the streched pw
|
||||||
|
$this->strechedpw($this->stretchpw($this->password()));
|
||||||
|
|
||||||
|
# generate hash of the streched pw
|
||||||
|
my $sha = new Digest::SHA('SHA-256');
|
||||||
|
$sha->reset();
|
||||||
|
$sha->add( ( $this->strechedpw() ) );
|
||||||
|
$this->shaps( $sha->digest() );
|
||||||
|
|
||||||
|
# encrypt b1 .. b4
|
||||||
|
my $crypt = Crypt::ECB->new;
|
||||||
|
#$crypt->padding(PADDING_AUTO);
|
||||||
|
$crypt->cipher('Twofish');
|
||||||
|
$crypt->key( $this->strechedpw() );
|
||||||
|
$this->b1( $crypt->encrypt( $this->random(16) ) );
|
||||||
|
$this->b2( $crypt->encrypt( $this->random(16) ) );
|
||||||
|
$this->b3( $crypt->encrypt( $this->random(16) ) );
|
||||||
|
$this->b4( $crypt->encrypt( $this->random(16) ) );
|
||||||
|
|
||||||
|
# create key k + l
|
||||||
|
$this->keyk( $crypt->decrypt( $this->b1() ) . $crypt->decrypt( $this->b2() ));
|
||||||
|
$this->keyl( $crypt->decrypt( $this->b3() ) . $crypt->decrypt( $this->b4() ));
|
||||||
|
|
||||||
|
# create IV
|
||||||
|
$this->iv( $this->random(16) );
|
||||||
|
|
||||||
|
# create hmac'er and cipher for actual encryption
|
||||||
|
$this->{hmacer} = new Digest::HMAC($this->keyl, "Crypt::PWSafe3::SHA256");
|
||||||
|
$this->{cipher} = new Crypt::CBC(
|
||||||
|
-key => $this->keyk,
|
||||||
|
-iv => $this->iv,
|
||||||
|
-cipher => 'Twofish',
|
||||||
|
-header => 'none',
|
||||||
|
-padding => 'null',
|
||||||
|
-literal_key => 1,
|
||||||
|
-keysize => 32,
|
||||||
|
-blocksize => 16
|
||||||
|
);
|
||||||
|
|
||||||
|
# empty for now
|
||||||
|
$this->hmac( $this->{hmacer}->digest() );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub read {
|
||||||
|
#
|
||||||
|
# read and decrypt an existing vault file
|
||||||
|
my($this) = @_;
|
||||||
|
|
||||||
|
my $fd = new FileHandle($this->file, 'r');
|
||||||
|
$fd->binmode();
|
||||||
|
$this->{fd} = $fd;
|
||||||
|
|
||||||
|
$this->tag( $this->readbytes(4) );
|
||||||
|
if ($this->tag ne 'PWS3') {
|
||||||
|
croak "Not a PasswordSave V3 file!";
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->salt( $this->readbytes(32) );
|
||||||
|
$this->iter( unpack("V", $this->readbytes(4) ) );
|
||||||
|
|
||||||
|
$this->strechedpw($this->stretchpw($this->password()));
|
||||||
|
|
||||||
|
my $sha = new Digest::SHA(256);
|
||||||
|
$sha->reset();
|
||||||
|
$sha->add( ( $this->strechedpw() ) );
|
||||||
|
$this->shaps( $sha->digest() );
|
||||||
|
|
||||||
|
my $fileshaps = $this->readbytes(32);
|
||||||
|
#print "sha1: <" . unpack('H*', $fileshaps) . ">\nsha2: <" . unpack('H*', $this->shaps) . ">\n";
|
||||||
|
if ($fileshaps ne $this->shaps) {
|
||||||
|
croak "Wrong password!";
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->b1( $this->readbytes(16) );
|
||||||
|
$this->b2( $this->readbytes(16) );
|
||||||
|
$this->b3( $this->readbytes(16) );
|
||||||
|
$this->b4( $this->readbytes(16) );
|
||||||
|
|
||||||
|
my $crypt = Crypt::ECB->new;
|
||||||
|
$crypt->cipher('Twofish') || die $crypt->errstring;
|
||||||
|
$crypt->key( $this->strechedpw() );
|
||||||
|
|
||||||
|
$this->keyk($crypt->decrypt($this->b1) . $crypt->decrypt($this->b2));
|
||||||
|
$this->keyl($crypt->decrypt($this->b3) . $crypt->decrypt($this->b4));
|
||||||
|
|
||||||
|
#print "keyk:<" . unpack('H*', $this->keyk) . ">\n";
|
||||||
|
|
||||||
|
$this->iv( $this->readbytes(16) );
|
||||||
|
|
||||||
|
# create hmac'er and cipher for actual encryption
|
||||||
|
$this->{hmacer} = new Digest::HMAC($this->keyl, "Crypt::PWSafe3::SHA256");
|
||||||
|
#print "keyk len: " . length($this->keyk) . "\n";
|
||||||
|
$this->{cipher} = new Crypt::CBC(
|
||||||
|
-key => $this->keyk,
|
||||||
|
-iv => $this->iv,
|
||||||
|
-cipher => 'Twofish',
|
||||||
|
-header => 'none',
|
||||||
|
-padding => 'null',
|
||||||
|
-literal_key => 1,
|
||||||
|
-keysize => 32,
|
||||||
|
-blocksize => 16
|
||||||
|
);
|
||||||
|
|
||||||
|
# read db header fields
|
||||||
|
$this->{header} = {};
|
||||||
|
while (1) {
|
||||||
|
my $field = $this->readfield('header');
|
||||||
|
if (! $field) {
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
if ($field->type == 0xff) {
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
$this->addheader($field);
|
||||||
|
$this->hmacer($field->raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
# read db records
|
||||||
|
my $record = new Crypt::PWSafe3::Record();
|
||||||
|
$this->{record} = {};
|
||||||
|
while (1) {
|
||||||
|
my $field = $this->readfield();
|
||||||
|
if (! $field) {
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
if ($field->type == 0xff) {
|
||||||
|
$this->addrecord($record);
|
||||||
|
#print "--- record added (uuid:" . $record->uuid . ")\n";
|
||||||
|
$record = new Crypt::PWSafe3::Record();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$record->addfield($field);
|
||||||
|
$this->hmacer($field->raw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# read and check file hmac
|
||||||
|
$this->hmac( $this->readbytes(32) );
|
||||||
|
my $calcmac = $this->{hmacer}->digest();
|
||||||
|
if ($calcmac ne $this->hmac) {
|
||||||
|
croak "File integrity check failed";
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->{fd}->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub save {
|
||||||
|
#
|
||||||
|
# write data to the vault file
|
||||||
|
my($this, %param) = @_;
|
||||||
|
my($file, $passwd);
|
||||||
|
|
||||||
|
if (! exists $param{file}) {
|
||||||
|
$file = $this->file;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$file = $param{file}
|
||||||
|
}
|
||||||
|
if (! exists $param{passwd}) {
|
||||||
|
$passwd = $this->password;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$passwd = $param{passwd}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $this->{modified}) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $lastsave = new Crypt::PWSafe3::HeaderField(type => 0x04, value => time);
|
||||||
|
my $whatsaved = new Crypt::PWSafe3::HeaderField(type => 0x06, value => $this->{program});
|
||||||
|
my $whosaved = new Crypt::PWSafe3::HeaderField(type => 0x05, value => $this->{whoami});
|
||||||
|
$this->addheader($lastsave);
|
||||||
|
$this->addheader($whatsaved);
|
||||||
|
$this->addheader($whosaved);
|
||||||
|
|
||||||
|
my $tmpfile = File::Spec->catfile(File::Spec->tmpdir(),
|
||||||
|
".vault-" . unpack("H*", $this->random(16)));
|
||||||
|
unlink $tmpfile;
|
||||||
|
my $fd = new FileHandle($tmpfile, 'w') or croak "Could not open tmpfile $tmpfile: $!\n";
|
||||||
|
$fd->binmode();
|
||||||
|
$this->{fd} = $fd;
|
||||||
|
|
||||||
|
$this->writebytes($this->tag);
|
||||||
|
$this->writebytes($this->salt);
|
||||||
|
$this->writebytes(pack("V", $this->iter));
|
||||||
|
|
||||||
|
$this->strechedpw($this->stretchpw($passwd));
|
||||||
|
|
||||||
|
# line 472
|
||||||
|
my $sha = new Digest::SHA(256);
|
||||||
|
$sha->reset();
|
||||||
|
$sha->add( ( $this->strechedpw() ) );
|
||||||
|
$this->shaps( $sha->digest() );
|
||||||
|
|
||||||
|
$this->writebytes($this->shaps);
|
||||||
|
$this->writebytes($this->b1);
|
||||||
|
$this->writebytes($this->b2);
|
||||||
|
$this->writebytes($this->b3);
|
||||||
|
$this->writebytes($this->b4);
|
||||||
|
|
||||||
|
my $crypt = Crypt::ECB->new;
|
||||||
|
$crypt->cipher('Twofish');
|
||||||
|
$crypt->key( $this->strechedpw() );
|
||||||
|
|
||||||
|
$this->keyk($crypt->decrypt($this->b1) . $crypt->decrypt($this->b2));
|
||||||
|
$this->keyl($crypt->decrypt($this->b3) . $crypt->decrypt($this->b4));
|
||||||
|
|
||||||
|
$this->writebytes($this->iv);
|
||||||
|
|
||||||
|
$this->{hmacer} = new Digest::HMAC($this->keyl, "Crypt::PWSafe3::SHA256");
|
||||||
|
$this->{cipher} = new Crypt::CBC(
|
||||||
|
-key => $this->keyk,
|
||||||
|
-iv => $this->iv,
|
||||||
|
-cipher => 'Twofish',
|
||||||
|
-header => 'none',
|
||||||
|
-padding => 'null',
|
||||||
|
-literal_key => 1,
|
||||||
|
-keysize => 32,
|
||||||
|
-blocksize => 16
|
||||||
|
);
|
||||||
|
|
||||||
|
my $eof = new Crypt::PWSafe3::HeaderField(type => 0xff, value => '');
|
||||||
|
|
||||||
|
foreach my $type (keys %{$this->{header}}) {
|
||||||
|
$this->writefield($this->{header}->{$type});
|
||||||
|
$this->hmacer($this->{header}->{$type}->{raw});
|
||||||
|
}
|
||||||
|
$this->writefield($eof);
|
||||||
|
$this->hmacer($eof->{raw});
|
||||||
|
|
||||||
|
$eof = new Crypt::PWSafe3::Field(type => 0xff, value => '');
|
||||||
|
|
||||||
|
foreach my $uuid (keys %{$this->{record}}) {
|
||||||
|
my $record = $this->{record}->{$uuid};
|
||||||
|
foreach my $type (keys %{$record->{field}}) {
|
||||||
|
$this->writefield($record->{field}->{$type});
|
||||||
|
$this->hmacer($record->{field}->{$type}->{raw});
|
||||||
|
}
|
||||||
|
$this->writefield($eof);
|
||||||
|
$this->hmacer($eof->{raw});
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->writefield(new Crypt::PWSafe3::Field(type => 'none', raw => 0));
|
||||||
|
|
||||||
|
$this->hmac( $this->{hmacer}->digest() );
|
||||||
|
$this->writebytes($this->hmac);
|
||||||
|
$this->{fd}->close();
|
||||||
|
|
||||||
|
# now try to read it in again to check if it
|
||||||
|
# is valid what we created
|
||||||
|
eval {
|
||||||
|
my $vault = new Crypt::PWSafe3(file => $tmpfile, password => $passwd);
|
||||||
|
};
|
||||||
|
if ($@) {
|
||||||
|
unlink $tmpfile;
|
||||||
|
croak "File integrity check failed ($@)";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
# well, seems to be ok :)
|
||||||
|
cp($tmpfile, $file);
|
||||||
|
unlink $tmpfile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub writefield {
|
||||||
|
#
|
||||||
|
# write a field to vault file
|
||||||
|
my($this, $field) = @_;
|
||||||
|
|
||||||
|
#print "write field " . $field->name . "\n";
|
||||||
|
|
||||||
|
if ($field->type eq 'none') {
|
||||||
|
$this->writebytes("PWS3-EOFPWS3-EOF");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $len = pack("V", $field->len);
|
||||||
|
my $type = pack("C", $field->type);
|
||||||
|
my $raw = $field->raw;
|
||||||
|
|
||||||
|
# Assemble TLV block and pad to 16-byte boundary
|
||||||
|
my $data = $len . $type . $raw;
|
||||||
|
|
||||||
|
if (length($data) % 16 != 0) {
|
||||||
|
# too small or too large, padding required
|
||||||
|
my $padcount = 16 - (length($data) % 16);
|
||||||
|
$data .= $this->random($padcount);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length($data) > 16) {
|
||||||
|
my $crypt;
|
||||||
|
while (1) {
|
||||||
|
#print "processing part\n";
|
||||||
|
my $part = substr($data, 0, 16);
|
||||||
|
$crypt .= $this->encrypt($part);
|
||||||
|
if (length($data) <= 16) {
|
||||||
|
#print " this was the last one\n";
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
#print " getting next\n";
|
||||||
|
$data = substr($data, 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#print " len: " . length($crypt) . "\n";
|
||||||
|
$this->writebytes($crypt);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->writebytes($this->encrypt($data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub getrecord {
|
||||||
|
#
|
||||||
|
# return the given record
|
||||||
|
my($this, $uuid) = @_;
|
||||||
|
if (exists $this->{record}->{$uuid}) {
|
||||||
|
return $this->{record}->{$uuid};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub getrecords {
|
||||||
|
#
|
||||||
|
# return all records we've got as a copy
|
||||||
|
my ($this) = @_;
|
||||||
|
return map { $this->{record}->{$_} } keys %{$this->{record}};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub looprecord {
|
||||||
|
#
|
||||||
|
# return a list of uuid's of all records
|
||||||
|
my ($this) = @_;
|
||||||
|
return keys %{$this->{record}};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub modifyrecord {
|
||||||
|
#
|
||||||
|
# modify a record identified by the given uuid
|
||||||
|
my($this, $uuid, %fields) = @_;
|
||||||
|
|
||||||
|
if (! exists $this->{record}->{$uuid}) {
|
||||||
|
croak "No record with uuid $uuid found!";
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach my $field (keys %fields) {
|
||||||
|
$this->{record}->{$uuid}->modifyfield($field, $fields{$field});
|
||||||
|
}
|
||||||
|
|
||||||
|
# mark vault as modified
|
||||||
|
$this->markmodified();
|
||||||
|
}
|
||||||
|
|
||||||
|
sub markmodified {
|
||||||
|
#
|
||||||
|
# mark the vault as modified by setting the appropriate header fields
|
||||||
|
my($this) = @_;
|
||||||
|
my $lastmod = new Crypt::PWSafe3::HeaderField(
|
||||||
|
name => "lastsavetime",
|
||||||
|
value => time
|
||||||
|
);
|
||||||
|
my $who = new Crypt::PWSafe3::HeaderField(
|
||||||
|
name => "wholastsaved",
|
||||||
|
value => $this->{whoami}
|
||||||
|
);
|
||||||
|
$this->addheader($lastmod);
|
||||||
|
$this->addheader($who);
|
||||||
|
$this->{modified} = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub newrecord {
|
||||||
|
#
|
||||||
|
# add a new record to an existing vault
|
||||||
|
my($this, %fields) = @_;
|
||||||
|
my $record = new Crypt::PWSafe3::Record();
|
||||||
|
foreach my $field (keys %fields) {
|
||||||
|
$record->modifyfield($field, $fields{$field});
|
||||||
|
}
|
||||||
|
$this->markmodified();
|
||||||
|
$this->addrecord($record);
|
||||||
|
return $record->uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub addrecord {
|
||||||
|
#
|
||||||
|
# add a record object to record hash
|
||||||
|
my($this, $record) = @_;
|
||||||
|
$this->{record}->{ $record->uuid } = $record;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub addheader {
|
||||||
|
#
|
||||||
|
# add a header field to header hash
|
||||||
|
my($this, $field) = @_;
|
||||||
|
$this->{header}->{ $field->name } = $field;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub readfield {
|
||||||
|
#
|
||||||
|
# read and return a field object of the vault
|
||||||
|
my($this, $header) = @_;
|
||||||
|
my $data = $this->readbytes(16);
|
||||||
|
if (! $data or length($data) < 16) {
|
||||||
|
croak "EOF encountered when parsing record field";
|
||||||
|
}
|
||||||
|
if ($data eq "PWS3-EOFPWS3-EOF") {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#print "\n raw: <" . unpack('H*', $data) . ">\n";
|
||||||
|
|
||||||
|
$data = $this->decrypt($data);
|
||||||
|
|
||||||
|
#print "clear: <" . unpack('H*', $data) . ">\n";
|
||||||
|
|
||||||
|
my $len = unpack("V", substr($data, 0, 4));
|
||||||
|
my $type = unpack("C", substr($data, 4, 1));
|
||||||
|
my $raw = substr($data, 5);
|
||||||
|
|
||||||
|
#print "readfield: len: $len, type: $type\n";
|
||||||
|
|
||||||
|
if ($len > 11) {
|
||||||
|
my $step = int(($len+4) / 16);
|
||||||
|
for (1 .. $step) {
|
||||||
|
my $data = $this->readbytes(16);
|
||||||
|
if (! $data or length($data) < 16) {
|
||||||
|
croak "EOF encountered when parsing record field";
|
||||||
|
}
|
||||||
|
$raw .= $this->decrypt($data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$raw = substr($raw, 0, $len);
|
||||||
|
if ($header) {
|
||||||
|
return new Crypt::PWSafe3::HeaderField(type => $type, raw => $raw);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new Crypt::PWSafe3::Field(type => $type, raw => $raw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub decrypt {
|
||||||
|
#
|
||||||
|
# helper, decrypt a string
|
||||||
|
my ($this, $data) = @_;
|
||||||
|
my $clear = $this->{cipher}->decrypt($data);
|
||||||
|
$this->{cipher}->iv($data);
|
||||||
|
return $clear;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub encrypt {
|
||||||
|
#
|
||||||
|
# helper, encrypt a string
|
||||||
|
my ($this, $data) = @_;
|
||||||
|
my $raw = $this->{cipher}->encrypt($data);
|
||||||
|
if (length($raw) > 16) {
|
||||||
|
# we use only the last 16byte block as next iv
|
||||||
|
# if data is more than 1 blocks than Crypt::CBC
|
||||||
|
# has already updated the iv for the inner blocks
|
||||||
|
$raw = substr($raw, -16, 16);
|
||||||
|
}
|
||||||
|
$this->{cipher}->iv($raw);
|
||||||
|
return $raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub hmacer {
|
||||||
|
#
|
||||||
|
# helper, hmac generator
|
||||||
|
my($this, $data) = @_;
|
||||||
|
|
||||||
|
$this->{hmacer}->add($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub readbytes {
|
||||||
|
#
|
||||||
|
# helper, reads number of bytes
|
||||||
|
my ($this, $size) = @_;
|
||||||
|
my $buffer;
|
||||||
|
my ($package, $filename, $line) = caller;
|
||||||
|
|
||||||
|
my $got = $this->{fd}->sysread($buffer, $size);
|
||||||
|
if ($got == $size) {
|
||||||
|
$this->{sum} += $got;
|
||||||
|
#print "Got $got bytes (read so far: $this->{sum} bytes) $package line $line\n";
|
||||||
|
return $buffer;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub writebytes {
|
||||||
|
#
|
||||||
|
# helper, reads number of bytes
|
||||||
|
my ($this, $bytes) = @_;
|
||||||
|
my $got = $this->{fd}->syswrite($bytes);
|
||||||
|
if ($got) {
|
||||||
|
return $got;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
croak "Could not write to $this->{file}: $!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub random {
|
||||||
|
#
|
||||||
|
# helper, return some secure random bytes
|
||||||
|
my($this, $len) = @_;
|
||||||
|
my $bits = makerandom(Size => 256, Strength => 1);
|
||||||
|
return substr($bits, 0, $len);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub getheader {
|
||||||
|
#
|
||||||
|
# return a header object
|
||||||
|
my($this, $name) = @_;
|
||||||
|
# $this->{header}->{ $field->name } = $field;
|
||||||
|
if (exists $this->{header}->{$name}) {
|
||||||
|
return $this->{header}->{$name};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
croak "Unknown header $name";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
=head1 NAME
|
||||||
|
|
||||||
|
Crypt::PWSafe3 - Read and write Passwordsafe v3 files
|
||||||
|
|
||||||
|
=head1 SYNOPSIS
|
||||||
|
|
||||||
|
use Crypt::PWSafe3;
|
||||||
|
my $vault = new Crypt::PWSafe3(file => 'filename.psafe3', password => 'somesecret');
|
||||||
|
|
||||||
|
# fetch all database records
|
||||||
|
my @records = $vault->getrecords();
|
||||||
|
foreach my $record (@records) {
|
||||||
|
print $record->uuid;
|
||||||
|
print $record->title;
|
||||||
|
print $record->passwd;
|
||||||
|
# see Crypt::PWSafe3::Record for more details on accessing records
|
||||||
|
}
|
||||||
|
|
||||||
|
# same as above but don't detach records from vault
|
||||||
|
foreach my $uuid ($vault->looprecord) {
|
||||||
|
# either change a record
|
||||||
|
$vault->modifyrecord($uuid, passwd => 'p1');
|
||||||
|
|
||||||
|
# or just access it directly
|
||||||
|
print $vault->{record}->{$uuid}->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
# add a new record
|
||||||
|
$vault->newrecord(user => 'u1', passwd => 'p1', title => 't1');
|
||||||
|
|
||||||
|
# modify an existing record
|
||||||
|
$vault->modifyrecord($uuid, passwd => 'p1');
|
||||||
|
|
||||||
|
# replace a record (aka edit it)
|
||||||
|
my $record = $vault->getrecord($uuid);
|
||||||
|
$record->title('t2');
|
||||||
|
$record->passwd('foobar');
|
||||||
|
$vault->addrecord($record);
|
||||||
|
|
||||||
|
# mark the vault as modified (not required if
|
||||||
|
# changes were done with ::modifyrecord()
|
||||||
|
$vault->markmodified();
|
||||||
|
|
||||||
|
# save the vault
|
||||||
|
$vault->save();
|
||||||
|
|
||||||
|
# save it under another name using another password
|
||||||
|
$vault->save(file => 'another.pwsafe3', passwd => 'blah');
|
||||||
|
|
||||||
|
# access database headers
|
||||||
|
print $vault->getheader('wholastsaved')->value();
|
||||||
|
print scalar localtime($vault->getheader('lastsavetime')->value());
|
||||||
|
|
||||||
|
# add/replace a database header
|
||||||
|
my $h = new Crypt::PWSafe3::HeaderField(name => 'savedonhost', value => 'localhost');
|
||||||
|
$vault->addheader($h);
|
||||||
|
|
||||||
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
|
Crypt::PWSafe3 provides read and write access to password
|
||||||
|
database files created by Password Safe V3 (and up) available at
|
||||||
|
http://passwordsafe.sf.net.
|
||||||
|
|
||||||
|
=head1 METHODS
|
||||||
|
|
||||||
|
=head2 B<new()>
|
||||||
|
|
||||||
|
The new() method creates a new Crypt::PWSafe3 object. Any parameters
|
||||||
|
must be given as hash parameters.
|
||||||
|
|
||||||
|
my $vault = new Crypt::PWSafe3(
|
||||||
|
file => 'vault.psafe3',
|
||||||
|
password => 'secret',
|
||||||
|
whoami => 'user1',
|
||||||
|
program => 'mypwtool v1'
|
||||||
|
);
|
||||||
|
|
||||||
|
Mandatory parameters:
|
||||||
|
|
||||||
|
=over
|
||||||
|
|
||||||
|
=item B<file>
|
||||||
|
|
||||||
|
Specifies the password safe (v3) file. If it exists
|
||||||
|
it will be read in. Otherwise it will be created
|
||||||
|
if you call B<save()>.
|
||||||
|
|
||||||
|
=item B<password>
|
||||||
|
|
||||||
|
The password required to decrypt the password safe file.
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
Optional parameters:
|
||||||
|
|
||||||
|
=over
|
||||||
|
|
||||||
|
=item B<whoami>
|
||||||
|
|
||||||
|
Specifies the user who saves the password safe file.
|
||||||
|
If omitted the environment variable USER will be used
|
||||||
|
when calling B<save()>.
|
||||||
|
|
||||||
|
=item B<program>
|
||||||
|
|
||||||
|
Specifies which program saved the password safe file.
|
||||||
|
If omitted, the content of the perl variable $0 will
|
||||||
|
be used, which contains the name of the current running
|
||||||
|
script.
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
The optional parameters will become header fields of
|
||||||
|
the password safe file. You can manually set/override
|
||||||
|
more headers. See section L<addheader()> for
|
||||||
|
more details.
|
||||||
|
|
||||||
|
=head2 B<getrecords()>
|
||||||
|
|
||||||
|
Returns a list of all records found in the password
|
||||||
|
safe file. Each element is an B<Crypt::PWSafe3::Record>
|
||||||
|
object.
|
||||||
|
|
||||||
|
A record object is identified by its B<UUID4> value,
|
||||||
|
which is a unique identifier. You can access the uuid by:
|
||||||
|
|
||||||
|
$record->uuid
|
||||||
|
|
||||||
|
Accessing other record properties works the same. For
|
||||||
|
more details, refer to L<Crypt::PWSafe3::Record>.
|
||||||
|
|
||||||
|
Please note that record objects accessed this way are
|
||||||
|
copies. If you change such a record object and save the
|
||||||
|
database, nothing will in fact change. In this case you
|
||||||
|
need to put the changed record back into the record
|
||||||
|
list of the Crypt::PWSafe3 object by:
|
||||||
|
|
||||||
|
$vault->addrecord($record):
|
||||||
|
|
||||||
|
See section L<addrecord()> for more details on this.
|
||||||
|
|
||||||
|
|
||||||
|
=head2 B<looprecord()>
|
||||||
|
|
||||||
|
Returns a list of UUIDs of all known records. You can
|
||||||
|
use this list to iterate over the records without
|
||||||
|
copying them and optionally changing them in place.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
foreach my $uuid ($vault->looprecord) {
|
||||||
|
# either change a record
|
||||||
|
$vault->modifyrecord($uuid, passwd => 'p1');
|
||||||
|
|
||||||
|
# or just access it directly
|
||||||
|
print $vault->{record}->{$uuid}->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
=head2 B<modifyrecord(uuid, parameter-hash)>
|
||||||
|
|
||||||
|
Modifies the record identified by the given UUID using
|
||||||
|
the values of the supplied parameter hash.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
$vault->modifyrecord($uuid, passwd => 'p1');
|
||||||
|
|
||||||
|
The parameter hash may contain any valid record field
|
||||||
|
type with according values. Refer to L<Crypt::PWSafe3::Record>
|
||||||
|
for details about available fields.
|
||||||
|
|
||||||
|
|
||||||
|
=head2 B<save([parameter-hash])>
|
||||||
|
|
||||||
|
Save the current password safe vault back to disk.
|
||||||
|
|
||||||
|
If not otherwise specified, use the same file and
|
||||||
|
password as we used to open it initially. If the
|
||||||
|
file doesn't exist it will be created.
|
||||||
|
|
||||||
|
You may specify another filename and password here
|
||||||
|
by using a parameter hash.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
$vault->save(file => 'anotherfile.psafe3', passwd => 'foo');
|
||||||
|
|
||||||
|
Please note, that the vault will be written to a
|
||||||
|
temporary file first, then this temporary file
|
||||||
|
will be read in and if that works, it will be
|
||||||
|
moved over the destination file. This way the original
|
||||||
|
file persists if the written database gets corrupted
|
||||||
|
by some unknown reason (a bug for instance).
|
||||||
|
|
||||||
|
|
||||||
|
=head2 B<getheader(name)>
|
||||||
|
|
||||||
|
Returns a raw B<Crypt::PWSafe3::HeaderField> object.
|
||||||
|
Refer to L<Crypt::PWSafe3::HeaderField> for details
|
||||||
|
how to access it.
|
||||||
|
|
||||||
|
=head2 B<addheader(object)>
|
||||||
|
|
||||||
|
Adds a header field to the password safe database. The
|
||||||
|
object parameter must be an B<Crypt::PWSafe3::HeaderField>
|
||||||
|
object.
|
||||||
|
|
||||||
|
If the header already exists it will be replaced.
|
||||||
|
|
||||||
|
Refer to L<Crypt::PWSafe3::HeaderField> for details
|
||||||
|
how to create new ones
|
||||||
|
.
|
||||||
|
|
||||||
|
=head1 AUTHOR
|
||||||
|
|
||||||
|
T. Linden <tlinden@cpan.org>
|
||||||
|
|
||||||
|
=head1 BUGS
|
||||||
|
|
||||||
|
Report bugs to
|
||||||
|
http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Crypt-PWSafe3.
|
||||||
|
|
||||||
|
=head1 VERSION
|
||||||
|
|
||||||
|
Crypt::PWSafe3 Version 1.03.
|
||||||
|
|
||||||
|
=head1 SEE ALSO
|
||||||
|
|
||||||
|
Subclasses:
|
||||||
|
|
||||||
|
L<Crypt::PWSafe3::Record>
|
||||||
|
L<Crypt::PWSafe3::Field>
|
||||||
|
L<Crypt::PWSafe3::HeaderField>
|
||||||
|
|
||||||
|
Password Safe Homepage:
|
||||||
|
L<http://passwordsafe.sourceforge.net/>
|
||||||
|
|
||||||
|
Another (read-only) perl module:
|
||||||
|
L<Crypt::Pwsafe>
|
||||||
|
|
||||||
|
A python port of Password Safe:
|
||||||
|
L<http://www.christoph-sommer.de/loxodo/>
|
||||||
|
Many thanks to Christoph Sommer, his python library
|
||||||
|
inspired me a lot and in fact most of the concepts
|
||||||
|
in this module are his ideas ported to perl.
|
||||||
|
|
||||||
|
=head1 COPYRIGHT
|
||||||
|
|
||||||
|
Copyright (c) 2011 by T.Linden <tlinden@cpan.org>.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
=head1 LICENSE
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it
|
||||||
|
and/or modify it under the same terms as Perl itself.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
1;
|
||||||
|
|
||||||
425
lib/Crypt/PWSafe3/Databaseformat.pm
Normal file
425
lib/Crypt/PWSafe3/Databaseformat.pm
Normal file
@@ -0,0 +1,425 @@
|
|||||||
|
=head1 NAME
|
||||||
|
|
||||||
|
PasswordSafe database format description version 3.03
|
||||||
|
|
||||||
|
|
||||||
|
=head1 LICENSE
|
||||||
|
|
||||||
|
Copyright (c) 2003-2008 Rony Shapiro <ronys@users.sourceforge.net>.
|
||||||
|
All rights reserved. Use of the code is allowed under the Artistic
|
||||||
|
License terms, as specified in the LICENSE file distributed with this
|
||||||
|
code, or available from
|
||||||
|
http://www.opensource.org/licenses/artistic-license-2.0.php
|
||||||
|
|
||||||
|
=head1 1. Introduction
|
||||||
|
|
||||||
|
This document defines a file format for the secure
|
||||||
|
storage of passwords and related data. The format is designed
|
||||||
|
according to current cryptographic best practices, and is beleived to
|
||||||
|
be secure, in the sense that without knowledge of the master
|
||||||
|
passphrase, only a brute-force attack or a flaw in the underlying
|
||||||
|
cryptographic algorithm will result in unauthorized access to the
|
||||||
|
data.
|
||||||
|
|
||||||
|
1.1 Design Goals: The PasswordSafe database format is designed to be
|
||||||
|
secure, extensible and platform-independent.
|
||||||
|
|
||||||
|
1.2 History: This specification is an evolution of previous
|
||||||
|
formats. The main differences between version 3 of the format and
|
||||||
|
previous versions are:
|
||||||
|
1.2.1. This version addresses a minor design flaw in previous versions
|
||||||
|
of the PasswordSafe database format.
|
||||||
|
1.2.3. This version replaces the underlying cryptographic functions
|
||||||
|
with more advanced versions.
|
||||||
|
1.2.4. This version allows the detection of a truncated or
|
||||||
|
corrupted/tampered database.
|
||||||
|
|
||||||
|
Meeting these goals is impossible without breaking compatibility: This
|
||||||
|
format is NOT compatible with previous (major) versions. Note,
|
||||||
|
however, that since the data stored in previous versions is a proper
|
||||||
|
subset of the data described here, implementers may read a database
|
||||||
|
written in an older version and store the result in the format
|
||||||
|
described here.
|
||||||
|
|
||||||
|
=head1 2. Format
|
||||||
|
|
||||||
|
A V3 format PasswordSafe is structured as follows:
|
||||||
|
|
||||||
|
TAG|SALT|ITER|H(P')|B1|B2|B3|B4|IV|HDR|R1|R2|...|Rn|EOF|HMAC
|
||||||
|
|
||||||
|
Where:
|
||||||
|
|
||||||
|
2.1 TAG is the sequence of 4 ASCII characters "PWS3". This is to serve as a
|
||||||
|
quick way for the application to identify the database as a PasswordSafe
|
||||||
|
version 3 file. This tag has no cryptographic value.
|
||||||
|
|
||||||
|
2.1 SALT is a 256 bit random value, generated at file creation time.
|
||||||
|
|
||||||
|
2.3 P' is the "stretched key" generated from the user's passphrase and
|
||||||
|
the SALT, as defined in by the hash-function-based key stretching
|
||||||
|
algorithm in [KEYSTRETCH] (Section 4.1), with SHA-256 [SHA256] as the
|
||||||
|
hash function, and ITER iterations (at least 2048, i.e., t = 11).
|
||||||
|
|
||||||
|
2.4 ITER is the number of iterations on the hash function to calculate P',
|
||||||
|
stored as a 32 bit little-endian value. This value is stored here in order
|
||||||
|
to future-proof the file format against increases in processing power.
|
||||||
|
|
||||||
|
2.5 H(P') is SHA-256(P'), and is used to verify that the user has the
|
||||||
|
correct passphrase.
|
||||||
|
|
||||||
|
2.6 B1 and B2 are two 128-bit blocks encrypted with Twofish [TWOFISH]
|
||||||
|
using P' as the key, in ECB mode. These blocks contain the 256 bit
|
||||||
|
random key K that is used to encrypt the actual records. (This has the
|
||||||
|
property that there is no known or guessable information on the
|
||||||
|
plaintext encrypted with the passphrase-derived key that allows an
|
||||||
|
attacker to mount an attack that bypasses the key stretching
|
||||||
|
algorithm.)
|
||||||
|
|
||||||
|
2.7 B3 and B4 are two 128-bit blocks encrypted with Twofish using P' as the
|
||||||
|
key, in ECB mode. These blocks contain the 256 bit random key L that is
|
||||||
|
used to calculate the HMAC (keyed-hash message authentication code) of the
|
||||||
|
encrypted data. See description of EOF field below for more details.
|
||||||
|
Implementation Note: K and L must NOT be related.
|
||||||
|
|
||||||
|
2.8 IV is the 128-bit random Initial Value for CBC mode.
|
||||||
|
|
||||||
|
2.9 All following records are encrypted using Twofish in CBC mode, with K
|
||||||
|
as the encryption key.
|
||||||
|
|
||||||
|
2.9.1 HDR: The database header. The header consists of one or more typed
|
||||||
|
fields (as defined in section 3.2), terminated by the 'END' type field. The
|
||||||
|
version number field is mandatory. Aside from the 'END' field, no
|
||||||
|
order is assumed on the field types.
|
||||||
|
|
||||||
|
2.9.2 R1..Rn: The actual database records. Each record consists of one or
|
||||||
|
more typed fields (as defined in Section 3.2), terminated by the 'END' type
|
||||||
|
field. The UUID, Title, and Password fields are mandatory. All non-
|
||||||
|
mandatory fields may either be absent or have zero length. When a field is
|
||||||
|
absent or zero-length, its default value shall be used. Aside from the
|
||||||
|
'END' field, no order is assumed on the field types.
|
||||||
|
|
||||||
|
2.10 EOF: The ASCII characters "PWS3-EOFPWS3-EOF" (note that this is
|
||||||
|
exactly one block long), unencrypted. This is an implementation convenience
|
||||||
|
to inform the application that the following bytes are to be processed
|
||||||
|
differently.
|
||||||
|
|
||||||
|
2.11 HMAC: The 256-bit keyed-hash MAC, as described in RFC2104, with SHA-
|
||||||
|
256 as the underlying hash function. The value is calculated over all of
|
||||||
|
the plaintext fields, that is, over all the data stored in all fields
|
||||||
|
(starting from the version number in the header, ending with the last field
|
||||||
|
of the last record). The key L as stored in B3 and B4 is used as the hash
|
||||||
|
key value.
|
||||||
|
|
||||||
|
3. Fields: Data in PasswordSafe is stored in typed fields. Each field
|
||||||
|
consists of one or more blocks. The blocks are the blocks of the underlying
|
||||||
|
encryption algorithm - 16 bytes long for Twofish. The first block contains
|
||||||
|
the field length in the first 4 bytes (little-endian), followed by a one-
|
||||||
|
byte type identifier. The rest of the block contains up to 11 bytes of
|
||||||
|
record data. If the record has less than 11 bytes of data, the extra bytes
|
||||||
|
are filled with random values. The type of a field also defines the data
|
||||||
|
representation.
|
||||||
|
|
||||||
|
=head1 3.1 Data representations
|
||||||
|
|
||||||
|
=head2 3.1.1 UUID
|
||||||
|
|
||||||
|
The UUID data type is 16 bytes long, as defined in RFC4122. Microsoft
|
||||||
|
Windows has functions for this, and the RFC has a sample
|
||||||
|
implementation.
|
||||||
|
|
||||||
|
=head2 3.1.2 Text
|
||||||
|
|
||||||
|
Text is represented in UTF-8 encoding (as defined in RFC3629), with
|
||||||
|
no byte order marker (BOM) and no end-of-string mark (e.g., null
|
||||||
|
byte). Note that the latter isn't neccessary since the length of the
|
||||||
|
field is provided explicitly. Note that ALL fields described as
|
||||||
|
"text" are UTF-8 encoded unless explicitly stated otherwise.
|
||||||
|
|
||||||
|
=head2 3.1.3 Time
|
||||||
|
|
||||||
|
Timestamps are stored as 32 bit, little endian, unsigned integers,
|
||||||
|
representing the number of seconds since Midnight, January 1, 1970, GMT.
|
||||||
|
(This is equivalent to the time_t type on Windows and POSIX. On the
|
||||||
|
Macintosh, the value needs to be adjusted by the constant value 2082844800
|
||||||
|
to account for the different epoch of its time_t type.)
|
||||||
|
Note that future versions of this format may allow time to be
|
||||||
|
specifed in 64 bits as well.
|
||||||
|
|
||||||
|
=head2 3.2 Field types for the PasswordSafe database header:
|
||||||
|
|
||||||
|
Currently
|
||||||
|
Name Value Type Implemented Comments
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
Version 0x00 2 bytes Y [1]
|
||||||
|
UUID 0x01 UUID Y [2]
|
||||||
|
Non-default preferences 0x02 Text Y [3]
|
||||||
|
Tree Display Status 0x03 Text Y [4]
|
||||||
|
Timestamp of last save 0x04 time_t Y [5]
|
||||||
|
Who performed last save 0x05 Text Y [DEPRECATED 6]
|
||||||
|
What performed last save 0x06 Text Y [7]
|
||||||
|
Last saved by user 0x07 Text Y [8]
|
||||||
|
Last saved on host 0x08 Text Y [9]
|
||||||
|
Database Name 0x09 Text Y [10]
|
||||||
|
Database Description 0x0a Text Y [11]
|
||||||
|
Database Filters 0x0b Text Y [12]
|
||||||
|
End of Entry 0xff [empty] Y [13]
|
||||||
|
|
||||||
|
[1] The version number of the database format. For this version, the value
|
||||||
|
is 0x0305 (stored in little-endian format, that is, 0x05, 0x03).
|
||||||
|
|
||||||
|
PasswordSafe V3.01 introduced Format 0x0300
|
||||||
|
PasswordSafe V3.03 introduced Format 0x0301
|
||||||
|
PasswordSafe V3.09 introduced Format 0x0302
|
||||||
|
PasswordSafe V3.12 introduced Format 0x0303
|
||||||
|
PasswordSafe V3.13 introduced Format 0x0304
|
||||||
|
PasswordSafe V3.14 introduced Format 0x0305
|
||||||
|
|
||||||
|
[2] A universally unique identifier is needed in order to synchronize
|
||||||
|
databases, e.g., between a handheld pocketPC device and a
|
||||||
|
PC. Representation is as described in Section 3.1.1.
|
||||||
|
|
||||||
|
[3] Non-default preferences are encoded in a string as follows: The string
|
||||||
|
is of the form "X nn vv X nn vv..." Where X=[BIS] for binary, integer and
|
||||||
|
string respectively, nn is the numeric value of the enum, and vv is the
|
||||||
|
value, {1 or 0} for bool, unsigned integer for int, and a delimited string
|
||||||
|
for String. Only non-default values are stored. See PWSprefs.cpp for more
|
||||||
|
details. Note: normally strings are delimited by the doublequote character.
|
||||||
|
However, if this character is in the string value, an arbitrary character
|
||||||
|
will be chosen to delimit the string.
|
||||||
|
|
||||||
|
[4] If requested to be saved, this is a string of 1s and 0s indicating the
|
||||||
|
expanded state of the tree display when the database was saved. This can
|
||||||
|
be applied at database open time, if the user wishes, so that the tree is
|
||||||
|
displayed as it was. Alternatively, it can be ignored and the tree
|
||||||
|
displayed completely expanded or collapsed. Note that the mapping of
|
||||||
|
the string to the display state is implementation-specific. Introduced
|
||||||
|
in format 0x0301.
|
||||||
|
|
||||||
|
[5] Representation is as described in Section 3.1.3. Note that prior
|
||||||
|
to PasswordSafe 3.09, this field was mistakenly represented as an
|
||||||
|
eight-byte hexadecimal ASCII string. Implementations SHOULD attempt to
|
||||||
|
parse 8-byte long timestamps as a hexadecimal ASCII string
|
||||||
|
representation of the timestamp value.
|
||||||
|
|
||||||
|
[6] Text saved in the format: nnnnu..uh..h, where:
|
||||||
|
nnnn = 4 hexadecimal digits giving length of following user name field
|
||||||
|
u..u = user name
|
||||||
|
h..h = host computer name
|
||||||
|
Note: As of format 0x0302, this field is deprecated, and should be
|
||||||
|
replaced by fields 0x07 and 0x08. In databases prior to format
|
||||||
|
0x0302, this field should be maintained. 0x0302 and later may
|
||||||
|
either maintain this field in addition to fields 0x07 and 0x08,
|
||||||
|
for backwards compatability, or not write this field. If both this
|
||||||
|
field and 0x07, 0x08 exist, they MUST represent the same values.
|
||||||
|
|
||||||
|
[7] Free form text giving the application that saved the database.
|
||||||
|
For example, the Windows PasswordSafe application will use the text
|
||||||
|
"Password Safe Vnn.mm", where nn and mm are the major and minor
|
||||||
|
version numbers. The major version will contain only the significant
|
||||||
|
digits whereas the minor version will be padded to the left with
|
||||||
|
zeroes e.g. "Password Safe V3.02".
|
||||||
|
|
||||||
|
[8] Text containing the username (e.g., login, userid, etc.) of the
|
||||||
|
user who last saved the database, as determined by the appropriate
|
||||||
|
operating-system dependent function. This field was introduced in
|
||||||
|
format version 0x0302, as a replacement for field 0x05. See Comment
|
||||||
|
[6].
|
||||||
|
|
||||||
|
[9] Text containing the hostname (e.g., machine name, hostid, etc.) of the
|
||||||
|
machine on which the database was last saved, as determined by the
|
||||||
|
appropriate operating-system dependent function. This field was
|
||||||
|
introduced in format version 0x0302, as a replacement for field
|
||||||
|
0x05. See Comment [6].
|
||||||
|
|
||||||
|
[10] Database name. A logical name for a database which can be used by
|
||||||
|
applications in place of the possibly lengthy filepath notion. Note
|
||||||
|
that this field SHOULD be limited to what can be displayed in a single
|
||||||
|
line. This field was introduced in format version 0x0302.
|
||||||
|
|
||||||
|
[11] Database Description. A purely informative description concerning
|
||||||
|
the purpose or other practical use of the database. This field was
|
||||||
|
introduced in format version 0x0302.
|
||||||
|
|
||||||
|
[12] Specfic filters for this database. This is the text equivalent to
|
||||||
|
the XML export of the filters as defined by the filter schema. The text
|
||||||
|
'image' has no 'print formatting' e.g. tabs and carraige return/line feeds,
|
||||||
|
since XML processing does not require this. This field was introduced in
|
||||||
|
format version 0x0305.
|
||||||
|
|
||||||
|
[13] An explicit end of entry field is useful for supporting new fields
|
||||||
|
without breaking backwards compatability.
|
||||||
|
|
||||||
|
=head2 3.3 Field types for database Records:
|
||||||
|
|
||||||
|
Currently
|
||||||
|
Name Value Type Implemented Comments
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
UUID 0x01 UUID Y [1]
|
||||||
|
Group 0x02 Text Y [2]
|
||||||
|
Title 0x03 Text Y
|
||||||
|
Username 0x04 Text Y
|
||||||
|
Notes 0x05 Text Y
|
||||||
|
Password 0x06 Text Y [3,4]
|
||||||
|
Creation Time 0x07 time_t Y [5]
|
||||||
|
Password Modification Time 0x08 time_t Y [5]
|
||||||
|
Last Access Time 0x09 time_t Y [5,6]
|
||||||
|
Password Expiry Time 0x0a time_t Y [5,7]
|
||||||
|
*RESERVED* 0x0b 4 bytes - [8]
|
||||||
|
Last Modification Time 0x0c time_t Y [5,9]
|
||||||
|
URL 0x0d Text Y [10]
|
||||||
|
Autotype 0x0e Text Y [11]
|
||||||
|
Password History 0x0f Text Y [12]
|
||||||
|
Password Policy 0x10 Text Y [13]
|
||||||
|
Password Expiry Interval 0x11 2 bytes Y [14]
|
||||||
|
End of Entry 0xff [empty] Y [15]
|
||||||
|
|
||||||
|
[1] Per-record UUID to assist in sync, merge, etc. Representation is
|
||||||
|
as described in Section 3.1.1.
|
||||||
|
|
||||||
|
[2] The "Group" supports displaying the entries in a tree-like manner.
|
||||||
|
Groups can be hierarchical, with elements separated by a period, supporting
|
||||||
|
groups such as "Finance.credit cards.Visa", "Finance.credit
|
||||||
|
cards.Mastercard", Finance.bank.web access", etc. Dots entered by the user
|
||||||
|
should be "escaped" by the application.
|
||||||
|
|
||||||
|
[3] If the entry is an alias, the password will be saved in a special form
|
||||||
|
of "[[uuidstr]]", where "uuidstr" is a 32-character representation of the
|
||||||
|
alias' associated base entry's UUID (field type 0x01). This representation
|
||||||
|
is the same as the standard 36-character string representation as defined in
|
||||||
|
RFC4122 but with the four hyphens removed. If an entry with this UUID is not
|
||||||
|
in the database, this is treated just as an 'unusual' password. The alias
|
||||||
|
will only use its base's password entry when copying it to the clipboard or
|
||||||
|
during Autotype.
|
||||||
|
|
||||||
|
[4] If the entry is a shortcut, the password will be saved in a special form
|
||||||
|
of "[~uuidstr~]", where "uuidstr" is a 32-character representation of the
|
||||||
|
shortcut's associated base entry's UUID (field type 0x01). This representation
|
||||||
|
is the same as the standard 36-character string representation as defined in
|
||||||
|
RFC4122 but with the four hyphens removed. If an entry with this UUID is not
|
||||||
|
in the database, this is treated just as an 'unusual' password. The shortcut
|
||||||
|
will use all its base's data when used in any action. It has no fields of
|
||||||
|
its own.
|
||||||
|
|
||||||
|
[5] Representation is as described in Section 3.1.3.
|
||||||
|
|
||||||
|
[6] This will be updated whenever any part of this entry is accessed
|
||||||
|
i.e., to copy its username, password or notes to the clipboard; to
|
||||||
|
perform autotype or to browse to url.
|
||||||
|
|
||||||
|
[7] This will allow the user to enter an expiry date for an entry. The
|
||||||
|
application can then prompt the user about passwords that need to be
|
||||||
|
changed. A value of zero means "forever".
|
||||||
|
|
||||||
|
[8] Although earmarked for Password Policy, the coding in versions prior
|
||||||
|
to V3.12 does not correctly handle the presence of this field. For this
|
||||||
|
reason, this value cannot be used for any future V3 field without causing
|
||||||
|
a potential issue when a user opens a V3.12 or later database with program
|
||||||
|
version V3.11 or earlier. See note [14].
|
||||||
|
|
||||||
|
[9] This is the time that any field of the record was modified, useful for
|
||||||
|
merging databases.
|
||||||
|
|
||||||
|
[10] The URL will be passed to the shell when the user chooses the "Browse
|
||||||
|
to" action for this entry. In version 2 of the format, this was extracted
|
||||||
|
from the Notes field. By placing it in a separate field, we are no longer
|
||||||
|
restricted to a URL - any action that may be executed by the shell may be
|
||||||
|
specified here.
|
||||||
|
|
||||||
|
[11] The text to be 'typed' by PasswordSafe upon the "Perform Autotype"
|
||||||
|
action maybe specified here. If unspecified, the default value of
|
||||||
|
'username, tab, password, tab, enter' is used. In version 2 of the format,
|
||||||
|
this was extracted from the Notes field. Several codes are recognized here,
|
||||||
|
e.g, '%p' is replaced by the record's password. See the user documentation
|
||||||
|
for the complete list of codes. The replacement is done by the application
|
||||||
|
at runtime, and is not stored in the database.
|
||||||
|
|
||||||
|
[12] Password History is an optional record. If it exists, it stores the
|
||||||
|
creation times and values of the last few passwords used in the current
|
||||||
|
entry, in the following format:
|
||||||
|
"fmmnnTLPTLP...TLP"
|
||||||
|
where:
|
||||||
|
f = {0,1} if password history is on/off
|
||||||
|
mm = 2 hexadecimal digits max size of history list (i.e. max = 255)
|
||||||
|
nn = 2 hexadecimal digits current size of history list
|
||||||
|
T = Time password was set (time_t written out in %08x)
|
||||||
|
L = 4 hexadecimal digit password length (in TCHAR)
|
||||||
|
P = Password
|
||||||
|
No history being kept for a record can be represented either by the lack of
|
||||||
|
the PWH field (preferred), or by a header of _T("00000"):
|
||||||
|
flag = 0, max = 00, num = 00
|
||||||
|
Note that 0aabb, where bb <= aa, is possible if password history was enabled
|
||||||
|
in the past and has then been disabled but the history hasn't been cleared.
|
||||||
|
|
||||||
|
[13] This field allows a specific Password Policy per entry. The format is:
|
||||||
|
|
||||||
|
"ffffnnnllluuudddsss"
|
||||||
|
|
||||||
|
where:
|
||||||
|
|
||||||
|
ffff = 4 hexadecimal digits representing the following flags
|
||||||
|
UseLowercase = 0x8000 - can have a minimum length
|
||||||
|
UseUppercase = 0x4000 - can have a minimum length
|
||||||
|
UseDigits = 0x2000 - can have a minimum length
|
||||||
|
UseSymbols = 0x1000 - can have a minimum length
|
||||||
|
UseHexDigits = 0x0800 (if set, then no other flags can be set)
|
||||||
|
UseEasyVision = 0x0400
|
||||||
|
MakePronounceable = 0x0200
|
||||||
|
Unused 0x01ff
|
||||||
|
nnn = 3 hexadecimal digits password total length
|
||||||
|
lll = 3 hexadecimal digits password minimum number of lowercase characters
|
||||||
|
uuu = 3 hexadecimal digits password minimum number of uppercase characters
|
||||||
|
ddd = 3 hexadecimal digits password minimum number of digit characters
|
||||||
|
sss = 3 hexadecimal digits password minimum number of symbol characters
|
||||||
|
|
||||||
|
[14] Password Expiry Interval, in days, before this password expires. Once set,
|
||||||
|
this value is used when the password is first generated and thereafter whenever
|
||||||
|
the password is changed, until this value is unset. Valid values are 1-3650
|
||||||
|
corresponding to up to approximately 10 years. A value of zero is equivalent to
|
||||||
|
this field not being set.
|
||||||
|
|
||||||
|
[15] An explicit end of entry field is useful for supporting new fields
|
||||||
|
without breaking backwards compatability.
|
||||||
|
|
||||||
|
=head1 4. Extensibility
|
||||||
|
|
||||||
|
4.1 Forward compatability: Implementations of this format SHOULD NOT
|
||||||
|
discard or report an error when encountering a filed of an unknown
|
||||||
|
type. Rather, the field(s) type and data should be read, and perserved
|
||||||
|
when the database is saved.
|
||||||
|
|
||||||
|
4.2 Field type identifiers: This document specifies the field type
|
||||||
|
identifiers for the current version of the format. Compliant
|
||||||
|
implementations MUST support the mandatory fields, and SHOULD support
|
||||||
|
the other fields described herein. Future versions of the format may
|
||||||
|
specify other type identifiers.
|
||||||
|
4.2.1 Application-unique type identifiers: The type identifiers
|
||||||
|
0xc0-0xdf are available for application developers on a first-come
|
||||||
|
first-serve basis. Application developers interested in reserving a
|
||||||
|
type identifier for their application should contact the maintainer of
|
||||||
|
this document (Currently the PasswordSafe project administrator at
|
||||||
|
SourceForge).
|
||||||
|
4.2.2 Application-specific type identifiers: The type identifiers
|
||||||
|
0xe0-0xfe are reserved for implementation-specific purposes, and will
|
||||||
|
NOT be specified in this or future versions of the format
|
||||||
|
description.
|
||||||
|
4.2.3 All unassigned identifiers except as listed in the previous two
|
||||||
|
subsections are reserved, and should not be used by other
|
||||||
|
implementations of this format specification in the interest of
|
||||||
|
interoperablity.
|
||||||
|
|
||||||
|
=head1 5. References:
|
||||||
|
|
||||||
|
[TWOFISH] http://www.schneier.com/paper-twofish-paper.html
|
||||||
|
[SHA256]
|
||||||
|
http://csrc.nist.gov/publications/fips/fips180-2/fips180-2withchangenotice.pdf
|
||||||
|
[KEYSTRETCH] http://www.schneier.com/paper-low-entropy.pdf
|
||||||
|
|
||||||
|
End of Format description.
|
||||||
|
|
||||||
|
=head1 SEE ALSO
|
||||||
|
|
||||||
|
Original source of this file:
|
||||||
|
|
||||||
|
http://passwordsafe.svn.sourceforge.net/viewvc/passwordsafe/trunk/pwsafe/pwsafe/docs/formatV3.txt?revision=2139
|
||||||
|
|
||||||
|
=cut
|
||||||
184
lib/Crypt/PWSafe3/Field.pm
Normal file
184
lib/Crypt/PWSafe3/Field.pm
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
package Crypt::PWSafe3::Field;
|
||||||
|
|
||||||
|
|
||||||
|
use Carp::Heavy;
|
||||||
|
use Carp;
|
||||||
|
use Exporter ();
|
||||||
|
use vars qw(@ISA @EXPORT);
|
||||||
|
use utf8;
|
||||||
|
|
||||||
|
$Crypt::PWSafe3::Field::VERSION = '1.01';
|
||||||
|
|
||||||
|
%Crypt::PWSafe3::Field::map2type = (
|
||||||
|
uuid => 0x01,
|
||||||
|
group => 0x02,
|
||||||
|
title => 0x03,
|
||||||
|
user => 0x04,
|
||||||
|
passwd => 0x06,
|
||||||
|
notes => 0x05,
|
||||||
|
ctime => 0x07,
|
||||||
|
mtime => 0x08,
|
||||||
|
atime => 0x09,
|
||||||
|
reserve => 0x0b,
|
||||||
|
lastmod => 0x0c,
|
||||||
|
url => 0x0d,
|
||||||
|
autotype => 0x0e,
|
||||||
|
pwhist => 0x0f,
|
||||||
|
pwpol => 0x10,
|
||||||
|
pwexp => 0x11,
|
||||||
|
eof => 0xff
|
||||||
|
);
|
||||||
|
%Crypt::PWSafe3::Field::map2name = map { $Crypt::PWSafe3::Field::map2type{$_} => $_ } keys %Crypt::PWSafe3::Field::map2type;
|
||||||
|
|
||||||
|
my @fields = qw(raw len value type name);
|
||||||
|
foreach my $field (@fields) {
|
||||||
|
eval qq(
|
||||||
|
*Crypt::PWSafe3::Field::$field = sub {
|
||||||
|
my(\$this, \$arg) = \@_;
|
||||||
|
if (\$arg) {
|
||||||
|
return \$this->{$field} = \$arg;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return \$this->{$field};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub new {
|
||||||
|
#
|
||||||
|
# new field object
|
||||||
|
my($this, %param) = @_;
|
||||||
|
my $class = ref($this) || $this;
|
||||||
|
my $self = \%param;
|
||||||
|
bless($self, $class);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (! exists $param{type}) {
|
||||||
|
if (exists $param{name}) {
|
||||||
|
$param{type} = $Crypt::PWSafe3::Field::map2type{$param{name}};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
croak "HeaderField needs to have a type/name parameter!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
my @convtime = (0x07, 0x08, 0x09, 0x0a, 0x0c);
|
||||||
|
my @convhex = (0x01);
|
||||||
|
my @convbyte = (0x00, 0x11);
|
||||||
|
|
||||||
|
if (exists $param{raw}) {
|
||||||
|
if (grep { $_ eq $param{type} } @convtime) {
|
||||||
|
$self->{value} = unpack("V", $param{raw});
|
||||||
|
}
|
||||||
|
elsif (grep { $_ eq $param{type} } @convhex) {
|
||||||
|
$self->{value} = unpack('H*', $param{raw});
|
||||||
|
}
|
||||||
|
elsif (grep { $_ eq $param{type} } @convbyte) {
|
||||||
|
$self->{value} = unpack('W*', $param{raw});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$self->{value} = $param{raw};
|
||||||
|
utf8::decode($self->{value});
|
||||||
|
}
|
||||||
|
$self->{len} = length($param{raw});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (exists $param{value}) {
|
||||||
|
if (grep { $_ eq $param{type} } @convtime) {
|
||||||
|
$self->{raw} = pack("V", $param{value});
|
||||||
|
}
|
||||||
|
elsif (grep { $_ eq $param{type} } @convhex) {
|
||||||
|
$self->{raw} = pack('H*', $param{value});
|
||||||
|
}
|
||||||
|
elsif (grep { $_ eq $param{type} } @convbyte) {
|
||||||
|
$self->{raw} = pack('W*', $param{value});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$self->{raw} = $param{value};
|
||||||
|
utf8::encode($param{raw});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
croak "Either raw or value must be given to Crypt::PWSafe3::Field->new()";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$self->{len} = length($param{raw});
|
||||||
|
|
||||||
|
if (exists $Crypt::PWSafe3::Field::map2name{$self->{type}}) {
|
||||||
|
$self->{name} = $Crypt::PWSafe3::Field::map2name{$self->{type}};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$self->{name} = $self->{type};
|
||||||
|
}
|
||||||
|
|
||||||
|
#print "New Field of type $self->{name}\n";
|
||||||
|
#print "Field Value: $self->{value}\n";
|
||||||
|
|
||||||
|
return $self;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub eq {
|
||||||
|
#
|
||||||
|
# compare this field with the given one
|
||||||
|
my ($this, $field) = @_;
|
||||||
|
return $this->type == $field->type and $this->value eq $field->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
=head1 NAME
|
||||||
|
|
||||||
|
Crypt::PWSafe3::Field - represent a passwordsafe v3 record field.
|
||||||
|
|
||||||
|
=head1 SYNOPSIS
|
||||||
|
|
||||||
|
use Crypt::PWSafe3;
|
||||||
|
my $record = $vault->getrecord($uuid);
|
||||||
|
print $record-{field}->{user}->raw();
|
||||||
|
print $record-{field}->{user}->len();
|
||||||
|
|
||||||
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
|
B<Crypt::PWSafe3::Field> represents a record field. This is the
|
||||||
|
raw implementation and you normally don't have to cope with it.
|
||||||
|
|
||||||
|
However, if you ever do, you can do it this way:
|
||||||
|
|
||||||
|
my $field = new Crypt::PWSafe3::Field(
|
||||||
|
value => 'testing',
|
||||||
|
name => 'title
|
||||||
|
);
|
||||||
|
$record->addfield($field);
|
||||||
|
|
||||||
|
This is the preferred way to do it, Crypt::PWSafe3 does
|
||||||
|
it internaly exactly like this.
|
||||||
|
|
||||||
|
If there already exists a record field of this type, it will
|
||||||
|
be overwritten.
|
||||||
|
|
||||||
|
The better way to handle fields is the method B<modifyfield()>
|
||||||
|
of the class L<Crypt::PWSafe3::Record>.
|
||||||
|
|
||||||
|
=head1 SEE ALSO
|
||||||
|
|
||||||
|
L<Crypt::PWSafe3::Record>
|
||||||
|
|
||||||
|
=head1 AUTHOR
|
||||||
|
|
||||||
|
T. Linden <tlinden@cpan.org>
|
||||||
|
|
||||||
|
=head1 COPYRIGHT
|
||||||
|
|
||||||
|
Copyright (c) 2011 by T.Linden <tlinden@cpan.org>.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
=head1 LICENSE
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it
|
||||||
|
and/or modify it under the same terms as Perl itself.
|
||||||
|
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
1;
|
||||||
209
lib/Crypt/PWSafe3/HeaderField.pm
Normal file
209
lib/Crypt/PWSafe3/HeaderField.pm
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
package Crypt::PWSafe3::HeaderField;
|
||||||
|
|
||||||
|
use Carp::Heavy;
|
||||||
|
use Carp;
|
||||||
|
use Exporter ();
|
||||||
|
use vars qw(@ISA @EXPORT);
|
||||||
|
use utf8;
|
||||||
|
|
||||||
|
$Crypt::PWSafe3::HeaderField::VERSION = '1.01';
|
||||||
|
|
||||||
|
%Crypt::PWSafe3::HeaderField::map2name = (
|
||||||
|
0x00 => "version",
|
||||||
|
0x01 => "uuid",
|
||||||
|
0x02 => "preferences",
|
||||||
|
0x03 => "treedisplaystatus",
|
||||||
|
0x04 => "lastsavetime",
|
||||||
|
0x05 => "wholastsaved",
|
||||||
|
0x06 => "whatlastsaved",
|
||||||
|
0x07 => "savedbyuser",
|
||||||
|
0x08 => "savedonhost",
|
||||||
|
0x09 => "databasename",
|
||||||
|
0x0a => "databasedescr",
|
||||||
|
0x0b => "databasefilters",
|
||||||
|
0xff => "eof"
|
||||||
|
);
|
||||||
|
|
||||||
|
%Crypt::PWSafe3::HeaderField::map2type = map { $Crypt::PWSafe3::HeaderField::map2name{$_} => $_ } keys %Crypt::PWSafe3::HeaderField::map2name;
|
||||||
|
|
||||||
|
my @fields = qw(raw len value type name);
|
||||||
|
foreach my $field (@fields) {
|
||||||
|
eval qq(
|
||||||
|
*Crypt::PWSafe3::HeaderField::$field = sub {
|
||||||
|
my(\$this, \$arg) = \@_;
|
||||||
|
if (\$arg) {
|
||||||
|
return \$this->{$field} = \$arg;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return \$this->{$field};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub new {
|
||||||
|
#
|
||||||
|
# new header field object
|
||||||
|
my($this, %param) = @_;
|
||||||
|
my $class = ref($this) || $this;
|
||||||
|
my $self = \%param;
|
||||||
|
bless($self, $class);
|
||||||
|
|
||||||
|
if (! exists $param{type}) {
|
||||||
|
if (exists $param{name}) {
|
||||||
|
if (exists $Crypt::PWSafe3::HeaderField::map2type{$param{name}}) {
|
||||||
|
$param{type} = $Crypt::PWSafe3::HeaderField::map2type{$param{name}};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
croak "Unknown header type $param{name}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
croak "HeaderField needs to have a type/name parameter!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exists $param{raw}) {
|
||||||
|
if ($param{type} == 0x00) {
|
||||||
|
$self->{value} = unpack('H*', $param{raw});# maybe WW or CC ?
|
||||||
|
}
|
||||||
|
elsif ($param{type} == 0x01) {
|
||||||
|
$self->{value} = unpack('H*', $param{raw});
|
||||||
|
}
|
||||||
|
elsif ($param{type} == 0x04) {
|
||||||
|
$self->{value} = unpack('V', $param{raw});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$self->{value} = $param{raw};
|
||||||
|
}
|
||||||
|
$self->{len} = length($param{raw});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (exists $param{value}) {
|
||||||
|
if ($param{type} == 0x00) {
|
||||||
|
$self->{raw} = pack("H*", $param{value});
|
||||||
|
}
|
||||||
|
elsif ($param{type} == 0x01) {
|
||||||
|
$self->{raw} = pack('H*', $param{value});
|
||||||
|
}
|
||||||
|
elsif ($param{type} == 0x04) {
|
||||||
|
$self->{raw} = pack('V', $param{value});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$self->{raw} = $param{value};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
croak "Either raw or value must be given to Crypt::PWSafe3::Field->new()";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$self->{len} = length($param{raw});
|
||||||
|
|
||||||
|
if (exists $Crypt::PWSafe3::HeaderField::map2name{$self->{type}}) {
|
||||||
|
$self->{name} = $Crypt::PWSafe3::HeaderField::map2name{$self->{type}};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$self->{name} = $self->{type};
|
||||||
|
}
|
||||||
|
|
||||||
|
return $self;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub eq {
|
||||||
|
#
|
||||||
|
# compare this field with the given one
|
||||||
|
my ($this, $field) = @_;
|
||||||
|
return $this->type == $field->type and $this->value eq $field->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
=head1 NAME
|
||||||
|
|
||||||
|
Crypt::PWSafe3::HeaderField - represent a passwordsafe v3 header field.
|
||||||
|
|
||||||
|
=head1 SYNOPSIS
|
||||||
|
|
||||||
|
use Crypt::PWSafe3;
|
||||||
|
my $who = $vault->getheader('wholastsaved');
|
||||||
|
print $who->value;
|
||||||
|
|
||||||
|
my $h = new Crypt::PWSafe3::HeaderField(name => 'savedonhost',
|
||||||
|
value => 'localhost');
|
||||||
|
$vault->addheader($h);
|
||||||
|
|
||||||
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
|
B<Crypt::PWSafe3::HeaderField> represents a header field. This is the
|
||||||
|
raw implementation and you normally don't have to cope with it.
|
||||||
|
|
||||||
|
However, if you ever do, you can add/replace any field type
|
||||||
|
this way:
|
||||||
|
|
||||||
|
my $field = new Crypt::PWSafe3::HeaderField(
|
||||||
|
value => 'localhost',
|
||||||
|
name => 'savedonhost'
|
||||||
|
);
|
||||||
|
$record->addheader($field);
|
||||||
|
|
||||||
|
This is the preferred way to do it, Crypt::PWSafe3 does
|
||||||
|
it internaly exactly like this.
|
||||||
|
|
||||||
|
If there already exists a field of this type, it will
|
||||||
|
be overwritten.
|
||||||
|
|
||||||
|
=head1 HEADER FIELDS
|
||||||
|
|
||||||
|
A password safe v3 database supports the following header fields:
|
||||||
|
|
||||||
|
version
|
||||||
|
|
||||||
|
uuid
|
||||||
|
|
||||||
|
preferences
|
||||||
|
|
||||||
|
treedisplaystatus
|
||||||
|
|
||||||
|
lastsavetime
|
||||||
|
|
||||||
|
wholastsaved
|
||||||
|
|
||||||
|
whatlastsaved
|
||||||
|
|
||||||
|
savedbyuser
|
||||||
|
|
||||||
|
savedonhost
|
||||||
|
|
||||||
|
databasename
|
||||||
|
|
||||||
|
databasedescr
|
||||||
|
|
||||||
|
databasefilters
|
||||||
|
|
||||||
|
eof
|
||||||
|
|
||||||
|
Refer to L<Crypt::PWSafe3::Databaseformat> for details on those
|
||||||
|
header fields.
|
||||||
|
|
||||||
|
=head1 SEE ALSO
|
||||||
|
|
||||||
|
L<Crypt::PWSafe3>
|
||||||
|
|
||||||
|
=head1 AUTHOR
|
||||||
|
|
||||||
|
T. Linden <tlinden@cpan.org>
|
||||||
|
|
||||||
|
=head1 COPYRIGHT
|
||||||
|
|
||||||
|
Copyright (c) 2011 by T.Linden <tlinden@cpan.org>.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
=head1 LICENSE
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it
|
||||||
|
and/or modify it under the same terms as Perl itself.
|
||||||
|
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
1;
|
||||||
296
lib/Crypt/PWSafe3/Record.pm
Normal file
296
lib/Crypt/PWSafe3/Record.pm
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
package Crypt::PWSafe3::Record;
|
||||||
|
|
||||||
|
use Carp::Heavy;
|
||||||
|
use Carp;
|
||||||
|
use Exporter ();
|
||||||
|
use vars qw(@ISA @EXPORT %map2name %map2type);
|
||||||
|
|
||||||
|
my %map2type = %Crypt::PWSafe3::Field::map2type;
|
||||||
|
|
||||||
|
my %map2name = %Crypt::PWSafe3::Field::map2name;
|
||||||
|
|
||||||
|
$Crypt::PWSafe3::Record::VERSION = '1.02';
|
||||||
|
|
||||||
|
foreach my $field (keys %map2type ) {
|
||||||
|
eval qq(
|
||||||
|
*Crypt::PWSafe3::Record::$field = sub {
|
||||||
|
my(\$this, \$arg) = \@_;
|
||||||
|
if (\$arg) {
|
||||||
|
return \$this->modifyfield("$field", \$arg);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return \$this->{field}->{$field}->{value};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub new {
|
||||||
|
#
|
||||||
|
# new record object
|
||||||
|
my($this) = @_;
|
||||||
|
my $class = ref($this) || $this;
|
||||||
|
my $self = { };
|
||||||
|
bless($self, $class);
|
||||||
|
$self->{field} = ();
|
||||||
|
|
||||||
|
# just in case this is a record to be filled by the user,
|
||||||
|
# initialize it properly
|
||||||
|
my $newuuid = $self->genuuid();
|
||||||
|
$self->addfield(new Crypt::PWSafe3::Field(
|
||||||
|
name => 'uuid',
|
||||||
|
raw => $newuuid,
|
||||||
|
));
|
||||||
|
|
||||||
|
$self->addfield(new Crypt::PWSafe3::Field(
|
||||||
|
name => 'ctime',
|
||||||
|
value => time,
|
||||||
|
));
|
||||||
|
|
||||||
|
$self->addfield(new Crypt::PWSafe3::Field(
|
||||||
|
name => 'mtime',
|
||||||
|
value => time
|
||||||
|
));
|
||||||
|
|
||||||
|
$self->addfield(new Crypt::PWSafe3::Field(
|
||||||
|
name => 'lastmod',
|
||||||
|
value => time
|
||||||
|
));
|
||||||
|
|
||||||
|
$self->addfield(new Crypt::PWSafe3::Field(
|
||||||
|
name => 'passwd',
|
||||||
|
value => ''
|
||||||
|
));
|
||||||
|
|
||||||
|
$self->addfield(new Crypt::PWSafe3::Field(
|
||||||
|
name => 'user',
|
||||||
|
value => ''
|
||||||
|
));
|
||||||
|
|
||||||
|
$self->addfield(new Crypt::PWSafe3::Field(
|
||||||
|
name => 'title',
|
||||||
|
value => ''
|
||||||
|
));
|
||||||
|
|
||||||
|
return $self;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub modifyfield {
|
||||||
|
#
|
||||||
|
# add or modify a record field
|
||||||
|
my($this, $name, $value) = @_;
|
||||||
|
if (exists $map2type{$name}) {
|
||||||
|
my $type = $map2type{$name};
|
||||||
|
my $field = new Crypt::PWSafe3::Field(
|
||||||
|
type => $type,
|
||||||
|
value => $value
|
||||||
|
);
|
||||||
|
# we are in fact just overwriting an eventually
|
||||||
|
# existing field with a new one, instead of modifying
|
||||||
|
# it, so we are using the conversion automatism in
|
||||||
|
# Field::new()
|
||||||
|
$this->addfield($field);
|
||||||
|
|
||||||
|
# mark the record as modified
|
||||||
|
$this->addfield(new Crypt::PWSafe3::Field(
|
||||||
|
name => 'mtime',
|
||||||
|
value => time
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->addfield(new Crypt::PWSafe3::Field(
|
||||||
|
name => "lastmod",
|
||||||
|
value => time
|
||||||
|
));
|
||||||
|
return $field;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
croak "Unknown field $name";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub genuuid {
|
||||||
|
#
|
||||||
|
# generate a v4 uuid string
|
||||||
|
my($this) = @_;
|
||||||
|
my $ug = new Data::UUID;
|
||||||
|
my $uuid = $ug->create();
|
||||||
|
return $uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub addfield {
|
||||||
|
#
|
||||||
|
# add a field to the record
|
||||||
|
my ($this, $field) = @_;
|
||||||
|
$this->{field}->{ $map2name{$field->type} } = $field;
|
||||||
|
}
|
||||||
|
|
||||||
|
=head1 NAME
|
||||||
|
|
||||||
|
Crypt::PWSafe3::Record - Represents a Passwordsafe v3 data record
|
||||||
|
|
||||||
|
=head1 SYNOPSIS
|
||||||
|
|
||||||
|
use Crypt::PWSafe3;
|
||||||
|
my $record = $vault->getrecord($uuid);
|
||||||
|
$record->title('t2');
|
||||||
|
$record->passwd('foobar');
|
||||||
|
print $record->notes;
|
||||||
|
|
||||||
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
|
B<Crypt::PWSafe3::Record> represents a Passwordsafe v3 data record.
|
||||||
|
Each record consists of a number of fields of type B<Crypt::PWSafe3::Field>.
|
||||||
|
The class provides get/set methods to access the values of those
|
||||||
|
fields.
|
||||||
|
|
||||||
|
It is also possible to access the raw unencoded values of the fields
|
||||||
|
by accessing them directly, refer to L<Crypt::PWSafe3::Field> for more
|
||||||
|
details on this.
|
||||||
|
|
||||||
|
=head1 METHODS
|
||||||
|
|
||||||
|
=head2 B<uuid([string])>
|
||||||
|
|
||||||
|
Returns the UUID without argument. Sets the UUID if an argument
|
||||||
|
is given. Must be a hex representation of an L<Data::UUID> object.
|
||||||
|
|
||||||
|
This will be generated automatically for new records, so you
|
||||||
|
normally don't have to cope with.
|
||||||
|
|
||||||
|
=head2 B<user([string])>
|
||||||
|
|
||||||
|
Returns the username without argument. Sets the username
|
||||||
|
if an argument is given.
|
||||||
|
|
||||||
|
=head2 B<title([string])>
|
||||||
|
|
||||||
|
Returns the title without argument. Sets the title
|
||||||
|
if an argument is given.
|
||||||
|
|
||||||
|
=head2 B<passwd([string])>
|
||||||
|
|
||||||
|
Returns the password without argument. Sets the password
|
||||||
|
if an argument is given.
|
||||||
|
|
||||||
|
=head2 B<notes([string])>
|
||||||
|
|
||||||
|
Returns the notes without argument. Sets the notes
|
||||||
|
if an argument is given.
|
||||||
|
|
||||||
|
=head2 B<group([string])>
|
||||||
|
|
||||||
|
Returns the group without argument. Sets the group
|
||||||
|
if an argument is given.
|
||||||
|
|
||||||
|
Group hierarchy can be done by separating subgroups
|
||||||
|
by dot, eg:
|
||||||
|
|
||||||
|
$record->group('accounts.banking');
|
||||||
|
|
||||||
|
=head2 B<ctime([time_t])>
|
||||||
|
|
||||||
|
Returns the creation time without argument. Sets the creation time
|
||||||
|
if an argument is given. Argument must be an integer timestamp
|
||||||
|
as returned by L<time()>.
|
||||||
|
|
||||||
|
This will be generated automatically for new records, so you
|
||||||
|
normally don't have to cope with.
|
||||||
|
|
||||||
|
=head2 B<atime([time_t])>
|
||||||
|
|
||||||
|
Returns the access time without argument. Sets the access time
|
||||||
|
if an argument is given. Argument must be an integer timestamp
|
||||||
|
as returned by L<time()>.
|
||||||
|
|
||||||
|
B<Crypt::PWSafe3> doesn't update the atime field currently. So if
|
||||||
|
you mind, do it yourself.
|
||||||
|
|
||||||
|
=head2 B<mtime([time_t])>
|
||||||
|
|
||||||
|
Returns the modification time without argument. Sets the modification time
|
||||||
|
if an argument is given. Argument must be an integer timestamp
|
||||||
|
as returned by L<time()>.
|
||||||
|
|
||||||
|
This will be generated automatically for modified records, so you
|
||||||
|
normally don't have to cope with.
|
||||||
|
|
||||||
|
=head2 B<lastmod([string])>
|
||||||
|
|
||||||
|
Returns the modification time without argument. Sets the modification time
|
||||||
|
if an argument is given. Argument must be an integer timestamp
|
||||||
|
as returned by L<time()>.
|
||||||
|
|
||||||
|
This will be generated automatically for modified records, so you
|
||||||
|
normally don't have to cope with.
|
||||||
|
|
||||||
|
I<Note: I don't really know, what's the difference to mtime,
|
||||||
|
so, I update both. If someone knows better, please tell me.>
|
||||||
|
|
||||||
|
=head2 B<url([string])>
|
||||||
|
|
||||||
|
Returns the url without argument. Sets the url
|
||||||
|
if an argument is given. The url must be in the well
|
||||||
|
known notation as:
|
||||||
|
|
||||||
|
proto://host/path
|
||||||
|
|
||||||
|
=head2 B<pwhist([string])>
|
||||||
|
|
||||||
|
Returns the password history without argument. Sets the password history
|
||||||
|
if an argument is given.
|
||||||
|
|
||||||
|
B<Crypt::PWSafe3> doesn't update the pwhist field currently. So if
|
||||||
|
you mind, do it yourself. Refer to L<Crypt::PWSafe3::Databaseformat>
|
||||||
|
for more details.
|
||||||
|
|
||||||
|
=head2 B<pwpol([string])>
|
||||||
|
|
||||||
|
Returns the password policy without argument. Sets the password policy
|
||||||
|
if an argument is given.
|
||||||
|
|
||||||
|
B<Crypt::PWSafe3> doesn't update the pwpol field currently. So if
|
||||||
|
you mind, do it yourself. Refer to L<Crypt::PWSafe3::Databaseformat>
|
||||||
|
for more details.
|
||||||
|
|
||||||
|
=head2 B<pwexp([string])>
|
||||||
|
|
||||||
|
Returns the password expire time without argument. Sets the password expire time
|
||||||
|
if an argument is given.
|
||||||
|
|
||||||
|
B<Crypt::PWSafe3> doesn't update the pwexp field currently. So if
|
||||||
|
you mind, do it yourself. Refer to L<Crypt::PWSafe3::Databaseformat>
|
||||||
|
for more details.
|
||||||
|
|
||||||
|
=head1 MANDATORY FIELDS
|
||||||
|
|
||||||
|
B<Crypt::PWSafe3::Record> creates the following fields automatically
|
||||||
|
on creation, because those fields are mandatory:
|
||||||
|
|
||||||
|
B<uuid> will be generated using L<Data::UUID>.
|
||||||
|
|
||||||
|
B<user, password, title> will be set to the empty string.
|
||||||
|
|
||||||
|
B<ctime, atime, mtime, lastmod> will be set to current
|
||||||
|
time of creation time.
|
||||||
|
|
||||||
|
=head1 SEE ALSO
|
||||||
|
|
||||||
|
L<Crypt::PWSafe3>
|
||||||
|
|
||||||
|
=head1 AUTHOR
|
||||||
|
|
||||||
|
T. Linden <tlinden@cpan.org>
|
||||||
|
|
||||||
|
=head1 COPYRIGHT
|
||||||
|
|
||||||
|
Copyright (c) 2011 by T.Linden <tlinden@cpan.org>.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
=head1 LICENSE
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it
|
||||||
|
and/or modify it under the same terms as Perl itself.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
1;
|
||||||
60
lib/Crypt/PWSafe3/SHA256.pm
Normal file
60
lib/Crypt/PWSafe3/SHA256.pm
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
#
|
||||||
|
# helper class to provide SHA-256 to HMAC class
|
||||||
|
|
||||||
|
package Crypt::PWSafe3::SHA256;
|
||||||
|
|
||||||
|
$Crypt::PWSafe3::SHA256::VERSION = '1.01';
|
||||||
|
|
||||||
|
use Digest::SHA;
|
||||||
|
|
||||||
|
sub new {
|
||||||
|
my($this) = @_;
|
||||||
|
my $class = ref($this) || $this;
|
||||||
|
my $self = { };
|
||||||
|
bless($self, $class);
|
||||||
|
my $sha = new Digest::SHA('SHA-256');
|
||||||
|
return $sha;
|
||||||
|
}
|
||||||
|
|
||||||
|
=head1 NAME
|
||||||
|
|
||||||
|
Crypt::PWSafe3::SHA256 - HMAC Helper Class
|
||||||
|
|
||||||
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
|
This is a small helper class used to work with
|
||||||
|
SHA256 in Digest::HMAC module. This is because the
|
||||||
|
Digest::HMAC module requires a module as parameter
|
||||||
|
for the algorithm but Digest::SHA256 doesn't exist
|
||||||
|
as a module.
|
||||||
|
|
||||||
|
This module here is just a wrapper, it doesn't return
|
||||||
|
an instance of its own but an instance of Digest::SHA('SHA-256')
|
||||||
|
instead.
|
||||||
|
|
||||||
|
=head1 AUTHOR
|
||||||
|
|
||||||
|
T. Linden <tlinden@cpan.org>
|
||||||
|
|
||||||
|
=head1 SEE ALSO
|
||||||
|
|
||||||
|
L<Crypt::PWSafe3>
|
||||||
|
L<Digest::SHA>
|
||||||
|
L<Digest::HMAC>
|
||||||
|
|
||||||
|
=head1 COPYRIGHT
|
||||||
|
|
||||||
|
Copyright (c) 2011 by T.Linden <tlinden@cpan.org>.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
=head1 LICENSE
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it
|
||||||
|
and/or modify it under the same terms as Perl itself.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
1;
|
||||||
108
t/run.t
Normal file
108
t/run.t
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
# -*-perl-*-
|
||||||
|
# testscript for Crypt::PWSafe3 Classes by Thomas Linden
|
||||||
|
#
|
||||||
|
# needs to be invoked using the command "make test" from
|
||||||
|
# the Crypt::PWSafe3 source directory.
|
||||||
|
#
|
||||||
|
# Under normal circumstances every test should succeed.
|
||||||
|
|
||||||
|
|
||||||
|
use Data::Dumper;
|
||||||
|
#use Test::More tests => 57;
|
||||||
|
use Test::More qw(no_plan);
|
||||||
|
|
||||||
|
|
||||||
|
### 1
|
||||||
|
# load module
|
||||||
|
BEGIN { use_ok "Crypt::PWSafe3"};
|
||||||
|
require_ok( 'Crypt::PWSafe3' );
|
||||||
|
|
||||||
|
### 2
|
||||||
|
# open vault and read in all records
|
||||||
|
eval {
|
||||||
|
my $vault = new Crypt::PWSafe3(file => 't/tom.psafe3', password => 'tom');
|
||||||
|
my @r = $vault->getrecords;
|
||||||
|
my $got = 0;
|
||||||
|
foreach my $rec (@r) {
|
||||||
|
if ($rec->uuid) {
|
||||||
|
$got++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! $got) {
|
||||||
|
die "No records found in test database";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ok(!$@, "open a pwsafe3 database");
|
||||||
|
|
||||||
|
### 3
|
||||||
|
# modify an existing record
|
||||||
|
my $uuid3;
|
||||||
|
my %rdata3;
|
||||||
|
my $rec3;
|
||||||
|
my %data3 = (
|
||||||
|
user => 'u3',
|
||||||
|
passwd => 'p3',
|
||||||
|
group => 'g3',
|
||||||
|
title => 't3',
|
||||||
|
notes => 'n3'
|
||||||
|
);
|
||||||
|
eval {
|
||||||
|
my $vault3 = new Crypt::PWSafe3(file => 't/tom.psafe3', password => 'tom');
|
||||||
|
foreach my $uuid ($vault3->looprecord) {
|
||||||
|
$uuid3 = $uuid;
|
||||||
|
$vault3->modifyrecord($uuid3, %data3);
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
$vault3->save(file=>'t/3.out');
|
||||||
|
|
||||||
|
my $rvault3 = new Crypt::PWSafe3(file => 't/3.out', password => 'tom');
|
||||||
|
$rec3 = $rvault3->getrecord($uuid3);
|
||||||
|
|
||||||
|
foreach my $name (keys %data3) {
|
||||||
|
$rdata3{$name} = $rec3->$name();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ok(!$@, "read a pwsafe3 database and change a record ($@)");
|
||||||
|
is_deeply(\%data3, \%rdata3, "Change a record an check if changes persist after saving");
|
||||||
|
|
||||||
|
|
||||||
|
### 4
|
||||||
|
# re-use $rec3 and change it the oop way
|
||||||
|
my $rec4;
|
||||||
|
eval {
|
||||||
|
my $vault4 = new Crypt::PWSafe3(file => 't/3.out', password => 'tom');
|
||||||
|
$rec4 = $vault4->getrecord($uuid3);
|
||||||
|
|
||||||
|
$rec4->user("u4");
|
||||||
|
$rec4->passwd("p4");
|
||||||
|
|
||||||
|
$vault4->addrecord($rec4);
|
||||||
|
$vault4->markmodified();
|
||||||
|
$vault4->save(file=>'t/4.out');
|
||||||
|
|
||||||
|
my $rvault4 = new Crypt::PWSafe3(file => 't/4.out', password => 'tom');
|
||||||
|
$rec4 = $rvault4->getrecord($uuid3);
|
||||||
|
if ($rec4->user ne 'u4') {
|
||||||
|
die "oop way record change failed";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ok(!$@, "re-use record and change it the oop way\n" . $@ . "\n");
|
||||||
|
|
||||||
|
|
||||||
|
### 5 modify some header fields
|
||||||
|
eval {
|
||||||
|
my $vault5 = new Crypt::PWSafe3(file => 't/tom.psafe3', password => 'tom');
|
||||||
|
|
||||||
|
my $h3 = new Crypt::PWSafe3::HeaderField(name => 'savedonhost', value => 'localhost');
|
||||||
|
|
||||||
|
$vault5->addheader($h3);
|
||||||
|
$vault5->markmodified();
|
||||||
|
$vault5->save(file=>'t/5.out');
|
||||||
|
|
||||||
|
my $rvault5 = new Crypt::PWSafe3(file => 't/5.out', password => 'tom');
|
||||||
|
|
||||||
|
if ($rvault5->getheader('savedonhost')->value() ne 'localhost') {
|
||||||
|
die "header savedonhost not correct";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ok(!$@, "modify some header fields ($@)");
|
||||||
BIN
t/tom.psafe3
Normal file
BIN
t/tom.psafe3
Normal file
Binary file not shown.
Reference in New Issue
Block a user