diff options
Diffstat (limited to 'res')
-rw-r--r-- | res/res_pjsip.c | 13 | ||||
-rw-r--r-- | res/res_pjsip/pjsip_configuration.c | 17 | ||||
-rw-r--r-- | res/res_rtp_asterisk.c | 335 |
3 files changed, 301 insertions, 64 deletions
diff --git a/res/res_pjsip.c b/res/res_pjsip.c index 7499ded3e..1b59b2834 100644 --- a/res/res_pjsip.c +++ b/res/res_pjsip.c @@ -772,6 +772,18 @@ If this is not set or the value provided is 0 rekeying will be disabled. </para></description> </configOption> + <configOption name="dtls_auto_generate_cert" default="no"> + <synopsis>Whether or not to automatically generate an ephemeral X.509 certificate</synopsis> + <description> + <para> + If enabled, Asterisk will generate an X.509 certificate for each DTLS session. + This option only applies if <replaceable>media_encryption</replaceable> is set + to <literal>dtls</literal>. This option will be automatically enabled if + <literal>webrtc</literal> is enabled and <literal>dtls_cert_file</literal> is + not specified. + </para> + </description> + </configOption> <configOption name="dtls_cert_file"> <synopsis>Path to certificate file to present to peer</synopsis> <description><para> @@ -1028,6 +1040,7 @@ use_received_transport. The following configuration settings also get defaulted as follows:</para> <para>media_encryption=dtls</para> + <para>dtls_auto_generate_cert=yes (if dtls_cert_file is not set)</para> <para>dtls_verify=fingerprint</para> <para>dtls_setup=actpass</para> </description> diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c index 6db5b3898..2b6a2bb2a 100644 --- a/res/res_pjsip/pjsip_configuration.c +++ b/res/res_pjsip/pjsip_configuration.c @@ -991,6 +991,13 @@ static int dtlsrekey_to_str(const void *obj, const intptr_t *args, char **buf) buf, "%u", endpoint->media.rtp.dtls_cfg.rekey) >=0 ? 0 : -1; } +static int dtlsautogeneratecert_to_str(const void *obj, const intptr_t *args, char **buf) +{ + const struct ast_sip_endpoint *endpoint = obj; + *buf = ast_strdup(AST_YESNO(endpoint->media.rtp.dtls_cfg.ephemeral_cert)); + return 0; +} + static int dtlscertfile_to_str(const void *obj, const intptr_t *args, char **buf) { const struct ast_sip_endpoint *endpoint = obj; @@ -1353,6 +1360,10 @@ static int sip_endpoint_apply_handler(const struct ast_sorcery *sorcery, void *o return -1; } + if (ast_rtp_dtls_cfg_validate(&endpoint->media.rtp.dtls_cfg)) { + return -1; + } + endpoint->media.topology = ast_stream_topology_create_from_format_cap(endpoint->media.codecs); if (!endpoint->media.topology) { return -1; @@ -1377,9 +1388,8 @@ static int sip_endpoint_apply_handler(const struct ast_sorcery *sorcery, void *o endpoint->media.rtp.dtls_cfg.verify = AST_RTP_DTLS_VERIFY_FINGERPRINT; if (ast_strlen_zero(endpoint->media.rtp.dtls_cfg.certfile)) { - ast_log(LOG_ERROR, "WebRTC can't be enabled on endpoint '%s' - a DTLS cert " - "has not been specified", ast_sorcery_object_get_id(endpoint)); - return -1; + /* If no certificate has been specified, try to automatically create one */ + endpoint->media.rtp.dtls_cfg.ephemeral_cert = 1; } } @@ -1967,6 +1977,7 @@ int ast_res_pjsip_initialize_configuration(void) ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtp_engine", "asterisk", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, media.rtp.engine)); ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_verify", "no", dtls_handler, dtlsverify_to_str, NULL, 0, 0); ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_rekey", "0", dtls_handler, dtlsrekey_to_str, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_auto_generate_cert", "no", dtls_handler, dtlsautogeneratecert_to_str, NULL, 0, 0); ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_cert_file", "", dtls_handler, dtlscertfile_to_str, NULL, 0, 0); ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_private_key", "", dtls_handler, dtlsprivatekey_to_str, NULL, 0, 0); ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_cipher", "", dtls_handler, dtlscipher_to_str, NULL, 0, 0); diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c index f5d913458..15ca1503b 100644 --- a/res/res_rtp_asterisk.c +++ b/res/res_rtp_asterisk.c @@ -1593,46 +1593,31 @@ static int dtls_setup_rtcp(struct ast_rtp_instance *instance) return dtls_details_initialize(&rtp->rtcp->dtls, rtp->ssl_ctx, rtp->dtls.dtls_setup); } -/*! \pre instance is locked */ -static int ast_rtp_dtls_set_configuration(struct ast_rtp_instance *instance, const struct ast_rtp_dtls_cfg *dtls_cfg) +static const SSL_METHOD *get_dtls_method(void) { - struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); - int res; -#ifdef HAVE_OPENSSL_EC - EC_KEY *ecdh; -#endif - - if (!dtls_cfg->enabled) { - return 0; - } - - if (!ast_rtp_engine_srtp_is_registered()) { - ast_log(LOG_ERROR, "SRTP support module is not loaded or available. Try loading res_srtp.so.\n"); - return -1; - } - - if (rtp->ssl_ctx) { - return 0; - } - #if OPENSSL_VERSION_NUMBER < 0x10002000L || defined(LIBRESSL_VERSION_NUMBER) - rtp->ssl_ctx = SSL_CTX_new(DTLSv1_method()); + return DTLSv1_method(); #else - rtp->ssl_ctx = SSL_CTX_new(DTLS_method()); + return DTLS_method(); #endif - if (!rtp->ssl_ctx) { - return -1; - } +} - SSL_CTX_set_read_ahead(rtp->ssl_ctx, 1); +struct dtls_cert_info { + EVP_PKEY *private_key; + X509 *certificate; +}; #ifdef HAVE_OPENSSL_EC +static void configure_dhparams(const struct ast_rtp *rtp, const struct ast_rtp_dtls_cfg *dtls_cfg) +{ + EC_KEY *ecdh; + if (!ast_strlen_zero(dtls_cfg->pvtfile)) { BIO *bio = BIO_new_file(dtls_cfg->pvtfile, "r"); - if (bio != NULL) { + if (bio) { DH *dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); - if (dh != NULL) { + if (dh) { if (SSL_CTX_set_tmp_dh(rtp->ssl_ctx, dh)) { long options = SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_SINGLE_DH_USE | SSL_OP_SINGLE_ECDH_USE; @@ -1644,9 +1629,10 @@ static int ast_rtp_dtls_set_configuration(struct ast_rtp_instance *instance, con BIO_free(bio); } } + /* enables AES-128 ciphers, to get AES-256 use NID_secp384r1 */ ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); - if (ecdh != NULL) { + if (ecdh) { if (SSL_CTX_set_tmp_ecdh(rtp->ssl_ctx, ecdh)) { #ifndef SSL_CTRL_SET_ECDH_AUTO #define SSL_CTRL_SET_ECDH_AUTO 94 @@ -1660,8 +1646,251 @@ static int ast_rtp_dtls_set_configuration(struct ast_rtp_instance *instance, con } EC_KEY_free(ecdh); } +} + +static int create_ephemeral_ec_keypair(EVP_PKEY **keypair) +{ + EC_KEY *eckey = NULL; + EC_GROUP *group = NULL; + + group = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1); + if (!group) { + goto error; + } + + EC_GROUP_set_asn1_flag(group, OPENSSL_EC_NAMED_CURVE); + EC_GROUP_set_point_conversion_form(group, POINT_CONVERSION_UNCOMPRESSED); + + eckey = EC_KEY_new(); + if (!eckey) { + goto error; + } + + if (!EC_KEY_set_group(eckey, group)) { + goto error; + } + + if (!EC_KEY_generate_key(eckey)) { + goto error; + } + + *keypair = EVP_PKEY_new(); + if (!*keypair) { + goto error; + } + + EVP_PKEY_assign_EC_KEY(*keypair, eckey); + EC_GROUP_free(group); + + return 0; + +error: + EC_KEY_free(eckey); + EC_GROUP_free(group); + + return -1; +} + +/* From OpenSSL's x509 command */ +#define SERIAL_RAND_BITS 159 + +static int create_ephemeral_certificate(EVP_PKEY *keypair, X509 **certificate) +{ + X509 *cert = NULL; + BIGNUM *serial = NULL; + X509_NAME *name = NULL; + + cert = X509_new(); + if (!cert) { + goto error; + } + + if (!X509_set_version(cert, 2)) { + goto error; + } + + /* Set the public key */ + X509_set_pubkey(cert, keypair); + + /* Generate a random serial number */ + if (!(serial = BN_new()) + || !BN_rand(serial, SERIAL_RAND_BITS, -1, 0) + || !BN_to_ASN1_INTEGER(serial, X509_get_serialNumber(cert))) { + goto error; + } + + /* + * Validity period - Current Chrome & Firefox make it 31 days starting + * with yesterday at the current time, so we will do the same. + */ + if (!X509_time_adj_ex(X509_get_notBefore(cert), -1, 0, NULL) + || !X509_time_adj_ex(X509_get_notAfter(cert), 30, 0, NULL)) { + goto error; + } + + /* Set the name and issuer */ + if (!(name = X509_get_subject_name(cert)) + || !X509_NAME_add_entry_by_NID(name, NID_commonName, MBSTRING_ASC, + (unsigned char *) "asterisk", -1, -1, 0) + || !X509_set_issuer_name(cert, name)) { + goto error; + } + + /* Sign it */ + if (!X509_sign(cert, keypair, EVP_sha256())) { + goto error; + } + + *certificate = cert; + + return 0; + +error: + BN_free(serial); + X509_free(cert); + + return -1; +} + +static int create_certificate_ephemeral(struct ast_rtp_instance *instance, + const struct ast_rtp_dtls_cfg *dtls_cfg, + struct dtls_cert_info *cert_info) +{ + /* Make sure these are initialized */ + cert_info->private_key = NULL; + cert_info->certificate = NULL; + + if (create_ephemeral_ec_keypair(&cert_info->private_key)) { + ast_log(LOG_ERROR, "Failed to create ephemeral ECDSA keypair\n"); + goto error; + } + + if (create_ephemeral_certificate(cert_info->private_key, &cert_info->certificate)) { + ast_log(LOG_ERROR, "Failed to create ephemeral X509 certificate\n"); + goto error; + } + + return 0; + + error: + X509_free(cert_info->certificate); + EVP_PKEY_free(cert_info->private_key); + + return -1; +} + +#else + +static void configure_dhparams(const struct ast_rtp *rtp, const struct ast_rtp_dtls_cfg *dtls_cfg) +{ +} + +static int create_certificate_ephemeral(struct ast_rtp_instance *instance, + const struct ast_rtp_dtls_cfg *dtls_cfg, + struct dtls_cert_info *cert_info) +{ + ast_log(LOG_ERROR, "Your version of OpenSSL does not support ECDSA keys\n"); + return -1; +} + +#endif /* HAVE_OPENSSL_EC */ + +static int create_certificate_from_file(struct ast_rtp_instance *instance, + const struct ast_rtp_dtls_cfg *dtls_cfg, + struct dtls_cert_info *cert_info) +{ + FILE *fp; + BIO *certbio = NULL; + EVP_PKEY *private_key = NULL; + X509 *cert = NULL; + char *private_key_file = ast_strlen_zero(dtls_cfg->pvtfile) ? dtls_cfg->certfile : dtls_cfg->pvtfile; + + fp = fopen(private_key_file, "r"); + if (!fp) { + ast_log(LOG_ERROR, "Failed to read private key from file '%s': %s\n", private_key_file, strerror(errno)); + goto error; + } + + if (!PEM_read_PrivateKey(fp, &private_key, NULL, NULL)) { + ast_log(LOG_ERROR, "Failed to read private key from PEM file '%s'\n", private_key_file); + fclose(fp); + goto error; + } + + if (fclose(fp)) { + ast_log(LOG_ERROR, "Failed to close private key file '%s': %s\n", private_key_file, strerror(errno)); + goto error; + } + + certbio = BIO_new(BIO_s_file()); + if (!certbio) { + ast_log(LOG_ERROR, "Failed to allocate memory for certificate fingerprinting on RTP instance '%p'\n", + instance); + goto error; + } + + if (!BIO_read_filename(certbio, dtls_cfg->certfile) + || !(cert = PEM_read_bio_X509(certbio, NULL, 0, NULL))) { + ast_log(LOG_ERROR, "Failed to read certificate from file '%s'\n", dtls_cfg->certfile); + goto error; + } + + cert_info->private_key = private_key; + cert_info->certificate = cert; + + BIO_free_all(certbio); + + return 0; + +error: + X509_free(cert); + BIO_free_all(certbio); + EVP_PKEY_free(private_key); + + return -1; +} + +static int load_dtls_certificate(struct ast_rtp_instance *instance, + const struct ast_rtp_dtls_cfg *dtls_cfg, + struct dtls_cert_info *cert_info) +{ + if (dtls_cfg->ephemeral_cert) { + return create_certificate_ephemeral(instance, dtls_cfg, cert_info); + } else if (!ast_strlen_zero(dtls_cfg->certfile)) { + return create_certificate_from_file(instance, dtls_cfg, cert_info); + } else { + return -1; + } +} -#endif /* #ifdef HAVE_OPENSSL_EC */ +/*! \pre instance is locked */ +static int ast_rtp_dtls_set_configuration(struct ast_rtp_instance *instance, const struct ast_rtp_dtls_cfg *dtls_cfg) +{ + struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); + struct dtls_cert_info cert_info = { 0 }; + int res; + + if (!dtls_cfg->enabled) { + return 0; + } + + if (!ast_rtp_engine_srtp_is_registered()) { + ast_log(LOG_ERROR, "SRTP support module is not loaded or available. Try loading res_srtp.so.\n"); + return -1; + } + + if (rtp->ssl_ctx) { + return 0; + } + + rtp->ssl_ctx = SSL_CTX_new(get_dtls_method()); + if (!rtp->ssl_ctx) { + return -1; + } + + SSL_CTX_set_read_ahead(rtp->ssl_ctx, 1); + + configure_dhparams(rtp, dtls_cfg); rtp->dtls_verify = dtls_cfg->verify; @@ -1680,25 +1909,22 @@ static int ast_rtp_dtls_set_configuration(struct ast_rtp_instance *instance, con rtp->local_hash = dtls_cfg->hash; - if (!ast_strlen_zero(dtls_cfg->certfile)) { - char *private = ast_strlen_zero(dtls_cfg->pvtfile) ? dtls_cfg->certfile : dtls_cfg->pvtfile; - BIO *certbio; - X509 *cert = NULL; + if (!load_dtls_certificate(instance, dtls_cfg, &cert_info)) { const EVP_MD *type; unsigned int size, i; unsigned char fingerprint[EVP_MAX_MD_SIZE]; char *local_fingerprint = rtp->local_fingerprint; - if (!SSL_CTX_use_certificate_file(rtp->ssl_ctx, dtls_cfg->certfile, SSL_FILETYPE_PEM)) { - ast_log(LOG_ERROR, "Specified certificate file '%s' for RTP instance '%p' could not be used\n", - dtls_cfg->certfile, instance); + if (!SSL_CTX_use_certificate(rtp->ssl_ctx, cert_info.certificate)) { + ast_log(LOG_ERROR, "Specified certificate for RTP instance '%p' could not be used\n", + instance); return -1; } - if (!SSL_CTX_use_PrivateKey_file(rtp->ssl_ctx, private, SSL_FILETYPE_PEM) || - !SSL_CTX_check_private_key(rtp->ssl_ctx)) { - ast_log(LOG_ERROR, "Specified private key file '%s' for RTP instance '%p' could not be used\n", - private, instance); + if (!SSL_CTX_use_PrivateKey(rtp->ssl_ctx, cert_info.private_key) + || !SSL_CTX_check_private_key(rtp->ssl_ctx)) { + ast_log(LOG_ERROR, "Specified private key for RTP instance '%p' could not be used\n", + instance); return -1; } @@ -1712,22 +1938,9 @@ static int ast_rtp_dtls_set_configuration(struct ast_rtp_instance *instance, con return -1; } - if (!(certbio = BIO_new(BIO_s_file()))) { - ast_log(LOG_ERROR, "Failed to allocate memory for certificate fingerprinting on RTP instance '%p'\n", - instance); - return -1; - } - - if (!BIO_read_filename(certbio, dtls_cfg->certfile) || - !(cert = PEM_read_bio_X509(certbio, NULL, 0, NULL)) || - !X509_digest(cert, type, fingerprint, &size) || - !size) { - ast_log(LOG_ERROR, "Could not produce fingerprint from certificate '%s' for RTP instance '%p'\n", - dtls_cfg->certfile, instance); - BIO_free_all(certbio); - if (cert) { - X509_free(cert); - } + if (!X509_digest(cert_info.certificate, type, fingerprint, &size) || !size) { + ast_log(LOG_ERROR, "Could not produce fingerprint from certificate for RTP instance '%p'\n", + instance); return -1; } @@ -1736,10 +1949,10 @@ static int ast_rtp_dtls_set_configuration(struct ast_rtp_instance *instance, con local_fingerprint += 3; } - *(local_fingerprint-1) = 0; + *(local_fingerprint - 1) = 0; - BIO_free_all(certbio); - X509_free(cert); + EVP_PKEY_free(cert_info.private_key); + X509_free(cert_info.certificate); } if (!ast_strlen_zero(dtls_cfg->cipher)) { |