diff --git a/configs/samples/stir_shaken.conf.sample b/configs/samples/stir_shaken.conf.sample
index 957fd14df72f9cb7eb549a5b58f0cfd346a3c3a6..1bd260641bb3801764a75b296056d97fafbda4f5 100644
--- a/configs/samples/stir_shaken.conf.sample
+++ b/configs/samples/stir_shaken.conf.sample
@@ -2,6 +2,29 @@
 ; 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]
 ;
@@ -33,9 +56,11 @@
 ; 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
 ;
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;
@@ -45,11 +70,13 @@
 ; 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
diff --git a/doc/UPGRADE-staging/stir-shaken-public-key-url.txt b/doc/UPGRADE-staging/stir-shaken-public-key-url.txt
new file mode 100644
index 0000000000000000000000000000000000000000..094bccfe72fdcc9917fde5508b28721ebae3b064
--- /dev/null
+++ b/doc/UPGRADE-staging/stir-shaken-public-key-url.txt
@@ -0,0 +1,6 @@
+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.
diff --git a/include/asterisk/res_stir_shaken.h b/include/asterisk/res_stir_shaken.h
index 34d58a306842b919a7667dc9bc0c9685b2b1c7e5..5175907bbdddefcdd837cb9a2d4fc4675c512fb6 100644
--- a/include/asterisk/res_stir_shaken.h
+++ b/include/asterisk/res_stir_shaken.h
@@ -43,13 +43,13 @@ struct ast_json;
 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
