crypto: introduce new module for handling TLS sessions

Introduce a QCryptoTLSSession object that will encapsulate
all the code for setting up and using a client/sever TLS
session. This isolates the code which depends on the gnutls
library, avoiding #ifdefs in the rest of the codebase, as
well as facilitating any possible future port to other TLS
libraries, if desired. It makes use of the previously
defined QCryptoTLSCreds object to access credentials to
use with the session. It also includes further unit tests
to validate the correctness of the TLS session handshake
and certificate validation. This is functionally equivalent
to the current TLS session handling code embedded in the
VNC server, and will obsolete it.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
This commit is contained in:
Daniel P. Berrange 2015-03-02 17:23:31 +00:00
parent 9a2fd4347c
commit d321e1e526
7 changed files with 1442 additions and 0 deletions

View File

@ -6,6 +6,7 @@ crypto-obj-y += cipher.o
crypto-obj-y += tlscreds.o
crypto-obj-y += tlscredsanon.o
crypto-obj-y += tlscredsx509.o
crypto-obj-y += tlssession.o
# Let the userspace emulators avoid linking gnutls/etc
crypto-aes-obj-y = aes.o

574
crypto/tlssession.c Normal file
View File

@ -0,0 +1,574 @@
/*
* QEMU crypto TLS session support
*
* Copyright (c) 2015 Red Hat, Inc.
*
* 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 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/>.
*
*/
#include "crypto/tlssession.h"
#include "crypto/tlscredsanon.h"
#include "crypto/tlscredsx509.h"
#include "qemu/acl.h"
#include "trace.h"
#ifdef CONFIG_GNUTLS
#include <gnutls/x509.h>
struct QCryptoTLSSession {
QCryptoTLSCreds *creds;
gnutls_session_t handle;
char *hostname;
char *aclname;
bool handshakeComplete;
QCryptoTLSSessionWriteFunc writeFunc;
QCryptoTLSSessionReadFunc readFunc;
void *opaque;
char *peername;
};
void
qcrypto_tls_session_free(QCryptoTLSSession *session)
{
if (!session) {
return;
}
gnutls_deinit(session->handle);
g_free(session->hostname);
g_free(session->peername);
g_free(session->aclname);
object_unref(OBJECT(session->creds));
g_free(session);
}
static ssize_t
qcrypto_tls_session_push(void *opaque, const void *buf, size_t len)
{
QCryptoTLSSession *session = opaque;
if (!session->writeFunc) {
errno = EIO;
return -1;
};
return session->writeFunc(buf, len, session->opaque);
}
static ssize_t
qcrypto_tls_session_pull(void *opaque, void *buf, size_t len)
{
QCryptoTLSSession *session = opaque;
if (!session->readFunc) {
errno = EIO;
return -1;
};
return session->readFunc(buf, len, session->opaque);
}
QCryptoTLSSession *
qcrypto_tls_session_new(QCryptoTLSCreds *creds,
const char *hostname,
const char *aclname,
QCryptoTLSCredsEndpoint endpoint,
Error **errp)
{
QCryptoTLSSession *session;
int ret;
session = g_new0(QCryptoTLSSession, 1);
trace_qcrypto_tls_session_new(
session, creds, hostname ? hostname : "<none>",
aclname ? aclname : "<none>", endpoint);
if (hostname) {
session->hostname = g_strdup(hostname);
}
if (aclname) {
session->aclname = g_strdup(aclname);
}
session->creds = creds;
object_ref(OBJECT(creds));
if (creds->endpoint != endpoint) {
error_setg(errp, "Credentials endpoint doesn't match session");
goto error;
}
if (endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
ret = gnutls_init(&session->handle, GNUTLS_SERVER);
} else {
ret = gnutls_init(&session->handle, GNUTLS_CLIENT);
}
if (ret < 0) {
error_setg(errp, "Cannot initialize TLS session: %s",
gnutls_strerror(ret));
goto error;
}
if (object_dynamic_cast(OBJECT(creds),
TYPE_QCRYPTO_TLS_CREDS_ANON)) {
QCryptoTLSCredsAnon *acreds = QCRYPTO_TLS_CREDS_ANON(creds);
ret = gnutls_priority_set_direct(session->handle,
"NORMAL:+ANON-DH", NULL);
if (ret < 0) {
error_setg(errp, "Unable to set TLS session priority: %s",
gnutls_strerror(ret));
goto error;
}
if (creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
ret = gnutls_credentials_set(session->handle,
GNUTLS_CRD_ANON,
acreds->data.server);
} else {
ret = gnutls_credentials_set(session->handle,
GNUTLS_CRD_ANON,
acreds->data.client);
}
if (ret < 0) {
error_setg(errp, "Cannot set session credentials: %s",
gnutls_strerror(ret));
goto error;
}
} else if (object_dynamic_cast(OBJECT(creds),
TYPE_QCRYPTO_TLS_CREDS_X509)) {
QCryptoTLSCredsX509 *tcreds = QCRYPTO_TLS_CREDS_X509(creds);
ret = gnutls_set_default_priority(session->handle);
if (ret < 0) {
error_setg(errp, "Cannot set default TLS session priority: %s",
gnutls_strerror(ret));
goto error;
}
ret = gnutls_credentials_set(session->handle,
GNUTLS_CRD_CERTIFICATE,
tcreds->data);
if (ret < 0) {
error_setg(errp, "Cannot set session credentials: %s",
gnutls_strerror(ret));
goto error;
}
if (creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
/* This requests, but does not enforce a client cert.
* The cert checking code later does enforcement */
gnutls_certificate_server_set_request(session->handle,
GNUTLS_CERT_REQUEST);
}
} else {
error_setg(errp, "Unsupported TLS credentials type %s",
object_get_typename(OBJECT(creds)));
goto error;
}
gnutls_transport_set_ptr(session->handle, session);
gnutls_transport_set_push_function(session->handle,
qcrypto_tls_session_push);
gnutls_transport_set_pull_function(session->handle,
qcrypto_tls_session_pull);
return session;
error:
qcrypto_tls_session_free(session);
return NULL;
}
static int
qcrypto_tls_session_check_certificate(QCryptoTLSSession *session,
Error **errp)
{
int ret;
unsigned int status;
const gnutls_datum_t *certs;
unsigned int nCerts, i;
time_t now;
gnutls_x509_crt_t cert = NULL;
now = time(NULL);
if (now == ((time_t)-1)) {
error_setg_errno(errp, errno, "Cannot get current time");
return -1;
}
ret = gnutls_certificate_verify_peers2(session->handle, &status);
if (ret < 0) {
error_setg(errp, "Verify failed: %s", gnutls_strerror(ret));
return -1;
}
if (status != 0) {
const char *reason = "Invalid certificate";
if (status & GNUTLS_CERT_INVALID) {
reason = "The certificate is not trusted";
}
if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) {
reason = "The certificate hasn't got a known issuer";
}
if (status & GNUTLS_CERT_REVOKED) {
reason = "The certificate has been revoked";
}
if (status & GNUTLS_CERT_INSECURE_ALGORITHM) {
reason = "The certificate uses an insecure algorithm";
}
error_setg(errp, "%s", reason);
return -1;
}
certs = gnutls_certificate_get_peers(session->handle, &nCerts);
if (!certs) {
error_setg(errp, "No certificate peers");
return -1;
}
for (i = 0; i < nCerts; i++) {
ret = gnutls_x509_crt_init(&cert);
if (ret < 0) {
error_setg(errp, "Cannot initialize certificate: %s",
gnutls_strerror(ret));
return -1;
}
ret = gnutls_x509_crt_import(cert, &certs[i], GNUTLS_X509_FMT_DER);
if (ret < 0) {
error_setg(errp, "Cannot import certificate: %s",
gnutls_strerror(ret));
goto error;
}
if (gnutls_x509_crt_get_expiration_time(cert) < now) {
error_setg(errp, "The certificate has expired");
goto error;
}
if (gnutls_x509_crt_get_activation_time(cert) > now) {
error_setg(errp, "The certificate is not yet activated");
goto error;
}
if (gnutls_x509_crt_get_activation_time(cert) > now) {
error_setg(errp, "The certificate is not yet activated");
goto error;
}
if (i == 0) {
size_t dnameSize = 1024;
session->peername = g_malloc(dnameSize);
requery:
ret = gnutls_x509_crt_get_dn(cert, session->peername, &dnameSize);
if (ret < 0) {
if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER) {
session->peername = g_realloc(session->peername,
dnameSize);
goto requery;
}
error_setg(errp, "Cannot get client distinguished name: %s",
gnutls_strerror(ret));
goto error;
}
if (session->aclname) {
qemu_acl *acl = qemu_acl_find(session->aclname);
int allow;
if (!acl) {
error_setg(errp, "Cannot find ACL %s",
session->aclname);
goto error;
}
allow = qemu_acl_party_is_allowed(acl, session->peername);
error_setg(errp, "TLS x509 ACL check for %s is %s",
session->peername, allow ? "allowed" : "denied");
if (!allow) {
goto error;
}
}
if (session->hostname) {
if (!gnutls_x509_crt_check_hostname(cert, session->hostname)) {
error_setg(errp,
"Certificate does not match the hostname %s",
session->hostname);
goto error;
}
}
}
gnutls_x509_crt_deinit(cert);
}
return 0;
error:
gnutls_x509_crt_deinit(cert);
return -1;
}
int
qcrypto_tls_session_check_credentials(QCryptoTLSSession *session,
Error **errp)
{
if (object_dynamic_cast(OBJECT(session->creds),
TYPE_QCRYPTO_TLS_CREDS_ANON)) {
return 0;
} else if (object_dynamic_cast(OBJECT(session->creds),
TYPE_QCRYPTO_TLS_CREDS_X509)) {
if (session->creds->verifyPeer) {
return qcrypto_tls_session_check_certificate(session,
errp);
} else {
return 0;
}
} else {
error_setg(errp, "Unexpected credential type %s",
object_get_typename(OBJECT(session->creds)));
return -1;
}
}
void
qcrypto_tls_session_set_callbacks(QCryptoTLSSession *session,
QCryptoTLSSessionWriteFunc writeFunc,
QCryptoTLSSessionReadFunc readFunc,
void *opaque)
{
session->writeFunc = writeFunc;
session->readFunc = readFunc;
session->opaque = opaque;
}
ssize_t
qcrypto_tls_session_write(QCryptoTLSSession *session,
const char *buf,
size_t len)
{
ssize_t ret = gnutls_record_send(session->handle, buf, len);
if (ret < 0) {
switch (ret) {
case GNUTLS_E_AGAIN:
errno = EAGAIN;
break;
case GNUTLS_E_INTERRUPTED:
errno = EINTR;
break;
default:
errno = EIO;
break;
}
ret = -1;
}
return ret;
}
ssize_t
qcrypto_tls_session_read(QCryptoTLSSession *session,
char *buf,
size_t len)
{
ssize_t ret = gnutls_record_recv(session->handle, buf, len);
if (ret < 0) {
switch (ret) {
case GNUTLS_E_AGAIN:
errno = EAGAIN;
break;
case GNUTLS_E_INTERRUPTED:
errno = EINTR;
break;
default:
errno = EIO;
break;
}
ret = -1;
}
return ret;
}
int
qcrypto_tls_session_handshake(QCryptoTLSSession *session,
Error **errp)
{
int ret = gnutls_handshake(session->handle);
if (ret == 0) {
session->handshakeComplete = true;
} else {
if (ret == GNUTLS_E_INTERRUPTED ||
ret == GNUTLS_E_AGAIN) {
ret = 1;
} else {
error_setg(errp, "TLS handshake failed: %s",
gnutls_strerror(ret));
ret = -1;
}
}
return ret;
}
QCryptoTLSSessionHandshakeStatus
qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *session)
{
if (session->handshakeComplete) {
return QCRYPTO_TLS_HANDSHAKE_COMPLETE;
} else if (gnutls_record_get_direction(session->handle) == 0) {
return QCRYPTO_TLS_HANDSHAKE_RECVING;
} else {
return QCRYPTO_TLS_HANDSHAKE_SENDING;
}
}
int
qcrypto_tls_session_get_key_size(QCryptoTLSSession *session,
Error **errp)
{
gnutls_cipher_algorithm_t cipher;
int ssf;
cipher = gnutls_cipher_get(session->handle);
ssf = gnutls_cipher_get_key_size(cipher);
if (!ssf) {
error_setg(errp, "Cannot get TLS cipher key size");
return -1;
}
return ssf;
}
char *
qcrypto_tls_session_get_peer_name(QCryptoTLSSession *session)
{
if (session->peername) {
return g_strdup(session->peername);
}
return NULL;
}
#else /* ! CONFIG_GNUTLS */
QCryptoTLSSession *
qcrypto_tls_session_new(QCryptoTLSCreds *creds G_GNUC_UNUSED,
const char *hostname G_GNUC_UNUSED,
const char *aclname G_GNUC_UNUSED,
QCryptoTLSCredsEndpoint endpoint G_GNUC_UNUSED,
Error **errp)
{
error_setg(errp, "TLS requires GNUTLS support");
return NULL;
}
void
qcrypto_tls_session_free(QCryptoTLSSession *sess G_GNUC_UNUSED)
{
}
int
qcrypto_tls_session_check_credentials(QCryptoTLSSession *sess G_GNUC_UNUSED,
Error **errp)
{
error_setg(errp, "TLS requires GNUTLS support");
return -1;
}
void
qcrypto_tls_session_set_callbacks(
QCryptoTLSSession *sess G_GNUC_UNUSED,
QCryptoTLSSessionWriteFunc writeFunc G_GNUC_UNUSED,
QCryptoTLSSessionReadFunc readFunc G_GNUC_UNUSED,
void *opaque G_GNUC_UNUSED)
{
}
ssize_t
qcrypto_tls_session_write(QCryptoTLSSession *sess,
const char *buf,
size_t len)
{
errno = -EIO;
return -1;
}
ssize_t
qcrypto_tls_session_read(QCryptoTLSSession *sess,
char *buf,
size_t len)
{
errno = -EIO;
return -1;
}
int
qcrypto_tls_session_handshake(QCryptoTLSSession *sess,
Error **errp)
{
error_setg(errp, "TLS requires GNUTLS support");
return -1;
}
QCryptoTLSSessionHandshakeStatus
qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *sess)
{
return QCRYPTO_TLS_HANDSHAKE_COMPLETE;
}
int
qcrypto_tls_session_get_key_size(QCryptoTLSSession *sess,
Error **errp)
{
error_setg(errp, "TLS requires GNUTLS support");
return -1;
}
char *
qcrypto_tls_session_get_peer_name(QCryptoTLSSession *sess)
{
return NULL;
}
#endif

