884 lines
25 KiB
C
884 lines
25 KiB
C
#define MODULE_LOG_PREFIX "emu"
|
|
|
|
#include "globals.h"
|
|
|
|
#ifdef WITH_EMU
|
|
|
|
#include "module-emulator-osemu.h"
|
|
#include "module-emulator-biss.h"
|
|
#include "oscam-aes.h"
|
|
#include "oscam-string.h"
|
|
#include <openssl/evp.h>
|
|
#include <openssl/pem.h>
|
|
//#include <openssl/rsa.h>
|
|
#include <openssl/x509.h>
|
|
|
|
// DVB-CISSA v1 IV as defined in ETSI TS 103 127
|
|
static const uint8_t dvb_cissa_iv[16] =
|
|
{
|
|
0x44, 0x56, 0x42, 0x54, 0x4D, 0x43, 0x50, 0x54,
|
|
0x41, 0x45, 0x53, 0x43, 0x49, 0x53, 0x53, 0x41
|
|
};
|
|
|
|
static void unify_orbitals(uint32_t *namespace)
|
|
{
|
|
// Unify orbitals to produce same namespace among users
|
|
// Set positions according to http://satellites-xml.org
|
|
|
|
uint16_t pos = (*namespace & 0x0FFF0000) >> 16;
|
|
|
|
switch (pos)
|
|
{
|
|
case 29: // Rascom QAF 1R
|
|
case 31: // Eutelsat 3B
|
|
{
|
|
pos = 30;
|
|
break;
|
|
}
|
|
|
|
case 49:
|
|
case 50: // SES 5
|
|
{
|
|
pos = 48; // Astra 4A
|
|
break;
|
|
}
|
|
|
|
case 215:
|
|
{
|
|
pos = 216; // Eutelsat 21B
|
|
break;
|
|
}
|
|
|
|
case 285: // Astra 2E
|
|
{
|
|
pos = 282; // Astra 2F/2G
|
|
break;
|
|
}
|
|
|
|
case 328: // Intelsat 28
|
|
case 329:
|
|
case 331: // Eutelsat 33C
|
|
{
|
|
pos = 330;
|
|
break;
|
|
}
|
|
|
|
case 359: // Eutelsat 36B
|
|
case 361: // Express AMU1
|
|
{
|
|
pos = 360;
|
|
break;
|
|
}
|
|
|
|
case 451: // Intelsat 904
|
|
{
|
|
pos = 450; // Intelsat 12
|
|
break;
|
|
}
|
|
|
|
case 550:
|
|
case 551: // G-Sat 8/16
|
|
{
|
|
pos = 549; // Yamal 402
|
|
break;
|
|
}
|
|
|
|
case 748:
|
|
case 749: // ABS 2A
|
|
{
|
|
pos = 750;
|
|
break;
|
|
}
|
|
|
|
case 848: // Horizons 2
|
|
case 852: // Intelsat 15
|
|
{
|
|
pos = 850;
|
|
break;
|
|
}
|
|
|
|
case 914: // Mesasat 3a
|
|
{
|
|
pos = 915; // Mesasat 3/3b
|
|
break;
|
|
}
|
|
|
|
case 934: // G-Sat 17
|
|
case 936: // Insat 4B
|
|
{
|
|
pos = 935; // G-Sat 15
|
|
break;
|
|
}
|
|
|
|
case 3600 - 911: // Nimiq 6
|
|
{
|
|
pos = 3600 - 910; // Galaxy 17
|
|
break;
|
|
}
|
|
|
|
case 3600 - 870: // SES 2
|
|
case 3600 - 872: // TKSat 1
|
|
{
|
|
pos = 3600 - 871;
|
|
break;
|
|
}
|
|
|
|
case 3600 - 432: // Sky Brasil 1
|
|
case 3600 - 430: // Intelsat 11
|
|
{
|
|
pos = 3600 - 431;
|
|
break;
|
|
}
|
|
|
|
case 3600 - 376: // Telstar 11N
|
|
case 3600 - 374: // NSS 10
|
|
{
|
|
pos = 3600 - 375;
|
|
break;
|
|
}
|
|
|
|
case 3600 - 359: // Hispasat 36W-1
|
|
{
|
|
pos = 3600 - 360; // Eutelsat 36 West A
|
|
break;
|
|
}
|
|
|
|
case 3600 - 81: // Eutelsat 8 West B
|
|
{
|
|
pos = 3600 - 80;
|
|
break;
|
|
}
|
|
|
|
case 3600 - 73: // Eutelsat 7 West A
|
|
case 3600 - 72:
|
|
case 3600 - 71:
|
|
{
|
|
pos = 3600 - 70; // Nilesat 201
|
|
break;
|
|
}
|
|
|
|
case 3600 - 10: // Intelsat 10-02
|
|
case 3600 - 9: // Thor 6
|
|
case 3600 - 7: // Thor 7
|
|
case 3600 - 6: // Thor 7
|
|
{
|
|
pos = 3600 - 8; // Thor 5
|
|
break;
|
|
}
|
|
}
|
|
|
|
*namespace = (*namespace & 0xF000FFFF) | (pos << 16);
|
|
}
|
|
|
|
static void annotate(char *buf, uint8_t len, const uint8_t *ecm, uint16_t ecmLen,
|
|
uint32_t hash, int8_t isNamespaceHash, int8_t datecoded)
|
|
{
|
|
// Extract useful information to append to the "Example key ..." message.
|
|
//
|
|
// For feeds, the orbital position & frequency are usually embedded in the namespace.
|
|
// See https://github.com/OpenPLi/enigma2/blob/develop/lib/dvb/frontend.cpp#L476
|
|
// hash = (sat.orbital_position << 16);
|
|
// hash |= ((sat.frequency/1000)&0xFFFF)|((sat.polarisation&1) << 15);
|
|
//
|
|
// If the onid & tsid appear to be a unique DVB identifier, enigma2 strips the frequency
|
|
// from our namespace. See https://github.com/OpenPLi/enigma2/blob/develop/lib/dvb/scan.cpp#L55
|
|
// In that case, our annotation contains the onid:tsid:sid triplet in lieu of frequency.
|
|
//
|
|
// For the universal case, we print the number of elementary stream pids & pmtpid.
|
|
// The sid and current time are included for all. Examples:
|
|
//
|
|
// F 1A2B3C4D 00000000 XXXXXXXXXXXXXXXX ; 110.5W 12345H sid:0001 added: 2017-10-17 @ 13:14:15 // namespace
|
|
// F 1A2B3C4D 20180123 XXXXXXXXXXXXXXXX ; 33.5E ABCD:9876:1234 added: 2017-10-17 @ 13:14:15 // stripped namespace
|
|
// F 1A2B3C4D 20180123 XXXXXXXXXXXXXXXX ; av:5 pmt:0134 sid:0001 added: 2017-10-17 @ 13:14:15 // universal
|
|
|
|
uint8_t pidcount;
|
|
uint16_t frequency, degrees, pmtpid, srvid, tsid, onid;
|
|
uint32_t ens;
|
|
char compass, polarisation, timeStr1[9], timeStr2[19];
|
|
|
|
if (datecoded)
|
|
{
|
|
date_to_str(timeStr1, sizeof(timeStr1), 4, 3);
|
|
}
|
|
else
|
|
{
|
|
snprintf(timeStr1, sizeof(timeStr1), "00000000");
|
|
}
|
|
|
|
date_to_str(timeStr2, sizeof(timeStr2), 0, 2);
|
|
|
|
if (isNamespaceHash) // Namespace hash
|
|
{
|
|
ens = b2i(4, ecm + ecmLen - 4); // Namespace will be the last 4 bytes of the ecm
|
|
degrees = (ens >> 16) & 0x0FFF; // Remove not-a-pid flag
|
|
|
|
if (degrees > 1800)
|
|
{
|
|
degrees = 3600 - degrees;
|
|
compass = 'W';
|
|
}
|
|
else
|
|
{
|
|
compass = 'E';
|
|
}
|
|
|
|
if (0 == (ens & 0xFFFF)) // Stripped namespace hash
|
|
{
|
|
srvid = b2i(2, ecm + 3);
|
|
tsid = b2i(2, ecm + ecmLen - 8);
|
|
onid = b2i(2, ecm + ecmLen - 6);
|
|
// Printing degree sign "\u00B0" requires c99 standard
|
|
snprintf(buf, len, "F %08X %s XXXXXXXXXXXXXXXX ; %5.1f%c %04X:%04X:%04X added: %s",
|
|
hash, timeStr1, degrees / 10.0, compass, onid, tsid, srvid, timeStr2);
|
|
}
|
|
else // Full namespace hash
|
|
{
|
|
srvid = b2i(2, ecm + 3);
|
|
frequency = ens & 0x7FFF; // Remove polarity bit
|
|
polarisation = ens & 0x8000 ? 'V' : 'H';
|
|
// Printing degree sign "\u00B0" requires c99 standard
|
|
snprintf(buf, len, "F %08X %s XXXXXXXXXXXXXXXX ; %5.1f%c %5d%c sid:%04X added: %s",
|
|
hash, timeStr1, degrees / 10.0, compass, frequency, polarisation, srvid, timeStr2);
|
|
}
|
|
}
|
|
else // Universal hash
|
|
{
|
|
srvid = b2i(2, ecm + 3);
|
|
pmtpid = b2i(2, ecm + 5);
|
|
pidcount = (ecmLen - 15) / 2; // video + audio pids count
|
|
snprintf(buf, len, "F %08X %s XXXXXXXXXXXXXXXX ; av:%d pmt:%04X sid:%04X added: %s",
|
|
hash, timeStr1, pidcount, pmtpid, srvid, timeStr2);
|
|
}
|
|
}
|
|
|
|
static int8_t is_common_hash(uint32_t hash)
|
|
{
|
|
// Check universal hash against a number of commnon universal
|
|
// hashes in order to warn users about potential key clashes
|
|
|
|
switch (hash)
|
|
{
|
|
case 0xBAFCD9FD: // 0001 0020 0200 1010 1020 (most common hash)
|
|
return 1;
|
|
case 0xA6A4FBD4: // 0001 0800 0200 1010 1020
|
|
return 1;
|
|
case 0xEFAB7A4D: // 0001 0800 1010 1020 0200
|
|
return 1;
|
|
case 0x83FA15D1: // 0001 0020 0134 0100 0101
|
|
return 1;
|
|
case 0x58934C38: // 0001 0800 1010 1020 1030 0200
|
|
return 1;
|
|
case 0x2C3CEC17: // 0001 0020 0134 0100
|
|
return 1;
|
|
case 0x73DF7F7E: // 0001 0020 0200 1010 1020 1030
|
|
return 1;
|
|
case 0xAFA85BC8: // 0001 0020 0021 0022 0023
|
|
return 1;
|
|
case 0x8C51F31D: // 0001 0800 0200 1010 1020 1030 1040
|
|
return 1;
|
|
case 0xE2F9BD29: // 0001 0800 0200 1010 1020 1030
|
|
return 1;
|
|
case 0xB9EBE0FF: // 0001 0100 0200 1010 1020 (less common hash)
|
|
return 1;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int8_t is_valid_namespace(uint32_t namespace)
|
|
{
|
|
// Note to developers:
|
|
// If we ever have a satellite at 0.0E, edit to allow stripped namespace
|
|
// '0xA0000000' with an additional test on tsid and onid being != 0
|
|
|
|
uint16_t orbital, frequency;
|
|
|
|
orbital = (namespace >> 16) & 0x0FFF;
|
|
frequency = namespace & 0x7FFF;
|
|
|
|
if ((namespace & 0xF0000000) != 0xA0000000) return 0; // Value isn't flagged as namespace
|
|
if ((namespace & 0x0FFFFFFF) == 0x00000000) return 0; // Empty namespace
|
|
if (orbital > 3599) return 0; // Allow only DVB-S
|
|
if (frequency == 0) return 1; // Stripped namespace
|
|
if (frequency >= 3400 && frequency <= 4200) return 1; // Super extended C band
|
|
if (frequency >= 10700 && frequency <= 12750) return 1; // Ku band Europe
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int8_t get_sw(uint32_t provider, uint8_t *sw, uint8_t sw_length, int8_t dateCoded, int8_t printMsg)
|
|
{
|
|
// If date-coded keys are enabled in the webif, this function evaluates the expiration date
|
|
// of the found keys. Expired keys are not sent to the calling function. If date-coded keys
|
|
// are disabled, then every key is sent without any evaluation. It takes the "provider" as
|
|
// input and outputs the "sw". Returns 0 (key not found, or expired) or 1 (key found).
|
|
|
|
// printMsg: 0 => No message
|
|
// printMsg: 1 => Print message only if key is found
|
|
// printMsg: 2 => Always print message, regardless if key is found or not
|
|
|
|
char keyExpDate[9] = "00000000";
|
|
|
|
if (emu_find_key('F', provider, 0, keyExpDate, sw, sw_length, 0, 0, 0, NULL)) // Key found
|
|
{
|
|
if (dateCoded) // Date-coded keys are enabled, evaluate expiration date
|
|
{
|
|
char currentDate[9];
|
|
date_to_str(currentDate, sizeof(currentDate), 0, 3);
|
|
|
|
if (strncmp("00000000", keyExpDate, 9) == 0 || strncmp(currentDate, keyExpDate, 9) < 0) // Evergreen or not expired
|
|
{
|
|
if (printMsg == 1 || printMsg == 2) cs_log("Key found: F %08X %s", provider, keyExpDate);
|
|
return 1;
|
|
}
|
|
else // Key expired
|
|
{
|
|
sw = NULL; // Make sure we don't send any expired key
|
|
if (printMsg == 2) cs_log("Key expired: F %08X %s", provider, keyExpDate);
|
|
return 0;
|
|
}
|
|
}
|
|
else // Date-coded keys are disabled, don't evaluate expiration date
|
|
{
|
|
if (printMsg == 1 || printMsg == 2) cs_log("Key found: F %08X %s", provider, keyExpDate);
|
|
return 1;
|
|
}
|
|
}
|
|
else // Key not found
|
|
{
|
|
if (printMsg == 2) cs_log("Key not found: F %08X", provider);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int8_t biss_mode1_ecm(struct s_reader *rdr, const uint8_t *ecm, uint16_t caid, uint16_t ecm_pid, uint8_t *dw, EXTENDED_CW *cw_ex)
|
|
{
|
|
// Oscam's fake ecm consists of [sid] [pmtpid] [pid1] [pid2] ... [pidx] [tsid] [onid] [ens]
|
|
// On enigma boxes tsid, onid and namespace should be non zero, while on non-enigma
|
|
// boxes they are usually all zero. The top 4 bits of the namespace are flagged with 0xA.
|
|
|
|
// The emulator creates a unique channel hash using srvid and enigma namespace or
|
|
// srvid, tsid, onid and namespace (in case of namespace without frequency) and
|
|
// another weaker (not unique) hash based on every pid of the channel. This universal
|
|
// hash should be available on all types of stbs (enigma and non-enigma).
|
|
|
|
// Key searches are made from highest priority (tightest test first) to lowest priority
|
|
// (loosest test last):
|
|
// 1. Namespace hash (only on enigma boxes)
|
|
// 2. Universal hash (all box types with emu r752+)
|
|
// 3. Valid tsid, onid combination
|
|
// 4. Reverse order pid (audio, video, pmt)
|
|
// 5. Legacy srvid, ecm pid combination
|
|
// 6. Default "All Feeds" key
|
|
|
|
// If enabled in the webif, a date based key search is performed. If the expiration
|
|
// date has passed, the key is not sent back from get_sw(). This option is used only
|
|
// in the namespace hash, universal hash and the "All Feeds" search methods.
|
|
|
|
uint32_t i, ens = 0, hash = 0;
|
|
uint16_t srvid, tsid = 0, onid = 0, pid, ecm_len = SCT_LEN(ecm);
|
|
uint8_t *sw, sw_length, ecm_copy[ecm_len];
|
|
char tmp_buffer1[33], tmp_buffer2[90] = "0", tmp_buffer3[90] = "0";
|
|
|
|
if (caid == 0x2602 && cw_ex != NULL) // BISS2
|
|
{
|
|
cw_ex->mode = CW_MODE_ONE_CW;
|
|
cw_ex->algo = CW_ALGO_AES128;
|
|
cw_ex->algo_mode = CW_ALGO_MODE_CBC;
|
|
memcpy(cw_ex->data, dvb_cissa_iv, 16);
|
|
|
|
sw = cw_ex->session_word;
|
|
sw_length = 16;
|
|
}
|
|
else // BISS1
|
|
{
|
|
sw = dw;
|
|
sw_length = 8;
|
|
}
|
|
|
|
srvid = b2i(2, ecm + 3);
|
|
|
|
if (ecm_len >= 17) // Likely an r752+ extended ecm
|
|
{
|
|
tsid = b2i(2, ecm + ecm_len - 8);
|
|
onid = b2i(2, ecm + ecm_len - 6);
|
|
ens = b2i(4, ecm + ecm_len - 4);
|
|
}
|
|
|
|
// 1. Namespace hash (enigma only)
|
|
if (is_valid_namespace(ens))
|
|
{
|
|
unify_orbitals(&ens);
|
|
memcpy(ecm_copy, ecm, ecm_len);
|
|
i2b_buf(4, ens, ecm_copy + ecm_len - 4);
|
|
|
|
for (i = 0; i < 5; i++) // Find key matching hash made with frequency modified to: f+0, then f-1, f+1, f-2, lastly f+2
|
|
{
|
|
ecm_copy[ecm_len - 1] = (i & 1) ? ecm_copy[ecm_len - 1] - i : ecm_copy[ecm_len - 1] + i; // frequency +/- 1, 2 MHz
|
|
|
|
if (0 != (ens & 0xFFFF)) // Full namespace - Calculate hash with srvid and namespace only
|
|
{
|
|
i2b_buf(2, srvid, ecm_copy + ecm_len - 6); // Put [srvid] right before [ens]
|
|
hash = crc32(caid, ecm_copy + ecm_len - 6, 6);
|
|
}
|
|
else // Namespace without frequency - Calculate hash with srvid, tsid, onid and namespace
|
|
{
|
|
i2b_buf(2, srvid, ecm_copy + ecm_len - 10); // Put [srvid] right before [tsid] [onid] [ens] sequence
|
|
hash = crc32(caid, ecm_copy + ecm_len - 10, 10);
|
|
}
|
|
|
|
if (get_sw(hash, sw, sw_length, rdr->emu_datecodedenabled, i == 0 ? 2 : 1)) // Do not print "key not found" for frequency off by 1, 2
|
|
{
|
|
memcpy(sw + sw_length, sw, sw_length);
|
|
return EMU_OK;
|
|
}
|
|
|
|
if (i == 0) // No key found matching our hash: create example SoftCam.Key BISS line for the live log
|
|
{
|
|
annotate(tmp_buffer2, sizeof(tmp_buffer2), ecm_copy, ecm_len, hash, 1, rdr->emu_datecodedenabled);
|
|
}
|
|
|
|
if (0 == (ens & 0xFFFF)) // Namespace without frequency - Do not iterate
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2. Universal hash (in r752+ style ecms that contain pmt pid)
|
|
if ((ens & 0xF0000000) == 0xA0000000)
|
|
{
|
|
hash = crc32(caid, ecm + 3, ecm_len - 3 - 8); // Do not include [tsid] [onid] [ens] in the hash
|
|
|
|
if (get_sw(hash, sw, sw_length, rdr->emu_datecodedenabled, 2))
|
|
{
|
|
memcpy(sw + sw_length, sw, sw_length);
|
|
return EMU_OK;
|
|
}
|
|
|
|
// No key found matching our hash: create example SoftCam.Key BISS line for the live log
|
|
annotate(tmp_buffer3, sizeof(tmp_buffer3), ecm_copy, ecm_len, hash, 0, rdr->emu_datecodedenabled);
|
|
}
|
|
|
|
// 3. Valid [tsid] [onid] combination (per enigma2)
|
|
if (onid != 0 && (onid != 1 || tsid >= 2) && onid < 0xFF00)
|
|
{
|
|
if (get_sw(tsid << 16 | onid, sw, sw_length, 0, 2))
|
|
{
|
|
memcpy(sw + sw_length, sw, sw_length);
|
|
return EMU_OK;
|
|
}
|
|
}
|
|
|
|
// 4. Reverse order pid search
|
|
// (better identifies channels with variable counts of audio pids)
|
|
// Strip [tsid] [onid] [ens] on r752+ ecms to be compatible with older versions)
|
|
if ((ens & 0xF0000000) == 0xA0000000)
|
|
{
|
|
ecm_len -= 8;
|
|
}
|
|
|
|
for (i = ecm_len - 2; i >= 5; i -= 2)
|
|
{
|
|
pid = b2i(2, ecm + i);
|
|
|
|
if (get_sw((srvid << 16) | pid, sw, sw_length, 0, 2))
|
|
{
|
|
memcpy(sw + sw_length, sw, sw_length);
|
|
return EMU_OK;
|
|
}
|
|
}
|
|
|
|
// 5. Legacy [srvid] [ecm pid] combination
|
|
if (get_sw((srvid << 16) | ecm_pid, sw, sw_length, 0, 2))
|
|
{
|
|
memcpy(sw + sw_length, sw, sw_length);
|
|
return EMU_OK;
|
|
}
|
|
|
|
// 6. Default BISS key for events with many feeds sharing the same session word
|
|
// (limited to local ecms, network ecms with ecm pid equal to zero are blocked)
|
|
if (ecm_pid != 0 && get_sw(0xA11FEED5, sw, sw_length, rdr->emu_datecodedenabled, 2))
|
|
{
|
|
memcpy(sw + sw_length, sw, sw_length);
|
|
cs_hexdump(0, sw, sw_length, tmp_buffer1, sizeof(tmp_buffer1));
|
|
cs_log("No specific match found. Using 'All Feeds' key: %s", tmp_buffer1);
|
|
return EMU_OK;
|
|
}
|
|
|
|
// Print example key lines for available hash search methods, if no key is found
|
|
if (strncmp(tmp_buffer2, "0", 2)) cs_log("Example key based on namespace hash: %s", tmp_buffer2);
|
|
if (strncmp(tmp_buffer3, "0", 2)) cs_log("Example key based on universal hash: %s", tmp_buffer3);
|
|
|
|
// Check if universal hash is common and warn user
|
|
if (is_common_hash(hash)) cs_log("Feed has commonly used pids, universal hash clashes in SoftCam.Key are likely!");
|
|
|
|
return EMU_KEY_NOT_FOUND;
|
|
}
|
|
|
|
static inline int8_t get_ecm_key(uint16_t onid, uint16_t esid, uint8_t parity, uint8_t *key)
|
|
{
|
|
return emu_find_key('G', onid << 16 | esid, 0, parity == 0 ? "00" : "01", key, 16, 1, 0, 0, NULL);
|
|
}
|
|
|
|
static int8_t biss2_mode_ca_ecm(const uint8_t *ecm, EXTENDED_CW *cw_ex)
|
|
{
|
|
uint8_t ecm_cipher_type, session_key_parity;
|
|
uint8_t session_key[16], iv[16];
|
|
uint16_t entitlement_session_id, original_network_id, descriptor_length;
|
|
uint16_t position, ecm_length = SCT_LEN(ecm);
|
|
uint32_t payload_checksum, calculated_checksum;
|
|
char tmp_buffer[64];
|
|
struct aes_keys aes;
|
|
|
|
// Calculate crc32 checksum and compare against the checksum bytes of the ECM
|
|
payload_checksum = b2i(4, ecm + ecm_length - 4);
|
|
calculated_checksum = ccitt32_crc((uint8_t *)ecm, ecm_length - 4);
|
|
|
|
if (payload_checksum != calculated_checksum)
|
|
{
|
|
cs_log_dbg(D_TRACE, "ECM checksum mismatch (payload: %08X vs calculated: %08X",
|
|
payload_checksum, calculated_checksum);
|
|
return EMU_CHECKSUM_ERROR;
|
|
}
|
|
|
|
// Unique identifiers of the session key
|
|
entitlement_session_id = b2i(2, ecm + 3);
|
|
original_network_id = b2i(2, ecm + 8);
|
|
|
|
ecm_cipher_type = ecm[10] >> 5;
|
|
if (ecm_cipher_type != 0) // Session words shall be encrypted with AES_128_CBC
|
|
{
|
|
cs_log("ECM cipher type %d not supported", ecm_cipher_type);
|
|
return EMU_NOT_SUPPORTED;
|
|
}
|
|
|
|
descriptor_length = b2i(2, ecm + 10) & 0x0FFF;
|
|
position = 12 + descriptor_length;
|
|
|
|
session_key_parity = ecm[position] >> 7; // Parity can be "00" or "01"
|
|
position++;
|
|
|
|
if (!get_ecm_key(original_network_id, entitlement_session_id, session_key_parity, session_key))
|
|
{
|
|
return EMU_KEY_NOT_FOUND;
|
|
}
|
|
|
|
memcpy(iv, ecm + position, 16); // "AES_128_CBC_enc_session_word_iv"
|
|
memcpy(cw_ex->session_word, ecm + position + 16, 16); // "AES_128_CBC_enc_session_word_0"
|
|
memcpy(cw_ex->session_word + 16, ecm + position + 32, 16); // "AES_128_CBC_enc_session_word_1"
|
|
|
|
// Delete these cs_log calls when everything is confirmed to work correctly
|
|
cs_hexdump(3, iv, 16, tmp_buffer, sizeof(tmp_buffer));
|
|
cs_log_dbg(D_TRACE, "session_word_iv: %s", tmp_buffer);
|
|
|
|
cs_hexdump(3, cw_ex->session_word, 16, tmp_buffer, sizeof(tmp_buffer));
|
|
cs_log_dbg(D_TRACE, "encrypted session_word_0: %s", tmp_buffer);
|
|
|
|
cs_hexdump(3, cw_ex->session_word + 16, 16, tmp_buffer, sizeof(tmp_buffer));
|
|
cs_log_dbg(D_TRACE, "encrypted session_word_1: %s", tmp_buffer);
|
|
|
|
// Decrypt session words
|
|
aes_set_key(&aes, (char *)session_key);
|
|
aes_cbc_decrypt(&aes, cw_ex->session_word, 16, iv);
|
|
memcpy(iv, ecm + position, 16); // Set iv again to the correct one
|
|
aes_cbc_decrypt(&aes, cw_ex->session_word + 16, 16, iv);
|
|
|
|
// Delete these cs_log calls when everything is confirmed to work correctly
|
|
cs_hexdump(3, cw_ex->session_word, 16, tmp_buffer, sizeof(tmp_buffer));
|
|
cs_log_dbg(D_TRACE, "decrypted session_word_0: %s", tmp_buffer);
|
|
|
|
cs_hexdump(3, cw_ex->session_word + 16, 16, tmp_buffer, sizeof(tmp_buffer));
|
|
cs_log_dbg(D_TRACE, "decrypted session_word_1: %s", tmp_buffer);
|
|
|
|
cw_ex->mode = CW_MODE_ONE_CW;
|
|
cw_ex->algo = CW_ALGO_AES128;
|
|
cw_ex->algo_mode = CW_ALGO_MODE_CBC;
|
|
memcpy(cw_ex->data, dvb_cissa_iv, 16);
|
|
|
|
return EMU_OK;
|
|
}
|
|
|
|
int8_t biss_ecm(struct s_reader *rdr, const uint8_t *ecm, uint16_t caid, uint16_t ecm_pid, uint8_t *dw, EXTENDED_CW *cw_ex)
|
|
{
|
|
switch (caid)
|
|
{
|
|
case 0x2600:
|
|
return biss_mode1_ecm(rdr, ecm, caid, ecm_pid, dw, NULL);
|
|
|
|
case 0x2602:
|
|
return biss_mode1_ecm(rdr, ecm, caid, ecm_pid, NULL, cw_ex);
|
|
|
|
case 0x2610:
|
|
return biss2_mode_ca_ecm(ecm, cw_ex);
|
|
|
|
default:
|
|
cs_log("Unknown Biss caid %04X - Please report!", caid);
|
|
return EMU_NOT_SUPPORTED;
|
|
}
|
|
}
|
|
|
|
static uint16_t parse_session_data_descriptor(const uint8_t *data, uint16_t esid, uint16_t onid, uint32_t *keysAdded)
|
|
{
|
|
uint8_t descriptor_tag = data[0];
|
|
uint8_t descriptor_length = data[1];
|
|
|
|
switch (descriptor_tag)
|
|
{
|
|
case 0x81: // session_key_descriptor
|
|
{
|
|
uint8_t session_key_type = data[2] >> 1;
|
|
if (session_key_type == 0) // AES-128
|
|
{
|
|
uint8_t session_key_parity = data[2] & 0x01;
|
|
uint8_t session_key_data[16];
|
|
memcpy(session_key_data, data + 3, 16); // This is the ECM key
|
|
|
|
SAFE_MUTEX_LOCK(&emu_key_data_mutex);
|
|
if (emu_update_key('G', onid << 16 | esid, session_key_parity ? "01" : "00", session_key_data, 16, 1, NULL))
|
|
{
|
|
(*keysAdded)++;
|
|
char tmp[33];
|
|
cs_hexdump(0, session_key_data, 16, tmp, sizeof(tmp));
|
|
cs_log("Key found in EMM: G %08X %02d %s", onid << 16 | esid, session_key_parity, tmp);
|
|
}
|
|
SAFE_MUTEX_UNLOCK(&emu_key_data_mutex);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 0x82: // entitlement_flags_descriptor
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 2 + descriptor_length;
|
|
}
|
|
|
|
static int8_t parse_session_data(const uint8_t *data, RSA *key, uint16_t esid, uint16_t onid, uint32_t *keysAdded)
|
|
{
|
|
// session_data is encrypted with RSA 2048 bit OAEP
|
|
// Maximum size of decrypted session_data is less than (256-41) bytes
|
|
uint8_t session_data[214];
|
|
|
|
if (RSA_private_decrypt(256, data, session_data, key, RSA_PKCS1_OAEP_PADDING) > 0)
|
|
{
|
|
uint16_t pos = 0;
|
|
uint16_t descriptor_length = b2i(2, session_data) & 0x0FFF;
|
|
|
|
while (pos < descriptor_length)
|
|
{
|
|
pos += parse_session_data_descriptor(session_data + 2 + pos, esid, onid, keysAdded);
|
|
}
|
|
|
|
return EMU_OK;
|
|
}
|
|
|
|
return EMU_NOT_SUPPORTED; // Decryption failed for whatever reason
|
|
}
|
|
|
|
static int8_t get_rsa_key(struct s_reader *rdr, const uint8_t *ekid, RSA **key)
|
|
{
|
|
LL_ITER itr;
|
|
biss2_rsa_key_t *data;
|
|
|
|
itr = ll_iter_create(rdr->ll_biss2_rsa_keys);
|
|
while ((data = ll_iter_next(&itr)))
|
|
{
|
|
if (data->ekid == ekid)
|
|
{
|
|
*key = data->key;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int8_t biss_emm(struct s_reader *rdr, const uint8_t *emm, uint32_t *keysAdded)
|
|
{
|
|
uint8_t emm_cipher_type, entitlement_priv_data_loop, entitlement_key_id[8];
|
|
uint16_t entitlement_session_id, original_network_id, descriptor_length;
|
|
uint16_t pos, emm_length = SCT_LEN(emm);
|
|
uint32_t payload_checksum, calculated_checksum;
|
|
int8_t result = EMU_NOT_SUPPORTED;
|
|
char tmp[17];
|
|
RSA *key;
|
|
|
|
// Calculate crc32 checksum and compare against the checksum bytes of the EMM
|
|
payload_checksum = b2i(4, emm + emm_length - 4);
|
|
calculated_checksum = ccitt32_crc((uint8_t *)emm, emm_length - 4);
|
|
|
|
if (payload_checksum != calculated_checksum)
|
|
{
|
|
cs_log_dbg(D_TRACE, "EMM checksum mismatch (payload: %08X vs calculated: %08X",
|
|
payload_checksum, calculated_checksum);
|
|
return EMU_CHECKSUM_ERROR;
|
|
}
|
|
|
|
// Identifiers of the session key carried in the EMM
|
|
// We just pass them to the "parse_session_data()" function
|
|
entitlement_session_id = b2i(2, emm + 3);
|
|
original_network_id = b2i(2, emm + 8);
|
|
cs_log_dbg(D_TRACE, "onid: %04X, esid: %04X", original_network_id, entitlement_session_id);
|
|
|
|
emm_cipher_type = emm[11] >> 5; // top 3 bits;
|
|
entitlement_priv_data_loop = (emm[11] >> 4) & 0x01; // 4th bit
|
|
|
|
if (emm_cipher_type != 0) // EMM payload is not encrypted with RSA_2048_OAEP
|
|
{
|
|
cs_log_dbg(D_TRACE, "EMM cipher type %d not supported", emm_cipher_type);
|
|
return EMU_NOT_SUPPORTED;
|
|
}
|
|
|
|
descriptor_length = b2i(2, emm + 12) & 0x0FFF;
|
|
pos = 14 + descriptor_length;
|
|
|
|
while (pos < emm_length - 4)
|
|
{
|
|
// Unique identifier of the public rsa key used for "session_data" encryption
|
|
memcpy(entitlement_key_id, emm + pos, 8);
|
|
pos += 8;
|
|
|
|
if (get_rsa_key(rdr, entitlement_key_id, &key)) // Key found
|
|
{
|
|
cs_hexdump(0, entitlement_key_id, 8, tmp, sizeof(tmp));
|
|
cs_log_dbg(D_TRACE, "RSA key found (ekid: %s)", tmp);
|
|
|
|
// Parse "encrypted_session_data"
|
|
result = parse_session_data(emm + pos, key, entitlement_session_id, original_network_id, keysAdded);
|
|
if (result == EMU_OK)
|
|
{
|
|
break; // No need to decrypt again with another key
|
|
}
|
|
}
|
|
else // Multiple ekid's can be present in the EMM - Do not exit just yet
|
|
{
|
|
cs_hexdump(0, entitlement_key_id, 8, tmp, sizeof(tmp));
|
|
cs_log_dbg(D_TRACE, "RSA key not found (ekid: %s)", tmp);
|
|
|
|
result = EMU_KEY_NOT_FOUND;
|
|
}
|
|
|
|
pos += 256; // 2048 bits
|
|
|
|
if (entitlement_priv_data_loop) // Skip any remaining bytes
|
|
{
|
|
pos += 2 + (b2i(2, emm + pos) & 0x0FFF);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static int8_t rsa_key_exists(struct s_reader *rdr, const biss2_rsa_key_t *item)
|
|
{
|
|
LL_ITER itr;
|
|
biss2_rsa_key_t *data;
|
|
|
|
itr = ll_iter_create(rdr->ll_biss2_rsa_keys);
|
|
while ((data = ll_iter_next(&itr)))
|
|
{
|
|
if (data->ekid == item->ekid)
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint16_t biss_read_pem(struct s_reader *rdr, uint8_t max_keys)
|
|
{
|
|
FILE *fp_pri = NULL;
|
|
//FILE *fp_pub = NULL;
|
|
|
|
char tmp[256];
|
|
uint8_t hash[32], *der = NULL;
|
|
uint16_t i, length, count = 0;;
|
|
biss2_rsa_key_t *new_item;
|
|
|
|
if (!rdr->ll_biss2_rsa_keys)
|
|
{
|
|
rdr->ll_biss2_rsa_keys = ll_create("ll_biss2_rsa_keys");
|
|
}
|
|
|
|
for (i = 0; i < max_keys; i++)
|
|
{
|
|
if (!cs_malloc(&new_item, sizeof(biss2_rsa_key_t)))
|
|
{
|
|
break; // No memory available (?) - Exit
|
|
}
|
|
|
|
snprintf(tmp, sizeof(tmp), "%sbiss2_private_%02d.pem", emu_keyfile_path, i);
|
|
if ((fp_pri = fopen(tmp, "r")) == NULL)
|
|
{
|
|
continue; // File does not exist
|
|
}
|
|
|
|
cs_log("Reading RSA key from: biss2_private_%02d.pem", i);
|
|
|
|
// Read RSA private key
|
|
if ((new_item->key = PEM_read_RSAPrivateKey(fp_pri, NULL, NULL, NULL)) == NULL)
|
|
{
|
|
cs_log("Error reading RSA private key");
|
|
continue;
|
|
}
|
|
|
|
fclose(fp_pri);
|
|
|
|
// Write public key in PEM formatted file
|
|
/*snprintf(tmp, sizeof(tmp), "%sbiss2_public_%02d.pem", emu_keyfile_path, i);
|
|
if ((fp_pub = fopen(tmp, "w")) != NULL)
|
|
{
|
|
PEM_write_RSA_PUBKEY(fp_pub, item->key);
|
|
fclose(fp_pub);
|
|
}*/
|
|
|
|
// Write public key in DER formatted file
|
|
/*snprintf(tmp, sizeof(tmp), "%sbiss2_public_%02d.der", emu_keyfile_path, i);
|
|
if ((fp_pub = fopen(tmp, "wb")) != NULL)
|
|
{
|
|
i2d_RSA_PUBKEY_fp(fp_pub, item->key);
|
|
fclose(fp_pub);
|
|
}*/
|
|
|
|
// Encode RSA public key into DER format
|
|
if ((length = i2d_RSA_PUBKEY(new_item->key, &der)) <= 0)
|
|
{
|
|
cs_log("Error encoding to DER format");
|
|
NULLFREE(der);
|
|
continue;
|
|
}
|
|
|
|
// Create SHA256 digest
|
|
EVP_MD_CTX *mdctx;
|
|
if ((mdctx = EVP_MD_CTX_create()) == NULL)
|
|
{
|
|
NULLFREE(der);
|
|
continue;
|
|
}
|
|
|
|
EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL);
|
|
EVP_DigestUpdate(mdctx, der, length);
|
|
EVP_DigestFinal_ex(mdctx, hash, NULL);
|
|
EVP_MD_CTX_destroy(mdctx);
|
|
|
|
NULLFREE(der);
|
|
memcpy(new_item->ekid, hash, 8);
|
|
|
|
// Add new RSA key, if not already present
|
|
if (!rsa_key_exists(rdr, new_item))
|
|
{
|
|
ll_append(rdr->ll_biss2_rsa_keys, new_item);
|
|
count++;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
#endif // WITH_EMU
|