987 lines
24 KiB
C
987 lines
24 KiB
C
|
|
#define MODULE_LOG_PREFIX "emu"
|
||
|
|
|
||
|
|
#include "globals.h"
|
||
|
|
|
||
|
|
#ifdef WITH_EMU
|
||
|
|
|
||
|
|
#include "oscam-string.h"
|
||
|
|
#include "module-streamrelay.h"
|
||
|
|
#include "module-emulator-osemu.h"
|
||
|
|
#include "module-emulator-biss.h"
|
||
|
|
#include "module-emulator-cryptoworks.h"
|
||
|
|
#include "module-emulator-director.h"
|
||
|
|
#include "module-emulator-irdeto.h"
|
||
|
|
#include "module-emulator-nagravision.h"
|
||
|
|
#include "module-emulator-omnicrypt.h"
|
||
|
|
#include "module-emulator-powervu.h"
|
||
|
|
#include "module-emulator-viaccess.h"
|
||
|
|
|
||
|
|
// Shared functions
|
||
|
|
|
||
|
|
int8_t is_valid_dcw(uint8_t *dw)
|
||
|
|
{
|
||
|
|
uint8_t i;
|
||
|
|
|
||
|
|
for (i = 0; i < 8; i+= 4)
|
||
|
|
{
|
||
|
|
if (((dw[i] + dw[i + 1] + dw[i + 2]) & 0xFF) != dw[i + 3])
|
||
|
|
{
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
int8_t char_to_bin(uint8_t *out, const char *in, uint32_t inLen)
|
||
|
|
{
|
||
|
|
uint32_t i, tmp;
|
||
|
|
|
||
|
|
for (i = 0; i < inLen / 2; i++)
|
||
|
|
{
|
||
|
|
if (sscanf(in + i * 2, "%02X", &tmp) != 1)
|
||
|
|
{
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
out[i] = (uint8_t)tmp;
|
||
|
|
}
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
void date_to_str(char *dateStr, uint8_t len, int8_t offset, uint8_t format)
|
||
|
|
{
|
||
|
|
// Creates a formatted date string for use in various functions.
|
||
|
|
// A positive or negative time offset (in hours) can be set as well
|
||
|
|
// as the format of the output string.
|
||
|
|
|
||
|
|
time_t rawtime;
|
||
|
|
struct tm timeinfo;
|
||
|
|
|
||
|
|
time(&rawtime);
|
||
|
|
rawtime += (time_t) offset * 60 * 60; // Add a positive or negative offset
|
||
|
|
localtime_r(&rawtime, &timeinfo);
|
||
|
|
|
||
|
|
switch (format)
|
||
|
|
{
|
||
|
|
case 1:
|
||
|
|
strftime(dateStr, len, "%c", &timeinfo);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 2:
|
||
|
|
strftime(dateStr, len, "%F @ %R", &timeinfo);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 3:
|
||
|
|
strftime(dateStr, len, "%y%m%d%H", &timeinfo);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Key DB
|
||
|
|
*
|
||
|
|
* The Emu reader gets keys from the OSCcam-Emu binary and the "SoftCam.Key" file.
|
||
|
|
*
|
||
|
|
* The keys are stored in structures of type "KeyDataContainer", one per CAS. Each
|
||
|
|
* container points to a dynamically allocated array of type "KeyData", which holds
|
||
|
|
* the actual keys. The array initially holds up to 64 keys (64 * KeyData), and it
|
||
|
|
* is expanded by 16 every time it's filled with keys. The "KeyDataContainer" also
|
||
|
|
* includes info about the number of keys it contains ("KeyCount") and the maximum
|
||
|
|
* number of keys it can store ("KeyMax").
|
||
|
|
*
|
||
|
|
* The "KeyData" structure, on the other hand, stores the actual key information,
|
||
|
|
* including the "identifier", "provider", "keyName", "key" and "keyLength". There
|
||
|
|
* is also a "nextKey" pointer to a similar "KeyData" structure which is only used
|
||
|
|
* for Irdeto multiple keys, in a linked list style structure. For all other CAS,
|
||
|
|
* the "nextKey" is a "NULL" pointer.
|
||
|
|
*
|
||
|
|
* For storing keys, the "SetKey" function is used. Duplicate keys are not allowed.
|
||
|
|
* When storing a key that is already present in the database, its "key" value is
|
||
|
|
* updated with the new one. For reading keys from the database, the "FindKey"
|
||
|
|
* function is used. To delete all keys in a container, the "DeleteKeysInContainer"
|
||
|
|
* function can be called.
|
||
|
|
*/
|
||
|
|
|
||
|
|
char *emu_keyfile_path = NULL;
|
||
|
|
|
||
|
|
void emu_set_keyfile_path(const char *path)
|
||
|
|
{
|
||
|
|
uint32_t pathLength;
|
||
|
|
|
||
|
|
if (emu_keyfile_path != NULL)
|
||
|
|
{
|
||
|
|
free(emu_keyfile_path);
|
||
|
|
}
|
||
|
|
|
||
|
|
pathLength = cs_strlen(path);
|
||
|
|
emu_keyfile_path = (char *)malloc(pathLength + 1);
|
||
|
|
if (emu_keyfile_path == NULL)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
cs_strncpy(emu_keyfile_path, path, pathLength + 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
KeyDataContainer CwKeys = { NULL, 0, 0 };
|
||
|
|
KeyDataContainer ViKeys = { NULL, 0, 0 };
|
||
|
|
KeyDataContainer NagraKeys = { NULL, 0, 0 };
|
||
|
|
KeyDataContainer IrdetoKeys = { NULL, 0, 0 };
|
||
|
|
KeyDataContainer BissSWs = { NULL, 0, 0 };
|
||
|
|
KeyDataContainer Biss2Keys = { NULL, 0, 0 };
|
||
|
|
KeyDataContainer OmnicryptKeys = { NULL, 0, 0 };
|
||
|
|
KeyDataContainer PowervuKeys = { NULL, 0, 0 };
|
||
|
|
KeyDataContainer TandbergKeys = { NULL, 0, 0 };
|
||
|
|
KeyDataContainer StreamKeys = { NULL, 0, 0 };
|
||
|
|
|
||
|
|
KeyDataContainer *emu_get_key_container(char identifier)
|
||
|
|
{
|
||
|
|
switch (identifier)
|
||
|
|
{
|
||
|
|
case 'W':
|
||
|
|
return &CwKeys;
|
||
|
|
case 'V':
|
||
|
|
return &ViKeys;
|
||
|
|
case 'N':
|
||
|
|
return &NagraKeys;
|
||
|
|
case 'I':
|
||
|
|
return &IrdetoKeys;
|
||
|
|
case 'F':
|
||
|
|
return &BissSWs;
|
||
|
|
case 'G':
|
||
|
|
return &Biss2Keys;
|
||
|
|
case 'O':
|
||
|
|
return &OmnicryptKeys;
|
||
|
|
case 'P':
|
||
|
|
return &PowervuKeys;
|
||
|
|
case 'T':
|
||
|
|
return &TandbergKeys;
|
||
|
|
case 'A':
|
||
|
|
return &StreamKeys;
|
||
|
|
default:
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void write_key_to_file(char identifier, uint32_t provider, const char *keyName, uint8_t *key,
|
||
|
|
uint32_t keyLength, char *comment)
|
||
|
|
{
|
||
|
|
char line[1200], dateText[100], filename[EMU_KEY_FILENAME_MAX_LEN + 1];
|
||
|
|
char *path, *filepath, *keyValue;
|
||
|
|
uint32_t pathLength;
|
||
|
|
uint8_t fileNameLen = cs_strlen(EMU_KEY_FILENAME);
|
||
|
|
struct dirent *pDirent;
|
||
|
|
DIR *pDir;
|
||
|
|
FILE *file = NULL;
|
||
|
|
|
||
|
|
pathLength = cs_strlen(emu_keyfile_path);
|
||
|
|
path = (char *)malloc(pathLength + 1);
|
||
|
|
if (path == NULL)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
cs_strncpy(path, emu_keyfile_path, pathLength + 1);
|
||
|
|
|
||
|
|
pathLength = cs_strlen(path);
|
||
|
|
if (pathLength >= fileNameLen && strcasecmp(path + pathLength - fileNameLen, EMU_KEY_FILENAME) == 0)
|
||
|
|
{
|
||
|
|
// cut file name
|
||
|
|
path[pathLength - fileNameLen] = '\0';
|
||
|
|
}
|
||
|
|
|
||
|
|
pathLength = cs_strlen(path);
|
||
|
|
if (path[pathLength - 1] == '/' || path[pathLength - 1] == '\\')
|
||
|
|
{
|
||
|
|
// cut trailing /
|
||
|
|
path[pathLength - 1] = '\0';
|
||
|
|
}
|
||
|
|
|
||
|
|
pDir = opendir(path);
|
||
|
|
if (pDir == NULL)
|
||
|
|
{
|
||
|
|
cs_log("Cannot open key file path: %s", path);
|
||
|
|
free(path);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
while ((pDirent = readdir(pDir)) != NULL)
|
||
|
|
{
|
||
|
|
if (strcasecmp(pDirent->d_name, EMU_KEY_FILENAME) == 0)
|
||
|
|
{
|
||
|
|
cs_strncpy(filename, pDirent->d_name, sizeof(filename));
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
closedir(pDir);
|
||
|
|
|
||
|
|
if (pDirent == NULL)
|
||
|
|
{
|
||
|
|
cs_strncpy(filename, EMU_KEY_FILENAME, sizeof(filename));
|
||
|
|
}
|
||
|
|
|
||
|
|
pathLength = cs_strlen(path) + 1 + cs_strlen(filename) + 1;
|
||
|
|
filepath = (char *)malloc(pathLength);
|
||
|
|
if (filepath == NULL)
|
||
|
|
{
|
||
|
|
free(path);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
snprintf(filepath, pathLength, "%s/%s", path, filename);
|
||
|
|
free(path);
|
||
|
|
|
||
|
|
cs_log("Writing key file: %s", filepath);
|
||
|
|
|
||
|
|
file = fopen(filepath, "a");
|
||
|
|
free(filepath);
|
||
|
|
if (file == NULL)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
date_to_str(dateText, sizeof(dateText), 0, 1);
|
||
|
|
|
||
|
|
keyValue = (char *)malloc((keyLength * 2) + 1);
|
||
|
|
if (keyValue == NULL)
|
||
|
|
{
|
||
|
|
fclose(file);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
cs_hexdump(0, key, keyLength, keyValue, (keyLength * 2) + 1);
|
||
|
|
|
||
|
|
if (comment)
|
||
|
|
{
|
||
|
|
snprintf(line, sizeof(line), "\n%c %08X %s %s ; added by Emu %s %s",
|
||
|
|
identifier, provider, keyName, keyValue, dateText, comment);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
snprintf(line, sizeof(line), "\n%c %08X %s %s ; added by Emu %s",
|
||
|
|
identifier, provider, keyName, keyValue, dateText);
|
||
|
|
}
|
||
|
|
|
||
|
|
cs_log("Key written: %c %08X %s %s", identifier, provider, keyName, keyValue);
|
||
|
|
|
||
|
|
free(keyValue);
|
||
|
|
|
||
|
|
fwrite(line, cs_strlen(line), 1, file);
|
||
|
|
fclose(file);
|
||
|
|
}
|
||
|
|
|
||
|
|
int8_t emu_set_key(char identifier, uint32_t provider, char *keyName, uint8_t *orgKey, uint32_t keyLength,
|
||
|
|
uint8_t writeKey, char *comment, struct s_reader *rdr)
|
||
|
|
{
|
||
|
|
uint32_t i, j;
|
||
|
|
uint8_t *tmpKey = NULL;
|
||
|
|
KeyDataContainer *KeyDB;
|
||
|
|
KeyData *tmpKeyData, *newKeyData;
|
||
|
|
|
||
|
|
identifier = (char)toupper((int)identifier);
|
||
|
|
|
||
|
|
KeyDB = emu_get_key_container(identifier);
|
||
|
|
if (KeyDB == NULL)
|
||
|
|
{
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
keyName = strtoupper(keyName);
|
||
|
|
|
||
|
|
if (identifier == 'F') // Prepare BISS keys before saving to the db
|
||
|
|
{
|
||
|
|
// Convert legacy BISS "00" & "01" keynames
|
||
|
|
if (0 == strcmp(keyName, "00") || 0 == strcmp(keyName, "01"))
|
||
|
|
{
|
||
|
|
keyName = "00000000";
|
||
|
|
}
|
||
|
|
|
||
|
|
// All keyNames should have a length of 8 after converting
|
||
|
|
if (cs_strlen(keyName) != 8)
|
||
|
|
{
|
||
|
|
cs_log("WARNING: Wrong key format in %s: F %08X %s", EMU_KEY_FILENAME, provider, keyName);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Verify date-coded keyName (if enabled), ignoring old (expired) keys
|
||
|
|
if (rdr->emu_datecodedenabled)
|
||
|
|
{
|
||
|
|
char timeStr[9];
|
||
|
|
date_to_str(timeStr, sizeof(timeStr), 0, 3);
|
||
|
|
|
||
|
|
// Reject old date-coded keys, but allow our "00000000" evergreen label
|
||
|
|
if (strcmp("00000000", keyName) != 0 && strcmp(timeStr, keyName) >= 0)
|
||
|
|
{
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fix checksum for BISS keys with a length of 6
|
||
|
|
if (identifier == 'F' && keyLength == 6)
|
||
|
|
{
|
||
|
|
tmpKey = (uint8_t *)malloc(8 * sizeof(uint8_t));
|
||
|
|
if(tmpKey == NULL)
|
||
|
|
{
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
tmpKey[0] = orgKey[0];
|
||
|
|
tmpKey[1] = orgKey[1];
|
||
|
|
tmpKey[2] = orgKey[2];
|
||
|
|
tmpKey[3] = ((orgKey[0] + orgKey[1] + orgKey[2]) & 0xFF);
|
||
|
|
tmpKey[4] = orgKey[3];
|
||
|
|
tmpKey[5] = orgKey[4];
|
||
|
|
tmpKey[6] = orgKey[5];
|
||
|
|
tmpKey[7] = ((orgKey[3] + orgKey[4] + orgKey[5]) & 0xFF);
|
||
|
|
|
||
|
|
keyLength = 8;
|
||
|
|
}
|
||
|
|
else // All keys with a length of 8, including BISS
|
||
|
|
{
|
||
|
|
tmpKey = (uint8_t *)malloc(keyLength * sizeof(uint8_t));
|
||
|
|
if (tmpKey == NULL)
|
||
|
|
{
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
memcpy(tmpKey, orgKey, keyLength);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fix patched mgcamd format for Irdeto
|
||
|
|
if (identifier == 'I' && provider < 0xFFFF)
|
||
|
|
{
|
||
|
|
provider = provider << 8;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Key already exists on db, update its value
|
||
|
|
for (i = 0; i < KeyDB->keyCount; i++)
|
||
|
|
{
|
||
|
|
if (KeyDB->EmuKeys[i].provider != provider)
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Don't match keyName (i.e. expiration date) for BISS1 and BISS2 mode 1/E sesssion words
|
||
|
|
if (identifier != 'F' && strcmp(KeyDB->EmuKeys[i].keyName, keyName))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Allow multiple keys for Irdeto
|
||
|
|
if (identifier == 'I')
|
||
|
|
{
|
||
|
|
// Reject duplicates
|
||
|
|
tmpKeyData = &KeyDB->EmuKeys[i];
|
||
|
|
do
|
||
|
|
{
|
||
|
|
if (memcmp(tmpKeyData->key, tmpKey, tmpKeyData->keyLength < keyLength ? tmpKeyData->keyLength : keyLength) == 0)
|
||
|
|
{
|
||
|
|
free(tmpKey);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
tmpKeyData = tmpKeyData->nextKey;
|
||
|
|
}
|
||
|
|
while(tmpKeyData != NULL);
|
||
|
|
|
||
|
|
// Add new key
|
||
|
|
newKeyData = (KeyData *)malloc(sizeof(KeyData));
|
||
|
|
if (newKeyData == NULL)
|
||
|
|
{
|
||
|
|
free(tmpKey);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
newKeyData->identifier = identifier;
|
||
|
|
newKeyData->provider = provider;
|
||
|
|
|
||
|
|
if (cs_strlen(keyName) < EMU_MAX_CHAR_KEYNAME)
|
||
|
|
{
|
||
|
|
cs_strncpy(newKeyData->keyName, keyName, EMU_MAX_CHAR_KEYNAME);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
memcpy(newKeyData->keyName, keyName, EMU_MAX_CHAR_KEYNAME);
|
||
|
|
}
|
||
|
|
|
||
|
|
newKeyData->keyName[EMU_MAX_CHAR_KEYNAME - 1] = 0;
|
||
|
|
newKeyData->key = tmpKey;
|
||
|
|
newKeyData->keyLength = keyLength;
|
||
|
|
newKeyData->nextKey = NULL;
|
||
|
|
|
||
|
|
tmpKeyData = &KeyDB->EmuKeys[i];
|
||
|
|
j = 0;
|
||
|
|
|
||
|
|
while (tmpKeyData->nextKey != NULL)
|
||
|
|
{
|
||
|
|
if (j == 0xFE)
|
||
|
|
{
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
tmpKeyData = tmpKeyData->nextKey;
|
||
|
|
j++;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (tmpKeyData->nextKey)
|
||
|
|
{
|
||
|
|
NULLFREE(tmpKeyData->nextKey->key);
|
||
|
|
NULLFREE(tmpKeyData->nextKey);
|
||
|
|
}
|
||
|
|
tmpKeyData->nextKey = newKeyData;
|
||
|
|
|
||
|
|
if (writeKey)
|
||
|
|
{
|
||
|
|
write_key_to_file(identifier, provider, keyName, tmpKey, keyLength, comment);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else // identifier != 'I'
|
||
|
|
{
|
||
|
|
free(KeyDB->EmuKeys[i].key);
|
||
|
|
KeyDB->EmuKeys[i].key = tmpKey;
|
||
|
|
KeyDB->EmuKeys[i].keyLength = keyLength;
|
||
|
|
|
||
|
|
if (identifier == 'F') // Update keyName (i.e. expiration date) for BISS
|
||
|
|
{
|
||
|
|
cs_strncpy(KeyDB->EmuKeys[i].keyName, keyName, EMU_MAX_CHAR_KEYNAME);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (writeKey)
|
||
|
|
{
|
||
|
|
write_key_to_file(identifier, provider, keyName, tmpKey, keyLength, comment);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Key does not exist on db
|
||
|
|
if (KeyDB->keyCount + 1 > KeyDB->keyMax)
|
||
|
|
{
|
||
|
|
if (KeyDB->EmuKeys == NULL) // db is empty
|
||
|
|
{
|
||
|
|
KeyDB->EmuKeys = (KeyData *)malloc(sizeof(KeyData) * (KeyDB->keyMax + 64));
|
||
|
|
if (KeyDB->EmuKeys == NULL)
|
||
|
|
{
|
||
|
|
free(tmpKey);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
KeyDB->keyMax += 64;
|
||
|
|
}
|
||
|
|
else // db is full, expand it
|
||
|
|
{
|
||
|
|
tmpKeyData = (KeyData *)realloc(KeyDB->EmuKeys, sizeof(KeyData) * (KeyDB->keyMax + 16));
|
||
|
|
if (tmpKeyData == NULL)
|
||
|
|
{
|
||
|
|
free(tmpKey);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
KeyDB->EmuKeys = tmpKeyData;
|
||
|
|
KeyDB->keyMax += 16;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
KeyDB->EmuKeys[KeyDB->keyCount].identifier = identifier;
|
||
|
|
KeyDB->EmuKeys[KeyDB->keyCount].provider = provider;
|
||
|
|
|
||
|
|
if (cs_strlen(keyName) < EMU_MAX_CHAR_KEYNAME)
|
||
|
|
{
|
||
|
|
cs_strncpy(KeyDB->EmuKeys[KeyDB->keyCount].keyName, keyName, EMU_MAX_CHAR_KEYNAME);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
memcpy(KeyDB->EmuKeys[KeyDB->keyCount].keyName, keyName, EMU_MAX_CHAR_KEYNAME);
|
||
|
|
}
|
||
|
|
|
||
|
|
KeyDB->EmuKeys[KeyDB->keyCount].keyName[EMU_MAX_CHAR_KEYNAME - 1] = 0;
|
||
|
|
KeyDB->EmuKeys[KeyDB->keyCount].key = tmpKey;
|
||
|
|
KeyDB->EmuKeys[KeyDB->keyCount].keyLength = keyLength;
|
||
|
|
KeyDB->EmuKeys[KeyDB->keyCount].nextKey = NULL;
|
||
|
|
KeyDB->keyCount++;
|
||
|
|
|
||
|
|
if (writeKey)
|
||
|
|
{
|
||
|
|
write_key_to_file(identifier, provider, keyName, tmpKey, keyLength, comment);
|
||
|
|
}
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
int8_t emu_find_key(char identifier, uint32_t provider, uint32_t providerIgnoreMask, char *keyName,
|
||
|
|
uint8_t *key, uint32_t maxKeyLength, uint8_t isCriticalKey, uint32_t keyRef,
|
||
|
|
uint8_t matchLength, uint32_t *getProvider)
|
||
|
|
{
|
||
|
|
uint32_t i;
|
||
|
|
uint16_t j;
|
||
|
|
uint8_t provider_matching_key_count = 0;
|
||
|
|
KeyDataContainer *KeyDB;
|
||
|
|
KeyData *tmpKeyData;
|
||
|
|
|
||
|
|
KeyDB = emu_get_key_container(identifier);
|
||
|
|
if (KeyDB == NULL)
|
||
|
|
{
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (i = 0; i < KeyDB->keyCount; i++)
|
||
|
|
{
|
||
|
|
if ((KeyDB->EmuKeys[i].provider & ~providerIgnoreMask) != provider)
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Don't match keyName (i.e. expiration date) for BISS
|
||
|
|
if (identifier != 'F' && strcmp(KeyDB->EmuKeys[i].keyName, keyName))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// "matchLength" cannot be used when multiple keys are allowed
|
||
|
|
// for a single provider/keyName combination.
|
||
|
|
// Currently this is the case only for Irdeto keys.
|
||
|
|
if (matchLength && KeyDB->EmuKeys[i].keyLength != maxKeyLength)
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (providerIgnoreMask)
|
||
|
|
{
|
||
|
|
if (provider_matching_key_count < keyRef)
|
||
|
|
{
|
||
|
|
provider_matching_key_count++;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
keyRef = 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
tmpKeyData = &KeyDB->EmuKeys[i];
|
||
|
|
|
||
|
|
j = 0;
|
||
|
|
while (j < keyRef && tmpKeyData->nextKey != NULL)
|
||
|
|
{
|
||
|
|
j++;
|
||
|
|
tmpKeyData = tmpKeyData->nextKey;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (j == keyRef)
|
||
|
|
{
|
||
|
|
memcpy(key, tmpKeyData->key, tmpKeyData->keyLength > maxKeyLength ? maxKeyLength : tmpKeyData->keyLength);
|
||
|
|
if (tmpKeyData->keyLength < maxKeyLength)
|
||
|
|
{
|
||
|
|
memset(key + tmpKeyData->keyLength, 0, maxKeyLength - tmpKeyData->keyLength);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Report the keyName (i.e. expiration date) of the session word found
|
||
|
|
if (identifier == 'F')
|
||
|
|
{
|
||
|
|
cs_strncpy(keyName, tmpKeyData->keyName, EMU_MAX_CHAR_KEYNAME);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (getProvider != NULL)
|
||
|
|
{
|
||
|
|
(*getProvider) = tmpKeyData->provider;
|
||
|
|
}
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (isCriticalKey)
|
||
|
|
{
|
||
|
|
cs_log("Key not found: %c %X %s", identifier, provider, keyName);
|
||
|
|
}
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
int8_t emu_update_key(char identifier, uint32_t provider, char *keyName, uint8_t *key,
|
||
|
|
uint32_t keyLength, uint8_t writeKey, char *comment)
|
||
|
|
{
|
||
|
|
uint32_t keyRef = 0;
|
||
|
|
uint8_t *tmpKey = (uint8_t *)malloc(sizeof(uint8_t) * keyLength);
|
||
|
|
|
||
|
|
if (tmpKey == NULL)
|
||
|
|
{
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
while (emu_find_key(identifier, provider, 0, keyName, tmpKey, keyLength, 0, keyRef, 0, NULL))
|
||
|
|
{
|
||
|
|
if (memcmp(tmpKey, key, keyLength) == 0)
|
||
|
|
{
|
||
|
|
free(tmpKey);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
keyRef++;
|
||
|
|
}
|
||
|
|
|
||
|
|
free(tmpKey);
|
||
|
|
return emu_set_key(identifier, provider, keyName, key, keyLength, writeKey, comment, NULL);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int32_t delete_keys_in_container(char identifier)
|
||
|
|
{
|
||
|
|
// Deletes all keys stored in memory for the specified identifier,
|
||
|
|
// but keeps the container itself, re-initialized at { NULL, 0, 0 }.
|
||
|
|
// Returns the count of deleted keys.
|
||
|
|
|
||
|
|
uint32_t oldKeyCount, i;
|
||
|
|
KeyData *tmpKeyData;
|
||
|
|
KeyDataContainer *KeyDB = emu_get_key_container(identifier);
|
||
|
|
|
||
|
|
if (KeyDB == NULL || KeyDB->EmuKeys == NULL || KeyDB->keyCount == 0)
|
||
|
|
{
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (i = 0; i < KeyDB->keyCount; i++)
|
||
|
|
{
|
||
|
|
// For Irdeto multiple keys only (linked list structure)
|
||
|
|
while (KeyDB->EmuKeys[i].nextKey != NULL)
|
||
|
|
{
|
||
|
|
tmpKeyData = KeyDB->EmuKeys[i].nextKey;
|
||
|
|
KeyDB->EmuKeys[i].nextKey = KeyDB->EmuKeys[i].nextKey->nextKey;
|
||
|
|
free(tmpKeyData->key); // Free key
|
||
|
|
free(tmpKeyData); // Free KeyData
|
||
|
|
}
|
||
|
|
|
||
|
|
// For single keys (all identifiers, including Irdeto)
|
||
|
|
free(KeyDB->EmuKeys[i].key); // Free key
|
||
|
|
}
|
||
|
|
|
||
|
|
// Free the KeyData array
|
||
|
|
NULLFREE(KeyDB->EmuKeys);
|
||
|
|
oldKeyCount = KeyDB->keyCount;
|
||
|
|
KeyDB->keyCount = 0;
|
||
|
|
KeyDB->keyMax = 0;
|
||
|
|
|
||
|
|
return oldKeyCount;
|
||
|
|
}
|
||
|
|
|
||
|
|
void emu_clear_keydata(void)
|
||
|
|
{
|
||
|
|
uint32_t total = 0;
|
||
|
|
|
||
|
|
total = CwKeys.keyCount;
|
||
|
|
total += ViKeys.keyCount;
|
||
|
|
total += NagraKeys.keyCount;
|
||
|
|
total += IrdetoKeys.keyCount;
|
||
|
|
total += BissSWs.keyCount;
|
||
|
|
total += Biss2Keys.keyCount;
|
||
|
|
total += OmnicryptKeys.keyCount;
|
||
|
|
total += PowervuKeys.keyCount;
|
||
|
|
total += TandbergKeys.keyCount;
|
||
|
|
total += StreamKeys.keyCount;
|
||
|
|
|
||
|
|
if (total != 0)
|
||
|
|
{
|
||
|
|
cs_log("Freeing keys in memory: W:%d V:%d N:%d I:%d F:%d G:%d O:%d P:%d T:%d A:%d",
|
||
|
|
CwKeys.keyCount, ViKeys.keyCount, NagraKeys.keyCount, IrdetoKeys.keyCount, BissSWs.keyCount,
|
||
|
|
Biss2Keys.keyCount, OmnicryptKeys.keyCount, PowervuKeys.keyCount, TandbergKeys.keyCount,
|
||
|
|
StreamKeys.keyCount);
|
||
|
|
|
||
|
|
delete_keys_in_container('W');
|
||
|
|
delete_keys_in_container('V');
|
||
|
|
delete_keys_in_container('N');
|
||
|
|
delete_keys_in_container('I');
|
||
|
|
delete_keys_in_container('F');
|
||
|
|
delete_keys_in_container('G');
|
||
|
|
delete_keys_in_container('O');
|
||
|
|
delete_keys_in_container('P');
|
||
|
|
delete_keys_in_container('T');
|
||
|
|
delete_keys_in_container('A');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
uint8_t emu_read_keyfile(struct s_reader *rdr, const char *opath)
|
||
|
|
{
|
||
|
|
char line[1200], keyName[EMU_MAX_CHAR_KEYNAME], keyString[1026], identifier;
|
||
|
|
char *path, *filepath, filename[EMU_KEY_FILENAME_MAX_LEN + 1];
|
||
|
|
uint32_t pathLength, provider, keyLength;
|
||
|
|
uint8_t fileNameLen = cs_strlen(EMU_KEY_FILENAME);
|
||
|
|
uint8_t *key;
|
||
|
|
struct dirent *pDirent;
|
||
|
|
DIR *pDir;
|
||
|
|
FILE *file = NULL;
|
||
|
|
|
||
|
|
pathLength = cs_strlen(opath);
|
||
|
|
path = (char *)malloc(pathLength + 1);
|
||
|
|
if (path == NULL)
|
||
|
|
{
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
cs_strncpy(path, opath, pathLength + 1);
|
||
|
|
|
||
|
|
pathLength = cs_strlen(path);
|
||
|
|
if (pathLength >= fileNameLen && strcasecmp(path + pathLength - fileNameLen, EMU_KEY_FILENAME) == 0)
|
||
|
|
{
|
||
|
|
// cut file name
|
||
|
|
path[pathLength - fileNameLen] = '\0';
|
||
|
|
}
|
||
|
|
|
||
|
|
pathLength = cs_strlen(path);
|
||
|
|
if (path[pathLength - 1] == '/' || path[pathLength - 1] == '\\')
|
||
|
|
{
|
||
|
|
// cut trailing /
|
||
|
|
path[pathLength - 1] = '\0';
|
||
|
|
}
|
||
|
|
|
||
|
|
pDir = opendir(path);
|
||
|
|
if (pDir == NULL)
|
||
|
|
{
|
||
|
|
cs_log("Cannot open key file path: %s", path);
|
||
|
|
free(path);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
while ((pDirent = readdir(pDir)) != NULL)
|
||
|
|
{
|
||
|
|
if (strcasecmp(pDirent->d_name, EMU_KEY_FILENAME) == 0)
|
||
|
|
{
|
||
|
|
cs_strncpy(filename, pDirent->d_name, sizeof(filename));
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
closedir(pDir);
|
||
|
|
|
||
|
|
if (pDirent == NULL)
|
||
|
|
{
|
||
|
|
cs_log("Key file not found in: %s", path);
|
||
|
|
free(path);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
pathLength = cs_strlen(path) + 1 + cs_strlen(filename) + 1;
|
||
|
|
filepath = (char *)malloc(pathLength);
|
||
|
|
if (filepath == NULL)
|
||
|
|
{
|
||
|
|
free(path);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
snprintf(filepath, pathLength, "%s/%s", path, filename);
|
||
|
|
free(path);
|
||
|
|
|
||
|
|
cs_log("Reading key file: %s", filepath);
|
||
|
|
|
||
|
|
file = fopen(filepath, "r");
|
||
|
|
free(filepath);
|
||
|
|
if (file == NULL)
|
||
|
|
{
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
emu_set_keyfile_path(opath);
|
||
|
|
|
||
|
|
while (fgets(line, 1200, file))
|
||
|
|
{
|
||
|
|
if (sscanf(line, "%c %8x %11s %1024s", &identifier, &provider, keyName, keyString) != 4)
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
keyLength = cs_strlen(keyString) / 2;
|
||
|
|
key = (uint8_t *)malloc(keyLength);
|
||
|
|
if (key == NULL)
|
||
|
|
{
|
||
|
|
fclose(file);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (char_to_bin(key, keyString, cs_strlen(keyString))) // Conversion OK
|
||
|
|
{
|
||
|
|
emu_set_key(identifier, provider, keyName, key, keyLength, 0, NULL, rdr);
|
||
|
|
}
|
||
|
|
else // Non-hex characters in keyString
|
||
|
|
{
|
||
|
|
if ((identifier != ';' && identifier != '#' && // Skip warning for comments, etc.
|
||
|
|
identifier != '=' && identifier != '-' &&
|
||
|
|
identifier != ' ') &&
|
||
|
|
!(identifier == 'F' && 0 == strncmp(keyString, "XXXXXXXXXXXX", 12))) // Skip warning for BISS 'Example key' lines
|
||
|
|
{
|
||
|
|
// Alert user regarding faulty line
|
||
|
|
cs_log("WARNING: non-hex value in %s at %c %08X %s %s",
|
||
|
|
EMU_KEY_FILENAME, identifier, provider, keyName, keyString);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
free(key);
|
||
|
|
}
|
||
|
|
fclose(file);
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
#if defined(WITH_SOFTCAM) && !defined(__APPLE__) && !defined(__ANDROID__)
|
||
|
|
extern uint8_t SoftCamKey_Data[] __asm__("_binary_SoftCam_Key_start");
|
||
|
|
extern uint8_t SoftCamKey_DataEnd[] __asm__("_binary_SoftCam_Key_end");
|
||
|
|
|
||
|
|
void emu_read_keymemory(struct s_reader *rdr)
|
||
|
|
{
|
||
|
|
char *keyData, *line, *saveptr, keyName[EMU_MAX_CHAR_KEYNAME], keyString[1026], identifier;
|
||
|
|
uint32_t provider, keyLength;
|
||
|
|
uint8_t *key;
|
||
|
|
|
||
|
|
keyData = (char *)malloc(SoftCamKey_DataEnd - SoftCamKey_Data + 1);
|
||
|
|
if (keyData == NULL)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
memcpy(keyData, SoftCamKey_Data, SoftCamKey_DataEnd - SoftCamKey_Data);
|
||
|
|
keyData[SoftCamKey_DataEnd-SoftCamKey_Data] = 0x00;
|
||
|
|
|
||
|
|
line = strtok_r(keyData, "\n", &saveptr);
|
||
|
|
while (line != NULL)
|
||
|
|
{
|
||
|
|
if (sscanf(line, "%c %8x %11s %1024s", &identifier, &provider, keyName, keyString) != 4)
|
||
|
|
{
|
||
|
|
line = strtok_r(NULL, "\n", &saveptr);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
keyLength = cs_strlen(keyString) / 2;
|
||
|
|
|
||
|
|
key = (uint8_t *)malloc(keyLength);
|
||
|
|
if (key == NULL)
|
||
|
|
{
|
||
|
|
free(keyData);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (char_to_bin(key, keyString, cs_strlen(keyString))) // Conversion OK
|
||
|
|
{
|
||
|
|
emu_set_key(identifier, provider, keyName, key, keyLength, 0, NULL, rdr);
|
||
|
|
}
|
||
|
|
else // Non-hex characters in keyString
|
||
|
|
{
|
||
|
|
if ((identifier != ';' && identifier != '#' && // Skip warning for comments, etc.
|
||
|
|
identifier != '=' && identifier != '-' &&
|
||
|
|
identifier != ' ') &&
|
||
|
|
!(identifier == 'F' && 0 == strncmp(keyString, "XXXXXXXXXXXX", 12))) // Skip warning for BISS 'Example key' lines
|
||
|
|
{
|
||
|
|
// Alert user regarding faulty line
|
||
|
|
cs_log("WARNING: non-hex value in internal keyfile at %c %08X %s %s",
|
||
|
|
identifier, provider, keyName, keyString);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
free(key);
|
||
|
|
line = strtok_r(NULL, "\n", &saveptr);
|
||
|
|
}
|
||
|
|
free(keyData);
|
||
|
|
}
|
||
|
|
#else
|
||
|
|
void emu_read_keymemory(struct s_reader *UNUSED(rdr)) { }
|
||
|
|
#endif
|
||
|
|
|
||
|
|
static const char *get_error_reason(int8_t result)
|
||
|
|
{
|
||
|
|
switch (result)
|
||
|
|
{
|
||
|
|
case EMU_OK:
|
||
|
|
return "No error";
|
||
|
|
|
||
|
|
case EMU_NOT_SUPPORTED:
|
||
|
|
return "Not supported";
|
||
|
|
|
||
|
|
case EMU_KEY_NOT_FOUND:
|
||
|
|
return "Key not found";
|
||
|
|
|
||
|
|
case EMU_KEY_REJECTED:
|
||
|
|
return "ECM key rejected";
|
||
|
|
|
||
|
|
case EMU_CORRUPT_DATA:
|
||
|
|
return "Corrupt data";
|
||
|
|
|
||
|
|
case EMU_CW_NOT_FOUND:
|
||
|
|
return "CW not found";
|
||
|
|
|
||
|
|
case EMU_CHECKSUM_ERROR:
|
||
|
|
return "Checksum error";
|
||
|
|
|
||
|
|
case EMU_OUT_OF_MEMORY:
|
||
|
|
return "Out of memory";
|
||
|
|
|
||
|
|
default:
|
||
|
|
return "Unknown reason";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
int8_t emu_process_ecm(struct s_reader *rdr, const ECM_REQUEST *er, uint8_t *cw, EXTENDED_CW *cw_ex)
|
||
|
|
{
|
||
|
|
if (er->ecmlen < 3)
|
||
|
|
{
|
||
|
|
cs_log_dbg(D_TRACE, "Received ecm data of zero length!");
|
||
|
|
return 4;
|
||
|
|
}
|
||
|
|
|
||
|
|
uint16_t ecmLen = SCT_LEN(er->ecm);
|
||
|
|
uint8_t ecmCopy[ecmLen];
|
||
|
|
int8_t result = 1;
|
||
|
|
|
||
|
|
if (ecmLen != er->ecmlen)
|
||
|
|
{
|
||
|
|
cs_log_dbg(D_TRACE, "Actual ecm data length 0x%03X but ecm section length is 0x%03X",
|
||
|
|
er->ecmlen, ecmLen);
|
||
|
|
return 4;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (ecmLen > EMU_MAX_ECM_LEN)
|
||
|
|
{
|
||
|
|
cs_log_dbg(D_TRACE, "Actual ecm data length 0x%03X but maximum supported ecm length is 0x%03X",
|
||
|
|
er->ecmlen, EMU_MAX_ECM_LEN);
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
memcpy(ecmCopy, er->ecm, ecmLen);
|
||
|
|
|
||
|
|
if (caid_is_viaccess(er->caid)) result = viaccess_ecm(ecmCopy, cw);
|
||
|
|
else if (caid_is_irdeto(er->caid)) result = irdeto2_ecm(er->caid, ecmCopy, cw);
|
||
|
|
else if (caid_is_cryptoworks(er->caid)) result = cryptoworks_ecm(er->caid, ecmCopy, cw);
|
||
|
|
else if (caid_is_powervu(er->caid))
|
||
|
|
{
|
||
|
|
#ifdef MODULE_STREAMRELAY
|
||
|
|
result = powervu_ecm(ecmCopy, cw, cw_ex, er->srvid, er->caid, er->tsid, er->onid, er->ens, NULL);
|
||
|
|
#else
|
||
|
|
result = powervu_ecm(ecmCopy, cw, cw_ex, er->srvid, er->caid, er->tsid, er->onid, er->ens);
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
else if (caid_is_director(er->caid)) result = director_ecm(ecmCopy, cw);
|
||
|
|
else if (caid_is_nagra(er->caid)) result = nagra2_ecm(ecmCopy, cw);
|
||
|
|
else if (caid_is_biss(er->caid)) result = biss_ecm(rdr, er->ecm, er->caid, er->pid, cw, cw_ex);
|
||
|
|
else if (er->caid == 0x00FF) result = omnicrypt_ecm(ecmCopy, cw); // temp caid
|
||
|
|
|
||
|
|
if (result != 0)
|
||
|
|
{
|
||
|
|
cs_log("ECM failed: %s", get_error_reason(result));
|
||
|
|
}
|
||
|
|
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
int8_t emu_process_emm(struct s_reader *rdr, uint16_t caid, const uint8_t *emm, uint32_t *keysAdded)
|
||
|
|
{
|
||
|
|
uint16_t emmLen = SCT_LEN(emm);
|
||
|
|
uint8_t emmCopy[emmLen];
|
||
|
|
int8_t result = 1;
|
||
|
|
|
||
|
|
if (emmLen > EMU_MAX_EMM_LEN)
|
||
|
|
{
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
memcpy(emmCopy, emm, emmLen);
|
||
|
|
*keysAdded = 0;
|
||
|
|
|
||
|
|
if (caid_is_viaccess(caid)) result = viaccess_emm(emmCopy, keysAdded);
|
||
|
|
else if (caid_is_irdeto(caid)) result = irdeto2_emm(caid, emmCopy, keysAdded);
|
||
|
|
else if (caid_is_powervu(caid)) result = powervu_emm(emmCopy, keysAdded);
|
||
|
|
else if (caid_is_director(caid)) result = director_emm(emmCopy, keysAdded);
|
||
|
|
else if (caid_is_biss_dynamic(caid)) result = biss_emm(rdr, emmCopy, keysAdded);
|
||
|
|
|
||
|
|
if (result != 0)
|
||
|
|
{
|
||
|
|
cs_log_dbg(D_EMM,"EMM failed: %s", get_error_reason(result));
|
||
|
|
}
|
||
|
|
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
#endif // WITH_EMU
|