322
include/crypto/tlssession.h Normal file
View File

@ -0,0 +1,322 @@
/*
* QEMU crypto TLS session support
*
* Copyright (c) 2015 Red Hat, Inc.
*
* 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 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/>.
*
*/
#ifndef QCRYPTO_TLS_SESSION_H__
#define QCRYPTO_TLS_SESSION_H__
#include "crypto/tlscreds.h"
/**
* QCryptoTLSSession:
*
* The QCryptoTLSSession object encapsulates the
* logic to integrate with a TLS providing library such
* as GNUTLS, to setup and run TLS sessions.
*
* The API is designed such that it has no assumption about
* the type of transport it is running over. It may be a
* traditional TCP socket, or something else entirely. The
* only requirement is a full-duplex stream of some kind.
*
* <example>
* <title>Using TLS session objects</title>
* <programlisting>
* static ssize_t mysock_send(const char *buf, size_t len,
* void *opaque)
* {
* int fd = GPOINTER_TO_INT(opaque);
*
* return write(*fd, buf, len);
* }
*
* static ssize_t mysock_recv(const char *buf, size_t len,
* void *opaque)
* {
* int fd = GPOINTER_TO_INT(opaque);
*
* return read(*fd, buf, len);
* }
*
* static int mysock_run_tls(int sockfd,
* QCryptoTLSCreds *creds,
* Error *erp)
* {
* QCryptoTLSSession *sess;
*
* sess = qcrypto_tls_session_new(creds,
* "vnc.example.com",
* NULL,
* QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT,
* errp);
* if (sess == NULL) {
* return -1;
* }
*
* qcrypto_tls_session_set_callbacks(sess,
* mysock_send,
* mysock_recv
* GINT_TO_POINTER(fd));
*
* while (1) {
* if (qcrypto_tls_session_handshake(sess, errp) < 0) {
* qcrypto_tls_session_free(sess);
* return -1;
* }
*
* switch(qcrypto_tls_session_get_handshake_status(sess)) {
* case QCRYPTO_TLS_HANDSHAKE_COMPLETE:
* if (qcrypto_tls_session_check_credentials(sess, errp) < )) {
* qcrypto_tls_session_free(sess);
* return -1;
* }
* goto done;
* case QCRYPTO_TLS_HANDSHAKE_RECVING:
* ...wait for GIO_IN event on fd...
* break;
* case QCRYPTO_TLS_HANDSHAKE_SENDING:
* ...wait for GIO_OUT event on fd...
* break;
* }
* }
* done:
*
* ....send/recv payload data on sess...
*
* qcrypto_tls_session_free(sess):
* }
* </programlisting>
* </example>
*/
typedef struct QCryptoTLSSession QCryptoTLSSession;
/**
* qcrypto_tls_session_new:
* @creds: pointer to a TLS credentials object
* @hostname: optional hostname to validate
* @aclname: optional ACL to validate peer credentials against
* @endpoint: role of the TLS session, client or server
* @errp: pointer to an uninitialized error object
*
* Create a new TLS session object that will be used to
* negotiate a TLS session over an arbitrary data channel.
* The session object can operate as either the server or
* client, according to the value of the @endpoint argument.
*
* For clients, the @hostname parameter should hold the full
* unmodified hostname as requested by the user. This will
* be used to verify the against the hostname reported in
* the server's credentials (aka x509 certificate).
*
* The @aclname parameter (optionally) specifies the name
* of an access control list that will be used to validate
* the peer's credentials. For x509 credentials, the ACL
* will be matched against the CommonName shown in the peer's
* certificate. If the session is acting as a server, setting
* an ACL will require that the client provide a validate
* x509 client certificate.
*
* After creating the session object, the I/O callbacks
* must be set using the qcrypto_tls_session_set_callbacks()
* method. A TLS handshake sequence must then be completed
* using qcrypto_tls_session_handshake(), before payload
* data is permitted to be sent/received.
*
* The session object must be released by calling
* qcrypto_tls_session_free() when no longer required
*
* Returns: a TLS session object, or NULL on error.
*/
QCryptoTLSSession *qcrypto_tls_session_new(QCryptoTLSCreds *creds,
const char *hostname,
const char *aclname,
QCryptoTLSCredsEndpoint endpoint,
Error **errp);
/**
* qcrypto_tls_session_free:
* @sess: the TLS session object
*
* Release all memory associated with the TLS session
* object previously allocated by qcrypto_tls_session_new()
*/
void qcrypto_tls_session_free(QCryptoTLSSession *sess);
/**
* qcrypto_tls_session_check_credentials:
* @sess: the TLS session object
* @errp: pointer to an uninitialized error object
*
* Validate the peer's credentials after a successful
* TLS handshake. It is an error to call this before
* qcrypto_tls_session_get_handshake_status() returns
* QCRYPTO_TLS_HANDSHAKE_COMPLETE
*
* Returns 0 if the credentials validated, -1 on error
*/
int qcrypto_tls_session_check_credentials(QCryptoTLSSession *sess,
Error **errp);
typedef ssize_t (*QCryptoTLSSessionWriteFunc)(const char *buf,
size_t len,
void *opaque);
typedef ssize_t (*QCryptoTLSSessionReadFunc)(char *buf,
size_t len,
void *opaque);
/**
* qcrypto_tls_session_set_callbacks:
* @sess: the TLS session object
* @writeFunc: callback for sending data
* @readFunc: callback to receiving data
* @opaque: data to pass to callbacks
*
* Sets the callback functions that are to be used for sending
* and receiving data on the underlying data channel. Typically
* the callbacks to write/read to/from a TCP socket, but there
* is no assumption made about the type of channel used.
*
* The @writeFunc callback will be passed the encrypted
* data to send to the remote peer.
*
* The @readFunc callback will be passed a pointer to fill
* with encrypted data received from the remote peer
*/
void qcrypto_tls_session_set_callbacks(QCryptoTLSSession *sess,
QCryptoTLSSessionWriteFunc writeFunc,
QCryptoTLSSessionReadFunc readFunc,
void *opaque);
/**
* qcrypto_tls_session_write:
* @sess: the TLS session object
* @buf: the plain text to send
* @len: the length of @buf
*
* Encrypt @len bytes of the data in @buf and send
* it to the remote peer using the callback previously
* registered with qcrypto_tls_session_set_callbacks()
*
* It is an error to call this before
* qcrypto_tls_session_get_handshake_status() returns
* QCRYPTO_TLS_HANDSHAKE_COMPLETE
*
* Returns: the number of bytes sent, or -1 on error
*/
ssize_t qcrypto_tls_session_write(QCryptoTLSSession *sess,
const char *buf,
size_t len);
/**
* qcrypto_tls_session_read:
* @sess: the TLS session object
* @buf: to fill with plain text received
* @len: the length of @buf
*
* Receive up to @len bytes of data from the remote peer
* using the callback previously registered with
* qcrypto_tls_session_set_callbacks(), decrypt it and
* store it in @buf.
*
* It is an error to call this before
* qcrypto_tls_session_get_handshake_status() returns
* QCRYPTO_TLS_HANDSHAKE_COMPLETE
*
* Returns: the number of bytes received, or -1 on error
*/
ssize_t qcrypto_tls_session_read(QCryptoTLSSession *sess,
char *buf,
size_t len);
/**
* qcrypto_tls_session_handshake:
* @sess: the TLS session object
* @errp: pointer to an uninitialized error object
*
* Start, or continue, a TLS handshake sequence. If
* the underlying data channel is non-blocking, then
* this method may return control before the handshake
* is complete. On non-blocking channels the
* qcrypto_tls_session_get_handshake_status() method
* should be used to determine whether the handshake
* has completed, or is waiting to send or receive
* data. In the latter cases, the caller should setup
* an event loop watch and call this method again
* once the underlying data channel is ready to read
* or write again
*/
int qcrypto_tls_session_handshake(QCryptoTLSSession *sess,
Error **errp);
typedef enum {
QCRYPTO_TLS_HANDSHAKE_COMPLETE,
QCRYPTO_TLS_HANDSHAKE_SENDING,
QCRYPTO_TLS_HANDSHAKE_RECVING,
} QCryptoTLSSessionHandshakeStatus;
/**
* qcrypto_tls_session_get_handshake_status:
* @sess: the TLS session object
*
* Check the status of the TLS handshake. This
* is used with non-blocking data channels to
* determine whether the handshake is waiting
* to send or receive further data to/from the
* remote peer.
*
* Once this returns QCRYPTO_TLS_HANDSHAKE_COMPLETE
* it is permitted to send/receive payload data on
* the channel
*/
QCryptoTLSSessionHandshakeStatus
qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *sess);
/**
* qcrypto_tls_session_get_key_size:
* @sess: the TLS session object
* @errp: pointer to an uninitialized error object
*
* Check the size of the data channel encryption key
*
* Returns: the length in bytes of the encryption key
* or -1 on error
*/
int qcrypto_tls_session_get_key_size(QCryptoTLSSession *sess,
Error **errp);
/**
* qcrypto_tls_session_get_peer_name:
* @sess: the TLS session object
*
* Get the identified name of the remote peer. If the
* TLS session was negotiated using x509 certificate
* credentials, this will return the CommonName from
* the peer's certificate. If no identified name is
* available it will return NULL.
*
* The returned data must be released with g_free()
* when no longer required.
*
* Returns: the peer's name or NULL.
*/
char *qcrypto_tls_session_get_peer_name(QCryptoTLSSession *sess);
#endif /* QCRYPTO_TLS_SESSION_H__ */

