luksrku/server.c
Johannes Bauer 3478fa4555 Unlocking LUKS volumes works
First complete technical round-trip complete, can unlock the LUKS
volumes described in the server/client databases successfully.
2019-10-25 12:19:01 +02:00

294 lines
8.2 KiB
C

/*
luksrku - Tool to remotely unlock LUKS disks using TLS.
Copyright (C) 2016-2016 Johannes Bauer
This file is part of luksrku.
luksrku is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; this program is ONLY licensed under
version 3 of the License, later versions are explicitly excluded.
luksrku 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with luksrku; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Johannes Bauer <JohannesBauer@gmx.de>
*/
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <signal.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include "log.h"
#include "openssl.h"
#include "global.h"
#include "msg.h"
#include "util.h"
#include "server.h"
#include "luks.h"
#include "pgmopts.h"
#include "uuid.h"
#include "thread.h"
#include "keydb.h"
#include "signals.h"
#include "udp.h"
#include "blacklist.h"
struct keyserver_t {
struct keydb_t* keydb;
struct generic_tls_ctx_t gctx;
const struct pgmopts_server_t *opts;
int tcp_sd, udp_sd;
};
struct client_thread_ctx_t {
struct generic_tls_ctx_t *gctx;
const struct keydb_t *keydb;
const struct host_entry_t *host;
int fd;
};
struct udp_listen_thread_ctx_t {
const struct keydb_t *keydb;
int udp_sd;
unsigned int port;
};
static int create_tcp_server_socket(int port) {
int sd = socket(AF_INET, SOCK_STREAM, 0);
if (sd < 0) {
log_libc(LLVL_ERROR, "Unable to create TCP socket(2)");
return -1;
}
{
int value = 1;
setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));
}
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(port),
.sin_addr.s_addr = htonl(INADDR_ANY),
};
if (bind(sd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
log_libc(LLVL_ERROR, "Unable to bind(2) socket");
return -1;
}
if (listen(sd, 1) < 0) {
log_libc(LLVL_ERROR, "Unable to listen(2) on socket");
return -1;
}
return sd;
}
static int psk_server_callback(SSL *ssl, const unsigned char *identity, size_t identity_len, SSL_SESSION **sessptr) {
struct client_thread_ctx_t *ctx = (struct client_thread_ctx_t*)SSL_get_app_data(ssl);
if (identity_len != ASCII_UUID_CHARACTER_COUNT) {
log_msg(LLVL_WARNING, "Received client identity of length %d, cannot be a UUID.", identity_len);
return 0;
}
char uuid_str[ASCII_UUID_BUFSIZE];
memcpy(uuid_str, identity, ASCII_UUID_CHARACTER_COUNT);
uuid_str[ASCII_UUID_CHARACTER_COUNT] = 0;
if (!is_valid_uuid(uuid_str)) {
log_msg(LLVL_WARNING, "Received client identity of length %d, but not a valid UUID.", identity_len);
return 0;
}
uint8_t uuid[16];
if (!parse_uuid(uuid, uuid_str)) {
log_msg(LLVL_ERROR, "Failed to parse valid UUID.");
return 0;
}
ctx->host = keydb_get_host_by_uuid(ctx->keydb, uuid);
if (!ctx->host) {
log_msg(LLVL_WARNING, "Client connected with client UUID %s, but not present in key database.", uuid_str);
return 0;
}
return openssl_tls13_psk_establish_session(ssl, ctx->host->tls_psk, PSK_SIZE_BYTES, EVP_sha256(), sessptr);
}
static void client_handler_thread(void *vctx) {
struct client_thread_ctx_t *client = (struct client_thread_ctx_t*)vctx;
SSL *ssl = SSL_new(client->gctx->ctx);
if (ssl) {
SSL_set_fd(ssl, client->fd);
SSL_set_app_data(ssl, client);
if (SSL_accept(ssl) <= 0) {
ERR_print_errors_fp(stderr);
} else {
if (client->host) {
log_msg(LLVL_DEBUG, "Client \"%s\" connected, sending unlock data for %d volumes.", client->host->host_name, client->host->volume_count);
for (unsigned int i = 0; i < client->host->volume_count; i++) {
const struct volume_entry_t *volume = &client->host->volumes[i];
struct msg_t msg = { 0 };
memcpy(msg.volume_uuid, volume->volume_uuid, 16);
memcpy(msg.luks_passphrase_raw, volume->luks_passphrase_raw, LUKS_PASSPHRASE_RAW_SIZE_BYTES);
int txlen = SSL_write(ssl, &msg, sizeof(msg));
OPENSSL_cleanse(&msg, sizeof(msg));
if (txlen != sizeof(msg)) {
log_msg(LLVL_WARNING, "Tried to send message of %d bytes, but sent %d. Severing connection to client.", sizeof(msg), txlen);
break;
}
}
} else {
log_msg(LLVL_FATAL, "Client connected, but no host set.");
}
}
} else {
log_openssl(LLVL_FATAL, "Cannot establish SSL context for connecting client");
}
SSL_free(ssl);
shutdown(client->fd, SHUT_RDWR);
close(client->fd);
}
static void udp_handler_thread(void *vctx) {
struct udp_listen_thread_ctx_t *client = (struct udp_listen_thread_ctx_t*)vctx;
while (true) {
struct udp_query_t rx_msg;
struct sockaddr_in origin;
if (!wait_udp_query(client->udp_sd, &rx_msg, &origin)) {
continue;
}
log_msg(LLVL_TRACE, "Recevied UDP query message from %d.%d.%d.%d:%d", PRINTF_FORMAT_IP(&origin), ntohs(origin.sin_port));
/* Ensure that we only reply to this host once every minute */
const uint32_t ipv4 = origin.sin_addr.s_addr;
if (is_ip_blacklisted(ipv4)) {
continue;
}
blacklist_ip(ipv4, BLACKLIST_TIMEOUT_SERVER);
/* Check if we have this host in our database */
if (keydb_get_host_by_uuid(client->keydb, rx_msg.host_uuid)) {
/* Yes, it is. Notify the client who's asking that we have their key. */
struct udp_response_t tx_msg;
memcpy(tx_msg.magic, UDP_MESSAGE_MAGIC, UDP_MESSAGE_MAGIC_SIZE);
send_udp_message(client->udp_sd, &origin, &tx_msg, sizeof(tx_msg), true);
}
}
}
bool keyserver_start(const struct pgmopts_server_t *opts) {
bool success = true;
struct keyserver_t keyserver = {
.opts = opts,
.tcp_sd = -1,
.udp_sd = -1,
};
do {
/* We ignore SIGPIPE or the server will die when clients disconnect suddenly */
ignore_signal(SIGPIPE);
/* Load key database first */
keyserver.keydb = keydb_read(opts->filename);
if (!keyserver.keydb) {
log_msg(LLVL_FATAL, "Failed to load key database: %s", opts->filename);
success = false;
break;
}
if (!keyserver.keydb->server_database) {
log_msg(LLVL_FATAL, "Not a server key database: %s", opts->filename);
success = false;
break;
}
if (!create_generic_tls_context(&keyserver.gctx, true)) {
log_msg(LLVL_FATAL, "Failed to create OpenSSL server context.");
success = false;
break;
}
SSL_CTX_set_psk_find_session_callback(keyserver.gctx.ctx, psk_server_callback);
keyserver.tcp_sd = create_tcp_server_socket(opts->port);
if (keyserver.tcp_sd == -1) {
log_msg(LLVL_ERROR, "Cannot start server without server socket.");
success = false;
break;
}
if (opts->answer_udp_queries) {
keyserver.udp_sd = create_udp_socket(opts->port, false, 1000);
if (keyserver.udp_sd == -1) {
success = false;
break;
}
struct udp_listen_thread_ctx_t udp_thread_ctx = {
.keydb = keyserver.keydb,
.udp_sd = keyserver.udp_sd,
.port = keyserver.opts->port,
};
if (!pthread_create_detached_thread(udp_handler_thread, &udp_thread_ctx, sizeof(udp_thread_ctx))) {
log_libc(LLVL_FATAL, "Unable to create detached thread for UDP messages.");
success = false;
break;
}
}
while (true) {
struct sockaddr_in addr;
unsigned int len = sizeof(addr);
int client = accept(keyserver.tcp_sd, (struct sockaddr*)&addr, &len);
if (client < 0) {
log_libc(LLVL_ERROR, "Unable to accept(2)");
success = false;
break;
}
/* Client has connected, fire up client thread. */
struct client_thread_ctx_t client_ctx = {
.gctx = &keyserver.gctx,
.keydb = keyserver.keydb,
.fd = client,
};
if (!pthread_create_detached_thread(client_handler_thread, &client_ctx, sizeof(client_ctx))) {
log_libc(LLVL_FATAL, "Unable to create detached thread for client.");
success = false;
break;
}
}
} while (false);
if (keyserver.udp_sd != -1) {
close(keyserver.udp_sd);
}
if (keyserver.tcp_sd != -1) {
close(keyserver.tcp_sd);
}
free_generic_tls_context(&keyserver.gctx);
keydb_free(keyserver.keydb);
return success;
}