From: Wolfgang Bumiller Date: Tue, 2 Apr 2019 10:21:55 +0000 (+0200) Subject: u2f authentication X-Git-Url: https://git.proxmox.com/?p=pve-access-control.git;a=commitdiff_plain;h=18f8ba1803db9889c21570179efe3d70de53b37c u2f authentication Signed-off-by: Wolfgang Bumiller --- diff --git a/PVE/API2/AccessControl.pm b/PVE/API2/AccessControl.pm index 6de43b7..677592b 100644 --- a/PVE/API2/AccessControl.pm +++ b/PVE/API2/AccessControl.pm @@ -123,21 +123,33 @@ my $verify_auth = sub { my $create_ticket = sub { my ($rpcenv, $username, $pw_or_ticket, $otp) = @_; - my $ticketuser; + my ($ticketuser, $u2fdata); if (($ticketuser = PVE::AccessControl::verify_ticket($pw_or_ticket, 1)) && ($ticketuser eq 'root@pam' || $ticketuser eq $username)) { # valid ticket. Note: root@pam can create tickets for other users } else { - $username = PVE::AccessControl::authenticate_user($username, $pw_or_ticket, $otp); + ($username, $u2fdata) = PVE::AccessControl::authenticate_user($username, $pw_or_ticket, $otp); + } + + my %extra; + my $ticket_data = $username; + if (defined($u2fdata)) { + my $u2f = get_u2f_instance($rpcenv, $u2fdata->@{qw(publicKey keyHandle)}); + my $challenge = $u2f->auth_challenge() + or die "failed to get u2f challenge\n"; + $challenge = decode_json($challenge); + $extra{U2FChallenge} = $challenge; + $ticket_data = "u2f!$username!$challenge->{challenge}"; } - my $ticket = PVE::AccessControl::assemble_ticket($username); + my $ticket = PVE::AccessControl::assemble_ticket($ticket_data); my $csrftoken = PVE::AccessControl::assemble_csrf_prevention_token($username); return { ticket => $ticket, username => $username, CSRFPreventionToken => $csrftoken, + %extra, }; }; @@ -257,6 +269,7 @@ __PACKAGE__->register_method ({ ticket => { type => 'string', optional => 1}, CSRFPreventionToken => { type => 'string', optional => 1 }, clustername => { type => 'string', optional => 1 }, + # cap => computed api permissions, unless there's a u2f challenge } }, code => sub { @@ -286,7 +299,8 @@ __PACKAGE__->register_method ({ die PVE::Exception->new("authentication failure\n", code => 401); } - $res->{cap} = &$compute_api_permission($rpcenv, $username); + $res->{cap} = &$compute_api_permission($rpcenv, $username) + if !defined($res->{U2FChallenge}); if (PVE::Corosync::check_conf_exists(1)) { if ($rpcenv->check($username, '/', ['Sys.Audit'], 1)) { diff --git a/PVE/AccessControl.pm b/PVE/AccessControl.pm index 655f306..0c59334 100644 --- a/PVE/AccessControl.pm +++ b/PVE/AccessControl.pm @@ -265,11 +265,11 @@ my $get_ticket_age_range = sub { }; sub assemble_ticket { - my ($username) = @_; + my ($data) = @_; my $rsa_priv = get_privkey(); - return PVE::Ticket::assemble_rsa_ticket($rsa_priv, 'PVE', $username); + return PVE::Ticket::assemble_rsa_ticket($rsa_priv, 'PVE', $data); } sub verify_ticket { @@ -290,23 +290,44 @@ sub verify_ticket { $rsa_pub, 'PVE', $ticket, undef, $min, $max, 1); }; - my ($username, $age) = $check->(); + my ($data, $age) = $check->(); # check with old, rotated key if current key failed - ($username, $age) = $check->(1) if !defined($username); + ($data, $age) = $check->(1) if !defined($data); - if (!defined($username)) { + my $auth_failure = sub { if ($noerr) { return undef; } else { # raise error via undef ticket PVE::Ticket::verify_rsa_ticket(undef, 'PVE'); } + }; + + if (!defined($data)) { + return $auth_failure->(); + } + + my ($username, $challenge); + if ($data =~ m{^u2f!([^!]+)!([0-9a-zA-Z/.=_\-+]+)$}) { + # Ticket for u2f-users: + ($username, $challenge) = ($1, $2); + if ($challenge eq 'verified') { + # u2f challenge was completed + $challenge = undef; + } elsif (!wantarray) { + # The caller is not aware there could be an ongoing challenge, + # so we treat this ticket as invalid: + return $auth_failure->(); + } + } else { + # Regular ticket (full access) + $username = $data; } return undef if !PVE::Auth::Plugin::verify_username($username, $noerr); - return wantarray ? ($username, $age) : $username; + return wantarray ? ($username, $age, $challenge) : $username; } # VNC tickets