mirror of
https://codeberg.org/scip/Crypt--PWSafe3.git
synced 2025-12-16 12:11:02 +01:00
move to codeberg
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
146
CHANGELOG
146
CHANGELOG
@@ -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
|
||||
14
MANIFEST
14
MANIFEST
@@ -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
|
||||
61
Makefile.PL
61
Makefile.PL
@@ -1,61 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2011-2015 T.v.Dein <tlinden |AT| cpan.org>.
|
||||
#
|
||||
# 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 <tlinden@cpan.org>',
|
||||
'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 );
|
||||
56
README
56
README
@@ -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 <tlinden@cpan.org>
|
||||
|
||||
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<http://www.perlfoundation.org/artistic_license_2_0>
|
||||
|
||||
HOMEPAGE
|
||||
|
||||
The homepage of Config::General is located at:
|
||||
|
||||
http://www.daemon.de/crypt-pwsafe3/.
|
||||
|
||||
|
||||
AUTHOR
|
||||
T.v.Dein <tlinden |AT| cpan.org>
|
||||
|
||||
|
||||
VERSION
|
||||
1.22
|
||||
|
||||
2
README.md
Normal file
2
README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
> [!CAUTION]
|
||||
> This software is now being maintained on [Codeberg](https://codeberg.org/scip/Crypt--PWSafe3/).
|
||||
1013
lib/Crypt/PWSafe3.pm
1013
lib/Crypt/PWSafe3.pm
File diff suppressed because it is too large
Load Diff
@@ -1,425 +0,0 @@
|
||||
=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
|
||||
@@ -1,187 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2011-2015 T.v.Dein <tlinden |AT| cpan.org>.
|
||||
#
|
||||
# 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<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 = 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<modifyfield()>
|
||||
of the class L<Crypt::PWSafe3::Record>.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Crypt::PWSafe3::Record>
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
T.v.Dein <tlinden@cpan.org>
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright (c) 2011-2015 by T.v.Dein <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 of the Artistic
|
||||
License 2.0, see: L<http://www.perlfoundation.org/artistic_license_2_0>
|
||||
|
||||
=cut
|
||||
|
||||
1;
|
||||
@@ -1,216 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2011-2015 T.v.Dein <tlinden |AT| cpan.org>.
|
||||
#
|
||||
# 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<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 = 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<Crypt::PWSafe3::Databaseformat> for details on those
|
||||
header fields.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Crypt::PWSafe3>
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
T.v.Dein <tlinden@cpan.org>
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright (c) 2011-2015 by T.v.Dein <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 of the Artistic
|
||||
License 2.0, see: L<http://www.perlfoundation.org/artistic_license_2_0>
|
||||
|
||||
=cut
|
||||
|
||||
1;
|
||||
@@ -1,211 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2011-2015 T.v.Dein <tlinden |AT| cpan.org>.
|
||||
#
|
||||
# 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<raw> parameter.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Crypt::PWSafe3::Record>
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
T.v.Dein <tlinden@cpan.org>
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright (c) 2011-2015 by T.v.Dein <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 of the Artistic
|
||||
License 2.0, see: L<http://www.perlfoundation.org/artistic_license_2_0>
|
||||
|
||||
|
||||
=cut
|
||||
|
||||
1;
|
||||
@@ -1,380 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2011-2015 T.v.Dein <tlinden |AT| cpan.org>.
|
||||
#
|
||||
# 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<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.
|
||||
|
||||
If the record object has been created by L<Crypt::PWSafe3> (and fetched with
|
||||
Crypt::PWSafe3::getrecord), then it's still associated with the L<Crypt::PWSafe3>
|
||||
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<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 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<time()>.
|
||||
|
||||
This will be generated automatically for modified records if the passwd field changed, 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.
|
||||
|
||||
=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.
|
||||
|
||||
This is the raw encoded policy string. If you want to access it, use the
|
||||
B<policy()> method, see below.
|
||||
|
||||
=head2 B<policy([Crypt::PWSafe3::PasswordPolicy object])>
|
||||
|
||||
If called without arguments, returns a Crypt::PWSafe3::PasswordPolicy
|
||||
object. See L<Crypt::PWSafe3::PasswordPolicy> 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<policy> method.
|
||||
|
||||
=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.v.Dein <tlinden@cpan.org>
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright (c) 2011-2015 by T.v.Dein <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 of the Artistic
|
||||
License 2.0, see: L<http://www.perlfoundation.org/artistic_license_2_0>
|
||||
|
||||
=cut
|
||||
|
||||
1;
|
||||
@@ -1,65 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2011-2015 T.v.Dein <tlinden |AT| cpan.org>.
|
||||
#
|
||||
# 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 <tlinden@cpan.org>
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Crypt::PWSafe3>
|
||||
L<Digest::SHA>
|
||||
L<Digest::HMAC>
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright (c) 2011-2015 by T.v.Dein <tlinden@cpan.org>.
|
||||
|
||||
=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<http://www.perlfoundation.org/artistic_license_2_0>
|
||||
|
||||
=cut
|
||||
|
||||
|
||||
|
||||
|
||||
1;
|
||||
@@ -1,50 +0,0 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
use lib qw(blib/lib);
|
||||
use Crypt::PWSafe3;
|
||||
|
||||
|
||||
my $file = shift;
|
||||
|
||||
if (!$file) {
|
||||
print STDERR "Usage $0 <vault>\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;
|
||||
}
|
||||
|
||||
218
t/run.t
218
t/run.t
@@ -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');
|
||||
BIN
t/tom.psafe3
BIN
t/tom.psafe3
Binary file not shown.
Reference in New Issue
Block a user