@ -24,6 +24,8 @@
# include "asterisk/module.h"
# include "asterisk/sorcery.h"
# include "asterisk/time.h"
# include "asterisk/json.h"
# include "asterisk/res_stir_shaken.h"
# include "res_stir_shaken/stir_shaken.h"
@ -31,16 +33,386 @@
# include "res_stir_shaken/store.h"
# include "res_stir_shaken/certificate.h"
# define STIR_SHAKEN_ENCRYPTION_ALGORITHM "ES256"
# define STIR_SHAKEN_PPT "shaken"
# define STIR_SHAKEN_TYPE "passport"
static struct ast_sorcery * stir_shaken_sorcery ;
struct ast_stir_shaken_payload {
/*! The JWT header */
struct ast_json * header ;
/*! The JWT payload */
struct ast_json * payload ;
/*! Signature for the payload */
unsigned char * signature ;
/*! The algorithm used */
char * algorithm ;
/*! THe URL to the public key for the certificate */
char * public_key_url ;
} ;
struct ast_sorcery * ast_stir_shaken_sorcery ( void )
{
return stir_shaken_sorcery ;
}
EVP_PKEY * ast_stir_shaken_get_private_key ( const char * caller_id_number )
void ast_stir_shaken_payload_free ( struct ast_stir_shaken_payload * payload )
{
if ( ! payload ) {
return ;
}
ast_json_unref ( payload - > header ) ;
ast_json_unref ( payload - > payload ) ;
ast_free ( payload - > algorithm ) ;
ast_free ( payload - > public_key_url ) ;
ast_free ( payload - > signature ) ;
ast_free ( payload ) ;
}
/*!
* \ brief Verifies the necessary contents are in the JSON and returns a
* ast_stir_shaken_payload with the extracted values .
*
* \ param json The JSON to verify
*
* \ return ast_stir_shaken_payload on success
* \ return NULL on failure
*/
static struct ast_stir_shaken_payload * stir_shaken_verify_json ( struct ast_json * json )
{
struct ast_stir_shaken_payload * payload ;
struct ast_json * obj ;
const char * val ;
payload = ast_calloc ( 1 , sizeof ( * payload ) ) ;
if ( ! payload ) {
ast_log ( LOG_ERROR , " Failed to allocate STIR_SHAKEN payload \n " ) ;
goto cleanup ;
}
/* Look through the header first */
obj = ast_json_object_get ( json , " header " ) ;
if ( ! obj ) {
ast_log ( LOG_ERROR , " STIR/SHAKEN JWT did not have the required field 'header' \n " ) ;
goto cleanup ;
}
payload - > header = ast_json_deep_copy ( obj ) ;
if ( ! payload - > header ) {
ast_log ( LOG_ERROR , " STIR_SHAKEN payload failed to copy 'header' \n " ) ;
goto cleanup ;
}
/* Check the ppt value for "shaken" */
val = ast_json_string_get ( ast_json_object_get ( obj , " ppt " ) ) ;
if ( ast_strlen_zero ( val ) ) {
ast_log ( LOG_ERROR , " STIR/SHAKEN JWT did not have the required field 'ppt' \n " ) ;
goto cleanup ;
}
if ( strcmp ( val , STIR_SHAKEN_PPT ) ) {
ast_log ( LOG_ERROR , " STIR/SHAKEN JWT field 'ppt' did not have "
" required value '%s' (was '%s') \n " , STIR_SHAKEN_PPT , val ) ;
goto cleanup ;
}
/* Check the typ value for "passport" */
val = ast_json_string_get ( ast_json_object_get ( obj , " typ " ) ) ;
if ( ast_strlen_zero ( val ) ) {
ast_log ( LOG_ERROR , " STIR/SHAKEN JWT did not have the required field 'typ' \n " ) ;
goto cleanup ;
}
if ( strcmp ( val , STIR_SHAKEN_TYPE ) ) {
ast_log ( LOG_ERROR , " STIR/SHAKEN JWT field 'typ' did not have "
" required value '%s' (was '%s') \n " , STIR_SHAKEN_TYPE , val ) ;
goto cleanup ;
}
/* Check the alg value for "ES256" */
val = ast_json_string_get ( ast_json_object_get ( obj , " alg " ) ) ;
if ( ast_strlen_zero ( val ) ) {
ast_log ( LOG_ERROR , " STIR/SHAKEN JWT did not have required field 'alg' \n " ) ;
goto cleanup ;
}
if ( strcmp ( val , STIR_SHAKEN_ENCRYPTION_ALGORITHM ) ) {
ast_log ( LOG_ERROR , " STIR/SHAKEN JWT field 'alg' did not have "
" required value '%s' (was '%s') \n " , STIR_SHAKEN_ENCRYPTION_ALGORITHM , val ) ;
goto cleanup ;
}
payload - > algorithm = ast_strdup ( val ) ;
if ( ! payload - > algorithm ) {
ast_log ( LOG_ERROR , " STIR/SHAKEN payload failed to copy 'algorithm' \n " ) ;
goto cleanup ;
}
/* Now let's check the payload section */
obj = ast_json_object_get ( json , " payload " ) ;
if ( ! obj ) {
ast_log ( LOG_ERROR , " STIR/SHAKEN payload JWT did not have required field 'payload' \n " ) ;
goto cleanup ;
}
/* Check the orig tn value for not NULL */
val = ast_json_string_get ( ast_json_object_get ( ast_json_object_get ( obj , " orig " ) , " tn " ) ) ;
if ( ast_strlen_zero ( val ) ) {
ast_log ( LOG_ERROR , " STIR/SHAKEN JWT did not have required field 'orig->tn' \n " ) ;
goto cleanup ;
}
/* Payload seems sane. Copy it and return on success */
payload - > payload = ast_json_deep_copy ( obj ) ;
if ( ! payload - > payload ) {
ast_log ( LOG_ERROR , " STIR/SHAKEN payload failed to copy 'payload' \n " ) ;
goto cleanup ;
}
return payload ;
cleanup :
ast_stir_shaken_payload_free ( payload ) ;
return NULL ;
}
/*!
* \ brief Signs the payload and returns the signature .
*
* \ param json_str The string representation of the JSON
* \ param private_key The private key used to sign the payload
*
* \ retval signature on success
* \ retval NULL on failure
*/
static unsigned char * stir_shaken_sign ( char * json_str , EVP_PKEY * private_key )
{
return stir_shaken_certificate_get_private_key ( caller_id_number ) ;
EVP_MD_CTX * mdctx = NULL ;
int ret = 0 ;
unsigned char * encoded_signature = NULL ;
unsigned char * signature = NULL ;
size_t encoded_length = 0 ;
size_t signature_length = 0 ;
mdctx = EVP_MD_CTX_create ( ) ;
if ( ! mdctx ) {
ast_log ( LOG_ERROR , " Failed to create Message Digest Context \n " ) ;
goto cleanup ;
}
ret = EVP_DigestSignInit ( mdctx , NULL , EVP_sha256 ( ) , NULL , private_key ) ;
if ( ret ! = 1 ) {
ast_log ( LOG_ERROR , " Failed to initialize Message Digest Context \n " ) ;
goto cleanup ;
}
ret = EVP_DigestSignUpdate ( mdctx , json_str , strlen ( json_str ) ) ;
if ( ret ! = 1 ) {
ast_log ( LOG_ERROR , " Failed to update Message Digest Context \n " ) ;
goto cleanup ;
}
ret = EVP_DigestSignFinal ( mdctx , NULL , & signature_length ) ;
if ( ret ! = 1 ) {
ast_log ( LOG_ERROR , " Failed initial phase of Message Digest Context signing \n " ) ;
goto cleanup ;
}
signature = ast_calloc ( 1 , sizeof ( unsigned char ) * signature_length ) ;
if ( ! signature ) {
ast_log ( LOG_ERROR , " Failed to allocate space for signature \n " ) ;
goto cleanup ;
}
ret = EVP_DigestSignFinal ( mdctx , signature , & signature_length ) ;
if ( ret ! = 1 ) {
ast_log ( LOG_ERROR , " Failed final phase of Message Digest Context signing \n " ) ;
goto cleanup ;
}
/* There are 6 bits to 1 base64 digit, so in order to get the size of the base64 encoded
* signature , we need to multiply by the number of bits in a byte and divide by 6. Since
* there ' s rounding when doing base64 conversions , add 3 bytes , just in case , and account
* for padding . Add another byte for the NULL - terminator so we don ' t lose data .
*/
encoded_length = ( ( signature_length * 4 / 3 + 3 ) & ~ 3 ) + 1 ;
encoded_signature = ast_calloc ( 1 , encoded_length ) ;
if ( ! encoded_signature ) {
ast_log ( LOG_ERROR , " Failed to allocate space for encoded signature \n " ) ;
goto cleanup ;
}
ast_base64encode ( ( char * ) encoded_signature , signature , signature_length , encoded_length ) ;
cleanup :
if ( mdctx ) {
EVP_MD_CTX_destroy ( mdctx ) ;
}
ast_free ( signature ) ;
return encoded_signature ;
}
/*!
* \ brief Adds the ' x5u ' ( public key URL ) field to the JWT .
*
* \ param json The JWT
* \ param x5u The public key URL
*
* \ retval 0 on success
* \ retval - 1 on failure
*/
static int stir_shaken_add_x5u ( struct ast_json * json , const char * x5u )
{
struct ast_json * value ;
value = ast_json_string_create ( x5u ) ;
if ( ! value ) {
return - 1 ;
}
return ast_json_object_set ( ast_json_object_get ( json , " header " ) , " x5u " , value ) ;
}
/*!
* \ brief Adds the ' attest ' field to the JWT .
*
* \ param json The JWT
* \ param attest The value to set attest to
*
* \ retval 0 on success
* \ retval - 1 on failure
*/
static int stir_shaken_add_attest ( struct ast_json * json , const char * attest )
{
struct ast_json * value ;
value = ast_json_string_create ( attest ) ;
if ( ! value ) {
return - 1 ;
}
return ast_json_object_set ( ast_json_object_get ( json , " payload " ) , " attest " , value ) ;
}
/*!
* \ brief Adds the ' origid ' field to the JWT .
*
* \ param json The JWT
* \ param origid The value to set origid to
*
* \ retval 0 on success
* \ retval - 1 on failure
*/
static int stir_shaken_add_origid ( struct ast_json * json , const char * origid )
{
struct ast_json * value ;
value = ast_json_string_create ( origid ) ;
if ( ! origid ) {
return - 1 ;
}
return ast_json_object_set ( ast_json_object_get ( json , " payload " ) , " origid " , value ) ;
}
/*!
* \ brief Adds the ' iat ' field to the JWT .
*
* \ param json The JWT
*
* \ retval 0 on success
* \ retval - 1 on failure
*/
static int stir_shaken_add_iat ( struct ast_json * json )
{
struct ast_json * value ;
struct timeval tv ;
int timestamp ;
tv = ast_tvnow ( ) ;
timestamp = tv . tv_sec + tv . tv_usec / 1000 ;
value = ast_json_integer_create ( timestamp ) ;
return ast_json_object_set ( ast_json_object_get ( json , " payload " ) , " iat " , value ) ;
}
struct ast_stir_shaken_payload * ast_stir_shaken_sign ( struct ast_json * json )
{
struct ast_stir_shaken_payload * payload ;
unsigned char * signature ;
const char * caller_id_num ;
char * json_str = NULL ;
struct stir_shaken_certificate * cert = NULL ;
payload = stir_shaken_verify_json ( json ) ;
if ( ! payload ) {
return NULL ;
}
/* From the payload section of the JSON, get the orig section, and then get
* the value of tn . This will be the caller ID number */
caller_id_num = ast_json_string_get ( ast_json_object_get ( ast_json_object_get (
ast_json_object_get ( json , " payload " ) , " orig " ) , " tn " ) ) ;
if ( ! caller_id_num ) {
ast_log ( LOG_ERROR , " Failed to get caller ID number from JWT \n " ) ;
goto cleanup ;
}
cert = stir_shaken_certificate_get_by_caller_id_number ( caller_id_num ) ;
if ( ! cert ) {
ast_log ( LOG_ERROR , " Failed to retrieve certificate for caller ID "
" '%s' \n " , caller_id_num ) ;
goto cleanup ;
}
if ( stir_shaken_add_x5u ( json , stir_shaken_certificate_get_public_key_url ( cert ) ) ) {
ast_log ( LOG_ERROR , " Failed to add 'x5u' (public key URL) to payload \n " ) ;
goto cleanup ;
}
/* 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 " ) ) {
ast_log ( LOG_ERROR , " Failed to add 'attest' to payload \n " ) ;
goto cleanup ;
}
if ( stir_shaken_add_origid ( json , " asterisk " ) ) {
ast_log ( LOG_ERROR , " Failed to add 'origid' to payload \n " ) ;
goto cleanup ;
}
if ( stir_shaken_add_iat ( json ) ) {
ast_log ( LOG_ERROR , " Failed to add 'iat' to payload \n " ) ;
goto cleanup ;
}
json_str = ast_json_dump_string ( json ) ;
if ( ! json_str ) {
ast_log ( LOG_ERROR , " Failed to convert JSON to string \n " ) ;
goto cleanup ;
}
signature = stir_shaken_sign ( json_str , stir_shaken_certificate_get_private_key ( cert ) ) ;
if ( ! signature ) {
goto cleanup ;
}
payload - > signature = signature ;
ao2_cleanup ( cert ) ;
ast_json_free ( json_str ) ;
return payload ;
cleanup :
ao2_cleanup ( cert ) ;
ast_stir_shaken_payload_free ( payload ) ;
ast_json_free ( json_str ) ;
return NULL ;
}
static int reload_module ( void )