Verified Commit 61f4b7e4 authored by Karel Koci's avatar Karel Koci 🤘

Implement URI in C

This is C implementation of URI with configuration inheritance and
ability to have relative URIs.
parent 37d7ac3d
......@@ -157,9 +157,15 @@ sig;;
URI. The option has effect only if `pubkey` is set so signature checking is
done. In default it's set to `nil`.
URIs specified in these verification options are not verified (default values, not
inherited ones, are used). Because of that it is suggested to used only
trusted/secure URIs for that purpose. Suggested are `file://` and `data://`.
IMPORTANT: URIs specified in these verification options are not verified (default
values, not inherited ones, are used). Because of that it is suggested and
required to use only trusted/secure local URIs for that purpose. That means:
`file://` and `data://`.
TIP: URIs specified in these verification options has to be valid but the resource
don't have to be available (such as missing file). That is ok and it does not
cause error directly. This means that you can provide keys and certificates that
might not be installed in configuration without worrying about their existence.
NOTE::
Another option `verification` exist. It was originally used for verification
......
......@@ -26,6 +26,7 @@ libupdater_MODULES := \
events \
subprocess \
download \
uri \
journal \
locks \
picosat \
......@@ -42,9 +43,9 @@ endif
libupdater_MODULES_3RDPARTY := picosat-965/picosat
libupdater_PKG_CONFIGS := $(LUA_NAME) libevent libcurl libcrypto
libupdater_PKG_CONFIGS := $(LUA_NAME) libevent libcurl libcrypto liburiparser
# Workaround, lua.pc doesn't containd -ldl, even when it uses dlopen
libupdater_SO_LIBS += dl
libupdater_SO_LIBS += dl b64
LIB_DOCS := \
journal \
......
......@@ -75,8 +75,6 @@ static const char *opt_help[COT_LAST] = {
"--output=<file> Put the output to given file.\n",
[COT_EXCLUDE] =
"--exclude=<name> Exclude this from output.\n",
[COT_USIGN] =
"--usign=<path> Path to usign tool used to verify packages signature. In default /usr/bin/usign.\n",
[COT_NO_REPLAN] =
"--no-replan Don't replan. Install everyting at once. Use this if updater you are running isn't from packages it installs.\n",
[COT_NO_IMMEDIATE_REBOOT] =
......@@ -97,7 +95,6 @@ enum option_val {
OPT_OUTPUT,
OPT_TASK_LOG_VAL,
OPT_EXCLUDE,
OPT_USIGN,
OPT_NO_REPLAN,
OPT_NO_IMMEDIATE_REBOOT,
OPT_OUT_OF_ROOT,
......@@ -120,7 +117,6 @@ static const struct option opt_long[] = {
{ .name = "output", .has_arg = required_argument, .val = OPT_OUTPUT },
{ .name = "task-log", .has_arg = required_argument, .val = OPT_TASK_LOG_VAL },
{ .name = "exclude", .has_arg = required_argument, .val = OPT_EXCLUDE },
{ .name = "usign", .has_arg = required_argument, .val = OPT_USIGN },
{ .name = "no-replan", .has_arg = no_argument, .val = OPT_NO_REPLAN },
{ .name = "no-immediate-reboot", .has_arg = no_argument, .val = OPT_NO_IMMEDIATE_REBOOT },
{ .name = "out-of-root", .has_arg = no_argument, .val = OPT_OUT_OF_ROOT },
......@@ -145,7 +141,6 @@ static const struct simple_opt {
[OPT_OUTPUT] = { COT_OUTPUT, true, true },
[OPT_TASK_LOG_VAL] = { COT_TASK_LOG, true, true },
[OPT_EXCLUDE] = { COT_EXCLUDE, true, true },
[OPT_USIGN] = { COT_USIGN, true, true },
[OPT_NO_REPLAN] = { COT_NO_REPLAN, false, true },
[OPT_NO_IMMEDIATE_REBOOT] = { COT_NO_IMMEDIATE_REBOOT, false, true },
[OPT_OUT_OF_ROOT] = { COT_OUT_OF_ROOT, false, false },
......@@ -265,7 +260,6 @@ struct cmd_op *cmd_args_parse(int argc, char *argv[], const enum cmd_op_type acc
case COT_OUTPUT:
case COT_APPROVE:
case COT_EXCLUDE:
case COT_USIGN:
case COT_NO_REPLAN:
case COT_TASK_LOG: {
struct cmd_op tmp = result[i];
......
......@@ -75,6 +75,8 @@ void log_internal(enum log_level level, const char *file, size_t line, const cha
#define ASSERT_MSG(COND, ...) do { if (!(COND)) DIE(__VA_ARGS__); } while (0)
#define ASSERT(COND) do { if (!(COND)) DIE("Failed assert: " #COND); } while (0)
#define STRBOOL(COND) ( COND ? "true" : "false" )
// If prepare of log would be long, check if it would be printed first.
bool would_log(enum log_level level);
......
This diff is collapsed.
/*
* Copyright 2019, CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This file is part of the Turris Updater.
*
* Updater 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, either version 3 of the License, or
* (at your option) any later version.
*
* Updater 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 Updater. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UPDATER_URI_H
#define UPDATER_URI_H
#include <stdint.h>
#include <stdbool.h>
#include <threads.h>
#include "download.h"
struct uri;
enum uri_error {
URI_E_INVALID_URI, // Parsed URI was deemed as invalid
URI_E_UNKNOWN_SCHEME, // URI scheme is not supported
URI_E_UNFINISHED_DOWNLOAD, // URI was not downloaded yet
URI_E_DOWNLOAD_FAILED, // URI download failed
URI_E_FILE_INPUT_ERROR, // Failure of opening file as an input (file://) (use errno for error)
URI_E_OUTPUT_OPEN_FAIL, // Unable to open output (file) for writing (use errno for error)
URI_E_OUTPUT_WRITE_FAIL, // Writing to output resulted to failure (use errno for error)
URI_E_SIG_FAIL, // Getting configured signature failed (see uri_sub_errno)
URI_E_VERIFY_FAIL, // Signature does not match any public key or content does not hold integrity
URI_E_NONLOCAL, // Configuration URI is not of local type
};
// URI error number
extern thread_local enum uri_error uri_errno;
// Error that is set when uri_errno is set to URI_E_SIG_FAIL
extern thread_local enum uri_error uri_sub_errno;
// URI object that caused uri_sub_errno error.
// This is valid only until original URI object is freed or new error happens
extern thread_local struct uri *uri_sub_err_uri;
#define URI_E_
enum uri_scheme {
URI_S_HTTP,
URI_S_HTTPS,
URI_S_FILE,
URI_S_DATA,
URI_S_UNKNOWN,
};
enum uri_output_type {
URI_OUT_T_FILE,
URI_OUT_T_TEMP_FILE,
URI_OUT_T_BUFFER,
};
// This implements list of local URI handlers
struct uri_local_list {
struct uri_local_list *next; // Link to (next) previous provided uri
unsigned ref_count; // Reference counter (counts number of usages in uri object)
struct uri *uri; // Uri object initialized by URI provided by user
char *path; // Used to store path to file
};
// URI representation
struct uri {
enum uri_scheme scheme;
bool finished;
char *uri; // Uri string in canonical format
// HTTPS options
bool ssl_verify; // If SSL should be verified
bool ocsp; // If OCSP should be used for ceritification validity check
struct uri_local_list *ca; // List of all configured CAs
struct uri_local_list *crl; // List of all configured CRLs
// Signature verification
struct uri_local_list *pubkey; // URIs to public keys used for verification
char *sig_uri_file; // path to output file for signature
struct uri *sig_uri; // signature URI
struct download_i *download_instance;
enum uri_output_type output_type;
union {
// When output_type is URI_OUT_T_FILE or URI_OUT_T_TEMP_FILE
char *fpath; // Path to file (if output is temporally then it is borrowed pointer, otherwise it is copy of passed string)
// When output_type is URI_OUT_T_BUFFER
struct {
uint8_t *data;
size_t size;
} buf;
} output_info;
};
// Create new URI object which content will be stored in file
// uri: URI string
// parent: parent URI object that should this URI inherit from.
// output_path: path to file where content will be stored in
// Returns URI object or NULL on error.
// Possible errors: URI_E_INVALID_URI, URI_E_UNKNOWN_SCHEME
struct uri *uri_to_file(const char *uri, const char *output_path,
const struct uri *parent) __attribute__((nonnull(1, 2)));
// Create new URI object which content will be stored in temporally file
// uri: URI string
// parent: parent URI object that should this URI inherit from.
// output_template: path to file where content will be stored in
// Returns URI object or NULL on error.
// Possible errors: URI_E_INVALID_URI, URI_E_UNKNOWN_SCHEME
struct uri *uri_to_temp_file(const char *uri, char *output_template,
const struct uri *parent) __attribute__((nonnull(1, 2)));
// Create new URI object which content will be stored in buffer in program
// uri: URI string
// parent: parent URI object that should this URI inherit from. You can pass NULL
// for no inheritence.
// Returns URI object or NULL on error.
// Possible errors: URI_E_INVALID_URI, URI_E_UNKNOWN_SCHEME
struct uri *uri_to_buffer(const char *uri, const struct uri *parent) __attribute__((nonnull(1)));
// Free URI object
void uri_free(struct uri *uri) __attribute__((nonnull));
// Check if given URI is local or remote (if downloader is needed or not)
bool uri_is_local(const struct uri *uri) __attribute__((nonnull));
// Returns Unix path from URI. This can be used only on URI of URI_S_FILE type.
// Returned pointer points to malloc allocated memory and should be freed by
// caller.
char *uri_path(const struct uri *uri) __attribute__((nonnull));
// Register given URI to downloader to be downloaded
// uri: URI object downloader is registered to
// downloader: Downloader object
// Returns true on success or false on error.
// Possible errors: URI_E_SIG_FAIL, URI_E_OUTPUT_OPEN_FAIL
bool uri_downloader_register(struct uri *uri, struct downloader *downloader) __attribute__((nonnull(1, 2)));
// Ensure that URI is received and stored to appropriate place (file or buffer)
// For remote ones call this after downloder_register and downloader_run.
// uri: URI object to be finished
// Returns true on retrieval success otherwise false.
// Possible errors: URI_E_UNFINISHED_DOWNLOAD, URI_E_DOWNLOAD_FAILED,
// URI_E_OUTPUT_OPEN_FAIL, URI_E_FILE_INPUT_ERROR, URI_E_OUTPUT_WRITE_FAIL,
// URI_E_VERIFY_FAIL, URI_E_SIG_FAIL
bool uri_finish(struct uri *uri) __attribute__((nonnull));
// Get buffer/content of URI
// Note that this can be called only once. Provided buffer has to be freed by
// caller.
// Call this only after uri_finish and only for uri_to_buffer initialized buffers.
// uri: URI object
// buffer: Pointer to pointer where address to first byte of buffer is set to
// len: Pointer to variable where size of buffer wiil be set to
void uri_take_buffer(struct uri *uri, uint8_t **buffer, size_t *len) __attribute__((nonnull(1, 2, 3)));
// Build error message of URI retrieval failure.
// Returns string with error message.
const char *uri_error_msg(enum uri_error);
// Returns pointer to error string for URI that reported URI_E_DOWNLOAD_FAILED
// when uri_finish was called.
// Returned string is valid until uri object is freed.
const char *uri_download_error(struct uri *uri) __attribute((nonnull));
// Returns name of scheme
const char *uri_scheme_string(enum uri_scheme);
// HTTPS configurations //
// Set if SSL certification verification should be done
// uri: URI object system CA to be set to
// verify: boolean value setting if verification should or should not be done
// In default this is enabled.
// This setting is inherited.
void uri_set_ssl_verify(struct uri *uri, bool verify) __attribute__((nonnull(1)));
// Set certification authority to be used
// uri: URI object CA to be set to
// ca_uri: URI to local CA to be added to list of CAs for SSL verification. You
// can pass NULL and in such case all URIs are dropped and defaul system SSL
// certificate bundle is used instead.
// In default system CAs are used.
// This setting is inherited.
// Possible errors: URI_E_NONLOCAL and all errors by uri_to_buffer
bool uri_add_ca(struct uri *uri, const char *ca_uri) __attribute__((nonnull(1)));
// Set URI to CRL that is used if CA verification is used
// uri: URI object CRLs to be set to
// crl_uri: URI to local CRL to be added to list of CRLs for SSL verification. You
// can also pass NULL and in such case all URIs are dropped and CRL verification
// is disabled.
// In default CRL verification is disabled.
// This setting is inherited.
// Possible errors: URI_E_NONLOCAL and all errors by uri_to_buffer
bool uri_add_crl(struct uri *uri, const char *crl_uri) __attribute__((nonnull(1)));
// Set URI OCSP verification
// uri: URI object OCSP to be set to
// enabled: If OCSP should be used
// In default OCSP is enabled.
// This setting is inherited.
void uri_set_ocsp(struct uri *uri, bool enabled) __attribute__((nonnull(1)));
// HTTP/HTTPS configuration //
// Set public key verification
// uri: URI object public keys are set to
// pubkey_uri: local URI to public key used to verify sigature. You can pass NULL
// to drop all added URIs and that way to disable signature verification.
//This setting is inherited.
// Possible errors: URI_E_NONLOCAL and all errors by uri_to_temp_file
bool uri_add_pubkey(struct uri *uri, const char *pubkey_uri) __attribute__((nonnull(1)));
// Set URI to signature to be used.
// uri: URI object signature URI to be set to
// sig_uri: string URI to signature. This signature is received with same
// configuration as given uri. NULL can be passed and in such case is URI used
// for signature retrieval derived by appending .sig to uri it self. If public
// key verification is enabled and this function was not called then it is
// automatically called when URI is registered to downloader or being finished.
// Note that uri created internally to receive this signature has same
// configuration as original uri but all subsequent configuration changes are not
// propagated to internally created uri. This means that you should call this as
// last command of all.
// This option is not inherited!
// Possible errors: all errors by uri_to_temp_file
bool uri_set_sig(struct uri *uri, const char *sig_uri) __attribute__((nonnull(1)));
#endif
......@@ -33,6 +33,7 @@
#include <dirent.h>
#include <signal.h>
#include <poll.h>
#include <b64/cdecode.h>
bool dump2file (const char *file, const char *text) {
FILE *f = fopen(file, "w");
......@@ -68,6 +69,19 @@ bool statfile(const char *file, int mode) {
return !access(file, mode);
}
char *writetempfile(char *buf, size_t len) {
char *fpath = strdup("/tmp/updater-temp-XXXXXX");
FILE *f = fdopen(mkstemp(fpath), "w");
if (!f) {
ERROR("Opening temporally file failed: %s", strerror(errno));
free(fpath);
return NULL;
}
ASSERT_MSG(fwrite(buf, 1, len, f) == len, "Not all data were written to temporally file.");
fclose(f);
return fpath;
}
static int exec_dir_filter(const struct dirent *de) {
// ignore system paths and accept only files
return strcmp(de->d_name, ".") && strcmp(de->d_name, "..") && de->d_type == DT_REG;
......@@ -94,6 +108,37 @@ void exec_hook(const char *dir, const char *message) {
free(namelist);
}
static bool base64_is_valid_char(const char c) {
return \
(c >= '0' && c <= '9') || \
(c >= 'A' && c <= 'Z') || \
(c >= 'a' && c <= 'z') || \
(c == '+' || c == '/' || c == '=');
}
unsigned base64_valid(const char *data) {
// TODO this is only minimal verification, we should do more some times in future
int check_off = 0;
while (data[check_off] != '\0')
if (!base64_is_valid_char(data[check_off++]))
return check_off;
return 0;
}
void base64_decode(const char *data, uint8_t **buf, size_t *len) {
size_t data_len = strlen(data);
size_t buff_len = (data_len * 3 / 4) + 2;
*buf = malloc(sizeof(uint8_t) * buff_len);
base64_decodestate s;
base64_init_decodestate(&s);
int cnt = base64_decode_block(data, data_len, (char*)*buf, &s);
ASSERT(cnt >= 0);
*len = cnt;
ASSERT_MSG((*len + 1) < buff_len, "Output buffer was too small, this should not happen!");
(*buf)[*len] = '\0'; // Terminate this with \0 so if it is string it can be used as such
}
static bool cleanup_registered = false;
static struct {
size_t size, allocated;
......
......@@ -34,6 +34,10 @@ bool dump2file (const char *file, const char *text) __attribute__((nonnull,nonnu
// Read content of whole file and return it as string
// Returned memory has to be freed by used.
char *readfile(const char *file) __attribute__((nonnull));
// Write buffer to temporally file
// This function returns path to file. It is your responsibility to both free
// returned memory and to unlink created file. On error NULL is returned.
char *writetempfile(char *buf, size_t len) __attribute__((nonnull));
// Returns true if file exists and is accessible in given mode
// Mode is bitwise OR of one or more of R_OK, W_OK, and X_OK.
......@@ -42,6 +46,17 @@ bool statfile(const char *file, int mode);
// Executes all executable files in given directory
void exec_hook(const char *dir, const char *message) __attribute__((nonnull));
// Verify if given data are encoded in base64 format (this is only minimal check,
// not complete one)
// It returns 0 if data are valid base64 format, it returns index+1 of problematic
// character otherwise.
unsigned base64_valid(const char *data);
// Decode given string as a base64 encoded data
// This function allocates buffer of appropriate size to buf argument and sets
// size of that buffer to len.
void base64_decode(const char *data, uint8_t **buf, size_t *len) __attribute__((nonnull));
// Using these functions you can register/unregister cleanup function. Note that
// they are called in reverse order of insertion. This is implemented using atexit
// function.
......
......@@ -13,6 +13,7 @@ C_TESTS := \
multiwrite \
subprocess \
download \
uri \
interpreter
LUA_TESTS := \
......
untrusted comment: signed by key d1ce0a8a82748a7d
RWTRzgqKgnSKfdqpDwFH38I6mh/mZ5nY217B7HnK/pVpKcCXChlIpnFO3efg1oWpzVNoTAEpwUBKoJrsMKnHQcyzJRP7MwnyJwM=
untrusted comment: signed by key 40e1ae405379ec08
RWRA4a5AU3nsCJKIEn7D9jhxUwdmZHXO7HwZyk0Nll3JsZ5EwW+UNvKBxHtvRX1SjW2/tIEaAKTvKvBizyCbCxyZ+4WVIaL5ZwQ=
untrusted comment: signed by key d1ce0a8a82748a7d
RWTRzgqKgnSKfWNUOILK11/029kM//cg35tabJam/feiEoh7NnVOCYuw359MSlTLSi+Ps6e6knkRNFOYhffwxJnp1hxvmGd21go=
untrusted comment: signed by key 40e1ae405379ec08
RWRA4a5AU3nsCLXe1YSgPcXb//GXQvhKB5k3iKvyzNlzflGrprYQzsxr/BA7KrgmfWUOWzwrpOX8Mj8lu8VzKxp9Db0iIwsfsgg=
untrusted comment: public key d1ce0a8a82748a7d
RWTRzgqKgnSKfdqpsPOfAewplIqDU7hfosnrrCdZV+So2l0bf+8OY48P
untrusted comment: private key d1ce0a8a82748a7d
RWRCSwAAAADEKeeLg5fDx1bTCQht/ka02y9AITsYvsXRzgqKgnSKfSioAlXENe5YgVqL+9ayDaM+hIOC+OCkEAwBU7OiRY5D2qmw858B7CmUioNTuF+iyeusJ1lX5KjaXRt/7w5jjw8=
untrusted comment: public key 40e1ae405379ec08
RWRA4a5AU3nsCKCGsa4DMtQDVXExF7nD0P04NhpASPDPggEyQuWjlwXG
untrusted comment: private key 40e1ae405379ec08
RWRCSwAAAAA4H2GXztRnpM4PiYWrbThcJ/Yj+lKLwJVA4a5AU3nsCJC4Yq5ZMvM4TMuLhb2zqB/n8+DhlzrC/mAL6dOCZApnoIaxrgMy1ANVcTEXucPQ/Tg2GkBI8M+CATJC5aOXBcY=
......@@ -36,6 +36,14 @@ const char *get_sdir();
#define FILE_LOREM_IPSUM_SHORT aprintf("%s/tests/data/lorem_ipsum_short.txt", get_sdir())
#define FILE_LOREM_IPSUM aprintf("%s/tests/data/lorem_ipsum.txt", get_sdir())
// Signatures
#define USIGN_KEY_1_PUB (aprintf("%s/tests/data/usign.key1.pub", get_sdir()))
#define USIGN_KEY_2_PUB (aprintf("%s/tests/data/usign.key2.pub", get_sdir()))
#define SIG_1_LOREM_IPSUM (aprintf("%s/tests/data/lorem_ipsum.txt.sig", get_sdir()))
#define SIG_2_LOREM_IPSUM (aprintf("%s/tests/data/lorem_ipsum.txt.sig2", get_sdir()))
#define SIG_1_LOREM_IPSUM_SHORT (aprintf("%s/tests/data/lorem_ipsum_short.txt.sig", get_sdir()))
#define SIG_2_LOREM_IPSUM_SHORT (aprintf("%s/tests/data/lorem_ipsum_short.txt.sig2", get_sdir()))
// Certificates
#define FILE_LETS_ENCRYPT_ROOTS aprintf("%s/tests/data/lets_encrypt_roots.pem", get_sdir())
#define URI_FILE_LETS_ENCRYPT_ROOTS aprintf("file://%s/tests/data/lets_encrypt_roots.pem", get_sdir())
......
This diff is collapsed.
......@@ -20,6 +20,25 @@
#include <stdbool.h>
#include "../src/lib/util.h"
#define BASE64_PLAIN "Hello\n"
#define BASE64_ENCOD "SGVsbG8K"
START_TEST(base64_is_valid) {
ck_assert_int_eq(0, base64_valid(BASE64_ENCOD));
ck_assert_int_eq(5, base64_valid("SGvs$bG8L"));
}
END_TEST
START_TEST(base64) {
uint8_t *result;
size_t result_len;
base64_decode(BASE64_ENCOD, &result, &result_len);
ck_assert_int_eq(6, result_len);
ck_assert_str_eq(BASE64_PLAIN, (char*)result);
free(result);
}
END_TEST
static int cleaned;
static void cleanup_func(void *data) {
......@@ -85,6 +104,8 @@ Suite *gen_test_suite(void) {
Suite *result = suite_create("Util");
TCase *util = tcase_create("util");
tcase_set_timeout(util, 30);
tcase_add_test(util, base64_is_valid);
tcase_add_test(util, base64);
tcase_add_test(util, cleanup_multi);
tcase_add_test(util, cleanup_single);
tcase_add_test(util, cleanup_by_data);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment