oscam-2.26.01-11942-802-wit.../module-emulator-biss.c

884 lines
25 KiB
C
Raw Normal View History

#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