replaced rand() with cryptographically secure alternative

This commit is contained in:
Rudis Muiznieks 2020-07-19 10:04:08 -05:00
parent 17b3c309fe
commit a5cf07a181
3 changed files with 17 additions and 23 deletions

View File

@ -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

View File

@ -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>;

View 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 \