mirror of
https://github.com/asterisk/asterisk.git
synced 2025-09-03 11:25:35 +00:00
res_stir_shaken: Add unit tests for signing and verification.
Added two unit tests, one for signing and another for verifying. stir_shaken_sign checks to make sure that all the required parameters are passed in and then signs the actual payload. If a signature is produced and a payload returned as a result, the test passes. stir_shaken_verify takes the signature from a signed payload to verify. This unit test also verifies that all the required information is passed in, and then attempts to verify the signature. If verification is successful and a payload is returned, the test passes. Change-Id: I9fa43380f861ccf710cd0f6b6c102a517c86ea13
This commit is contained in:
committed by
Friendly Automation
parent
a7aaee70c6
commit
f506cc4896
@@ -35,6 +35,7 @@
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/global_datastores.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/test.h"
|
||||
|
||||
#include "asterisk/res_stir_shaken.h"
|
||||
#include "res_stir_shaken/stir_shaken.h"
|
||||
@@ -1195,6 +1196,348 @@ static struct ast_custom_function stir_shaken_function = {
|
||||
.read = stir_shaken_read,
|
||||
};
|
||||
|
||||
#ifdef TEST_FRAMEWORK
|
||||
|
||||
static void test_stir_shaken_add_fake_astdb_entry(const char *public_key_url, const char *file_path)
|
||||
{
|
||||
struct timeval expires = ast_tvnow();
|
||||
char time_buf[32];
|
||||
char hash[41];
|
||||
|
||||
ast_sha1_hash(hash, public_key_url);
|
||||
add_public_key_to_astdb(public_key_url, file_path);
|
||||
snprintf(time_buf, sizeof(time_buf), "%30lu", expires.tv_sec + 300);
|
||||
|
||||
ast_db_put(hash, "expiration", time_buf);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Create a private or public key certificate
|
||||
*
|
||||
* \param file_path The path of the file to create
|
||||
* \param private Set to 0 if public, 1 if private
|
||||
*
|
||||
* \retval -1 on failure
|
||||
* \retval 0 on success
|
||||
*/
|
||||
static int test_stir_shaken_write_temp_key(char *file_path, int private)
|
||||
{
|
||||
FILE *file;
|
||||
int fd;
|
||||
char *data;
|
||||
char *type = private ? "private" : "public";
|
||||
char *private_data =
|
||||
"-----BEGIN EC PRIVATE KEY-----\n"
|
||||
"MHcCAQEEIFkNGlrmRky2j7wmjGBGoPFBsyEQELmEYN02BiiG508noAoGCCqGSM49\n"
|
||||
"AwEHoUQDQgAECwCaeAYwVG/FAnEnkwaucz6o047iSWq3cJBBUc0n2ZlUDr5VywAz\n"
|
||||
"MZ86EthIqF3CGZjhLHn0xRITXYwfqTtWBw==\n"
|
||||
"-----END EC PRIVATE KEY-----";
|
||||
char *public_data =
|
||||
"-----BEGIN PUBLIC KEY-----\n"
|
||||
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECwCaeAYwVG/FAnEnkwaucz6o047i\n"
|
||||
"SWq3cJBBUc0n2ZlUDr5VywAzMZ86EthIqF3CGZjhLHn0xRITXYwfqTtWBw==\n"
|
||||
"-----END PUBLIC KEY-----";
|
||||
|
||||
fd = mkstemp(file_path);
|
||||
if (fd < 0) {
|
||||
ast_log(LOG_ERROR, "Failed to create temp %s file: %s\n", type, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
file = fdopen(fd, "w");
|
||||
if (!file) {
|
||||
ast_log(LOG_ERROR, "Failed to create temp %s key file: %s\n", type, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
data = private ? private_data : public_data;
|
||||
if (fputs(data, file) == EOF) {
|
||||
ast_log(LOG_ERROR, "Failed to write temp %s key file\n", type);
|
||||
fclose(file);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
AST_TEST_DEFINE(test_stir_shaken_sign)
|
||||
{
|
||||
char *caller_id_number = "1234567";
|
||||
char file_path[] = "/tmp/stir_shaken_private.XXXXXX";
|
||||
RAII_VAR(char *, rm_on_exit, file_path, unlink);
|
||||
RAII_VAR(struct ast_json *, json, NULL, ast_json_free);
|
||||
RAII_VAR(struct ast_stir_shaken_payload *, payload, NULL, ast_stir_shaken_payload_free);
|
||||
|
||||
switch (cmd) {
|
||||
case TEST_INIT:
|
||||
info->name = "stir_shaken_sign";
|
||||
info->category = "/res/res_stir_shaken/";
|
||||
info->summary = "STIR/SHAKEN sign unit test";
|
||||
info->description =
|
||||
"Tests signing a JWT with a private key.";
|
||||
return AST_TEST_NOT_RUN;
|
||||
case TEST_EXECUTE:
|
||||
break;
|
||||
}
|
||||
|
||||
/* We only need a private key to sign */
|
||||
test_stir_shaken_write_temp_key(file_path, 1);
|
||||
test_stir_shaken_create_cert(caller_id_number, file_path);
|
||||
|
||||
/* Test missing header section */
|
||||
json = ast_json_pack("{s: {s: {s: s}}}", "payload", "orig", "tn", caller_id_number);
|
||||
payload = ast_stir_shaken_sign(json);
|
||||
if (payload) {
|
||||
ast_test_status_update(test, "Signed an invalid JWT (missing 'header')\n");
|
||||
test_stir_shaken_cleanup_cert(caller_id_number);
|
||||
return AST_TEST_FAIL;
|
||||
}
|
||||
|
||||
/* Test missing payload section */
|
||||
ast_json_free(json);
|
||||
json = ast_json_pack("{s: {s: s, s: s, s: s, s: s}}", "header", "alg",
|
||||
STIR_SHAKEN_ENCRYPTION_ALGORITHM, "ppt", STIR_SHAKEN_PPT, "typ", STIR_SHAKEN_TYPE,
|
||||
"x5u", "http://testing123");
|
||||
payload = ast_stir_shaken_sign(json);
|
||||
if (payload) {
|
||||
ast_test_status_update(test, "Signed an invalid JWT (missing 'payload')\n");
|
||||
test_stir_shaken_cleanup_cert(caller_id_number);
|
||||
return AST_TEST_FAIL;
|
||||
}
|
||||
|
||||
/* Test missing alg section */
|
||||
ast_json_free(json);
|
||||
json = ast_json_pack("{s: {s: s, s: s, s: s}, s: {s: {s: s}}}", "header", "ppt",
|
||||
STIR_SHAKEN_PPT, "typ", STIR_SHAKEN_TYPE, "x5u", "http://testing123", "payload",
|
||||
"orig", "tn", caller_id_number);
|
||||
payload = ast_stir_shaken_sign(json);
|
||||
if (payload) {
|
||||
ast_test_status_update(test, "Signed an invalid JWT (missing 'alg')\n");
|
||||
test_stir_shaken_cleanup_cert(caller_id_number);
|
||||
return AST_TEST_FAIL;
|
||||
}
|
||||
|
||||
/* Test invalid alg value */
|
||||
ast_json_free(json);
|
||||
json = ast_json_pack("{s: {s: s, s: s, s: s, s: s}, s: {s: {s: s}}}", "header", "alg",
|
||||
"invalid algorithm", "ppt", STIR_SHAKEN_PPT, "typ", STIR_SHAKEN_TYPE,
|
||||
"x5u", "http://testing123", "payload", "orig", "tn", caller_id_number);
|
||||
payload = ast_stir_shaken_sign(json);
|
||||
if (payload) {
|
||||
ast_test_status_update(test, "Signed an invalid JWT (wrong 'alg')\n");
|
||||
test_stir_shaken_cleanup_cert(caller_id_number);
|
||||
return AST_TEST_FAIL;
|
||||
}
|
||||
|
||||
/* Test missing ppt section */
|
||||
ast_json_free(json);
|
||||
json = ast_json_pack("{s: {s: s, s: s, s: s}, s: {s: {s: s}}}", "header", "alg",
|
||||
STIR_SHAKEN_ENCRYPTION_ALGORITHM, "typ", STIR_SHAKEN_TYPE, "x5u", "http://testing123",
|
||||
"payload", "orig", "tn", caller_id_number);
|
||||
payload = ast_stir_shaken_sign(json);
|
||||
if (payload) {
|
||||
ast_test_status_update(test, "Signed an invalid JWT (missing 'ppt')\n");
|
||||
test_stir_shaken_cleanup_cert(caller_id_number);
|
||||
return AST_TEST_FAIL;
|
||||
}
|
||||
|
||||
/* Test invalid ppt value */
|
||||
ast_json_free(json);
|
||||
json = ast_json_pack("{s: {s: s, s: s, s: s, s: s}, s: {s: {s: s}}}", "header", "alg",
|
||||
STIR_SHAKEN_ENCRYPTION_ALGORITHM, "ppt", "invalid ppt", "typ", STIR_SHAKEN_TYPE,
|
||||
"x5u", "http://testing123", "payload", "orig", "tn", caller_id_number);
|
||||
payload = ast_stir_shaken_sign(json);
|
||||
if (payload) {
|
||||
ast_test_status_update(test, "Signed an invalid JWT (wrong 'ppt')\n");
|
||||
test_stir_shaken_cleanup_cert(caller_id_number);
|
||||
return AST_TEST_FAIL;
|
||||
}
|
||||
|
||||
/* Test missing typ section */
|
||||
ast_json_free(json);
|
||||
json = ast_json_pack("{s: {s: s, s: s, s: s}, s: {s: {s: s}}}", "header", "alg",
|
||||
STIR_SHAKEN_ENCRYPTION_ALGORITHM, "ppt", STIR_SHAKEN_PPT, "x5u", "http://testing123",
|
||||
"payload", "orig", "tn", caller_id_number);
|
||||
payload = ast_stir_shaken_sign(json);
|
||||
if (payload) {
|
||||
ast_test_status_update(test, "Signed an invalid JWT (missing 'typ')\n");
|
||||
test_stir_shaken_cleanup_cert(caller_id_number);
|
||||
return AST_TEST_FAIL;
|
||||
}
|
||||
|
||||
/* Test invalid typ value */
|
||||
ast_json_free(json);
|
||||
json = ast_json_pack("{s: {s: s, s: s, s: s, s: s}, s: {s: {s: s}}}", "header", "alg",
|
||||
STIR_SHAKEN_ENCRYPTION_ALGORITHM, "ppt", STIR_SHAKEN_PPT, "typ", "invalid typ",
|
||||
"x5u", "http://testing123", "payload", "orig", "tn", caller_id_number);
|
||||
payload = ast_stir_shaken_sign(json);
|
||||
if (payload) {
|
||||
ast_test_status_update(test, "Signed an invalid JWT (wrong 'typ')\n");
|
||||
test_stir_shaken_cleanup_cert(caller_id_number);
|
||||
return AST_TEST_FAIL;
|
||||
}
|
||||
|
||||
/* Test missing orig section */
|
||||
ast_json_free(json);
|
||||
json = ast_json_pack("{s: {s: s, s: s, s: s, s: s}, s: {s: s}}", "header", "alg",
|
||||
STIR_SHAKEN_ENCRYPTION_ALGORITHM, "ppt", STIR_SHAKEN_PPT, "typ", STIR_SHAKEN_TYPE,
|
||||
"x5u", "http://testing123", "payload", "filler", "filler");
|
||||
payload = ast_stir_shaken_sign(json);
|
||||
if (payload) {
|
||||
ast_test_status_update(test, "Signed an invalid JWT (missing 'orig')\n");
|
||||
test_stir_shaken_cleanup_cert(caller_id_number);
|
||||
return AST_TEST_FAIL;
|
||||
}
|
||||
|
||||
/* Test missing tn section */
|
||||
ast_json_free(json);
|
||||
json = ast_json_pack("{s: {s: s, s: s, s: s, s: s}, s: {s: s}}", "header", "alg",
|
||||
STIR_SHAKEN_ENCRYPTION_ALGORITHM, "ppt", STIR_SHAKEN_PPT, "typ", STIR_SHAKEN_TYPE,
|
||||
"x5u", "http://testing123", "payload", "orig", "filler");
|
||||
payload = ast_stir_shaken_sign(json);
|
||||
if (payload) {
|
||||
ast_test_status_update(test, "Signed an invalid JWT (missing 'tn')\n");
|
||||
test_stir_shaken_cleanup_cert(caller_id_number);
|
||||
return AST_TEST_FAIL;
|
||||
}
|
||||
|
||||
/* Test valid JWT */
|
||||
ast_json_free(json);
|
||||
json = ast_json_pack("{s: {s: s, s: s, s: s, s: s}, s: {s: {s: s}}}", "header", "alg",
|
||||
STIR_SHAKEN_ENCRYPTION_ALGORITHM, "ppt", STIR_SHAKEN_PPT, "typ", STIR_SHAKEN_TYPE,
|
||||
"x5u", "http://testing123", "payload", "orig", "tn", caller_id_number);
|
||||
payload = ast_stir_shaken_sign(json);
|
||||
if (!payload) {
|
||||
ast_test_status_update(test, "Failed to sign a valid JWT\n");
|
||||
test_stir_shaken_cleanup_cert(caller_id_number);
|
||||
return AST_TEST_FAIL;
|
||||
}
|
||||
|
||||
test_stir_shaken_cleanup_cert(caller_id_number);
|
||||
|
||||
return AST_TEST_PASS;
|
||||
}
|
||||
|
||||
AST_TEST_DEFINE(test_stir_shaken_verify)
|
||||
{
|
||||
char *caller_id_number = "1234567";
|
||||
char *public_key_url = "http://testing123";
|
||||
char *header = "{\"header\": \"placeholder\"}";
|
||||
char public_path[] = "/tmp/stir_shaken_public.XXXXXX";
|
||||
char private_path[] = "/tmp/stir_shaken_public.XXXXXX";
|
||||
RAII_VAR(char *, rm_on_exit_public, public_path, unlink);
|
||||
RAII_VAR(char *, rm_on_exit_private, private_path, unlink);
|
||||
RAII_VAR(char *, json_str, NULL, ast_json_free);
|
||||
RAII_VAR(struct ast_json *, json, NULL, ast_json_free);
|
||||
RAII_VAR(struct ast_stir_shaken_payload *, signed_payload, NULL, ast_stir_shaken_payload_free);
|
||||
RAII_VAR(struct ast_stir_shaken_payload *, returned_payload, NULL, ast_stir_shaken_payload_free);
|
||||
|
||||
switch (cmd) {
|
||||
case TEST_INIT:
|
||||
info->name = "stir_shaken_verify";
|
||||
info->category = "/res/res_stir_shaken/";
|
||||
info->summary = "STIR/SHAKEN verify unit test";
|
||||
info->description =
|
||||
"Tests verifying a signature with a public key";
|
||||
return AST_TEST_NOT_RUN;
|
||||
case TEST_EXECUTE:
|
||||
break;
|
||||
}
|
||||
|
||||
/* We need the private key to sign, but we also need the corresponding
|
||||
* public key to verify */
|
||||
test_stir_shaken_write_temp_key(public_path, 0);
|
||||
test_stir_shaken_write_temp_key(private_path, 1);
|
||||
test_stir_shaken_create_cert(caller_id_number, private_path);
|
||||
|
||||
/* Get the signature */
|
||||
json = ast_json_pack("{s: {s: s, s: s, s: s, s: s}, s: {s: {s: s}}}", "header", "alg",
|
||||
STIR_SHAKEN_ENCRYPTION_ALGORITHM, "ppt", STIR_SHAKEN_PPT, "typ", STIR_SHAKEN_TYPE,
|
||||
"x5u", public_key_url, "payload", "orig", "tn", caller_id_number);
|
||||
signed_payload = ast_stir_shaken_sign(json);
|
||||
if (!signed_payload) {
|
||||
ast_test_status_update(test, "Failed to sign a valid JWT\n");
|
||||
test_stir_shaken_cleanup_cert(caller_id_number);
|
||||
return AST_TEST_FAIL;
|
||||
}
|
||||
|
||||
/* Get the message to use for verification */
|
||||
json_str = ast_json_dump_string(json);
|
||||
if (!json_str) {
|
||||
ast_test_status_update(test, "Failed to create string from JSON\n");
|
||||
test_stir_shaken_cleanup_cert(caller_id_number);
|
||||
return AST_TEST_FAIL;
|
||||
}
|
||||
|
||||
/* Test empty header parameter */
|
||||
returned_payload = ast_stir_shaken_verify("", json_str, (const char *)signed_payload->signature,
|
||||
STIR_SHAKEN_ENCRYPTION_ALGORITHM, public_key_url);
|
||||
if (returned_payload) {
|
||||
ast_test_status_update(test, "Verified a signature with missing 'header'\n");
|
||||
test_stir_shaken_cleanup_cert(caller_id_number);
|
||||
return AST_TEST_FAIL;
|
||||
}
|
||||
|
||||
/* Test empty payload parameter */
|
||||
returned_payload = ast_stir_shaken_verify(header, "", (const char *)signed_payload->signature,
|
||||
STIR_SHAKEN_ENCRYPTION_ALGORITHM, public_key_url);
|
||||
if (returned_payload) {
|
||||
ast_test_status_update(test, "Verified a signature with missing 'payload'\n");
|
||||
test_stir_shaken_cleanup_cert(caller_id_number);
|
||||
return AST_TEST_FAIL;
|
||||
}
|
||||
|
||||
/* Test empty signature parameter */
|
||||
returned_payload = ast_stir_shaken_verify(header, json_str, "",
|
||||
STIR_SHAKEN_ENCRYPTION_ALGORITHM, public_key_url);
|
||||
if (returned_payload) {
|
||||
ast_test_status_update(test, "Verified a signature with missing 'signature'\n");
|
||||
test_stir_shaken_cleanup_cert(caller_id_number);
|
||||
return AST_TEST_FAIL;
|
||||
}
|
||||
|
||||
/* Test empty algorithm parameter */
|
||||
returned_payload = ast_stir_shaken_verify(header, json_str, (const char *)signed_payload->signature,
|
||||
"", public_key_url);
|
||||
if (returned_payload) {
|
||||
ast_test_status_update(test, "Verified a signature with missing 'algorithm'\n");
|
||||
test_stir_shaken_cleanup_cert(caller_id_number);
|
||||
return AST_TEST_FAIL;
|
||||
}
|
||||
|
||||
/* Test empty public key URL */
|
||||
returned_payload = ast_stir_shaken_verify(header, json_str, (const char *)signed_payload->signature,
|
||||
STIR_SHAKEN_ENCRYPTION_ALGORITHM, "");
|
||||
if (returned_payload) {
|
||||
ast_test_status_update(test, "Verified a signature with missing 'public key URL'\n");
|
||||
test_stir_shaken_cleanup_cert(caller_id_number);
|
||||
return AST_TEST_FAIL;
|
||||
}
|
||||
|
||||
/* Trick the function into thinking we've already downloaded the key */
|
||||
test_stir_shaken_add_fake_astdb_entry(public_key_url, public_path);
|
||||
|
||||
/* Verify a valid signature */
|
||||
returned_payload = ast_stir_shaken_verify(header, json_str, (const char *)signed_payload->signature,
|
||||
STIR_SHAKEN_ENCRYPTION_ALGORITHM, public_key_url);
|
||||
if (!returned_payload) {
|
||||
ast_test_status_update(test, "Failed to verify a valid signature\n");
|
||||
remove_public_key_from_astdb(public_key_url);
|
||||
test_stir_shaken_cleanup_cert(caller_id_number);
|
||||
return AST_TEST_FAIL;
|
||||
}
|
||||
|
||||
remove_public_key_from_astdb(public_key_url);
|
||||
|
||||
test_stir_shaken_cleanup_cert(caller_id_number);
|
||||
|
||||
return AST_TEST_PASS;
|
||||
}
|
||||
|
||||
#endif /* TEST_FRAMEWORK */
|
||||
|
||||
static int reload_module(void)
|
||||
{
|
||||
if (stir_shaken_sorcery) {
|
||||
@@ -1217,6 +1560,9 @@ static int unload_module(void)
|
||||
|
||||
res |= ast_custom_function_unregister(&stir_shaken_function);
|
||||
|
||||
AST_TEST_UNREGISTER(test_stir_shaken_sign);
|
||||
AST_TEST_UNREGISTER(test_stir_shaken_verify);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -1248,6 +1594,9 @@ static int load_module(void)
|
||||
|
||||
res |= ast_custom_function_register(&stir_shaken_function);
|
||||
|
||||
AST_TEST_REGISTER(test_stir_shaken_sign);
|
||||
AST_TEST_REGISTER(test_stir_shaken_verify);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user