release 2.023000
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
2.023000 -
|
||||
2.023000 - 2014-02-10
|
||||
|
||||
[NEW FEATURES]
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ lib/App/Netdisco.pm
|
||||
lib/App/Netdisco/Core/Arpnip.pm
|
||||
lib/App/Netdisco/Core/Discover.pm
|
||||
lib/App/Netdisco/Core/Macsuck.pm
|
||||
lib/App/Netdisco/Core/Nbtstat.pm
|
||||
lib/App/Netdisco/Daemon/DB.pm
|
||||
lib/App/Netdisco/Daemon/DB/Result/Admin.pm
|
||||
lib/App/Netdisco/Daemon/Queue.pm
|
||||
@@ -37,6 +38,7 @@ lib/App/Netdisco/Daemon/Worker/Poller/Common.pm
|
||||
lib/App/Netdisco/Daemon/Worker/Poller/Device.pm
|
||||
lib/App/Netdisco/Daemon/Worker/Poller/Expiry.pm
|
||||
lib/App/Netdisco/Daemon/Worker/Poller/Macsuck.pm
|
||||
lib/App/Netdisco/Daemon/Worker/Poller/Nbtstat.pm
|
||||
lib/App/Netdisco/Daemon/Worker/Scheduler.pm
|
||||
lib/App/Netdisco/DB.pm
|
||||
lib/App/Netdisco/DB/ExplicitLocking.pm
|
||||
@@ -71,6 +73,7 @@ lib/App/Netdisco/DB/Result/Virtual/ActiveNode.pm
|
||||
lib/App/Netdisco/DB/Result/Virtual/ActiveNodeWithAge.pm
|
||||
lib/App/Netdisco/DB/Result/Virtual/ApRadioChannelPower.pm
|
||||
lib/App/Netdisco/DB/Result/Virtual/CidrIps.pm
|
||||
lib/App/Netdisco/DB/Result/Virtual/DeviceDnsMismatch.pm
|
||||
lib/App/Netdisco/DB/Result/Virtual/DeviceLinks.pm
|
||||
lib/App/Netdisco/DB/Result/Virtual/DuplexMismatch.pm
|
||||
lib/App/Netdisco/DB/Result/Virtual/NodeWithAge.pm
|
||||
@@ -89,10 +92,10 @@ lib/App/Netdisco/DB/ResultSet/Device.pm
|
||||
lib/App/Netdisco/DB/ResultSet/DevicePort.pm
|
||||
lib/App/Netdisco/DB/ResultSet/Node.pm
|
||||
lib/App/Netdisco/DB/ResultSet/NodeIp.pm
|
||||
lib/App/Netdisco/DB/ResultSet/NodeNbt.pm
|
||||
lib/App/Netdisco/DB/ResultSet/NodeWireless.pm
|
||||
lib/App/Netdisco/DB/ResultSet/Subnet.pm
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-1-2-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-1-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-10-11-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-11-12-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-12-13-PostgreSQL.sql
|
||||
@@ -100,13 +103,10 @@ lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-13-14-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-14-15-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-15-16-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-16-17-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-16-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-17-18-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-17-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-18-19-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-19-20-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-2-3-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-2-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-20-21-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-21-22-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-22-23-PostgreSQL.sql
|
||||
@@ -122,12 +122,15 @@ lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-30-31-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-31-32-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-32-33-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-33-34-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-34-35-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-35-36-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-36-37-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-37-38-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-4-5-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-5-6-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-6-7-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-7-8-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-8-9-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-8-PostgreSQL.sql
|
||||
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-9-10-PostgreSQL.sql
|
||||
lib/App/Netdisco/Manual/Configuration.pod
|
||||
lib/App/Netdisco/Manual/Deployment.pod
|
||||
@@ -136,6 +139,7 @@ lib/App/Netdisco/Manual/ReleaseNotes.pod
|
||||
lib/App/Netdisco/Manual/WritingPlugins.pod
|
||||
lib/App/Netdisco/Util/Device.pm
|
||||
lib/App/Netdisco/Util/DNS.pm
|
||||
lib/App/Netdisco/Util/Node.pm
|
||||
lib/App/Netdisco/Util/Noop.pm
|
||||
lib/App/Netdisco/Util/Port.pm
|
||||
lib/App/Netdisco/Util/PortMAC.pm
|
||||
@@ -170,10 +174,12 @@ lib/App/Netdisco/Web/Plugin/Report/ApClients.pm
|
||||
lib/App/Netdisco/Web/Plugin/Report/ApRadioChannelPower.pm
|
||||
lib/App/Netdisco/Web/Plugin/Report/DeviceAddrNoDNS.pm
|
||||
lib/App/Netdisco/Web/Plugin/Report/DeviceByLocation.pm
|
||||
lib/App/Netdisco/Web/Plugin/Report/DeviceDnsMismatch.pm
|
||||
lib/App/Netdisco/Web/Plugin/Report/DevicePoeStatus.pm
|
||||
lib/App/Netdisco/Web/Plugin/Report/DuplexMismatch.pm
|
||||
lib/App/Netdisco/Web/Plugin/Report/HalfDuplex.pm
|
||||
lib/App/Netdisco/Web/Plugin/Report/IpInventory.pm
|
||||
lib/App/Netdisco/Web/Plugin/Report/Netbios.pm
|
||||
lib/App/Netdisco/Web/Plugin/Report/NodeMultiIPs.pm
|
||||
lib/App/Netdisco/Web/Plugin/Report/PhonesDiscovered.pm
|
||||
lib/App/Netdisco/Web/Plugin/Report/PortAdminDown.pm
|
||||
@@ -241,6 +247,7 @@ share/public/images/tango_sweep.png
|
||||
share/public/images/vaga_copy.png
|
||||
share/public/javascripts/bootstrap.min.js
|
||||
share/public/javascripts/d3.min.js
|
||||
share/public/javascripts/daterangepicker.js
|
||||
share/public/javascripts/jquery-deserialize.js
|
||||
share/public/javascripts/jquery-history.js
|
||||
share/public/javascripts/jquery-latest.min.js
|
||||
@@ -248,6 +255,7 @@ share/public/javascripts/jquery-ui.custom.min.js
|
||||
share/public/javascripts/jquery.cookie.js
|
||||
share/public/javascripts/jquery.floatThead.js
|
||||
share/public/javascripts/jquery.qtip.min.js
|
||||
share/public/javascripts/moment.min.js
|
||||
share/public/javascripts/netdisco.js
|
||||
share/public/javascripts/netdisco_portcontrol.js
|
||||
share/public/javascripts/toastr.js
|
||||
@@ -281,6 +289,8 @@ share/views/ajax/report/deviceaddrnodns.tt
|
||||
share/views/ajax/report/deviceaddrnodns_csv.tt
|
||||
share/views/ajax/report/devicebylocation.tt
|
||||
share/views/ajax/report/devicebylocation_csv.tt
|
||||
share/views/ajax/report/devicednsmismatch.tt
|
||||
share/views/ajax/report/devicednsmismatch_csv.tt
|
||||
share/views/ajax/report/devicepoestatus.tt
|
||||
share/views/ajax/report/devicepoestatus_csv.tt
|
||||
share/views/ajax/report/duplexmismatch.tt
|
||||
@@ -289,6 +299,7 @@ share/views/ajax/report/halfduplex.tt
|
||||
share/views/ajax/report/halfduplex_csv.tt
|
||||
share/views/ajax/report/ipinventory.tt
|
||||
share/views/ajax/report/ipinventory_csv.tt
|
||||
share/views/ajax/report/netbios.tt
|
||||
share/views/ajax/report/nodemultiips.tt
|
||||
share/views/ajax/report/nodemultiips_csv.tt
|
||||
share/views/ajax/report/phonesdiscovered.tt
|
||||
@@ -321,9 +332,7 @@ share/views/inventory.tt
|
||||
share/views/js/admintask.js
|
||||
share/views/js/bootstrap-tree.js
|
||||
share/views/js/common.js
|
||||
share/views/js/daterangepicker.js
|
||||
share/views/js/device.js
|
||||
share/views/js/moment.min.js
|
||||
share/views/js/report.js
|
||||
share/views/js/search.js
|
||||
share/views/layouts/main.tt
|
||||
@@ -334,6 +343,7 @@ share/views/sidebar/admintask/portlog.tt
|
||||
share/views/sidebar/device/netmap.tt
|
||||
share/views/sidebar/device/ports.tt
|
||||
share/views/sidebar/report/ipinventory.tt
|
||||
share/views/sidebar/report/netbios.tt
|
||||
share/views/sidebar/report/subnets.tt
|
||||
share/views/sidebar/search/device.tt
|
||||
share/views/sidebar/search/node.tt
|
||||
|
||||
@@ -34,7 +34,7 @@ requires:
|
||||
Dancer: 1.3112
|
||||
Dancer::Plugin::Auth::Extensible: 0.3
|
||||
Dancer::Plugin::DBIC: 0.1803
|
||||
Dancer::Plugin::Passphrase: 2
|
||||
Dancer::Plugin::Passphrase: 2.0.1
|
||||
File::ShareDir: 1.03
|
||||
Guard: 1.022
|
||||
HTML::Parser: 3.7
|
||||
@@ -53,7 +53,7 @@ requires:
|
||||
Plack: 1.0023
|
||||
Plack::Middleware::Expires: 0.03
|
||||
Role::Tiny: 1.002005
|
||||
SNMP::Info: 3.11
|
||||
SNMP::Info: 3.12
|
||||
SQL::Translator: 0.11016
|
||||
Socket6: 0.23
|
||||
Starman: 0.4008
|
||||
@@ -74,4 +74,4 @@ resources:
|
||||
homepage: http://netdisco.org/
|
||||
license: http://opensource.org/licenses/bsd-license.php
|
||||
repository: git://git.code.sf.net/p/netdisco/netdisco-ng
|
||||
version: 2.022000
|
||||
version: 2.023000
|
||||
|
||||
@@ -18,9 +18,7 @@ requires 'Daemon::Control' => 0.001000;
|
||||
requires 'Dancer' => 1.3112;
|
||||
requires 'Dancer::Plugin::DBIC' => 0.1803;
|
||||
requires 'Dancer::Plugin::Auth::Extensible' => 0.30;
|
||||
requires 'Data::Entropy::Algorithms' => 0.007;
|
||||
requires 'Digest::Bcrypt' => 1.0.1;
|
||||
requires 'Digest::SHA' => 5.86;
|
||||
requires 'Dancer::Plugin::Passphrase' => '2.0.1';
|
||||
requires 'File::ShareDir' => 1.03;
|
||||
requires 'Guard' => 1.022;
|
||||
requires 'HTML::Parser' => 3.70;
|
||||
@@ -41,7 +39,7 @@ requires 'Plack::Middleware::Expires' => 0.03;
|
||||
requires 'Role::Tiny' => 1.002005;
|
||||
requires 'Socket6' => 0.23;
|
||||
requires 'Starman' => 0.4008;
|
||||
requires 'SNMP::Info' => 3.11;
|
||||
requires 'SNMP::Info' => 3.12;
|
||||
requires 'SQL::Translator' => 0.11016;
|
||||
requires 'Template' => 2.24;
|
||||
requires 'Template::Plugin::CSV' => 0.04;
|
||||
|
||||
@@ -37,7 +37,7 @@ BEGIN {
|
||||
use App::Netdisco;
|
||||
use Dancer ':script';
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
use App::Netdisco::Web::Plugin::Passphrase;
|
||||
use Dancer::Plugin::Passphrase;
|
||||
|
||||
info "App::Netdisco version $App::Netdisco::VERSION loaded.";
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use 5.010_000;
|
||||
use File::ShareDir 'dist_dir';
|
||||
use Path::Class;
|
||||
|
||||
our $VERSION = '2.022000';
|
||||
our $VERSION = '2.023000';
|
||||
|
||||
BEGIN {
|
||||
if (not ($ENV{DANCER_APPDIR} || '')
|
||||
|
||||
@@ -10,7 +10,7 @@ use base 'Dancer::Plugin::Auth::Extensible::Provider::Base';
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use App::Netdisco::Web::Plugin::Passphrase;
|
||||
use Dancer::Plugin::Passphrase;
|
||||
use Digest::MD5;
|
||||
|
||||
sub authenticate_user {
|
||||
|
||||
@@ -4,7 +4,7 @@ use Dancer ':syntax';
|
||||
use Dancer::Plugin::Ajax;
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
use App::Netdisco::Web::Plugin::Passphrase;
|
||||
use Dancer::Plugin::Passphrase;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
use Digest::MD5 ();
|
||||
|
||||
@@ -1,753 +0,0 @@
|
||||
package App::Netdisco::Web::Plugin::Passphrase;
|
||||
|
||||
# ABSTRACT: Passphrases and Passwords as objects for Dancer
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Dancer::Plugin::Passphrase - Passphrases and Passwords as objects for Dancer
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This plugin manages the hashing of passwords for Dancer apps, allowing
|
||||
developers to follow cryptography best practices without having to
|
||||
become a cryptography expert.
|
||||
|
||||
It uses the bcrypt algorithm as the default, while also supporting any
|
||||
hashing function provided by L<Digest>
|
||||
|
||||
=head1 USAGE
|
||||
|
||||
package MyWebService;
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::Passphrase;
|
||||
|
||||
post '/login' => sub {
|
||||
my $phrase = passphrase( param('my password') )->generate;
|
||||
|
||||
# $phrase is now an object that contains RFC 2307 representation
|
||||
# of the hashed passphrase, along with the salt, and other metadata
|
||||
|
||||
# You should store $phrase->rfc2307() for use later
|
||||
};
|
||||
|
||||
get '/protected' => sub {
|
||||
# Retrieve $stored_rfc_2307_string, like we created above.
|
||||
# IT MUST be a valid RFC 2307 string
|
||||
|
||||
if ( passphrase( param('my password') )->matches( $stored_rfc_2307 ) ) {
|
||||
# Passphrase matches!
|
||||
}
|
||||
};
|
||||
|
||||
get '/generate_new_password' => sub {
|
||||
return passphrase->generate_random;
|
||||
};
|
||||
|
||||
=cut
|
||||
|
||||
use strict;
|
||||
use feature 'switch';
|
||||
|
||||
use Dancer::Plugin;
|
||||
|
||||
use Carp qw(carp croak);
|
||||
use Data::Entropy::Algorithms qw(rand_bits rand_int);
|
||||
use Digest;
|
||||
use MIME::Base64 qw(decode_base64 encode_base64);
|
||||
use Scalar::Util qw(blessed);
|
||||
|
||||
our $VERSION = '2.0.0';
|
||||
|
||||
# Auto stringifies and returns the RFC 2307 representation
|
||||
# of the object unless we are calling a method on it
|
||||
use overload (
|
||||
'""' => sub {
|
||||
if (blessed($_[0]) && $_[0]->isa('App::Netdisco::Web::Plugin::Passphrase')) {
|
||||
$_[0]->rfc2307();
|
||||
}
|
||||
},
|
||||
fallback => 1,
|
||||
);
|
||||
|
||||
register passphrase => \&passphrase;
|
||||
|
||||
|
||||
=head1 KEYWORDS
|
||||
|
||||
=head2 passphrase
|
||||
|
||||
Given a plaintext password, it returns a Dancer::Plugin::Passphrase
|
||||
object that you can generate a new hash from, or match against a stored hash.
|
||||
|
||||
=cut
|
||||
|
||||
sub passphrase {
|
||||
# Dancer 2 keywords receive a reference to the DSL object as a first param.
|
||||
# We don't need it, so get rid of it, and just get the plaintext
|
||||
shift if blessed($_[0]) && $_[0]->isa('Dancer::Core::DSL');
|
||||
|
||||
my $plaintext = $_[0];
|
||||
|
||||
return bless {
|
||||
plaintext => $plaintext
|
||||
}, 'App::Netdisco::Web::Plugin::Passphrase';
|
||||
}
|
||||
|
||||
|
||||
|
||||
=head1 MAIN METHODS
|
||||
|
||||
=head2 generate
|
||||
|
||||
Generates an RFC 2307 representation of the hashed passphrase
|
||||
that is suitable for storage in a database.
|
||||
|
||||
my $pass = passphrase('my passphrase')->generate;
|
||||
|
||||
You should store C<$phrase->rfc_2307()> in your database. For convenience
|
||||
the object will automagically return the RFC 2307 representation when no
|
||||
method is called on it.
|
||||
|
||||
Accepts a hashref of options to specify what kind of hash should be
|
||||
generated. All options settable in the config file are valid.
|
||||
|
||||
If you specify only the algorithm, the default settings for that algorithm will be used.
|
||||
|
||||
A cryptographically random salt is used if salt is not defined.
|
||||
Only if you specify the empty string will an empty salt be used
|
||||
This is not recommended, and should only be used to upgrade old insecure hashes
|
||||
|
||||
my $phrase = passphrase('my password')->generate({
|
||||
algorithm => '', # What algorithm is used to generate the hash
|
||||
cost => '', # Cost / Work Factor if using bcrypt
|
||||
salt => '', # Manually specify salt if using a salted digest
|
||||
});
|
||||
|
||||
=cut
|
||||
|
||||
sub generate {
|
||||
my ($self, $options) = @_;
|
||||
|
||||
$self->_get_settings($options);
|
||||
$self->_calculate_hash;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub generate_hash {
|
||||
carp "generate_hash method is deprecated";
|
||||
return shift->generate();
|
||||
}
|
||||
|
||||
|
||||
=head2 matches
|
||||
|
||||
Matches a plaintext password against a stored hash.
|
||||
Returns 1 if the hash of the password matches the stored hash.
|
||||
Returns undef if they don't match or if there was an error
|
||||
Fail-Secure, rather than Fail-Safe.
|
||||
|
||||
passphrase('my password')->matches($stored_rfc_2307_string);
|
||||
|
||||
$stored_rfc_2307_string B<MUST> be a valid RFC 2307 string,
|
||||
as created by L<generate()|/"passphrase__generate">
|
||||
|
||||
An RFC 2307 string is made up of a scheme identifier, followed by a
|
||||
base64 encoded string. The base64 encoded string should contain
|
||||
the password hash and the salt concatenated together - in that order.
|
||||
|
||||
'{'.$scheme.'}'.encode_base64($hash . $salt, '');
|
||||
|
||||
Where C<$scheme> can be any of the following and their unsalted variants,
|
||||
which have the leading S removed. CRYPT will be Bcrypt.
|
||||
|
||||
SMD5 SSHA SSHA224 SSHA256 SSHA384 SSHA512 CRYPT
|
||||
|
||||
A complete RFC2307 string looks like this:
|
||||
|
||||
{SSHA}K3LAbIjRL5CpLzOlm3/HzS3qt/hUaGVTYWx0
|
||||
|
||||
This is the format created by L<generate()|/"passphrase__generate">
|
||||
|
||||
=cut
|
||||
|
||||
sub matches {
|
||||
my ($self, $stored_hash) = @_;
|
||||
|
||||
# Force auto stringification in case we were passed an object.
|
||||
($stored_hash) = ($stored_hash =~ m/(.*)/s);
|
||||
|
||||
my $new_hash = $self->_extract_settings($stored_hash)->_calculate_hash->rfc2307;
|
||||
|
||||
return ($new_hash eq $stored_hash) ? 1 : undef;
|
||||
}
|
||||
|
||||
|
||||
|
||||
=head2 generate_random
|
||||
|
||||
Generates and returns any number of cryptographically random
|
||||
characters from the url-safe base64 charater set.
|
||||
|
||||
my $rand_pass = passphrase->generate_random;
|
||||
|
||||
The passwords generated are suitable for use as
|
||||
temporary passwords or one-time authentication tokens.
|
||||
|
||||
You can configure the length and the character set
|
||||
used by passing a hashref of options.
|
||||
|
||||
my $rand_pass = passphrase->generate_random({
|
||||
length => 32,
|
||||
charset => ['a'..'z', 'A'..'Z'],
|
||||
});
|
||||
|
||||
=cut
|
||||
|
||||
sub generate_random {
|
||||
my ($self, $options) = @_;
|
||||
|
||||
# Default is 16 URL-safe base64 chars. Supported everywhere and a reasonable length
|
||||
my $length = $options->{length} || 16;
|
||||
my $charset = $options->{charset} || ['a'..'z', 'A'..'Z', '0'..'9', '-', '_'];
|
||||
|
||||
return join '', map { @$charset[rand_int scalar @$charset] } 1..$length;
|
||||
}
|
||||
|
||||
|
||||
|
||||
=head1 ADDITIONAL METHODS
|
||||
|
||||
The methods are only applicable once you have called C<generate>
|
||||
|
||||
passphrase( 'my password' )->generate->rfc2307; # CORRECT
|
||||
|
||||
passphrase( 'my password' )->rfc2307; # INCORRECT, Returns undef
|
||||
|
||||
|
||||
=head2 rfc2307
|
||||
|
||||
Returns the rfc2307 representation from a C<Dancer::Plugin::Passphrase> object.
|
||||
|
||||
passphrase('my password')->generate->rfc2307;
|
||||
|
||||
=cut
|
||||
|
||||
sub rfc2307 {
|
||||
return shift->{rfc2307} || undef;
|
||||
}
|
||||
|
||||
sub as_rfc2307 {
|
||||
carp "as_rfc2307 method is deprecated";
|
||||
return shift->rfc2307();
|
||||
}
|
||||
|
||||
|
||||
|
||||
=head2 scheme
|
||||
|
||||
Returns the scheme name from a C<Dancer::Plugin::Passphrase> object.
|
||||
|
||||
This is the scheme name as used in the RFC 2307 representation
|
||||
|
||||
passphrase('my password')->generate->scheme;
|
||||
|
||||
The scheme name can be any of the following, and will always be capitalized
|
||||
|
||||
SMD5 SSHA SSHA224 SSHA256 SSHA384 SSHA512 CRYPT
|
||||
MD5 SHA SHA224 SHA256 SHA384 SHA512
|
||||
|
||||
=cut
|
||||
|
||||
sub scheme {
|
||||
return shift->{scheme} || undef;
|
||||
}
|
||||
|
||||
|
||||
=head2 algorithm
|
||||
|
||||
Returns the algorithm name from a C<Dancer::Plugin::Passphrase> object.
|
||||
|
||||
The algorithm name can be anything that is accepted by C<Digest->new($alg)>
|
||||
This includes any modules in the C<Digest::> Namespace
|
||||
|
||||
passphrase('my password')->generate->algorithm;
|
||||
|
||||
=cut
|
||||
|
||||
sub algorithm {
|
||||
return shift->{algorithm} || undef;
|
||||
}
|
||||
|
||||
|
||||
=head2 cost
|
||||
|
||||
Returns the bcrypt cost from a C<Dancer::Plugin::Passphrase> object.
|
||||
Only works when using the bcrypt algorithm, returns undef for other algorithms
|
||||
|
||||
passphrase('my password')->generate->cost;
|
||||
|
||||
=cut
|
||||
|
||||
sub cost {
|
||||
return shift->{cost} || undef;
|
||||
}
|
||||
|
||||
|
||||
=head2 salt_raw
|
||||
|
||||
Returns the raw salt from a C<Dancer::Plugin::Passphrase> object.
|
||||
|
||||
passphrase('my password')->generate->salt_raw;
|
||||
|
||||
Can be defined, but false - The empty string is technically a valid salt.
|
||||
|
||||
Returns C<undef> if there is no salt.
|
||||
|
||||
=cut
|
||||
|
||||
sub salt_raw {
|
||||
return shift->{salt} // undef;
|
||||
}
|
||||
|
||||
sub raw_salt {
|
||||
carp "raw_salt method is deprecated";
|
||||
return shift->salt_raw();
|
||||
}
|
||||
|
||||
=head2 hash_raw
|
||||
|
||||
Returns the raw hash from a C<Dancer::Plugin::Passphrase> object.
|
||||
|
||||
passphrase('my password')->generate->hash_raw;
|
||||
|
||||
=cut
|
||||
|
||||
sub hash_raw {
|
||||
return shift->{hash} || undef;
|
||||
}
|
||||
|
||||
sub raw_hash {
|
||||
carp "raw_hash method is deprecated";
|
||||
return shift->hash_raw();
|
||||
}
|
||||
|
||||
|
||||
=head2 salt_hex
|
||||
|
||||
Returns the hex-encoded salt from a C<Dancer::Plugin::Passphrase> object.
|
||||
|
||||
Can be defined, but false - The empty string is technically a valid salt.
|
||||
Returns C<undef> if there is no salt.
|
||||
|
||||
passphrase('my password')->generate->salt_hex;
|
||||
|
||||
=cut
|
||||
|
||||
sub salt_hex {
|
||||
return unpack("H*", shift->{salt}) // undef;
|
||||
}
|
||||
|
||||
|
||||
=head2 hash_hex
|
||||
|
||||
Returns the hex-encoded hash from a C<Dancer::Plugin::Passphrase> object.
|
||||
|
||||
passphrase('my password')->generate->hash_hex;
|
||||
|
||||
=cut
|
||||
|
||||
sub hash_hex {
|
||||
return unpack("H*", shift->{hash}) || undef;
|
||||
}
|
||||
|
||||
|
||||
=head2 salt_base64
|
||||
|
||||
Returns the base64 encoded salt from a C<Dancer::Plugin::Passphrase> object.
|
||||
|
||||
Can be defined, but false - The empty string is technically a valid salt.
|
||||
Returns C<undef> if there is no salt.
|
||||
|
||||
passphrase('my password')->generate->salt_base64;
|
||||
|
||||
=cut
|
||||
|
||||
sub salt_base64 {
|
||||
return encode_base64(shift->{salt}, '') // undef;
|
||||
}
|
||||
|
||||
|
||||
=head2 hash_base64
|
||||
|
||||
Returns the base64 encoded hash from a C<Dancer::Plugin::Passphrase> object.
|
||||
|
||||
passphrase('my password')->generate->hash_base64;
|
||||
|
||||
=cut
|
||||
|
||||
sub hash_base64 {
|
||||
return encode_base64(shift->{hash}, '') || undef;
|
||||
}
|
||||
|
||||
=head2 plaintext
|
||||
|
||||
Returns the plaintext password as originally supplied to the L<passphrase> keyword.
|
||||
|
||||
passphrase('my password')->generate->plaintext;
|
||||
|
||||
=cut
|
||||
|
||||
sub plaintext {
|
||||
return shift->{plaintext} || undef;
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Actual generation of the hash, using the provided settings
|
||||
sub _calculate_hash {
|
||||
my $self = shift;
|
||||
|
||||
my $hasher = Digest->new( $self->algorithm );
|
||||
|
||||
given ($self->algorithm) {
|
||||
when ('Bcrypt') {
|
||||
$hasher->add($self->{plaintext});
|
||||
$hasher->salt($self->salt_raw);
|
||||
$hasher->cost($self->cost);
|
||||
|
||||
$self->{hash} = $hasher->digest;
|
||||
$self->{rfc2307}
|
||||
= '{CRYPT}$'
|
||||
. $self->{type} . '$'
|
||||
. $self->cost . '$'
|
||||
. _en_bcrypt_base64($self->salt_raw)
|
||||
. _en_bcrypt_base64($self->hash_raw);
|
||||
}
|
||||
default {
|
||||
$hasher->add($self->{plaintext});
|
||||
$hasher->add($self->{salt});
|
||||
|
||||
$self->{hash} = $hasher->digest;
|
||||
$self->{rfc2307}
|
||||
= '{' . $self->{scheme} . '}'
|
||||
. encode_base64($self->hash_raw . $self->salt_raw, '');
|
||||
}
|
||||
}
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
|
||||
# Extracts the settings from an RFC 2307 string
|
||||
sub _extract_settings {
|
||||
my ($self, $rfc2307_string) = @_;
|
||||
|
||||
my ($scheme, $settings) = ($rfc2307_string =~ m/^{(\w+)}(.*)/s);
|
||||
|
||||
unless ($scheme && $settings) {
|
||||
croak "An RFC 2307 compliant string must be passed to matches()";
|
||||
}
|
||||
|
||||
if ($scheme eq 'CRYPT'){
|
||||
given ($settings) {
|
||||
when (/^\$2(?:a|x|y)\$/) {
|
||||
$scheme = 'Bcrypt';
|
||||
$settings =~ m{\A\$(2a|2x|2y)\$([0-9]{2})\$([./A-Za-z0-9]{22})}x;
|
||||
|
||||
($self->{type}, $self->{cost}, $self->{salt}) = ($1, $2, _de_bcrypt_base64($3));
|
||||
}
|
||||
default { croak "Unknown CRYPT format: $_"; }
|
||||
}
|
||||
}
|
||||
|
||||
my $scheme_meta = {
|
||||
'MD5' => { algorithm => 'MD5', octets => 128 / 8 },
|
||||
'SMD5' => { algorithm => 'MD5', octets => 128 / 8 },
|
||||
'SHA' => { algorithm => 'SHA-1', octets => 160 / 8 },
|
||||
'SSHA' => { algorithm => 'SHA-1', octets => 160 / 8 },
|
||||
'SHA224' => { algorithm => 'SHA-224', octets => 224 / 8 },
|
||||
'SSHA224' => { algorithm => 'SHA-224', octets => 224 / 8 },
|
||||
'SHA256' => { algorithm => 'SHA-256', octets => 256 / 8 },
|
||||
'SSHA256' => { algorithm => 'SHA-256', octets => 256 / 8 },
|
||||
'SHA384' => { algorithm => 'SHA-384', octets => 384 / 8 },
|
||||
'SSHA384' => { algorithm => 'SHA-384', octets => 384 / 8 },
|
||||
'SHA512' => { algorithm => 'SHA-512', octets => 512 / 8 },
|
||||
'SSHA512' => { algorithm => 'SHA-512', octets => 512 / 8 },
|
||||
'Bcrypt' => { algorithm => 'Bcrypt', octets => 128 / 8 },
|
||||
};
|
||||
|
||||
$self->{scheme} = $scheme;
|
||||
$self->{algorithm} = $scheme_meta->{$scheme}->{algorithm};
|
||||
|
||||
if (!defined $self->{salt}) {
|
||||
$self->{salt} = substr(decode_base64($settings), $scheme_meta->{$scheme}->{octets});
|
||||
}
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
|
||||
# Gets the settings from config.yml, and merges them with any custom
|
||||
# settings given to the constructor
|
||||
sub _get_settings {
|
||||
my ($self, $options) = @_;
|
||||
|
||||
$self->{algorithm} = $options->{algorithm} ||
|
||||
plugin_setting->{algorithm} ||
|
||||
'Bcrypt';
|
||||
|
||||
my $plugin_setting = plugin_setting->{$self->algorithm};
|
||||
|
||||
# Specify empty string to get an unsalted hash
|
||||
# Leaving it undefs results in 128 random bits being used as salt
|
||||
# bcrypt requires this amount, and is reasonable for other algorithms
|
||||
$self->{salt} = $options->{salt} //
|
||||
$plugin_setting->{salt} //
|
||||
rand_bits(128);
|
||||
|
||||
# RFC 2307 scheme is based on the algorithm, with a prefixed 'S' for salted
|
||||
$self->{scheme} = join '', $self->algorithm =~ /[\w]+/g;
|
||||
$self->{scheme} = 'S'.$self->{scheme} if $self->{salt};
|
||||
|
||||
given ($self->{scheme}) {
|
||||
when ('SHA1') { $self->{scheme} = 'SHA'; }
|
||||
when ('SSHA1') { $self->{scheme} = 'SSHA'; }
|
||||
}
|
||||
|
||||
# Bcrypt requires a cost parameter
|
||||
if ($self->algorithm eq 'Bcrypt') {
|
||||
$self->{scheme} = 'CRYPT';
|
||||
$self->{type} = '2a';
|
||||
$self->{cost} = $options->{cost} ||
|
||||
$plugin_setting->{cost} ||
|
||||
4;
|
||||
|
||||
$self->{cost} = 31 if $self->cost > 31;
|
||||
$self->{cost} = sprintf("%02d", $self->cost);
|
||||
}
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
|
||||
# From Crypt::Eksblowfish::Bcrypt.
|
||||
# Bcrypt uses it's own variation on base64
|
||||
sub _en_bcrypt_base64 {
|
||||
my ($octets) = @_;
|
||||
my $text = encode_base64($octets, '');
|
||||
$text =~ tr{A-Za-z0-9+/=}{./A-Za-z0-9}d;
|
||||
return $text;
|
||||
}
|
||||
|
||||
|
||||
# And the decoder of bcrypt's custom base64
|
||||
sub _de_bcrypt_base64 {
|
||||
my ($text) = @_;
|
||||
$text =~ tr{./A-Za-z0-9}{A-Za-z0-9+/};
|
||||
$text .= "=" x (3 - (length($text) + 3) % 4);
|
||||
return decode_base64($text);
|
||||
}
|
||||
|
||||
|
||||
register_plugin for_versions => [ 1, 2 ];
|
||||
|
||||
1;
|
||||
|
||||
|
||||
=head1 MORE INFORMATION
|
||||
|
||||
=head2 Purpose
|
||||
|
||||
The aim of this module is to help you store new passwords in a secure manner,
|
||||
whilst still being able to verify and upgrade older passwords.
|
||||
|
||||
Cryptography is a vast and complex field. Many people try to roll their own
|
||||
methods for securing user data, but succeed only in coming up with
|
||||
a system that has little real security.
|
||||
|
||||
This plugin provides a simple way of managing that complexity, allowing
|
||||
developers to follow crypto best practice without having to become an expert.
|
||||
|
||||
|
||||
=head2 Rationale
|
||||
|
||||
The module defaults to hashing passwords using the bcrypt algorithm, returning them
|
||||
in RFC 2307 format.
|
||||
|
||||
RFC 2307 describes an encoding system for passphrase hashes, as used in the "userPassword"
|
||||
attribute in LDAP databases. It encodes hashes as ASCII text, and supports several
|
||||
passphrase schemes by starting the encoding with an alphanumeric scheme identifier enclosed
|
||||
in braces.
|
||||
|
||||
RFC 2307 only specifies the C<MD5>, and C<SHA> schemes - however in real-world usage,
|
||||
schemes that are salted are widely supported, and are thus provided by this module.
|
||||
|
||||
Bcrypt is an adaptive hashing algorithm that is designed to resist brute
|
||||
force attacks by including a cost (aka work factor). This cost increases
|
||||
the computational effort it takes to compute the hash.
|
||||
|
||||
SHA and MD5 are designed to be fast, and modern machines compute a billion
|
||||
hashes a second. With computers getting faster every day, brute forcing
|
||||
SHA hashes is a very real problem that cannot be easily solved.
|
||||
|
||||
Increasing the cost of generating a bcrypt hash is a trivial way to make
|
||||
brute forcing ineffective. With a low cost setting, bcrypt is just as secure
|
||||
as a more traditional SHA+salt scheme, and just as fast. Increasing the cost
|
||||
as computers become more powerful keeps you one step ahead
|
||||
|
||||
For a more detailed description of why bcrypt is preferred, see this article:
|
||||
L<http://codahale.com/how-to-safely-store-a-password/>
|
||||
|
||||
|
||||
=head2 Configuration
|
||||
|
||||
In your applications config file, you can set the default hashing algorithm,
|
||||
and the default settings for every supported algorithm. Calls to
|
||||
L<generate()|/"passphrase__generate"> will use the default settings
|
||||
for that algorithm specified in here.
|
||||
|
||||
You can override these defaults when you call L<generate()|/"passphrase__generate">.
|
||||
|
||||
If you do no configuration at all, the default is to bcrypt with a cost of 4, and
|
||||
a strong psuedo-random salt.
|
||||
|
||||
plugins:
|
||||
Passphrase:
|
||||
default: Bcrypt
|
||||
|
||||
Bcrypt:
|
||||
cost: 8
|
||||
|
||||
|
||||
=head2 Storage in a database
|
||||
|
||||
You should be storing the RFC 2307 string in your database, it's the easiest way
|
||||
to use this module. You could store the C<raw_salt>, C<raw_hash>, and C<scheme>
|
||||
separately, but this strongly discouraged. RFC 2307 strings are specifically
|
||||
designed for storing hashed passwords, and should be used wherever possible.
|
||||
|
||||
The length of the string produced by L<generate()|/"passphrase__generate"> can
|
||||
vary dependent on your settings. Below is a table of the lengths generated
|
||||
using default settings.
|
||||
|
||||
You will need to make sure your database columns are at least this long.
|
||||
If the string gets truncated, the password can I<never> be validated.
|
||||
|
||||
ALGORITHM LENGTH EXAMPLE RFC 2307 STRING
|
||||
|
||||
Bcrypt 68 {CRYPT}$2a$04$MjkMhQxasFQod1qq56DXCOvWu6YTWk9X.EZGnmSSIbbtyEBIAixbS
|
||||
SHA-512 118 {SSHA512}lZG4dZ5EU6dPEbJ1kBPPzEcupFloFSIJjiXCwMVxJXOy/x5qhBA5XH8FiUWj7u59onQxa97xYdqje/fwY5TDUcW1Urplf3KHMo9NO8KO47o=
|
||||
SHA-384 98 {SSHA384}SqZF5YYyk4NdjIM8YgQVfRieXDxNG0dKH4XBcM40Eblm+ribCzdyf0JV7i2xJvVHZsFSQNcuZPKtiTMzDyOU+w==
|
||||
SHA-256 74 {SSHA256}xsJHNzPlNCpOZ41OkTfQOU35ZY+nRyZFaM8lHg5U2pc0xT3DKNlGW2UTY0NPYsxU
|
||||
SHA-224 70 {SSHA224}FTHNkvKOdyX1d6f45iKLVxpaXZiHel8pfilUT1dIZ5u+WIUyhDGxLnx72X0=
|
||||
SHA-1 55 {SSHA}Qsaao/Xi/bYTRMQnpHuD3y5nj02wbdcw5Cek2y2nLs3pIlPh
|
||||
MD5 51 {SMD5}bgfLiUQWgzUm36+nBhFx62bi0xdwTp+UpEeNKDxSLfM=
|
||||
|
||||
=head2 Common Mistakes
|
||||
|
||||
Common mistakes people make when creating their own solution. If any of these
|
||||
seem familiar, you should probably be using this module
|
||||
|
||||
=over
|
||||
|
||||
=item Passwords are stored as plain text for a reason
|
||||
|
||||
There is never a valid reason to store a password as plain text.
|
||||
Passwords should be reset and not emailed to customers when they forget.
|
||||
Support people should be able to login as a user without knowing the users password.
|
||||
No-one except the user should know the password - that is the point of authentication.
|
||||
|
||||
=item No-one will ever guess our super secret algorithm!
|
||||
|
||||
Unless you're a cryptography expert with many years spent studying
|
||||
super-complex maths, your algorithm is almost certainly not as secure
|
||||
as you think. Just because it's hard for you to break doesn't mean
|
||||
it's difficult for a computer.
|
||||
|
||||
=item Our application-wide salt is "Sup3r_S3cret_L0ng_Word" - No-one will ever guess that.
|
||||
|
||||
This is common misunderstanding of what a salt is meant to do. The purpose of a
|
||||
salt is to make sure the same password doesn't always generate the same hash.
|
||||
A fresh salt needs to be created each time you hash a password. It isn't meant
|
||||
to be a secret key.
|
||||
|
||||
=item We generate our random salt using C<rand>.
|
||||
|
||||
C<rand> isn't actually random, it's a non-unform pseudo-random number generator,
|
||||
and not suitable for cryptographic applications. Whilst this module also defaults to
|
||||
a PRNG, it is better than the one provided by C<rand>. Using a true RNG is a config
|
||||
option away, but is not the default as it it could potentially block output if the
|
||||
system does not have enough entropy to generate a truly random number
|
||||
|
||||
=item We use C<md5(pass.salt)>, and the salt is from C</dev/random>
|
||||
|
||||
MD5 has been broken for many years. Commodity hardware can find a
|
||||
hash collision in seconds, meaning an attacker can easily generate
|
||||
the correct MD5 hash without using the correct password.
|
||||
|
||||
=item We use C<sha(pass.salt)>, and the salt is from C</dev/random>
|
||||
|
||||
SHA isn't quite as broken as MD5, but it shares the same theoretical
|
||||
weaknesses. Even without hash collisions, it is vulnerable to brute forcing.
|
||||
Modern hardware is so powerful it can try around a billion hashes a second.
|
||||
That means every 7 chracter password in the range [A-Za-z0-9] can be cracked
|
||||
in one hour on your average desktop computer.
|
||||
|
||||
=item If the only way to break the hash is to brute-force it, it's secure enough
|
||||
|
||||
It is unlikely that your database will be hacked and your hashes brute forced.
|
||||
However, in the event that it does happen, or SHA512 is broken, using this module
|
||||
gives you an easy way to change to a different algorithm, while still allowing
|
||||
you to validate old passphrases
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=head1 KNOWN ISSUES
|
||||
|
||||
If you see errors like this
|
||||
|
||||
Wide character in subroutine entry
|
||||
|
||||
or
|
||||
|
||||
Input must contain only octets
|
||||
|
||||
The C<MD5>, C<bcrypt>, and C<SHA> algorithms can't handle chracters with an ordinal
|
||||
value above 255, producing errors like this if they encounter them.
|
||||
It is not possible for this plugin to automagically work out the correct
|
||||
encoding for a given string.
|
||||
|
||||
If you see errors like this, then you probably need to use the L<Encode> module
|
||||
to encode your text as UTF-8 (or whatever encoding it is) before giving it
|
||||
to C<passphrase>.
|
||||
|
||||
Text encoding is a bag of hurt, and errors like this are probably indicitive
|
||||
of deeper problems within your app's code.
|
||||
|
||||
You will save yourself a lot of trouble if you read up on the
|
||||
L<Encode> module sooner rather than later.
|
||||
|
||||
For further reading on UTF-8, unicode, and text encoding in perl,
|
||||
see L<http://training.perl.com/OSCON2011/index.html>
|
||||
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Dancer>, L<Digest>, L<Crypt::Eksblowfish::Bcrypt>, L<Dancer::Plugin::Bcrypt>
|
||||
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
James Aitken <jaitken@cpan.org>
|
||||
|
||||
|
||||
=head1 COPYRIGHT AND LICENSE
|
||||
|
||||
This software is copyright (c) 2012 by James Aitken.
|
||||
|
||||
This is free software; you can redistribute it and/or modify it under
|
||||
the same terms as the Perl 5 programming language system itself.
|
||||
|
||||
=cut
|
||||
Reference in New Issue
Block a user