mirror of
https://github.com/checkpoint-restore/criu.git
synced 2026-01-23 02:14:37 +00:00
page-xfer: Add TLS support with X509 certificates
This commit adds Transport Layer Security (TLS) support for remote page-server connections. The following command-line options are introduced with this commit: --tls-cacert FILE Trust certificates signed only by this CA --tls-cacrl FILE CA certificate revocation list --tls-cert FILE TLS certificate --tls-key FILE TLS private key --tls Use TLS to secure remote connections The default PKI locations are: CA certificate /etc/pki/CA/cacert.pem CA revocation list /etc/pki/CA/cacrl.pem Client/server certificate /etc/pki/criu/cert.pem Client/server private key /etc/pki/criu/private/key.pem The files cacert.pem and cacrl.pem are optional. If they are not present, and not explicitly specified with a command-line option, CRIU will use only the system's trusted CAs to verify the remote peer's identity. This implies that if a CA certificate is specified using "--tls-cacert" only this CA will be used for verification. If CA certificate (cacert.pem) is not present, certificate revocation list (cacrl.pem) will be ignored. Both (client and server) sides require a private key and certificate. When the "--tls" option is specified, a TLS handshake (key exchange) will be performed immediately after the remote TCP connection has been accepted. X.509 certificates can be generated as follows: -------------------------%<------------------------- # Generate CA key and certificate echo -ne "ca\ncert_signing_key" > temp certtool --generate-privkey > cakey.pem certtool --generate-self-signed \ --template temp \ --load-privkey cakey.pem \ --outfile cacert.pem # Generate server key and certificate echo -ne "cn=$HOSTNAME\nencryption_key\nsigning_key" > temp certtool --generate-privkey > key.pem certtool --generate-certificate \ --template temp \ --load-privkey key.pem \ --load-ca-certificate cacert.pem \ --load-ca-privkey cakey.pem \ --outfile cert.pem rm temp mkdir -p /etc/pki/CA mkdir -p /etc/pki/criu/private mv cacert.pem /etc/pki/CA/ mv cert.pem /etc/pki/criu/ mv key.pem /etc/pki/criu/private -------------------------%<------------------------- Usage Example: Page-server: [src]# criu page-server -D <PATH> --port <PORT> --tls [dst]# criu dump --page-server --address <SRC> --port <PORT> \ -t <PID> -D <PATH> --tls Lazy migration: [src]# criu dump --lazy-pages --port <PORT> -t <PID> -D <PATH> --tls [dst]# criu lazy-pages --page-server --address <SRC> --port <PORT> \ -D <PATH> --tls [dst]# criu restore -D <PATH> --lazy-pages Signed-off-by: Radostin Stoyanov <rstoyanov1@gmail.com>
This commit is contained in:
parent
b7230b6132
commit
76a41209b0
11 changed files with 546 additions and 34 deletions
|
|
@ -594,6 +594,33 @@ Launches *criu* in page server mode.
|
|||
remote *lazy-pages* daemon to request memory pages in random
|
||||
order.
|
||||
|
||||
*--tls-cacert* 'file'::
|
||||
Specifies the path to a trusted Certificate Authority (CA) certificate
|
||||
file to be used for verification of a client or server certificate.
|
||||
The 'file' must be in PEM format. When this option is used only the
|
||||
specified CA is used for verification. Otherwise, the system's trusted CAs
|
||||
and, if present, '/etc/pki/CA/cacert.pem' will be used.
|
||||
|
||||
*--tls-cacrl* 'file'::
|
||||
Specifies a path to a Certificate Revocation List (CRL) 'file' which
|
||||
contains a list of revoked certificates that should no longer be trusted.
|
||||
The 'file' must be in PEM format. When this option is not specified, the
|
||||
file, if present, '/etc/pki/CA/cacrl.pem' will be used.
|
||||
|
||||
*--tls-cert* 'file'::
|
||||
Specifies a path to a file that contains a X.509 certificate to present
|
||||
to the remote entity. The 'file' must be in PEM format. When this option
|
||||
is not specified, the default location ('/etc/pki/criu/cert.pem') will be
|
||||
used.
|
||||
|
||||
*--tls-key* 'file'::
|
||||
Specifies a path to a file that contains TLS private key. The 'file' must
|
||||
be in PEM format. When this option is not the default location
|
||||
('/etc/pki/criu/private/key.pem') will be used.
|
||||
|
||||
*--tls*::
|
||||
Use TLS to secure remote connections.
|
||||
|
||||
*lazy-pages*
|
||||
~~~~~~~~~~~~
|
||||
Launches *criu* in lazy-pages daemon mode.
|
||||
|
|
|
|||
1
Makefile
1
Makefile
|
|
@ -193,6 +193,7 @@ include Makefile.config
|
|||
else
|
||||
# To clean all files, enable make/build options here
|
||||
export CONFIG_COMPAT := y
|
||||
export CONFIG_GNUTLS := y
|
||||
endif
|
||||
|
||||
#
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# here is a workaround for a bug in libnl-3:
|
||||
# 6a8d90f5fec4 "attr: Allow attribute type 0"
|
||||
# 6a8d90f5fec4 "attr: Allow attribute type 0"
|
||||
WRAPFLAGS += -Wl,--wrap=nla_parse,--wrap=nlmsg_parse
|
||||
|
||||
ARCH_DIR := criu/arch/$(SRCARCH)
|
||||
|
|
@ -14,6 +14,7 @@ endif
|
|||
|
||||
#
|
||||
# Configuration file paths
|
||||
CONFIG-DEFINES += -DSYSCONFDIR='"/etc"'
|
||||
CONFIG-DEFINES += -DGLOBAL_CONFIG_DIR='"/etc/criu/"'
|
||||
CONFIG-DEFINES += -DDEFAULT_CONFIG_FILENAME='"default.conf"'
|
||||
CONFIG-DEFINES += -DUSER_CONFIG_DIR='".criu/"'
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ obj-y += string.o
|
|||
obj-y += sysctl.o
|
||||
obj-y += sysfs_parse.o
|
||||
obj-y += timerfd.o
|
||||
obj-$(CONFIG_GNUTLS) += tls.o
|
||||
obj-y += tty.o
|
||||
obj-y += tun.o
|
||||
obj-y += util.o
|
||||
|
|
|
|||
|
|
@ -510,6 +510,11 @@ int parse_options(int argc, char **argv, bool *usage_error,
|
|||
{ "ps-socket", required_argument, 0, 1091},
|
||||
{ "config", required_argument, 0, 1089},
|
||||
{ "no-default-config", no_argument, 0, 1090},
|
||||
{ "tls-cacert", required_argument, 0, 1092},
|
||||
{ "tls-cacrl", required_argument, 0, 1093},
|
||||
{ "tls-cert", required_argument, 0, 1094},
|
||||
{ "tls-key", required_argument, 0, 1095},
|
||||
BOOL_OPT("tls", &opts.tls),
|
||||
{ },
|
||||
};
|
||||
|
||||
|
|
@ -796,6 +801,18 @@ int parse_options(int argc, char **argv, bool *usage_error,
|
|||
case 1091:
|
||||
opts.ps_socket = atoi(optarg);
|
||||
break;
|
||||
case 1092:
|
||||
SET_CHAR_OPTS(tls_cacert, optarg);
|
||||
break;
|
||||
case 1093:
|
||||
SET_CHAR_OPTS(tls_cacrl, optarg);
|
||||
break;
|
||||
case 1094:
|
||||
SET_CHAR_OPTS(tls_cert, optarg);
|
||||
break;
|
||||
case 1095:
|
||||
SET_CHAR_OPTS(tls_key, optarg);
|
||||
break;
|
||||
case 'V':
|
||||
pr_msg("Version: %s\n", CRIU_VERSION);
|
||||
if (strcmp(CRIU_GITID, "0"))
|
||||
|
|
@ -857,6 +874,13 @@ int check_options()
|
|||
}
|
||||
}
|
||||
|
||||
#ifndef CONFIG_GNUTLS
|
||||
if (opts.tls) {
|
||||
pr_err("CRIU was built without TLS support\n");
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (check_namespace_opts()) {
|
||||
pr_err("Error: namespace flags conflict\n");
|
||||
return 1;
|
||||
|
|
|
|||
|
|
@ -427,6 +427,11 @@ usage:
|
|||
" -d|--daemon run in the background after creating socket\n"
|
||||
" --status-fd FD write \\0 to the FD and close it once process is ready\n"
|
||||
" to handle requests\n"
|
||||
" --tls-cacert FILE trust certificates signed only by this CA\n"
|
||||
" --tls-cacrl FILE path to CA certificate revocation list file\n"
|
||||
" --tls-cert FILE path to TLS certificate file\n"
|
||||
" --tls-key FILE path to TLS private key file\n"
|
||||
" --tls use TLS to secure remote connection\n"
|
||||
"\n"
|
||||
"Configuration file options:\n"
|
||||
" --config FILEPATH pass a specific configuration file\n"
|
||||
|
|
|
|||
|
|
@ -138,6 +138,11 @@ struct cr_options {
|
|||
pid_t tree_id;
|
||||
int log_level;
|
||||
char *imgs_dir;
|
||||
char *tls_cacert;
|
||||
char *tls_cacrl;
|
||||
char *tls_cert;
|
||||
char *tls_key;
|
||||
int tls;
|
||||
};
|
||||
|
||||
extern struct cr_options opts;
|
||||
|
|
|
|||
26
criu/include/tls.h
Normal file
26
criu/include/tls.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#ifndef __CR_TLS_H__
|
||||
#define __CR_TLS_H__
|
||||
|
||||
# ifdef CONFIG_GNUTLS
|
||||
|
||||
int tls_x509_init(int sockfd, bool is_server);
|
||||
void tls_terminate_session();
|
||||
|
||||
ssize_t tls_send(const void *buf, size_t len, int flags);
|
||||
ssize_t tls_recv(void *buf, size_t len, int flags);
|
||||
|
||||
int tls_send_data_from_fd(int fd, unsigned long len);
|
||||
int tls_recv_data_to_fd(int fd, unsigned long len);
|
||||
|
||||
# else /* CONFIG_GNUTLS */
|
||||
|
||||
#define tls_x509_init(sockfd, is_server) (0)
|
||||
#define tls_send(buf, len, flags) (-1)
|
||||
#define tls_recv(buf, len, flags) (-1)
|
||||
#define tls_send_data_from_fd(fd, len) (-1)
|
||||
#define tls_recv_data_to_fd(fd, len) (-1)
|
||||
#define tls_terminate_session()
|
||||
|
||||
#endif /* CONFIG_HAS_GNUTLS */
|
||||
|
||||
#endif /* __CR_TLS_H__ */
|
||||
119
criu/page-xfer.c
119
criu/page-xfer.c
|
|
@ -21,6 +21,7 @@
|
|||
#include "parasite-syscall.h"
|
||||
#include "rst_info.h"
|
||||
#include "stats.h"
|
||||
#include "tls.h"
|
||||
|
||||
static int page_server_sk = -1;
|
||||
|
||||
|
|
@ -128,13 +129,22 @@ static inline u32 decode_ps_flags(u32 cmd)
|
|||
return cmd >> PS_CMD_BITS;
|
||||
}
|
||||
|
||||
static inline int __send(int sk, const void *buf, size_t sz, int fl)
|
||||
{
|
||||
return opts.tls ? tls_send(buf, sz, fl) : send(sk, buf, sz, fl);
|
||||
}
|
||||
|
||||
static inline int __recv(int sk, void *buf, size_t sz, int fl)
|
||||
{
|
||||
return opts.tls ? tls_recv(buf, sz, fl) : recv(sk, buf, sz, fl);
|
||||
}
|
||||
|
||||
static inline int send_psi_flags(int sk, struct page_server_iov *pi, int flags)
|
||||
{
|
||||
if (send(sk, pi, sizeof(*pi), flags) != sizeof(*pi)) {
|
||||
if (__send(sk, pi, sizeof(*pi), flags) != sizeof(*pi)) {
|
||||
pr_perror("Can't send PSI %d to server", pi->cmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -149,17 +159,28 @@ static int write_pages_to_server(struct page_xfer *xfer,
|
|||
{
|
||||
ssize_t ret, left = len;
|
||||
|
||||
pr_debug("Splicing %lu bytes / %lu pages into socket\n", len, len / PAGE_SIZE);
|
||||
if (opts.tls) {
|
||||
pr_debug("Sending %lu bytes / %lu pages\n",
|
||||
len, len / PAGE_SIZE);
|
||||
|
||||
while (left > 0) {
|
||||
ret = splice(p, NULL, xfer->sk, NULL, left, SPLICE_F_MOVE);
|
||||
if (ret < 0) {
|
||||
pr_perror("Can't write pages to socket");
|
||||
if (tls_send_data_from_fd(p, len))
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
pr_debug("Splicing %lu bytes / %lu pages into socket\n",
|
||||
len, len / PAGE_SIZE);
|
||||
|
||||
pr_debug("\tSpliced: %lu bytes sent\n", (unsigned long)ret);
|
||||
left -= ret;
|
||||
while (left > 0) {
|
||||
ret = splice(p, NULL, xfer->sk, NULL, left,
|
||||
SPLICE_F_MOVE);
|
||||
if (ret < 0) {
|
||||
pr_perror("Can't write pages to socket");
|
||||
return -1;
|
||||
}
|
||||
|
||||
pr_debug("\tSpliced: %lu bytes sent\n",
|
||||
(unsigned long)ret);
|
||||
left -= ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
@ -205,7 +226,7 @@ static int open_page_server_xfer(struct page_xfer *xfer, int fd_type, unsigned l
|
|||
/* Push the command NOW */
|
||||
tcp_nodelay(xfer->sk, true);
|
||||
|
||||
if (read(xfer->sk, &has_parent, 1) != 1) {
|
||||
if (__recv(xfer->sk, &has_parent, 1, 0) != 1) {
|
||||
pr_perror("The page server doesn't answer");
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -539,7 +560,7 @@ static int page_server_check_parent(int sk, struct page_server_iov *pi)
|
|||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
if (write(sk, &ret, sizeof(ret)) != sizeof(ret)) {
|
||||
if (__send(sk, &ret, sizeof(ret), 0) != sizeof(ret)) {
|
||||
pr_perror("Unable to send response");
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -560,7 +581,7 @@ static int check_parent_server_xfer(int fd_type, unsigned long img_id)
|
|||
|
||||
tcp_nodelay(page_server_sk, true);
|
||||
|
||||
if (read(page_server_sk, &has_parent, sizeof(int)) != sizeof(int)) {
|
||||
if (__recv(page_server_sk, &has_parent, sizeof(int), 0) != sizeof(int)) {
|
||||
pr_perror("The page server doesn't answer");
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -624,8 +645,7 @@ static int page_server_open(int sk, struct page_server_iov *pi)
|
|||
|
||||
if (sk >= 0) {
|
||||
char has_parent = !!cxfer.loc_xfer.parent;
|
||||
|
||||
if (write(sk, &has_parent, 1) != 1) {
|
||||
if (__send(sk, &has_parent, 1, 0) != 1) {
|
||||
pr_perror("Unable to send response");
|
||||
close_page_xfer(&cxfer.loc_xfer);
|
||||
return -1;
|
||||
|
|
@ -684,14 +704,23 @@ static int page_server_add(int sk, struct page_server_iov *pi, u32 flags)
|
|||
return -1;
|
||||
}
|
||||
|
||||
chunk = splice(sk, NULL, cxfer.p[1], NULL, chunk, SPLICE_F_MOVE | SPLICE_F_NONBLOCK);
|
||||
if (chunk < 0) {
|
||||
pr_perror("Can't read from socket");
|
||||
return -1;
|
||||
}
|
||||
if (chunk == 0) {
|
||||
pr_err("A socket was closed unexpectedly\n");
|
||||
return -1;
|
||||
if (opts.tls) {
|
||||
if(tls_recv_data_to_fd(cxfer.p[1], chunk)) {
|
||||
pr_err("Can't read from socket\n");
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
chunk = splice(sk, NULL, cxfer.p[1], NULL, chunk,
|
||||
SPLICE_F_MOVE | SPLICE_F_NONBLOCK);
|
||||
|
||||
if (chunk < 0) {
|
||||
pr_perror("Can't read from socket");
|
||||
return -1;
|
||||
}
|
||||
if (chunk == 0) {
|
||||
pr_err("A socket was closed unexpectedly\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (lxfer->write_pages(lxfer, cxfer.p[0], chunk))
|
||||
|
|
@ -733,9 +762,16 @@ static int page_server_get_pages(int sk, struct page_server_iov *pi)
|
|||
return -1;
|
||||
|
||||
len = pi->nr_pages * PAGE_SIZE;
|
||||
ret = splice(pipe_read_dest.p[0], NULL, sk, NULL, len, SPLICE_F_MOVE);
|
||||
if (ret != len)
|
||||
return -1;
|
||||
|
||||
if (opts.tls) {
|
||||
if (tls_send_data_from_fd(pipe_read_dest.p[0], len))
|
||||
return -1;
|
||||
} else {
|
||||
ret = splice(pipe_read_dest.p[0], NULL, sk, NULL, len,
|
||||
SPLICE_F_MOVE);
|
||||
if (ret != len)
|
||||
return -1;
|
||||
}
|
||||
|
||||
tcp_nodelay(sk, true);
|
||||
|
||||
|
|
@ -773,7 +809,7 @@ static int page_server_serve(int sk)
|
|||
struct page_server_iov pi;
|
||||
u32 cmd;
|
||||
|
||||
ret = recv(sk, &pi, sizeof(pi), MSG_WAITALL);
|
||||
ret = __recv(sk, &pi, sizeof(pi), MSG_WAITALL);
|
||||
if (!ret)
|
||||
break;
|
||||
|
||||
|
|
@ -823,7 +859,7 @@ static int page_server_serve(int sk)
|
|||
* An answer must be sent back to inform another side,
|
||||
* that all data were received
|
||||
*/
|
||||
if (write(sk, &status, sizeof(status)) != sizeof(status)) {
|
||||
if (__send(sk, &status, sizeof(status), 0) != sizeof(status)) {
|
||||
pr_perror("Can't send the final package");
|
||||
ret = -1;
|
||||
}
|
||||
|
|
@ -856,14 +892,15 @@ static int page_server_serve(int sk)
|
|||
* Wait when a remote side closes the connection
|
||||
* to avoid TIME_WAIT bucket
|
||||
*/
|
||||
|
||||
if (read(sk, &c, sizeof(c)) != 0) {
|
||||
pr_perror("Unexpected data");
|
||||
ret = -1;
|
||||
}
|
||||
}
|
||||
|
||||
tls_terminate_session();
|
||||
page_server_close();
|
||||
|
||||
pr_info("Session over\n");
|
||||
|
||||
close(sk);
|
||||
|
|
@ -1011,6 +1048,11 @@ no_server:
|
|||
if (ret != 0)
|
||||
return ret > 0 ? 0 : -1;
|
||||
|
||||
if (tls_x509_init(ask, true)) {
|
||||
close(sk);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ask >= 0)
|
||||
ret = page_server_serve(ask);
|
||||
|
||||
|
|
@ -1034,6 +1076,11 @@ static int connect_to_page_server(void)
|
|||
page_server_sk = setup_tcp_client(opts.addr);
|
||||
if (page_server_sk == -1)
|
||||
return -1;
|
||||
|
||||
if (tls_x509_init(page_server_sk, false)) {
|
||||
close(page_server_sk);
|
||||
return -1;
|
||||
}
|
||||
out:
|
||||
/*
|
||||
* CORK the socket at the very beginning. As per ANK
|
||||
|
|
@ -1076,14 +1123,16 @@ int disconnect_from_page_server(void)
|
|||
if (send_psi(page_server_sk, &pi))
|
||||
goto out;
|
||||
|
||||
if (read(page_server_sk, &status, sizeof(status)) != sizeof(status)) {
|
||||
if (__recv(page_server_sk, &status, sizeof(status), 0) != sizeof(status)) {
|
||||
pr_perror("The page server doesn't answer");
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
out:
|
||||
tls_terminate_session();
|
||||
close_safe(&page_server_sk);
|
||||
|
||||
return ret ? : status;
|
||||
}
|
||||
|
||||
|
|
@ -1160,10 +1209,14 @@ static int page_server_read(struct ps_async_read *ar, int flags)
|
|||
need = ar->goal - ar->rb;
|
||||
}
|
||||
|
||||
ret = recv(page_server_sk, buf, need, flags);
|
||||
ret = __recv(page_server_sk, buf, need, flags);
|
||||
if (ret < 0) {
|
||||
pr_perror("Error reading async data from page server");
|
||||
return -1;
|
||||
if (flags == MSG_DONTWAIT && (errno == EAGAIN || errno == EINTR)) {
|
||||
ret = 0;
|
||||
} else {
|
||||
pr_perror("Error reading data from page server");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
ar->rb += ret;
|
||||
|
|
|
|||
366
criu/tls.c
Normal file
366
criu/tls.c
Normal file
|
|
@ -0,0 +1,366 @@
|
|||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <linux/limits.h>
|
||||
|
||||
#include <gnutls/gnutls.h>
|
||||
|
||||
#include "cr_options.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
/* Compatability with GnuTLS verson <3.5 */
|
||||
#ifndef GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR
|
||||
# define GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR GNUTLS_E_CERTIFICATE_ERROR
|
||||
#endif
|
||||
|
||||
#undef LOG_PREFIX
|
||||
#define LOG_PREFIX "tls: "
|
||||
|
||||
#define CRIU_PKI_DIR SYSCONFDIR "/pki"
|
||||
#define CRIU_CACERT CRIU_PKI_DIR "/CA/cacert.pem"
|
||||
#define CRIU_CACRL CRIU_PKI_DIR "/CA/cacrl.pem"
|
||||
#define CRIU_CERT CRIU_PKI_DIR "/criu/cert.pem"
|
||||
#define CRIU_KEY CRIU_PKI_DIR "/criu/private/key.pem"
|
||||
|
||||
#define SPLICE_BUF_SZ_MAX (PIPE_BUF * 100)
|
||||
|
||||
#define tls_perror(msg, ret) pr_err("%s: %s\n", msg, gnutls_strerror(ret))
|
||||
|
||||
static gnutls_session_t session;
|
||||
static gnutls_certificate_credentials_t x509_cred;
|
||||
static int tls_sk = -1;
|
||||
static int tls_sk_flags = 0;
|
||||
|
||||
void tls_terminate_session()
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!opts.tls)
|
||||
return;
|
||||
|
||||
if (session) {
|
||||
do {
|
||||
/* don't wait for peer to close connection */
|
||||
ret = gnutls_bye(session, GNUTLS_SHUT_WR);
|
||||
} while(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED);
|
||||
gnutls_deinit(session);
|
||||
}
|
||||
|
||||
tls_sk = -1;
|
||||
if (x509_cred)
|
||||
gnutls_certificate_free_credentials(x509_cred);
|
||||
}
|
||||
|
||||
ssize_t tls_send(const void *buf, size_t len, int flags)
|
||||
{
|
||||
int ret;
|
||||
|
||||
tls_sk_flags = flags;
|
||||
ret = gnutls_record_send(session, buf, len);
|
||||
tls_sk_flags = 0;
|
||||
|
||||
if (ret < 0) {
|
||||
switch(ret) {
|
||||
case GNUTLS_E_AGAIN:
|
||||
errno = EAGAIN;
|
||||
break;
|
||||
case GNUTLS_E_INTERRUPTED:
|
||||
errno = EINTR;
|
||||
break;
|
||||
case GNUTLS_E_UNEXPECTED_PACKET_LENGTH:
|
||||
errno = ENOMSG;
|
||||
break;
|
||||
default:
|
||||
tls_perror("Failed to send data", ret);
|
||||
errno = EIO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read data from a file descriptor, then encrypt and send it with GnuTLS.
|
||||
* This function is used for cases when we would otherwise use splice()
|
||||
* to transfer data from PIPE to TCP socket.
|
||||
*/
|
||||
int tls_send_data_from_fd(int fd, unsigned long len)
|
||||
{
|
||||
ssize_t copied;
|
||||
unsigned long buf_size = min(len, (unsigned long)SPLICE_BUF_SZ_MAX);
|
||||
void *buf = xmalloc(buf_size);
|
||||
|
||||
if (!buf)
|
||||
return -1;
|
||||
|
||||
while (len > 0) {
|
||||
int ret, sent;
|
||||
|
||||
copied = read(fd, buf, min(len, buf_size));
|
||||
if (copied <= 0) {
|
||||
pr_perror("Can't read from pipe");
|
||||
goto err;
|
||||
}
|
||||
|
||||
for(sent = 0; sent < copied; sent += ret) {
|
||||
ret = tls_send((buf + sent), (copied - sent), 0);
|
||||
if (ret < 0) {
|
||||
tls_perror("Failed sending data", ret);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
len -= copied;
|
||||
}
|
||||
err:
|
||||
xfree(buf);
|
||||
return (len > 0);
|
||||
}
|
||||
|
||||
ssize_t tls_recv(void *buf, size_t len, int flags)
|
||||
{
|
||||
int ret;
|
||||
|
||||
tls_sk_flags = flags;
|
||||
ret = gnutls_record_recv(session, buf, len);
|
||||
tls_sk_flags = 0;
|
||||
|
||||
/* Check if there are any data to receive in the gnutls buffers. */
|
||||
if (flags == MSG_DONTWAIT
|
||||
&& (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)) {
|
||||
size_t pending = gnutls_record_check_pending(session);
|
||||
if (pending > 0) {
|
||||
pr_debug("Receiving pending data (%zu bytes)\n", pending);
|
||||
ret = gnutls_record_recv(session, buf, len);
|
||||
}
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
switch (ret) {
|
||||
case GNUTLS_E_AGAIN:
|
||||
errno = EAGAIN;
|
||||
break;
|
||||
case GNUTLS_E_INTERRUPTED:
|
||||
errno = EINTR;
|
||||
break;
|
||||
default:
|
||||
tls_perror("Failed receiving data", ret);
|
||||
errno = EIO;
|
||||
break;
|
||||
}
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read and decrypt data with GnuTLS, then write it to a file descriptor.
|
||||
* This function is used for cases when we would otherwise use splice()
|
||||
* to transfer data from a TCP socket to a PIPE.
|
||||
*/
|
||||
int tls_recv_data_to_fd(int fd, unsigned long len)
|
||||
{
|
||||
gnutls_packet_t packet;
|
||||
|
||||
while (len > 0) {
|
||||
int ret, w;
|
||||
gnutls_datum_t pdata;
|
||||
|
||||
ret = gnutls_record_recv_packet(session, &packet);
|
||||
if (ret == 0) {
|
||||
pr_info("Connection closed by peer\n");
|
||||
break;
|
||||
} else if (ret < 0) {
|
||||
tls_perror("Received corrupted data", ret);
|
||||
break;
|
||||
}
|
||||
|
||||
gnutls_packet_get(packet, &pdata, NULL);
|
||||
for(w = 0; w < pdata.size; w += ret) {
|
||||
ret = write(fd, (pdata.data + w), (pdata.size - w));
|
||||
if (ret < 0) {
|
||||
pr_perror("Failed writing to fd");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
len -= pdata.size;
|
||||
}
|
||||
err:
|
||||
gnutls_packet_deinit(packet);
|
||||
return (len > 0);
|
||||
}
|
||||
|
||||
static inline void tls_handshake_verification_status_print(int ret, unsigned status)
|
||||
{
|
||||
gnutls_datum_t out;
|
||||
int type = gnutls_certificate_type_get(session);
|
||||
|
||||
if (!gnutls_certificate_verification_status_print(status, type, &out, 0))
|
||||
pr_err("%s\n", out.data);
|
||||
|
||||
gnutls_free(out.data);
|
||||
}
|
||||
|
||||
static int tls_x509_verify_peer_cert(void)
|
||||
{
|
||||
int ret;
|
||||
unsigned status;
|
||||
|
||||
ret = gnutls_certificate_verify_peers3(session, opts.addr, &status);
|
||||
if (ret != GNUTLS_E_SUCCESS) {
|
||||
tls_perror("Unable to verify TLS peer", ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (status != 0) {
|
||||
pr_err("Invalid certificate\n");
|
||||
tls_handshake_verification_status_print(
|
||||
GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR, status);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tls_handshake()
|
||||
{
|
||||
int ret = -1;
|
||||
while (ret != GNUTLS_E_SUCCESS) {
|
||||
ret = gnutls_handshake(session);
|
||||
if (gnutls_error_is_fatal(ret)) {
|
||||
tls_perror("TLS handshake failed", ret);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
pr_info("TLS handshake completed\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tls_x509_setup_creds()
|
||||
{
|
||||
int ret;
|
||||
char *cacert = CRIU_CACERT;
|
||||
char *cacrl = CRIU_CACRL;
|
||||
char *cert = CRIU_CERT;
|
||||
char *key = CRIU_KEY;
|
||||
gnutls_x509_crt_fmt_t pem = GNUTLS_X509_FMT_PEM;
|
||||
|
||||
if (opts.tls_cacert)
|
||||
cacert = opts.tls_cacert;
|
||||
if (opts.tls_cacrl)
|
||||
cacrl = opts.tls_cacrl;
|
||||
if (opts.tls_cert)
|
||||
cert = opts.tls_cert;
|
||||
if (opts.tls_key)
|
||||
key = opts.tls_key;
|
||||
|
||||
ret = gnutls_certificate_allocate_credentials(&x509_cred);
|
||||
if (ret != GNUTLS_E_SUCCESS) {
|
||||
tls_perror("Failed to allocate x509 credentials", ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!opts.tls_cacert) {
|
||||
ret = gnutls_certificate_set_x509_system_trust(x509_cred);
|
||||
if (ret < 0) {
|
||||
tls_perror("Failed to load default trusted CAs", ret);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
ret = gnutls_certificate_set_x509_trust_file(x509_cred, cacert, pem);
|
||||
if (ret == 0) {
|
||||
pr_info("No trusted CA certificates added (%s)\n", cacert);
|
||||
if (opts.tls_cacert)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!access(cacrl, R_OK)) {
|
||||
ret = gnutls_certificate_set_x509_crl_file(x509_cred, cacrl, pem);
|
||||
if (ret < 0) {
|
||||
tls_perror("Can't set certificate revocation list", ret);
|
||||
return -1;
|
||||
}
|
||||
} else if (opts.tls_cacrl) {
|
||||
pr_perror("Can't read certificate revocation list %s", cacrl);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = gnutls_certificate_set_x509_key_file(x509_cred, cert, key, pem);
|
||||
if (ret != GNUTLS_E_SUCCESS) {
|
||||
tls_perror("Failed to set certificate/private key pair", ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t _tls_push_cb(void *p, const void* data, size_t sz)
|
||||
{
|
||||
int fd = *(int *)(p);
|
||||
return send(fd, data, sz, tls_sk_flags);
|
||||
}
|
||||
|
||||
static ssize_t _tls_pull_cb(void *p, void* data, size_t sz)
|
||||
{
|
||||
int fd = *(int *)(p);
|
||||
return recv(fd, data, sz, tls_sk_flags);
|
||||
}
|
||||
|
||||
static int tls_x509_setup_session(unsigned int flags)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = gnutls_init(&session, flags);
|
||||
if (ret != GNUTLS_E_SUCCESS) {
|
||||
tls_perror("Failed to initialize session", ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred);
|
||||
if (ret != GNUTLS_E_SUCCESS) {
|
||||
tls_perror("Failed to set session credentials", ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = gnutls_set_default_priority(session);
|
||||
if (ret != GNUTLS_E_SUCCESS) {
|
||||
tls_perror("Failed to set priority", ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
gnutls_transport_set_ptr(session, &tls_sk);
|
||||
gnutls_transport_set_push_function(session, _tls_push_cb);
|
||||
gnutls_transport_set_pull_function(session, _tls_pull_cb);
|
||||
|
||||
if (flags == GNUTLS_SERVER) {
|
||||
/* Require client certificate */
|
||||
gnutls_certificate_server_set_request(session, GNUTLS_CERT_REQUIRE);
|
||||
/* Do not advertise trusted CAs to the client */
|
||||
gnutls_certificate_send_x509_rdn_sequence(session, 1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tls_x509_init(int sockfd, bool is_server)
|
||||
{
|
||||
if (!opts.tls)
|
||||
return 0;
|
||||
|
||||
tls_sk = sockfd;
|
||||
if (tls_x509_setup_creds())
|
||||
goto err;
|
||||
if (tls_x509_setup_session(is_server ? GNUTLS_SERVER : GNUTLS_CLIENT))
|
||||
goto err;
|
||||
if (tls_handshake())
|
||||
goto err;
|
||||
if (tls_x509_verify_peer_cert())
|
||||
goto err;
|
||||
|
||||
return 0;
|
||||
err:
|
||||
tls_terminate_session();
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -37,6 +37,7 @@
|
|||
#include "page-xfer.h"
|
||||
#include "common/lock.h"
|
||||
#include "rst-malloc.h"
|
||||
#include "tls.h"
|
||||
#include "fdstore.h"
|
||||
#include "util.h"
|
||||
|
||||
|
|
@ -1469,5 +1470,7 @@ int cr_lazy_pages(bool daemon)
|
|||
|
||||
ret = handle_requests(epollfd, events, nr_fds);
|
||||
|
||||
tls_terminate_session();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue