Files
media_cas_packager_sdk/example/wv_ecmg_example.cc
Widevine Buildbot b215264c6d Support for group license
Content keys in ECM v3 can now additionally be encrypted by group
entitlement keys.
2021-03-04 22:51:24 +00:00

232 lines
8.6 KiB
C++

////////////////////////////////////////////////////////////////////////////////
// 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 <netinet/in.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cerrno>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <ostream>
#include <string>
#include <cstdint>
#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<uint16_t>(*(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);
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);
size_t processed_total_length = 0;
while (processed_total_length < request_length) {
bzero(response, kBufferSizeBytes);
size_t processed_length = 0;
ecmg->HandleRequest(kBufferSizeBytes - processed_total_length,
request + processed_total_length, kBufferSizeBytes,
response, response_length, processed_length);
if (processed_length == 0) {
std::cerr << "Failed to process the request" << std::endl;
return;
}
processed_total_length += processed_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<EntitlementKeyInfo> 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, /*group_id=*/"", /*is_even_key=*/true,
entitlement_key_id_even, entitlement_key_value_even},
{track_types_hd, /*group_id=*/"", /*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<char*>(&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<struct sockaddr*>(&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;
}
// Ignoring SIGCHLD signal to prevent Zombie processes.
signal(SIGCHLD, SIG_IGN);
// 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<struct sockaddr*>(&client_address),
&client_address_size);
std::cout << "\nTCP connection " << client_socket_fd << " start"
<< std::endl;
if (client_socket_fd < 0 && errno != EINTR) {
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;
}