luksrku/server.c
Johannes Bauer 0d4d2220b2 Implemented unlock cnt and blacklist
Can now unlock a specified number of hosts as specified on the command
line (e.g., if you want a luksrku client run indefinitely) and also used
the already implemented blacklisting functionality (i.e., if an
unlocking is unsuccessful, it is retried in 120 seconds, not
immediately, as not to spam servers with illegal credentials).
2016-09-24 11:45:58 +02:00

295 lines
8.5 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 <openssl/ssl.h>
#include <openssl/err.h>
#include "log.h"
#include "openssl.h"
#include "global.h"
#include "keyfile.h"
#include "msg.h"
#include "util.h"
#include "cmdline.h"
#include "server.h"
#include "luks.h"
static const struct keyentry_t *server_key;
static unsigned int psk_server_callback(SSL *ssl, const char *identity, unsigned char *psk, unsigned int max_psk_len) {
if (max_psk_len < PSK_SIZE_BYTES) {
log_msg(LLVL_FATAL, "Server error: max_psk_len too small.");
return 0;
}
if (strcmp(identity, CLIENT_PSK_IDENTITY)) {
log_msg(LLVL_FATAL, "Server error: client identity '%s' unexpected (expected '%s').", identity, CLIENT_PSK_IDENTITY);
return 0;
}
memcpy(psk, server_key->psk, PSK_SIZE_BYTES);
return PSK_SIZE_BYTES;
}
static int create_tcp_socket(int port) {
int s;
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0) {
log_libc(LLVL_ERROR, "Unable to create TCP socket(2)");
return -1;
}
{
int value = 1;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));
}
if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
log_libc(LLVL_ERROR, "Unable to bind(2) socket");
return -1;
}
if (listen(s, 1) < 0) {
log_libc(LLVL_ERROR, "Unable to listen(2) on socket");
return -1;
}
return s;
}
/* Wait for the socket to become acceptable or time out after given number of
* milliseconds. Return true if acceptable socket is present or false if
* timeout occured. */
static bool socket_wait_acceptable(int sd, int timeout_millis) {
struct timeval tv;
memset(&tv, 0, sizeof(tv));
tv.tv_usec = timeout_millis * 1000;
fd_set fds;
FD_ZERO(&fds);
FD_SET(sd, &fds);
int result = select(sd + 1, &fds, NULL, NULL, &tv);
return result != 0;
}
static int create_udp_socket(void) {
int sd = socket(AF_INET, SOCK_DGRAM, 0);
if (sd < 0) {
log_libc(LLVL_ERROR, "Unable to create UDP server socket(2)");
return -1;
}
{
int value = 1;
if (setsockopt(sd, SOL_SOCKET, SO_BROADCAST, &value, sizeof(value))) {
log_libc(LLVL_ERROR, "Unable to set UDP socket in broadcast mode using setsockopt(2)");
close(sd);
return -1;
}
}
return sd;
}
static bool send_udp_broadcast_message(int sd, int port, const void *data, int length) {
struct sockaddr_in destination;
memset(&destination, 0, sizeof(struct sockaddr_in));
destination.sin_family = AF_INET;
destination.sin_port = htons(port);
destination.sin_addr.s_addr = htonl(INADDR_BROADCAST);
if (sendto(sd, data, length, 0, (struct sockaddr *)&destination, sizeof(struct sockaddr_in)) < 0) {
log_libc(LLVL_ERROR, "Unable to sendto(2)");
return false;
}
return true;
}
static bool announce_waiting_message(int sd, int port, const struct keyentry_t *key) {
struct announcement_t msg;
const uint8_t magic[16] = CLIENT_ANNOUNCE_MAGIC;
memset(&msg, 0, sizeof(msg));
memcpy(msg.magic, magic, 16);
memcpy(msg.host_uuid, key->host_uuid, 16);
return send_udp_broadcast_message(sd, port, &msg, sizeof(msg));
}
static bool unlock_disk(const struct diskentry_t *disk, const uint8_t *passphrase, int passphrase_length) {
char ascii_uuid[40];
sprintf_uuid(ascii_uuid, disk->disk_uuid);
log_msg(LLVL_INFO, "Trying to unlock disk %s with UUID %s", disk->devmapper_name, ascii_uuid);
#ifdef DEBUG
fprintf(stderr, "Using key: ");
dump_hex(stderr, passphrase, passphrase_length);
fprintf(stderr, "\n");
#endif
if (is_luks_device_opened(disk->devmapper_name)) {
log_msg(LLVL_INFO, "Disk %s already unlocked, nothing to do.", disk->devmapper_name, ascii_uuid);
return true;
}
return open_luks_device_pw(disk->disk_uuid, disk->devmapper_name, passphrase, passphrase_length);
}
static bool all_disks_unlocked(const struct keyentry_t *keyentry) {
for (int i = 0; i < MAX_DISKS_PER_HOST; i++) {
if (keyentry->disk_keys[i].occupied && !is_luks_device_opened(keyentry->disk_keys[i].devmapper_name)) {
return false;
}
}
return true;
}
bool dtls_server(const struct keyentry_t *key, const struct options_t *options) {
if (all_disks_unlocked(key)) {
log_msg(LLVL_INFO, "Starting of server not necessary, all disks already unlocked.");
return true;
}
struct generic_ssl_ctx_t gctx;
create_generic_ssl_context(&gctx, true);
server_key = key;
{
char ascii_host_uuid[40];
sprintf_uuid(ascii_host_uuid, key->host_uuid);
SSL_CTX_use_psk_identity_hint(gctx.ctx, ascii_host_uuid);
}
SSL_CTX_set_psk_server_callback(gctx.ctx, psk_server_callback);
int tcp_sock = create_tcp_socket(options->port);
if (tcp_sock == -1) {
log_msg(LLVL_ERROR, "Cannot start server without server socket.");
free_generic_ssl_context(&gctx);
return false;
}
int udp_sock = create_udp_socket();
if (tcp_sock == -1) {
log_msg(LLVL_ERROR, "Cannot broadcast without announcement UDP socket.");
close(tcp_sock);
free_generic_ssl_context(&gctx);
return false;
}
log_msg(LLVL_DEBUG, "Created listening socket on port %d", options->port);
int tries = 0;
while ((options->unlock_cnt == 0) || (tries < options->unlock_cnt)) {
struct sockaddr_in addr;
unsigned int len = sizeof(addr);
log_msg(LLVL_DEBUG, "Waiting for incoming connection...");
announce_waiting_message(udp_sock, options->port, key);
if (!socket_wait_acceptable(tcp_sock, WAITING_MESSAGE_BROADCAST_INTERVAL_MILLISECONDS)) {
/* No connection pending, timeout. */
continue;
}
log_msg(LLVL_DEBUG, "Trying to accept connection...");
int client = accept(tcp_sock, (struct sockaddr*)&addr, &len);
if (client < 0) {
log_libc(LLVL_ERROR, "Unable to accept(2)");
close(udp_sock);
close(tcp_sock);
free_generic_ssl_context(&gctx);
return false;
}
SSL *ssl = SSL_new(gctx.ctx);
SSL_set_fd(ssl, client);
if (SSL_accept(ssl) <= 0) {
ERR_print_errors_fp(stderr);
} else {
tries++;
log_msg(LLVL_DEBUG, "Client connected, waiting for data...");
while (true) {
struct msg_t msg;
int rxlen = SSL_read(ssl, &msg, sizeof(msg));
if (rxlen == 0) {
/* Client severed the connection */
break;
}
if (rxlen != sizeof(msg)) {
log_msg(LLVL_ERROR, "Truncated message (%d bytes) received, terminating connection. Expected %d bytes.", rxlen, sizeof(msg));
break;
}
msg_to_hbo(&msg);
if ((msg.passphrase_length == 0) || (msg.passphrase_length > MAX_PASSPHRASE_LENGTH)) {
log_msg(LLVL_FATAL, "Client sent malformed message indicating illegal passphrase length of %d bytes. Aborting connection.", msg.passphrase_length);
break;
}
/* Now check if this is one of they keys we're actually looking for */
bool found = false;
for (int i = 0; i < MAX_DISKS_PER_HOST; i++) {
if (!memcmp(key->disk_keys[i].disk_uuid, msg.disk_uuid, 16)) {
bool success = unlock_disk(&key->disk_keys[i], msg.passphrase, msg.passphrase_length);
log_msg(LLVL_DEBUG, "Unlocking of disk was %s", success ? "successful" : "unsuccessful");
found = true;
break;
}
}
if (!found) {
char ascii_uuid[40];
sprintf_uuid(ascii_uuid, msg.disk_uuid);
log_msg(LLVL_INFO, "Client sent passphrase for UUID %s; we were not expecting it. Ignored.", ascii_uuid);
}
}
}
SSL_free(ssl);
close(client);
/* Connection closed */
if (all_disks_unlocked(key)) {
log_msg(LLVL_INFO, "All disks successfully unlocked.");
break;
} else {
log_msg(LLVL_DEBUG, "At least one disk remains locked after communication.");
}
}
close(udp_sock);
close(tcp_sock);
free_generic_ssl_context(&gctx);
return true;
}