Subject: Add tests for key manipulation functions Origin: v7-7-gc71df1d Upstream-Author: Sergio Correia Date: Fri Nov 27 09:42:38 2020 -0300 In this commit we add tests for the key manipulation functions added in src/keys.{c|h}. --- /dev/null +++ b/tests/keys/-bWkGaJi0Zdvxaj4DCp28umLcRA.jwk @@ -0,0 +1 @@ +{"alg":"ECMR","crv":"P-521","d":"AQ3u1g0L__GIGSJRX1LtjSArwJxxQz0kWXIi-X4PqwoheoeY57cw36pmWmyVsn43jDEZ6SAsiNeIw9sHDkFZe1VV","key_ops":["deriveKey"],"kty":"EC","x":"AcfowrKEKteg_jKX1CiR2RQfbUGJ73KXlcl8AgIDAgN7R6yNKWpKhZNBmV2tAxxMCQcIksqQl17UXwemvH2j2fem","y":"ACrb-y4ZhLIGX-41QYgJhniiZ85qkjILbkVUcC8gBYxOAnKWIpMGLsjrT3AYhM6jk6puwnNYbEM28s2caAEogUcA"} --- /dev/null +++ b/tests/keys/.r4E2wG1u_YyKUo0N0rIK7jJF5Xg.jwk @@ -0,0 +1 @@ +{"alg":"ES512","crv":"P-521","d":"Af5SfYh_4LmBDF9dCGDQRDA52yzqnzDeo-GZU05hpnLr6bsIBTFpc8rdcCm95mhZ59ngC-6WNtmAF_nLCDcHKg0A","key_ops":["sign","verify"],"kty":"EC","x":"AUxS3DXdoONUB-6-nyzdd3V42iD7obGTJ1m40t3V6jzXfABWp_gtTidwiKyDJQXxhEzMSToo-V0RGq6Qz2XTOgPe","y":"AFrkJfkLGJz_2v-k3-wdydckVcBXql2NR66HaF0U9NlcfGLezQau7XihArm4GE3-sHoQLsRa-HvYET9zyd9Syh5y"} --- /dev/null +++ b/tests/keys/.uZ0s8YTXcGcuWduWWBSiR2OjOVg.jwk @@ -0,0 +1 @@ +{"alg":"ECMR","crv":"P-521","d":"Acuk66sHvSS5TN-p0XhwHhVKyifYr-ecur-7zfgQZMzSq9SeBJjuX6Ttav-7AbnVvXU_S55BgtGL5iymXGuMguCp","key_ops":["deriveKey"],"kty":"EC","x":"AEz9EwBOIrLeV4in6M1oWVnOWVR7ubkFB0R0-AwyIL7u5-7G3u2tvPIJRQY-l1Wttn7Ar4DhflcMhnb3rk5hT5yh","y":"AZt35wqOhNsnEi-GLAgyCaiW_c6h6Zyo4xwjuvXzmQMDwh9MtdaUigFuBOTlfRj1uri_YBqdpI09nYrqqgx97Ca6"} --- /dev/null +++ b/tests/keys/another-bad-file @@ -0,0 +1 @@ +foobar --- /dev/null +++ b/tests/keys/invalid.jwk @@ -0,0 +1 @@ +foo --- /dev/null +++ b/tests/keys/qgmqJSo6AEEuVQY7zVlklqdTMqY.jwk @@ -0,0 +1 @@ +{"alg":"ES512","crv":"P-521","d":"AUQpuWtoSqURl0OuWQ-I9i4X1F3sDak0Hbf9Ixj7uwjA20A0ABJdCHbai1Ai0t3yoxWKPYi6t2XjjeRzHIKyhXbf","key_ops":["sign","verify"],"kty":"EC","x":"ACbYnvh1EtLePkM5jCtuxLpMroOUNRfv-wQdXgT5AQ5bhSLv6wkrBzh1rwymo-fmCNWzrcTflzqWf-wXAd00lokM","y":"AEaDByxXfbee4TlPXgPRg5S4MVOqgObjX6_JJkySTudSfOygcx7dujsf32dOEI25d8bNKUgdjQt8lc5XtHeWXH3a"} --- a/tests/meson.build +++ b/tests/meson.build @@ -1,22 +1,42 @@ +incdir = include_directories( + join_paths('..', 'src') +) + +test_data = configuration_data() +test_data.set('testjwkdir', join_paths(meson.source_root(), 'tests','keys')) + +test_keys_c = configure_file( + input: 'test-keys.c.in', + output: 'test-keys.c', + configuration: test_data +) + +test_keys = executable('test-keys', + test_keys_c, + 'test-util.c', + dependencies: [jose], + include_directories: incdir +) + sd_activate = find_program( 'systemd-socket-activate', 'systemd-activate', required: false ) +env = environment() +env.prepend('PATH', + join_paths(meson.source_root(), 'src'), + join_paths(meson.build_root(), 'src'), + separator: ':' +) + if sd_activate.found() - env = environment() - env.prepend('PATH', - join_paths(meson.source_root(), 'src'), - join_paths(meson.build_root(), 'src'), - separator: ':' - ) env.set('SD_ACTIVATE', sd_activate.path() + ' --inetd') - test('adv', find_program('adv'), env: env) + test('adv', find_program('adv'), env: env, timeout: 60) test('rec', find_program('rec'), env: env) -else - warning('Will not run the tests due to missing dependencies!') endif +test('test-keys', test_keys, env: env, timeout: 60) # vim:set ts=2 sw=2 et: --- /dev/null +++ b/tests/test-keys.c.in @@ -0,0 +1,257 @@ +/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */ +/* + * Copyright (c) 2020 Red Hat, Inc. + * Author: Sergio Correia + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +#include "keys.c" +#include "test-util.h" + +const char* jwkdir = "@testjwkdir@"; + +struct thp_result { + const char* thp; + int valid; +}; + +struct test_result_int { + const char* data; + int expected; +}; + +static void +test_create_new_keys(void) +{ + __attribute__((cleanup(cleanup_str))) char* newdir = create_tempdir(); + ASSERT(newdir); + __attribute__((cleanup(cleanup_tang_keys_info))) struct tang_keys_info* tki = read_keys(newdir); + ASSERT(tki); + ASSERT(tki->m_keys_count == 2); + remove_tempdir(newdir); +} + + +static void +test_is_hash(void) +{ + const struct test_result_int test_data[] = { + {NULL, 0}, + {"", 0}, + {"ES512", 0}, + {"ECMR", 0}, + {"foobar", 0}, + {"{", 0}, + {"[}", 0}, + {"[]", 0}, + {"S1", 1}, + {"S224", 1}, + {"S256", 1}, + {"S384", 1}, + {"S512", 1}, + {"S42", 0} + }; + for (int i = 0, len = ARRAY_COUNT(test_data); i < len; i++) { + int ret = is_hash(test_data[i].data); + ASSERT_WITH_MSG(ret == test_data[i].expected, "i = %d, alg = %s", i, test_data[i].data); + }; + +} + +static void +test_jwk_generate(void) +{ + const struct test_result_int test_data[] = { + {NULL, 0}, + {"", 0}, + {"ES512", 1}, + {"ECMR", 1}, + {"foobar", 0}, + {"{", 0}, + {"[}", 0}, + {"[]", 0} + }; + + for (int i = 0, len = ARRAY_COUNT(test_data); i < len; i++) { + json_auto_t* jwk = jwk_generate(test_data[i].data); + ASSERT_WITH_MSG(!!jwk == test_data[i].expected, "i = %d, alg = %s", i, test_data[i].data); + }; +} + +static void +test_find_jws(void) +{ + const struct thp_result test_data[] = { + {"00BUQM4A7NYxbOrBR9QDfkzGVGj3k57Fs4jCbJxcLYAgRFHu5B7jtbL97x1T7stQ", 1}, + {"dd5qbN1lQ6UWdZszbfx2oIcH34ShklzFL1SUQg", 1}, + {"dOZkUtZ_gLDUP53GIlyAxHMNuyrk8vdY-XXND32GccqNbT_MKpqGC-13-GNEye48", 1}, + {"DZrlBQvfvlwPQlvH_IieBdc_KpesEramLygVL_rFr7g", 1}, + {"FL_Zt5fFadUL4syeMMpUnss8aKdCrPGFy3102JGR3EE", 1}, + {"qgmqJSo6AEEuVQY7zVlklqdTMqY", 1}, + {"r4E2wG1u_YyKUo0N0rIK7jJF5Xg", 1}, + {"ugJ4Ula-YABQIiJ-0g3B_jpFpF2nl3W-DNpfLdXArhTusV0QCcd1vtgDeGHEPzpm7jEsyC7VYYSSOkZicK22mw", 1}, + {"up0Z4fRhpd4O5QwBaMCXDTlrvxCmZacU0MD8kw", 1}, + {"vllHS-M0aQFCo2yUCcAahMU4TAtXACyeuRf-zbmmTPBg7V0Pb-RRFGo5C6MnpzdirK8B3ORLOsN8RyXClvtjxA", 1}, + {NULL, 1}, + {"a", 0}, + {"foo", 0}, + {"bar", 0}, + {"XXXXXXXXXXXXXXXXXX", 0} + }; + + __attribute__((cleanup(cleanup_tang_keys_info))) struct tang_keys_info* tki = read_keys(jwkdir); + for (int i = 0, len = ARRAY_COUNT(test_data); i < len; i++) { + json_auto_t* jws = find_jws(tki, test_data[i].thp); + ASSERT_WITH_MSG(!!jws == test_data[i].valid, "i = %d, thp = %s", i, test_data[i].thp); + } + + /* Passing NULL to find_jws should return the default advertisement */ + json_auto_t* adv = find_jws(tki, NULL); + ASSERT(adv); + + + /* + * The default set of signing keys are the signing keys that are not + * rotated. The payload is made of deriving keys that are also not + * rotated. The default advertisement should be signed by this set of + * default signing keys. + */ + ASSERT(jose_jws_ver(NULL, adv, NULL, tki->m_sign, 1)); + + /* find_jws should be able to respond to thumbprints of keys using any + * of jose supported hash algorithms. */ + const char** hashes = supported_hashes(); + size_t idx; + json_t* jwk; + + /* Let's put together all the keys, including rotated ones. */ + json_auto_t* keys = json_deep_copy(tki->m_keys); + ASSERT(keys); + ASSERT(json_array_extend(keys, tki->m_rotated_keys) == 0); + ASSERT(json_array_size(keys) == (size_t)tki->m_keys_count); + + for (int i = 0; hashes[i]; i++) { + json_array_foreach(keys, idx, jwk) { + if (!jwk_valid_for_signing(jwk)) { + continue; + } + __attribute__((cleanup(cleanup_str))) char* thp = jwk_thumbprint(jwk, hashes[i]); + ASSERT_WITH_MSG(thp, "i = %d, hash = %s, key idx = %d", i, hashes[i], idx); + json_auto_t* jws = find_jws(tki, thp); + ASSERT_WITH_MSG(jws, "i = %d, hash = %s, key idx = %d, thp = %s", i, hashes[i], idx, thp); + + /* Signing keys should sign the payload, in addition to the + * default set of signing keys. */ + json_auto_t* sign = json_deep_copy(tki->m_sign); + ASSERT_WITH_MSG(sign, "i = %d, hash = %s, key idx = %d, thp = %s", i, hashes[i], idx, thp); + ASSERT_WITH_MSG(json_array_append(sign, jwk) == 0, "i = %d, hash = %s, key idx = %d, thp = %s", i, hashes[i], idx, thp); + ASSERT_WITH_MSG(jose_jws_ver(NULL, jws, NULL, sign, 1), "i = %d, hash = %s, key idx = %d, thp = %s", i, hashes[i], idx, thp); + } + } +} + +static void +test_find_jwk(void) +{ + const struct thp_result test_data[] = { + {"1HdF3XKRSsuZdkpXNurBPoL_pvxdvCOlHuhB4DP-4xWFqbZ51zo29kR4fSiT3BGy9UrHVJ26JMBLOA1vKq3lxA", 1}, + {"9U8qgy_YjyY6Isuq6QuiKEiYZgNJShcGgJx5FJzCu6m3N6zFaIPy_HDkxkVqAZ9E", 1}, + {"-bWkGaJi0Zdvxaj4DCp28umLcRA", 1}, + {"Cy73glFjs6B6RU7wy6vWxAc-2bJy5VJOT9LyK80eKgZ8k27wXZ-3rjsuNU5tua_yHWtluyoSYtjoKXfI0E8ESw", 1}, + {"kfjbqx_b3BsgPC87HwlOWL9daGMMHBzxcFLClw", 1}, + {"L4xg2tZXTEVbsK39bzOZM1jGWn3HtOxF5gh6F9YVf5Q", 1}, + {"LsVAV2ig5LlfstM8TRSf-c7IAkLpNYbIysNuRCVlxocRCGqAh6-f9PklM4nU4N-J", 1}, + {"OkAcDxYHNlo7-tul8OubYuWXB8CPEhAkcacCmhTclMU", 1}, + {"uZ0s8YTXcGcuWduWWBSiR2OjOVg", 1}, + {"WEpfFyeoNKkE2-TosN_bP-gd9UgRvQCZpVasZQ", 1}, + {NULL, 0}, + {"a", 0}, + {"foo", 0}, + {"bar", 0}, + {"XXXXXXXXXXXXXXXXXX", 0}, + }; + + __attribute__((cleanup(cleanup_tang_keys_info))) struct tang_keys_info* tki = read_keys(jwkdir); + + for (int i = 0, len = ARRAY_COUNT(test_data); i < len; i++) { + json_auto_t* tjwk = find_jwk(tki, test_data[i].thp); + ASSERT_WITH_MSG(!!tjwk == test_data[i].valid, "i = %d, thp = %s", i, test_data[i].thp); + } + /* Passing NULL to find_jwk should fail */ + json_auto_t* bad_jwk = find_jwk(tki, NULL); + ASSERT(bad_jwk == NULL); + + /* find_jwk should be able to respond to thumbprints of keys using any + * of jose supported hash algorithms. */ + const char** hashes = supported_hashes(); + size_t idx; + json_t* jwk; + + /* Let's put together all the keys, including rotated ones. */ + json_auto_t* keys = json_deep_copy(tki->m_keys); + ASSERT(keys); + ASSERT(json_array_extend(keys, tki->m_rotated_keys) == 0); + ASSERT(json_array_size(keys) == (size_t)tki->m_keys_count); + + for (int i = 0; hashes[i]; i++) { + json_array_foreach(keys, idx, jwk) { + if (!jwk_valid_for_deriving_keys(jwk)) { + continue; + } + __attribute__((cleanup(cleanup_str))) char* thp = jwk_thumbprint(jwk, hashes[i]); + json_auto_t* tjwk = find_jwk(tki, thp); + ASSERT_WITH_MSG(tjwk, "i = %d, hash = %s, key idx = %d, thp = %s", i, hashes[i], idx, thp); + } + } +} + +static void +test_read_keys(void) +{ + __attribute__((cleanup(cleanup_tang_keys_info))) struct tang_keys_info* tki = read_keys(jwkdir); + ASSERT(tki); + + /* + * Keys in tests/keys: + * - .uZ0s8YTXcGcuWduWWBSiR2OjOVg.jwk + * - .r4E2wG1u_YyKUo0N0rIK7jJF5Xg.jwk + * - qgmqJSo6AEEuVQY7zVlklqdTMqY.jwk + * - -bWkGaJi0Zdvxaj4DCp28umLcRA.jwk + */ + ASSERT(tki->m_keys_count == 4); + ASSERT(json_array_size(tki->m_keys) == 2); + ASSERT(json_array_size(tki->m_rotated_keys) == 2); + + const char* invalid_jwkdir = "foobar"; + __attribute__((cleanup(cleanup_tang_keys_info))) struct tang_keys_info* tki2 = read_keys(invalid_jwkdir); + ASSERT(tki2 == NULL); +} + +static void +run_tests(void) +{ + test_read_keys(); + test_find_jwk(); + test_find_jws(); + test_jwk_generate(); + test_is_hash(); + test_create_new_keys(); +} + +int main(int argc, char** argv) +{ + run_tests(); + return 0; +} --- /dev/null +++ b/tests/test-util.c @@ -0,0 +1,75 @@ +/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */ +/* + * Copyright (c) 2020 Red Hat, Inc. + * Author: Sergio Correia + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +#define _XOPEN_SOURCE 500L + +#include +#include +#include +#include +#include +#include + +#include "test-util.h" + +void +assert_func(const char* filename, + int lineno, + const char* funcname, + const char* expr, + const char* fmt, + ...) +{ + char buffer[MAX_BUF_LEN] = {}; + if (fmt) { + va_list ap; + va_start(ap, fmt); + vsnprintf(buffer, MAX_BUF_LEN, fmt, ap); + va_end(ap); + buffer[strcspn(buffer, "\r\n")] = '\0'; + } + fprintf(stderr, "%s:%d: assertion '%s' failed in %s(). %s\n", filename, + lineno, + expr, + funcname, + buffer); + abort(); +} + +static int +nftw_remove_callback(const char* path, const struct stat* stat, + int type, struct FTW* ftw) +{ + return remove(path); +} + +char* +create_tempdir(void) +{ + char template[] = "/tmp/tang.test.XXXXXX"; + char *tmpdir = mkdtemp(template); + return strdup(tmpdir); +} + +int +remove_tempdir(const char* path) +{ + return nftw(path, nftw_remove_callback, FOPEN_MAX, FTW_DEPTH|FTW_MOUNT|FTW_PHYS); +} + --- /dev/null +++ b/tests/test-util.h @@ -0,0 +1,46 @@ +/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */ +/* + * Copyright (c) 2020 Red Hat, Inc. + * Author: Sergio Correia + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +#pragma once + +#include + +#define ARRAY_COUNT(arr) (sizeof(arr)/sizeof(0[arr])) +#define MAX_BUF_LEN 2048 + +void +assert_func(const char* /* filename */, + int /* line number */, + const char* /* function name */, + const char* /* expression */, + const char* /* format */, + ...); + +#define ASSERT_WITH_MSG(expr, fmt, ...) \ + if (!(expr)) \ + assert_func(__FILE__, __LINE__, __FUNCTION__, #expr, fmt, ##__VA_ARGS__) + +#define ASSERT(expr) \ + ASSERT_WITH_MSG(expr, NULL) + +char* +create_tempdir(void); + +int +remove_tempdir(const char* /* path */);