From 051716c095f3d321555d79261de6832c0d7b3b23 Mon Sep 17 00:00:00 2001 From: Rudis Muiznieks Date: Fri, 17 Jul 2020 23:57:40 -0500 Subject: [PATCH] started on POST /verify/{email} service --- README.md | 8 ++++---- server.pl | 58 +++++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 8a7dc07..2c6f29c 100644 --- a/README.md +++ b/README.md @@ -34,16 +34,16 @@ ### Plans - `PUT /plan/{email}` - update a plan - - request data: `{"plan":"whatever","signature":"whatever","auth":"token"}` + - request data: `{"plan":"whatever","signature":"base64 encoded signature","auth":"token"}` - omitting `plan` from the payload will delete the existing plan - `GET /plan/{email}` - retrieve a plan - `text/plain` by default - raw plan content - `?format=html` or `Accept: text/html` - plan content with html entity encoding for special characters - - `?format=json` or `Accept: application/json` - response data: `{"plan":"whatever","signature":"whatever"}` + - `?format=json` or `Accept: application/json` - response data: `{"plan":"whatever","signature":"base64 encoded signature"}` - `404` if no plan found - `301` redirect if plan is on a different provider - `POST /verify/{email}` - verify PGP signature of a plan - - request data: `{"pgpkey":"public key"}` - - response data: `{"plan":"whatever","verified":true}` or `{"verified":false}` + - request data: `{"pgpkey":"ascii public key"}` + - response data: `{"plan":"whatever","verified":1}` or `{"verified":0}` - `404` if no plan found - `308` redirect if plan is on a different provider diff --git a/server.pl b/server.pl index 87cda14..6560af2 100644 --- a/server.pl +++ b/server.pl @@ -23,6 +23,7 @@ my $minimum_email_length = $ENV{'MINIMUM_EMAIL_LENGTH'} || 6; my $maximum_email_length = $ENV{'MAXIMUM_EMAIL_LENGTH'} || 120; my $maximum_plan_length = $ENV{'MAXIMUM_PLAN_LENGTH'} || 4096; my $maximum_signature_length = $ENV{'MAXIMUM_SIGNATURE_LENGTH'} || 1024; +my $maximum_pubkey_length = $ENV{'MAXIMUM_PUBKEY_LENGTH'} || 5125; my $hostname = $ENV{'HOSTNAME'}; my $localdomains = {}; @@ -49,6 +50,7 @@ if (defined $ENV{'LOCAL_DOMAINS'}) { } use DBI; + use File::Temp qw(tempfile); use Fcntl qw(:flock); use Net::DNS::Resolver; use Crypt::Eksblowfish::Bcrypt qw(bcrypt_hash en_base64); @@ -336,7 +338,6 @@ EOF my $plan = $body->{'plan'}; my $signature = $body->{'signature'}; my $token = $body->{'auth'}; - util_log("authenticating $token $user->{token}"); if (!defined $user->{'token'} || !defined $user->{'token_expires'} || !defined $token || $token ne $user->{'token'} || $user->{'token_expires'} < time) { print_response($cgi, 401, $not_authorized); } elsif (length($plan) > $maximum_plan_length) { @@ -384,7 +385,41 @@ EOF } ##### POST /verify/{email} - sub verify_plan { shift; print_response(shift, 501, $not_implemented); } + sub verify_plan { + my ($email, $cgi) = @_; + + my $plan = util_get_plan($email); + + if (defined $plan && defined $plan->{'redirect'}) { + # found external plan service, redirect request + print_response($cgi, 308, encode_json({location => $plan->{'redirect'}}), 'application/json', $plan->{'redirect'}); + } elsif (defined $plan) { + my $pubkey = util_json_body($cgi)->{'pgpkey'}; + if (!defined $pubkey || !defined $plan->{'signature'}) { + print_json_response($cgi, 200, {verified => 0}); + } elsif (length($pubkey) > $maximum_pubkey_length) { + print_json_response($cgi, 400, {error => "Pubkey exceeds maximum length of $maximum_pubkey_length."}); + } else { + my ($keyfh, $keyfile) = tempfile('tmpXXXXXX', TMPDIR => 1); + print $keyfh $pubkey; + close($keyfh); + util_log("saved key file to $keyfile"); + my $basename = "$plan_dir/" . shell_quote($email); + my $convert = system("gpg2 --dearmor $keyfile > $keyfile.gpg"); + my $valid = system("gpg2 --no-default-keyring --keyring $keyfile.gpg --verify $basename.sig $basename.plan"); + if ($convert != 0 || $valid != 0) { + print_json_response($cgi, 200, {verified => 0}); + } else { + print_json_response($cgi, 200, { + plan => $plan->{'plan'}, + verified => 1 + }); + } + } + } else { + print_response($cgi, 404, $not_found); + } + } ################### # Utility Functions @@ -511,7 +546,8 @@ EOF if (defined $plan && defined $signature) { open(my $sig_file, '>', "$basename.sig"); flock($sig_file, LOCK_EX); - print $sig_file $signature; + binmode $sig_file; + print $sig_file decode_base64($signature); close($sig_file); } elsif (-f "$basename.sig") { unlink "$basename.sig"; @@ -586,22 +622,10 @@ EOF my $json = $cgi->param('POSTDATA') || $cgi->param('PUTDATA'); return decode_json($json); } - - ############ - # Destructor - ############ - - END { - if (defined $_log) { - close($_log); - } - if (defined $_dbh) { - $_dbh->disconnect(); - } - } } -my $daemonize = $ARGV[0] == '-d' if @ARGV > 0; +# only supports one optional argument -d to daemonize +my $daemonize = $ARGV[0] == '-d' if @ARGV == 1; # start server and fork process as current user my ($user, $passwd, $uid, $gid) = getpwuid $<;