4
tests/.gitignore vendored
View File

@ -15,6 +15,10 @@ test-crypto-hash
test-crypto-tlscredsx509
test-crypto-tlscredsx509-work/
test-crypto-tlscredsx509-certs/
test-crypto-tlssession
test-crypto-tlssession-work/
test-crypto-tlssession-client/
test-crypto-tlssession-server/
test-cutils
test-hbitmap
test-int128

View File

@ -79,6 +79,7 @@ gcov-files-test-write-threshold-y = block/write-threshold.c
check-unit-$(CONFIG_GNUTLS_HASH) += tests/test-crypto-hash$(EXESUF)
check-unit-y += tests/test-crypto-cipher$(EXESUF)
check-unit-$(CONFIG_GNUTLS) += tests/test-crypto-tlscredsx509$(EXESUF)
check-unit-$(CONFIG_GNUTLS) += tests/test-crypto-tlssession$(EXESUF)
check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
@ -361,6 +362,8 @@ tests/test-crypto-hash$(EXESUF): tests/test-crypto-hash.o $(test-crypto-obj-y)
tests/test-crypto-cipher$(EXESUF): tests/test-crypto-cipher.o $(test-crypto-obj-y)
tests/test-crypto-tlscredsx509$(EXESUF): tests/test-crypto-tlscredsx509.o \
tests/crypto-tls-x509-helpers.o tests/pkix_asn1_tab.o $(test-crypto-obj-y)
tests/test-crypto-tlssession$(EXESUF): tests/test-crypto-tlssession.o \
tests/crypto-tls-x509-helpers.o tests/pkix_asn1_tab.o $(test-crypto-obj-y)
libqos-obj-y = tests/libqos/pci.o tests/libqos/fw_cfg.o tests/libqos/malloc.o
libqos-obj-y += tests/libqos/i2c.o tests/libqos/libqos.o

