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