replaced rand() with cryptographically secure alternative
This commit is contained in:
parent
17b3c309fe
commit
a5cf07a181
|
@ -1,14 +1,14 @@
|
||||||
from alpine:latest
|
from alpine:latest
|
||||||
|
|
||||||
run apk add wget gnupg sqlite build-base perl perl-dev perl-app-cpanminus
|
run apk add wget gnupg sqlite unzip build-base perl perl-dev perl-app-cpanminus
|
||||||
run cpanm --notest IPC::Run DBD::SQLite Net::DNS::Resolver Crypt::Eksblowfish::Bcrypt JSON URI::Escape HTML::Entities String::ShellQuote Net::Server HTTP::Server::Simple
|
run cpanm --notest IPC::Run DBD::SQLite Net::DNS::Resolver Crypt::Eksblowfish::Bcrypt JSON URI::Escape HTML::Entities String::ShellQuote Net::Server HTTP::Server::Simple Crypt::Random
|
||||||
|
|
||||||
run mkdir -p /opt/data/plans
|
run mkdir -p /opt/data/plans
|
||||||
copy schema.sql /opt/data
|
copy schema.sql /opt/data
|
||||||
run cat /opt/data/schema.sql | sqlite3 /opt/data/users.db
|
run cat /opt/data/schema.sql | sqlite3 /opt/data/users.db
|
||||||
run rm /opt/data/schema.sql
|
run rm /opt/data/schema.sql
|
||||||
|
|
||||||
run apk del build-base perl-dev perl-app-cpanminus wget sqlite
|
run apk del build-base perl-dev perl-app-cpanminus wget sqlite unzip
|
||||||
|
|
||||||
copy server.pl /opt
|
copy server.pl /opt
|
||||||
workdir /opt
|
workdir /opt
|
||||||
|
|
32
server.pl
32
server.pl
|
@ -55,6 +55,7 @@ if (defined $ENV{'LOCAL_DOMAINS'}) {
|
||||||
use File::Temp qw(tempfile);
|
use File::Temp qw(tempfile);
|
||||||
use Fcntl qw(:flock);
|
use Fcntl qw(:flock);
|
||||||
use Net::DNS::Resolver;
|
use Net::DNS::Resolver;
|
||||||
|
use Crypt::Random qw(makerandom_itv);
|
||||||
use Crypt::Eksblowfish::Bcrypt qw(bcrypt_hash en_base64);
|
use Crypt::Eksblowfish::Bcrypt qw(bcrypt_hash en_base64);
|
||||||
use MIME::Base64 qw(decode_base64);
|
use MIME::Base64 qw(decode_base64);
|
||||||
use POSIX qw(strftime);
|
use POSIX qw(strftime);
|
||||||
|
@ -92,7 +93,7 @@ if (defined $ENV{'LOCAL_DOMAINS'}) {
|
||||||
sub handle_request {
|
sub handle_request {
|
||||||
my ($self, $cgi) = @_;
|
my ($self, $cgi) = @_;
|
||||||
# assign a random request id for anonymous logging
|
# assign a random request id for anonymous logging
|
||||||
my $req_id = util_req_id();
|
my $req_id = util_token(12);
|
||||||
$cgi->param('request_id', $req_id);
|
$cgi->param('request_id', $req_id);
|
||||||
my $path = $cgi->path_info();
|
my $path = $cgi->path_info();
|
||||||
my $method = $cgi->request_method();
|
my $method = $cgi->request_method();
|
||||||
|
@ -206,7 +207,7 @@ EOF
|
||||||
: "INSERT INTO users (password, pw_token, pw_token_expires, email) values (?, ?, datetime('now', '+$pw_token_expiration_minutes minutes'), ?)";
|
: "INSERT INTO users (password, pw_token, pw_token_expires, email) values (?, ?, datetime('now', '+$pw_token_expiration_minutes minutes'), ?)";
|
||||||
my $crypted = util_bcrypt($password);
|
my $crypted = util_bcrypt($password);
|
||||||
my $sth = util_get_dbh()->prepare($query);
|
my $sth = util_get_dbh()->prepare($query);
|
||||||
$sth->execute($crypted, util_token(), $email);
|
$sth->execute($crypted, util_token(24), $email);
|
||||||
die $sth->errstr if $sth->err;
|
die $sth->errstr if $sth->err;
|
||||||
# TODO: send email
|
# TODO: send email
|
||||||
print_json_response($cgi, 200, {email => $email});
|
print_json_response($cgi, 200, {email => $email});
|
||||||
|
@ -244,10 +245,10 @@ EOF
|
||||||
print_response($cgi, 401, $not_authorized);
|
print_response($cgi, 401, $not_authorized);
|
||||||
} else {
|
} else {
|
||||||
my $sth = util_get_dbh()->prepare("UPDATE users SET token=?, token_expires=datetime('now', ?) WHERE email=?");
|
my $sth = util_get_dbh()->prepare("UPDATE users SET token=?, token_expires=datetime('now', ?) WHERE email=?");
|
||||||
my $token = util_token();
|
my $token = util_token(24);
|
||||||
my $expires = $cgi->param('expires');
|
my $expires = $cgi->param('expires');
|
||||||
my $minutes = $auth_token_default_expiration_minutes;
|
my $minutes = $auth_token_default_expiration_minutes;
|
||||||
if ($expires =~ /^\d+$/) {
|
if (defined $expires && $expires =~ /^\d+$/) {
|
||||||
$minutes = int($expires);
|
$minutes = int($expires);
|
||||||
if ($minutes <= 0) {
|
if ($minutes <= 0) {
|
||||||
$minutes = $auth_token_default_expiration_minutes;
|
$minutes = $auth_token_default_expiration_minutes;
|
||||||
|
@ -282,7 +283,7 @@ EOF
|
||||||
} elsif (defined $user->{'pw_token_expires'} && $user->{'pw_token_expires'} >= time) {
|
} elsif (defined $user->{'pw_token_expires'} && $user->{'pw_token_expires'} >= time) {
|
||||||
print_json_response($cgi, 429, {error => "Wait $pw_token_expiration_minutes between this type of request."});
|
print_json_response($cgi, 429, {error => "Wait $pw_token_expiration_minutes between this type of request."});
|
||||||
} else {
|
} else {
|
||||||
my $token = util_token();
|
my $token = util_token(24);
|
||||||
my $sth = util_get_dbh()->prepare("UPDATE users SET pw_token=?, pw_token_expires=datetime('now', '+10 minutes') WHERE email=?");
|
my $sth = util_get_dbh()->prepare("UPDATE users SET pw_token=?, pw_token_expires=datetime('now', '+10 minutes') WHERE email=?");
|
||||||
$sth->execute($token, $email);
|
$sth->execute($token, $email);
|
||||||
die $sth->errstr if $sth->err;
|
die $sth->errstr if $sth->err;
|
||||||
|
@ -426,7 +427,7 @@ EOF
|
||||||
my $msg = shift;
|
my $msg = shift;
|
||||||
my $timestamp = strftime("%Y-%m-%d %H:%M:%S", localtime(time()));
|
my $timestamp = strftime("%Y-%m-%d %H:%M:%S", localtime(time()));
|
||||||
if (!defined $_log) {
|
if (!defined $_log) {
|
||||||
open($_log, '>>', $log_file);
|
open($_log, '>>', $log_file) or die $!;
|
||||||
binmode($_log, ':unix');
|
binmode($_log, ':unix');
|
||||||
}
|
}
|
||||||
print $_log "$timestamp $msg\n";
|
print $_log "$timestamp $msg\n";
|
||||||
|
@ -470,7 +471,7 @@ EOF
|
||||||
sub util_salt {
|
sub util_salt {
|
||||||
my $itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
my $itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||||
my $salt = '';
|
my $salt = '';
|
||||||
$salt .= substr($itoa64,int(rand(64)),1) while length($salt) < 16;
|
$salt .= substr($itoa64,int(makerandom_itv(Strength => 0, Upper => 64)),1) while length($salt) < 16;
|
||||||
return $salt;
|
return $salt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -504,20 +505,13 @@ EOF
|
||||||
|
|
||||||
# generate an authorization token
|
# generate an authorization token
|
||||||
sub util_token {
|
sub util_token {
|
||||||
my $itoa62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
my $length = shift;
|
||||||
|
my $chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||||
my $token = '';
|
my $token = '';
|
||||||
$token .= substr($itoa62,int(rand(62)),1) while length($token) < 24;
|
$token .= substr($chars,int(makerandom_itv(Strength => 0, Upper => 62)),1) while length($token) < $length;
|
||||||
return $token;
|
return $token;
|
||||||
}
|
}
|
||||||
|
|
||||||
# generate a random request id
|
|
||||||
sub util_req_id {
|
|
||||||
my $itoa36 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
||||||
my $id = '';
|
|
||||||
$id .= substr($itoa36,int(rand(36)),1) while length($id) < 8;
|
|
||||||
return $id;
|
|
||||||
}
|
|
||||||
|
|
||||||
# get a user from the database by email
|
# get a user from the database by email
|
||||||
sub util_get_user {
|
sub util_get_user {
|
||||||
my $email = shift;
|
my $email = shift;
|
||||||
|
@ -564,7 +558,7 @@ EOF
|
||||||
|
|
||||||
if (-f "$basename.plan") {
|
if (-f "$basename.plan") {
|
||||||
my $details = {};
|
my $details = {};
|
||||||
open(my $plan_file, '<', "$basename.plan");
|
open(my $plan_file, '<', "$basename.plan") or die $!;
|
||||||
flock($plan_file, LOCK_SH);
|
flock($plan_file, LOCK_SH);
|
||||||
my $mtime = (stat($plan_file))[9];
|
my $mtime = (stat($plan_file))[9];
|
||||||
my $timestamp = strftime("%a, %d %b %Y %H:%M:%S %z", localtime($mtime));
|
my $timestamp = strftime("%a, %d %b %Y %H:%M:%S %z", localtime($mtime));
|
||||||
|
@ -574,7 +568,7 @@ EOF
|
||||||
close($plan_file);
|
close($plan_file);
|
||||||
|
|
||||||
if (-f "$basename.asc") {
|
if (-f "$basename.asc") {
|
||||||
open(my $sig_file, '<', "$basename.asc");
|
open(my $sig_file, '<', "$basename.asc") or die $!;
|
||||||
flock($sig_file, LOCK_SH);
|
flock($sig_file, LOCK_SH);
|
||||||
local $/;
|
local $/;
|
||||||
$details->{'signature'} = <$sig_file>;
|
$details->{'signature'} = <$sig_file>;
|
||||||
|
|
|
@ -108,7 +108,7 @@ if [ -z "$USE_DOCKER" ]; then
|
||||||
DATABASE="$BASEDIR/data/test.db" \
|
DATABASE="$BASEDIR/data/test.db" \
|
||||||
PLAN_DIR="$BASEDIR/data/plans" \
|
PLAN_DIR="$BASEDIR/data/plans" \
|
||||||
SENDMAIL=/usr/bin/true \
|
SENDMAIL=/usr/bin/true \
|
||||||
perl "$BASEDIR/../server.pl" -d >>/dev/null 2>>/dev/null
|
perl "$BASEDIR/../server.pl" -d >>/dev/null
|
||||||
else
|
else
|
||||||
docker build -t dotplan-online-test "$BASEDIR/.."
|
docker build -t dotplan-online-test "$BASEDIR/.."
|
||||||
docker run --name dotplan_online_test -d --rm \
|
docker run --name dotplan_online_test -d --rm \
|
||||||
|
|
Reference in New Issue