View File

@ -0,0 +1,535 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
*
* 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 <stdlib.h>
#include <fcntl.h>
#include "config-host.h"
#include "crypto-tls-x509-helpers.h"
#include "crypto/tlscredsx509.h"
#include "crypto/tlssession.h"
#include "qom/object_interfaces.h"
#include "qemu/sockets.h"
#include "qemu/acl.h"
#ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
#define WORKDIR "tests/test-crypto-tlssession-work/"
#define KEYFILE WORKDIR "key-ctx.pem"
struct QCryptoTLSSessionTestData {
const char *servercacrt;
const char *clientcacrt;
const char *servercrt;
const char *clientcrt;
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);
}
static QCryptoTLSCreds *test_tls_creds_create(QCryptoTLSCredsEndpoint endpoint,
const char *certdir,
Error **errp)
{
Error *err = NULL;
Object *parent = object_get_objects_root();
Object *creds = object_new_with_props(
TYPE_QCRYPTO_TLS_CREDS_X509,
parent,
(endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER ?
"testtlscredsserver" : "testtlscredsclient"),
&err,
"endpoint", (endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER ?
"server" : "client"),
"dir", certdir,
"verify-peer", "yes",
/* We skip initial sanity checks here because we
* want to make sure that problems are being
* detected at the TLS session validation stage,
* and the test-crypto-tlscreds test already
* validate the sanity check code.
*/
"sanity-check", "no",
NULL
);
if (err) {
error_propagate(errp, err);
return NULL;
}
return QCRYPTO_TLS_CREDS(creds);
}
/*
* 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. We then get a socketpair, and
* initiate a TLS session across them. Finally do
* do actual cert validation tests
*/
static void test_crypto_tls_session(const void *opaque)
{
struct QCryptoTLSSessionTestData *data =
(struct QCryptoTLSSessionTestData *)opaque;
QCryptoTLSCreds *clientCreds;
QCryptoTLSCreds *serverCreds;
QCryptoTLSSession *clientSess = NULL;
QCryptoTLSSession *serverSess = NULL;
qemu_acl *acl;
const char * const *wildcards;
int channel[2];
bool clientShake = false;
bool serverShake = false;
Error *err = NULL;
int ret;
/* We'll use this for our fake client-server connection */
ret = socketpair(AF_UNIX, SOCK_STREAM, 0, channel);
g_assert(ret == 0);
/*
* We have an evil loop to do the handshake in a single
* thread, so we need these non-blocking to avoid deadlock
* of ourselves
*/
qemu_set_nonblock(channel[0]);
qemu_set_nonblock(channel[1]);
#define CLIENT_CERT_DIR "tests/test-crypto-tlssession-client/"
#define SERVER_CERT_DIR "tests/test-crypto-tlssession-server/"
mkdir(CLIENT_CERT_DIR, 0700);
mkdir(SERVER_CERT_DIR, 0700);
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT);
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY);
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT);
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY);
g_assert(link(data->servercacrt,
SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT) == 0);
g_assert(link(data->servercrt,
SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT) == 0);
g_assert(link(KEYFILE,
SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY) == 0);
g_assert(link(data->clientcacrt,
CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT) == 0);
g_assert(link(data->clientcrt,
CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT) == 0);
g_assert(link(KEYFILE,
CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY) == 0);
clientCreds = test_tls_creds_create(
QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT,
CLIENT_CERT_DIR,
&err);
g_assert(clientCreds != NULL);
serverCreds = test_tls_creds_create(
QCRYPTO_TLS_CREDS_ENDPOINT_SERVER,
SERVER_CERT_DIR,
&err);
g_assert(serverCreds != NULL);
acl = qemu_acl_init("tlssessionacl");
qemu_acl_reset(acl);
wildcards = data->wildcards;
while (wildcards && *wildcards) {
qemu_acl_append(acl, 0, *wildcards);
wildcards++;
}
/* Now the real part of the test, setup the sessions */
clientSess = qcrypto_tls_session_new(
clientCreds, data->hostname, NULL,
QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT, &err);
serverSess = qcrypto_tls_session_new(
serverCreds, NULL,
data->wildcards ? "tlssessionacl" : NULL,
QCRYPTO_TLS_CREDS_ENDPOINT_SERVER, &err);
g_assert(clientSess != NULL);
g_assert(serverSess != NULL);
/* For handshake to work, we need to set the I/O callbacks
* to read/write over the socketpair
*/
qcrypto_tls_session_set_callbacks(serverSess,
testWrite, testRead,
&channel[0]);
qcrypto_tls_session_set_callbacks(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 = qcrypto_tls_session_handshake(serverSess,
&err);
g_assert(rv >= 0);
if (qcrypto_tls_session_get_handshake_status(serverSess) ==
QCRYPTO_TLS_HANDSHAKE_COMPLETE) {
serverShake = true;
}
}
if (!clientShake) {
rv = qcrypto_tls_session_handshake(clientSess,
&err);
g_assert(rv >= 0);
if (qcrypto_tls_session_get_handshake_status(clientSess) ==
QCRYPTO_TLS_HANDSHAKE_COMPLETE) {
clientShake = true;
}
}
} while (!clientShake && !serverShake);
/* Finally make sure the server validation does what
* we were expecting
*/
if (qcrypto_tls_session_check_credentials(serverSess, &err) < 0) {
g_assert(data->expectServerFail);
error_free(err);
err = NULL;
} else {
g_assert(!data->expectServerFail);
}
/*
* And the same for the client validation check
*/
if (qcrypto_tls_session_check_credentials(clientSess, &err) < 0) {
g_assert(data->expectClientFail);
error_free(err);
err = NULL;
} else {
g_assert(!data->expectClientFail);
}
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT);
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY);
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT);
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY);
rmdir(CLIENT_CERT_DIR);
rmdir(SERVER_CERT_DIR);
object_unparent(OBJECT(serverCreds));
object_unparent(OBJECT(clientCreds));
qcrypto_tls_session_free(serverSess);
qcrypto_tls_session_free(clientSess);
close(channel[0]);
close(channel[1]);
}
int main(int argc, char **argv)
{
int ret;
module_call_init(MODULE_INIT_QOM);
g_test_init(&argc, &argv, NULL);
setenv("GNUTLS_FORCE_FIPS_MODE", "2", 1);
mkdir(WORKDIR, 0700);
test_tls_init(KEYFILE);
# define TEST_SESS_REG(name, caCrt, \
serverCrt, clientCrt, \
expectServerFail, expectClientFail, \
hostname, wildcards) \
struct QCryptoTLSSessionTestData name = { \
caCrt, caCrt, serverCrt, clientCrt, \
expectServerFail, expectClientFail, \
hostname, wildcards \
}; \
g_test_add_data_func("/qcrypto/tlssession/" # name, \
&name, test_crypto_tls_session); \
# define TEST_SESS_REG_EXT(name, serverCaCrt, clientCaCrt, \
serverCrt, clientCrt, \
expectServerFail, expectClientFail, \
hostname, wildcards) \
struct QCryptoTLSSessionTestData name = { \
serverCaCrt, clientCaCrt, serverCrt, clientCrt, \
expectServerFail, expectClientFail, \
hostname, wildcards \
}; \
g_test_add_data_func("/qcrypto/tlssession/" # name, \
&name, test_crypto_tls_session); \
/* A perfect CA, perfect client & perfect server */
/* Basic:CA:critical */
TLS_ROOT_REQ(cacertreq,
"UK", "qemu 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", "qemu CA 1", NULL, NULL, NULL, NULL,
true, true, true,
false, false, 0,
false, false, NULL, NULL,
0, 0);
TLS_CERT_REQ(servercertreq, cacertreq,
"UK", "qemu.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", "qemu", 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", "qemu", 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);
TEST_SESS_REG(basicca, cacertreq.filename,
servercertreq.filename, clientcertreq.filename,
false, false, "qemu.org", NULL);
TEST_SESS_REG_EXT(differentca, cacertreq.filename,
altcacertreq.filename, servercertreq.filename,
clientcertaltreq.filename, true, true, "qemu.org", NULL);
/* When an altname is set, the CN is ignored, so it must be duplicated
* as an altname for it to match */
TLS_CERT_REQ(servercertalt1req, cacertreq,
"UK", "qemu.org", "www.qemu.org", "qemu.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);
/* This intentionally doesn't replicate */
TLS_CERT_REQ(servercertalt2req, cacertreq,
"UK", "qemu.org", "www.qemu.org", "wiki.qemu.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);
TEST_SESS_REG(altname1, cacertreq.filename,
servercertalt1req.filename, clientcertreq.filename,
false, false, "qemu.org", NULL);
TEST_SESS_REG(altname2, cacertreq.filename,
servercertalt1req.filename, clientcertreq.filename,
false, false, "www.qemu.org", NULL);
TEST_SESS_REG(altname3, cacertreq.filename,
servercertalt1req.filename, clientcertreq.filename,
false, true, "wiki.qemu.org", NULL);
TEST_SESS_REG(altname4, cacertreq.filename,
servercertalt2req.filename, clientcertreq.filename,
false, true, "qemu.org", NULL);
TEST_SESS_REG(altname5, cacertreq.filename,
servercertalt2req.filename, clientcertreq.filename,
false, false, "www.qemu.org", NULL);
TEST_SESS_REG(altname6, cacertreq.filename,
servercertalt2req.filename, clientcertreq.filename,
false, false, "wiki.qemu.org", NULL);
const char *const wildcards1[] = {
"C=UK,CN=dogfood",
NULL,
};
const char *const wildcards2[] = {
"C=UK,CN=qemu",
NULL,
};
const char *const wildcards3[] = {
"C=UK,CN=dogfood",
"C=UK,CN=qemu",
NULL,
};
const char *const wildcards4[] = {
"C=UK,CN=qemustuff",
NULL,
};
const char *const wildcards5[] = {
"C=UK,CN=qemu*",
NULL,
};
const char *const wildcards6[] = {
"C=UK,CN=*emu*",
NULL,
};
TEST_SESS_REG(wildcard1, cacertreq.filename,
servercertreq.filename, clientcertreq.filename,
true, false, "qemu.org", wildcards1);
TEST_SESS_REG(wildcard2, cacertreq.filename,
servercertreq.filename, clientcertreq.filename,
false, false, "qemu.org", wildcards2);
TEST_SESS_REG(wildcard3, cacertreq.filename,
servercertreq.filename, clientcertreq.filename,
false, false, "qemu.org", wildcards3);
TEST_SESS_REG(wildcard4, cacertreq.filename,
servercertreq.filename, clientcertreq.filename,
true, false, "qemu.org", wildcards4);
TEST_SESS_REG(wildcard5, cacertreq.filename,
servercertreq.filename, clientcertreq.filename,
false, false, "qemu.org", wildcards5);
TEST_SESS_REG(wildcard6, cacertreq.filename,
servercertreq.filename, clientcertreq.filename,
false, false, "qemu.org", wildcards6);
TLS_ROOT_REQ(cacertrootreq,
"UK", "qemu 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", "qemu 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", "qemu 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", "qemu 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", "qemu.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", "qemu 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,
};
test_tls_write_cert_chain(WORKDIR "cacertchain-sess.pem",
certchain,
G_N_ELEMENTS(certchain));
TEST_SESS_REG(cachain, WORKDIR "cacertchain-sess.pem",
servercertlevel3areq.filename, clientcertlevel2breq.filename,
false, false, "qemu.org", NULL);
ret = g_test_run();
test_tls_discard_cert(&clientcertreq);
test_tls_discard_cert(&clientcertaltreq);
test_tls_discard_cert(&servercertreq);
test_tls_discard_cert(&servercertalt1req);
test_tls_discard_cert(&servercertalt2req);
test_tls_discard_cert(&cacertreq);
test_tls_discard_cert(&altcacertreq);
test_tls_discard_cert(&cacertrootreq);
test_tls_discard_cert(&cacertlevel1areq);
test_tls_discard_cert(&cacertlevel1breq);
test_tls_discard_cert(&cacertlevel2areq);
test_tls_discard_cert(&servercertlevel3areq);
test_tls_discard_cert(&clientcertlevel2breq);
unlink(WORKDIR "cacertchain-sess.pem");
test_tls_cleanup(KEYFILE);
rmdir(WORKDIR);
return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
#else /* ! QCRYPTO_HAVE_TLS_TEST_SUPPORT */
int
main(void)
{
return EXIT_SUCCESS;
}
#endif /* ! QCRYPTO_HAVE_TLS_TEST_SUPPORT */

View File

@ -1681,3 +1681,6 @@ qcrypto_tls_creds_x509_check_key_usage(void *creds, const char *file, int status
qcrypto_tls_creds_x509_check_key_purpose(void *creds, const char *file, int status, const char *usage, int critical) "TLS creds x509 check key usage creds=%p file=%s status=%d usage=%s critical=%d"
qcrypto_tls_creds_x509_load_cert(void *creds, int isServer, const char *file) "TLS creds x509 load cert creds=%p isServer=%d file=%s"
qcrypto_tls_creds_x509_load_cert_list(void *creds, const char *file) "TLS creds x509 load cert list creds=%p file=%s"
# crypto/tlssession.c
qcrypto_tls_session_new(void *session, void *creds, const char *hostname, const char *aclname, int endpoint) "TLS session new session=%p creds=%p hostname=%s aclname=%s endpoint=%d"