package AccessCheck::Tools; use Mojo::Base -strict; use Crypt::Bcrypt; use Crypt::OpenSSL::Random (); use Encode; use English qw(-no_match_vars); use List::Util qw(shuffle); use List::MoreUtils qw(pairwise); use MIME::Base64; use Template; use Template::Constants qw(:chomp); use AccessCheck::Template::Plugin::Quote; use constant { SIG_BCRYPT => '2y', PASSWORD_BCRYPT_DEFAULT_COST => 10, PASSWORD_BCRYPT_MAX_PASSWORD_LEN => 72, }; sub encrypt { my ($string, $key) = @_; my @string_chars = split(//, $string); my @key_chars = split(//, $key); return encode_base64(otp(\@string_chars, \@key_chars)); } sub decrypt { my ($string, $key) = @_; my @string_chars = split(//, decode_base64($string)); my @key_chars = split(//, $key); return otp(\@string_chars, \@key_chars); } sub otp { my ($string, $key) = @_; my @chars = pairwise { chr(ord($a) ^ ord($b)) } @$string, @$key; return join('', @chars); } # shamelessly stolen from PHP::Functions::Password # https://metacpan.org/dist/PHP-Functions-Password/source/lib/PHP/Functions/Password.pm sub hash { my ($string) = @_; my $salt = Crypt::OpenSSL::Random::random_bytes(16); my $cost = PASSWORD_BCRYPT_DEFAULT_COST; # Treat passwords as strings of bytes # "\x{100}" becomes "\xc4\x80"; preferred equivalent of Encode::is_utf8($string) && Encode::_utf8_off($password); utf8::is_utf8($string) && utf8::encode($string); # Everything beyond the max password length in bytes for bcrypt is silently ignored. require bytes; if (bytes::length($string) > PASSWORD_BCRYPT_MAX_PASSWORD_LEN) { # $string is already bytes, so the bytes:: prefix is redundant here $string = substr($string, 0, PASSWORD_BCRYPT_MAX_PASSWORD_LEN); } return Crypt::Bcrypt::bcrypt($string, SIG_BCRYPT, $cost, $salt); } sub generate_password { my ($size) = @_; # define alphabet my @uppers = ('A' .. 'N', 'P' .. 'Z'); my @lowers = ('a' .. 'k', 'm' .. 'z'); my @punctuations = (':', '!', '?', '&', '$', '=', '-', '#'); my @numerics = ('0' .. '9'); my @all = (@uppers, @lowers, @punctuations, @numerics); # start with a random character of each class my @chars = ( $uppers[ rand @uppers ], $lowers[ rand @lowers ], $punctuations[ rand @punctuations ], $numerics[ rand @numerics ] ); # complete with additional characters for my $i (1 .. $size - 4) { push(@chars, $all[ rand @all ]); } return join('', shuffle(@chars)); } sub generate_secret { my ($size) = @_; # define alphabet my @lowers = ('a' .. 'k', 'm' .. 'z'); my @numerics = ('0' .. '9'); my @all = (@lowers, @numerics); # fill characters list my @chars; for my $i (1 .. $size) { push(@chars, $all[ rand @all ]); } return join('', shuffle(@chars)); } ## Updates simpleSamlPhp authsources.php configuration file sub update_ssp_authsources { my ($templates_dir, $output, $accounts) = @_; my $tt2 = Template->new({ ENCODING => 'utf8', PRE_CHOMP => CHOMP_ONE, INCLUDE_PATH => [ sprintf("%s/other", $templates_dir), sprintf("%s/accounts", $templates_dir), ], }); $tt2->process( 'accounts.php.tt2', { accounts => $accounts}, $output, { binmode => ':utf8' } ) or die $tt2->error(); } 1; __END__ =head1 NAME AccessCheck::Tools - Set of subroutines usefull for the Test Account manager =head1 DESCRIPTION The Test Account manager instanciates test accounts associated to a SAML Identity Provider. This module gathers a set of usefull subroutines. =head1 FUNCTIONS =over =item generate_password() Returns a random password following some security guidelines. =item update_ssp_authsources() Update simpleSAMLphp authsources.php configuration file with the currently valid test accounts. =back