;
; URL to the public key
;public_key_url=http://mycompany.com/alice.pub
+;
+; Must have an attestation of A, B, or C
+;attestation=C
+;
+; The origination identifier for the certificate
+;origid=MyAsterisk
#include <openssl/evp.h>
#include <openssl/pem.h>
+#define STIR_SHAKEN_ENCRYPTION_ALGORITHM "ES256"
+#define STIR_SHAKEN_PPT "shaken"
+#define STIR_SHAKEN_TYPE "passport"
+
enum ast_stir_shaken_verification_result {
AST_STIR_SHAKEN_VERIFY_NOT_PRESENT, /*! No STIR/SHAKEN information was available */
AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED, /*! Signature verification failed */
struct ast_json;
+/*!
+ * \brief Retrieve the value for 'signature' from an ast_stir_shaken_payload
+ *
+ * \param payload The payload
+ *
+ * \retval The signature
+ */
+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
+ *
+ * \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);
+
/*!
* \brief Retrieve the value for 'signature_timeout' from 'general' config object
*
*/
int ast_base64encode(char *dst, const unsigned char *src, int srclen, int max);
+/*!
+ * \brief Same as ast_base64encode, but does hte math for you and returns
+ * an encoded string
+ *
+ * \note The returned string will need to be freed later
+ *
+ * \param src The source buffer
+ *
+ * \retval NULL on failure
+ * \retval Encoded string on success
+ */
+char *ast_base64encode_string(const char *src);
+
/*!
* \brief Decode data from base64
* \param dst the destination buffer
return ast_base64encode_full(dst, src, srclen, max, 0);
}
+/*! \brief Encode to BASE64 and return encoded string */
+char *ast_base64encode_string(const char *src)
+{
+ size_t encoded_len;
+ char *encoded_string;
+
+ if (ast_strlen_zero(src)) {
+ return NULL;
+ }
+
+ encoded_len = ((strlen(src) * 4 / 3 + 3) & ~3) + 1;
+ encoded_string = ast_calloc(1, encoded_len);
+
+ ast_base64encode(encoded_string, (const unsigned char *)src, strlen(src), encoded_len);
+
+ return encoded_string;
+}
+
static void base64_init(void)
{
int x;
return 0;
}
+static void add_identity_header(const struct ast_sip_session *session, pjsip_tx_data *tdata)
+{
+ static const pj_str_t identity_str = { "Identity", 8 };
+ pjsip_generic_string_hdr *identity_hdr;
+ pj_str_t identity_val;
+ pjsip_fromto_hdr *old_identity;
+ char *signature;
+ char *public_key_url;
+ struct ast_json *header;
+ struct ast_json *payload;
+ char *dumped_string;
+ RAII_VAR(struct ast_json *, json, NULL, ast_json_free);
+ RAII_VAR(struct ast_stir_shaken_payload *, ss_payload, NULL, ast_stir_shaken_payload_free);
+ RAII_VAR(char *, encoded_header, NULL, ast_free);
+ RAII_VAR(char *, encoded_payload, NULL, ast_free);
+ RAII_VAR(char *, combined_str, NULL, ast_free);
+ size_t combined_size;
+
+ old_identity = pjsip_msg_find_hdr_by_name(tdata->msg, &identity_str, NULL);
+ if (old_identity) {
+ return;
+ }
+
+ /* x5u (public key URL), attestation, and origid will be added by ast_stir_shaken_sign */
+ json = ast_json_pack("{s: {s: s, s: s, s: s}, s: {s: {s: s}}}", "header", "alg", "ES256", "ppt", "shaken", "typ", "passport",
+ "payload", "orig", "tn", session->id.number.str);
+ if (!json) {
+ ast_log(LOG_ERROR, "Failed to allocate memory for STIR/SHAKEN JSON\n");
+ return;
+ }
+
+ ss_payload = ast_stir_shaken_sign(json);
+ if (!ss_payload) {
+ ast_log(LOG_ERROR, "Failed to allocate memory for STIR/SHAKEN payload\n");
+ return;
+ }
+
+ header = ast_json_object_get(json, "header");
+ dumped_string = ast_json_dump_string(header);
+ encoded_header = ast_base64encode_string(dumped_string);
+ ast_json_free(dumped_string);
+ if (!encoded_header) {
+ ast_log(LOG_ERROR, "Failed to encode STIR/SHAKEN header\n");
+ return;
+ }
+
+ payload = ast_json_object_get(json, "payload");
+ dumped_string = ast_json_dump_string(payload);
+ encoded_payload = ast_base64encode_string(dumped_string);
+ ast_json_free(dumped_string);
+ if (!encoded_payload) {
+ ast_log(LOG_ERROR, "Failed to encode STIR/SHAKEN payload\n");
+ return;
+ }
+
+ signature = (char *)ast_stir_shaken_payload_get_signature(ss_payload);
+ public_key_url = ast_stir_shaken_payload_get_public_key_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
+ */
+ combined_size = strlen(encoded_header) + 1 + strlen(encoded_payload) + 1
+ + strlen(signature) + strlen(";info=<>alg=;ppt=") + strlen(public_key_url)
+ + strlen(STIR_SHAKEN_ENCRYPTION_ALGORITHM) + strlen(STIR_SHAKEN_PPT) + 1;
+ combined_str = ast_calloc(1, combined_size);
+ if (!combined_str) {
+ ast_log(LOG_ERROR, "Failed to allocate memory for STIR/SHAKEN identity string\n");
+ 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);
+
+ identity_val = pj_str(combined_str);
+ identity_hdr = pjsip_generic_string_hdr_create(tdata->pool, &identity_str, &identity_val);
+ if (!identity_hdr) {
+ ast_log(LOG_ERROR, "Failed to create STIR/SHAKEN Identity header\n");
+ return;
+ }
+
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)identity_hdr);
+}
+
+static void stir_shaken_outgoing_request(struct ast_sip_session *session, pjsip_tx_data *tdata)
+{
+ if (ast_strlen_zero(session->id.number.str) && session->id.number.valid) {
+ return;
+ }
+
+ add_identity_header(session, tdata);
+}
+
static struct ast_sip_session_supplement stir_shaken_supplement = {
.method = "INVITE",
.priority = AST_SIP_SUPPLEMENT_PRIORITY_CHANNEL + 1, /* Run AFTER channel creation */
.incoming_request = stir_shaken_incoming_request,
+ .outgoing_request = stir_shaken_outgoing_request,
};
static int unload_module(void)
Must be a valid http, or https, URL.
</para></description>
</configOption>
+ <configOption name="attestation">
+ <synopsis>Attestation level</synopsis>
+ </configOption>
+ <configOption name="origid" default="">
+ <synopsis>The origination ID</synopsis>
+ </configOption>
<configOption name="caller_id_number" default="">
<synopsis>The caller ID number to match on.</synopsis>
</configOption>
</function>
***/
-#define STIR_SHAKEN_ENCRYPTION_ALGORITHM "ES256"
-#define STIR_SHAKEN_PPT "shaken"
-#define STIR_SHAKEN_TYPE "passport"
-
static struct ast_sorcery *stir_shaken_sorcery;
/* Used for AstDB entries */
ast_free(payload);
}
+unsigned char *ast_stir_shaken_payload_get_signature(const struct ast_stir_shaken_payload *payload)
+{
+ return payload ? payload->signature : NULL;
+}
+
+char *ast_stir_shaken_payload_get_public_key_url(const struct ast_stir_shaken_payload *payload)
+{
+ return payload ? payload->public_key_url : NULL;
+}
+
unsigned int ast_stir_shaken_get_signature_timeout(void)
{
return ast_stir_shaken_signature_timeout(stir_shaken_general_get());
{
struct ast_stir_shaken_payload *ss_payload;
unsigned char *signature;
+ const char *public_key_url;
const char *caller_id_num;
const char *header;
const char *payload;
goto cleanup;
}
- if (stir_shaken_add_x5u(json, stir_shaken_certificate_get_public_key_url(cert))) {
+ 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");
goto cleanup;
}
+ ss_payload->public_key_url = ast_strdup(public_key_url);
- /* TODO: This is just a placeholder for adding 'attest', 'iat', and
- * 'origid' to the payload. Later, additional logic will need to be
- * added to determine what these values actually are, but the functions
- * themselves are ready to go.
- */
- if (stir_shaken_add_attest(json, "B")) {
+ if (stir_shaken_add_attest(json, stir_shaken_certificate_get_attestation(cert))) {
ast_log(LOG_ERROR, "Failed to add 'attest' to payload\n");
goto cleanup;
}
- if (stir_shaken_add_origid(json, "asterisk")) {
+ if (stir_shaken_add_origid(json, stir_shaken_certificate_get_origid(cert))) {
ast_log(LOG_ERROR, "Failed to add 'origid' to payload\n");
goto cleanup;
}
AST_STRING_FIELD(public_key_url);
/*! The caller ID number associated with the certificate */
AST_STRING_FIELD(caller_id_number);
+ /*! The attestation level for this certificate */
+ AST_STRING_FIELD(attestation);
+ /*! The origination ID for this certificate */
+ AST_STRING_FIELD(origid);
);
/*! The private key for the certificate */
EVP_PKEY *private_key;
const char *stir_shaken_certificate_get_public_key_url(struct stir_shaken_certificate *cert)
{
- if (!cert) {
- return NULL;
- }
+ return cert ? cert->public_key_url : NULL;
+}
- return cert->public_key_url;
+const char *stir_shaken_certificate_get_attestation(struct stir_shaken_certificate *cert)
+{
+ return cert ? cert->attestation : NULL;
}
-EVP_PKEY *stir_shaken_certificate_get_private_key(struct stir_shaken_certificate *cert)
+const char *stir_shaken_certificate_get_origid(struct stir_shaken_certificate *cert)
{
- if (!cert) {
- return NULL;
- }
+ return cert ? cert->origid : NULL;
+}
- return cert->private_key;
+EVP_PKEY *stir_shaken_certificate_get_private_key(struct stir_shaken_certificate *cert)
+{
+ return cert ? cert->private_key : NULL;
}
static int stir_shaken_certificate_apply(const struct ast_sorcery *sorcery, void *obj)
EVP_PKEY *private_key;
struct stir_shaken_certificate *cert = obj;
- if (strlen(cert->caller_id_number) == 0) {
+ if (ast_strlen_zero(cert->caller_id_number)) {
ast_log(LOG_ERROR, "Caller ID must be present\n");
return -1;
}
+ if (ast_strlen_zero(cert->attestation)) {
+ ast_log(LOG_ERROR, "Attestation must be present\n");
+ return -1;
+ }
+
private_key = stir_shaken_read_key(cert->path, 1);
if (!private_key) {
return -1;
return 0;
}
+static int on_load_attestation(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+ struct stir_shaken_certificate *cfg = obj;
+
+ if (strcmp(var->value, "A") && strcmp(var->value, "B") && strcmp(var->value, "C")) {
+ ast_log(LOG_ERROR, "stir/shaken - attestation level must be A, B, or C (object=%s)\n",
+ ast_sorcery_object_get_id(cfg));
+ return -1;
+ }
+
+ return ast_string_field_set(cfg, attestation, var->value);
+}
+
+static int attestation_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+ const struct stir_shaken_certificate *cfg = obj;
+
+ *buf = ast_strdup(cfg->attestation);
+
+ return 0;
+}
+
#ifdef TEST_FRAMEWORK
/* Name for test certificaate */
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, "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));
ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "caller_id_number", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct stir_shaken_certificate, caller_id_number));
ast_cli_register_multiple(stir_shaken_certificate_cli,
*/
const char *stir_shaken_certificate_get_public_key_url(struct stir_shaken_certificate *cert);
+/*!
+ * \brief Get the attestation level associated with a certificate
+ *
+ * \param cert The certificate
+ *
+ * \retval NULL on failure
+ * \retval The attestation on success
+ */
+const char *stir_shaken_certificate_get_attestation(struct stir_shaken_certificate *cert);
+
+/*!
+ * \brief Get the origination ID associated with a certificate
+ *
+ * \param cert The certificate
+ *
+ * \retval NULL on failure
+ * \retval The origid on success
+ */
+const char *stir_shaken_certificate_get_origid(struct stir_shaken_certificate *cert);
+
/*!
* \brief Get the private key associated with a certificate
*