@@ -79,13 +79,13 @@ int ast_stir_shaken_add_verification(struct ast_channel *chan, const char *ident
  * \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
diff --git a/res/res_pjsip_stir_shaken.c b/res/res_pjsip_stir_shaken.c
index 8c1c70f05334940189bbf2ea74cd4d82ad7fe6c7..351d7ccf29ca0f9ac77b232c61875a23feb474c3 100644
--- a/res/res_pjsip_stir_shaken.c
+++ b/res/res_pjsip_stir_shaken.c
@@ -130,7 +130,7 @@ static int stir_shaken_incoming_request(struct ast_sip_session *session, pjsip_r
 	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;
@@ -168,8 +168,8 @@ static int stir_shaken_incoming_request(struct ast_sip_session *session, pjsip_r
 
 	/* 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;
 	}
@@ -182,7 +182,7 @@ static int stir_shaken_incoming_request(struct ast_sip_session *session, pjsip_r
 
 	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;
@@ -209,7 +209,7 @@ static void add_identity_header(const struct ast_sip_session *session, pjsip_tx_
 	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;
@@ -258,13 +258,13 @@ static void add_identity_header(const struct ast_sip_session *session, pjsip_tx_
 	}
 
 	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) {
@@ -272,7 +272,7 @@ static void add_identity_header(const struct ast_sip_session *session, pjsip_tx_
 		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);
diff --git a/res/res_stir_shaken.c b/res/res_stir_shaken.c
index a9f861baa1b9689c4e90eaff37dda8827096d3db..f8eb97fe4fd3617693e14f6e5cb5d837f16d4f3a 100644
--- a/res/res_stir_shaken.c
+++ b/res/res_stir_shaken.c
@@ -79,8 +79,8 @@
 				<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
@@ -95,8 +95,8 @@
 				<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>
@@ -169,8 +169,8 @@ struct ast_stir_shaken_payload {
 	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)
@@ -187,7 +187,7 @@ void ast_stir_shaken_payload_free(struct ast_stir_shaken_payload *payload)
 	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);
@@ -198,9 +198,9 @@ unsigned char *ast_stir_shaken_payload_get_signature(const struct ast_stir_shake
 	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)
@@ -349,17 +349,17 @@ int ast_stir_shaken_add_verification(struct ast_channel *chan, const char *ident
  * \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)) {
@@ -400,19 +400,19 @@ static void set_public_key_expiration(const char *public_key_url, const struct c
 /*!
  * \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)) {
@@ -429,17 +429,17 @@ static int public_key_is_expired(const char *public_key_url)
 /*!
  * \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));
 
@@ -453,30 +453,30 @@ static char *get_path_to_public_key(const char *public_key_url)
 /*!
  * \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));
@@ -484,7 +484,7 @@ static void remove_public_key_from_astdb(const char *public_key_url)
 	/* 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);
 }
 
@@ -554,77 +554,87 @@ static int stir_shaken_verify_signature(const char *msg, const char *signature,
 }
 
 /*!
- * \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;
 
@@ -648,41 +658,39 @@ struct ast_stir_shaken_payload *ast_stir_shaken_verify(const char *header, const
 		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;
 		}
 
@@ -692,18 +700,20 @@ struct ast_stir_shaken_payload *ast_stir_shaken_verify(const char *header, const
 		/* 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;
 		}
 	}
@@ -715,16 +725,18 @@ struct ast_stir_shaken_payload *ast_stir_shaken_verify(const char *header, const
 
 		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;
 		}
 	}
@@ -769,7 +781,7 @@ struct ast_stir_shaken_payload *ast_stir_shaken_verify(const char *header, const
 
 	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;
 }
@@ -1043,7 +1055,7 @@ struct ast_stir_shaken_payload *ast_stir_shaken_sign(struct ast_json *json)
 {
 	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;
@@ -1073,12 +1085,12 @@ struct ast_stir_shaken_payload *ast_stir_shaken_sign(struct ast_json *json)
 		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");
@@ -1253,14 +1265,14 @@ static struct ast_custom_function stir_shaken_function = {
 
 #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);
@@ -1283,15 +1295,23 @@ static int test_stir_shaken_write_temp_key(char *file_path, int private)
 	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) {
@@ -1302,6 +1322,7 @@ static int test_stir_shaken_write_temp_key(char *file_path, int private)
 	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;
 	}
 
@@ -1478,7 +1499,7 @@ AST_TEST_DEFINE(test_stir_shaken_sign)
 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;
@@ -1511,7 +1532,7 @@ AST_TEST_DEFINE(test_stir_shaken_verify)
 	/* 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");
@@ -1527,7 +1548,7 @@ AST_TEST_DEFINE(test_stir_shaken_verify)
 
 	/* 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);
@@ -1536,7 +1557,7 @@ AST_TEST_DEFINE(test_stir_shaken_verify)
 
 	/* 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);
@@ -1545,7 +1566,7 @@ AST_TEST_DEFINE(test_stir_shaken_verify)
 
 	/* 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);
@@ -1554,7 +1575,7 @@ AST_TEST_DEFINE(test_stir_shaken_verify)
 
 	/* 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);
@@ -1571,19 +1592,19 @@ AST_TEST_DEFINE(test_stir_shaken_verify)
 	}
 
 	/* 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);
 
diff --git a/res/res_stir_shaken/certificate.c b/res/res_stir_shaken/certificate.c
index 1a1447e5acdf4eb208a4066d7ae1253beb1ba583..f4103f96ecf1821feefbb30e07c0021d351c136b 100644
--- a/res/res_stir_shaken/certificate.c
+++ b/res/res_stir_shaken/certificate.c
@@ -34,8 +34,8 @@ struct stir_shaken_certificate {
 	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 */
@@ -95,9 +95,9 @@ struct stir_shaken_certificate *stir_shaken_certificate_get_by_caller_id_number(
 		"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)
@@ -234,23 +234,23 @@ static int path_to_str(const void *obj, const intptr_t *args, char **buf)
 	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;
 }
@@ -332,7 +332,7 @@ int test_stir_shaken_create_cert(const char *caller_id_number, const char *file_
 	}
 
 	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);
@@ -374,8 +374,8 @@ int stir_shaken_certificate_load(void)
 	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));
