Verified Commit 8cab9897 authored by Karel Koci's avatar Karel Koci 🤘

Allow usage of system CAs in URI

parent 34be10c9
......@@ -159,17 +159,20 @@ pubkey::
An URI or table of URIs with trusted public signature keys. These
are not verified (therefore it is recommended to come from a already
verified source ‒ like `data:` URI or `file://` URI). If it is not
specified, it is inherited from the verification of the script
specified (`nil`), it is inherited from the verification of the script
running the command. While it has no direct effect if the option is
specified on another verification than `sig` or `both`, it
influences the inheritance.
influences the inheritance. Default value is `{}`.
ca::
An URI or table of URIs with trusted SSL certificate authorities, in
PEM format. Similar notes as with `pubkey` apply.
PEM format. Similar notes as with `pubkey` apply. But instead of table
or URI you can also specify special value `system_cas`, which results
into system authorities to be used. `system_cas` is also default value.
crl::
An URI or table of URIs with CRLs relevant to the server. If not
set, CRL is not checked. Note that the `crl` field is also
inherited, therefore you may want to reset it manually to nil.
An URI or table of URIs with CRLs relevant to the server. If set into
`ignore_crl`, CRL is not checked. Note that the `crl` field is also
inherited, therefore you may want to set it manually to `ignore_crl`.
Default value is `ignore_crl`.
The file signature is verified using the `usign` utility.
......
......@@ -47,7 +47,11 @@ local sha256 = sha256
module "uri"
-- luacheck: globals wait signature_check parse new
-- luacheck: globals wait signature_check parse new system_cas ignore_crl
-- Constants used for inheritance breakage
system_cas = "uri_system_cas"
ignore_crl = "uri_ignore_crl"
local function percent_decode(text)
return text:gsub('%%(..)', function (encoded)
......@@ -144,14 +148,14 @@ local function handler_internal(uri, err_cback, done_cback)
end
-- Actually, both for http and https
local function handler_http(uri, err_cback, done_cback, ca, crl)
local function handler_http(uri, err_cback, done_cback, ca, crl, use_ssl)
return download(function (status, answer)
if status == 200 then
done_cback(answer)
else
err_cback(utils.exception("unreachable", uri .. ": " .. tostring(answer)))
end
end, uri, ca, crl)
end, uri, ca, crl, use_ssl)
end
local function match_check(uri, context)
......@@ -328,9 +332,8 @@ function new(context, uri, verification)
error(utils.exception('bad value', "Unknown verification mode " .. vermode))
end
local do_cert = handler.can_check_cert and (vermode == 'both' or vermode == 'cert')
local use_ca, use_crl
local explicit_ca, use_crl
if do_cert then
local ca, ca_context = ver_lookup('ca', nil, true)
local function pem_get(uris, context)
if type(uris) == 'string' then
uris = {uris}
......@@ -351,12 +354,15 @@ function new(context, uri, verification)
error(utils.exception('bad value', "The ca and crl must be either string or table, not " .. type(uris)))
end
end
use_ca = pem_get(ca, ca_context)
if not use_ca then
return result
local ca, ca_context = ver_lookup('ca', system_cas, true)
if ca ~= system_cas then
explicit_ca = pem_get(ca, ca_context)
if not explicit_ca then
return result
end
end
local crl, crl_context = ver_lookup('crl', nil, true)
if crl then
local crl, crl_context = ver_lookup('crl', ignore_crl, true)
if crl ~= ignore_crl then
use_crl = pem_get(crl, crl_context)
if not use_crl then
return result
......@@ -375,7 +381,7 @@ function new(context, uri, verification)
if do_cert then sig_veri.verification = 'cert' end -- If the main resource checks cert, the .sig should too
sig_data = new(context, sig_uri, sig_veri)
table.insert(sub_uris, sig_data)
local pubkeys, pk_context = ver_lookup('pubkey', nil, true)
local pubkeys, pk_context = ver_lookup('pubkey', {}, true)
if type(pubkeys) == 'string' then
pubkeys = {pubkeys}
end
......@@ -457,7 +463,7 @@ function new(context, uri, verification)
result.content = content
dispatch()
end
result.events = {handler.handler(uri, err_cback, done_cback, use_ca, use_crl)}
result.events = {handler.handler(uri, err_cback, done_cback, explicit_ca, use_crl, do_cert)}
-- Wait for the sub uris and include them in our events
local function sub_cback()
wait_sub_uris = wait_sub_uris - 1
......
......@@ -174,7 +174,7 @@ end
-- END_MAGIC
-- Functions available in the restricted level
-- Functions and "constants" available in the restricted level
local rest_available_funcs = {
"table",
"string",
......@@ -194,7 +194,9 @@ local rest_available_funcs = {
"ERROR",
"WARN",
"INFO",
"DBG"
"DBG",
"system_cas",
"ignore_crl"
}
state_vars = nil
......
......@@ -813,7 +813,7 @@ static size_t download_write_callback(char *ptr, size_t size, size_t nmemb, void
return rsize;
}
struct wait_id download(struct events *events, download_callback_t callback, void *data, const char *url, const char *cacert, const char *crl) {
struct wait_id download(struct events *events, download_callback_t callback, void *data, const char *url, const char *cacert, const char *crl, bool ssl) {
DBG("Downloading %s", url);
struct download_data *res = malloc(sizeof *res);
*res = (struct download_data) {
......@@ -834,12 +834,13 @@ struct wait_id download(struct events *events, download_callback_t callback, voi
CURL_SETOPT(CURLOPT_TIMEOUT, 120); // Timeout after 2 minutes per try so in total with possible 2 repeats it's 6 minutes (3*2)
CURL_SETOPT(CURLOPT_CONNECTTIMEOUT, 30); // Timeout connection after half of a minute.
CURL_SETOPT(CURLOPT_FAILONERROR, 1); // If we use http and request fails (response >= 400) request also fails. TODO according to documentation this doesn't cover authentications errors. If authentication is added, this won't be enough.
if (cacert)
CURL_SETOPT(CURLOPT_CAINFO, cacert);
else
if (ssl) {
if (cacert)
CURL_SETOPT(CURLOPT_CAINFO, cacert);
if (crl)
CURL_SETOPT(CURLOPT_CRLFILE, crl);
} else
CURL_SETOPT(CURLOPT_SSL_VERIFYPEER, 0L);
if (crl)
CURL_SETOPT(CURLOPT_CRLFILE, crl);
CURL_SETOPT(CURLOPT_WRITEFUNCTION, download_write_callback);
CURL_SETOPT(CURLOPT_WRITEDATA, res);
CURL_SETOPT(CURLOPT_ERRORBUFFER, res->curl_err);
......
......@@ -24,6 +24,7 @@
#include <unistd.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>
struct events;
......@@ -169,9 +170,10 @@ typedef void (*download_callback_t)(struct wait_id id, void *data, int status, s
*
* Optionally, check certificate and revocation list specified by parameters
* cacert or crl respectively (paths to .pem files). If no certificate is specified,
* insecure https connections are allowed.
* system certificate is used. Specifying ssl as false insecure https connections
* are allowed. Specifying ssl as true for http has no effect.
*/
struct wait_id download(struct events *events, download_callback_t callback, void *data, const char *url, const char *cacert, const char *crl) __attribute__((nonnull(1, 2, 4)));
struct wait_id download(struct events *events, download_callback_t callback, void *data, const char *url, const char *cacert, const char *crl, bool ssl) __attribute__((nonnull(1, 2, 4)));
/*
* Set the number of maximum parallel downloads
*
......
......@@ -379,6 +379,7 @@ static int lua_download(lua_State *L) {
const char *crl = NULL;
if (pcount >= 4 && !lua_isnil(L, 4))
crl = luaL_checkstring(L, 4);
bool ssl = lua_toboolean(L, 5);
// Handle the callback
struct lua_download_data *data = malloc(sizeof *data);
data->L = L;
......@@ -386,7 +387,7 @@ static int lua_download(lua_State *L) {
// Run the download
struct events *events = extract_registry(L, "events");
ASSERT(events);
struct wait_id id = download(events, download_callback, data, url, cacert, crl);
struct wait_id id = download(events, download_callback, data, url, cacert, crl, ssl);
// Return the ID
push_wid(L, &id);
return 1;
......
......@@ -99,7 +99,7 @@ The other event is `download`:
end
end
local id = download(callback, "https://example.org/file", "/path/to/cert", "/path/to/crl")
local id = download(callback, "https://example.org/file", "/path/to/cert", "/path/to/crl", true)
events_wait(id)
......
......@@ -262,20 +262,26 @@ START_TEST(command_stuff) {
}
END_TEST
static void download_done_callback(struct wait_id id __attribute__((unused)), void *data __attribute__((unused)), int status, size_t out_size __attribute__((unused)), const char *out) {
static void download_done_callback_api(struct wait_id id __attribute__((unused)), void *data __attribute__((unused)), int status, size_t out_size __attribute__((unused)), const char *out) {
ck_assert_uint_eq(200, status);
const char *res = strstr(out, "Not for your eyes");
ck_assert(res);
}
static void download_done_callback_repo(struct wait_id id __attribute__((unused)), void *data __attribute__((unused)), int status, size_t out_size __attribute__((unused)), const char *out) {
ck_assert_uint_eq(200, status);
const char *res = strstr(out, "Index of /");
ck_assert(res);
}
static void download_failed_callback(struct wait_id id __attribute__((unused)), void *data __attribute__((unused)), int status, size_t out_size __attribute__((unused)), const char *out __attribute__((unused))) {
ck_assert_uint_eq(500, status);
}
// Just like the done callback, but download one more thing from within this callback (and wait for it).
static void download_recursive_callback(struct wait_id id, void *data, int status, size_t out_size, const char *out) {
download_done_callback(id, data, status, out_size, out);
struct wait_id new_id = download(data, download_done_callback, NULL, "https://api.turris.cz/index.html", NULL, NULL);
download_done_callback_api(id, data, status, out_size, out);
struct wait_id new_id = download(data, download_done_callback_api, NULL, "https://api.turris.cz/index.html", NULL, NULL, false);
events_wait(data, 1, &new_id);
}
......@@ -284,19 +290,25 @@ START_TEST(command_download) {
if (!s_dir)
s_dir = ".";
const char *cert_file = aprintf("%s/tests/data/updater.pem", s_dir);
const size_t cnt = 5;
struct wait_id ids[cnt * 3];
const size_t cnt = 2;
struct wait_id ids[cnt * 6];
struct events *events = events_new();
download_slot_count_set(events, 2);
for (size_t i = 0; i < cnt; i++) {
ids[i] = download(events, download_done_callback, NULL, "https://api.turris.cz/index.html", cert_file, NULL);
ids[i + cnt] = download(events, download_failed_callback, NULL, "https://api.turris.cz/does_not_exist.dat", cert_file, NULL);
ids[i + 2 * cnt] = download(events, download_recursive_callback, events, "https://api.turris.cz/index.html", cert_file, NULL);
ids[i] = download(events, download_done_callback_api, NULL, "https://api.turris.cz/index.html", cert_file, NULL, true);
ids[i + cnt] = download(events, download_failed_callback, NULL, "https://api.turris.cz/does_not_exist.dat", cert_file, NULL, true);
ids[i + 2 * cnt] = download(events, download_recursive_callback, events, "https://api.turris.cz/index.html", cert_file, NULL, true);
// api has special ca so this should fail
ids[i + 3 * cnt] = download(events, download_failed_callback, NULL, "https://api.turris.cz/index.html", NULL, NULL, true);
// but with disabled ssl it should work
ids[i + 4 * cnt] = download(events, download_done_callback_api, NULL, "https://api.turris.cz/index.html", NULL, NULL, false);
// repo uses public ca so this should work with system one
ids[i + 5 * cnt] = download(events, download_done_callback_repo, NULL, "https://repo.turris.cz", NULL, NULL, true);
}
events_wait(events, cnt * 3, ids);
events_wait(events, cnt * 6, ids);
events_destroy(events);
}
END_TEST
......
......@@ -183,12 +183,20 @@ function test_https_cert()
local u2 = uri(context, "https://api.turris.cz/", {verification = "cert", ca = "file:///dev/null"})
-- We may specify the ca as a table of possibilities
local u3 = uri(context, "https://api.turris.cz/", {verification = "cert", ca = {"file:///dev/null", ca_file}})
-- nil ca should result in failure as api has certificate not added to standard paths
local u4 = uri(context, "https://api.turris.cz/", {verification = "cert"})
-- nil ca should result in success on repo as it's signed by common authority
local u5 = uri(context, "https://repo.turris.cz/", {verification = "cert"})
local ok1 = u1:get()
assert(ok1)
local ok2 = u2:get()
assert_false(ok2)
local ok3 = u3:get()
assert(ok3)
local ok4 = u4:get()
assert_false(ok4)
local ok5 = u5:get()
assert(ok5)
-- Check we can put the verification stuff into the context
context.ca = ca_file
context.verification = "cert"
......
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