; This file is used by the res_stir_shaken module to configure parameters
; used for STIR/SHAKEN.
;
+; There are 2 sides to STIR/SHAKEN: attestation and verification.
+;
+; Attestation is done on outgoing calls and makes use out of the certificate
+; objects. The cert located at path will be used to sign, and the cert
+; located at public_cert_url will be placed in the Identity header to let the
+; remote side know where to download the public cert from. These 2 certs must
+; match; that is, the cert located at public_cert_url must be the public cert
+; derived from the private cert located at path.
+;
+; Verification is done on incoming calls and doesn't rely on cert objects
+; defined in this file.
+;
+; The general section applies to all STIR/SHAKEN operations. However,
+; cache_max_size, curl_timeout, and signature_timeout only apply to the
+; verification side.
+;
+; It's important to note that downloaded certificates are stored in
+; <ast_config_AST_DATA_DIR>/keys/stir_shaken, which is usually
+; /etc/asterisk/keys/stir_shaken, but may be changed depending on where your
+; config directory is.
+;
+; Visit the wiki page:
+; https://wiki.asterisk.org/wiki/display/AST/STIR+and+SHAKEN
;
; [general]
;
; Path to a directory containing certificates
;path=/etc/asterisk/stir
;
-; URL to the public key(s). Must contain variable '${CERTIFICATE}' used for
-; substitution
-;public_key_url=http://mycompany.com/${CERTIFICATE}.pub
+; URL to the public certificate(s). Must contain variable '${CERTIFICATE}' used for
+; substitution. '${CERTIFICATE}' will be replaced by the names of the files located
+; at path.
+; This will be put in the Identity header when signing.
+;public_cert_url=http://mycompany.com/${CERTIFICATE}.pem
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; type must be "certificate"
;type=certificate
;
-; File path to a certificate
-;path=/etc/asterisk/stir/alice.crt
+; File path to a certificate. This can be RSA or ECDSA, but eventually only ECDSA will be supported.
+;path=/etc/asterisk/stir/alice.pem
;
-; URL to the public key
-;public_key_url=http://mycompany.com/alice.pub
+; URL to the public certificate. Must be of type X509 and be derived from the
+; certificate located at path.
+; This will be put in the identity header when signing.
+;public_cert_url=http://mycompany.com/alice.pem
;
; The caller ID number to match on
;caller_id_number=1234567
--- /dev/null
+Subject: STIR/SHAKEN
+
+The configuration option public_key_url in stir_shaken.conf
+has been renamed to public_cert_url to better fit what it
+contains. Only the name has changed - functionality is the
+same.
unsigned char *ast_stir_shaken_payload_get_signature(const struct ast_stir_shaken_payload *payload);
/*!
- * \brief Retrieve the value for 'public_key_url' from an ast_stir_shaken_payload
+ * \brief Retrieve the value for 'public_cert_url' from an ast_stir_shaken_payload
*
* \param payload The payload
*
* \retval The public key URL
*/
-char *ast_stir_shaken_payload_get_public_key_url(const struct ast_stir_shaken_payload *payload);
+char *ast_stir_shaken_payload_get_public_cert_url(const struct ast_stir_shaken_payload *payload);
/*!
* \brief Retrieve the value for 'signature_timeout' from 'general' config object
* \param payload The payload section
* \param signature The payload signature
* \param algorithm The signature algorithm
- * \param public_key_url The public key URL
+ * \param public_cert_url The public key URL
*
* \retval ast_stir_shaken_payload on success
* \retval NULL on failure
*/
struct ast_stir_shaken_payload *ast_stir_shaken_verify(const char *header, const char *payload, const char *signature,
- const char *algorithm, const char *public_key_url);
+ const char *algorithm, const char *public_cert_url);
/*!
* \brief Retrieve the stir/shaken sorcery context
RAII_VAR(char *, payload, NULL, ast_free);
char *signature;
char *algorithm;
- char *public_key_url;
+ char *public_cert_url;
char *attestation;
int mismatch = 0;
struct ast_stir_shaken_payload *ss_payload;
/* Trim "info=<" to get public key URL */
strtok_r(identity_hdr_val, "<", &identity_hdr_val);
- public_key_url = strtok_r(identity_hdr_val, ">", &identity_hdr_val);
- if (ast_strlen_zero(public_key_url)) {
+ public_cert_url = strtok_r(identity_hdr_val, ">", &identity_hdr_val);
+ if (ast_strlen_zero(public_cert_url)) {
ast_stir_shaken_add_verification(chan, caller_id, "", AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED);
return 0;
}
attestation = get_attestation_from_payload(payload);
- ss_payload = ast_stir_shaken_verify(header, payload, signature, algorithm, public_key_url);
+ ss_payload = ast_stir_shaken_verify(header, payload, signature, algorithm, public_cert_url);
if (!ss_payload) {
ast_stir_shaken_add_verification(chan, caller_id, attestation, AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED);
return 0;
pj_str_t identity_val;
pjsip_fromto_hdr *old_identity;
char *signature;
- char *public_key_url;
+ char *public_cert_url;
struct ast_json *header;
struct ast_json *payload;
char *dumped_string;
}
signature = (char *)ast_stir_shaken_payload_get_signature(ss_payload);
- public_key_url = ast_stir_shaken_payload_get_public_key_url(ss_payload);
+ public_cert_url = ast_stir_shaken_payload_get_public_cert_url(ss_payload);
/* The format for the identity header:
- * header.payload.signature;info=<public_key_url>alg=STIR_SHAKEN_ENCRYPTION_ALGORITHM;ppt=STIR_SHAKEN_PPT
+ * header.payload.signature;info=<public_cert_url>alg=STIR_SHAKEN_ENCRYPTION_ALGORITHM;ppt=STIR_SHAKEN_PPT
*/
combined_size = strlen(encoded_header) + 1 + strlen(encoded_payload) + 1
- + strlen(signature) + strlen(";info=<>alg=;ppt=") + strlen(public_key_url)
+ + strlen(signature) + strlen(";info=<>alg=;ppt=") + strlen(public_cert_url)
+ strlen(STIR_SHAKEN_ENCRYPTION_ALGORITHM) + strlen(STIR_SHAKEN_PPT) + 1;
combined_str = ast_calloc(1, combined_size);
if (!combined_str) {
return;
}
snprintf(combined_str, combined_size, "%s.%s.%s;info=<%s>alg=%s;ppt=%s", encoded_header,
- encoded_payload, signature, public_key_url, STIR_SHAKEN_ENCRYPTION_ALGORITHM, STIR_SHAKEN_PPT);
+ encoded_payload, signature, public_cert_url, STIR_SHAKEN_ENCRYPTION_ALGORITHM, STIR_SHAKEN_PPT);
identity_val = pj_str(combined_str);
identity_hdr = pjsip_generic_string_hdr_create(tdata->pool, &identity_str, &identity_val);
<configOption name="path" default="">
<synopsis>Path to a directory containing certificates</synopsis>
</configOption>
- <configOption name="public_key_url" default="">
- <synopsis>URL to the public key(s)</synopsis>
+ <configOption name="public_cert_url" default="">
+ <synopsis>URL to the public certificate(s)</synopsis>
<description><para>
Must be a valid http, or https, URL. The URL must also contain the ${CERTIFICATE} variable, which is used for public key name substitution.
For example: http://mycompany.com/${CERTIFICATE}.pub
<configOption name="path" default="">
<synopsis>File path to a certificate</synopsis>
</configOption>
- <configOption name="public_key_url" default="">
- <synopsis>URL to the public key</synopsis>
+ <configOption name="public_cert_url" default="">
+ <synopsis>URL to the public certificate</synopsis>
<description><para>
Must be a valid http, or https, URL.
</para></description>
unsigned char *signature;
/*! The algorithm used */
char *algorithm;
- /*! THe URL to the public key for the certificate */
- char *public_key_url;
+ /*! THe URL to the public certificate */
+ char *public_cert_url;
};
struct ast_sorcery *ast_stir_shaken_sorcery(void)
ast_json_unref(payload->header);
ast_json_unref(payload->payload);
ast_free(payload->algorithm);
- ast_free(payload->public_key_url);
+ ast_free(payload->public_cert_url);
ast_free(payload->signature);
ast_free(payload);
return payload ? payload->signature : NULL;
}
-char *ast_stir_shaken_payload_get_public_key_url(const struct ast_stir_shaken_payload *payload)
+char *ast_stir_shaken_payload_get_public_cert_url(const struct ast_stir_shaken_payload *payload)
{
- return payload ? payload->public_key_url : NULL;
+ return payload ? payload->public_cert_url : NULL;
}
unsigned int ast_stir_shaken_get_signature_timeout(void)
* \brief Sets the expiration for the public key based on the provided fields.
* If Cache-Control is present, use it. Otherwise, use Expires.
*
- * \param hash The hash for the public key URL
+ * \param public_cert_url The URL to the public certificate
* \param data The CURL callback data containing expiration data
*/
-static void set_public_key_expiration(const char *public_key_url, const struct curl_cb_data *data)
+static void set_public_key_expiration(const char *public_cert_url, const struct curl_cb_data *data)
{
char time_buf[32];
char *value;
struct timeval actual_expires = ast_tvnow();
char hash[41];
- ast_sha1_hash(hash, public_key_url);
+ ast_sha1_hash(hash, public_cert_url);
value = curl_cb_data_get_cache_control(data);
if (!ast_strlen_zero(value)) {
/*!
* \brief Check to see if the public key is expired
*
- * \param public_key_url The public key URL
+ * \param public_cert_url The public cert URL
*
* \retval 1 if expired
* \retval 0 if not expired
*/
-static int public_key_is_expired(const char *public_key_url)
+static int public_key_is_expired(const char *public_cert_url)
{
struct timeval current_time = ast_tvnow();
struct timeval expires = { .tv_sec = 0, .tv_usec = 0 };
char expiration[32];
char hash[41];
- ast_sha1_hash(hash, public_key_url);
+ ast_sha1_hash(hash, public_cert_url);
ast_db_get(hash, "expiration", expiration, sizeof(expiration));
if (ast_strlen_zero(expiration)) {
/*!
* \brief Returns the path to the downloaded file for the provided URL
*
- * \param public_key_url The public key URL
+ * \param public_cert_url The public cert URL
*
* \retval Empty string if not present in AstDB
* \retval The file path if present in AstDB
*/
-static char *get_path_to_public_key(const char *public_key_url)
+static char *get_path_to_public_key(const char *public_cert_url)
{
char hash[41];
char file_path[MAX_PATH_LEN];
- ast_sha1_hash(hash, public_key_url);
+ ast_sha1_hash(hash, public_cert_url);
ast_db_get(hash, "path", file_path, sizeof(file_path));
/*!
* \brief Add the public key details and file path to AstDB
*
- * \param public_key_url The public key URL
+ * \param public_cert_url The public cert URL
* \param filepath The path to the file
*/
-static void add_public_key_to_astdb(const char *public_key_url, const char *filepath)
+static void add_public_key_to_astdb(const char *public_cert_url, const char *filepath)
{
char hash[41];
- ast_sha1_hash(hash, public_key_url);
+ ast_sha1_hash(hash, public_cert_url);
- ast_db_put(AST_DB_FAMILY, public_key_url, hash);
+ ast_db_put(AST_DB_FAMILY, public_cert_url, hash);
ast_db_put(hash, "path", filepath);
}
/*!
* \brief Remove the public key details and associated information from AstDB
*
- * \param public_key_url The public key URL
+ * \param public_cert_url The public cert URL
*/
-static void remove_public_key_from_astdb(const char *public_key_url)
+static void remove_public_key_from_astdb(const char *public_cert_url)
{
char hash[41];
char filepath[MAX_PATH_LEN];
- ast_sha1_hash(hash, public_key_url);
+ ast_sha1_hash(hash, public_cert_url);
/* Remove this public key from storage */
ast_db_get(hash, "path", filepath, sizeof(filepath));
/* Remove the actual file from the system */
remove(filepath);
- ast_db_del(AST_DB_FAMILY, public_key_url);
+ ast_db_del(AST_DB_FAMILY, public_cert_url);
ast_db_deltree(hash, NULL);
}
}
/*!
- * \brief CURL the file located at public_key_url to the specified path
+ * \brief CURL the file located at public_cert_url to the specified path
*
- * \param public_key_url The public key URL
+ * \note filename will need to be freed by the caller
+ *
+ * \param public_cert_url The public cert URL
* \param path The path to download the file to
*
- * \retval -1 on failure
- * \retval 0 on success
+ * \retval NULL on failure
+ * \retval full path filename on success
*/
-static int run_curl(const char *public_key_url, const char *path)
+static char *run_curl(const char *public_cert_url, const char *path)
{
struct curl_cb_data *data;
+ char *filename;
data = curl_cb_data_create();
if (!data) {
ast_log(LOG_ERROR, "Failed to create CURL callback data\n");
- return -1;
+ return NULL;
}
- if (curl_public_key(public_key_url, path, data)) {
- ast_log(LOG_ERROR, "Could not retrieve public key for '%s'\n", public_key_url);
+ filename = curl_public_key(public_cert_url, path, data);
+ if (!filename) {
+ ast_log(LOG_ERROR, "Could not retrieve public key for '%s'\n", public_cert_url);
curl_cb_data_free(data);
- return -1;
+ return NULL;
}
- set_public_key_expiration(public_key_url, data);
+ set_public_key_expiration(public_cert_url, data);
curl_cb_data_free(data);
- return 0;
+ return filename;
}
/*!
- * \brief Downloads the public key from public_key_url. If curl is non-zero, that signals
+ * \brief Downloads the public cert from public_cert_url. If curl is non-zero, that signals
* CURL has already been run, and we should bail here. The entry is added to AstDB as well.
*
- * \param public_key_url The public key URL
+ * \note filename will need to be freed by the caller
+ *
+ * \param public_cert_url The public cert URL
* \param path The path to download the file to
* \param curl Flag signaling if we have run CURL or not
*
- * \retval -1 on failure
- * \retval 0 on success
+ * \retval NULL on failure
+ * \retval full path filename on success
*/
-static int curl_and_check_expiration(const char *public_key_url, const char *path, int *curl)
+static char *curl_and_check_expiration(const char *public_cert_url, const char *path, int *curl)
{
+ char *filename;
+
if (curl) {
ast_log(LOG_ERROR, "Already downloaded public key '%s'\n", path);
- return -1;
+ return NULL;
}
- if (run_curl(public_key_url, path)) {
- return -1;
+ filename = run_curl(public_cert_url, path);
+ if (!filename) {
+ return NULL;
}
- if (public_key_is_expired(public_key_url)) {
+ if (public_key_is_expired(public_cert_url)) {
ast_log(LOG_ERROR, "Newly downloaded public key '%s' is expired\n", path);
- return -1;
+ ast_free(filename);
+ return NULL;
}
*curl = 1;
- add_public_key_to_astdb(public_key_url, path);
+ add_public_key_to_astdb(public_cert_url, filename);
- return 0;
+ return filename;
}
struct ast_stir_shaken_payload *ast_stir_shaken_verify(const char *header, const char *payload, const char *signature,
- const char *algorithm, const char *public_key_url)
+ const char *algorithm, const char *public_cert_url)
{
struct ast_stir_shaken_payload *ret_payload;
EVP_PKEY *public_key;
- char *filename;
int curl = 0;
RAII_VAR(char *, file_path, NULL, ast_free);
+ RAII_VAR(char *, dir_path, NULL, ast_free);
RAII_VAR(char *, combined_str, NULL, ast_free);
size_t combined_size;
return NULL;
}
- if (ast_strlen_zero(public_key_url)) {
- ast_log(LOG_ERROR, "'public_key_url' is required for STIR/SHAKEN verification\n");
+ if (ast_strlen_zero(public_cert_url)) {
+ ast_log(LOG_ERROR, "'public_cert_url' is required for STIR/SHAKEN verification\n");
return NULL;
}
- /* Check to see if we have already downloaded this public key. The reason we
+ /* Check to see if we have already downloaded this public cert. The reason we
* store the file path is because:
*
* 1. If, for some reason, the default directory changes, we still know where
* to look for the files we already have.
*
- * 2. In the future, if we want to add a way to store the keys in multiple
+ * 2. In the future, if we want to add a way to store the certs in multiple
* {configurable) directories, we already have the storage mechanism in place.
* The only thing that would be left to do is pull from the configuration.
*/
- file_path = get_path_to_public_key(public_key_url);
+ file_path = get_path_to_public_key(public_cert_url);
+ if (ast_asprintf(&dir_path, "%s/keys/%s", ast_config_AST_DATA_DIR, STIR_SHAKEN_DIR_NAME) < 0) {
+ return NULL;
+ }
/* If we don't have an entry in AstDB, CURL from the provided URL */
if (ast_strlen_zero(file_path)) {
/* Remove this entry from the database, since we will be
* downloading a new file anyways.
*/
- remove_public_key_from_astdb(public_key_url);
+ remove_public_key_from_astdb(public_cert_url);
/* Go ahead and free file_path, in case anything was allocated above */
ast_free(file_path);
- /* Set up the default path */
- filename = basename(public_key_url);
- if (ast_asprintf(&file_path, "%s/keys/%s/%s", ast_config_AST_DATA_DIR, STIR_SHAKEN_DIR_NAME, filename) < 0) {
- return NULL;
- }
-
/* Download to the default path */
- if (run_curl(public_key_url, file_path)) {
+ file_path = run_curl(public_cert_url, dir_path);
+ if (!file_path) {
return NULL;
}
/* We should have a successful download at this point, so
* add an entry to the database.
*/
- add_public_key_to_astdb(public_key_url, file_path);
+ add_public_key_to_astdb(public_cert_url, file_path);
}
- /* Check to see if the key we downloaded (or already had) is expired */
- if (public_key_is_expired(public_key_url)) {
+ /* Check to see if the cert we downloaded (or already had) is expired */
+ if (public_key_is_expired(public_cert_url)) {
- ast_debug(3, "Public key '%s' is expired\n", public_key_url);
+ ast_debug(3, "Public cert '%s' is expired\n", public_cert_url);
- remove_public_key_from_astdb(public_key_url);
+ remove_public_key_from_astdb(public_cert_url);
/* If this fails, then there's nothing we can do */
- if (curl_and_check_expiration(public_key_url, file_path, &curl)) {
+ ast_free(file_path);
+ file_path = curl_and_check_expiration(public_cert_url, dir_path, &curl);
+ if (!file_path) {
return NULL;
}
}
ast_debug(3, "Failed first read of public key file '%s'\n", file_path);
- remove_public_key_from_astdb(public_key_url);
+ remove_public_key_from_astdb(public_cert_url);
- if (curl_and_check_expiration(public_key_url, file_path, &curl)) {
+ ast_free(file_path);
+ file_path = curl_and_check_expiration(public_cert_url, dir_path, &curl);
+ if (!file_path) {
return NULL;
}
public_key = stir_shaken_read_key(file_path, 0);
if (!public_key) {
ast_log(LOG_ERROR, "Failed to read public key from '%s'\n", file_path);
- remove_public_key_from_astdb(public_key_url);
+ remove_public_key_from_astdb(public_cert_url);
return NULL;
}
}
ret_payload->signature = (unsigned char *)ast_strdup(signature);
ret_payload->algorithm = ast_strdup(algorithm);
- ret_payload->public_key_url = ast_strdup(public_key_url);
+ ret_payload->public_cert_url = ast_strdup(public_cert_url);
return ret_payload;
}
{
struct ast_stir_shaken_payload *ss_payload;
unsigned char *signature;
- const char *public_key_url;
+ const char *public_cert_url;
const char *caller_id_num;
const char *header;
const char *payload;
goto cleanup;
}
- public_key_url = stir_shaken_certificate_get_public_key_url(cert);
- if (stir_shaken_add_x5u(json, public_key_url)) {
- ast_log(LOG_ERROR, "Failed to add 'x5u' (public key URL) to payload\n");
+ public_cert_url = stir_shaken_certificate_get_public_cert_url(cert);
+ if (stir_shaken_add_x5u(json, public_cert_url)) {
+ ast_log(LOG_ERROR, "Failed to add 'x5u' (public cert URL) to payload\n");
goto cleanup;
}
- ss_payload->public_key_url = ast_strdup(public_key_url);
+ ss_payload->public_cert_url = ast_strdup(public_cert_url);
if (stir_shaken_add_attest(json, stir_shaken_certificate_get_attestation(cert))) {
ast_log(LOG_ERROR, "Failed to add 'attest' to payload\n");
#ifdef TEST_FRAMEWORK
-static void test_stir_shaken_add_fake_astdb_entry(const char *public_key_url, const char *file_path)
+static void test_stir_shaken_add_fake_astdb_entry(const char *public_cert_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);
+ ast_sha1_hash(hash, public_cert_url);
+ add_public_key_to_astdb(public_cert_url, file_path);
snprintf(time_buf, sizeof(time_buf), "%30lu", expires.tv_sec + 300);
ast_db_put(hash, "expiration", time_buf);
char *type = private ? "private" : "public";
char *private_data =
"-----BEGIN EC PRIVATE KEY-----\n"
- "MHcCAQEEIFkNGlrmRky2j7wmjGBGoPFBsyEQELmEYN02BiiG508noAoGCCqGSM49\n"
- "AwEHoUQDQgAECwCaeAYwVG/FAnEnkwaucz6o047iSWq3cJBBUc0n2ZlUDr5VywAz\n"
- "MZ86EthIqF3CGZjhLHn0xRITXYwfqTtWBw==\n"
+ "MHcCAQEEIC+xv2GKNTDd81vJM8rwGAGNqgklKKxz9Qejn+pcRPC1oAoGCCqGSM49\n"
+ "AwEHoUQDQgAEq12QXu8lH295ZMZ4udKy5VV8wVgE4qSOnkdofn3hEDsh6QTKTZg9\n"
+ "W6PncYAVnmOFRL4cTGRbmAIShN4naZk2Yg==\n"
"-----END EC PRIVATE KEY-----";
char *public_data =
- "-----BEGIN PUBLIC KEY-----\n"
- "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECwCaeAYwVG/FAnEnkwaucz6o047i\n"
- "SWq3cJBBUc0n2ZlUDr5VywAzMZ86EthIqF3CGZjhLHn0xRITXYwfqTtWBw==\n"
- "-----END PUBLIC KEY-----";
+ "-----BEGIN CERTIFICATE-----\n"
+ "MIIBzDCCAXGgAwIBAgIUXDt6EC0OixT1iRSSPV3jB/zQAlQwCgYIKoZIzj0EAwIw\n"
+ "RTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu\n"
+ "dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMTA0MTMwNjM3MjRaFw0yMzA3MTcw\n"
+ "NjM3MjRaMGoxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJWQTESMBAGA1UEBwwJU29t\n"
+ "ZXdoZXJlMRowGAYDVQQKDBFBY21lVGVsZWNvbSwgSW5jLjENMAsGA1UECwwEVk9J\n"
+ "UDEPMA0GA1UEAwwGU0hBS0VOMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEq12Q\n"
+ "Xu8lH295ZMZ4udKy5VV8wVgE4qSOnkdofn3hEDsh6QTKTZg9W6PncYAVnmOFRL4c\n"
+ "TGRbmAIShN4naZk2YqMaMBgwFgYIKwYBBQUHARoECjAIoAYWBDEwMDEwCgYIKoZI\n"
+ "zj0EAwIDSQAwRgIhAMa9Ky38DgVaIgVm9Mgws/qN3zxjMQXfxEExAbDwyq/WAiEA\n"
+ "zbC29mvtSulwbvQJ4fBdFU84cFC3Ctu1QrCeFOiZHc4=\n"
+ "-----END CERTIFICATE-----";
fd = mkstemp(file_path);
if (fd < 0) {
file = fdopen(fd, "w");
if (!file) {
ast_log(LOG_ERROR, "Failed to create temp %s key file: %s\n", type, strerror(errno));
+ close(fd);
return -1;
}
AST_TEST_DEFINE(test_stir_shaken_verify)
{
char *caller_id_number = "1234567";
- char *public_key_url = "http://testing123";
+ char *public_cert_url = "http://testing123";
char *header;
char *payload;
struct ast_json *tmp_json;
/* 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);
+ "x5u", public_cert_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 empty header parameter */
returned_payload = ast_stir_shaken_verify("", payload, (const char *)signed_payload->signature,
- STIR_SHAKEN_ENCRYPTION_ALGORITHM, public_key_url);
+ STIR_SHAKEN_ENCRYPTION_ALGORITHM, public_cert_url);
if (returned_payload) {
ast_test_status_update(test, "Verified a signature with missing 'header'\n");
test_stir_shaken_cleanup_cert(caller_id_number);
/* Test empty payload parameter */
returned_payload = ast_stir_shaken_verify(header, "", (const char *)signed_payload->signature,
- STIR_SHAKEN_ENCRYPTION_ALGORITHM, public_key_url);
+ STIR_SHAKEN_ENCRYPTION_ALGORITHM, public_cert_url);
if (returned_payload) {
ast_test_status_update(test, "Verified a signature with missing 'payload'\n");
test_stir_shaken_cleanup_cert(caller_id_number);
/* Test empty signature parameter */
returned_payload = ast_stir_shaken_verify(header, payload, "",
- STIR_SHAKEN_ENCRYPTION_ALGORITHM, public_key_url);
+ STIR_SHAKEN_ENCRYPTION_ALGORITHM, public_cert_url);
if (returned_payload) {
ast_test_status_update(test, "Verified a signature with missing 'signature'\n");
test_stir_shaken_cleanup_cert(caller_id_number);
/* Test empty algorithm parameter */
returned_payload = ast_stir_shaken_verify(header, payload, (const char *)signed_payload->signature,
- "", public_key_url);
+ "", public_cert_url);
if (returned_payload) {
ast_test_status_update(test, "Verified a signature with missing 'algorithm'\n");
test_stir_shaken_cleanup_cert(caller_id_number);
}
/* Trick the function into thinking we've already downloaded the key */
- test_stir_shaken_add_fake_astdb_entry(public_key_url, public_path);
+ test_stir_shaken_add_fake_astdb_entry(public_cert_url, public_path);
/* Verify a valid signature */
returned_payload = ast_stir_shaken_verify(header, payload, (const char *)signed_payload->signature,
- STIR_SHAKEN_ENCRYPTION_ALGORITHM, public_key_url);
+ STIR_SHAKEN_ENCRYPTION_ALGORITHM, public_cert_url);
if (!returned_payload) {
ast_test_status_update(test, "Failed to verify a valid signature\n");
- remove_public_key_from_astdb(public_key_url);
+ remove_public_key_from_astdb(public_cert_url);
test_stir_shaken_cleanup_cert(caller_id_number);
return AST_TEST_FAIL;
}
- remove_public_key_from_astdb(public_key_url);
+ remove_public_key_from_astdb(public_cert_url);
test_stir_shaken_cleanup_cert(caller_id_number);
AST_DECLARE_STRING_FIELDS(
/*! Path to a directory containing certificates */
AST_STRING_FIELD(path);
- /*! URL to the public key */
- AST_STRING_FIELD(public_key_url);
+ /*! URL to the public certificate */
+ AST_STRING_FIELD(public_cert_url);
/*! The caller ID number associated with the certificate */
AST_STRING_FIELD(caller_id_number);
/*! The attestation level for this certificate */
"certificate", AST_RETRIEVE_FLAG_DEFAULT, &fields);
}
-const char *stir_shaken_certificate_get_public_key_url(struct stir_shaken_certificate *cert)
+const char *stir_shaken_certificate_get_public_cert_url(struct stir_shaken_certificate *cert)
{
- return cert ? cert->public_key_url : NULL;
+ return cert ? cert->public_cert_url : NULL;
}
const char *stir_shaken_certificate_get_attestation(struct stir_shaken_certificate *cert)
return 0;
}
-static int on_load_public_key_url(const struct aco_option *opt, struct ast_variable *var, void *obj)
+static int on_load_public_cert_url(const struct aco_option *opt, struct ast_variable *var, void *obj)
{
struct stir_shaken_certificate *cfg = obj;
if (!ast_begins_with(var->value, "http")) {
- ast_log(LOG_ERROR, "stir/shaken - public_key_url scheme must be 'http[s]'\n");
+ ast_log(LOG_ERROR, "stir/shaken - public_cert_url scheme must be 'http[s]'\n");
return -1;
}
- return ast_string_field_set(cfg, public_key_url, var->value);
+ return ast_string_field_set(cfg, public_cert_url, var->value);
}
-static int public_key_url_to_str(const void *obj, const intptr_t *args, char **buf)
+static int public_cert_url_to_str(const void *obj, const intptr_t *args, char **buf)
{
const struct stir_shaken_certificate *cfg = obj;
- *buf = ast_strdup(cfg->public_key_url);
+ *buf = ast_strdup(cfg->public_cert_url);
return 0;
}
}
ast_string_field_set(cert, path, file_path);
- ast_string_field_set(cert, public_key_url, TEST_CONFIG_URL);
+ ast_string_field_set(cert, public_cert_url, TEST_CONFIG_URL);
ast_string_field_set(cert, caller_id_number, caller_id_number);
private_key = stir_shaken_read_key(cert->path, 1);
ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "type", "", OPT_NOOP_T, 0, 0);
ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "path", "",
on_load_path, path_to_str, NULL, 0, 0);
- ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "public_key_url", "",
- on_load_public_key_url, public_key_url_to_str, NULL, 0, 0);
+ ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "public_cert_url", "",
+ on_load_public_cert_url, public_cert_url_to_str, NULL, 0, 0);
ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "attestation", "",
on_load_attestation, attestation_to_str, NULL, 0, 0);
ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "origid", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct stir_shaken_certificate, origid));
* \retval NULL on failure
* \retval The public key URL on success
*/
-const char *stir_shaken_certificate_get_public_key_url(struct stir_shaken_certificate *cert);
+const char *stir_shaken_certificate_get_public_cert_url(struct stir_shaken_certificate *cert);
/*!
* \brief Get the attestation level associated with a certificate
#include "asterisk/logger.h"
#include "curl.h"
#include "general.h"
+#include "stir_shaken.h"
#include <curl/curl.h>
+#include <sys/stat.h>
/* Used to check CURL headers */
#define MAX_HEADER_LENGTH 1023
return curl;
}
-int curl_public_key(const char *public_key_url, const char *path, struct curl_cb_data *data)
+/*!
+ * \brief Create a temporary file located at path
+ *
+ * \note This function assumes path does not end with a '/'
+ *
+ * \param path The directory path to create the file in
+ * \param filename Function allocates memory and stores full filename (including path) here
+ *
+ * \retval -1 on failure
+ * \retval file descriptor on success
+ */
+static int create_temp_file(const char *path, char **filename)
+{
+ const char *template_name = "certXXXXXX";
+ int fd;
+
+ if (ast_asprintf(filename, "%s/%s", path, template_name) < 0) {
+ ast_log(LOG_ERROR, "Failed to set up temporary file path for CURL\n");
+ return -1;
+ }
+
+ ast_mkdir(path, 0644);
+
+ if ((fd = mkstemp(*filename)) < 0) {
+ ast_log(LOG_NOTICE, "Failed to create temporary file for CURL\n");
+ ast_free(*filename);
+ return -1;
+ }
+
+ return fd;
+}
+
+char *curl_public_key(const char *public_cert_url, const char *path, struct curl_cb_data *data)
{
FILE *public_key_file;
+ RAII_VAR(char *, tmp_filename, NULL, ast_free);
+ char *filename;
+ char *serial;
+ int fd;
long http_code;
CURL *curl;
char curl_errbuf[CURL_ERROR_SIZE + 1];
- char hash[41];
-
- ast_sha1_hash(hash, public_key_url);
curl_errbuf[CURL_ERROR_SIZE] = '\0';
- public_key_file = fopen(path, "wb");
+ /* For now, it's fine to pass in path as is - it shouldn't end with a '/'. However,
+ * if we decide to change how certificates are stored in the future (configurable paths),
+ * then we will need to check to see if path ends with '/', copy everything up to the '/',
+ * and use this new variable for create_temp_file as well as for ast_asprintf below.
+ */
+ fd = create_temp_file(path, &tmp_filename);
+ if (fd == -1) {
+ ast_log(LOG_ERROR, "Failed to get temporary file descriptor for CURL\n");
+ return NULL;
+ }
+
+ public_key_file = fdopen(fd, "wb");
if (!public_key_file) {
ast_log(LOG_ERROR, "Failed to open file '%s' to write public key from '%s': %s (%d)\n",
- path, public_key_url, strerror(errno), errno);
- return -1;
+ tmp_filename, public_cert_url, strerror(errno), errno);
+ close(fd);
+ remove(tmp_filename);
+ return NULL;
}
curl = get_curl_instance(data);
if (!curl) {
- ast_log(LOG_ERROR, "Failed to set up CURL isntance for '%s'\n", public_key_url);
+ ast_log(LOG_ERROR, "Failed to set up CURL isntance for '%s'\n", public_cert_url);
fclose(public_key_file);
- return -1;
+ remove(tmp_filename);
+ return NULL;
}
- curl_easy_setopt(curl, CURLOPT_URL, public_key_url);
+ curl_easy_setopt(curl, CURLOPT_URL, public_cert_url);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, public_key_file);
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errbuf);
ast_log(LOG_ERROR, "%s\n", curl_errbuf);
curl_easy_cleanup(curl);
fclose(public_key_file);
- return -1;
+ remove(tmp_filename);
+ return NULL;
}
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
fclose(public_key_file);
if (http_code / 100 != 2) {
- ast_log(LOG_ERROR, "Failed to retrieve URL '%s': code %ld\n", public_key_url, http_code);
- return -1;
+ ast_log(LOG_ERROR, "Failed to retrieve URL '%s': code %ld\n", public_cert_url, http_code);
+ remove(tmp_filename);
+ return NULL;
+ }
+
+ serial = stir_shaken_get_serial_number_x509(tmp_filename);
+ if (!serial) {
+ ast_log(LOG_ERROR, "Failed to get serial from cert %s\n", tmp_filename);
+ remove(tmp_filename);
+ return NULL;
+ }
+
+ if (ast_asprintf(&filename, "%s/%s.pem", path, serial) < 0) {
+ ast_log(LOG_ERROR, "Failed to allocate memory for new filename for temporary "
+ "file %s after CURL\n", tmp_filename);
+ ast_free(serial);
+ remove(tmp_filename);
+ return NULL;
+ }
+
+ ast_free(serial);
+
+ if (rename(tmp_filename, filename)) {
+ ast_log(LOG_ERROR, "Failed to rename temporary file %s to %s after CURL\n", tmp_filename, filename);
+ ast_free(filename);
+ remove(tmp_filename);
+ return NULL;
}
- return 0;
+ return filename;
}
/*!
* \brief CURL the public key from the provided URL to the specified path
*
- * \param public_key_url The public key URL
+ * \note The returned string will need to be freed by the caller
+ *
+ * \param public_cert_url The public cert URL
* \param path The path to download the file to
* \param data The curl_cb_data
*
- * \retval 1 on failure
- * \retval 0 on success
+ * \retval NULL on failure
+ * \retval full path filename on success
*/
-int curl_public_key(const char *public_key_url, const char *path, struct curl_cb_data *data);
+char *curl_public_key(const char *public_cert_url, const char *path, struct curl_cb_data *data);
#endif /* _STIR_SHAKEN_CURL_H */
{
EVP_PKEY *key = NULL;
FILE *fp;
+ X509 *cert = NULL;
fp = fopen(path, "r");
if (!fp) {
return NULL;
}
+ /* If this is to get the private key, the file will be ECDSA or RSA, with the former eventually
+ * replacing the latter. For the public key, the file will be X.509.
+ */
if (priv) {
key = PEM_read_PrivateKey(fp, NULL, NULL, NULL);
} else {
- key = PEM_read_PUBKEY(fp, NULL, NULL, NULL);
+ cert = PEM_read_X509(fp, NULL, NULL, NULL);
+ if (!cert) {
+ ast_log(LOG_ERROR, "Failed to read X.509 cert from file '%s'\n", path);
+ fclose(fp);
+ return NULL;
+ }
+ key = X509_get_pubkey(cert);
+ /* It's fine to free the cert after we get the key because they are 2
+ * independent objects; you don't need a X509 object to be in memory
+ * in order to have an EVP_PKEY, and it doesn't rely on it being there.
+ */
+ X509_free(cert);
}
if (!key) {
return NULL;
}
- if (EVP_PKEY_id(key) != EVP_PKEY_EC) {
- ast_log(LOG_ERROR, "%s key from '%s' must be of type EVP_PKEY_EC\n", priv ? "private" : "public", path);
+ if (EVP_PKEY_id(key) != EVP_PKEY_EC && EVP_PKEY_id(key) != EVP_PKEY_RSA) {
+ ast_log(LOG_ERROR, "%s key from '%s' must be of type EVP_PKEY_EC or EVP_PKEY_RSA\n",
+ priv ? "Private" : "Public", path);
fclose(fp);
EVP_PKEY_free(key);
return NULL;
return key;
}
+
+char *stir_shaken_get_serial_number_x509(const char *path)
+{
+ FILE *fp;
+ X509 *cert;
+ ASN1_INTEGER *serial;
+ BIGNUM *bignum;
+ char *serial_hex;
+
+ fp = fopen(path, "r");
+ if (!fp) {
+ ast_log(LOG_ERROR, "Failed to open file %s\n", path);
+ return NULL;
+ }
+
+ cert = PEM_read_X509(fp, NULL, NULL, NULL);
+ if (!cert) {
+ ast_log(LOG_ERROR, "Failed to read X.509 cert from file %s\n", path);
+ fclose(fp);
+ return NULL;
+ }
+
+ serial = X509_get_serialNumber(cert);
+ if (!serial) {
+ ast_log(LOG_ERROR, "Failed to get serial number from certificate %s\n", path);
+ X509_free(cert);
+ fclose(fp);
+ return NULL;
+ }
+
+ bignum = ASN1_INTEGER_to_BN(serial, NULL);
+ if (bignum == NULL) {
+ ast_log(LOG_ERROR, "Failed to convert serial to bignum for certificate %s\n", path);
+ X509_free(cert);
+ fclose(fp);
+ return NULL;
+ }
+
+ /* This will return a string with memory allocated. After we get the string,
+ * we don't need the cert, file, or bignum references anymore, so free them
+ * and return the string, if BN_bn2hex was a success.
+ */
+ serial_hex = BN_bn2hex(bignum);
+ X509_free(cert);
+ fclose(fp);
+ BN_free(bignum);
+
+ if (!serial_hex) {
+ ast_log(LOG_ERROR, "Failed to convert bignum to hex for certificate %s\n", path);
+ return NULL;
+ }
+
+ return serial_hex;
+}
*/
EVP_PKEY *stir_shaken_read_key(const char *path, int priv);
+/*!
+ * \brief Gets the serial number in hex form from the X509 certificate at path
+ *
+ * \note The returned string will need to be freed by the caller
+ *
+ * \param path The full path of the X509 certificate
+ *
+ * \retval NULL on failure
+ * \retval serial number on success
+ */
+char *stir_shaken_get_serial_number_x509(const char *path);
+
#endif /* _STIR_SHAKEN_H */
AST_DECLARE_STRING_FIELDS(
/*! Path to a directory containing certificates */
AST_STRING_FIELD(path);
- /*! URL to the public key */
- AST_STRING_FIELD(public_key_url);
+ /*! URL to the public certificate */
+ AST_STRING_FIELD(public_cert_url);
);
};
return 0;
}
-static int on_load_public_key_url(const struct aco_option *opt, struct ast_variable *var, void *obj)
+static int on_load_public_cert_url(const struct aco_option *opt, struct ast_variable *var, void *obj)
{
struct stir_shaken_store *cfg = obj;
if (!ast_begins_with(var->value, "http")) {
- ast_log(LOG_ERROR, "stir/shaken - public_key_url scheme must be 'http[s]'\n");
+ ast_log(LOG_ERROR, "stir/shaken - public_cert_url scheme must be 'http[s]'\n");
return -1;
}
if (!strstr(var->value, VARIABLE_SUBSTITUTE)) {
- ast_log(LOG_ERROR, "stir/shaken - public_key_url must contain variable '%s' "
+ ast_log(LOG_ERROR, "stir/shaken - public_cert_url must contain variable '%s' "
"used for substitution\n", VARIABLE_SUBSTITUTE);
return -1;
}
- return ast_string_field_set(cfg, public_key_url, var->value);
+ return ast_string_field_set(cfg, public_cert_url, var->value);
}
-static int public_key_url_to_str(const void *obj, const intptr_t *args, char **buf)
+static int public_cert_url_to_str(const void *obj, const intptr_t *args, char **buf)
{
const struct stir_shaken_store *cfg = obj;
- *buf = ast_strdup(cfg->public_key_url);
+ *buf = ast_strdup(cfg->public_cert_url);
return 0;
}
ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "type", "", OPT_NOOP_T, 0, 0);
ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "path", "",
on_load_path, path_to_str, NULL, 0, 0);
- ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "public_key_url", "",
- on_load_public_key_url, public_key_url_to_str, NULL, 0, 0);
+ ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "public_cert_url", "",
+ on_load_public_cert_url, public_cert_url_to_str, NULL, 0, 0);
ast_cli_register_multiple(stir_shaken_store_cli,
ARRAY_LEN(stir_shaken_store_cli));