diff --git a/.woodpecker/build.yaml b/.woodpecker/build.yaml deleted file mode 100644 index b1511c5..0000000 --- a/.woodpecker/build.yaml +++ /dev/null @@ -1,31 +0,0 @@ -matrix: - include: - # - image: perl:5.22.4-stretch - # - image: perl:5.36.0-slim-bullseye - # - image: perl:5.38.0-slim-bookworm - # - image: perl:5.40.0-slim-bookworm - # - image: perl:5.42.0-slim-bookworm - - image: perl:5.43.5-slim-bookworm - -steps: - test: - when: - event: [push] - image: ${image} - commands: - - apt-get update -y - - apt-get install -y gcc - - cpanm -n Digest::SHA3 - - cpanm -n Crypt::PBKDF2 - - cpanm -n Crypt::Cipher::AES - - cpanm -n Crypt::CBC - - cpanm -n Digest::SHA - - cpanm -n Crypt::ECB - - cpanm -n Crypt::Twofish - - cpanm -n Data::UUID - - cpanm -n Shell - - cpanm -n File::Temp - - cpanm -n Crypt::Random - - perl Makefile.PL - - make - - make test diff --git a/.woodpecker/release.sh b/.woodpecker/release.sh deleted file mode 100755 index a44513a..0000000 --- a/.woodpecker/release.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/bash - -# This is my own simple codeberg generic releaser. It takes to -# binaries to be uploaded as arguments and takes every other args from -# env. Works on tags or normal commits (push), tags must start with v. - - -set -e - -die() { - echo $* - exit 1 -} - -if test -z "$DEPLOY_TOKEN"; then - die "token DEPLOY_TOKEN not set" -fi - -git fetch --all - -# determine current tag or commit hash -version="$CI_COMMIT_TAG" -previous="" -log="" -if test -z "$version"; then - version="${CI_COMMIT_SHA:0:6}" - log=$(git log -1 --oneline) -else - previous=$(git tag -l | grep -E "^v" | tac | grep -A1 "$version" | tail -1) - log=$(git log -1 --oneline "${previous}..${version}" | sed 's|^|- |g') -fi - -# release body -printf "# Changes\n\n %s\n" "$log" > body.txt - -# create the release -https --ignore-stdin --check-status -b -A bearer -a "$DEPLOY_TOKEN" POST \ - "https://codeberg.org/api/v1/repos/${CI_REPO_OWNER}/${CI_REPO_NAME}/releases" \ - tag_name="$version" name="Release $version" body=@body.txt > release.json - -# we need the id to upload files -ID=$(jq -r .id < release.json) - -if test -z "$ID"; then - cat release.json - die "failed to create release" -fi - -# actually upload -for file in "$@"; do - https --ignore-stdin --check-status -A bearer -a "$DEPLOY_TOKEN" -f POST \ - "https://codeberg.org/api/v1/repos/${CI_REPO_OWNER}/${CI_REPO_NAME}/releases/$ID/assets" \ - "name=${file}" "attachment@${file}" -done diff --git a/.woodpecker/release.yaml b/.woodpecker/release.yaml deleted file mode 100644 index a285cc4..0000000 --- a/.woodpecker/release.yaml +++ /dev/null @@ -1,23 +0,0 @@ -# build release - -steps: - compile: - when: - event: [tag] - image: perl:5.43.5-slim-bookworm - commands: - - perl Makefile.PL - - make - - make dist - - release: - image: alpine:latest - when: - event: [tag] - environment: - DEPLOY_TOKEN: - from_secret: DEPLOY_TOKEN - commands: - - apk update - - apk add --no-cache bash httpie jq git - - .woodpecker/release.sh Crypt-PWSafe3-$CI_COMMIT_TAG.tar.gz diff --git a/CHANGELOG b/CHANGELOG deleted file mode 100644 index 606c362..0000000 --- a/CHANGELOG +++ /dev/null @@ -1,146 +0,0 @@ -1.22: Records created by Crypt::PWSafe3 (eg. the ones fetched - with getrecords) are now associated with the parent - object, so that you can modify them directly and call - $vault->save afterwards without using $vault->modifyrecord. - - Erase passwd from memory using zeros instead of - random bytes. fixes github#9. - - Fixed rt.cpan.org#112975: Crypt::ECB (which we use) have been - reworked and among other issues it fixed handline of padding. - PWSafe3.pm did not specify a padding scheme (because it doesn't - use it) and therefore Crypt::ECB enforced a default scheme - which lead to an invalid key size. Now we specify explicitly - padding:none, as suggested by Christoph Appel. - -1.21: - forgot to load File::Spec - -1.20: - applied another patch by David Dick: writing tmp files in - the same directory where the vault file resides (unless it's - not writable). - -1.19: - applied patch by David Dick, which adds some more precautions - of i/o error handling and flushing. - -1.17: - added license to META - - open tmpfiles with O_EXLOCK disabled (cpantesters) - - added new parameter 'create', enabled by default, - which creates a new vault if enabled and dies otherwise. - - enhanced unit tests to report if read/write of files fails. - - added unit test to create a new vault - - added POD for newrecord() and addrecord(), previously - missing. - - replaced "new $perl::$object" with $perl::$object->new() - everywhere. - - using weak random source for unit tests to avoid blocking - /dev/random on cpantesters systems with heavy entropy - load. - -1.16 - re-licensed from artistic1 to artistic2 in order to be - compatible to fedora packaging. no code changes otherwise - -1.15 - fixed github#8, using File::Temp instead of self baked - File::Spec. - -1.14 - fixed github#7: added PasswordPolicy.pm to MANIFEST. - -1.13 - added Crypt::PWSafe3::PasswordPolicy submodule, which allows - to access the password policy of a PasswordSafe record. - -1.12 - I somehow managed to mangle version numbers of sub modules, - now all properly incremented. - -1.11 - fixed: - https://github.com/TLINDEN/Crypt--PWSafe3/issues/6 - https://github.com/TLINDEN/Crypt--PWSafe3/issues/5 - This was NOT caused by polish characters, but by the - password expire field being set (I didn't use it so far - and my test database doesn't contain records with this - field set). The 'W<*' pack identifier had been used - for this field and W doesn't support < indeed. I changed - it to use 'S<' now, which is a 2 byte little endian - value according to the db-spec. I also edited the - test database so that it contains the field now, so that - a make test catches it. - - fixed cpantester problem with taintmode unlink() call, - now $tmpfile is untainted before - -1.10 - I forgot to fix the pack() format as well. - -1.09 - the unpack() formatstring for the uuid (field 0x01) "L<4" didn't - return a hex string, but a number only, which is fine for most - cases, but isn't a correct Data::UUID representation. Changed back - to "H*". - -1.08 - fixed pack/unpack formats to use strictly little-endian values as required. - machine-dependend unpack() formats changed to machine-independend. - -1.07 - applied patch by https://github.com/Mekk: - https://github.com/TLINDEN/Crypt--PWSafe3/pull/3, - fix import of Bytes::Random::Secure. - -1.06 - applied patch by https://github.com/Mekk: - https://github.com/TLINDEN/Crypt--PWSafe3/pull/2, - adds new function "deleterecord()", improves performance - when using Bytes::Random::Secure[now optional] and fixes - an error in ::Record::addfield(). - -1.05 - applied patch by https://github.com/Mekk: - https://github.com/TLINDEN/Crypt--PWSafe3/pull/1, - which replaces use of cp and mv commands with File::Copy. - this makes it portable. - -1.04 - - fixed rt.cpan.org#75145. uninitialized fields lead to - program abort. solved by pre-initializing them in the - new records method. types notes and groups affected. - - fixed rt.cpan.org#75146. mtime will only modified if - the passwd field changed. POD adjusted. Fix suggested - by Luca Filipozzi - thx. - - -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 diff --git a/MANIFEST b/MANIFEST deleted file mode 100644 index ec8b5f6..0000000 --- a/MANIFEST +++ /dev/null @@ -1,14 +0,0 @@ -lib/Crypt/PWSafe3/Databaseformat.pm -lib/Crypt/PWSafe3/PasswordPolicy.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 -sample/test.pl diff --git a/Makefile.PL b/Makefile.PL deleted file mode 100644 index afad6ee..0000000 --- a/Makefile.PL +++ /dev/null @@ -1,61 +0,0 @@ -# -# Copyright (c) 2011-2015 T.v.Dein . -# -# Licensed under the terms of the Artistic License 2.0 -# see: http://www.perlfoundation.org/artistic_license_2_0 -# - -require 5.004; -use ExtUtils::MakeMaker; - -my %optional = ( - 'Bytes::Random::Secure' => 0.09, - ); - -foreach my $module (sort keys %optional) { - eval "require $module"; - if ($@) { - warn("Optional module $module not installed, $optional{$module}\n"); - } -} - - -my %params = ( - '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, - 'Data::UUID' => 1.217, - 'Shell' => 0.5, - 'File::Temp' => 0, - }, - 'AUTHOR' => 'T.v.Dein ', - 'clean' => { - FILES => 't/*.out *~ */*~ */*/*~ */*/*/*~' - }, - ($ExtUtils::MakeMaker::VERSION >= 6.3002 ? ('LICENSE' => 'perl', ) : ()), -); - -# Already tried requiring Bytes::Random::Secure earlier, so now check the version -# and if it's OK, add a dependency on it; otherwise, fall back to Crypt::Random -if (eval { Bytes::Random::Secure->VERSION('0.09') }) { - $params{'PREREQ_PM'}{'Bytes::Random::Secure'} = 0.09; -} else { - $params{'PREREQ_PM'}{'Crypt::Random'} = 1.25; -}; - -if ( $ExtUtils::MakeMaker::VERSION ge '6.46' ) { - $params{META_MERGE} = { - resources => { - homepage => 'http://www.daemon.de/', - bugtracker => 'http://github.com/tlinden/Crypt--PWSafe3', - license => 'http://www.perlfoundation.org/artistic_license_2_0', - repository => 'git://github.com/tlinden/Crypt--PWSafe3.git', - } - }; -} - -WriteMakefile( %params ); diff --git a/README b/README deleted file mode 100644 index b146f2b..0000000 --- a/README +++ /dev/null @@ -1,56 +0,0 @@ -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-2016 by T.v.Dein - -LICENSE - - This program is free software; you can redistribute it - and/or modify it under the same terms of the Artistic - License 2.0, see: L - -HOMEPAGE - - The homepage of Config::General is located at: - - http://www.daemon.de/crypt-pwsafe3/. - - -AUTHOR - T.v.Dein - - -VERSION - 1.22 - diff --git a/README.md b/README.md new file mode 100644 index 0000000..bb2ada1 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +> [!CAUTION] +> This software is now being maintained on [Codeberg](https://codeberg.org/scip/Crypt--PWSafe3/). diff --git a/lib/Crypt/PWSafe3.pm b/lib/Crypt/PWSafe3.pm deleted file mode 100644 index a2bd1fd..0000000 --- a/lib/Crypt/PWSafe3.pm +++ /dev/null @@ -1,1013 +0,0 @@ -# -# Copyright (c) 2011-2016 T.v.Dein . -# -# Licensed under the terms of the Artistic License 2.0 -# see: http://www.perlfoundation.org/artistic_license_2_0 -# -# Implements: -# http://passwordsafe.svn.sourceforge.net/viewvc/passwordsafe/trunk/pwsafe/pwsafe/docs/formatV3.txt?revision=2139 - -package Crypt::PWSafe3; - -use strict; - -use Config; - -use Carp::Heavy; -use Carp; - -use Crypt::CBC; -use Crypt::ECB; -use Crypt::Twofish; -use Digest::HMAC; -use Digest::SHA; -use Data::UUID; -use File::Copy qw(copy move); -use File::Temp; -use File::Spec; -use FileHandle; -use Data::Dumper; -use Exporter (); -use vars qw(@ISA @EXPORT); - -$Crypt::PWSafe3::VERSION = '1.23'; - -use Crypt::PWSafe3::Field; -use Crypt::PWSafe3::HeaderField; -use Crypt::PWSafe3::Record; -use Crypt::PWSafe3::SHA256; -use Crypt::PWSafe3::PasswordPolicy; - -require 5.10.0; - -# -# check which random source to use. -# install a wrapper closure around the -# one we found. -BEGIN { - eval { - require Bytes::Random::Secure; - Bytes::Random::Secure->import("random_bytes"); - }; - if ($@) { - # well, didn' work, use slow function - eval { require Crypt::Random; };# qw( makerandom ); }; - if ($@) { - croak "Could not find either Crypt::Random or Bytes::Random::Secure. Install one of them and retry!"; - } - else { - *Crypt::PWSafe3::random = sub { - my($this, $len) = @_; - return makerandom(Size => $len * 8, Strength => 1, Uniform => 1); - }; - } - } - else { - # good. use the faster one - *Crypt::PWSafe3::random = sub { - my($this, $len) = @_; - return random_bytes($len); - }; - } -} - -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, create - 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->{create}) { - $self->{create} = 1; - } - - if (! exists $self->{file}) { - $self->{file} = ''; - $self->create(); - } - else { - if (! -s $self->{file} || ! -r $self->{file}) { - if ($self->{create}) { - $self->create(); - } - else { - croak "PWSafe3 file $self->{file} does not exist or is not readable"; - } - } - 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 = Digest::SHA->new('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 = 0 x 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 = Digest::SHA->new('SHA-256'); - $sha->reset(); - $sha->add( ( $this->strechedpw() ) ); - $this->shaps( $sha->digest() ); - - # encrypt b1 .. b4 - my $crypt = Crypt::ECB->new; - $crypt->padding('none'); - $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} = Digest::HMAC->new($this->keyl, "Crypt::PWSafe3::SHA256"); - $this->{cipher} = Crypt::CBC->new( - -key => $this->keyk, - -iv => $this->iv, - -cipher => 'Twofish', - -header => 'none', - -padding => 'rijndael_compat', - -literal_key => 1, - -keysize => 32, - ); - - # empty for now - $this->hmac( $this->{hmacer}->digest() ); -} - -sub read { - # - # read and decrypt an existing vault file - my($this) = @_; - - my $file = $this->file(); - my $fd = FileHandle->new($file, 'r') or croak "Could not open $file for reading: $!"; - $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("L<", $this->readbytes(4) ) ); - - $this->strechedpw($this->stretchpw($this->password())); - - my $sha = Digest::SHA->new(256); - $sha->reset(); - $sha->add( ( $this->strechedpw() ) ); - $this->shaps( $sha->digest() ); - - my $fileshaps = $this->readbytes(32); - 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->padding('none'); - $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)); - - $this->iv( $this->readbytes(16) ); - - # create hmac'er and cipher for actual encryption - $this->{hmacer} = Digest::HMAC->new($this->keyl, "Crypt::PWSafe3::SHA256"); - $this->{cipher} = Crypt::CBC->new( - -key => $this->keyk, - -iv => $this->iv, - -cipher => 'Twofish', - -header => 'none', - -padding => 'rijndael_compat', - -literal_key => 1, - -keysize => 32, - ); - - # 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 = Crypt::PWSafe3::Record->new(super => $this); - $this->{record} = {}; - while (1) { - my $field = $this->readfield(); - if (! $field) { - last; - } - if ($field->type == 0xff) { - $this->addrecord($record); - $record = Crypt::PWSafe3::Record->new(super => $this); - } - 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, invalid HMAC"; - } - - $this->{fd}->close() or croak "Could not close $file: $!"; -} - -sub untaint { - # - # untaint path's - my ($this, $path) = @_; - if($path =~ /([\w\-\/\\\.:]+\z)/) { - return $1; - } - else { - # fail, return unchanged - return $path; - } -} - -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 = Crypt::PWSafe3::HeaderField->new(type => 0x04, value => time); - my $whatsaved = Crypt::PWSafe3::HeaderField->new(type => 0x06, value => $this->{program}); - my $whosaved = Crypt::PWSafe3::HeaderField->new(type => 0x05, value => $this->{whoami}); - $this->addheader($lastsave); - $this->addheader($whatsaved); - $this->addheader($whosaved); - - # open a temp vault file, first we try it in the same directory as the target vault file - my $tmpdir; - if (File::Spec->file_name_is_absolute($file)) { - my ($volume, $directories, undef) = File::Spec->splitpath($file); - $tmpdir = File::Spec->catdir($volume, $directories); - } - else { - my ($volume, $directories, undef) = File::Spec->splitpath(File::Spec->rel2abs($file)); - $tmpdir = File::Spec->abs2rel(File::Spec->catdir($volume, $directories)); - } - - my $fd; - if (-w $tmpdir) { - $fd = File::Temp->new(TEMPLATE => '.vaultXXXXXXXX', DIR => $tmpdir, EXLOCK => 0) or croak "Could not open tmpfile: $!\n"; - } - else { - # well, then we'll use one in the tmp dir of the system - $fd = File::Temp->new(TEMPLATE => '.vaultXXXXXXXX', TMPDIR => 1, EXLOCK => 0) or croak "Could not open tmpfile: $!\n"; - } - my $tmpfile = "$fd"; - - $this->{fd} = $fd; - - $this->writebytes($this->tag); - $this->writebytes($this->salt); - $this->writebytes(pack("L<", $this->iter)); - - $this->strechedpw($this->stretchpw($passwd)); - - # line 472 - my $sha = Digest::SHA->new(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->padding('none'); - $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} = Digest::HMAC->new($this->keyl, "Crypt::PWSafe3::SHA256"); - $this->{cipher} = Crypt::CBC->new( - -key => $this->keyk, - -iv => $this->iv, - -cipher => 'Twofish', - -header => 'none', - -padding => 'rijndael_compat', - -literal_key => 1, - -keysize => 32, - ); - - my $eof = Crypt::PWSafe3::HeaderField->new(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 = Crypt::PWSafe3::Field->new(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(Crypt::PWSafe3::Field->new(type => 'none', raw => 0)); - - $this->hmac( $this->{hmacer}->digest() ); - $this->writebytes($this->hmac); - if ($Config{d_fsync}) { - $this->{fd}->sync() or croak "Could not fsync: $!"; - } - $this->{fd}->close() or croak "Could not close tmpfile: $!"; - - # now try to read it in again to check if it - # is valid what we created - eval { - my $vault = Crypt::PWSafe3->new(file => $tmpfile, create => 0, password => $passwd); - }; - if ($@) { - unlink $tmpfile; - croak "File integrity check failed ($@)"; - } - else { - # well, seems to be ok :) - move($tmpfile, $file) or croak "Could not move $tmpfile to $file: $!"; - } -} - -sub writefield { - # - # write a field to vault file - my($this, $field) = @_; - - if ($field->type eq 'none') { - $this->writebytes("PWS3-EOFPWS3-EOF"); - return; - } - - my $len = pack("L<", $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) { - my $part = substr($data, 0, 16); - $crypt .= $this->encrypt($part); - if (length($data) <= 16) { - last; - } - else { - $data = substr($data, 16); - } - } - $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 deleterecord { - # - # delete a record identified by the given uuid, if present - # returns 1 if record was actually removed, 0 if it was not present - my($this, $uuid) = @_; - - if (! exists $this->{record}->{$uuid}) { - return 0; - } - - delete $this->{record}->{$uuid}; - - # mark vault as modified - $this->markmodified(); - - return 1; -} - - -sub markmodified { - # - # mark the vault as modified by setting the appropriate header fields - my($this) = @_; - my $lastmod = Crypt::PWSafe3::HeaderField->new( - name => "lastsavetime", - value => time - ); - my $who = Crypt::PWSafe3::HeaderField->new( - 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 = Crypt::PWSafe3::Record->new(); - foreach my $field (keys %fields) { - $record->modifyfield($field, $fields{$field}); - } - $this->markmodified(); - $this->addrecord($record); - $this->{records}->{$record->uuid}->{super} = $this; - 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; - } - - $data = $this->decrypt($data); - - my $len = unpack("L<", substr($data, 0, 4)); - my $type = unpack("C", substr($data, 4, 1)); - my $raw = substr($data, 5); - - 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 Crypt::PWSafe3::HeaderField->new(type => $type, raw => $raw); - } - else { - return Crypt::PWSafe3::Field->new(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 then 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; - 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 getheader { - # - # return a header object - my($this, $name) = @_; - 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 = Crypt::PWSafe3->new(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 = Crypt::PWSafe3::HeaderField->new(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 - -The new() method creates a new Crypt::PWSafe3 object. Any parameters -must be given as hash parameters. - - my $vault = Crypt::PWSafe3->new( - file => 'vault.psafe3', - password => 'secret', - whoami => 'user1', - program => 'mypwtool v1', - create => 0, # or 1 - ); - -Mandatory parameters: - -=over - -=item B - -Specifies the password safe (v3) file. If it exists -it will be read in. Otherwise it will be created -if you call B. - -=item B - -The password required to decrypt the password safe file. - -=back - -Optional parameters: - -=over - -=item B - -Specifies the user who saves the password safe file. -If omitted the environment variable USER will be used -when calling B. - -=item B - -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. - -=item B - -If set to 0, B will fail if the file doesn't exist, -otherwise it will try to create it. Enabled by default. - -=back - -The optional parameters will become header fields of -the password safe file. You can manually set/override -more headers. See section L for -more details. - -=head2 B - -Returns a list of all records found in the password -safe file. Each element is an B -object. - -A record object is identified by its B 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. - -B: record objects returned by getrecords() are -still associated with the L object. So, -if you modify a field of such a record, the change will -be populated back into the vault. Of course you'd still -need to save it. - -Sample: - - foreach my $rec ($vault->getrecords) { - $rec->note('blah fasel'); - } - $vault->save(); - -However, it's also possible to use the B -method, see below. - -=head2 B - -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 - -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 -for details about available fields. - -=head2 B - -Create a new record. The UUID of the record will be generated -automatically. Refer to L -for details about available fields. - -=head2 B - -Add a record to the vault. The record must be an -L object. - -=head2 B - -Delete the record identified by the given UUID. - -=head2 B - -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 - -Returns a raw B object. -Refer to L for details -how to access it. - -=head2 B - -Adds a header field to the password safe database. The -object parameter must be an B -object. - -If the header already exists it will be replaced. - -Refer to L for details -how to create new ones -. - -=head1 AUTHOR - -T.v.Dein - -=head1 BUGS - -Report bugs to -http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Crypt-PWSafe3. - - -=head1 SEE ALSO - -Subclasses: - -L -L -L - -Password Safe Homepage: -L - -Another (read-only) perl module: -L - -A python port of Password Safe: -L -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-2016 by T.v.Dein . - -=head1 LICENSE - -This program is free software; you can redistribute it -and/or modify it under the same terms of the Artistic -License 2.0, see: L - -=head1 VERSION - -Crypt::PWSafe3 Version 1.22. - -=cut - - - - -1; - diff --git a/lib/Crypt/PWSafe3/Databaseformat.pm b/lib/Crypt/PWSafe3/Databaseformat.pm deleted file mode 100644 index faac3e5..0000000 --- a/lib/Crypt/PWSafe3/Databaseformat.pm +++ /dev/null @@ -1,425 +0,0 @@ -=head1 NAME - -PasswordSafe database format description version 3.03 - - -=head1 LICENSE - -Copyright (c) 2003-2008 Rony Shapiro . -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 diff --git a/lib/Crypt/PWSafe3/Field.pm b/lib/Crypt/PWSafe3/Field.pm deleted file mode 100644 index 43ce381..0000000 --- a/lib/Crypt/PWSafe3/Field.pm +++ /dev/null @@ -1,187 +0,0 @@ -# -# Copyright (c) 2011-2015 T.v.Dein . -# -# Licensed under the terms of the Artistic License 2.0 -# see: http://www.perlfoundation.org/artistic_license_2_0 -# -package Crypt::PWSafe3::Field; - - -use Carp::Heavy; -use Carp; -use Exporter (); -use vars qw(@ISA @EXPORT); -use utf8; - -$Crypt::PWSafe3::Field::VERSION = '1.06'; - -%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("L<", $param{raw}); - } - elsif (grep { $_ eq $param{type} } @convhex) { - $self->{value} = unpack('H*', $param{raw}); - } - elsif (grep { $_ eq $param{type} } @convbyte) { - $self->{value} = unpack('S<', $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("L<", $param{value}); - } - elsif (grep { $_ eq $param{type} } @convhex) { - $self->{raw} = pack('H*', $param{value}); - } - elsif (grep { $_ eq $param{type} } @convbyte) { - $self->{raw} = pack('S<', $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}; - } - - 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 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 = Crypt::PWSafe3::Field->new( - 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 -of the class L. - -=head1 SEE ALSO - -L - -=head1 AUTHOR - -T.v.Dein - -=head1 COPYRIGHT - -Copyright (c) 2011-2015 by T.v.Dein . -All rights reserved. - -=head1 LICENSE - -This program is free software; you can redistribute it -and/or modify it under the same terms of the Artistic -License 2.0, see: L - -=cut - -1; diff --git a/lib/Crypt/PWSafe3/HeaderField.pm b/lib/Crypt/PWSafe3/HeaderField.pm deleted file mode 100644 index 5110cba..0000000 --- a/lib/Crypt/PWSafe3/HeaderField.pm +++ /dev/null @@ -1,216 +0,0 @@ -# -# Copyright (c) 2011-2015 T.v.Dein . -# -# Licensed under the terms of the Artistic License 2.0 -# see: http://www.perlfoundation.org/artistic_license_2_0 -# -# -package Crypt::PWSafe3::HeaderField; - -use Carp::Heavy; -use Carp; -use Exporter (); -use vars qw(@ISA @EXPORT); -use utf8; - -$Crypt::PWSafe3::HeaderField::VERSION = '1.05'; - -%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('L<2', $param{raw});# maybe WW or CC ? - } - elsif ($param{type} == 0x01) { - $self->{value} = unpack('L<4', $param{raw}); - } - elsif ($param{type} == 0x04) { - $self->{value} = unpack('L<', $param{raw}); - } - else { - $self->{value} = $param{raw}; - } - $self->{len} = length($param{raw}); - } - else { - if (exists $param{value}) { - if ($param{type} == 0x00) { - $self->{raw} = pack("L<2", $param{value}); - } - elsif ($param{type} == 0x01) { - $self->{raw} = pack('L<4', $param{value}); - } - elsif ($param{type} == 0x04) { - $self->{raw} = pack('L<', $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 = Crypt::PWSafe3::HeaderField->new(name => 'savedonhost', - value => 'localhost'); - $vault->addheader($h); - -=head1 DESCRIPTION - -B 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 = Crypt::PWSafe3::HeaderField->new( - 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 for details on those -header fields. - -=head1 SEE ALSO - -L - -=head1 AUTHOR - -T.v.Dein - -=head1 COPYRIGHT - -Copyright (c) 2011-2015 by T.v.Dein . -All rights reserved. - -=head1 LICENSE - -This program is free software; you can redistribute it -and/or modify it under the same terms of the Artistic -License 2.0, see: L - -=cut - -1; diff --git a/lib/Crypt/PWSafe3/PasswordPolicy.pm b/lib/Crypt/PWSafe3/PasswordPolicy.pm deleted file mode 100644 index a5c747a..0000000 --- a/lib/Crypt/PWSafe3/PasswordPolicy.pm +++ /dev/null @@ -1,211 +0,0 @@ -# -# Copyright (c) 2011-2015 T.v.Dein . -# -# Licensed under the terms of the Artistic License 2.0 -# see: http://www.perlfoundation.org/artistic_license_2_0 -# -package Crypt::PWSafe3::PasswordPolicy; - - -use Carp::Heavy; -use Carp; -use Exporter (); -use vars qw(@ISA @EXPORT); -use utf8; - -$Crypt::PWSafe3::PasswordPolicy::VERSION = '1.01'; - -my %flagbits = ( - UseLowercase => 0x8000, - UseUppercase => 0x4000, - UseDigits => 0x2000, - UseSymbols => 0x1000, - UseHexDigits => 0x0800, - UseEasyVision => 0x0400, - MakePronounceable => 0x0200 -); - - -my @flags = qw(UseLowercase UseUppercase UseDigits UseSymbols UseHexDigits UseEasyVision MakePronounceable); -my @fields = qw(raw MaxLength MinLowercase MinUppercase MinDigits MinSymbols); - -foreach my $field (@fields, @flags) { - eval qq( - *Crypt::PWSafe3::PasswordPolicy::$field = sub { - my(\$this, \$arg) = \@_; - if (\$arg) { - return \$this->{$field} = \$arg; - } - else { - return \$this->{$field}; - } - } - ); -} - -sub new { - # - # new PasswordPolicy object - my($this, %param) = @_; - my $class = ref($this) || $this; - my $self = \%param; - bless($self, $class); - - - if (exists $param{raw}) { - $self->decode($param{raw}); - } - else { - foreach my $field (@fields, @flags) { - if(exists $param{$field}) { - $self->{$field} = $param{$field}; - } - else { - $self->{$field} = 0; - } - } - } - - return $self; -} - - -sub decode { - my($this, $raw) = @_; - - return if $raw eq ''; - - # expected input: ffffnnnllluuudddsss - - # create a 6-elemt array - my @exp = unpack("A4A3A3A3A3A3", $raw); - - # convert the hex strings to integers - my %pwpol; - foreach my $i (0 .. 5) { - $pwpol{$fields[$i]} = hex($exp[$i]); - } - - # assign the numbers to interns - foreach my $field (@fields) { - next if $field eq "raw"; - $this->{$field} = $pwpol{$field}; - } - - # convert binary flags to true/false values - foreach my $bit (keys %flagbits) { - if($pwpol{raw} & $flagbits{$bit}) { - $this->{$bit} = 1; - } - else { - $this->{$bit} = 0; - } - } -} - - -sub encode { - my($this) = @_; - - # create the bitmask - my $mask = 0; - foreach my $bit (keys %flagbits) { - if($this->{$bit}) { - $mask |= $flagbits{$bit}; - } - } - - # first 4hex chars for the bitmask of policy flags - my $raw = sprintf "%04x", $mask; - - # followed by the number fields - foreach my $field (@fields) { - next if $field eq "raw"; - $raw .= sprintf "%03x", $this->{$field}; - } - - if($raw =~ /^0*$/) { - return ''; - } - else { - return $raw; - } -} - - - -=head1 NAME - -Crypt::PWSafe3::PasswordPolicy - represent a passwordsafe v3 passwprd policy entry of a record. - -=head1 SYNOPSIS - - use Data::Dumper; - use Crypt::PWSafe3; - use Crypt::PWSafe3::PasswordPolicy; - my $record = $vault->getrecord($uuid); - my $policy = $record->policy; - - # print current values - print Dumper($policy); - - # print some values - print $policy->UseEasyVision; - - # change some of them - $policy->MaxLength(8); - $policy->MinSymbols(2); - - # put back into record - $record->policy($policy); - - - -=head1 DESCRIPTION - -The following flags can be set (1 = TRUE, 0 = FALSE): - - - UseLowercase - - UseUppercase - - UseDigits - - UseSymbols - - UseHexDigits - - UseEasyVision - - MakePronounceable - -The following numerical settings can be tuned: - - - MaxLength - - MinLowercase - - MinUppercase - - MinDigits - - MinSymbols - -All of them can be called as functions, see SYNOPSIS for examples. If called with an argument, -the value will be changed, otherwise it will just returned. - -The raw database value can be assigned by using the B parameter. - -=head1 SEE ALSO - -L - -=head1 AUTHOR - -T.v.Dein - -=head1 COPYRIGHT - -Copyright (c) 2011-2015 by T.v.Dein . -All rights reserved. - -=head1 LICENSE - -This program is free software; you can redistribute it -and/or modify it under the same terms of the Artistic -License 2.0, see: L - - -=cut - -1; diff --git a/lib/Crypt/PWSafe3/Record.pm b/lib/Crypt/PWSafe3/Record.pm deleted file mode 100644 index 3b2f217..0000000 --- a/lib/Crypt/PWSafe3/Record.pm +++ /dev/null @@ -1,380 +0,0 @@ -# -# Copyright (c) 2011-2015 T.v.Dein . -# -# Licensed under the terms of the Artistic License 2.0 -# see: http://www.perlfoundation.org/artistic_license_2_0 -# -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.10'; - -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, %param) = @_; - my $class = ref($this) || $this; - my $self = \%param; - 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(); - - my $time = time; - - $self->addfield(Crypt::PWSafe3::Field->new( - name => 'uuid', - raw => $newuuid, - )); - - $self->addfield(Crypt::PWSafe3::Field->new( - name => 'ctime', - value => $time, - )); - - $self->addfield(Crypt::PWSafe3::Field->new( - name => 'mtime', - value => $time - )); - - $self->addfield(Crypt::PWSafe3::Field->new( - name => 'lastmod', - value => $time - )); - - $self->addfield(Crypt::PWSafe3::Field->new( - name => 'passwd', - value => '' - )); - - $self->addfield(Crypt::PWSafe3::Field->new( - name => 'user', - value => '' - )); - - $self->addfield(Crypt::PWSafe3::Field->new( - name => 'title', - value => '' - )); - - $self->addfield(Crypt::PWSafe3::Field->new( - name => 'notes', - value => '' - )); - - $self->addfield(Crypt::PWSafe3::Field->new( - name => 'group', - 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 = Crypt::PWSafe3::Field->new( - type => $type, - value => $value - ); - - my $time = time; - - # 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 field as modified if it's passwd field - $this->addfield(Crypt::PWSafe3::Field->new( - name => 'mtime', - value => $time - )) if $name eq 'passwd'; - - $this->addfield(Crypt::PWSafe3::Field->new( - name => "lastmod", - value => $time - )); - - my ($package, $filename, $line, $subroutine, @ignore) = caller(1); - - # this looks a little bit weird but it's a cool feat. - # 'super' contains the vault object (of class Crypt::PWSafe3), - # which initially called our new() method, so we know to which - # vault we belong. - # therefore, if the user just calls $record->passwd('newpw'), - # then we can update the record directly on the vault object, - # so that the user doesn't have to call modifyrecord. this is - # especially usefull inside a loop. - # also note, that the 'super' parameter to Crypt::PWSafe3::Record::new() - # is not documented, so it's an internal parameter not to be used - # by users. however, maybe in the future it would be useful to - # have it populated so that if a user has a function which takes a - # record as parameter, then in this function he could access the - # vault as well. maybe. - # - # Thu May 21 10:04:15 CEST 2015 tlinden\@cpan.org - if (exists $this->{super} && - "${package}::${subroutine}" !~ /Crypt::PWSafe3::modifyrecord$/ && - "${package}::${subroutine}" !~ /Crypt::PWSafe3::newrecord$/ && - "${package}::${subroutine}" !~ /Crypt::PWSafe3::Record::modifyfield$/ - ) { - # we've been called from the outside (the user in fact) and - # we're attached to a vault, so update ourselfes there as well - $this->{super}->modifyrecord($this->uuid, $name, $value); - } - - return $field; - } - else { - croak "Unknown field $name"; - } -} - -sub genuuid { - # - # generate a v4 uuid string - my($this) = @_; - my $ug = Data::UUID->new(); - my $uuid = $ug->create(); - return $uuid; -} - -sub addfield { - # - # add a field to the record - my ($this, $field) = @_; - my $name = $map2name{$field->type}; - unless( defined($name) ) { - $name = $field->type; # consistent with Field->new - } - $this->{field}->{ $name } = $field; -} - -sub policy { - # - # return or set a password policy - my ($this, $policy) = @_; - - if($policy) { - $this->{policy} = $policy; - $this->pwpol($policy->encode()); - } - else { - $this->{policy} = Crypt::PWSafe3::PasswordPolicy->new(raw => $this->pwpol); - } - - return $this->{policy}; -} - -=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 represents a Passwordsafe v3 data record. -Each record consists of a number of fields of type B. -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 for more -details on this. - -If the record object has been created by L (and fetched with -Crypt::PWSafe3::getrecord), then it's still associated with the L -parent object. Changes to the record will therefore automatically populated -back into the parent object (the vault). This is not the case if you created -the record object yourself. - -=head1 METHODS - -=head2 B - -Returns the UUID without argument. Sets the UUID if an argument -is given. Must be a hex representation of an L object. - -This will be generated automatically for new records, so you -normally don't have to cope with. - -=head2 B - -Returns the username without argument. Sets the username -if an argument is given. - -=head2 B - -Returns the title without argument. Sets the title -if an argument is given. - -=head2 B - -Returns the password without argument. Sets the password -if an argument is given. - -=head2 B - -Returns the notes without argument. Sets the notes -if an argument is given. - -=head2 B - -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 - -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. - -This will be generated automatically for new records, so you -normally don't have to cope with. - -=head2 B - -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. - -B doesn't update the atime field currently. So if -you mind, do it yourself. - -=head2 B - -Returns the modification time of the passwd field without argument. Sets the modification time -if an argument is given. Argument must be an integer timestamp -as returned by L. - -This will be generated automatically for modified records if the passwd field changed, so you -normally don't have to cope with. - -=head2 B - -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. - -This will be generated automatically for modified records, so you -normally don't have to cope with. - -=head2 B - -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 - -Returns the password history without argument. Sets the password history -if an argument is given. - -B doesn't update the pwhist field currently. So if -you mind, do it yourself. Refer to L -for more details. - -=head2 B - -Returns the password policy without argument. Sets the password policy -if an argument is given. - -This is the raw encoded policy string. If you want to access it, use the -B method, see below. - -=head2 B - -If called without arguments, returns a Crypt::PWSafe3::PasswordPolicy -object. See L for details, how to access -it. - -To modify the password policy, create new Crypt::PWSafe3::PasswordPolicy -object or modify the existing one and pass it as argument to the -B method. - -=head2 B - -Returns the password expire time without argument. Sets the password expire time -if an argument is given. - -B doesn't update the pwexp field currently. So if -you mind, do it yourself. Refer to L -for more details. - -=head1 MANDATORY FIELDS - -B creates the following fields automatically -on creation, because those fields are mandatory: - -B will be generated using L. - -B will be set to the empty string. - -B will be set to current -time of creation time. - -=head1 SEE ALSO - -L - -=head1 AUTHOR - -T.v.Dein - -=head1 COPYRIGHT - -Copyright (c) 2011-2015 by T.v.Dein . -All rights reserved. - -=head1 LICENSE - -This program is free software; you can redistribute it -and/or modify it under the same terms of the Artistic -License 2.0, see: L - -=cut - -1; diff --git a/lib/Crypt/PWSafe3/SHA256.pm b/lib/Crypt/PWSafe3/SHA256.pm deleted file mode 100644 index 39d2897..0000000 --- a/lib/Crypt/PWSafe3/SHA256.pm +++ /dev/null @@ -1,65 +0,0 @@ -# -# Copyright (c) 2011-2015 T.v.Dein . -# -# Licensed under the terms of the Artistic License 2.0 -# see: http://www.perlfoundation.org/artistic_license_2_0 -# -# helper class to provide SHA-256 to HMAC class - -package Crypt::PWSafe3::SHA256; - -$Crypt::PWSafe3::SHA256::VERSION = '1.03'; - -use Digest::SHA; - -sub new { - my($this) = @_; - my $class = ref($this) || $this; - my $self = { }; - bless($self, $class); - my $sha = Digest::SHA->new('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.v.Dein - -=head1 SEE ALSO - -L -L -L - -=head1 COPYRIGHT - -Copyright (c) 2011-2015 by T.v.Dein . - -=head1 LICENSE - -This program is free software; you can redistribute it -and/or modify it under the same terms of the Artistic -License 2.0, see: L - -=cut - - - - -1; diff --git a/sample/test.pl b/sample/test.pl deleted file mode 100755 index 6b459ea..0000000 --- a/sample/test.pl +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/perl - -use lib qw(blib/lib); -use Crypt::PWSafe3; - - -my $file = shift; - -if (!$file) { - print STDERR "Usage $0 \n"; - exit 1; -} - -my $create = 1; -if (-e $file) { - $create = 0; -} - -my $vault = Crypt::PWSafe3->new(file => $file, create => $create, password => 'blah') or die "$!"; - -if ($create) { - my %record = ( - user => 'u3', - passwd => 'p3', - group => 'g3', - title => 't3', - notes => scalar localtime(time) - ); - $vault->newrecord(%record); - $vault->save(); - print "record saved to $file, execute $0 again to view it\n" -} -else { - my @r = $vault->getrecords; - foreach my $rec (@r) { - printf qq(%s: - User: %s -Passwd: %s - Group: %s - Title: %s - Notes: %s -), $rec->uuid, $rec->user, $rec->passwd, $rec->group, $rec->title, $rec->notes; - - $rec->notes( scalar localtime(time)); -# $vault->modifyrecord($rec->uuid, notes => scalar localtime(time)); - } - - $vault->save; -} - diff --git a/t/run.t b/t/run.t deleted file mode 100644 index bac8541..0000000 --- a/t/run.t +++ /dev/null @@ -1,218 +0,0 @@ -#!perl -T -# -# testscript for Crypt::PWSafe3 Classes by T.v.Dein -# -# needs to be invoked using the command "make test" from -# the Crypt::PWSafe3 source directory. -# -# Under normal circumstances every test should succeed. -# -# Licensed under the terms of the Artistic License 2.0 -# see: http://www.perlfoundation.org/artistic_license_2_0 -# - -use Data::Dumper; -use Test::More tests => 13; -#use Test::More qw(no_plan); - - -my %params = (create => 0, password => 'tom'); - -my %record = ( - user => 'u3', - passwd => 'p3', - group => 'g3', - title => 't3', - notes => 'n3' - ); - - - -sub rdpw { - my $file = shift; - my $vault = Crypt::PWSafe3->new(file => $file, %params) or die "$!"; - return $vault; -} - -### 1 -# load module -BEGIN { use_ok "Crypt::PWSafe3"}; -require_ok( 'Crypt::PWSafe3' ); - -{ - # I'm going to replace the secure random number generator - # backends with this very primitive and insecure one, because - # these are only unit tests and because we use external modules - # for the purpose anyway (which are not to be tested with these - # unit tests). - # This has to be done so that unit tests running on cpantesters - # don't block if we use a real (and exhausted) random source, - # which has reportedly happened in the past. - # ***** CAUTION: DO NOT USE THIS CODE IN PRODUCTION. EVER. **** - no warnings 'redefine'; - *Crypt::PWSafe3::random = sub { return join'',map{chr(int(rand(255)))}(1..$_[1]); }; -}; - -### 2 -# open vault and read in all records -eval { - my $vault = &rdpw('t/tom.psafe3'); - 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 ($@)"); - - -### 1a -# create a new vault -my %rdata1a; -my $fd = File::Temp->new(TEMPLATE => '.myvaultXXXXXXXX', TMPDIR => 1, EXLOCK => 0) or die "Could not open tmpfile: $!\n"; -my $tmpfile = "$fd"; -close($fd); - -eval { - my $vault = Crypt::PWSafe3->new(file => $tmpfile, password => 'tom') or die "$!"; - $vault->newrecord(%record); - $vault->save(); -}; -ok(!$@, "create a new pwsafe3 database ($@)"); - -eval { - my $rvault1a = &rdpw($tmpfile); - my $rec1a = ($rvault1a->getrecords())[0]; - foreach my $name (keys %record) { - $rdata1a{$name} = $rec1a->$name(); - } -}; -ok(!$@, "read created new pwsafe3 database ($@)"); -is_deeply(\%record, \%rdata1a, "Write record to a new pwsafe3 database"); -unlink($tmpfile); - -### 3 -# modify an existing record -my $uuid3; -my %rdata3; -my $rec3; - -eval { - my $vault3 = &rdpw('t/tom.psafe3'); - foreach my $uuid ($vault3->looprecord) { - $uuid3 = $uuid; - $vault3->modifyrecord($uuid3, %record); - last; - } - $vault3->save(file=>'t/3.out'); - - my $rvault3 = &rdpw('t/3.out'); - $rec3 = $rvault3->getrecord($uuid3); - - foreach my $name (keys %record) { - $rdata3{$name} = $rec3->$name(); - } -}; -ok(!$@, "read a pwsafe3 database and change a record, traditional method ($@)"); -is_deeply(\%record, \%rdata3, "Change a record an check if changes persist after saving, traditional method"); -diag("3 done\n"); - -### 3a -# modify an existing record, new method -my $uuid3a; -my %rdata3a; -my $rec3a; - -eval { - my $vault3a = &rdpw('t/tom.psafe3'); - foreach my $rec ($vault3a->getrecords) { - $rec->notes('n3a'); - $uuid3a = $rec->uuid; - last; - } - $vault3a->save(file=>'t/3a.out'); - - my $rvault3a = &rdpw('t/3a.out'); - $rec3a = $rvault3a->getrecord($uuid3a); -}; -ok(!$@, "read a pwsafe3 database and change a record, new method ($@)"); -is_deeply($rec3a->notes, 'n3a', "Change a record an check if changes persist after saving, new method"); - - -### 4 -# re-use $rec3 and change it the oop way -my $rec4; -eval { - my $vault4 = &rdpw('t/tom.psafe3'); - $rec4 = $vault4->getrecord($uuid3); - - $rec4->user("u4"); - $rec4->passwd("p4"); - - $vault4->addrecord($rec4); - $vault4->markmodified(); - $vault4->save(file=>'t/4.out'); - - my $rvault4 = &rdpw('t/4.out'); - $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 = &rdpw('t/tom.psafe3'); - - my $h3 = new Crypt::PWSafe3::HeaderField(name => 'savedonhost', value => 'localhost'); - - $vault5->addheader($h3); - $vault5->markmodified(); - $vault5->save(file=>'t/5.out'); - - my $rvault5 = &rdpw('t/5.out'); - - if ($rvault5->getheader('savedonhost')->value() ne 'localhost') { - die "header savedonhost not correct"; - } -}; -ok(!$@, "modify some header fields ($@)"); - -### 6 delete -eval { - my $vault6 = &rdpw('t/3.out'); - my $uuid = $vault6->newrecord(user => 'xxx', passwd => 'y'); - $vault6->save(file=>'t/6.out'); - - my $rvault6 = &rdpw('t/6.out'); - my $rec = $rvault6->getrecord($uuid); - if ($rec->user ne 'xxx') { - die "oop way record change failed"; - } - $rvault6->deleterecord($uuid); - if ($rvault6->getrecord($uuid)) { - die "deleted record still present in open vault"; - } - $vault6->save(file=>'t/6a.out'); - - my $rvault6a = &rdpw('t/6a.out'); - if ($rvault6->getrecord($uuid)) { - die "deleted record reappears after save and reload"; - } -}; -ok(!$@, "delete record\n" . $@ . "\n"); - - -### clean temporary files -unlink('t/3.out'); -unlink('t/4.out'); -unlink('t/5.out'); -unlink('t/6.out'); -unlink('t/6a.out'); diff --git a/t/tom.psafe3 b/t/tom.psafe3 deleted file mode 100644 index 3b983f5..0000000 Binary files a/t/tom.psafe3 and /dev/null differ