diff --git a/res/res_stir_shaken/certificate.h b/res/res_stir_shaken/certificate.h
index 6eeb36bec82d41523b5d4492b994efee93354912..9574d46795051cf1e2a4842574d6b5fa83902aaa 100644
--- a/res/res_stir_shaken/certificate.h
+++ b/res/res_stir_shaken/certificate.h
@@ -42,7 +42,7 @@ struct stir_shaken_certificate *stir_shaken_certificate_get_by_caller_id_number(
  * \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
diff --git a/res/res_stir_shaken/curl.c b/res/res_stir_shaken/curl.c
index ab29e3d833d9f46e2e59213b5253260a2ecfd46d..2030f463869fb263dcc6d6a892d8b7b44efc13d4 100644
--- a/res/res_stir_shaken/curl.c
+++ b/res/res_stir_shaken/curl.c
@@ -22,8 +22,10 @@
 #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
@@ -148,33 +150,80 @@ static CURL *get_curl_instance(struct curl_cb_data *data)
 	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);
 
@@ -182,7 +231,8 @@ int curl_public_key(const char *public_key_url, const char *path, struct curl_cb
 		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);
@@ -191,9 +241,34 @@ int curl_public_key(const char *public_key_url, const char *path, struct curl_cb
 	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;
 }
diff --git a/res/res_stir_shaken/curl.h b/res/res_stir_shaken/curl.h
index d587327ddf4fca367568c6650f4b26ea7486e01e..7009d36598ec5c698bfbf24072a7cdf2ff5988f2 100644
--- a/res/res_stir_shaken/curl.h
+++ b/res/res_stir_shaken/curl.h
@@ -61,13 +61,15 @@ char *curl_cb_data_get_expires(const struct curl_cb_data *data);
 /*!
  * \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 */
diff --git a/res/res_stir_shaken/stir_shaken.c b/res/res_stir_shaken/stir_shaken.c
index 0b38732141d44ea3a3a510e353134363ebdd0452..b580773c31e271eab3673fbec79ef46778795f38 100644
--- a/res/res_stir_shaken/stir_shaken.c
+++ b/res/res_stir_shaken/stir_shaken.c
@@ -90,6 +90,7 @@ EVP_PKEY *stir_shaken_read_key(const char *path, int priv)
 {
 	EVP_PKEY *key = NULL;
 	FILE *fp;
+	X509 *cert = NULL;
 
 	fp = fopen(path, "r");
 	if (!fp) {
@@ -97,10 +98,24 @@ EVP_PKEY *stir_shaken_read_key(const char *path, int priv)
 		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) {
@@ -109,8 +124,9 @@ EVP_PKEY *stir_shaken_read_key(const char *path, int priv)
 		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;
@@ -120,3 +136,57 @@ EVP_PKEY *stir_shaken_read_key(const char *path, int priv)
 
 	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;
+}
diff --git a/res/res_stir_shaken/stir_shaken.h b/res/res_stir_shaken/stir_shaken.h
index a49050e539ea6c3783f95c2feb27887b4911f9c8..90df4e97e298d578d7e293a163f02a5535c6b81e 100644
--- a/res/res_stir_shaken/stir_shaken.h
+++ b/res/res_stir_shaken/stir_shaken.h
@@ -52,4 +52,16 @@ char *stir_shaken_tab_complete_name(const char *word, struct ao2_container *cont
  */
 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 */
diff --git a/res/res_stir_shaken/store.c b/res/res_stir_shaken/store.c
index 99a50383e7fb3560a64b70f67bc7904298277e45..30bc63aed0975d14a8af03e9fc1e999a51721df4 100644
--- a/res/res_stir_shaken/store.c
+++ b/res/res_stir_shaken/store.c
@@ -36,8 +36,8 @@ struct stir_shaken_store {
 	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);
 	);
 };
 
@@ -142,29 +142,29 @@ static int path_to_str(const void *obj, const intptr_t *args, char **buf)
 	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;
 }
@@ -192,8 +192,8 @@ int stir_shaken_store_load(void)
 	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));