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

Allow usage of system CAs in URI

parent 34be10c9
...@@ -159,17 +159,20 @@ pubkey:: ...@@ -159,17 +159,20 @@ pubkey::
An URI or table of URIs with trusted public signature keys. These An URI or table of URIs with trusted public signature keys. These
are not verified (therefore it is recommended to come from a already are not verified (therefore it is recommended to come from a already
verified source ‒ like `data:` URI or `file://` URI). If it is not 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 running the command. While it has no direct effect if the option is
specified on another verification than `sig` or `both`, it specified on another verification than `sig` or `both`, it
influences the inheritance. influences the inheritance. Default value is `{}`.
ca:: ca::
An URI or table of URIs with trusted SSL certificate authorities, in 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:: crl::
An URI or table of URIs with CRLs relevant to the server. If not An URI or table of URIs with CRLs relevant to the server. If set into
set, CRL is not checked. Note that the `crl` field is also `ignore_crl`, CRL is not checked. Note that the `crl` field is also
inherited, therefore you may want to reset it manually to nil. 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. The file signature is verified using the `usign` utility.
......
...@@ -47,7 +47,11 @@ local sha256 = sha256 ...@@ -47,7 +47,11 @@ local sha256 = sha256
module "uri" 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) local function percent_decode(text)
return text:gsub('%%(..)', function (encoded) return text:gsub('%%(..)', function (encoded)
...@@ -144,14 +148,14 @@ local function handler_internal(uri, err_cback, done_cback) ...@@ -144,14 +148,14 @@ local function handler_internal(uri, err_cback, done_cback)
end end
-- Actually, both for http and https -- 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) return download(function (status, answer)
if status == 200 then if status == 200 then
done_cback(answer) done_cback(answer)
else else
err_cback(utils.exception("unreachable", uri .. ": " .. tostring(answer))) err_cback(utils.exception("unreachable", uri .. ": " .. tostring(answer)))
end end
end, uri, ca, crl) end, uri, ca, crl, use_ssl)
end end
local function match_check(uri, context) local function match_check(uri, context)
...@@ -328,9 +332,8 @@ function new(context, uri, verification) ...@@ -328,9 +332,8 @@ function new(context, uri, verification)
error(utils.exception('bad value', "Unknown verification mode " .. vermode)) error(utils.exception('bad value', "Unknown verification mode " .. vermode))
end end
local do_cert = handler.can_check_cert and (vermode == 'both' or vermode == 'cert') 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 if do_cert then
local ca, ca_context = ver_lookup('ca', nil, true)
local function pem_get(uris, context) local function pem_get(uris, context)
if type(uris) == 'string' then if type(uris) == 'string' then
uris = {uris} uris = {uris}
...@@ -351,12 +354,15 @@ function new(context, uri, verification) ...@@ -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))) error(utils.exception('bad value', "The ca and crl must be either string or table, not " .. type(uris)))
end end
end end
use_ca = pem_get(ca, ca_context) local ca, ca_context = ver_lookup('ca', system_cas, true)
if not use_ca then if ca ~= system_cas then
return result explicit_ca = pem_get(ca, ca_context)
if not explicit_ca then
return result
end
end end
local crl, crl_context = ver_lookup('crl', nil, true) local crl, crl_context = ver_lookup('crl', ignore_crl, true)
if crl then if crl ~= ignore_crl then
use_crl = pem_get(crl, crl_context) use_crl = pem_get(crl, crl_context)
if not use_crl then if not use_crl then
return result return result
...@@ -375,7 +381,7 @@ function new(context, uri, verification) ...@@ -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 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) sig_data = new(context, sig_uri, sig_veri)
table.insert(sub_uris, sig_data) 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 if type(pubkeys) == 'string' then
pubkeys = {pubkeys} pubkeys = {pubkeys}
end end
...@@ -457,7 +463,7 @@ function new(context, uri, verification) ...@@ -457,7 +463,7 @@ function new(context, uri, verification)
result.content = content result.content = content
dispatch() dispatch()
end 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 -- Wait for the sub uris and include them in our events
local function sub_cback() local function sub_cback()
wait_sub_uris = wait_sub_uris - 1 wait_sub_uris = wait_sub_uris - 1
......
...@@ -174,7 +174,7 @@ end ...@@ -174,7 +174,7 @@ end
-- END_MAGIC -- END_MAGIC
-- Functions available in the restricted level -- Functions and "constants" available in the restricted level
local rest_available_funcs = { local rest_available_funcs = {
"table", "table",
"string", "string",
...@@ -194,7 +194,9 @@ local rest_available_funcs = { ...@@ -194,7 +194,9 @@ local rest_available_funcs = {
"ERROR", "ERROR",
"WARN", "WARN",
"INFO", "INFO",
"DBG" "DBG",
"system_cas",
"ignore_crl"
} }
state_vars = nil state_vars = nil
......
...@@ -813,7 +813,7 @@ static size_t download_write_callback(char *ptr, size_t size, size_t nmemb, void ...@@ -813,7 +813,7 @@ static size_t download_write_callback(char *ptr, size_t size, size_t nmemb, void
return rsize; 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); DBG("Downloading %s", url);
struct download_data *res = malloc(sizeof *res); struct download_data *res = malloc(sizeof *res);
*res = (struct download_data) { *res = (struct download_data) {
...@@ -834,12 +834,13 @@ struct wait_id download(struct events *events, download_callback_t callback, voi ...@@ -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_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_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. 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) if (ssl) {
CURL_SETOPT(CURLOPT_CAINFO, cacert); if (cacert)
else CURL_SETOPT(CURLOPT_CAINFO, cacert);
if (crl)
CURL_SETOPT(CURLOPT_CRLFILE, crl);
} else
CURL_SETOPT(CURLOPT_SSL_VERIFYPEER, 0L); CURL_SETOPT(CURLOPT_SSL_VERIFYPEER, 0L);
if (crl)
CURL_SETOPT(CURLOPT_CRLFILE, crl);
CURL_SETOPT(CURLOPT_WRITEFUNCTION, download_write_callback); CURL_SETOPT(CURLOPT_WRITEFUNCTION, download_write_callback);
CURL_SETOPT(CURLOPT_WRITEDATA, res); CURL_SETOPT(CURLOPT_WRITEDATA, res);
CURL_SETOPT(CURLOPT_ERRORBUFFER, res->curl_err); CURL_SETOPT(CURLOPT_ERRORBUFFER, res->curl_err);
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#include <unistd.h> #include <unistd.h>
#include <stdint.h> #include <stdint.h>
#include <stdbool.h>
#include <stdarg.h> #include <stdarg.h>
struct events; struct events;
...@@ -169,9 +170,10 @@ typedef void (*download_callback_t)(struct wait_id id, void *data, int status, s ...@@ -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 * Optionally, check certificate and revocation list specified by parameters
* cacert or crl respectively (paths to .pem files). If no certificate is specified, * 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 * Set the number of maximum parallel downloads
* *
......
...@@ -379,6 +379,7 @@ static int lua_download(lua_State *L) { ...@@ -379,6 +379,7 @@ static int lua_download(lua_State *L) {
const char *crl = NULL; const char *crl = NULL;
if (pcount >= 4 && !lua_isnil(L, 4)) if (pcount >= 4 && !lua_isnil(L, 4))
crl = luaL_checkstring(L, 4); crl = luaL_checkstring(L, 4);
bool ssl = lua_toboolean(L, 5);
// Handle the callback // Handle the callback
struct lua_download_data *data = malloc(sizeof *data); struct lua_download_data *data = malloc(sizeof *data);
data->L = L; data->L = L;
...@@ -386,7 +387,7 @@ static int lua_download(lua_State *L) { ...@@ -386,7 +387,7 @@ static int lua_download(lua_State *L) {
// Run the download // Run the download
struct events *events = extract_registry(L, "events"); struct events *events = extract_registry(L, "events");
ASSERT(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 // Return the ID
push_wid(L, &id); push_wid(L, &id);
return 1; return 1;
......
...@@ -99,7 +99,7 @@ The other event is `download`: ...@@ -99,7 +99,7 @@ The other event is `download`:
end end
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) events_wait(id)
......
...@@ -262,20 +262,26 @@ START_TEST(command_stuff) { ...@@ -262,20 +262,26 @@ START_TEST(command_stuff) {
} }
END_TEST 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); ck_assert_uint_eq(200, status);
const char *res = strstr(out, "Not for your eyes"); const char *res = strstr(out, "Not for your eyes");
ck_assert(res); 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))) { 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); ck_assert_uint_eq(500, status);
} }
// Just like the done callback, but download one more thing from within this callback (and wait for it). // 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) { 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); download_done_callback_api(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); 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); events_wait(data, 1, &new_id);
} }
...@@ -284,19 +290,25 @@ START_TEST(command_download) { ...@@ -284,19 +290,25 @@ START_TEST(command_download) {
if (!s_dir) if (!s_dir)
s_dir = "."; s_dir = ".";
const char *cert_file = aprintf("%s/tests/data/updater.pem", s_dir); const char *cert_file = aprintf("%s/tests/data/updater.pem", s_dir);
const size_t cnt = 5; const size_t cnt = 2;
struct wait_id ids[cnt * 3]; struct wait_id ids[cnt * 6];
struct events *events = events_new(); struct events *events = events_new();
download_slot_count_set(events, 2); download_slot_count_set(events, 2);
for (size_t i = 0; i < cnt; i++) { 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] = 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); 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); 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); events_destroy(events);
} }
END_TEST END_TEST
......
...@@ -183,12 +183,20 @@ function test_https_cert() ...@@ -183,12 +183,20 @@ function test_https_cert()
local u2 = uri(context, "https://api.turris.cz/", {verification = "cert", ca = "file:///dev/null"}) local u2 = uri(context, "https://api.turris.cz/", {verification = "cert", ca = "file:///dev/null"})
-- We may specify the ca as a table of possibilities -- 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}}) 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() local ok1 = u1:get()
assert(ok1) assert(ok1)
local ok2 = u2:get() local ok2 = u2:get()
assert_false(ok2) assert_false(ok2)
local ok3 = u3:get() local ok3 = u3:get()
assert(ok3) 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 -- Check we can put the verification stuff into the context
context.ca = ca_file context.ca = ca_file
context.verification = "cert" 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