2016-09-22 20:47:43 +02:00
/*
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 >
*/
2016-09-22 20:40:58 +02:00
# include <stdio.h>
# include <string.h>
# include <sys/socket.h>
# include <arpa/inet.h>
# include <stdbool.h>
# include <unistd.h>
# include <openssl/ssl.h>
# include <openssl/err.h>
2019-10-23 21:13:50 +02:00
# include <sys/types.h>
# include <sys/socket.h>
# include <netdb.h>
2016-09-22 20:40:58 +02:00
# include "log.h"
# include "openssl.h"
# include "util.h"
# include "msg.h"
# include "client.h"
2016-09-24 11:45:58 +02:00
# include "blacklist.h"
2019-10-23 21:13:50 +02:00
# include "keydb.h"
2019-10-23 21:54:10 +02:00
# include "uuid.h"
2019-10-25 11:08:20 +02:00
# include "udp.h"
2019-10-25 12:19:01 +02:00
# include "luks.h"
2016-09-22 20:40:58 +02:00
2019-10-23 21:13:50 +02:00
struct keyclient_t {
const struct pgmopts_client_t * opts ;
2021-06-26 23:27:57 +02:00
keydb_t * keydb ;
2019-10-23 21:13:50 +02:00
bool volume_unlocked [ MAX_VOLUMES_PER_HOST ] ;
2019-10-23 21:54:10 +02:00
unsigned char identifier [ ASCII_UUID_BUFSIZE ] ;
2019-10-25 11:08:20 +02:00
double broadcast_start_time ;
2019-10-23 21:13:50 +02:00
} ;
2019-10-23 21:54:10 +02:00
static int psk_client_callback ( SSL * ssl , const EVP_MD * md , const unsigned char * * id , size_t * idlen , SSL_SESSION * * sessptr ) {
struct keyclient_t * key_client = ( struct keyclient_t * ) SSL_get_app_data ( ssl ) ;
* id = key_client - > identifier ;
* idlen = ASCII_UUID_CHARACTER_COUNT ;
2019-10-23 22:06:47 +02:00
return openssl_tls13_psk_establish_session ( ssl , key_client - > keydb - > hosts [ 0 ] . tls_psk , PSK_SIZE_BYTES , EVP_sha256 ( ) , sessptr ) ;
2019-10-23 21:13:50 +02:00
}
2021-06-26 23:27:57 +02:00
static bool unlock_luks_volume ( const volume_entry_t * volume , const struct msg_t * unlock_msg ) {
2019-10-25 12:19:01 +02:00
bool success = true ;
char luks_passphrase [ LUKS_PASSPHRASE_TEXT_SIZE_BYTES ] ;
if ( ascii_encode ( luks_passphrase , sizeof ( luks_passphrase ) , unlock_msg - > luks_passphrase_raw , sizeof ( unlock_msg - > luks_passphrase_raw ) ) ) {
2021-06-27 09:47:59 +02:00
bool allow_discards = volume - > volume_flags & VOLUME_FLAG_ALLOW_DISCARDS ;
success = open_luks_device ( volume - > volume_uuid , volume - > devmapper_name , luks_passphrase , strlen ( luks_passphrase ) , allow_discards ) ;
2019-10-25 12:19:01 +02:00
} else {
log_msg ( LLVL_FATAL , " Failed to transcribe raw LUKS passphrase to text form. " ) ;
success = false ;
}
OPENSSL_cleanse ( luks_passphrase , sizeof ( luks_passphrase ) ) ;
return success ;
2019-10-25 11:08:20 +02:00
}
2019-10-25 12:19:01 +02:00
static bool attempt_unlock_luks_volume ( struct keyclient_t * keyclient , const struct msg_t * unlock_msg ) {
2021-06-26 23:27:57 +02:00
const host_entry_t * host = & keyclient - > keydb - > hosts [ 0 ] ;
const volume_entry_t * volume = keydb_get_volume_by_uuid ( host , unlock_msg - > volume_uuid ) ;
2019-10-25 12:19:01 +02:00
char volume_uuid_str [ ASCII_UUID_BUFSIZE ] ;
sprintf_uuid ( volume_uuid_str , unlock_msg - > volume_uuid ) ;
2019-10-25 11:08:20 +02:00
if ( ! volume ) {
2019-10-25 12:19:01 +02:00
log_msg ( LLVL_WARNING , " Keyserver provided key for unlocking volume UUID %s, but this volume is not known on the client side. " , volume_uuid_str ) ;
2019-10-25 11:08:20 +02:00
return false ;
}
2019-10-25 18:17:43 +02:00
/* This is a valid volume which we need to unlock */
2019-10-25 11:08:20 +02:00
int volume_index = keydb_get_volume_index ( host , volume ) ;
if ( volume_index ! = - 1 ) {
if ( keyclient - > opts - > no_luks ) {
keyclient - > volume_unlocked [ volume_index ] = true ;
2019-10-25 18:17:43 +02:00
# ifdef DEBUG
dump_hexline ( stderr , " Raw key: " , unlock_msg - > luks_passphrase_raw , LUKS_PASSPHRASE_RAW_SIZE_BYTES , false ) ;
# endif
2019-10-25 11:08:20 +02:00
} else {
2019-10-25 12:19:01 +02:00
if ( ! keyclient - > volume_unlocked [ volume_index ] ) {
bool success = unlock_luks_volume ( volume , unlock_msg ) ;
keyclient - > volume_unlocked [ volume_index ] = success ;
if ( ! success ) {
log_msg ( LLVL_ERROR , " Unlocking of volume %s / %s failed with the server-provided passphrase. " , volume - > devmapper_name , volume_uuid_str ) ;
return false ;
}
} else {
log_msg ( LLVL_WARNING , " Volume %s / %s already unlocked, not attemping to unlock again. " , volume - > devmapper_name , volume_uuid_str ) ;
}
2019-10-25 11:08:20 +02:00
}
} else {
log_msg ( LLVL_FATAL , " Error calculating volume offset for volume %p from base %p. " , volume , host - > volumes ) ;
return false ;
}
return true ;
}
2019-10-23 21:13:50 +02:00
static bool contact_keyserver_socket ( struct keyclient_t * keyclient , int sd ) {
struct generic_tls_ctx_t gctx ;
if ( ! create_generic_tls_context ( & gctx , false ) ) {
log_msg ( LLVL_FATAL , " Failed to create OpenSSL client context. " ) ;
return false ;
}
SSL_CTX_set_psk_use_session_callback ( gctx . ctx , psk_client_callback ) ;
SSL * ssl = SSL_new ( gctx . ctx ) ;
if ( ssl ) {
SSL_set_fd ( ssl , sd ) ;
SSL_set_app_data ( ssl , keyclient ) ;
if ( SSL_connect ( ssl ) = = 1 ) {
2019-10-23 21:54:10 +02:00
struct msg_t msg ;
while ( true ) {
int bytes_read = SSL_read ( ssl , & msg , sizeof ( msg ) ) ;
if ( bytes_read = = 0 ) {
/* Server closed the connection. */
break ;
}
if ( bytes_read ! = sizeof ( msg ) ) {
log_openssl ( LLVL_FATAL , " SSL_read returned %d bytes when we expected to read %d " , bytes_read , sizeof ( msg ) ) ;
break ;
}
2019-10-25 11:08:20 +02:00
char uuid_str [ ASCII_UUID_BUFSIZE ] ;
sprintf_uuid ( uuid_str , msg . volume_uuid ) ;
log_msg ( LLVL_TRACE , " Received LUKS key to unlock volume with UUID %s " , uuid_str ) ;
2019-10-25 12:19:01 +02:00
if ( attempt_unlock_luks_volume ( keyclient , & msg ) ) {
2019-10-25 11:08:20 +02:00
log_msg ( LLVL_DEBUG , " Successfully unlocked volume with UUID %s " , uuid_str ) ;
} else {
log_msg ( LLVL_ERROR , " Failed to unlocked volume with UUID %s " , uuid_str ) ;
2019-10-23 21:54:10 +02:00
}
}
OPENSSL_cleanse ( & msg , sizeof ( msg ) ) ;
2019-10-23 21:13:50 +02:00
} else {
log_openssl ( LLVL_FATAL , " SSL_connect failed " ) ;
}
} else {
log_openssl ( LLVL_FATAL , " Cannot establish SSL context when trying to connect to server " ) ;
}
SSL_free ( ssl ) ;
free_generic_tls_context ( & gctx ) ;
2019-10-23 20:13:25 +02:00
return true ;
}
2019-10-23 21:13:50 +02:00
static bool contact_keyserver_ipv4 ( struct keyclient_t * keyclient , struct sockaddr_in * sockaddr_in , unsigned int port ) {
sockaddr_in - > sin_port = htons ( port ) ;
int sd = socket ( sockaddr_in - > sin_family , SOCK_STREAM , 0 ) ;
if ( sd = = - 1 ) {
log_libc ( LLVL_ERROR , " Failed to create socket(3) " ) ;
return false ;
}
if ( connect ( sd , ( struct sockaddr * ) sockaddr_in , sizeof ( struct sockaddr_in ) ) = = - 1 ) {
log_libc ( LLVL_ERROR , " Failed to connect(3) to %d.%d.%d.%d:%d " , PRINTF_FORMAT_IP ( sockaddr_in ) , port ) ;
close ( sd ) ;
return false ;
}
bool success = contact_keyserver_socket ( keyclient , sd ) ;
shutdown ( sd , SHUT_RDWR ) ;
close ( sd ) ;
return success ;
}
static bool contact_keyserver_hostname ( struct keyclient_t * keyclient , const char * hostname ) {
struct addrinfo hints = {
. ai_family = AF_INET ,
. ai_socktype = SOCK_STREAM ,
} ;
struct addrinfo * result ;
int resolve_result = getaddrinfo ( hostname , NULL , & hints , & result ) ;
if ( resolve_result ) {
log_msg ( LLVL_ERROR , " Failed to resolve hostname %s using getaddrinfo(3): %s " , hostname , gai_strerror ( resolve_result ) ) ;
return false ;
}
if ( result - > ai_addr - > sa_family ! = AF_INET ) {
freeaddrinfo ( result ) ;
log_msg ( LLVL_ERROR , " getaddrinfo(3) returned non-IPv4 entry " ) ;
return false ;
}
struct sockaddr_in * sin_address = ( struct sockaddr_in * ) result - > ai_addr ;
2019-10-23 21:54:10 +02:00
log_msg ( LLVL_TRACE , " Resolved %s to %d.%d.%d.%d " , hostname , PRINTF_FORMAT_IP ( sin_address ) ) ;
2019-10-23 21:13:50 +02:00
bool success = contact_keyserver_ipv4 ( keyclient , sin_address , keyclient - > opts - > port ) ;
freeaddrinfo ( result ) ;
return success ;
}
2019-10-25 12:19:01 +02:00
static unsigned int locked_volume_count ( struct keyclient_t * keyclient ) {
unsigned int count = 0 ;
2019-10-25 11:08:20 +02:00
const unsigned int volume_count = keyclient - > keydb - > hosts [ 0 ] . volume_count ;
for ( unsigned int i = 0 ; i < volume_count ; i + + ) {
if ( ! keyclient - > volume_unlocked [ i ] ) {
2019-10-25 12:19:01 +02:00
count + + ;
2019-10-23 22:29:40 +02:00
}
}
2019-10-25 12:19:01 +02:00
return count ;
}
static bool all_volumes_unlocked ( struct keyclient_t * keyclient ) {
return locked_volume_count ( keyclient ) = = 0 ;
2019-10-23 22:29:40 +02:00
}
2021-06-27 13:29:43 +02:00
static unsigned int determine_timeout ( struct keyclient_t * keyclient ) {
2021-06-27 12:51:51 +02:00
unsigned int client_timeout_secs = 0 ;
2019-10-25 11:08:20 +02:00
if ( keyclient - > opts - > timeout_seconds ) {
2021-06-27 12:51:51 +02:00
/* Command line always has precedence */
client_timeout_secs = keyclient - > opts - > timeout_seconds ;
} else {
/* Alternatively, take the one in the configuration file */
client_timeout_secs = keyclient - > keydb - > hosts [ 0 ] . client_default_timeout_secs ;
}
2021-06-27 13:29:43 +02:00
return client_timeout_secs ;
}
2021-06-27 12:51:51 +02:00
2021-06-27 13:29:43 +02:00
static bool abort_searching_for_keyserver ( struct keyclient_t * keyclient ) {
if ( all_volumes_unlocked ( keyclient ) ) {
log_msg ( LLVL_DEBUG , " All volumes unlocked successfully. " ) ;
return true ;
}
2021-06-27 12:51:51 +02:00
2021-06-27 13:29:43 +02:00
unsigned int client_timeout_secs = determine_timeout ( keyclient ) ;
2021-06-27 12:51:51 +02:00
if ( client_timeout_secs ) {
2019-10-25 11:08:20 +02:00
double time_passed = now ( ) - keyclient - > broadcast_start_time ;
2021-06-27 12:51:51 +02:00
if ( time_passed > = client_timeout_secs ) {
log_msg ( LLVL_WARNING , " Could not unlock all volumes after %u seconds, giving up. %d volumes still locked. " , client_timeout_secs , locked_volume_count ( keyclient ) ) ;
2019-10-25 11:08:20 +02:00
return true ;
}
2019-10-23 22:29:40 +02:00
}
2019-10-25 11:08:20 +02:00
return false ;
}
2019-10-23 22:29:40 +02:00
static bool broadcast_for_keyserver ( struct keyclient_t * keyclient ) {
2021-06-27 13:29:43 +02:00
{
unsigned int client_timeout_secs = determine_timeout ( keyclient ) ;
if ( client_timeout_secs ) {
log_msg ( LLVL_DEBUG , " Searching luksrku keyserver, will give up after %u seconds " , client_timeout_secs ) ;
} else {
log_msg ( LLVL_DEBUG , " Searching luksrku keyserver, will not give up until all volumes unlocked " ) ;
}
}
2019-10-25 11:08:20 +02:00
int sd = create_udp_socket ( 0 , true , 1000 ) ;
2019-10-23 22:29:40 +02:00
if ( sd = = - 1 ) {
return false ;
}
2021-06-27 13:29:43 +02:00
2019-10-25 11:08:20 +02:00
keyclient - > broadcast_start_time = now ( ) ;
2019-10-23 22:29:40 +02:00
struct udp_query_t query ;
memcpy ( query . magic , UDP_MESSAGE_MAGIC , sizeof ( query . magic ) ) ;
memcpy ( query . host_uuid , keyclient - > keydb - > hosts [ 0 ] . host_uuid , 16 ) ;
while ( true ) {
2019-10-25 18:02:51 +02:00
log_msg ( LLVL_TRACE , " Broadcasting search for luksrku keyserver " ) ;
2019-10-23 22:29:40 +02:00
send_udp_broadcast_message ( sd , keyclient - > opts - > port , & query , sizeof ( query ) ) ;
2019-10-25 11:08:20 +02:00
struct sockaddr_in src = {
. sin_family = AF_INET ,
. sin_port = htons ( keyclient - > opts - > port ) ,
. sin_addr . s_addr = htonl ( INADDR_ANY ) ,
} ;
struct udp_response_t response ;
if ( wait_udp_response ( sd , & response , & src ) ) {
2019-10-25 12:19:01 +02:00
if ( ! is_ip_blacklisted ( src . sin_addr . s_addr ) ) {
log_msg ( LLVL_INFO , " Keyserver found at %d.%d.%d.%d " , PRINTF_FORMAT_IP ( & src ) ) ;
blacklist_ip ( src . sin_addr . s_addr , BLACKLIST_TIMEOUT_CLIENT ) ;
if ( ! contact_keyserver_ipv4 ( keyclient , & src , keyclient - > opts - > port ) ) {
log_msg ( LLVL_WARNING , " Keyserver announced at %d.%d.%d.%d, but connection to it failed. " , PRINTF_FORMAT_IP ( & src ) ) ;
}
} else {
log_msg ( LLVL_DEBUG , " Potential keyserver at %d.%d.%d.%d ignored, blacklist in effect. " , PRINTF_FORMAT_IP ( & src ) ) ;
2019-10-25 11:08:20 +02:00
}
}
if ( abort_searching_for_keyserver ( keyclient ) ) {
break ;
}
2019-10-23 22:29:40 +02:00
}
return true ;
}
2019-10-23 21:13:50 +02:00
bool keyclient_start ( const struct pgmopts_client_t * opts ) {
/* Load key database first */
struct keyclient_t keyclient = {
. opts = opts ,
} ;
bool success = true ;
do {
keyclient . keydb = keydb_read ( opts - > filename ) ;
if ( ! keyclient . keydb ) {
log_msg ( LLVL_FATAL , " Failed to load key database: %s " , opts - > filename ) ;
success = false ;
break ;
}
if ( keyclient . keydb - > server_database ) {
log_msg ( LLVL_FATAL , " Not an exported key database: %s -- this database contains LUKS passphrases, refusing to work with it! " , opts - > filename ) ;
success = false ;
break ;
}
if ( keyclient . keydb - > host_count ! = 1 ) {
log_msg ( LLVL_FATAL , " Host count %d in %s -- expected exactly one host entry for an exported database. " , keyclient . keydb - > host_count , opts - > filename ) ;
success = false ;
break ;
}
2021-06-26 23:27:57 +02:00
host_entry_t * host = & keyclient . keydb - > hosts [ 0 ] ;
2019-10-23 21:13:50 +02:00
if ( host - > volume_count = = 0 ) {
log_msg ( LLVL_FATAL , " No volumes found in exported database %s. " , opts - > filename ) ;
success = false ;
break ;
}
2019-10-25 12:19:01 +02:00
/* Determine which of these volumes are already unlocked */
for ( unsigned int i = 0 ; i < host - > volume_count ; i + + ) {
keyclient . volume_unlocked [ i ] = is_luks_device_opened ( host - > volumes [ i ] . devmapper_name ) ;
}
if ( all_volumes_unlocked ( & keyclient ) ) {
log_msg ( LLVL_INFO , " All %u volumes are unlocked already, not contacting luksrku key server. " , host - > volume_count ) ;
break ;
} else {
log_msg ( LLVL_DEBUG , " %u of %u volumes are currently locked. " , locked_volume_count ( & keyclient ) , host - > volume_count ) ;
}
2019-10-23 21:54:10 +02:00
/* Transcribe the host UUID to ASCII so we only have to do this once */
sprintf_uuid ( ( char * ) keyclient . identifier , host - > host_uuid ) ;
2019-10-23 21:13:50 +02:00
if ( opts - > hostname ) {
if ( ! contact_keyserver_hostname ( & keyclient , opts - > hostname ) ) {
log_msg ( LLVL_ERROR , " Failed to contact key server: %s " , opts - > hostname ) ;
success = false ;
break ;
}
} else {
2019-10-23 22:29:40 +02:00
if ( ! broadcast_for_keyserver ( & keyclient ) ) {
log_msg ( LLVL_ERROR , " Failed to find key server using UDP broadcast. " ) ;
success = false ;
break ;
}
2019-10-23 21:13:50 +02:00
}
} while ( false ) ;
if ( keyclient . keydb ) {
keydb_free ( keyclient . keydb ) ;
}
return success ;
}