From 272ca9d0f6a5ffbe5f787637ccd30ae34b9272be Mon Sep 17 00:00:00 2001 From: winlin Date: Fri, 6 Nov 2020 15:05:01 +0800 Subject: [PATCH] For #1657, Support HTTPS API --- trunk/conf/full.conf | 17 ++ trunk/conf/server.crt | 20 ++ trunk/conf/server.key | 27 ++ trunk/src/app/srs_app_config.cpp | 146 ++++++++- trunk/src/app/srs_app_config.hpp | 10 + trunk/src/app/srs_app_conn.cpp | 280 ++++++++++++++++++ trunk/src/app/srs_app_conn.hpp | 32 ++ trunk/src/app/srs_app_http_api.cpp | 20 +- trunk/src/app/srs_app_http_api.hpp | 1 + trunk/src/app/srs_app_http_conn.cpp | 1 - trunk/src/app/srs_app_reload.cpp | 10 + trunk/src/app/srs_app_reload.hpp | 2 + trunk/src/app/srs_app_server.cpp | 52 +++- trunk/src/app/srs_app_server.hpp | 5 + trunk/src/kernel/srs_kernel_error.hpp | 1 + trunk/src/protocol/srs_http_stack.cpp | 11 + trunk/src/protocol/srs_http_stack.hpp | 2 + .../src/protocol/srs_service_http_client.cpp | 14 +- trunk/src/protocol/srs_service_http_conn.cpp | 5 + trunk/src/protocol/srs_service_http_conn.hpp | 2 + 20 files changed, 647 insertions(+), 11 deletions(-) create mode 100644 trunk/conf/server.crt create mode 100644 trunk/conf/server.key diff --git a/trunk/conf/full.conf b/trunk/conf/full.conf index 33fe0c001..33fa894ef 100644 --- a/trunk/conf/full.conf +++ b/trunk/conf/full.conf @@ -197,6 +197,23 @@ http_api { # default: off allow_update off; } + # For https_api or HTTPS API. + https { + # Whether enable HTTPS API. + # default: off + enabled on; + # The listen endpoint for HTTPS API. + # default: 1986 + listen 1986; + # The SSL private key file, generated by: + # openssl genrsa -out server.key 2048 + # default: ./conf/server.key + key ./conf/server.key; + # The SSL public cert file, generated by: + # openssl req -new -x509 -key server.key -out server.crt -days 3650 -subj "/C=CN/ST=Beijing/L=Beijing/O=Me/OU=Me/CN=ossrs.net" + # default: ./conf/server.crt + cert ./conf/server.crt; + } } # embedded http server in srs. # the http streaming config, for HLS/HDS/DASH/HTTPProgressive diff --git a/trunk/conf/server.crt b/trunk/conf/server.crt new file mode 100644 index 000000000..6c93b0bb9 --- /dev/null +++ b/trunk/conf/server.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDOjCCAiICCQDdW9nBokaeVjANBgkqhkiG9w0BAQsFADBfMQswCQYDVQQGEwJD +TjEQMA4GA1UECAwHQmVpamluZzEQMA4GA1UEBwwHQmVpamluZzELMAkGA1UECgwC +TWUxCzAJBgNVBAsMAk1lMRIwEAYDVQQDDAlvc3Nycy5uZXQwHhcNMjAxMTA0MDMz +NzA2WhcNMzAxMTAyMDMzNzA2WjBfMQswCQYDVQQGEwJDTjEQMA4GA1UECAwHQmVp +amluZzEQMA4GA1UEBwwHQmVpamluZzELMAkGA1UECgwCTWUxCzAJBgNVBAsMAk1l +MRIwEAYDVQQDDAlvc3Nycy5uZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQDboRBymzLCMS0rpJ0EpBBEtkY3kKETYrAcs3AyJyB8YghrotOeY+Klh1ca +GmUMy2UncOBxZKHeupNEytOBWmdk67nrWVc5+nIWoWkqyzSkRFwzE+XbuCbZYjpF +O/G0/aka71mQyuI9W6djhxFUGPctM9ixSYF14/BkuYYDYrW0hXT5PWu0mxleVZAZ +UQsy8DLhwjj9Vn3Y3D6Ttrdax5DHH+WoPmC3L7wrgfpn0ccE8UrvkxvkUJL7pgHN +4n+21lXhrpoH6PP7TLU3Y7cq6XXNlApoitcjxKBBKKKP1jHEN5v8FKCGN0yQuGI2 +Pf+ZmuqUnWiDIjQeStknckdmliNnAgMBAAEwDQYJKoZIhvcNAQELBQADggEBABvp +HMf97otfELZBTrk/YbeosgSbyRcxVIUN9nYT06s948katw/bmDABhoslI5ePlomq +X9NrfMEz6eeFIQUOd7KKcfp/sz3nmlkykR5hLe10RxGlB2pwV+Piu9hQfP59y2X8 +7hWkT5CbtN8Jm2U151AkZkbEnKVnAbz9XS352Ta5M8AuJGp+rNcFOFZhaMG23kt3 +oqZMdehI3RO5CcIiWr/lsX+ZwcDpT+x0DC6F3lBw9r9hBv4pVSP4WZ+zuUCaano8 +BCT/FmoxUWMpusGIkhNTNJGAOH1tgtZkiIfiKCKuyHX32sLQ0ls1PUTLqbKubIlS +fYGN4X/2UKfFCXKrNes= +-----END CERTIFICATE----- diff --git a/trunk/conf/server.key b/trunk/conf/server.key new file mode 100644 index 000000000..6639ddbe2 --- /dev/null +++ b/trunk/conf/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA26EQcpsywjEtK6SdBKQQRLZGN5ChE2KwHLNwMicgfGIIa6LT +nmPipYdXGhplDMtlJ3DgcWSh3rqTRMrTgVpnZOu561lXOfpyFqFpKss0pERcMxPl +27gm2WI6RTvxtP2pGu9ZkMriPVunY4cRVBj3LTPYsUmBdePwZLmGA2K1tIV0+T1r +tJsZXlWQGVELMvAy4cI4/VZ92Nw+k7a3WseQxx/lqD5gty+8K4H6Z9HHBPFK75Mb +5FCS+6YBzeJ/ttZV4a6aB+jz+0y1N2O3Kul1zZQKaIrXI8SgQSiij9YxxDeb/BSg +hjdMkLhiNj3/mZrqlJ1ogyI0HkrZJ3JHZpYjZwIDAQABAoIBAHMEcUmjjzx1ZnNx +nK0+giqJzlHxEvnE9/e/3OTW6sNYz5IWzn4nTx1iuDG6WusNZWb3TQL1MXQj/1XK +ZmNahcUrUc03l1+MkczaMOoxStsv1Z0GMZ0UXnv/Xga98sHXCYVKjXwvU7XQVuPf +ayrAfEmW2kdV+E9V1KHbKpyBSClFtmGTCL7lFq2F+fokTDxItZNDQbkrCTPDa8sR +i4gk/vKi48AJgbnJC2uF9/0+NMJhMjRvpJ8U2roYWd8HQMuJZXYbLPwUDZDMW+rq +l6oax+GZQhrez9bJESrmWZebrJLcJMOhMHv9hVAh8Yu4Bzqhxxsrmjff5PmHIE+t +Upf4J2ECgYEA80Fs6rqbHrrNfNXGolM8h3e9wQyrpWXZnS+OBS1poosns5YalF6g +s1J/GF/lwPs7bQNsX/JSX9NTezKi326qrzbu67K8dxufPNliI10/XlYpOFJRD48m +2ShYUezbA3AipmOiS/Tr4OO0D4uZ4Uo6ThiOEZgM/Eq2vkLX8IKjAxECgYEA5yLC +JJM7Lyr7X2HHAPGBwUyMWVrmbNcECTlk4MxYCY1EKZr+1zgN3Z5do927gqLQYRWH +B3/cAl5en+HTF3aWHPw2CiDsW6jzmgrhHnQOZngh3dGgQN3hFJ5TZy97851gQrsI +xv6dEH/uzl51SKZbS4KccDczdySHu1Za75xvTvcCgYBUIUqUHBnOFPlAtYbPWU5T +49viyokK2SDcNjg+HiisqMgAWmey7M9TdbKzMWd6yOkSmN6AiIRo0+PJdgfSkqnB +k5QqPFTmNM1r9Br29CcYb2AuNHoIkY/0BvoUy4ArvjqdpVPaRUjXLKl3vuZnfo6P +t/pap2XGU+jEAMZCTkwmoQKBgQClPp5aE8CuWiZY7MifjgncLmPwxiITEi3agmPy +q2UNfyeKLzueln6jQMNlkfKq1MfxgLiGzgx2zQ0NdR+7mJZ9pnrkBuG0Ljfqm3iS +kxpwe9aKhYHyni561S5/iN0vMAZP7vO5gPK9hxkuBS4IgJaoh3pcZ7qtpTo83uIo +iEizxQKBgQDpHcsuZy4ZNAQLDnQXjFut3Pypy80NkpThCjOa6yEdleBY9CqttD0K +olVoFQ5h5dv95oBdM5TaKkQNhKDFpLG0vOYRCua7k+xfnDt5Faaiy6Qe/e7cDKbf +9QDejoY43wlEtYzwfSeojnvP0ASPwiWb8DLfBpE0uOTs8/N8qwRiBA== +-----END RSA PRIVATE KEY----- diff --git a/trunk/src/app/srs_app_config.cpp b/trunk/src/app/srs_app_config.cpp index f88e4fd66..be2b947c1 100644 --- a/trunk/src/app/srs_app_config.cpp +++ b/trunk/src/app/srs_app_config.cpp @@ -1538,6 +1538,11 @@ srs_error_t SrsConfig::reload_conf(SrsConfig* conf) if ((err = reload_http_api(old_root)) != srs_success) { return srs_error_wrap(err, "http api");; } + + // merge config: http_api.https + if ((err = reload_https_api(old_root)) != srs_success) { + return srs_error_wrap(err, "https api");; + } // merge config: http_stream if ((err = reload_http_stream(old_root)) != srs_success) { @@ -1637,6 +1642,67 @@ srs_error_t SrsConfig::reload_http_api(SrsConfDirective* old_root) return err; } +srs_error_t SrsConfig::reload_https_api(SrsConfDirective* old_root) +{ + srs_error_t err = srs_success; + + // merge config. + std::vector::iterator it; + + // state graph + // old_https_api new_https_api + // DISABLED => ENABLED + // ENABLED => DISABLED + // ENABLED => ENABLED (modified) + + SrsConfDirective* new_http_api = root->get("http_api"); + SrsConfDirective* old_http_api = old_root->get("http_api"); + + SrsConfDirective* new_https_api = (new_http_api? new_http_api->get("https") : NULL); + SrsConfDirective* old_https_api = (old_http_api? old_http_api->get("https") : NULL); + + // DISABLED => ENABLED + if (!get_https_api_enabled(old_https_api) && get_https_api_enabled(new_https_api)) { + for (it = subscribes.begin(); it != subscribes.end(); ++it) { + ISrsReloadHandler* subscribe = *it; + if ((err = subscribe->on_reload_https_api_enabled()) != srs_success) { + return srs_error_wrap(err, "https api off=>on"); + } + } + srs_trace("reload off=>on https_api success."); + return err; + } + + // ENABLED => DISABLED + if (get_https_api_enabled(old_https_api) && !get_https_api_enabled(new_https_api)) { + for (it = subscribes.begin(); it != subscribes.end(); ++it) { + ISrsReloadHandler* subscribe = *it; + if ((err = subscribe->on_reload_https_api_disabled()) != srs_success) { + return srs_error_wrap(err, "https api on=>off"); + } + } + srs_trace("reload https_api on=>off success."); + return err; + } + + // ENABLED => ENABLED (modified) + if (get_https_api_enabled(old_https_api) && get_https_api_enabled(new_https_api) + && !srs_directive_equals(old_https_api, new_https_api) + ) { + for (it = subscribes.begin(); it != subscribes.end(); ++it) { + ISrsReloadHandler* subscribe = *it; + if ((err = subscribe->on_reload_https_api_enabled()) != srs_success) { + return srs_error_wrap(err, "https api enabled"); + } + } + srs_trace("reload https api enabled success."); + return err; + } + + srs_trace("reload https_api success, nothing changed."); + return err; +} + srs_error_t SrsConfig::reload_http_stream(SrsConfDirective* old_root) { srs_error_t err = srs_success; @@ -3584,7 +3650,7 @@ srs_error_t SrsConfig::check_normal_config() for (int i = 0; conf && i < (int)conf->directives.size(); i++) { SrsConfDirective* obj = conf->at(i); string n = obj->name; - if (n != "enabled" && n != "listen" && n != "crossdomain" && n != "raw_api") { + if (n != "enabled" && n != "listen" && n != "crossdomain" && n != "raw_api" && n != "https") { return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "illegal http_api.%s", n.c_str()); } @@ -7661,6 +7727,84 @@ bool SrsConfig::get_raw_api_allow_update() return SRS_CONF_PERFER_FALSE(conf->arg0()); } +SrsConfDirective* SrsConfig::get_https_api() +{ + SrsConfDirective* conf = root->get("http_api"); + if (!conf) { + return NULL; + } + + return conf->get("https"); +} + +bool SrsConfig::get_https_api_enabled(SrsConfDirective* conf) +{ + static bool DEFAULT = false; + + conf = conf->get("enabled"); + if (!conf) { + return DEFAULT; + } + + return SRS_CONF_PERFER_FALSE(conf->arg0()); +} + +bool SrsConfig::get_https_api_enabled() +{ + SrsConfDirective* conf = get_https_api(); + return get_https_api_enabled(conf); +} + +string SrsConfig::get_https_api_listen() +{ + static string DEFAULT = "1986"; + + SrsConfDirective* conf = get_https_api(); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("listen"); + if (!conf) { + return DEFAULT; + } + + return conf->arg0(); +} + +string SrsConfig::get_https_api_ssl_key() +{ + static string DEFAULT = "./conf/server.key"; + + SrsConfDirective* conf = get_https_api(); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("key"); + if (!conf) { + return DEFAULT; + } + + return conf->arg0(); +} + +string SrsConfig::get_https_api_ssl_cert() +{ + static string DEFAULT = "./conf/server.crt"; + + SrsConfDirective* conf = get_https_api(); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("cert"); + if (!conf) { + return DEFAULT; + } + + return conf->arg0(); +} bool SrsConfig::get_srt_enabled() { diff --git a/trunk/src/app/srs_app_config.hpp b/trunk/src/app/srs_app_config.hpp index 6b7fb77c1..93d8a85b8 100644 --- a/trunk/src/app/srs_app_config.hpp +++ b/trunk/src/app/srs_app_config.hpp @@ -330,6 +330,7 @@ protected: private: // Reload the http_api section of config. virtual srs_error_t reload_http_api(SrsConfDirective* old_root); + virtual srs_error_t reload_https_api(SrsConfDirective* old_root); // Reload the http_stream section of config. // TODO: FIXME: rename to http_server. virtual srs_error_t reload_http_stream(SrsConfDirective* old_root); @@ -1014,6 +1015,15 @@ public: virtual bool get_raw_api_allow_query(); // Whether allow rpc update. virtual bool get_raw_api_allow_update(); +// https api section +private: + SrsConfDirective* get_https_api(); + virtual bool get_https_api_enabled(SrsConfDirective* conf); +public: + virtual bool get_https_api_enabled(); + virtual std::string get_https_api_listen(); + virtual std::string get_https_api_ssl_key(); + virtual std::string get_https_api_ssl_cert(); // http stream section private: // Whether http stream enabled. diff --git a/trunk/src/app/srs_app_conn.cpp b/trunk/src/app/srs_app_conn.cpp index 1dc566dde..817b010d2 100644 --- a/trunk/src/app/srs_app_conn.cpp +++ b/trunk/src/app/srs_app_conn.cpp @@ -33,6 +33,8 @@ using namespace std; #include #include #include +#include +#include ISrsDisposingHandler::ISrsDisposingHandler() { @@ -487,3 +489,281 @@ srs_error_t SrsTcpConnection::writev(const iovec *iov, int iov_size, ssize_t* nw return skt->writev(iov, iov_size, nwrite); } +SrsSslConnection::SrsSslConnection(ISrsProtocolReadWriter* c) +{ + transport = c; + ssl_ctx = NULL; + ssl = NULL; +} + +SrsSslConnection::~SrsSslConnection() +{ + if (ssl) { + // this function will free bio_in and bio_out + SSL_free(ssl); + ssl = NULL; + } + + if (ssl_ctx) { + SSL_CTX_free(ssl_ctx); + ssl_ctx = NULL; + } +} + +srs_error_t SrsSslConnection::handshake() +{ + srs_error_t err = srs_success; + + // For HTTPS, try to connect over security transport. +#if (OPENSSL_VERSION_NUMBER < 0x10002000L) // v1.0.2 + ssl_ctx = SSL_CTX_new(TLS_method()); +#else + ssl_ctx = SSL_CTX_new(TLSv1_2_method()); +#endif + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL); + srs_assert(SSL_CTX_set_cipher_list(ssl_ctx, "ALL") == 1); + + // TODO: Setup callback, see SSL_set_ex_data and SSL_set_info_callback + if ((ssl = SSL_new(ssl_ctx)) == NULL) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "SSL_new ssl"); + } + + if ((bio_in = BIO_new(BIO_s_mem())) == NULL) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_new in"); + } + + if ((bio_out = BIO_new(BIO_s_mem())) == NULL) { + BIO_free(bio_in); + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_new out"); + } + + SSL_set_bio(ssl, bio_in, bio_out); + + // SSL setup active, as server role. + SSL_set_accept_state(ssl); + SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE); + + uint8_t* data = NULL; + int r0, r1, size; + + // Setup the key and cert file for server. + string crt_file = _srs_config->get_https_api_ssl_cert(); + if ((r0 = SSL_use_certificate_file(ssl, crt_file.c_str(), SSL_FILETYPE_PEM)) != 1) { + return srs_error_new(ERROR_HTTPS_KEY_CRT, "use cert %s", crt_file.c_str()); + } + + string key_file = _srs_config->get_https_api_ssl_key(); + if ((r0 = SSL_use_RSAPrivateKey_file(ssl, key_file.c_str(), SSL_FILETYPE_PEM)) != 1) { + return srs_error_new(ERROR_HTTPS_KEY_CRT, "use key %s", key_file.c_str()); + } + + if ((r0 = SSL_check_private_key(ssl)) != 1) { + return srs_error_new(ERROR_HTTPS_KEY_CRT, "check key %s with cert %s", + key_file.c_str(), crt_file.c_str()); + } + srs_info("ssl: use key %s and cert %s", key_file.c_str(), crt_file.c_str()); + + // Receive ClientHello + while (true) { + char buf[1024]; ssize_t nn = 0; + if ((err = transport->read(buf, sizeof(buf), &nn)) != srs_success) { + return srs_error_wrap(err, "handshake: read"); + } + + if ((r0 = BIO_write(bio_in, buf, nn)) <= 0) { + // TODO: 0 or -1 maybe block, use BIO_should_retry to check. + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_write r0=%d, data=%p, size=%d", r0, buf, nn); + } + + r0 = SSL_do_handshake(ssl); r1 = SSL_get_error(ssl, r0); + if (r0 != -1 || r1 != SSL_ERROR_WANT_READ) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "handshake r0=%d, r1=%d", r0, r1); + } + + if ((size = BIO_get_mem_data(bio_out, &data)) > 0) { + // OK, reset it for the next write. + if ((r0 = BIO_reset(bio_in)) != 1) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_reset r0=%d", r0); + } + break; + } + } + + srs_info("https: ClientHello done"); + + // Send ServerHello, Certificate, Server Key Exchange, Server Hello Done + size = BIO_get_mem_data(bio_out, &data); + if (!data || size <= 0) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "handshake data=%p, size=%d", data, size); + } + if ((err = transport->write(data, size, NULL)) != srs_success) { + return srs_error_wrap(err, "handshake: write data=%p, size=%d", data, size); + } + if ((r0 = BIO_reset(bio_out)) != 1) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_reset r0=%d", r0); + } + + srs_info("https: ServerHello done"); + + // Receive Client Key Exchange, Change Cipher Spec, Encrypted Handshake Message + while (true) { + char buf[1024]; ssize_t nn = 0; + if ((err = transport->read(buf, sizeof(buf), &nn)) != srs_success) { + return srs_error_wrap(err, "handshake: read"); + } + + if ((r0 = BIO_write(bio_in, buf, nn)) <= 0) { + // TODO: 0 or -1 maybe block, use BIO_should_retry to check. + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_write r0=%d, data=%p, size=%d", r0, buf, nn); + } + + r0 = SSL_do_handshake(ssl); r1 = SSL_get_error(ssl, r0); + if (r0 == 1 && r1 == SSL_ERROR_NONE) { + break; + } + + if (r0 != -1 || r1 != SSL_ERROR_WANT_READ) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "handshake r0=%d, r1=%d", r0, r1); + } + + if ((size = BIO_get_mem_data(bio_out, &data)) > 0) { + // OK, reset it for the next write. + if ((r0 = BIO_reset(bio_in)) != 1) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_reset r0=%d", r0); + } + break; + } + } + + srs_info("https: Client done"); + + // Send New Session Ticket, Change Cipher Spec, Encrypted Handshake Message + size = BIO_get_mem_data(bio_out, &data); + if (!data || size <= 0) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "handshake data=%p, size=%d", data, size); + } + if ((err = transport->write(data, size, NULL)) != srs_success) { + return srs_error_wrap(err, "handshake: write data=%p, size=%d", data, size); + } + if ((r0 = BIO_reset(bio_out)) != 1) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_reset r0=%d", r0); + } + + srs_info("https: Server done"); + + return err; +} + +void SrsSslConnection::set_recv_timeout(srs_utime_t tm) +{ + transport->set_recv_timeout(tm); +} + +srs_utime_t SrsSslConnection::get_recv_timeout() +{ + return transport->get_recv_timeout(); +} + +srs_error_t SrsSslConnection::read_fully(void* buf, size_t size, ssize_t* nread) +{ + return transport->read_fully(buf, size, nread); +} + +int64_t SrsSslConnection::get_recv_bytes() +{ + return transport->get_recv_bytes(); +} + +int64_t SrsSslConnection::get_send_bytes() +{ + return transport->get_send_bytes(); +} + +srs_error_t SrsSslConnection::read(void* plaintext, size_t nn_plaintext, ssize_t* nread) +{ + srs_error_t err = srs_success; + + // TODO: Can we avoid copy? + int nn_cipher = nn_plaintext; + char* cipher = new char[nn_cipher]; + SrsAutoFreeA(char, cipher); + + ssize_t nn = 0; + // Read the cipher from SSL. + if ((err = transport->read(cipher, nn_cipher, &nn)) != srs_success) { + return srs_error_wrap(err, "https: read"); + } + + int r0 = BIO_write(bio_in, cipher, nn); + if (r0 <= 0) { + // TODO: 0 or -1 maybe block, use BIO_should_retry to check. + return srs_error_new(ERROR_HTTPS_READ, "BIO_write r0=%d, cipher=%p, size=%d", r0, cipher, nn); + } + + r0 = SSL_read(ssl, plaintext, nn); + if (r0 <= 0) { + return srs_error_new(ERROR_HTTPS_READ, "SSL_read r0=%d, cipher=%p, size=%d", r0, cipher, nn); + } + + srs_assert(r0 <= nn_plaintext); + if (nread) { + *nread = r0; + } + + return err; +} + +void SrsSslConnection::set_send_timeout(srs_utime_t tm) +{ + transport->set_send_timeout(tm); +} + +srs_utime_t SrsSslConnection::get_send_timeout() +{ + return transport->get_send_timeout(); +} + +srs_error_t SrsSslConnection::write(void* plaintext, size_t nn_plaintext, ssize_t* nwrite) +{ + srs_error_t err = srs_success; + + for (char* p = (char*)plaintext; p < (char*)plaintext + nn_plaintext;) { + int left = (int)nn_plaintext - (p - (char*)plaintext); + int r0 = SSL_write(ssl, (const void*)p, left); + int r1 = SSL_get_error(ssl, r0); + if (r0 <= 0) { + return srs_error_new(ERROR_HTTPS_WRITE, "https: write data=%p, size=%d, r0=%d, r1=%d", p, left, r0, r1); + } + + // Move p to the next writing position. + p += r0; + if (nwrite) { + *nwrite += (ssize_t)r0; + } + + uint8_t* data = NULL; + int size = BIO_get_mem_data(bio_out, &data); + if ((err = transport->write(data, size, NULL)) != srs_success) { + return srs_error_wrap(err, "https: write data=%p, size=%d", data, size); + } + if ((r0 = BIO_reset(bio_out)) != 1) { + return srs_error_new(ERROR_HTTPS_WRITE, "BIO_reset r0=%d", r0); + } + } + + return err; +} + +srs_error_t SrsSslConnection::writev(const iovec *iov, int iov_size, ssize_t* nwrite) +{ + srs_error_t err = srs_success; + + for (int i = 0; i < iov_size; i++) { + if ((err = write((void*)iov->iov_base, (size_t)iov->iov_len, nwrite)) != srs_success) { + return srs_error_wrap(err, "write iov base=%p, size=%d", iov->iov_base, iov->iov_len); + } + } + + return err; +} + diff --git a/trunk/src/app/srs_app_conn.hpp b/trunk/src/app/srs_app_conn.hpp index a338046ed..cace1ad08 100644 --- a/trunk/src/app/srs_app_conn.hpp +++ b/trunk/src/app/srs_app_conn.hpp @@ -30,6 +30,8 @@ #include #include +#include + #include #include #include @@ -161,4 +163,34 @@ public: virtual srs_error_t writev(const iovec *iov, int iov_size, ssize_t* nwrite); }; +// The SSL connection over TCP transport, in server mode. +class SrsSslConnection : virtual public ISrsProtocolReadWriter +{ +private: + // The under-layer plaintext transport. + ISrsProtocolReadWriter* transport; +private: + SSL_CTX* ssl_ctx; + SSL* ssl; + BIO* bio_in; + BIO* bio_out; +public: + SrsSslConnection(ISrsProtocolReadWriter* c); + virtual ~SrsSslConnection(); +public: + virtual srs_error_t handshake(); +// Interface ISrsProtocolReadWriter +public: + virtual void set_recv_timeout(srs_utime_t tm); + virtual srs_utime_t get_recv_timeout(); + virtual srs_error_t read_fully(void* buf, size_t size, ssize_t* nread); + virtual int64_t get_recv_bytes(); + virtual int64_t get_send_bytes(); + virtual srs_error_t read(void* buf, size_t size, ssize_t* nread); + virtual void set_send_timeout(srs_utime_t tm); + virtual srs_utime_t get_send_timeout(); + virtual srs_error_t write(void* buf, size_t size, ssize_t* nwrite); + virtual srs_error_t writev(const iovec *iov, int iov_size, ssize_t* nwrite); +}; + #endif diff --git a/trunk/src/app/srs_app_http_api.cpp b/trunk/src/app/srs_app_http_api.cpp index f3944f79d..95772dddd 100644 --- a/trunk/src/app/srs_app_http_api.cpp +++ b/trunk/src/app/srs_app_http_api.cpp @@ -1678,7 +1678,14 @@ SrsHttpApi::SrsHttpApi(bool https, ISrsResourceManager* cm, srs_netfd_t fd, SrsH { manager = cm; skt = new SrsTcpConnection(fd); - conn = new SrsHttpConn(this, skt, m, cip, port); + + if (https) { + ssl = new SrsSslConnection(skt); + conn = new SrsHttpConn(this, ssl, m, cip, port); + } else { + ssl = NULL; + conn = new SrsHttpConn(this, skt, m, cip, port); + } _srs_config->subscribe(this); } @@ -1688,6 +1695,7 @@ SrsHttpApi::~SrsHttpApi() _srs_config->unsubscribe(this); srs_freep(conn); + srs_freep(ssl); srs_freep(skt); } @@ -1695,6 +1703,10 @@ srs_error_t SrsHttpApi::on_start() { srs_error_t err = srs_success; + if (ssl && (err = ssl->handshake()) != srs_success) { + return srs_error_wrap(err, "handshake"); + } + if ((err = conn->set_jsonp(true)) != srs_success) { return srs_error_wrap(err, "set jsonp"); } @@ -1706,6 +1718,12 @@ srs_error_t SrsHttpApi::on_http_message(ISrsHttpMessage* r, SrsHttpResponseWrite { srs_error_t err = srs_success; + // After parsed the message, set the schema to https. + if (ssl) { + SrsHttpMessage* hm = dynamic_cast(r); + hm->set_https(true); + } + // TODO: For each API session, we use short-term HTTP connection. //SrsHttpHeader* hdr = w->header(); //hdr->set("Connection", "Close"); diff --git a/trunk/src/app/srs_app_http_api.hpp b/trunk/src/app/srs_app_http_api.hpp index 4484d9f39..dbdeafbed 100644 --- a/trunk/src/app/srs_app_http_api.hpp +++ b/trunk/src/app/srs_app_http_api.hpp @@ -263,6 +263,7 @@ private: // The manager object to manage the connection. ISrsResourceManager* manager; SrsTcpConnection* skt; + SrsSslConnection* ssl; SrsHttpConn* conn; public: SrsHttpApi(bool https, ISrsResourceManager* cm, srs_netfd_t fd, SrsHttpServeMux* m, std::string cip, int port); diff --git a/trunk/src/app/srs_app_http_conn.cpp b/trunk/src/app/srs_app_http_conn.cpp index 090d5d240..9b6650111 100644 --- a/trunk/src/app/srs_app_http_conn.cpp +++ b/trunk/src/app/srs_app_http_conn.cpp @@ -94,7 +94,6 @@ SrsHttpConn::~SrsHttpConn() srs_freep(kbps); srs_freep(clk); - srs_freep(skt); } std::string SrsHttpConn::desc() diff --git a/trunk/src/app/srs_app_reload.cpp b/trunk/src/app/srs_app_reload.cpp index 3546f44de..5203617fb 100644 --- a/trunk/src/app/srs_app_reload.cpp +++ b/trunk/src/app/srs_app_reload.cpp @@ -85,6 +85,16 @@ srs_error_t ISrsReloadHandler::on_reload_http_api_disabled() return srs_success; } +srs_error_t ISrsReloadHandler::on_reload_https_api_enabled() +{ + return srs_success; +} + +srs_error_t ISrsReloadHandler::on_reload_https_api_disabled() +{ + return srs_success; +} + srs_error_t ISrsReloadHandler::on_reload_http_api_crossdomain() { return srs_success; diff --git a/trunk/src/app/srs_app_reload.hpp b/trunk/src/app/srs_app_reload.hpp index a7af5e790..bf277f9dd 100644 --- a/trunk/src/app/srs_app_reload.hpp +++ b/trunk/src/app/srs_app_reload.hpp @@ -49,6 +49,8 @@ public: virtual srs_error_t on_reload_pithy_print(); virtual srs_error_t on_reload_http_api_enabled(); virtual srs_error_t on_reload_http_api_disabled(); + virtual srs_error_t on_reload_https_api_enabled(); + virtual srs_error_t on_reload_https_api_disabled(); virtual srs_error_t on_reload_http_api_crossdomain(); virtual srs_error_t on_reload_http_api_raw_api(); virtual srs_error_t on_reload_http_stream_enabled(); diff --git a/trunk/src/app/srs_app_server.cpp b/trunk/src/app/srs_app_server.cpp index a4116c014..a18a692aa 100644 --- a/trunk/src/app/srs_app_server.cpp +++ b/trunk/src/app/srs_app_server.cpp @@ -104,6 +104,8 @@ std::string srs_listener_type2string(SrsListenerType type) return "RTMP"; case SrsListenerHttpApi: return "HTTP-API"; + case SrsListenerHttpsApi: + return "HTTPS-API"; case SrsListenerHttpStream: return "HTTP-Server"; case SrsListenerMpegTsOverUdp: @@ -707,6 +709,7 @@ void SrsServer::dispose() // prevent fresh clients. close_listeners(SrsListenerRtmpStream); close_listeners(SrsListenerHttpApi); + close_listeners(SrsListenerHttpsApi); close_listeners(SrsListenerHttpStream); close_listeners(SrsListenerMpegTsOverUdp); close_listeners(SrsListenerRtsp); @@ -736,6 +739,7 @@ void SrsServer::gracefully_dispose() // prevent fresh clients. close_listeners(SrsListenerRtmpStream); close_listeners(SrsListenerHttpApi); + close_listeners(SrsListenerHttpsApi); close_listeners(SrsListenerHttpStream); close_listeners(SrsListenerMpegTsOverUdp); close_listeners(SrsListenerRtsp); @@ -894,6 +898,10 @@ srs_error_t SrsServer::listen() if ((err = listen_http_api()) != srs_success) { return srs_error_wrap(err, "http api listen"); } + + if ((err = listen_https_api()) != srs_success) { + return srs_error_wrap(err, "https api listen"); + } if ((err = listen_http_stream()) != srs_success) { return srs_error_wrap(err, "http stream listen"); @@ -1325,6 +1333,29 @@ srs_error_t SrsServer::listen_http_api() return err; } +srs_error_t SrsServer::listen_https_api() +{ + srs_error_t err = srs_success; + + close_listeners(SrsListenerHttpsApi); + if (_srs_config->get_https_api_enabled()) { + SrsListener* listener = new SrsBufferListener(this, SrsListenerHttpsApi); + listeners.push_back(listener); + + std::string ep = _srs_config->get_https_api_listen(); + + std::string ip; + int port; + srs_parse_endpoint(ep, ip, port); + + if ((err = listener->listen(ip, port)) != srs_success) { + return srs_error_wrap(err, "https api listen %s:%d", ip.c_str(), port); + } + } + + return err; +} + srs_error_t SrsServer::listen_http_stream() { srs_error_t err = srs_success; @@ -1550,7 +1581,9 @@ srs_error_t SrsServer::fd_to_resource(SrsListenerType type, srs_netfd_t stfd, IS if (type == SrsListenerRtmpStream) { *pr = new SrsRtmpConn(this, stfd, ip, port); } else if (type == SrsListenerHttpApi) { - *pr = new SrsHttpApi(this, stfd, http_api_mux, ip, port); + *pr = new SrsHttpApi(false, this, stfd, http_api_mux, ip, port); + } else if (type == SrsListenerHttpsApi) { + *pr = new SrsHttpApi(true, this, stfd, http_api_mux, ip, port); } else if (type == SrsListenerHttpStream) { *pr = new SrsResponseOnlyHttpConn(this, stfd, http_server, ip, port); } else { @@ -1646,6 +1679,23 @@ srs_error_t SrsServer::on_reload_http_api_disabled() return srs_success; } +srs_error_t SrsServer::on_reload_https_api_enabled() +{ + srs_error_t err = srs_success; + + if ((err = listen_https_api()) != srs_success) { + return srs_error_wrap(err, "reload https_api"); + } + + return err; +} + +srs_error_t SrsServer::on_reload_https_api_disabled() +{ + close_listeners(SrsListenerHttpsApi); + return srs_success; +} + srs_error_t SrsServer::on_reload_http_stream_enabled() { srs_error_t err = srs_success; diff --git a/trunk/src/app/srs_app_server.hpp b/trunk/src/app/srs_app_server.hpp index c60205a54..38ac91cfb 100644 --- a/trunk/src/app/srs_app_server.hpp +++ b/trunk/src/app/srs_app_server.hpp @@ -76,6 +76,8 @@ enum SrsListenerType SrsListenerGb28181RtpMux = 6, // UDP gb28181 sip server SrsListenerGb28181Sip = 7, + // HTTPS api, + SrsListenerHttpsApi = 8, }; // A common tcp listener, for RTMP/HTTP server. @@ -321,6 +323,7 @@ private: // listen at specified protocol. virtual srs_error_t listen_rtmp(); virtual srs_error_t listen_http_api(); + virtual srs_error_t listen_https_api(); virtual srs_error_t listen_http_stream(); virtual srs_error_t listen_stream_caster(); #ifdef SRS_GB28181 @@ -356,6 +359,8 @@ public: virtual srs_error_t on_reload_vhost_removed(std::string vhost); virtual srs_error_t on_reload_http_api_enabled(); virtual srs_error_t on_reload_http_api_disabled(); + virtual srs_error_t on_reload_https_api_enabled(); + virtual srs_error_t on_reload_https_api_disabled(); virtual srs_error_t on_reload_http_stream_enabled(); virtual srs_error_t on_reload_http_stream_disabled(); virtual srs_error_t on_reload_http_stream_updated(); diff --git a/trunk/src/kernel/srs_kernel_error.hpp b/trunk/src/kernel/srs_kernel_error.hpp index e0b8288f5..7b37af5aa 100644 --- a/trunk/src/kernel/srs_kernel_error.hpp +++ b/trunk/src/kernel/srs_kernel_error.hpp @@ -328,6 +328,7 @@ #define ERROR_HTTPS_HANDSHAKE 4042 #define ERROR_HTTPS_READ 4043 #define ERROR_HTTPS_WRITE 4044 +#define ERROR_HTTPS_KEY_CRT 4045 /////////////////////////////////////////////////////// // RTC protocol error. diff --git a/trunk/src/protocol/srs_http_stack.cpp b/trunk/src/protocol/srs_http_stack.cpp index 4416824fd..f0db261af 100644 --- a/trunk/src/protocol/srs_http_stack.cpp +++ b/trunk/src/protocol/srs_http_stack.cpp @@ -916,6 +916,17 @@ srs_error_t SrsHttpUri::initialize(string _url) return err; } +void SrsHttpUri::set_schema(std::string v) +{ + schema = v; + + // Update url with new schema. + size_t pos = url.find("://"); + if (pos != string::npos) { + url = schema + "://" + url.substr(pos + 3); + } +} + string SrsHttpUri::get_url() { return url; diff --git a/trunk/src/protocol/srs_http_stack.hpp b/trunk/src/protocol/srs_http_stack.hpp index 0bd712714..14a6fc8cc 100644 --- a/trunk/src/protocol/srs_http_stack.hpp +++ b/trunk/src/protocol/srs_http_stack.hpp @@ -525,6 +525,8 @@ public: public: // Initialize the http uri. virtual srs_error_t initialize(std::string _url); + // After parsed the message, set the schema to https. + virtual void set_schema(std::string v); public: virtual std::string get_url(); virtual std::string get_schema(); diff --git a/trunk/src/protocol/srs_service_http_client.cpp b/trunk/src/protocol/srs_service_http_client.cpp index 9ca239632..57f5ac5f0 100644 --- a/trunk/src/protocol/srs_service_http_client.cpp +++ b/trunk/src/protocol/srs_service_http_client.cpp @@ -59,16 +59,16 @@ SrsSslClient::SrsSslClient(SrsTcpClient* tcp) SrsSslClient::~SrsSslClient() { - if (ssl_ctx) { - SSL_CTX_free(ssl_ctx); - ssl_ctx = NULL; - } - if (ssl) { // this function will free bio_in and bio_out SSL_free(ssl); ssl = NULL; } + + if (ssl_ctx) { + SSL_CTX_free(ssl_ctx); + ssl_ctx = NULL; + } } srs_error_t SrsSslClient::handshake() @@ -77,9 +77,9 @@ srs_error_t SrsSslClient::handshake() // For HTTPS, try to connect over security transport. #if (OPENSSL_VERSION_NUMBER < 0x10002000L) // v1.0.2 - SSL_CTX* ssl_ctx = SSL_CTX_new(TLS_method()); + ssl_ctx = SSL_CTX_new(TLS_method()); #else - SSL_CTX* ssl_ctx = SSL_CTX_new(TLSv1_2_method()); + ssl_ctx = SSL_CTX_new(TLSv1_2_method()); #endif SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, srs_verify_callback); srs_assert(SSL_CTX_set_cipher_list(ssl_ctx, "ALL") == 1); diff --git a/trunk/src/protocol/srs_service_http_conn.cpp b/trunk/src/protocol/srs_service_http_conn.cpp index bb2574f63..23a2591a0 100644 --- a/trunk/src/protocol/srs_service_http_conn.cpp +++ b/trunk/src/protocol/srs_service_http_conn.cpp @@ -414,6 +414,11 @@ srs_error_t SrsHttpMessage::set_url(string url, bool allow_jsonp) return err; } +void SrsHttpMessage::set_https(bool v) +{ + _uri->set_schema(v? "https" : "http"); +} + ISrsConnection* SrsHttpMessage::connection() { return owner_conn; diff --git a/trunk/src/protocol/srs_service_http_conn.hpp b/trunk/src/protocol/srs_service_http_conn.hpp index b380faba1..fa048d4b4 100644 --- a/trunk/src/protocol/srs_service_http_conn.hpp +++ b/trunk/src/protocol/srs_service_http_conn.hpp @@ -145,6 +145,8 @@ public: virtual void set_header(SrsHttpHeader* header, bool keep_alive); // set the original messages, then update the message. virtual srs_error_t set_url(std::string url, bool allow_jsonp); + // After parsed the message, set the schema to https. + virtual void set_https(bool v); public: // Get the owner connection, maybe NULL. virtual ISrsConnection* connection();