From 76a41209b0942fcc76508f1bdee7e7119c79f625 Mon Sep 17 00:00:00 2001 From: Radostin Stoyanov Date: Sun, 31 Mar 2019 12:05:22 +0100 Subject: [PATCH] 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 --port --tls [dst]# criu dump --page-server --address --port \ -t -D --tls Lazy migration: [src]# criu dump --lazy-pages --port -t -D --tls [dst]# criu lazy-pages --page-server --address --port \ -D --tls [dst]# criu restore -D --lazy-pages Signed-off-by: Radostin Stoyanov --- Documentation/criu.txt | 27 +++ Makefile | 1 + criu/Makefile | 3 +- criu/Makefile.crtools | 1 + criu/config.c | 24 +++ criu/crtools.c | 5 + criu/include/cr_options.h | 5 + criu/include/tls.h | 26 +++ criu/page-xfer.c | 119 +++++++++---- criu/tls.c | 366 ++++++++++++++++++++++++++++++++++++++ criu/uffd.c | 3 + 11 files changed, 546 insertions(+), 34 deletions(-) create mode 100644 criu/include/tls.h create mode 100644 criu/tls.c diff --git a/Documentation/criu.txt b/Documentation/criu.txt index 6111c3baf..94fc5428a 100644 --- a/Documentation/criu.txt +++ b/Documentation/criu.txt @@ -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. diff --git a/Makefile b/Makefile index 09cf2406a..9d83862d1 100644 --- a/Makefile +++ b/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 # diff --git a/criu/Makefile b/criu/Makefile index 3de6eb217..4134e5052 100644 --- a/criu/Makefile +++ b/criu/Makefile @@ -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/"' diff --git a/criu/Makefile.crtools b/criu/Makefile.crtools index 383ed1940..4588ea5b8 100644 --- a/criu/Makefile.crtools +++ b/criu/Makefile.crtools @@ -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 diff --git a/criu/config.c b/criu/config.c index bcf5176d9..bfdd6c658 100644 --- a/criu/config.c +++ b/criu/config.c @@ -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; diff --git a/criu/crtools.c b/criu/crtools.c index 6c83b27da..a07df064c 100644 --- a/criu/crtools.c +++ b/criu/crtools.c @@ -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" diff --git a/criu/include/cr_options.h b/criu/include/cr_options.h index 9f152fac0..fcba278e0 100644 --- a/criu/include/cr_options.h +++ b/criu/include/cr_options.h @@ -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; diff --git a/criu/include/tls.h b/criu/include/tls.h new file mode 100644 index 000000000..aa2517887 --- /dev/null +++ b/criu/include/tls.h @@ -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__ */ diff --git a/criu/page-xfer.c b/criu/page-xfer.c index 8868ed226..f74716100 100644 --- a/criu/page-xfer.c +++ b/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; diff --git a/criu/tls.c b/criu/tls.c new file mode 100644 index 000000000..a6bf25db4 --- /dev/null +++ b/criu/tls.c @@ -0,0 +1,366 @@ +#include +#include +#include +#include + +#include + +#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; +} diff --git a/criu/uffd.c b/criu/uffd.c index 6699cb14a..5c1e32184 100644 --- a/criu/uffd.c +++ b/criu/uffd.c @@ -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; }