2013-08-05 19:49:24 +04:00
/*
2014-03-17 13:38:38 +04:00
* Copyright ( C ) 2011 - 2012 , 2014 Red Hat , Inc .
2013-08-05 19:49:24 +04:00
*
* This library is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation ; either
* version 2.1 of the License , or ( at your option ) any later version .
*
* This library is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library . If not , see
* < http : //www.gnu.org/licenses/>.
*
* Author : Daniel P . Berrange < berrange @ redhat . com >
*/
# include <config.h>
# include <stdlib.h>
# include <fcntl.h>
# include <sys/socket.h>
# include "testutils.h"
# include "virnettlshelpers.h"
# include "virutil.h"
# include "virerror.h"
# include "viralloc.h"
# include "virlog.h"
# include "virfile.h"
# include "vircommand.h"
# include "virsocketaddr.h"
# if !defined WIN32 && HAVE_LIBTASN1_H && LIBGNUTLS_VERSION_NUMBER >= 0x020600
# define VIR_FROM_THIS VIR_FROM_RPC
2014-02-28 16:16:17 +04:00
VIR_LOG_INIT ( " tests.nettlssessiontest " ) ;
2013-08-09 02:08:25 +04:00
# define KEYFILE "key-sess.pem"
2013-08-05 19:49:24 +04:00
struct testTLSSessionData {
2013-08-06 14:35:49 +04:00
const char * servercacrt ;
const char * clientcacrt ;
const char * servercrt ;
const char * clientcrt ;
2013-08-05 19:49:24 +04:00
bool expectServerFail ;
bool expectClientFail ;
const char * hostname ;
const char * const * wildcards ;
} ;
static ssize_t testWrite ( const char * buf , size_t len , void * opaque )
{
int * fd = opaque ;
return write ( * fd , buf , len ) ;
}
static ssize_t testRead ( char * buf , size_t len , void * opaque )
{
int * fd = opaque ;
return read ( * fd , buf , len ) ;
}
/*
* This tests validation checking of peer certificates
*
* This is replicating the checks that are done for an
* active TLS session after handshake completes . To
* simulate that we create our TLS contexts , skipping
* sanity checks . When then get a socketpair , and
* initiate a TLS session across them . Finally do
* do actual cert validation tests
*/
static int testTLSSessionInit ( const void * opaque )
{
struct testTLSSessionData * data = ( struct testTLSSessionData * ) opaque ;
virNetTLSContextPtr clientCtxt = NULL ;
virNetTLSContextPtr serverCtxt = NULL ;
virNetTLSSessionPtr clientSess = NULL ;
virNetTLSSessionPtr serverSess = NULL ;
int ret = - 1 ;
int channel [ 2 ] ;
bool clientShake = false ;
bool serverShake = false ;
/* We'll use this for our fake client-server connection */
if ( socketpair ( AF_UNIX , SOCK_STREAM , 0 , channel ) < 0 )
abort ( ) ;
/*
* We have an evil loop to do the handshake in a single
* thread , so we need these non - blocking to avoid deadlock
* of ourselves
*/
ignore_value ( virSetNonBlock ( channel [ 0 ] ) ) ;
ignore_value ( virSetNonBlock ( channel [ 1 ] ) ) ;
/* We skip initial sanity checks here because we
* want to make sure that problems are being
* detected at the TLS session validation stage
*/
2013-08-06 14:35:49 +04:00
serverCtxt = virNetTLSContextNewServer ( data - > servercacrt ,
2013-08-05 19:49:24 +04:00
NULL ,
2013-08-06 14:35:49 +04:00
data - > servercrt ,
2013-08-09 02:08:25 +04:00
KEYFILE ,
2013-08-05 19:49:24 +04:00
data - > wildcards ,
false ,
true ) ;
2013-08-06 14:35:49 +04:00
clientCtxt = virNetTLSContextNewClient ( data - > clientcacrt ,
2013-08-05 19:49:24 +04:00
NULL ,
2013-08-06 14:35:49 +04:00
data - > clientcrt ,
2013-08-09 02:08:25 +04:00
KEYFILE ,
2013-08-05 19:49:24 +04:00
false ,
true ) ;
if ( ! serverCtxt ) {
VIR_WARN ( " Unexpected failure loading %s against %s " ,
2013-08-06 14:35:49 +04:00
data - > servercacrt , data - > servercrt ) ;
2013-08-05 19:49:24 +04:00
goto cleanup ;
}
if ( ! clientCtxt ) {
VIR_WARN ( " Unexpected failure loading %s against %s " ,
2013-08-06 14:35:49 +04:00
data - > clientcacrt , data - > clientcrt ) ;
2013-08-05 19:49:24 +04:00
goto cleanup ;
}
/* Now the real part of the test, setup the sessions */
serverSess = virNetTLSSessionNew ( serverCtxt , NULL ) ;
clientSess = virNetTLSSessionNew ( clientCtxt , data - > hostname ) ;
if ( ! serverSess ) {
VIR_WARN ( " Unexpected failure using %s against %s " ,
2013-08-06 14:35:49 +04:00
data - > servercacrt , data - > servercrt ) ;
2013-08-05 19:49:24 +04:00
goto cleanup ;
}
if ( ! clientSess ) {
VIR_WARN ( " Unexpected failure using %s against %s " ,
2013-08-06 14:35:49 +04:00
data - > clientcacrt , data - > clientcrt ) ;
2013-08-05 19:49:24 +04:00
goto cleanup ;
}
/* For handshake to work, we need to set the I/O callbacks
* to read / write over the socketpair
*/
virNetTLSSessionSetIOCallbacks ( serverSess , testWrite , testRead , & channel [ 0 ] ) ;
virNetTLSSessionSetIOCallbacks ( clientSess , testWrite , testRead , & channel [ 1 ] ) ;
/*
* Finally we loop around & around doing handshake on each
* session until we get an error , or the handshake completes .
* This relies on the socketpair being nonblocking to avoid
* deadlocking ourselves upon handshake
*/
do {
int rv ;
if ( ! serverShake ) {
rv = virNetTLSSessionHandshake ( serverSess ) ;
if ( rv < 0 )
goto cleanup ;
if ( rv = = VIR_NET_TLS_HANDSHAKE_COMPLETE )
serverShake = true ;
}
if ( ! clientShake ) {
rv = virNetTLSSessionHandshake ( clientSess ) ;
if ( rv < 0 )
goto cleanup ;
if ( rv = = VIR_NET_TLS_HANDSHAKE_COMPLETE )
clientShake = true ;
}
} while ( ! clientShake & & ! serverShake ) ;
/* Finally make sure the server validation does what
* we were expecting
*/
if ( virNetTLSContextCheckCertificate ( serverCtxt ,
serverSess ) < 0 ) {
if ( ! data - > expectServerFail ) {
VIR_WARN ( " Unexpected server cert check fail " ) ;
goto cleanup ;
} else {
VIR_DEBUG ( " Got expected server cert fail " ) ;
}
} else {
if ( data - > expectServerFail ) {
VIR_WARN ( " Expected server cert check fail " ) ;
goto cleanup ;
} else {
2013-08-06 15:31:20 +04:00
VIR_DEBUG ( " No unexpected server cert fail " ) ;
2013-08-05 19:49:24 +04:00
}
}
/*
* And the same for the client validation check
*/
if ( virNetTLSContextCheckCertificate ( clientCtxt ,
clientSess ) < 0 ) {
if ( ! data - > expectClientFail ) {
VIR_WARN ( " Unexpected client cert check fail " ) ;
goto cleanup ;
} else {
VIR_DEBUG ( " Got expected client cert fail " ) ;
}
} else {
if ( data - > expectClientFail ) {
VIR_WARN ( " Expected client cert check fail " ) ;
goto cleanup ;
} else {
2013-08-06 15:31:20 +04:00
VIR_DEBUG ( " No unexpected client cert fail " ) ;
2013-08-05 19:49:24 +04:00
}
}
ret = 0 ;
2014-03-25 10:53:44 +04:00
cleanup :
2013-08-05 19:49:24 +04:00
virObjectUnref ( serverCtxt ) ;
virObjectUnref ( clientCtxt ) ;
virObjectUnref ( serverSess ) ;
virObjectUnref ( clientSess ) ;
VIR_FORCE_CLOSE ( channel [ 0 ] ) ;
VIR_FORCE_CLOSE ( channel [ 1 ] ) ;
return ret ;
}
static int
mymain ( void )
{
int ret = 0 ;
2014-09-04 13:23:16 +04:00
setenv ( " GNUTLS_FORCE_FIPS_MODE " , " 2 " , 1 ) ;
2013-08-09 02:08:25 +04:00
testTLSInit ( KEYFILE ) ;
2013-08-05 19:49:24 +04:00
2013-08-06 14:35:49 +04:00
# define DO_SESS_TEST(_caCrt, _serverCrt, _clientCrt, _expectServerFail, \
2013-08-05 19:49:24 +04:00
_expectClientFail , _hostname , _wildcards ) \
do { \
static struct testTLSSessionData data ; \
2013-08-06 14:35:49 +04:00
data . servercacrt = _caCrt ; \
data . clientcacrt = _caCrt ; \
data . servercrt = _serverCrt ; \
data . clientcrt = _clientCrt ; \
2013-08-05 19:49:24 +04:00
data . expectServerFail = _expectServerFail ; \
data . expectClientFail = _expectClientFail ; \
data . hostname = _hostname ; \
data . wildcards = _wildcards ; \
2013-08-06 14:35:49 +04:00
if ( virtTestRun ( " TLS Session " # _serverCrt " + " # _clientCrt , \
2013-09-20 22:13:35 +04:00
testTLSSessionInit , & data ) < 0 ) \
2013-08-05 19:49:24 +04:00
ret = - 1 ; \
} while ( 0 )
2013-08-06 14:35:49 +04:00
# define DO_SESS_TEST_EXT(_serverCaCrt, _clientCaCrt, _serverCrt, _clientCrt, \
2013-08-05 19:49:24 +04:00
_expectServerFail , _expectClientFail , \
_hostname , _wildcards ) \
do { \
static struct testTLSSessionData data ; \
2013-08-06 14:35:49 +04:00
data . servercacrt = _serverCaCrt ; \
data . clientcacrt = _clientCaCrt ; \
data . servercrt = _serverCrt ; \
data . clientcrt = _clientCrt ; \
2013-08-05 19:49:24 +04:00
data . expectServerFail = _expectServerFail ; \
data . expectClientFail = _expectClientFail ; \
data . hostname = _hostname ; \
data . wildcards = _wildcards ; \
2013-08-06 14:35:49 +04:00
if ( virtTestRun ( " TLS Session " # _serverCrt " + " # _clientCrt , \
2013-09-20 22:13:35 +04:00
testTLSSessionInit , & data ) < 0 ) \
2013-08-05 19:49:24 +04:00
ret = - 1 ; \
} while ( 0 )
2013-08-05 20:08:17 +04:00
# define TLS_CERT_REQ(varname, cavarname, \
co , cn , an1 , an2 , ia1 , ia2 , bce , bcc , bci , \
kue , kuc , kuv , kpe , kpc , kpo1 , kpo2 , so , eo ) \
static struct testTLSCertReq varname = { \
2013-08-06 14:35:49 +04:00
NULL , # varname " -sess.pem " , \
2013-08-05 20:08:17 +04:00
co , cn , an1 , an2 , ia1 , ia2 , bce , bcc , bci , \
kue , kuc , kuv , kpe , kpc , kpo1 , kpo2 , so , so \
} ; \
testTLSGenerateCert ( & varname , cavarname . crt )
# define TLS_ROOT_REQ(varname, \
co , cn , an1 , an2 , ia1 , ia2 , bce , bcc , bci , \
kue , kuc , kuv , kpe , kpc , kpo1 , kpo2 , so , eo ) \
static struct testTLSCertReq varname = { \
2013-08-06 14:35:49 +04:00
NULL , # varname " -sess.pem " , \
2013-08-05 20:08:17 +04:00
co , cn , an1 , an2 , ia1 , ia2 , bce , bcc , bci , \
kue , kuc , kuv , kpe , kpc , kpo1 , kpo2 , so , so \
} ; \
testTLSGenerateCert ( & varname , NULL )
2013-08-05 19:49:24 +04:00
/* A perfect CA, perfect client & perfect server */
/* Basic:CA:critical */
2013-08-05 20:08:17 +04:00
TLS_ROOT_REQ ( cacertreq ,
" UK " , " libvirt CA " , NULL , NULL , NULL , NULL ,
true , true , true ,
true , true , GNUTLS_KEY_KEY_CERT_SIGN ,
false , false , NULL , NULL ,
0 , 0 ) ;
TLS_ROOT_REQ ( altcacertreq ,
" UK " , " libvirt CA 1 " , NULL , NULL , NULL , NULL ,
true , true , true ,
false , false , 0 ,
false , false , NULL , NULL ,
0 , 0 ) ;
TLS_CERT_REQ ( servercertreq , cacertreq ,
" UK " , " libvirt.org " , NULL , NULL , NULL , NULL ,
true , true , false ,
true , true , GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT ,
true , true , GNUTLS_KP_TLS_WWW_SERVER , NULL ,
0 , 0 ) ;
TLS_CERT_REQ ( clientcertreq , cacertreq ,
" UK " , " libvirt " , NULL , NULL , NULL , NULL ,
true , true , false ,
true , true , GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT ,
true , true , GNUTLS_KP_TLS_WWW_CLIENT , NULL ,
0 , 0 ) ;
TLS_CERT_REQ ( clientcertaltreq , altcacertreq ,
" UK " , " libvirt " , NULL , NULL , NULL , NULL ,
true , true , false ,
true , true , GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT ,
true , true , GNUTLS_KP_TLS_WWW_CLIENT , NULL ,
0 , 0 ) ;
2013-08-05 19:49:24 +04:00
2013-08-06 14:35:49 +04:00
DO_SESS_TEST ( cacertreq . filename , servercertreq . filename , clientcertreq . filename ,
false , false , " libvirt.org " , NULL ) ;
DO_SESS_TEST_EXT ( cacertreq . filename , altcacertreq . filename , servercertreq . filename ,
clientcertaltreq . filename , true , true , " libvirt.org " , NULL ) ;
2013-08-05 20:08:17 +04:00
2013-08-05 19:49:24 +04:00
/* When an altname is set, the CN is ignored, so it must be duplicated
* as an altname for it to match */
2013-08-05 20:08:17 +04:00
TLS_CERT_REQ ( servercertalt1req , cacertreq ,
" UK " , " libvirt.org " , " www.libvirt.org " , " libvirt.org " , " 192.168.122.1 " , " fec0::dead:beaf " ,
true , true , false ,
true , true , GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT ,
true , true , GNUTLS_KP_TLS_WWW_SERVER , NULL ,
0 , 0 ) ;
2013-08-05 19:49:24 +04:00
/* This intentionally doesn't replicate */
2013-08-05 20:08:17 +04:00
TLS_CERT_REQ ( servercertalt2req , cacertreq ,
" UK " , " libvirt.org " , " www.libvirt.org " , " wiki.libvirt.org " , " 192.168.122.1 " , " fec0::dead:beaf " ,
true , true , false ,
true , true , GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT ,
true , true , GNUTLS_KP_TLS_WWW_SERVER , NULL ,
0 , 0 ) ;
2013-08-05 19:49:24 +04:00
2013-08-06 14:35:49 +04:00
DO_SESS_TEST ( cacertreq . filename , servercertalt1req . filename , clientcertreq . filename ,
false , false , " libvirt.org " , NULL ) ;
DO_SESS_TEST ( cacertreq . filename , servercertalt1req . filename , clientcertreq . filename ,
false , false , " www.libvirt.org " , NULL ) ;
DO_SESS_TEST ( cacertreq . filename , servercertalt1req . filename , clientcertreq . filename ,
false , true , " wiki.libvirt.org " , NULL ) ;
2013-08-05 19:49:24 +04:00
2013-08-06 14:35:49 +04:00
DO_SESS_TEST ( cacertreq . filename , servercertalt2req . filename , clientcertreq . filename ,
false , true , " libvirt.org " , NULL ) ;
DO_SESS_TEST ( cacertreq . filename , servercertalt2req . filename , clientcertreq . filename ,
false , false , " www.libvirt.org " , NULL ) ;
DO_SESS_TEST ( cacertreq . filename , servercertalt2req . filename , clientcertreq . filename ,
false , false , " wiki.libvirt.org " , NULL ) ;
2013-08-05 19:49:24 +04:00
const char * const wildcards1 [ ] = {
" C=UK,CN=dogfood " ,
NULL ,
} ;
const char * const wildcards2 [ ] = {
" C=UK,CN=libvirt " ,
NULL ,
} ;
const char * const wildcards3 [ ] = {
" C=UK,CN=dogfood " ,
" C=UK,CN=libvirt " ,
NULL ,
} ;
const char * const wildcards4 [ ] = {
" C=UK,CN=libvirtstuff " ,
NULL ,
} ;
const char * const wildcards5 [ ] = {
" C=UK,CN=libvirt* " ,
NULL ,
} ;
const char * const wildcards6 [ ] = {
" C=UK,CN=*virt* " ,
NULL ,
} ;
2013-08-06 14:35:49 +04:00
DO_SESS_TEST ( cacertreq . filename , servercertreq . filename , clientcertreq . filename ,
true , false , " libvirt.org " , wildcards1 ) ;
DO_SESS_TEST ( cacertreq . filename , servercertreq . filename , clientcertreq . filename ,
false , false , " libvirt.org " , wildcards2 ) ;
DO_SESS_TEST ( cacertreq . filename , servercertreq . filename , clientcertreq . filename ,
false , false , " libvirt.org " , wildcards3 ) ;
DO_SESS_TEST ( cacertreq . filename , servercertreq . filename , clientcertreq . filename ,
true , false , " libvirt.org " , wildcards4 ) ;
DO_SESS_TEST ( cacertreq . filename , servercertreq . filename , clientcertreq . filename ,
false , false , " libvirt.org " , wildcards5 ) ;
DO_SESS_TEST ( cacertreq . filename , servercertreq . filename , clientcertreq . filename ,
false , false , " libvirt.org " , wildcards6 ) ;
2013-08-05 19:49:24 +04:00
2013-08-06 15:31:20 +04:00
TLS_ROOT_REQ ( cacertrootreq ,
" UK " , " libvirt root " , NULL , NULL , NULL , NULL ,
true , true , true ,
true , true , GNUTLS_KEY_KEY_CERT_SIGN ,
false , false , NULL , NULL ,
0 , 0 ) ;
TLS_CERT_REQ ( cacertlevel1areq , cacertrootreq ,
" UK " , " libvirt level 1a " , NULL , NULL , NULL , NULL ,
true , true , true ,
true , true , GNUTLS_KEY_KEY_CERT_SIGN ,
false , false , NULL , NULL ,
0 , 0 ) ;
TLS_CERT_REQ ( cacertlevel1breq , cacertrootreq ,
" UK " , " libvirt level 1b " , NULL , NULL , NULL , NULL ,
true , true , true ,
true , true , GNUTLS_KEY_KEY_CERT_SIGN ,
false , false , NULL , NULL ,
0 , 0 ) ;
TLS_CERT_REQ ( cacertlevel2areq , cacertlevel1areq ,
" UK " , " libvirt level 2a " , NULL , NULL , NULL , NULL ,
true , true , true ,
true , true , GNUTLS_KEY_KEY_CERT_SIGN ,
false , false , NULL , NULL ,
0 , 0 ) ;
TLS_CERT_REQ ( servercertlevel3areq , cacertlevel2areq ,
" UK " , " libvirt.org " , NULL , NULL , NULL , NULL ,
true , true , false ,
true , true , GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT ,
true , true , GNUTLS_KP_TLS_WWW_SERVER , NULL ,
0 , 0 ) ;
TLS_CERT_REQ ( clientcertlevel2breq , cacertlevel1breq ,
" UK " , " libvirt client level 2b " , NULL , NULL , NULL , NULL ,
true , true , false ,
true , true , GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT ,
true , true , GNUTLS_KP_TLS_WWW_CLIENT , NULL ,
0 , 0 ) ;
gnutls_x509_crt_t certchain [ ] = {
cacertrootreq . crt ,
cacertlevel1areq . crt ,
cacertlevel1breq . crt ,
cacertlevel2areq . crt ,
} ;
2013-08-09 11:53:30 +04:00
testTLSWriteCertChain ( " cacertchain-sess.pem " ,
2013-08-06 15:31:20 +04:00
certchain ,
ARRAY_CARDINALITY ( certchain ) ) ;
2013-08-09 11:53:30 +04:00
DO_SESS_TEST ( " cacertchain-sess.pem " , servercertlevel3areq . filename , clientcertlevel2breq . filename ,
2013-08-06 15:31:20 +04:00
false , false , " libvirt.org " , NULL ) ;
2013-08-05 20:08:17 +04:00
testTLSDiscardCert ( & clientcertreq ) ;
testTLSDiscardCert ( & clientcertaltreq ) ;
testTLSDiscardCert ( & servercertreq ) ;
testTLSDiscardCert ( & servercertalt1req ) ;
testTLSDiscardCert ( & servercertalt2req ) ;
testTLSDiscardCert ( & cacertreq ) ;
testTLSDiscardCert ( & altcacertreq ) ;
2013-08-06 15:31:20 +04:00
testTLSDiscardCert ( & cacertrootreq ) ;
testTLSDiscardCert ( & cacertlevel1areq ) ;
testTLSDiscardCert ( & cacertlevel1breq ) ;
testTLSDiscardCert ( & cacertlevel2areq ) ;
testTLSDiscardCert ( & servercertlevel3areq ) ;
testTLSDiscardCert ( & clientcertlevel2breq ) ;
2013-08-09 11:53:30 +04:00
unlink ( " cacertchain-sess.pem " ) ;
2013-08-06 15:31:20 +04:00
2013-08-09 02:08:25 +04:00
testTLSCleanup ( KEYFILE ) ;
2013-08-05 19:49:24 +04:00
2014-03-17 13:38:38 +04:00
return ret = = 0 ? EXIT_SUCCESS : EXIT_FAILURE ;
2013-08-05 19:49:24 +04:00
}
VIRT_TEST_MAIN ( mymain )
# else
int
main ( void )
{
return EXIT_AM_SKIP ;
}
# endif