2024-06-07 10:27:10 +00:00
/*
* SPDX - License - Identifier : GPL - 2.0 - or - later
*/
# include <config.h>
# include "internal.h"
# include <getopt.h>
# include <stdio.h>
# include <stdlib.h>
# include <gnutls/gnutls.h>
# include <gnutls/x509.h>
# include "virgettext.h"
# include "virfile.h"
# include "virnettlsconfig.h"
# include "virnettlscert.h"
# include "virutil.h"
# include "virt-validate-common.h"
static bool
virPKIValidateFile ( const char * file ,
uid_t owner ,
gid_t group ,
mode_t mode )
{
struct stat sb ;
if ( stat ( file , & sb ) < 0 )
return false ;
if ( sb . st_uid ! = owner | |
sb . st_gid ! = group )
return false ;
return ( sb . st_mode & 0777 ) = = mode ;
}
# define FILE_REQUIRE_EXISTS(scope, path, message, hint, ...) \
do { \
virValidateCheck ( scope , " %s " , message ) ; \
if ( ! virFileExists ( path ) ) { \
virValidateFail ( VIR_VALIDATE_FAIL , hint , __VA_ARGS__ ) ; \
ok = false ; \
goto done ; \
} else { \
virValidatePass ( ) ; \
} \
} while ( 0 )
# define FILE_REQUIRE_ACCESS(scope, path, message, uid, gid, mode, hint, ...) \
do { \
virValidateCheck ( scope , " %s " , message ) ; \
if ( ! virPKIValidateFile ( path , uid , gid , mode ) ) { \
virValidateFail ( VIR_VALIDATE_FAIL , hint , __VA_ARGS__ ) ; \
ok = false ; \
} else { \
virValidatePass ( ) ; \
} \
} while ( 0 )
static bool
2024-06-07 10:30:35 +00:00
virPKIValidateTrust ( bool system , const char * path )
2024-06-07 10:27:10 +00:00
{
g_autofree char * cacert = NULL , * cacrl = NULL ;
bool ok = true ;
2024-06-07 10:30:35 +00:00
if ( system ) {
virNetTLSConfigSystemTrust ( & cacert ,
& cacrl ) ;
FILE_REQUIRE_EXISTS ( " TRUST " ,
LIBVIRT_PKI_DIR ,
_ ( " Checking if system PKI dir exists " ) ,
_ ( " The system PKI dir %1$s is usually installed as part of the base filesystem or openssl packages " ) ,
LIBVIRT_PKI_DIR ) ;
FILE_REQUIRE_ACCESS ( " TRUST " ,
LIBVIRT_PKI_DIR ,
_ ( " Checking system PKI dir access " ) ,
0 , 0 , 0755 ,
_ ( " The system PKI dir %1$s must be accessible to all users. As root, run: chown root.root; chmod 0755 %2$s " ) ,
LIBVIRT_PKI_DIR , LIBVIRT_PKI_DIR ) ;
FILE_REQUIRE_EXISTS ( " TRUST " ,
LIBVIRT_CACERT_DIR ,
_ ( " Checking if system CA dir exists " ) ,
_ ( " The system CA dir %1$s is usually installed as part of the base filesystem or openssl packages " ) ,
LIBVIRT_CACERT_DIR ) ;
FILE_REQUIRE_ACCESS ( " TRUST " ,
LIBVIRT_CACERT_DIR ,
_ ( " Checking system CA dir access " ) ,
0 , 0 , 0755 ,
_ ( " The system CA dir %1$s must be accessible to all users. As root, run: chown root.root; chmod 0755 %2$s " ) ,
LIBVIRT_CACERT_DIR , LIBVIRT_CACERT_DIR ) ;
} else if ( path ) {
virNetTLSConfigCustomTrust ( path ,
& cacert ,
& cacrl ) ;
FILE_REQUIRE_EXISTS ( " TRUST " ,
path ,
_ ( " Checking if custom PKI base dir exists " ) ,
_ ( " Create the dir %1$s " ) ,
path ) ;
FILE_REQUIRE_ACCESS ( " TRUST " ,
path ,
_ ( " Checking custom PKI base dir access " ) ,
getuid ( ) , getgid ( ) , 0700 ,
_ ( " The PKI base dir %1$s must not be accessible to other users. Run: chown %2$d.%3$d %4$s; chmod 0700 %5$s " ) ,
path , getuid ( ) , getgid ( ) , path , path ) ;
} else {
g_autofree char * pkipath = virNetTLSConfigUserPKIBaseDir ( ) ;
virNetTLSConfigUserTrust ( & cacert ,
& cacrl ) ;
FILE_REQUIRE_EXISTS ( " TRUST " ,
pkipath ,
_ ( " Checking if user PKI base dir exists " ) ,
_ ( " Create the dir %1$s " ) ,
pkipath ) ;
FILE_REQUIRE_ACCESS ( " TRUST " ,
pkipath ,
_ ( " Checking user PKI base dir access " ) ,
getuid ( ) , getgid ( ) , 0700 ,
_ ( " The PKI base dir %1$s must not be accessible to other users. Run: chown %2$d.%3$d %4$s; chmod 0700 %5$s " ) ,
pkipath , getuid ( ) , getgid ( ) , pkipath , pkipath ) ;
}
2024-06-07 10:27:10 +00:00
FILE_REQUIRE_EXISTS ( " TRUST " ,
cacert ,
_ ( " Checking if CA cert exists " ) ,
_ ( " The machine cannot act as a client or server. See https://libvirt.org/kbase/tlscerts.html#setting-up-a-certificate-authority-ca on how to install %1$s " ) ,
cacert ) ;
2024-06-07 10:30:35 +00:00
if ( system ) {
FILE_REQUIRE_ACCESS ( " TRUST " ,
cacert ,
_ ( " Checking CA cert access " ) ,
0 , 0 , 0644 ,
_ ( " The CA certificate %1$s must be accessible to all users. As root run: chown root.root %2$s; chmod 0644 %3$s " ) ,
cacert , cacert , cacert ) ;
} else {
FILE_REQUIRE_ACCESS ( " TRUST " ,
cacert ,
_ ( " Checking CA cert access " ) ,
getuid ( ) , getgid ( ) , 0600 ,
_ ( " The CA certificate %1$s must not be accessible to other users. As this user, run: chown %2$d.%3$d %4$s; chmod 0600 %5$s " ) ,
cacert , getuid ( ) , getgid ( ) , cacert , cacert ) ;
}
2024-06-07 10:27:10 +00:00
done :
return ok ;
}
static bool
2024-06-07 10:30:35 +00:00
virPKIValidateIdentity ( bool isServer , bool system , const char * path )
2024-06-07 10:27:10 +00:00
{
g_autofree char * cacert = NULL , * cacrl = NULL ;
g_autofree char * cert = NULL , * key = NULL ;
bool ok = true ;
const char * scope = isServer ? " SERVER " : " CLIENT " ;
2024-06-07 10:30:35 +00:00
if ( system ) {
virNetTLSConfigSystemTrust ( & cacert ,
& cacrl ) ;
virNetTLSConfigSystemIdentity ( isServer ,
& cert ,
& key ) ;
FILE_REQUIRE_EXISTS ( scope ,
LIBVIRT_CERT_DIR ,
_ ( " Checking if system cert dir exists " ) ,
_ ( " The system cert dir %1$s is usually installed as part of the libvirt package " ) ,
LIBVIRT_CERT_DIR ) ;
FILE_REQUIRE_ACCESS ( scope ,
LIBVIRT_CERT_DIR ,
_ ( " Checking system cert dir access " ) ,
0 , 0 , 0755 ,
_ ( " The system cert dir %1$s must be accessible to all users. As root, run: chown root.root; chmod 0755 %2$s " ) ,
2024-06-12 10:54:26 +00:00
LIBVIRT_CERT_DIR , LIBVIRT_CERT_DIR ) ;
2024-06-07 10:30:35 +00:00
FILE_REQUIRE_EXISTS ( scope ,
LIBVIRT_KEY_DIR ,
_ ( " Checking if system key dir exists " ) ,
_ ( " The system key dir %1$s is usually installed as part of the libvirt package " ) ,
LIBVIRT_KEY_DIR ) ;
FILE_REQUIRE_ACCESS ( scope ,
LIBVIRT_KEY_DIR ,
_ ( " Checking system key dir access " ) ,
0 , 0 , 0755 ,
_ ( " The system key dir %1$s must be accessible to all users. As root, run: chown root.root; chmod 0755 %2$s " ) ,
2024-06-12 10:54:26 +00:00
LIBVIRT_KEY_DIR , LIBVIRT_KEY_DIR ) ;
2024-06-07 10:30:35 +00:00
} else if ( path ) {
virNetTLSConfigCustomTrust ( path ,
& cacert ,
& cacrl ) ;
virNetTLSConfigCustomIdentity ( path ,
isServer ,
& cert ,
& key ) ;
} else {
virNetTLSConfigUserTrust ( & cacert ,
& cacrl ) ;
virNetTLSConfigUserIdentity ( isServer ,
& cert ,
& key ) ;
}
2024-06-07 10:27:10 +00:00
FILE_REQUIRE_EXISTS ( scope ,
key ,
_ ( " Checking if key exists " ) ,
isServer ?
_ ( " The machine cannot act as a server. See https://libvirt.org/kbase/tlscerts.html#issuing-server-certificates on how to regenerate %1$s " ) :
_ ( " The machine cannot act as a client. See https://libvirt.org/kbase/tlscerts.html#issuing-client-certificates on how to regenerate %1$s " ) ,
key ) ;
2024-06-07 10:30:35 +00:00
if ( system ) {
FILE_REQUIRE_ACCESS ( scope ,
key ,
_ ( " Checking key access " ) ,
0 , 0 , isServer ? 0600 : 0644 ,
isServer ?
_ ( " The server key %1$s must not be accessible to unprivileged users. As root run: chown root.root %2$s; chmod 0600 %3$s " ) :
_ ( " The client key %1$s must be accessible to all users. As root run: chown root.root %2$s; chmod 0644 %3$s " ) ,
key , key , key ) ;
} else {
FILE_REQUIRE_ACCESS ( scope ,
key ,
_ ( " Checking key access " ) ,
getuid ( ) , getgid ( ) , 0600 ,
isServer ?
_ ( " The server key %1$s must be not be accessible to other users. As this user, run: chown %2$d.%3$d %4$s; chmod 0600 %5$s " ) :
_ ( " The client key %1$s must be not be accessible to other users. As this user, run: chown %2$d.%3$d %4$s; chmod 0600 %5$s " ) ,
key , getuid ( ) , getgid ( ) , key , key ) ;
}
2024-06-07 10:27:10 +00:00
FILE_REQUIRE_EXISTS ( scope ,
cert ,
_ ( " Checking if cert exists " ) ,
isServer ?
_ ( " The machine cannot act as a server. See https://libvirt.org/kbase/tlscerts.html#issuing-server-certificates on how to regenerate %1$s " ) :
_ ( " The machine cannot act as a client. See https://libvirt.org/kbase/tlscerts.html#issuing-client-certificates on how to regenerate %1$s " ) ,
cert ) ;
2024-06-07 10:30:35 +00:00
if ( system ) {
FILE_REQUIRE_ACCESS ( scope ,
cert ,
_ ( " Checking cert access " ) ,
0 , 0 , 0644 ,
isServer ?
_ ( " The server cert %1$s must be accessible to all users. As root run: chown root.root %2$s; chmod 0644 %3$s " ) :
_ ( " The client cert %1$s must be accessible to all users. As root run: chown root.root %2$s; chmod 0644 %3$s " ) ,
cert , cert , cert ) ;
} else {
FILE_REQUIRE_ACCESS ( scope ,
cert ,
_ ( " Checking cert access " ) ,
getuid ( ) , getgid ( ) , 0600 ,
isServer ?
_ ( " The server cert %1$s must be restricted to this user. As this user, run: chown %2$d.%3$d %4$s; chmod 0600 %5$s " ) :
_ ( " The client cert %1$s must be restricted to this user. As this user, run: chown %2$d.%3$d %4$s; chmod 0600 %5$s " ) ,
cert , getuid ( ) , getgid ( ) , cert , cert ) ;
}
2024-06-07 10:27:10 +00:00
virValidateCheck ( scope , " %s " , _ ( " Checking cert properties " ) ) ;
if ( virNetTLSCertSanityCheck ( isServer ,
cacert ,
cert ) < 0 ) {
virValidateFail ( VIR_VALIDATE_FAIL , " %s " ,
virGetLastErrorMessage ( ) ) ;
ok = false ;
} else {
virValidatePass ( ) ;
}
if ( isServer ) {
gnutls_x509_crt_t crt ;
virValidateCheck ( scope , " %s " , _ ( " Checking cert hostname match " ) ) ;
if ( ! ( crt = virNetTLSCertLoadFromFile ( cert , true ) ) ) {
virValidateFail ( VIR_VALIDATE_FAIL ,
_ ( " Unable to load %1$s: %2$s " ) ,
cert , virGetLastErrorMessage ( ) ) ;
} else {
g_autofree char * hostname = virGetHostname ( ) ;
int ret = gnutls_x509_crt_check_hostname ( crt , hostname ) ;
gnutls_x509_crt_deinit ( crt ) ;
if ( ! ret ) {
/* Only warning, since there can be valid reasons for mis-match */
virValidateFail ( VIR_VALIDATE_WARN ,
_ ( " Certificate %1$s owner does not match the hostname %2$s " ) ,
cert , hostname ) ;
ok = false ;
} else {
virValidatePass ( ) ;
}
}
}
done :
return ok ;
}
static void
print_usage ( const char * progname ,
FILE * out )
{
fprintf ( out ,
_ ( " Usage: \n "
" %1$s { -v | -h } [TRUST|SERVER|CLIENT] \n "
" \n "
" Validate TLS certificate configuration \n "
" \n "
" options: \n "
2024-06-07 10:30:35 +00:00
" -s | --system validate system certificates (default) \n "
" -u | --user validate user certificates \n "
" -p DIR | --path DIR validate custom certificate path \n "
2024-06-07 10:27:10 +00:00
" -h | --help display this help and exit \n "
" -v | --version output version information and exit \n " ) ,
progname ) ;
}
int main ( int argc , char * * argv )
{
const char * scope = NULL ;
2024-06-07 10:30:35 +00:00
bool system = false ;
bool user = false ;
const char * path = NULL ;
2024-06-07 10:27:10 +00:00
bool quiet = false ;
int arg = 0 ;
bool ok = true ;
const char * progname = argv [ 0 ] ;
struct option opt [ ] = {
{ " help " , no_argument , NULL , ' h ' } ,
{ " version " , no_argument , NULL , ' v ' } ,
2024-06-07 10:30:35 +00:00
{ " system " , no_argument , NULL , ' s ' } ,
{ " user " , no_argument , NULL , ' u ' } ,
{ " path " , required_argument , NULL , ' p ' } ,
2024-06-07 10:27:10 +00:00
{ NULL , 0 , NULL , 0 } ,
} ;
if ( virGettextInitialize ( ) < 0 )
return EXIT_FAILURE ;
while ( ( arg = getopt_long ( argc , argv , " hvsup: " , opt , NULL ) ) ! = - 1 ) {
switch ( arg ) {
2024-06-07 10:30:35 +00:00
case ' s ' :
system = true ;
break ;
case ' u ' :
user = true ;
break ;
case ' p ' :
path = optarg ;
break ;
2024-06-07 10:27:10 +00:00
case ' v ' :
printf ( " %s \n " , PACKAGE_VERSION ) ;
return EXIT_SUCCESS ;
case ' h ' :
print_usage ( progname , stdout ) ;
return EXIT_SUCCESS ;
case ' q ' :
quiet = true ;
break ;
case ' ? ' :
default :
print_usage ( progname , stderr ) ;
return EXIT_FAILURE ;
}
}
if ( ( argc - optind ) > 2 ) {
fprintf ( stderr , _ ( " %1$s: too many command line arguments \n " ) , argv [ 0 ] ) ;
print_usage ( progname , stderr ) ;
return EXIT_FAILURE ;
}
if ( argc > 1 )
scope = argv [ optind ] ;
virValidateSetQuiet ( quiet ) ;
2024-06-07 10:30:35 +00:00
if ( ( system & & user ) | |
( system & & path ) | |
( user & & path ) ) {
g_printerr ( " --system, --user & --path are mutually exclusive \n " ) ;
return EXIT_FAILURE ;
}
if ( ! system & & ! user & & ! path )
system = true ;
2024-06-07 10:27:10 +00:00
if ( ( ! scope | | g_str_equal ( scope , " trust " ) ) & &
2024-06-07 10:30:35 +00:00
! virPKIValidateTrust ( system , path ) )
2024-06-07 10:27:10 +00:00
ok = false ;
if ( ( ! scope | | g_str_equal ( scope , " server " ) ) & &
2024-06-07 10:30:35 +00:00
! virPKIValidateIdentity ( true , system , path ) )
2024-06-07 10:27:10 +00:00
ok = false ;
if ( ( ! scope | | g_str_equal ( scope , " client " ) ) & &
2024-06-07 10:30:35 +00:00
! virPKIValidateIdentity ( false , system , path ) )
2024-06-07 10:27:10 +00:00
ok = false ;
if ( ! ok )
return EXIT_FAILURE ;
return EXIT_SUCCESS ;
}