mirror of
https://git.proxmox.com/git/pve-access-control
synced 2025-06-12 15:04:42 +00:00
use new PVE::Ticket class
This commit is contained in:
parent
1f1c4593a1
commit
a1f8aaae84
@ -10,6 +10,8 @@ use Net::IP;
|
|||||||
use MIME::Base64;
|
use MIME::Base64;
|
||||||
use MIME::Base32; #libmime-base32-perl
|
use MIME::Base32; #libmime-base32-perl
|
||||||
use Digest::SHA;
|
use Digest::SHA;
|
||||||
|
|
||||||
|
use PVE::Ticket;
|
||||||
use URI::Escape;
|
use URI::Escape;
|
||||||
use LWP::UserAgent;
|
use LWP::UserAgent;
|
||||||
use PVE::Tools qw(run_command lock_file file_get_contents split_list safe_print);
|
use PVE::Tools qw(run_command lock_file file_get_contents split_list safe_print);
|
||||||
@ -89,30 +91,18 @@ my $get_csrfr_secret = sub {
|
|||||||
sub assemble_csrf_prevention_token {
|
sub assemble_csrf_prevention_token {
|
||||||
my ($username) = @_;
|
my ($username) = @_;
|
||||||
|
|
||||||
my $timestamp = sprintf("%08X", time());
|
my $secret = &$get_csrfr_secret();
|
||||||
|
|
||||||
my $digest = Digest::SHA::sha1_base64("$timestamp:$username", &$get_csrfr_secret());
|
return PVE::Ticket::assemble_csrf_prevention_token ($secret, $username);
|
||||||
|
|
||||||
return "$timestamp:$digest";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub verify_csrf_prevention_token {
|
sub verify_csrf_prevention_token {
|
||||||
my ($username, $token, $noerr) = @_;
|
my ($username, $token, $noerr) = @_;
|
||||||
|
|
||||||
if ($token =~ m/^([A-Z0-9]{8}):(\S+)$/) {
|
my $secret = &$get_csrfr_secret();
|
||||||
my $sig = $2;
|
|
||||||
my $timestamp = $1;
|
|
||||||
my $ttime = hex($timestamp);
|
|
||||||
|
|
||||||
my $digest = Digest::SHA::sha1_base64("$timestamp:$username", &$get_csrfr_secret());
|
return PVE::Ticket::verify_csrf_prevention_token(
|
||||||
|
$secret, $username, $token, -300, $ticket_lifetime, $noerr);
|
||||||
my $age = time() - $ttime;
|
|
||||||
return if ($digest eq $sig) && ($age > -300) && ($age < $ticket_lifetime);
|
|
||||||
}
|
|
||||||
|
|
||||||
die "Permission denied - invalid csrf token\n" if !$noerr;
|
|
||||||
|
|
||||||
return undef;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
my $pve_auth_priv_key;
|
my $pve_auth_priv_key;
|
||||||
@ -132,42 +122,22 @@ sub assemble_ticket {
|
|||||||
|
|
||||||
my $rsa_priv = get_privkey();
|
my $rsa_priv = get_privkey();
|
||||||
|
|
||||||
my $timestamp = sprintf("%08X", time());
|
return PVE::Ticket::assemble_rsa_ticket($rsa_priv, 'PVE', $username);
|
||||||
|
|
||||||
my $plain = "PVE:$username:$timestamp";
|
|
||||||
|
|
||||||
my $ticket = $plain . "::" . encode_base64($rsa_priv->sign($plain), '');
|
|
||||||
|
|
||||||
return $ticket;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub verify_ticket {
|
sub verify_ticket {
|
||||||
my ($ticket, $noerr) = @_;
|
my ($ticket, $noerr) = @_;
|
||||||
|
|
||||||
if ($ticket && $ticket =~ m/^(PVE:\S+)::([^:\s]+)$/) {
|
my $rsa_pub = get_pubkey();
|
||||||
my $plain = $1;
|
|
||||||
my $sig = $2;
|
|
||||||
|
|
||||||
my $rsa_pub = get_pubkey();
|
my ($username, $age) = PVE::Ticket::verify_rsa_ticket(
|
||||||
if ($rsa_pub->verify($plain, decode_base64($sig))) {
|
$rsa_pub, 'PVE', $ticket, undef, -300, $ticket_lifetime, $noerr);
|
||||||
if ($plain =~ m/^PVE:(\S+):([A-Z0-9]{8})$/) {
|
|
||||||
my $username = $1;
|
|
||||||
my $timestamp = $2;
|
|
||||||
my $ttime = hex($timestamp);
|
|
||||||
|
|
||||||
my $age = time() - $ttime;
|
return undef if $noerr && !defined($username);
|
||||||
|
|
||||||
if (PVE::Auth::Plugin::verify_username($username, 1) &&
|
return undef if !PVE::Auth::Plugin::verify_username($username, $noerr);
|
||||||
($age > -300) && ($age < $ticket_lifetime)) {
|
|
||||||
return wantarray ? ($username, $age) : $username;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
die "permission denied - invalid ticket\n" if !$noerr;
|
return wantarray ? ($username, $age) : $username;
|
||||||
|
|
||||||
return undef;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# VNC tickets
|
# VNC tickets
|
||||||
@ -178,108 +148,41 @@ sub assemble_vnc_ticket {
|
|||||||
|
|
||||||
my $rsa_priv = get_privkey();
|
my $rsa_priv = get_privkey();
|
||||||
|
|
||||||
my $timestamp = sprintf("%08X", time());
|
|
||||||
|
|
||||||
my $plain = "PVEVNC:$timestamp";
|
|
||||||
|
|
||||||
$path = normalize_path($path);
|
$path = normalize_path($path);
|
||||||
|
|
||||||
my $full = "$plain:$username:$path";
|
my $secret_data = "$username:$path";
|
||||||
|
|
||||||
my $ticket = $plain . "::" . encode_base64($rsa_priv->sign($full), '');
|
return PVE::Ticket::assemble_rsa_ticket(
|
||||||
|
$rsa_priv, 'PVEVNC', undef, $secret_data);
|
||||||
return $ticket;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub verify_vnc_ticket {
|
sub verify_vnc_ticket {
|
||||||
my ($ticket, $username, $path, $noerr) = @_;
|
my ($ticket, $username, $path, $noerr) = @_;
|
||||||
|
|
||||||
if ($ticket && $ticket =~ m/^(PVEVNC:\S+)::([^:\s]+)$/) {
|
my $rsa_pub = get_pubkey();
|
||||||
my $plain = $1;
|
|
||||||
my $sig = $2;
|
|
||||||
my $full = "$plain:$username:$path";
|
|
||||||
|
|
||||||
my $rsa_pub = get_pubkey();
|
my $secret_data = "$username:$path";
|
||||||
# Note: sign only match if $username and $path is correct
|
|
||||||
if ($rsa_pub->verify($full, decode_base64($sig))) {
|
|
||||||
if ($plain =~ m/^PVEVNC:([A-Z0-9]{8})$/) {
|
|
||||||
my $ttime = hex($1);
|
|
||||||
|
|
||||||
my $age = time() - $ttime;
|
return PVE::Ticket::verify_rsa_ticket(
|
||||||
|
$rsa_pub, 'PVEVNC', $ticket, $secret_data, -20, 40, $noerr);
|
||||||
if (($age > -20) && ($age < 40)) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
die "permission denied - invalid vnc ticket\n" if !$noerr;
|
|
||||||
|
|
||||||
return undef;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub assemble_spice_ticket {
|
sub assemble_spice_ticket {
|
||||||
my ($username, $vmid, $node) = @_;
|
my ($username, $vmid, $node) = @_;
|
||||||
|
|
||||||
my $rsa_priv = get_privkey();
|
|
||||||
|
|
||||||
my $timestamp = sprintf("%08x", time());
|
|
||||||
|
|
||||||
my $randomstr = "PVESPICE:$timestamp:$vmid:$node:" . rand(10);
|
|
||||||
|
|
||||||
# this should be used as one-time password
|
|
||||||
# max length is 60 chars (spice limit)
|
|
||||||
# we pass this to qemu set_pasword and limit lifetime there
|
|
||||||
# keep this secret
|
|
||||||
my $ticket = Digest::SHA::sha1_hex($rsa_priv->sign($randomstr));
|
|
||||||
|
|
||||||
# Note: spice proxy connects with HTTP, so $proxyticket is exposed to public
|
|
||||||
# we use a signature/timestamp to make sure nobody can fake such a ticket
|
|
||||||
# an attacker can use this $proxyticket, but he will fail because $ticket is
|
|
||||||
# private.
|
|
||||||
# The proxy needs to be able to extract/verify the ticket
|
|
||||||
# Note: data needs to be lower case only, because virt-viewer needs that
|
|
||||||
# Note: RSA signature are too long (>=256 charaters) and make problems with remote-viewer
|
|
||||||
|
|
||||||
my $secret = &$get_csrfr_secret();
|
my $secret = &$get_csrfr_secret();
|
||||||
my $plain = "pvespiceproxy:$timestamp:$vmid:" . lc($node);
|
|
||||||
|
|
||||||
# produces 40 characters
|
return PVE::Ticket::assemble_spice_ticket(
|
||||||
my $sig = unpack("H*", Digest::SHA::sha1($plain, &$get_csrfr_secret()));
|
$secret, $username, $vmid, $node);
|
||||||
|
|
||||||
#my $sig = unpack("H*", $rsa_priv->sign($plain)); # this produce too long strings (512)
|
|
||||||
|
|
||||||
my $proxyticket = $plain . "::" . $sig;
|
|
||||||
|
|
||||||
return ($ticket, $proxyticket);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
sub verify_spice_connect_url {
|
sub verify_spice_connect_url {
|
||||||
my ($connect_str) = @_;
|
my ($connect_str) = @_;
|
||||||
|
|
||||||
# Note: we pass the spice ticket as 'host', so the
|
my $secret = &$get_csrfr_secret();
|
||||||
# spice viewer connects with "$ticket:$port"
|
|
||||||
|
|
||||||
return undef if !$connect_str;
|
return PVE::Ticket::verify_spice_connect_url($secret, $connect_str);
|
||||||
|
|
||||||
if ($connect_str =~m/^pvespiceproxy:([a-z0-9]{8}):(\d+):(\S+)::([a-z0-9]{40}):(\d+)$/) {
|
|
||||||
my ($timestamp, $vmid, $node, $hexsig, $port) = ($1, $2, $3, $4, $5, $6);
|
|
||||||
my $ttime = hex($timestamp);
|
|
||||||
my $age = time() - $ttime;
|
|
||||||
|
|
||||||
# use very limited lifetime - is this enough?
|
|
||||||
return undef if !(($age > -20) && ($age < 40));
|
|
||||||
|
|
||||||
my $plain = "pvespiceproxy:$timestamp:$vmid:$node";
|
|
||||||
my $sig = unpack("H*", Digest::SHA::sha1($plain, &$get_csrfr_secret()));
|
|
||||||
|
|
||||||
if ($sig eq $hexsig) {
|
|
||||||
return ($vmid, $node, $port);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return undef;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub read_x509_subject_spice {
|
sub read_x509_subject_spice {
|
||||||
|
Loading…
Reference in New Issue
Block a user