diff --git a/README.md b/README.md index 0ab65db..8f5eea8 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,8 @@ For more information on how to generate these files, please consult the official [Docker documentation](https://docs.docker.com/engine/security/protect-access/). The files can be uploaded to the device using HTTP. -The dockerd service will restart, or try to start, after each HTTP POST request. +The request will be rejected if the file being uploaded has the incorrect header or footer for that file type. +The dockerd service will restart, or try to start, after each successful HTTP POST request. ```sh curl --anyauth -u "root:$DEVICE_PASSWORD" -F file=@ca.pem -X POST \ diff --git a/app/http_request.c b/app/http_request.c index 1f6cd8c..df7a01e 100644 --- a/app/http_request.c +++ b/app/http_request.c @@ -2,6 +2,7 @@ #include "app_paths.h" #include "fcgi_write_file_from_stream.h" #include "log.h" +#include "tls.h" #include #include @@ -81,7 +82,11 @@ post_request(FCGX_Request* request, const char* filename, restart_dockerd_t rest response_msg(request, HTTP_422_UNPROCESSABLE_CONTENT, "Upload to temporary file failed."); return; } - if (!copy_to_localdata(temp_file, filename)) + if (!tls_file_has_correct_format(filename, temp_file)) { + g_autofree char* msg = + g_strdup_printf("File is not a valid %s.", tls_file_description(filename)); + response_msg(request, HTTP_400_BAD_REQUEST, msg); + } else if (!copy_to_localdata(temp_file, filename)) response_msg(request, HTTP_500_INTERNAL_SERVER_ERROR, "Failed to copy file to localdata"); else { response_204_no_content(request); diff --git a/app/tls.c b/app/tls.c index 2b6f1e2..6039bc1 100644 --- a/app/tls.c +++ b/app/tls.c @@ -2,6 +2,7 @@ #include "app_paths.h" #include "log.h" #include +#include #include #define TLS_CERT_PATH APP_LOCALDATA @@ -18,6 +19,17 @@ static struct cert tls_certs[] = {{"--tlscacert", "ca.pem", "CA certificate"}, #define NUM_TLS_CERTS (sizeof(tls_certs) / sizeof(tls_certs[0])) +#define BEGIN(x) "-----BEGIN " x "-----\n" +#define END(x) "-----END " x "-----\n" +#define CERTIFICATE "CERTIFICATE" +#define PRIVATE_KEY "PRIVATE KEY" +#define RSA_PRIVATE_KEY "RSA PRIVATE KEY" + +// Filename is assumed to be one of those listed in tls_certs[]. +static bool is_key_file(const char* filename) { + return strstr(filename, "key"); +} + static bool cert_file_exists(const struct cert* tls_cert) { g_autofree char* full_path = g_strdup_printf("%s/%s", TLS_CERT_PATH, tls_cert->filename); return access(full_path, F_OK) == 0; @@ -39,6 +51,13 @@ void tls_log_missing_cert_warnings(void) { tls_certs[i].filename); } +const char* tls_file_description(const char* filename) { + for (size_t i = 0; i < NUM_TLS_CERTS; ++i) + if (strcmp(filename, tls_certs[i].filename) == 0) + return tls_certs[i].description; + return NULL; +} + const char* tls_args_for_dockerd(void) { static char args[512]; // Too small buffer will cause truncated options, nothing more. const char* end = args + sizeof(args); @@ -53,3 +72,52 @@ const char* tls_args_for_dockerd(void) { tls_certs[i].filename); return args; } + +static bool read_bytes_from(FILE* fp, int whence, char* buffer, int num_bytes) { + const long offset = whence == SEEK_SET ? 0 : -num_bytes; + if (fseek(fp, offset, whence) != 0) { + log_error("Could not reposition stream to %s%ld: %s", + whence == SEEK_SET ? "SEEK_SET+" : "SEEK_END", + offset, + strerror(errno)); + return false; + } + if (fread(buffer, num_bytes, 1, fp) != 1) { + log_error("Could not read %d bytes: %s", num_bytes, strerror(errno)); + return false; + } + return true; +} + +static bool is_file_section_equal_to(FILE* fp, int whence, const char* section) { + char buffer[128]; + int to_read = strlen(section); + if (!read_bytes_from(fp, whence, buffer, to_read)) + return false; + buffer[to_read] = '\0'; + return strncmp(buffer, section, to_read) == 0; +} + +static bool has_header_and_footer(FILE* fp, const char* header, const char* footer) { + return is_file_section_equal_to(fp, SEEK_SET, header) && + is_file_section_equal_to(fp, SEEK_END, footer); +} + +bool tls_file_has_correct_format(const char* filename, const char* path_to_file) { + FILE* fp = fopen(path_to_file, "r"); + if (!fp) { + log_error("Could not read %s", path_to_file); + return false; + } + + bool correct = is_key_file(filename) + ? (has_header_and_footer(fp, BEGIN(PRIVATE_KEY), END(PRIVATE_KEY)) || + has_header_and_footer(fp, BEGIN(RSA_PRIVATE_KEY), END(RSA_PRIVATE_KEY))) + : has_header_and_footer(fp, BEGIN(CERTIFICATE), END(CERTIFICATE)); + if (!correct) + log_error("%s does not contain the headers and footers for a %s.", + path_to_file, + tls_file_description(filename)); + fclose(fp); + return correct; +} diff --git a/app/tls.h b/app/tls.h index 60e3203..2d951d3 100644 --- a/app/tls.h +++ b/app/tls.h @@ -3,4 +3,6 @@ bool tls_missing_certs(void); void tls_log_missing_cert_warnings(void); +const char* tls_file_description(const char* filename); const char* tls_args_for_dockerd(void); +bool tls_file_has_correct_format(const char* filename, const char* path_to_file);