mirror of
https://github.com/asterisk/asterisk.git
synced 2025-09-04 20:04:50 +00:00
res_stir_shaken: Add outbound INVITE support.
Integrated STIR/SHAKEN support with outgoing INVITEs. When an INVITE is sent, the caller ID will be checked to see if there is a certificate that corresponds to it. If so, that information will be retrieved and an Identity header will be added to the SIP message. The format is: header.payload.signature;info=<public_key_url>alg=ES256;ppt=shaken Header, payload, and signature are all BASE64 encoded. The public key URL is retrieved from the certificate. Currently the algorithm and ppt are ES256 and shaken, respectively. This message is signed and can be used for verification on the receiving end. Two new configuration options have been added to the certificate object: attestation and origid. The attestation is required and must be A, B, or C. origid is the origination identifier. A new utility function has been added as well that takes a string, allocates space, BASE64 encodes it, then returns it, eliminating the need to calculate the size yourself. Change-Id: I1f84d6a5839cb2ed152ef4255b380cfc2de662b4
This commit is contained in:
committed by
Friendly Automation
parent
db012e8cc6
commit
1274117102
@@ -47,3 +47,9 @@
|
||||
;
|
||||
; 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
|
||||
|
@@ -21,6 +21,10 @@
|
||||
#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 */
|
||||
@@ -32,6 +36,24 @@ struct ast_stir_shaken_payload;
|
||||
|
||||
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
|
||||
*
|
||||
|
@@ -239,6 +239,19 @@ int ast_base64encode_full(char *dst, const unsigned char *src, int srclen, int m
|
||||
*/
|
||||
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
|
||||
|
18
main/utils.c
18
main/utils.c
@@ -398,6 +398,24 @@ int ast_base64encode(char *dst, const unsigned char *src, int srclen, int max)
|
||||
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;
|
||||
|
@@ -194,10 +194,102 @@ static int stir_shaken_incoming_request(struct ast_sip_session *session, pjsip_r
|
||||
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)
|
||||
|
@@ -99,6 +99,12 @@
|
||||
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>
|
||||
@@ -136,10 +142,6 @@
|
||||
</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 */
|
||||
@@ -184,6 +186,16 @@ void ast_stir_shaken_payload_free(struct ast_stir_shaken_payload *payload)
|
||||
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());
|
||||
@@ -1020,6 +1032,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 *caller_id_num;
|
||||
const char *header;
|
||||
const char *payload;
|
||||
@@ -1049,22 +1062,19 @@ struct ast_stir_shaken_payload *ast_stir_shaken_sign(struct ast_json *json)
|
||||
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;
|
||||
}
|
||||
|
@@ -38,6 +38,10 @@ struct stir_shaken_certificate {
|
||||
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;
|
||||
@@ -93,20 +97,22 @@ struct stir_shaken_certificate *stir_shaken_certificate_get_by_caller_id_number(
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const char *stir_shaken_certificate_get_origid(struct stir_shaken_certificate *cert)
|
||||
{
|
||||
return cert ? cert->origid : NULL;
|
||||
}
|
||||
|
||||
EVP_PKEY *stir_shaken_certificate_get_private_key(struct stir_shaken_certificate *cert)
|
||||
{
|
||||
if (!cert) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return cert->private_key;
|
||||
return cert ? cert->private_key : NULL;
|
||||
}
|
||||
|
||||
static int stir_shaken_certificate_apply(const struct ast_sorcery *sorcery, void *obj)
|
||||
@@ -114,11 +120,16 @@ static int stir_shaken_certificate_apply(const struct ast_sorcery *sorcery, void
|
||||
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;
|
||||
@@ -244,6 +255,28 @@ static int public_key_url_to_str(const void *obj, const intptr_t *args, char **b
|
||||
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 */
|
||||
@@ -343,6 +376,9 @@ int stir_shaken_certificate_load(void)
|
||||
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,
|
||||
|
@@ -44,6 +44,26 @@ struct stir_shaken_certificate *stir_shaken_certificate_get_by_caller_id_number(
|
||||
*/
|
||||
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
|
||||
*
|
||||
|
Reference in New Issue
Block a user