//////////////////////////////////////////////////////////////////////////////// // Copyright 2020 Google LLC. // // This software is licensed under the terms defined in the Widevine Master // License Agreement. For a copy of this agreement, please contact // widevine-licensing@google.com. //////////////////////////////////////////////////////////////////////////////// // Example of Simulcrypt ECMG with custom entitlement key fetcher. #include #include #include #include #include #include #include #include #include #include #include #include #include "media_cas_packager_sdk/public/wv_cas_ecmg_client_handler.h" #include "media_cas_packager_sdk/public/wv_cas_types.h" namespace { using widevine::cas::EcmgConfig; using widevine::cas::EntitlementKeyInfo; using widevine::cas::WvCasEcmgClientHandler; constexpr int kServerPortNumber = 1234; constexpr int kListenQueueSize = 20; constexpr int kBufferSizeBytes = 2048; constexpr unsigned char kTestECPrivateKey2Secp256r1[] = { 0x30, 0x77, 0x02, 0x01, 0x01, 0x04, 0x20, 0x34, 0x9a, 0xf2, 0x95, 0x94, 0xd4, 0xca, 0xb9, 0xa0, 0x81, 0xe4, 0x1c, 0xf5, 0xde, 0x8d, 0x23, 0xf6, 0x79, 0xba, 0x3c, 0x6e, 0xc9, 0x0b, 0x56, 0x0f, 0x07, 0x5e, 0x9f, 0xe9, 0x38, 0x18, 0xfc, 0xa0, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0xa1, 0x44, 0x03, 0x42, 0x00, 0x04, 0x7b, 0x2a, 0x61, 0x59, 0xe5, 0x1b, 0xb6, 0x30, 0x7b, 0x59, 0x98, 0x42, 0x59, 0x37, 0xfb, 0x46, 0xfe, 0x53, 0xfe, 0x32, 0xa1, 0x5c, 0x93, 0x36, 0x11, 0xb0, 0x5a, 0xae, 0xa4, 0x48, 0xe3, 0x20, 0x12, 0xce, 0x78, 0xa7, 0x7f, 0xfd, 0x73, 0x5e, 0x09, 0x77, 0x53, 0x77, 0x8f, 0xd6, 0x1b, 0x26, 0xfa, 0xc4, 0x2c, 0xc4, 0x11, 0xfa, 0x72, 0x6a, 0xbe, 0x94, 0x78, 0x4d, 0x74, 0x20, 0x27, 0x86}; void BuildEcmgConfig(EcmgConfig* config) { config->delay_start = 200; // in milliseconds. config->delay_stop = 200; // in milliseconds. config->ecm_rep_period = 100; // in milliseconds. config->max_comp_time = 100; // in milliseconds. config->access_criteria_transfer_mode = 0; config->number_of_content_keys = 2; config->crypto_mode = widevine::cas::CryptoMode::kAesCtr; config->ecc_private_signing_key.assign( std::begin(kTestECPrivateKey2Secp256r1), std::end(kTestECPrivateKey2Secp256r1)); } void PrintMessage(const std::string& description, const char* const message, size_t length) { std::cout << description << std::endl; for (size_t i = 0; i < length; i++) { printf("'\\x%02x', ", static_cast(*(message + i))); fflush(stdout); } printf("\n"); fflush(stdout); } void ServeClient(int socket_fd, WvCasEcmgClientHandler* ecmg) { char request[kBufferSizeBytes]; char response[kBufferSizeBytes]; while (true) { bzero(request, kBufferSizeBytes); bzero(response, kBufferSizeBytes); size_t response_length = 0; size_t request_length = recv(socket_fd, request, kBufferSizeBytes, 0); if (request_length == 0) { std::cout << "No more request from client." << std::endl; return; } if (request_length < 0) { std::cerr << "Failed to receive request from client." << std::endl; return; } PrintMessage("Request", request, request_length); ecmg->HandleRequest(kBufferSizeBytes, request, kBufferSizeBytes, response, &response_length); PrintMessage("Response", response, response_length); if (send(socket_fd, response, response_length, 0) < 0) { std::cerr << "Failed to send response to client." << std::endl; return; } } } std::vector MyEntitlementKeyFetcherFun(uint16_t channel_id, uint16_t stream_id, uint16_t ecm_id) { // Fill the function to get entitlement keys based on the input parameters. // Potential step 1: Convert the input {channel_id, stream_id, ecm_id} to // EntitlementRequestParams defined in wv_cas_key_fetcher.h, such as // content_id, content provider, etc. // Potential step 2: Make the key fetch request. Refer to wv_cas_key_fetcher // and wv_cas_curl_key_fetcher for APIs that can fetch keys from Widevine // server with EntitlementRequestParams. // The size of the returned vector must not exceed 2. When the vector size is // 2, one of them must be specified as even key and the other must be odd key. constexpr char track_types_hd[] = "HD"; constexpr char entitlement_key_id_even[] = "fake_key_id1...."; constexpr char entitlement_key_value_even[] = "fakefakefakefakefakefakefake1..."; constexpr char entitlement_key_id_odd[] = "fake_key_id2...."; constexpr char entitlement_key_value_odd[] = "fakefakefakefakefakefakefake2..."; return {{track_types_hd, /*is_even_key=*/true, entitlement_key_id_even, entitlement_key_value_even}, {track_types_hd, /*is_even_key=*/false, entitlement_key_id_odd, entitlement_key_value_odd}}; } int create_listen_socket_fd() { // Server address. struct sockaddr_in server_address; bzero(reinterpret_cast(&server_address), sizeof(server_address)); server_address.sin_family = AF_INET; server_address.sin_addr.s_addr = htonl(INADDR_ANY); server_address.sin_port = htons(kServerPortNumber); // Create a listening socket. int listen_socket_fd = socket(AF_INET, SOCK_STREAM, /* protocol= */ 0); if (listen_socket_fd < 0) { std::cerr << "Failed to open listening socket" << std::endl; } // Set SO_REUSEADDR on a socket to true (1). int optval = 1; setsockopt(listen_socket_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); // Bind address. if (bind(listen_socket_fd, reinterpret_cast(&server_address), sizeof(server_address)) < 0) { std::cerr << "Failed to bind on server socket" << std::endl; } return listen_socket_fd; } } // namespace int main(int argc, char** argv) { EcmgConfig ecmg_config; BuildEcmgConfig(&ecmg_config); int listen_socket_fd = create_listen_socket_fd(); // Listen for connection from clients. std::cout << "Server listening ..." << std::endl << std::flush; int return_val = listen(listen_socket_fd, kListenQueueSize); switch (return_val) { case EADDRINUSE: std::cerr << "Another socket is already listening on the same port." << std::endl; break; case EBADF: std::cerr << "The argument sockfd is not a valid descriptor." << std::endl; break; case ENOTSOCK: std::cerr << "The argument sockfd is not a socket." << std::endl; break; case EOPNOTSUPP: std::cerr << "The socket is not of a type that supports the listen() " "operation." << std::endl; break; default: break; } // While loop to serve different client connections. while (true) { struct sockaddr_in client_address; socklen_t client_address_size = sizeof(client_address); int client_socket_fd = accept( listen_socket_fd, reinterpret_cast(&client_address), &client_address_size); std::cout << "\nTCP connection " << client_socket_fd << " start" << std::endl; if (client_socket_fd < 0) { std::cerr << "Failed to accept connection request from client" << std::endl; } else { if (fork() == 0) { // Reaching here, I am in the child process. close(listen_socket_fd); WvCasEcmgClientHandler client_handler(ecmg_config); client_handler.SetCustomEntitlementKeyFetcherFunc( MyEntitlementKeyFetcherFun); ServeClient(client_socket_fd, &client_handler); std::cout << "\nTCP connection " << client_socket_fd << " closed" << std::endl; close(client_socket_fd); // Terminate child process. exit(0); } // Reaching here, I am in parent process. close(client_socket_fd); } usleep(1000); } return 0; }