diff options
author | Matthias Benkard <matthias.benkard@egym.de> | 2015-04-14 08:39:27 +0000 |
---|---|---|
committer | Matthias Andreas Benkard <code@mail.matthias.benkard.de> | 2015-04-23 21:55:02 +0200 |
commit | ba65cc50b7b468f0738398312a468ea413727bdc (patch) | |
tree | 19749bd8803a2f718013cf8901c04ea658f98731 | |
parent | faea34e5c94922645b337bdeb5db32871eb1fde9 (diff) |
QT-1900 Add a CSRF token to the OIDC login flow.
This improves security by generating a CSRF token, passing it to the
OIDC IdP, and validating it afterwards. The token is stored in
a cookie reverse-encrypted with MulkyID's private key.
-rw-r--r-- | build.PL | 4 | ||||
-rwxr-xr-x | www/authenticate.pl | 28 | ||||
-rw-r--r-- | www/common.pl | 27 | ||||
-rwxr-xr-x | www/logged_in_p.pl | 1 | ||||
-rwxr-xr-x | www/login.pl | 8 |
5 files changed, 54 insertions, 14 deletions
@@ -29,7 +29,9 @@ my $build = Net::MulkyID::Builder->new "OIDC::Lite::Client::WebServer" => 0, "OIDC::Lite::Model::IDToken" => 0, "LWP::UserAgent" => 0, - "HTTP::Request" => 0 + "HTTP::Request" => 0, + "Math::Random::ISAAC::XS" => 0, + "Bytes::Random::Secure" => 0 }, build_requires => { "LWP::Simple" => 0, diff --git a/www/authenticate.pl b/www/authenticate.pl index 3c865ac..b2bd43f 100755 --- a/www/authenticate.pl +++ b/www/authenticate.pl @@ -13,6 +13,8 @@ use CGI::Fast; use OIDC::Lite; use OIDC::Lite::Client::WebServer; +use Bytes::Random::Secure qw(random_bytes_base64); + do "common.pl"; while (my $cgi = new CGI::Fast) { @@ -35,18 +37,20 @@ while (my $cgi = new CGI::Fast) { authorize_uri => 'https://accounts.google.com/o/oauth2/auth', access_token_uri => 'https://accounts.google.com/o/oauth2/token' ); - # FIXME: Make `state` a unique, random session token! (Maybe a - # signed, timestamped web token, so stateless?) - print $cgi->redirect($oidc_client->uri_to_redirect( - redirect_uri => reluri($cgi, 'login.pl'), - scope => 'openid email', - state => '', - extra => { - access_type => 'online', - login_hint => $claimed_email, - response_type => 'code' - } - )); + my $csrf_token = random_bytes_base64(32); #256 bits + my $csrf_token_cookie = make_cookie('mulkyid_csrf_token', $csrf_token); + print $cgi->redirect( + -cookie => $csrf_token_cookie, + -url => $oidc_client->uri_to_redirect( + redirect_uri => reluri($cgi, 'login.pl'), + scope => 'openid email', + state => $csrf_token, + extra => { + access_type => 'online', + login_hint => $claimed_email, + response_type => 'code' + }) + ); } default { die "Invalid auth_type! " . $::MULKONF->{auth_type}; diff --git a/www/common.pl b/www/common.pl index 63b8d0f..eb965f3 100644 --- a/www/common.pl +++ b/www/common.pl @@ -5,9 +5,12 @@ use common::sense; #use Modern::Perl 2011; use Modern::Perl; +use File::Slurp; use Mail::ExpandAliases; use URI; use MIME::Base64 qw(encode_base64 decode_base64); +use Crypt::OpenSSL::RSA; +use CGI::Cookie; sub load_config() { $::MULKONF = { }; @@ -54,3 +57,27 @@ sub encode_base64_url($) { $s =~ s/\n//g; return $s; } + +sub acquire_private_key() { + my $key = Crypt::OpenSSL::RSA->new_private_key(scalar read_file($::MULKONF->{pemfile})); + $key->use_pkcs1_padding(); + $key->use_sha256_hash(); + return $key; +} + +sub make_cookie($$) { + my ($name, $value) = @_; + my $key = acquire_private_key; + my $reverse_encrypted_value = $key->private_encrypt($value); + my $cookie = CGI::Cookie->new(-name => $name, -value =>encode_base64_url($reverse_encrypted_value)); +} + +sub read_cookie($$) { + my ($cgi, $name) = @_; + my $cookie = $cgi->cookie($name); + return unless ($cookie); + my $key = acquire_private_key; + my $value = $key->public_decrypt(decode_base64_url($cookie)); + warn "cookie `$name` was forged!" unless $value; + return $value; +} diff --git a/www/logged_in_p.pl b/www/logged_in_p.pl index b076618..5752959 100755 --- a/www/logged_in_p.pl +++ b/www/logged_in_p.pl @@ -52,4 +52,3 @@ while (my $cgi = new CGI::Fast) { say encode_json({logged_in_p => 0}); } } - diff --git a/www/login.pl b/www/login.pl index fe1729d..5d27cbe 100755 --- a/www/login.pl +++ b/www/login.pl @@ -79,6 +79,14 @@ while (my $cgi = new CGI::Fast) { } when ('google') { my $code = $cgi->param('code') or die "Authorization code is missing."; + + # Validate CSRF token. + my $oauth_state = $cgi->param('state'); + my $csrf_token = read_cookie($cgi, 'mulkyid_csrf_token'); + unless ($csrf_token && $oauth_state && $csrf_token eq $oauth_state) { + die "CSRF token was forged!"; + } + my $oidc_client = OIDC::Lite::Client::WebServer->new( id => $::MULKONF->{'google_oauth2_client_id'}, secret => $::MULKONF->{'google_oauth2_client_secret'}, |