From 0b59aafe8a9658a21a3a39b2d5e47d3844376287 Mon Sep 17 00:00:00 2001 From: Liong Sauw Ming Date: Thu, 27 Oct 2016 07:58:01 +0000 Subject: Fixed #1975: Add support to select elliptic curve and signature algorithm for TLS git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@5472 74dad513-b988-da41-8d7b-12977e46ad98 --- pjlib/include/pj/config.h | 10 ++ pjlib/include/pj/ssl_sock.h | 138 +++++++++++++++++++ pjlib/src/pj/ssl_sock_common.c | 18 +++ pjlib/src/pj/ssl_sock_ossl.c | 230 +++++++++++++++++++++++++++++++- pjsip/include/pjsip/sip_transport_tls.h | 55 ++++++++ pjsip/src/pjsip/sip_transport_tls.c | 10 ++ 6 files changed, 456 insertions(+), 5 deletions(-) diff --git a/pjlib/include/pj/config.h b/pjlib/include/pj/config.h index 80ae59d2..c8a4ef72 100644 --- a/pjlib/include/pj/config.h +++ b/pjlib/include/pj/config.h @@ -890,6 +890,16 @@ #endif +/** + * Define the maximum number of curves supported by the secure socket. + * + * Default: 32 + */ +#ifndef PJ_SSL_SOCK_MAX_CURVES +# define PJ_SSL_SOCK_MAX_CURVES 32 +#endif + + /** * Disable WSAECONNRESET error for UDP sockets on Win32 platforms. See * https://trac.pjsip.org/repos/ticket/1197. diff --git a/pjlib/include/pj/ssl_sock.h b/pjlib/include/pj/ssl_sock.h index e69d2624..a05c1f44 100644 --- a/pjlib/include/pj/ssl_sock.h +++ b/pjlib/include/pj/ssl_sock.h @@ -401,6 +401,99 @@ PJ_DECL(const char*) pj_ssl_cipher_name(pj_ssl_cipher cipher); */ PJ_DECL(pj_ssl_cipher) pj_ssl_cipher_id(const char *cipher_name); +/** + * Elliptic curves enumeration. + */ +typedef enum pj_ssl_curve +{ + PJ_TLS_UNKNOWN_CURVE = 0, + PJ_TLS_CURVE_SECT163K1 = 1, + PJ_TLS_CURVE_SECT163R1 = 2, + PJ_TLS_CURVE_SECT163R2 = 3, + PJ_TLS_CURVE_SECT193R1 = 4, + PJ_TLS_CURVE_SECT193R2 = 5, + PJ_TLS_CURVE_SECT233K1 = 6, + PJ_TLS_CURVE_SECT233R1 = 7, + PJ_TLS_CURVE_SECT239K1 = 8, + PJ_TLS_CURVE_SECT283K1 = 9, + PJ_TLS_CURVE_SECT283R1 = 10, + PJ_TLS_CURVE_SECT409K1 = 11, + PJ_TLS_CURVE_SECT409R1 = 12, + PJ_TLS_CURVE_SECT571K1 = 13, + PJ_TLS_CURVE_SECT571R1 = 14, + PJ_TLS_CURVE_SECP160K1 = 15, + PJ_TLS_CURVE_SECP160R1 = 16, + PJ_TLS_CURVE_SECP160R2 = 17, + PJ_TLS_CURVE_SECP192K1 = 18, + PJ_TLS_CURVE_SECP192R1 = 19, + PJ_TLS_CURVE_SECP224K1 = 20, + PJ_TLS_CURVE_SECP224R1 = 21, + PJ_TLS_CURVE_SECP256K1 = 22, + PJ_TLS_CURVE_SECP256R1 = 23, + PJ_TLS_CURVE_SECP384R1 = 24, + PJ_TLS_CURVE_SECP521R1 = 25, + PJ_TLS_CURVE_BRAINPOOLP256R1 = 26, + PJ_TLS_CURVE_BRAINPOOLP384R1 = 27, + PJ_TLS_CURVE_BRAINPOOLP512R1 = 28, + PJ_TLS_CURVE_ARBITRARY_EXPLICIT_PRIME_CURVES = 0XFF01, + PJ_TLS_CURVE_ARBITRARY_EXPLICIT_CHAR2_CURVES = 0XFF02 +} pj_ssl_curve; + +/** + * Get curve list supported by SSL/TLS backend. + * + * @param curves The curves buffer to receive curve list. + * @param curves_num Maximum number of curves to be received. + * + * @return PJ_SUCCESS when successful. + */ +PJ_DECL(pj_status_t) pj_ssl_curve_get_availables(pj_ssl_curve curves[], + unsigned *curve_num); + +/** + * Check if the specified curve is supported by SSL/TLS backend. + * + * @param curve The curve. + * + * @return PJ_TRUE when supported. + */ +PJ_DECL(pj_bool_t) pj_ssl_curve_is_supported(pj_ssl_curve curve); + + +/** + * Get curve name string. + * + * @param curve The curve. + * + * @return The curve name or NULL if curve is not recognized/ + * supported. + */ +PJ_DECL(const char*) pj_ssl_curve_name(pj_ssl_curve curve); + +/** + * Get curve ID from curve name string. Note that on different backends + * (e.g. OpenSSL or Symbian implementation), curve names may not be + * equivalent for the same curve ID. + * + * @param curve_name The curve name string. + * + * @return The curve ID or PJ_TLS_UNKNOWN_CURVE if the curve + * name string is not recognized/supported. + */ +PJ_DECL(pj_ssl_curve) pj_ssl_curve_id(const char *curve_name); + +/* + * Entropy enumeration + */ +typedef enum pj_ssl_entropy +{ + PJ_SSL_ENTROPY_NONE = 0, + PJ_SSL_ENTROPY_EGD = 1, + PJ_SSL_ENTROPY_RANDOM = 2, + PJ_SSL_ENTROPY_URANDOM = 3, + PJ_SSL_ENTROPY_FILE = 4, + PJ_SSL_ENTROPY_UNKNOWN = 0x0F +} pj_ssl_entropy_t; /** * This structure contains the callbacks to be called by the secure socket. @@ -769,6 +862,51 @@ typedef struct pj_ssl_sock_param */ pj_ssl_cipher *ciphers; + /** + * Number of curves contained in the specified curve preference. + * If this is set to zero, then default curve list of the backend + * will be used. + * + * Default: 0 (zero). + */ + unsigned curves_num; + + /** + * Curves and order preference. The #pj_ssl_curve_get_availables() + * can be used to check the available curves supported by backend. + */ + pj_ssl_curve *curves; + + /** + * The supported signature algorithms. Set the sigalgs string + * using this form: + * "+:+" + * Digests are: "RSA", "DSA" or "ECDSA" + * Algorithms are: "MD5", "SHA1", "SHA224", "SHA256", "SHA384", "SHA512" + * Example: "ECDSA+SHA256:RSA+SHA256" + */ + pj_str_t sigalgs; + + /** + * Reseed random number generator. + * For type #PJ_SSL_ENTROPY_FILE, parameter \a entropy_path + * must be set to a file. + * For type #PJ_SSL_ENTROPY_EGD, parameter \a entropy_path + * must be set to a socket. + * + * Default value is PJ_SSL_ENTROPY_NONE. + */ + pj_ssl_entropy_t entropy_type; + + /** + * When using a file/socket for entropy #PJ_SSL_ENTROPY_EGD or + * #PJ_SSL_ENTROPY_FILE, \a entropy_path must contain the path + * to entropy socket/file. + * + * Default value is an empty string. + */ + pj_str_t entropy_path; + /** * Security negotiation timeout. If this is set to zero (both sec and * msec), the negotiation doesn't have a timeout. diff --git a/pjlib/src/pj/ssl_sock_common.c b/pjlib/src/pj/ssl_sock_common.c index 864a0456..445aa9ef 100644 --- a/pjlib/src/pj/ssl_sock_common.c +++ b/pjlib/src/pj/ssl_sock_common.c @@ -67,10 +67,28 @@ PJ_DEF(void) pj_ssl_sock_param_copy( pj_pool_t *pool, dst->ciphers[i] = src->ciphers[i]; } + if (src->curves_num > 0) { + unsigned i; + dst->curves = (pj_ssl_curve *)pj_pool_calloc(pool, src->curves_num, + sizeof(pj_ssl_curve)); + for (i = 0; i < src->curves_num; ++i) + dst->curves[i] = src->curves[i]; + } + if (src->server_name.slen) { /* Server name must be null-terminated */ pj_strdup_with_null(pool, &dst->server_name, &src->server_name); } + + if (src->sigalgs.slen) { + /* Sigalgs name must be null-terminated */ + pj_strdup_with_null(pool, &dst->sigalgs, &src->sigalgs); + } + + if (src->entropy_path.slen) { + /* Path name must be null-terminated */ + pj_strdup_with_null(pool, &dst->entropy_path, &src->entropy_path); + } } diff --git a/pjlib/src/pj/ssl_sock_ossl.c b/pjlib/src/pj/ssl_sock_ossl.c index ceab67ab..e4ed7842 100644 --- a/pjlib/src/pj/ssl_sock_ossl.c +++ b/pjlib/src/pj/ssl_sock_ossl.c @@ -49,7 +49,13 @@ #include #include #include +#include +#include +#if !defined(OPENSSL_NO_EC) + extern int tls1_ec_nid2curve_id(int nid); + extern int tls1_ec_curve_id2nid(int curve_id); +#endif #ifdef _MSC_VER # pragma comment( lib, "libeay32") @@ -299,6 +305,13 @@ static struct openssl_ciphers_t { const char *name; } openssl_ciphers[PJ_SSL_SOCK_MAX_CIPHERS]; +/* OpenSSL available curves */ +static unsigned openssl_curves_num; +static struct openssl_curves_t { + pj_ssl_curve id; + const char *name; +} openssl_curves[PJ_SSL_SOCK_MAX_CURVES]; + /* OpenSSL application data index */ static int sslsock_idx; @@ -328,12 +341,14 @@ static pj_status_t init_openssl(void) #endif /* Init available ciphers */ - if (openssl_cipher_num == 0) { + if (openssl_cipher_num == 0 || openssl_curves_num == 0) { SSL_METHOD *meth = NULL; SSL_CTX *ctx; SSL *ssl; STACK_OF(SSL_CIPHER) *sk_cipher; unsigned i, n; + int nid; + const char *cname; meth = (SSL_METHOD*)SSLv23_server_method(); if (!meth) @@ -352,6 +367,7 @@ static pj_status_t init_openssl(void) SSL_CTX_set_cipher_list(ctx, "ALL:COMPLEMENTOFALL"); ssl = SSL_new(ctx); + sk_cipher = SSL_get_ciphers(ssl); n = sk_SSL_CIPHER_num(sk_cipher); @@ -365,17 +381,42 @@ static pj_status_t init_openssl(void) (pj_uint32_t)c->id & 0x00FFFFFF; openssl_ciphers[i].name = SSL_CIPHER_get_name(c); } + openssl_cipher_num = n; + + ssl->session = SSL_SESSION_new(); + +#if !defined(OPENSSL_NO_EC) + openssl_curves_num = SSL_get_shared_curve(ssl,-1); + if (openssl_curves_num > PJ_ARRAY_SIZE(openssl_curves)) + openssl_curves_num = PJ_ARRAY_SIZE(openssl_curves); + + for (i = 0; i < openssl_curves_num; i++) { + nid = SSL_get_shared_curve(ssl, i); + + if (nid & TLSEXT_nid_unknown) { + cname = "curve unknown"; + nid &= 0xFFFF; + } else { + cname = EC_curve_nid2nist(nid); + if (!cname) + cname = OBJ_nid2sn(nid); + } + + openssl_curves[i].id = tls1_ec_nid2curve_id(nid); + openssl_curves[i].name = cname; + } +#else + openssl_curves_num = 0; +#endif SSL_free(ssl); SSL_CTX_free(ctx); - - openssl_cipher_num = n; } /* Create OpenSSL application data index for SSL socket */ sslsock_idx = SSL_get_ex_new_index(0, "SSL socket", NULL, NULL, NULL); - return PJ_SUCCESS; + return status; } @@ -498,7 +539,12 @@ static int verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx) /* Setting SSL sock cipher list */ static pj_status_t set_cipher_list(pj_ssl_sock_t *ssock); - +/* Setting SSL sock curves list */ +static pj_status_t set_curves_list(pj_ssl_sock_t *ssock); +/* Setting sigalgs list */ +static pj_status_t set_sigalgs(pj_ssl_sock_t *ssock); +/* Setting entropy for rng */ +static void set_entropy(pj_ssl_sock_t *ssock); /* Create and initialize new SSL context and instance */ static pj_status_t create_ssl(pj_ssl_sock_t *ssock) @@ -523,6 +569,8 @@ static pj_status_t create_ssl(pj_ssl_sock_t *ssock) /* Make sure OpenSSL library has been initialized */ init_openssl(); + set_entropy(ssock); + if (ssock->param.proto == PJ_SSL_SOCK_PROTO_DEFAULT) ssock->param.proto = PJ_SSL_SOCK_PROTO_SSL23; @@ -775,6 +823,16 @@ static pj_status_t create_ssl(pj_ssl_sock_t *ssock) if (status != PJ_SUCCESS) return status; + /* Set curve list */ + status = set_curves_list(ssock); + if (status != PJ_SUCCESS) + return status; + + /* Set sigalg list */ + status = set_sigalgs(ssock); + if (status != PJ_SUCCESS) + return status; + /* Setup SSL BIOs */ ssock->ossl_rbio = BIO_new(BIO_s_mem()); ssock->ossl_wbio = BIO_new(BIO_s_mem()); @@ -939,6 +997,89 @@ static pj_status_t set_cipher_list(pj_ssl_sock_t *ssock) return PJ_SUCCESS; } +static pj_status_t set_curves_list(pj_ssl_sock_t *ssock) +{ +#if !defined(OPENSSL_NO_EC) + int ret; + int curves[PJ_SSL_SOCK_MAX_CURVES]; + int cnt; + + if (ssock->param.curves_num == 0) + return PJ_SUCCESS; + + for (cnt = 0; cnt < ssock->param.curves_num; cnt++) { + curves[cnt] = tls1_ec_curve_id2nid(ssock->param.curves[cnt]); + } + + if( ssock->ossl_ssl->server ) { + ret = SSL_set1_curves(ssock->ossl_ssl, curves, + ssock->param.curves_num); + if (ret < 1) + return GET_SSL_STATUS(ssock); + } else { + ret = SSL_CTX_set1_curves(ssock->ossl_ctx, curves, + ssock->param.curves_num); + if (ret < 1) + return GET_SSL_STATUS(ssock); + } + + return PJ_SUCCESS; +#else + return PJ_ENOTSUP; +#endif +} + +static pj_status_t set_sigalgs(pj_ssl_sock_t *ssock) +{ + int ret; + + if (ssock->param.sigalgs.ptr && ssock->param.sigalgs.slen) { + if (ssock->is_server) { + ret = SSL_set1_client_sigalgs_list(ssock->ossl_ssl, + ssock->param.sigalgs.ptr); + } else { + ret = SSL_set1_sigalgs_list(ssock->ossl_ssl, + ssock->param.sigalgs.ptr); + } + + if (ret < 1) + return GET_SSL_STATUS(ssock); + } + + return PJ_SUCCESS; +} + +static void set_entropy(pj_ssl_sock_t *ssock) +{ + int ret; + + switch (ssock->param.entropy_type) { +#ifndef OPENSSL_NO_EGD + case PJ_SSL_ENTROPY_EGD: + ret = RAND_egd(ssock->param.entropy_path.ptr); + break; +#endif + case PJ_SSL_ENTROPY_RANDOM: + ret = RAND_load_file("/dev/random",255); + break; + case PJ_SSL_ENTROPY_URANDOM: + ret = RAND_load_file("/dev/urandom",255); + break; + case PJ_SSL_ENTROPY_FILE: + ret = RAND_load_file(ssock->param.entropy_path.ptr,255); + break; + case PJ_SSL_ENTROPY_NONE: + default: + return; + break; + } + + if (ret < 0) { + PJ_LOG(3, (ssock->pool->obj_name, "SSL failed to reseed with " + "entropy type %d", + ssock->param.entropy_type)); + } +} /* Parse OpenSSL ASN1_TIME to pj_time_val and GMT info */ static pj_bool_t parse_ossl_asn1_time(pj_time_val *tv, pj_bool_t *gmt, @@ -2231,6 +2372,85 @@ PJ_DEF(pj_bool_t) pj_ssl_cipher_is_supported(pj_ssl_cipher cipher) return PJ_FALSE; } +/* Get available curves. */ +PJ_DEF(pj_status_t) pj_ssl_curve_get_availables(pj_ssl_curve curves[], + unsigned *curve_num) +{ + unsigned i; + + PJ_ASSERT_RETURN(curves && curve_num, PJ_EINVAL); + + if (openssl_curves_num == 0) { + init_openssl(); + shutdown_openssl(); + } + + if (openssl_curves_num == 0) { + *curve_num = 0; + return PJ_ENOTFOUND; + } + + *curve_num = PJ_MIN(*curve_num, openssl_curves_num); + + for (i = 0; i < *curve_num; ++i) + curves[i] = openssl_curves[i].id; + + return PJ_SUCCESS; +} + +/* Get curve name string. */ +PJ_DEF(const char*) pj_ssl_curve_name(pj_ssl_curve curve) +{ + unsigned i; + + if (openssl_curves_num == 0) { + init_openssl(); + shutdown_openssl(); + } + + for (i = 0; i < openssl_curves_num; ++i) { + if (curve == openssl_curves[i].id) + return openssl_curves[i].name; + } + + return NULL; +} + +/* Get curve ID from curve name string. */ +PJ_DEF(pj_ssl_curve) pj_ssl_curve_id(const char *curve_name) +{ + unsigned i; + + if (openssl_curves_num == 0) { + init_openssl(); + shutdown_openssl(); + } + + for (i = 0; i < openssl_curves_num; ++i) { + if (!pj_ansi_stricmp(openssl_curves[i].name, curve_name)) + return openssl_curves[i].id; + } + + return PJ_TLS_UNKNOWN_CURVE; +} + +/* Check if the specified curve is supported by SSL/TLS backend. */ +PJ_DEF(pj_bool_t) pj_ssl_curve_is_supported(pj_ssl_curve curve) +{ + unsigned i; + + if (openssl_curves_num == 0) { + init_openssl(); + shutdown_openssl(); + } + + for (i = 0; i < openssl_curves_num; ++i) { + if (curve == openssl_curves[i].id) + return PJ_TRUE; + } + + return PJ_FALSE; +} /* * Create SSL socket instance. diff --git a/pjsip/include/pjsip/sip_transport_tls.h b/pjsip/include/pjsip/sip_transport_tls.h index 98a2c84d..f7645c46 100644 --- a/pjsip/include/pjsip/sip_transport_tls.h +++ b/pjsip/include/pjsip/sip_transport_tls.h @@ -140,6 +140,51 @@ typedef struct pjsip_tls_setting */ pj_ssl_cipher *ciphers; + /** + * Number of curves contained in the specified curve preference. + * If this is set to zero, then default curve list of the backend + * will be used. + * + * Default: 0 (zero). + */ + unsigned curves_num; + + /** + * Curves and order preference. The #pj_ssl_curve_get_availables() + * can be used to check the available curves supported by backend. + */ + pj_ssl_curve *curves; + + /** + * The supported signature algorithms. Set the sigalgs string + * using this form: + * "+:+" + * Digests are: "RSA", "DSA" or "ECDSA" + * Algorithms are: "MD5", "SHA1", "SHA224", "SHA256", "SHA384", "SHA512" + * Example: "ECDSA+SHA256:RSA+SHA256" + */ + pj_str_t sigalgs; + + /** + * Reseed random number generator. + * For type #PJ_SSL_ENTROPY_FILE, parameter \a entropy_path + * must be set to a file. + * For type #PJ_SSL_ENTROPY_EGD, parameter \a entropy_path + * must be set to a socket. + * + * Default value is PJ_SSL_ENTROPY_NONE. + */ + pj_ssl_entropy_t entropy_type; + + /** + * When using a file/socket for entropy #PJ_SSL_ENTROPY_EGD or + * #PJ_SSL_ENTROPY_FILE, \a entropy_path must contain the path + * to entropy socket/file. + * + * Default value is an empty string. + */ + pj_str_t entropy_path; + /** * Specifies TLS transport behavior on the server TLS certificate * verification result: @@ -292,6 +337,8 @@ PJ_INLINE(void) pjsip_tls_setting_copy(pj_pool_t *pool, pj_strdup_with_null(pool, &dst->cert_file, &src->cert_file); pj_strdup_with_null(pool, &dst->privkey_file, &src->privkey_file); pj_strdup_with_null(pool, &dst->password, &src->password); + pj_strdup_with_null(pool, &dst->sigalgs, &src->sigalgs); + pj_strdup_with_null(pool, &dst->entropy_path, &src->entropy_path); if (src->ciphers_num) { unsigned i; dst->ciphers = (pj_ssl_cipher*) pj_pool_calloc(pool, src->ciphers_num, @@ -299,6 +346,14 @@ PJ_INLINE(void) pjsip_tls_setting_copy(pj_pool_t *pool, for (i=0; iciphers_num; ++i) dst->ciphers[i] = src->ciphers[i]; } + + if (src->curves_num) { + unsigned i; + dst->curves = (pj_ssl_curve*) pj_pool_calloc(pool, src->curves_num, + sizeof(pj_ssl_curve)); + for (i=0; icurves_num; ++i) + dst->curves[i] = src->curves[i]; + } } diff --git a/pjsip/src/pjsip/sip_transport_tls.c b/pjsip/src/pjsip/sip_transport_tls.c index 19eed519..c1789641 100644 --- a/pjsip/src/pjsip/sip_transport_tls.c +++ b/pjsip/src/pjsip/sip_transport_tls.c @@ -391,6 +391,11 @@ PJ_DEF(pj_status_t) pjsip_tls_transport_start2( pjsip_endpoint *endpt, ssock_param.read_buffer_size = PJSIP_MAX_PKT_LEN; ssock_param.ciphers_num = listener->tls_setting.ciphers_num; ssock_param.ciphers = listener->tls_setting.ciphers; + ssock_param.curves_num = listener->tls_setting.curves_num; + ssock_param.curves = listener->tls_setting.curves; + ssock_param.sigalgs = listener->tls_setting.sigalgs; + ssock_param.entropy_type = listener->tls_setting.entropy_type; + ssock_param.entropy_path = listener->tls_setting.entropy_path; ssock_param.reuse_addr = listener->tls_setting.reuse_addr; ssock_param.qos_type = listener->tls_setting.qos_type; ssock_param.qos_ignore_error = listener->tls_setting.qos_ignore_error; @@ -1070,6 +1075,11 @@ static pj_status_t lis_create_transport(pjsip_tpfactory *factory, ssock_param.read_buffer_size = PJSIP_MAX_PKT_LEN; ssock_param.ciphers_num = listener->tls_setting.ciphers_num; ssock_param.ciphers = listener->tls_setting.ciphers; + ssock_param.curves_num = listener->tls_setting.curves_num; + ssock_param.curves = listener->tls_setting.curves; + ssock_param.sigalgs = listener->tls_setting.sigalgs; + ssock_param.entropy_type = listener->tls_setting.entropy_type; + ssock_param.entropy_path = listener->tls_setting.entropy_path; ssock_param.qos_type = listener->tls_setting.qos_type; ssock_param.qos_ignore_error = listener->tls_setting.qos_ignore_error; pj_memcpy(&ssock_param.qos_params, &listener->tls_setting.qos_params, -- cgit v1.2.3