diff --git a/circle.yml b/circle.yml index 34c81598c9..16fce5dff3 100644 --- a/circle.yml +++ b/circle.yml @@ -17,6 +17,6 @@ dependencies: test: override: - - (cd trunk && ./configure --without-ssl && make) + - (cd trunk && ./configure --with-ssl=openssl && make) - (cd trunk && ./objs/srs_utest) diff --git a/trunk/conf/full.conf b/trunk/conf/full.conf index 4edac7d3d2..358a7cbb76 100644 --- a/trunk/conf/full.conf +++ b/trunk/conf/full.conf @@ -639,6 +639,26 @@ vhost with-hls.srs.com { # default: on hls_wait_keyframe on; + #whether using AES encryption + #default: off + hls_keys on; + #how much ts that one key can encrypt + #default: 10 + hls_fragments_per_key 10; + # the hls key file name. + # we supports some variables to generate the filename. + # [vhost], the vhost of stream. + # [app], the app of stream. + # [stream], the stream name of stream. + # [seq], the sequence number of key corresponding to the ts. + hls_key_file [app]/[stream]-[seq].key; + # the key output path + # the key file is configed by hls_path/hls_key_file, the default is: + # ./objs/nginx/html/[app]/[stream]-[seq].key + hls_key_file_path ./objs/nginx/html; + # the key root URL which can support https + hls_key_url https://localhost:8080; + # on_hls, never config in here, should config in http_hooks. # for the hls http callback, @see http_hooks.on_hls of vhost hooks.callback.srs.com # @read https://github.com/ossrs/srs/wiki/v2_CN_DeliveryHLS#http-callback diff --git a/trunk/src/app/srs_app_config.cpp b/trunk/src/app/srs_app_config.cpp index 05b2624c48..4f3e6377cd 100644 --- a/trunk/src/app/srs_app_config.cpp +++ b/trunk/src/app/srs_app_config.cpp @@ -1898,8 +1898,8 @@ int SrsConfig::check_config() if (m != "enabled" && m != "hls_entry_prefix" && m != "hls_path" && m != "hls_fragment" && m != "hls_window" && m != "hls_on_error" && m != "hls_storage" && m != "hls_mount" && m != "hls_td_ratio" && m != "hls_aof_ratio" && m != "hls_acodec" && m != "hls_vcodec" && m != "hls_m3u8_file" && m != "hls_ts_file" && m != "hls_ts_floor" && m != "hls_cleanup" && m != "hls_nb_notify" - && m != "hls_wait_keyframe" && m != "hls_dispose" - ) { + && m != "hls_wait_keyframe" && m != "hls_dispose" && m != "hls_keys" && m != "hls_fragments_per_key" && m != "hls_key_file" + && m != "hls_key_file_path" && m != "hls_key_url") { ret = ERROR_SYSTEM_CONFIG_INVALID; srs_error("unsupported vhost hls directive %s, ret=%d", m.c_str(), ret); return ret; @@ -3898,6 +3898,93 @@ bool SrsConfig::get_hls_wait_keyframe(string vhost) return SRS_CONF_PERFER_TRUE(conf->arg0()); } +bool SrsConfig::get_hls_keys(string vhost) +{ + static bool DEFAULT = false; + + SrsConfDirective* conf = get_hls(vhost); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("hls_keys"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return SRS_CONF_PERFER_TRUE(conf->arg0()); +} + +int SrsConfig::get_hls_fragments_per_key(string vhost) +{ + static int DEFAULT = 10; + + SrsConfDirective* conf = get_hls(vhost); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("hls_fragments_per_key"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return ::atoi(conf->arg0().c_str()); +} + +string SrsConfig::get_hls_key_file(string vhost) +{ + static string DEFAULT = "[app]/[stream]-[seq].key"; + + SrsConfDirective* conf = get_hls(vhost); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("hls_key_file"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return conf->arg0(); +} + +string SrsConfig::get_hls_key_file_path(std::string vhost) +{ + //put the key in ts path defaultly. + static string DEFAULT = get_hls_path(vhost); + + SrsConfDirective* conf = get_hls(vhost); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("hls_key_file_path"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return conf->arg0(); +} + +string SrsConfig::get_hls_key_url(std::string vhost) +{ + //put the key in ts path defaultly. + static string DEFAULT = get_hls_path(vhost); + + SrsConfDirective* conf = get_hls(vhost); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("hls_key_url"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return conf->arg0(); +} + SrsConfDirective *SrsConfig::get_hds(const string &vhost) { SrsConfDirective* conf = get_vhost(vhost); diff --git a/trunk/src/app/srs_app_config.hpp b/trunk/src/app/srs_app_config.hpp index 89f3922d65..faff75f896 100644 --- a/trunk/src/app/srs_app_config.hpp +++ b/trunk/src/app/srs_app_config.hpp @@ -934,6 +934,26 @@ class SrsConfig * whether reap the ts when got keyframe. */ virtual bool get_hls_wait_keyframe(std::string vhost); + /** + * encrypt ts or not + */ + virtual bool get_hls_keys(std::string vhost); + /** + * how many fragments can one key encrypted. + */ + virtual int get_hls_fragments_per_key(std::string vhost); + /** + * get the HLS key file path template. + */ + virtual std::string get_hls_key_file(std::string vhost); + /** + * get the HLS key file store path. + */ + virtual std::string get_hls_key_file_path(std::string vhost); + /** + * get the HLS key file url which will be put in m3u8 + */ + virtual std::string get_hls_key_url(std::string vhost); /** * get the size of bytes to read from cdn network, for the on_hls_notify callback, * that is, to read max bytes of the bytes from the callback, or timeout or error. diff --git a/trunk/src/app/srs_app_hls.cpp b/trunk/src/app/srs_app_hls.cpp index 384cb7a9e8..ee80e3cefd 100644 --- a/trunk/src/app/srs_app_hls.cpp +++ b/trunk/src/app/srs_app_hls.cpp @@ -29,6 +29,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include #include +#include #include #include @@ -132,20 +133,88 @@ string SrsHlsCacheWriter::cache() return data; } -SrsHlsSegment::SrsHlsSegment(SrsTsContext* c, bool write_cache, bool write_file, SrsCodecAudio ac, SrsCodecVideo vc) +int SrsEncFileWriter::write(void* buf, size_t count, ssize_t* pnwrite) +{ + + srs_assert(count == SRS_TS_PACKET_SIZE); + int err = ERROR_SUCCESS; + + if(buflength != HLS_AES_ENCRYPT_BLOCK_LENGTH) + { + memcpy(tmpbuf+buflength,(char*)buf,SRS_TS_PACKET_SIZE); + buflength += SRS_TS_PACKET_SIZE; + } + if(buflength == HLS_AES_ENCRYPT_BLOCK_LENGTH) + { + unsigned char encryptedbuf[HLS_AES_ENCRYPT_BLOCK_LENGTH]; + memset(encryptedbuf,0,HLS_AES_ENCRYPT_BLOCK_LENGTH); + AES_cbc_encrypt((unsigned char *)tmpbuf, (unsigned char *)encryptedbuf, HLS_AES_ENCRYPT_BLOCK_LENGTH, &key, iv, AES_ENCRYPT); + buflength = 0; + memset(tmpbuf,0,HLS_AES_ENCRYPT_BLOCK_LENGTH); + return SrsFileWriter::write(encryptedbuf,HLS_AES_ENCRYPT_BLOCK_LENGTH,pnwrite); + } + else + { + return err; + } + +}; + +int SrsEncFileWriter::SetEncCfg(unsigned char* keyval,unsigned char *ivval) +{ + + int err = ERROR_SUCCESS; + + if (AES_set_encrypt_key(keyval, 16*8, &key)) + { + srs_error("set aes key failed\n"); + return ERROR_SYSTEM_FILE_WRITE; + } + + memcpy(iv,ivval,16); + return err; +} + +void SrsEncFileWriter::close() +{ + if(buflength > 0) + { + int addBytes = 16 - buflength % 16; + memset(tmpbuf + buflength, addBytes, addBytes); + unsigned char encryptedbuf[buflength+addBytes]; + memset(encryptedbuf,0,buflength+addBytes); + AES_cbc_encrypt((unsigned char *)tmpbuf, (unsigned char *)encryptedbuf, buflength+addBytes, &key, iv, AES_ENCRYPT); + SrsFileWriter::write(encryptedbuf,buflength+addBytes,NULL); + + buflength = 0; + memset(tmpbuf,0,HLS_AES_ENCRYPT_BLOCK_LENGTH); + } + SrsFileWriter::close(); +} + + + + +SrsHlsSegment::SrsHlsSegment(SrsTsContext* c, bool write_cache, bool write_file, SrsCodecAudio ac, SrsCodecVideo vc,SrsFileWriter *srswriter) { duration = 0; sequence_no = 0; segment_start_dts = 0; is_sequence_header = false; - writer = new SrsHlsCacheWriter(write_cache, write_file); + writer = srswriter;//new SrsHlsCacheWriter(write_cache, write_file); muxer = new SrsTSMuxer(writer, c, ac, vc); } +void SrsHlsSegment::SrsSetEncCfg(unsigned char* keyval,unsigned char *ivval) +{ + memcpy(iv,ivval,16); + dynamic_cast(writer)->SetEncCfg(keyval,ivval); +} + SrsHlsSegment::~SrsHlsSegment() { srs_freep(muxer); - srs_freep(writer); + //srs_freep(writer); } void SrsHlsSegment::update_duration(int64_t current_frame_dts) @@ -297,6 +366,8 @@ SrsHlsMuxer::SrsHlsMuxer() max_td = 0; _sequence_no = 0; current = NULL; + hls_keys = false; + hls_fragments_per_key = 10; acodec = SrsCodecAudioReserved1; should_write_cache = false; should_write_file = true; @@ -317,6 +388,7 @@ SrsHlsMuxer::~SrsHlsMuxer() srs_freep(req); srs_freep(async); srs_freep(context); + srs_freep(writer); } void SrsHlsMuxer::dispose() @@ -388,8 +460,9 @@ int SrsHlsMuxer::initialize() int SrsHlsMuxer::update_config(SrsRequest* r, string entry_prefix, string path, string m3u8_file, string ts_file, double fragment, double window, - bool ts_floor, double aof_ratio, bool cleanup, bool wait_keyframe -) { + bool ts_floor, double aof_ratio, bool cleanup, bool wait_keyframe , bool keys, + int fragments_per_key, string key_file ,string key_file_path, string key_url) +{ int ret = ERROR_SUCCESS; srs_freep(req); @@ -407,7 +480,13 @@ int SrsHlsMuxer::update_config(SrsRequest* r, string entry_prefix, accept_floor_ts = 0; hls_window = window; deviation_ts = 0; - + + hls_keys = keys; + hls_fragments_per_key = fragments_per_key; + hls_key_file = key_file; + hls_key_file_path = key_file_path; + hls_key_url = key_url; + // generate the m3u8 dir and path. m3u8_url = srs_path_build_stream(m3u8_file, req->vhost, req->app, req->stream); m3u8 = path + "/" + m3u8_url; @@ -426,6 +505,28 @@ int SrsHlsMuxer::update_config(SrsRequest* r, string entry_prefix, return ret; } srs_info("create m3u8 dir %s ok", m3u8_dir.c_str()); + + if(hls_keys && (hls_path != hls_key_file_path) ) + { + string key_file = hls_key_file; + key_file = srs_path_build_stream(key_file, req->vhost, req->app, req->stream); + + string key_dir = srs_path_dirname(hls_key_file_path + "/" + key_file); + if ((ret = srs_create_dir_recursively(key_dir)) != ERROR_SUCCESS) { + srs_error("create dir error:%d",ret); + return ret; + } + } + + + if(hls_keys) + { + writer = new SrsEncFileWriter(); + } + else + { + writer = new SrsHlsCacheWriter(should_write_cache,should_write_file); + } return ret; } @@ -476,9 +577,58 @@ int SrsHlsMuxer::segment_open(int64_t segment_start_dts) } // new segment. - current = new SrsHlsSegment(context, should_write_cache, should_write_file, default_acodec, default_vcodec); + current = new SrsHlsSegment(context, should_write_cache, should_write_file, default_acodec, default_vcodec,writer); current->sequence_no = _sequence_no++; current->segment_start_dts = segment_start_dts; + + if(hls_keys){ + + + if(current->sequence_no % hls_fragments_per_key == 0) + { + string key_file = hls_key_file; + key_file = srs_path_build_stream(key_file, req->vhost, req->app, req->stream); + + + if (true) { + std::stringstream ss; + ss << current->sequence_no; + key_file = srs_string_replace(key_file, "[seq]", ss.str()); + } + + string key_full_path = hls_key_file_path + "/" + key_file; + + if (RAND_bytes(key, 16) < 0) { + srs_error("rand key failed."); + } + + if (RAND_bytes(iv, 16) < 0) { + srs_error("rand iv failed."); + } + + int flags = O_CREAT|O_WRONLY|O_TRUNC; + mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH; + int fd; + + if ((fd = ::open(key_full_path.c_str(), flags, mode)) < 0) { + srs_error("open file %s failed %d", key_full_path.c_str(),ERROR_SYSTEM_FILE_OPENE); + return ERROR_SYSTEM_FILE_OPENE; + } + ssize_t nwrite; + if ((nwrite = ::write(fd, key, 16)) != 16) { + srs_error("write to file %s failed %d", key_full_path.c_str(),ERROR_SYSTEM_FILE_WRITE); + return ERROR_SYSTEM_FILE_WRITE; + } + + if (::close(fd) < 0) { + srs_warn("close file %s failed",key_full_path.c_str()); + } + + current->key_full_path = key_full_path; + } + current->SrsSetEncCfg(key,iv); + + } // generate filename. std::string ts_file = hls_ts_file; @@ -800,6 +950,11 @@ int SrsHlsMuxer::segment_close(string log_desc) if (unlink(segment->full_path.c_str()) < 0) { srs_warn("cleanup unlink path failed, file=%s.", segment->full_path.c_str()); } + if(hls_keys && (segment->sequence_no % hls_fragments_per_key == 0)){ + if(unlink(segment->key_full_path.c_str()) < 0){ + srs_warn("cleanup unlink path failed, file=%s.", segment->key_full_path.c_str()); + } + } } srs_freep(segment); @@ -904,6 +1059,22 @@ int SrsHlsMuxer::_refresh_m3u8(string m3u8_file) ss << "#EXT-X-DISCONTINUITY" << SRS_CONSTS_LF; srs_verbose("write m3u8 segment discontinuity success."); } + + if(hls_keys && (segment->sequence_no%hls_fragments_per_key == 0)) + { + string filename = req->stream+"-"+srs_int2str(segment->sequence_no)+".key"; + char hexiv[33]; + srs_data_to_hex(hexiv,segment->iv,16); + hexiv[32] = '\0'; + string key_path; + //if key_url is not set,only use the file name + if(hls_key_url == hls_key_file_path){ + key_path = filename; + }else{ + key_path = hls_key_url+"/"+filename; + } + ss << "#EXT-X-KEY:METHOD=AES-128,URI=" << "\""<< key_path <<"\",IV=0x"<get_hls_dispose(vhost); + + bool hls_keys = _srs_config->get_hls_keys(vhost); + int hls_fragments_per_key = _srs_config->get_hls_fragments_per_key(vhost); + string hls_key_file = _srs_config->get_hls_key_file(vhost); + string hls_key_file_path = _srs_config->get_hls_key_file_path(vhost); + string hls_key_url = _srs_config->get_hls_key_url(vhost); + + // TODO: FIXME: support load exists m3u8, to continue publish stream. // for the HLS donot requires the EXT-X-MEDIA-SEQUENCE be monotonically increase. // open muxer if ((ret = muxer->update_config(req, entry_prefix, path, m3u8_file, ts_file, hls_fragment, hls_window, ts_floor, hls_aof_ratio, - cleanup, wait_keyframe)) != ERROR_SUCCESS + cleanup, wait_keyframe,hls_keys,hls_fragments_per_key, + hls_key_file, hls_key_file_path, hls_key_url)) != ERROR_SUCCESS ) { srs_error("m3u8 muxer update config failed. ret=%d", ret); return ret; diff --git a/trunk/src/app/srs_app_hls.hpp b/trunk/src/app/srs_app_hls.hpp index 960a295bb7..378ce0ee34 100644 --- a/trunk/src/app/srs_app_hls.hpp +++ b/trunk/src/app/srs_app_hls.hpp @@ -31,6 +31,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include +#include +#include #include #include @@ -93,6 +95,43 @@ class SrsHlsCacheWriter : public SrsFileWriter virtual std::string cache(); }; +/* +* Used for HLS Encryption +*/ + + +#define HLS_AES_ENCRYPT_BLOCK_LENGTH 188*4 + +class SrsEncFileWriter: public SrsFileWriter +{ +public: + SrsEncFileWriter() + { + memset(iv,0,16); + memset(tmpbuf,0,HLS_AES_ENCRYPT_BLOCK_LENGTH); + buflength = 0; + } + virtual ~SrsEncFileWriter(){} + + virtual int write(void* buf, size_t count, ssize_t* pnwrite); + + int SetEncCfg(unsigned char* key,unsigned char *iv); + + virtual void close(); + +private: + AES_KEY key; + unsigned char iv[16]; + +private: + + char tmpbuf[HLS_AES_ENCRYPT_BLOCK_LENGTH]; + int buflength; + +}; + + + /** * the wrapper of m3u8 segment from specification: * @@ -111,14 +150,18 @@ class SrsHlsSegment // ts full file to write. std::string full_path; // the muxer to write ts. - SrsHlsCacheWriter* writer; + SrsFileWriter* writer; SrsTSMuxer* muxer; // current segment start dts for m3u8 int64_t segment_start_dts; // whether current segement is sequence header. bool is_sequence_header; + // Will be saved in m3u8 file. + unsigned char iv[16]; + // The full key path. + std::string key_full_path; public: - SrsHlsSegment(SrsTsContext* c, bool write_cache, bool write_file, SrsCodecAudio ac, SrsCodecVideo vc); + SrsHlsSegment(SrsTsContext* c, bool write_cache, bool write_file, SrsCodecAudio ac, SrsCodecVideo vc, SrsFileWriter *srswriter); virtual ~SrsHlsSegment(); public: /** @@ -126,6 +169,9 @@ class SrsHlsSegment * @current_frame_dts the dts of frame, in tbn of ts. */ virtual void update_duration(int64_t current_frame_dts); +public: + + void SrsSetEncCfg(unsigned char* keyval,unsigned char * ivval); }; /** @@ -200,6 +246,22 @@ class SrsHlsMuxer // used to detect the dup or jmp or ts. int64_t accept_floor_ts; int64_t previous_floor_ts; +private: + //encrypted or not + bool hls_keys; + int hls_fragments_per_key; + //key file name + std::string hls_key_file; + //key file path + std::string hls_key_file_path; + //key file url + std::string hls_key_url; + //media server address + + unsigned char key[16]; + unsigned char iv[16]; + + SrsFileWriter *writer; private: int _sequence_no; int max_td; @@ -250,7 +312,8 @@ class SrsHlsMuxer virtual int update_config(SrsRequest* r, std::string entry_prefix, std::string path, std::string m3u8_file, std::string ts_file, double fragment, double window, bool ts_floor, double aof_ratio, - bool cleanup, bool wait_keyframe); + bool cleanup, bool wait_keyframe, bool keys, int fragments_per_key, + std::string key_file , std::string key_file_path,std::string key_url); /** * open a new segment(a new ts file), * @param segment_start_dts use to calc the segment duration, diff --git a/trunk/src/kernel/srs_kernel_utility.cpp b/trunk/src/kernel/srs_kernel_utility.cpp index 276c321233..fe5ec56122 100644 --- a/trunk/src/kernel/srs_kernel_utility.cpp +++ b/trunk/src/kernel/srs_kernel_utility.cpp @@ -752,6 +752,28 @@ int av_toupper(int c) return c; } +string srs_int2str(int64_t value) +{ + // len(max int64_t) is 20, plus one "+-." + char tmp[22]; + snprintf(tmp, 22, "%" PRId64, value); + return tmp; +} + + +char *srs_data_to_hex(char *des,const u_int8_t *src,int len) +{ + if(src == NULL || len == 0 || des == NULL){ + return NULL; + } + for (int i=0; i> 4]; + des[i * 2 + 1] = "0123456789ABCDEF"[src[i] & 0x0F]; + } + + return des; +} + int ff_hex_to_data(u_int8_t* data, const char* p) { int c, len, v; diff --git a/trunk/src/kernel/srs_kernel_utility.hpp b/trunk/src/kernel/srs_kernel_utility.hpp index ad5dd5765b..faefa75b07 100644 --- a/trunk/src/kernel/srs_kernel_utility.hpp +++ b/trunk/src/kernel/srs_kernel_utility.hpp @@ -140,6 +140,13 @@ extern char* srs_av_base64_encode(char* out, int out_size, const u_int8_t* in, i * output hex to data={0x13, 0x90, 0x56, 0xe5, 0xa0} */ extern int ff_hex_to_data(u_int8_t* data, const char* p); +/** + * convert data string to hex. + */ +extern char *srs_data_to_hex(char *des,const u_int8_t *src,int len); + +// parse the int64 value to string. +extern std::string srs_int2str(int64_t value); /** * generate the c0 chunk header for msg.