Created
December 5, 2015 01:24
-
-
Save MilhouseVH/bd80fdd1adc505859754 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* This file is part of libaacs | |
* Copyright (C) 2009-2010 Obliter0n | |
* Copyright (C) 2009-2013 npzacs | |
* | |
* This library is free software; you can redistribute it and/or | |
* modify it under the terms of the GNU Lesser General Public | |
* License as published by the Free Software Foundation; either | |
* version 2.1 of the License, or (at your option) any later version. | |
* | |
* This library is distributed in the hope that it will be useful, | |
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
* Lesser General Public License for more details. | |
* | |
* You should have received a copy of the GNU Lesser General Public | |
* License along with this library. If not, see | |
* <http://www.gnu.org/licenses/>. | |
*/ | |
#if HAVE_CONFIG_H | |
#include "config.h" | |
#endif | |
#include "util/attributes.h" | |
#include "aacs-version.h" | |
#include "aacs.h" | |
#include "crypto.h" | |
#include "mmc.h" | |
#include "mkb.h" | |
#include "file/file.h" | |
#include "file/keydbcfg.h" | |
#include "util/macro.h" | |
#include "util/logging.h" | |
#include "util/strutl.h" | |
#include <inttypes.h> | |
#include <string.h> | |
#include <stdio.h> | |
#ifdef HAVE_SYS_SELECT_H | |
#include <sys/select.h> | |
#endif | |
#include <gcrypt.h> | |
#define SECTOR_LEN 2048 /* bus encryption block size */ | |
#define ALIGNED_UNIT_LEN 6144 /* aacs block size */ | |
struct aacs { | |
void *fopen_handle; | |
AACS_FILE_OPEN2 fopen; | |
/* current disc */ | |
char *path; | |
int mkb_version; | |
uint8_t disc_id[20]; | |
/* VID is cached for BD-J */ | |
uint8_t vid[16]; | |
/* PMSN is cached for BD-J */ | |
uint8_t pmsn[16]; | |
/* Media key is cached for BD+ */ | |
uint8_t mk[16]; | |
/* unit key for each CPS unit */ | |
uint32_t num_uks; | |
uint8_t *uks; | |
/* CPS unit of currently selected title */ | |
uint16_t current_cps_unit; | |
uint8_t cps_unit_selected; | |
/* title -> CPS unit mappings */ | |
uint32_t num_titles; | |
uint16_t *cps_units; /* [0] = first play ; [1] = top menu ; [2] = title 1 ... */ | |
/* bus encryption */ | |
int bee; /* bus encryption enabled flag in content certificate */ | |
int bec; /* bus encryption capable flag in drive certificate */ | |
uint8_t read_data_key[16]; | |
/* AACS Online (BD-J) */ | |
uint8_t device_nonce[16]; | |
uint8_t device_binding_id[16]; | |
}; | |
static const uint8_t empty_key[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; | |
static const uint8_t aacs_iv[16] = { 0x0b, 0xa0, 0xf8, 0xdd, 0xfe, 0xa6, 0x1f, 0xb3, | |
0xd8, 0xdf, 0x9f, 0x56, 0x6a, 0x05, 0x0f, 0x78 }; | |
static int _validate_pk(const uint8_t *pk, | |
const uint8_t *cvalue, const uint8_t *uv, const uint8_t *vd, | |
uint8_t *mk) | |
{ | |
gcry_cipher_hd_t gcry_h; | |
int a; | |
uint8_t dec_vd[16]; | |
char str[40]; | |
BD_DEBUG(DBG_AACS, "Validate processing key %s...\n", str_print_hex(str, pk, 16)); | |
BD_DEBUG(DBG_AACS, " Using:\n"); | |
BD_DEBUG(DBG_AACS, " UV: %s\n", str_print_hex(str, uv, 4)); | |
BD_DEBUG(DBG_AACS, " cvalue: %s\n", str_print_hex(str, cvalue, 16)); | |
BD_DEBUG(DBG_AACS, " Verification data: %s\n", str_print_hex(str, vd, 16)); | |
gcry_cipher_open(&gcry_h, GCRY_CIPHER_AES, GCRY_CIPHER_MODE_ECB, 0); | |
gcry_cipher_setkey(gcry_h, pk, 16); | |
gcry_cipher_decrypt(gcry_h, mk, 16, cvalue, 16); | |
for (a = 0; a < 4; a++) { | |
mk[a + 12] ^= uv[a]; | |
} | |
gcry_cipher_setkey(gcry_h, mk, 16); | |
gcry_cipher_decrypt (gcry_h, dec_vd, 16, vd, 16); | |
gcry_cipher_close(gcry_h); | |
if (!memcmp(dec_vd, "\x01\x23\x45\x67\x89\xAB\xCD\xEF", 8)) { | |
BD_DEBUG(DBG_AACS, "Processing key %s is valid!\n", str_print_hex(str, pk, 16)); | |
return AACS_SUCCESS; | |
} | |
return AACS_ERROR_NO_PK; | |
} | |
static int _rl_verify_signature(const uint8_t *rl, size_t size) | |
{ | |
int entries = MKINT_BE32(rl + 12 + 8); | |
size_t len = 12 + 12 + 8 * entries; /* type_and_version_rec=12, rl_header=12, rl=entries*8 */ | |
if (len + 40 > size) { | |
BD_DEBUG(DBG_AACS, "revocation list size mismatch\n"); | |
return 0; | |
} | |
return crypto_aacs_verify_aacsla(rl + len, rl, len); | |
} | |
static void _save_rl(const char *name, uint32_t version, const uint8_t *version_rec, | |
const uint8_t *rl_rec, size_t rl_len) | |
{ | |
int len = MKINT_BE24(rl_rec - 3); | |
int entries = MKINT_BE32(rl_rec + 4); /* entries in first signature block */ | |
if (len < 4 || !entries) { | |
BD_DEBUG(DBG_AACS | DBG_CRIT, "ignoring empty %s\n", name); | |
return; | |
} | |
if (rl_rec && version_rec) { | |
rl_rec -= 4; | |
rl_len += 4; | |
uint8_t *data = malloc(12 + rl_len); | |
memcpy(data, version_rec, 12); | |
memcpy(data + 12, rl_rec, rl_len); | |
if (!_rl_verify_signature(data, rl_len + 12)) { | |
BD_DEBUG(DBG_AACS | DBG_CRIT, "invalid %s signature, not using it\n", name); | |
} else { | |
cache_save(name, version, data, rl_len + 12); | |
} | |
X_FREE(data); | |
} | |
} | |
static void _update_rl(MKB *mkb) | |
{ | |
uint32_t version = mkb_version(mkb); | |
uint32_t cache_version; | |
size_t rl_len; | |
if (!cache_get("drl", &cache_version, NULL, NULL) || cache_version < version) { | |
const uint8_t *version_rec = mkb_type_and_version_record(mkb); | |
const uint8_t *drl_rec = mkb_drive_revokation_entries(mkb, &rl_len); | |
if (drl_rec && rl_len > 8) { | |
_save_rl("drl", version, version_rec, drl_rec, rl_len); | |
} | |
} | |
if (!cache_get("hrl", &cache_version, NULL, NULL) || cache_version < version) { | |
const uint8_t *version_rec = mkb_type_and_version_record(mkb); | |
const uint8_t *hrl_rec = mkb_host_revokation_entries(mkb, &rl_len); | |
if (hrl_rec && rl_len > 8) { | |
_save_rl("hrl", version, version_rec, hrl_rec, rl_len); | |
} | |
} | |
} | |
static uint32_t _calc_v_mask(uint32_t uv) | |
{ | |
uint32_t v_mask = 0xffffffff; | |
while (!(uv & ~v_mask)) { | |
v_mask <<= 1; | |
} | |
return v_mask; | |
} | |
static void _calc_pk(const uint8_t *dk, uint8_t *pk, uint32_t uv, uint32_t v_mask, uint32_t dev_key_v_mask) | |
{ | |
unsigned char left_child[16], right_child[16]; | |
crypto_aesg3(dk, left_child, right_child, pk); | |
while (dev_key_v_mask != v_mask) { | |
int i; | |
for (i = 31; i >= 0; i--) { | |
if (!(dev_key_v_mask & (1ul << i))) { | |
break; | |
} | |
} | |
uint8_t curr_key[16]; | |
if (!(uv & (1ul << i))) { | |
memcpy(curr_key, left_child, 16); | |
} else { | |
memcpy(curr_key, right_child, 16); | |
} | |
crypto_aesg3(curr_key, left_child, right_child, pk); | |
dev_key_v_mask = ((int) dev_key_v_mask) >> 1; | |
} | |
char str[40]; | |
BD_DEBUG(DBG_AACS, "Processing key: %s\n", str_print_hex(str, pk, 16)); | |
} | |
static dk_list *_find_dk(dk_list *dkl, uint32_t *p_dev_key_v_mask, uint32_t uv, uint32_t u_mask) | |
{ | |
uint32_t device_number = dkl->node; | |
uint32_t dev_key_uv, dev_key_u_mask, dev_key_v_mask; | |
unsigned key_idx = 0; | |
for (; dkl; dkl = dkl->next) { | |
if (device_number != dkl->node) { | |
/* wrong device */ | |
continue; | |
} | |
if (!dkl->uv) { | |
continue; | |
} | |
key_idx++; | |
dev_key_uv = dkl->uv; | |
dev_key_u_mask = 0xffffffff << dkl->u_mask_shift; | |
dev_key_v_mask = _calc_v_mask(dev_key_uv); | |
if ((u_mask == dev_key_u_mask) && | |
((uv & dev_key_v_mask) == (dev_key_uv & dev_key_v_mask))) { | |
break; | |
} | |
} | |
if (!dkl) { | |
BD_DEBUG(DBG_AACS | DBG_CRIT, "could not find applying device key (device 0x%x)\n", device_number); | |
} else { | |
char str[128]; | |
BD_DEBUG(DBG_AACS, "Applying device key is #%d %s\n", key_idx, str_print_hex(str, dkl->key, 16)); | |
BD_DEBUG(DBG_AACS, " UV: 0x%08x U mask: 0x%08x V mask: 0x%08x\n", dev_key_uv, dev_key_u_mask, dev_key_v_mask); | |
*p_dev_key_v_mask = dev_key_v_mask; | |
} | |
return dkl; | |
} | |
static int _calc_pk_mk(MKB *mkb, dk_list *dkl, uint8_t *mk) | |
{ | |
/* calculate processing key and media key using device keys */ | |
const uint8_t *uvs, *cvalues; | |
unsigned num_uvs; | |
size_t len; | |
char str[128]; | |
/* get mkb data */ | |
uvs = mkb_subdiff_records(mkb, &len); | |
cvalues = mkb_cvalues(mkb, &len); | |
num_uvs = len / 5; | |
if (num_uvs < 1) { | |
return AACS_ERROR_CORRUPTED_DISC; | |
} | |
/* loop over all known devices */ | |
dk_list *dk_num; | |
uint32_t device_number = (uint32_t)-1; | |
for (dk_num = dkl; dk_num; dk_num = dk_num->next) { | |
/* find next device */ | |
if (device_number == dk_num->node) { | |
continue; | |
} | |
device_number = dk_num->node; | |
/* find applying subset difference */ | |
unsigned uvs_idx; | |
uint32_t u_mask, v_mask, uv; | |
for (uvs_idx = 0; uvs_idx < num_uvs; uvs_idx++) { | |
const uint8_t *p_uv = uvs + 1 + 5 * uvs_idx; | |
uint8_t u_mask_shift = p_uv[-1]; | |
uv = MKINT_BE32(p_uv); | |
if (!uv) { | |
continue; | |
} | |
if (u_mask_shift & 0xc0) { | |
BD_DEBUG(DBG_AACS | DBG_CRIT, "device 0x%x is revoked\n", device_number); | |
uvs_idx = num_uvs; | |
} else { | |
u_mask = 0xffffffff << u_mask_shift; | |
v_mask = _calc_v_mask(uv); | |
if (((device_number & u_mask) == (uv & u_mask)) && ((device_number & v_mask) != (uv & v_mask))) { | |
break; | |
} | |
} | |
} | |
if (uvs_idx >= num_uvs) { | |
BD_DEBUG(DBG_AACS | DBG_CRIT, "could not find applying subset-difference for device 0x%x\n", device_number); | |
/* try next device */ | |
continue; | |
} | |
BD_DEBUG(DBG_AACS, "Applying subset-difference for device 0x%x is #%d:\n", device_number, uvs_idx); | |
BD_DEBUG(DBG_AACS," UV: 0x%08x U mask: 0x%08x V mask: 0x%08x\n", uv, u_mask, v_mask); | |
/* find applying device key */ | |
uint32_t dev_key_v_mask = 0; | |
dk_list *dk; | |
dk = _find_dk(dk_num, &dev_key_v_mask, uv, u_mask); | |
if (!dk) { | |
/* try next device */ | |
continue; | |
} | |
/* calculate processing key */ | |
uint8_t pk[16]; | |
_calc_pk(dk->key, pk, uv, v_mask, dev_key_v_mask); | |
/* calculate and verify media key */ | |
if ( _validate_pk(pk, | |
cvalues + uvs_idx * 16, | |
uvs + 1 + uvs_idx * 5, | |
mkb_mk_dv(mkb), | |
mk) | |
== AACS_SUCCESS) { | |
BD_DEBUG(DBG_AACS, "Media key: %s\n", str_print_hex(str, mk, 16)); | |
return AACS_SUCCESS; | |
} | |
BD_DEBUG(DBG_AACS | DBG_CRIT, "Processing key %s is invalid!\n", str_print_hex(str, pk, 16)); | |
/* try next device */ | |
} | |
return AACS_ERROR_NO_DK; | |
} | |
static AACS_FILE_H *_file_open(AACS *aacs, const char *file) | |
{ | |
AACS_FILE_H *fp; | |
char *f_name; | |
if (aacs->fopen) { | |
return aacs->fopen(aacs->fopen_handle, file); | |
} | |
if (!aacs->path) { | |
return NULL; | |
} | |
f_name = str_printf("%s" DIR_SEP "%s", aacs->path, file); | |
fp = file_open(f_name, "rb"); | |
X_FREE(f_name); | |
return fp; | |
} | |
static MKB *_mkb_open(AACS *aacs) | |
{ | |
AACS_FILE_H *fp; | |
MKB *mkb; | |
fp = _file_open(aacs, "AACS" DIR_SEP "MKB_RO.inf"); | |
if (!fp) { | |
BD_DEBUG(DBG_AACS | DBG_CRIT, "Error opening MKB file (AACS/MKB_RO.inf)\n"); | |
return NULL; | |
} | |
mkb = mkb_read(fp); | |
file_close(fp); | |
return mkb; | |
} | |
static int _calc_mk(AACS *aacs, uint8_t *mk, pk_list *pkl, dk_list *dkl) | |
{ | |
int a, num_uvs = 0; | |
size_t len; | |
MKB *mkb = NULL; | |
const uint8_t *rec, *uvs; | |
/* Skip if retrieved from config file */ | |
if (memcmp(mk, empty_key, 16)) { | |
return AACS_SUCCESS; | |
} | |
BD_DEBUG(DBG_AACS, "Calculate media key...\n"); | |
if ((mkb = _mkb_open(aacs))) { | |
aacs->mkb_version = mkb_version(mkb); | |
_update_rl(mkb); | |
/* try device keys first */ | |
if (dkl && _calc_pk_mk(mkb, dkl, mk) == AACS_SUCCESS) { | |
memcpy(aacs->mk, mk, sizeof(aacs->mk)); | |
mkb_close(mkb); | |
return AACS_SUCCESS; | |
} | |
BD_DEBUG(DBG_AACS, "Get UVS...\n"); | |
uvs = mkb_subdiff_records(mkb, &len); | |
rec = uvs; | |
while (rec < uvs + len) { | |
if (rec[0] & 0xc0) | |
break; | |
rec += 5; | |
num_uvs++; | |
} | |
BD_DEBUG(DBG_AACS, "Get cvalues...\n"); | |
rec = mkb_cvalues(mkb, &len); | |
for (; pkl; pkl = pkl->next) { | |
BD_DEBUG(DBG_AACS, "Trying processing key...\n"); | |
for (a = 0; a < num_uvs; a++) { | |
if (AACS_SUCCESS == _validate_pk(pkl->key, rec + a * 16, uvs + 1 + a * 5, | |
mkb_mk_dv(mkb), mk)) { | |
mkb_close(mkb); | |
char str[40]; | |
BD_DEBUG(DBG_AACS, "Media key: %s\n", str_print_hex(str, mk, 16)); | |
memcpy(aacs->mk, mk, sizeof(aacs->mk)); | |
return AACS_SUCCESS; | |
} | |
} | |
} | |
mkb_close(mkb); | |
BD_DEBUG(DBG_AACS | DBG_CRIT, "Error calculating media key. Missing right processing key ?\n"); | |
return AACS_ERROR_NO_PK; | |
} | |
return AACS_ERROR_CORRUPTED_DISC; | |
} | |
static int _mmc_read_auth(AACS *aacs, cert_list *hcl, int type, uint8_t *p1, uint8_t *p2) | |
{ | |
MMC* mmc = NULL; | |
if (!(mmc = mmc_open(aacs->path))) { | |
return AACS_ERROR_MMC_OPEN; | |
} | |
int error_code = AACS_ERROR_NO_CERT; | |
const uint8_t *drive_cert = mmc_get_drive_cert(mmc); | |
for (; hcl ; hcl = hcl->next) { | |
char tmp_str[2*92+1]; | |
if (!crypto_aacs_verify_host_cert(hcl->host_cert)) { | |
BD_DEBUG(DBG_AACS, "Not using invalid host certificate %s.\n", | |
str_print_hex(tmp_str, hcl->host_cert, 92)); | |
continue; | |
} | |
if (drive_cert && (drive_cert[1] & 0x01) && !(hcl->host_cert[1] & 0x01)) { | |
BD_DEBUG(DBG_AACS, "Certificate (id 0x%s) does not support bus encryption\n", | |
str_print_hex(tmp_str, hcl->host_cert + 4, 6)); | |
//continue; | |
} | |
BD_DEBUG(DBG_AACS, "Trying host certificate (id 0x%s)...\n", | |
str_print_hex(tmp_str, hcl->host_cert + 4, 6)); | |
int mmc_result = mmc_read_auth(mmc, hcl->host_priv_key, hcl->host_cert, type, p1, p2); | |
switch (mmc_result) { | |
case MMC_SUCCESS: | |
mmc_close(mmc); | |
return AACS_SUCCESS; | |
case MMC_ERROR_CERT_REVOKED: | |
error_code = AACS_ERROR_CERT_REVOKED; | |
break; | |
case MMC_ERROR: | |
default: | |
error_code = AACS_ERROR_MMC_FAILURE; | |
break; | |
} | |
} | |
mmc_close(mmc); | |
return error_code; | |
} | |
static int _read_vid(AACS *aacs, cert_list *hcl) | |
{ | |
/* Use VID given in config file if available */ | |
if (memcmp(aacs->vid, empty_key, 16)) { | |
return AACS_SUCCESS; | |
} | |
int error_code = _mmc_read_auth(aacs, hcl, MMC_READ_VID, aacs->vid, NULL); | |
if (error_code != AACS_SUCCESS) { | |
BD_DEBUG(DBG_AACS, "Error reading VID!\n"); | |
} else { | |
/* cache vid */ | |
keycache_save("vid", aacs->disc_id, aacs->vid, 16); | |
} | |
return error_code; | |
} | |
static int _read_read_data_key(AACS *aacs, cert_list *hcl) | |
{ | |
int error_code = _mmc_read_auth(aacs, hcl, MMC_READ_DATA_KEYS, aacs->read_data_key, NULL); | |
if (error_code != AACS_SUCCESS) { | |
BD_DEBUG(DBG_AACS, "Error reading data keys!\n"); | |
} | |
return error_code; | |
} | |
static int _read_pmsn(AACS *aacs, cert_list *hcl) | |
{ | |
int error_code = _mmc_read_auth(aacs, hcl, MMC_READ_PMSN, aacs->pmsn, NULL); | |
if (error_code != AACS_SUCCESS) { | |
BD_DEBUG(DBG_AACS, "Error reading PMSN!\n"); | |
} | |
return error_code; | |
} | |
static int _calc_vuk(AACS *aacs, uint8_t *mk, uint8_t *vuk, config_file *cf) | |
{ | |
int error_code; | |
/* Skip if retrieved from config file */ | |
if (memcmp(vuk, empty_key, 16)) { | |
BD_DEBUG(DBG_AACS, "Using VUK from config file\n"); | |
return AACS_SUCCESS; | |
} | |
/* get cached vuk */ | |
if (keycache_find("vuk", aacs->disc_id, vuk, 16)) { | |
BD_DEBUG(DBG_AACS, "Using cached VUK\n"); | |
return AACS_SUCCESS; | |
} | |
if (!cf) { | |
return AACS_ERROR_NO_CONFIG; | |
} | |
/* make sure we have media key */ | |
error_code = _calc_mk(aacs, mk, cf->pkl, cf->dkl); | |
if (error_code != AACS_SUCCESS) { | |
return error_code; | |
} | |
/* acquire VID */ | |
error_code = _read_vid(aacs, cf->host_cert_list); | |
if (error_code != AACS_SUCCESS) { | |
return error_code; | |
} | |
/* calculate VUK */ | |
crypto_aes128d(mk, aacs->vid, vuk); | |
int a; | |
for (a = 0; a < 16; a++) { | |
vuk[a] ^= aacs->vid[a]; | |
} | |
char str[40]; | |
BD_DEBUG(DBG_AACS, "Volume unique key: %s\n", str_print_hex(str, vuk, 16)); | |
/* cache vuk */ | |
keycache_save("vuk", aacs->disc_id, vuk, 16); | |
return AACS_SUCCESS; | |
} | |
static uint16_t _read_u16(AACS_FILE_H *fp) | |
{ | |
uint8_t data[2] = {0, 0}; | |
file_read(fp, data, sizeof(uint16_t)); | |
return MKINT_BE16(data); | |
} | |
static void _read_uks_map(AACS *aacs, AACS_FILE_H *fp) | |
{ | |
uint16_t first_play, top_menu; | |
unsigned i; | |
BD_DEBUG(DBG_AACS, "Assigning CPS units to titles ...\n"); | |
X_FREE(aacs->cps_units); | |
aacs->current_cps_unit = 0; | |
file_seek(fp, 16 + 4, SEEK_SET); | |
first_play = _read_u16(fp); | |
top_menu = _read_u16(fp); | |
BD_DEBUG(DBG_AACS, "Title FP : CPS unit %d\n", first_play); | |
BD_DEBUG(DBG_AACS, "Title TM : CPS unit %d\n", top_menu); | |
aacs->num_titles = _read_u16(fp); | |
aacs->cps_units = calloc(sizeof(uint16_t), aacs->num_titles + 2); | |
aacs->cps_units[0] = first_play; | |
aacs->cps_units[1] = top_menu; | |
for (i = 2; i < aacs->num_titles + 2; i++) { | |
_read_u16(fp); /* reserved */ | |
aacs->cps_units[i] = _read_u16(fp); | |
BD_DEBUG(DBG_AACS, "Title %02d : CPS unit %d\n", i - 1, aacs->cps_units[i]); | |
} | |
/* validate */ | |
for (i = 0; i < aacs->num_titles + 2; i++) { | |
if (aacs->cps_units[i]) | |
aacs->cps_units[i]--; /* number [1...N] --> index [0...N-1] */ | |
if (aacs->cps_units[i] >= aacs->num_uks) { | |
BD_DEBUG(DBG_AACS, " *** Invalid CPS unit for title %d: %d !\n", (int) i - 1, aacs->cps_units[i]); | |
aacs->cps_units[i] = 0; | |
} | |
} | |
} | |
/* Function that collects keys from keydb config entry */ | |
static void _find_config_entry(AACS *aacs, title_entry_list *ce, | |
uint8_t *mk, uint8_t *vuk) | |
{ | |
char str[48]; | |
aacs->uks = NULL; | |
aacs->num_uks = 0; | |
while (ce && ce->entry.discid) { | |
if (!memcmp(aacs->disc_id, ce->entry.discid, 20)) { | |
BD_DEBUG(DBG_AACS, "Found config entry for discid %s\n", | |
str_print_hex(str, ce->entry.discid, 20)); | |
break; | |
} | |
ce = ce->next; | |
} | |
if (!ce) { | |
return; | |
} | |
if (ce->entry.mek) { | |
hexstring_to_hex_array(mk, 16, ce->entry.mek); | |
BD_DEBUG(DBG_AACS, "Found media key for %s: %s\n", | |
ce->entry.discid, str_print_hex(str, mk, 16)); | |
} | |
if (ce->entry.vid) { | |
hexstring_to_hex_array(aacs->vid, sizeof(aacs->vid), | |
ce->entry.vid); | |
BD_DEBUG(DBG_AACS, "Found volume id for %s: %s\n", | |
ce->entry.discid, str_print_hex(str, aacs->vid, 16)); | |
} | |
if (ce->entry.vuk) { | |
hexstring_to_hex_array(vuk, 16, ce->entry.vuk); | |
BD_DEBUG(DBG_AACS, "Found volume unique key for %s: %s\n", | |
ce->entry.discid, str_print_hex(str, vuk, 16)); | |
} | |
if (ce->entry.uk) { | |
BD_DEBUG(DBG_AACS, "Acquire CPS unit keys from keydb config file...\n"); | |
digit_key_pair_list *ukcursor = ce->entry.uk; | |
while (ukcursor && ukcursor->key_pair.key) { | |
aacs->num_uks++; | |
aacs->uks = (uint8_t*)realloc(aacs->uks, 16 * aacs->num_uks); | |
hexstring_to_hex_array(aacs->uks + (16 * (aacs->num_uks - 1)), 16, | |
ukcursor->key_pair.key); | |
BD_DEBUG(DBG_AACS, "Unit key %d from keydb entry: %s\n", | |
aacs->num_uks, | |
str_print_hex(str, aacs->uks + (16 * (aacs->num_uks - 1)), 16)); | |
ukcursor = ukcursor->next; | |
} | |
} | |
} | |
static int _calc_uks(AACS *aacs, config_file *cf) | |
{ | |
AACS_FILE_H *fp = NULL; | |
uint8_t buf[16]; | |
uint64_t f_pos; | |
unsigned int i; | |
int error_code; | |
uint8_t mk[16] = {0}, vuk[16] = {0}; | |
if (cf) { | |
BD_DEBUG(DBG_AACS, "Searching for keydb config entry...\n"); | |
_find_config_entry(aacs, cf->list, mk, vuk); | |
/* Skip if retrieved from config file */ | |
if (aacs->uks) { | |
return AACS_SUCCESS; | |
} | |
} | |
/* Make sure we have VUK */ | |
error_code = _calc_vuk(aacs, mk, vuk, cf); | |
if (error_code != AACS_SUCCESS) { | |
return error_code; | |
} | |
BD_DEBUG(DBG_AACS, "Calculate CPS unit keys...\n"); | |
fp = _file_open(aacs, "AACS" DIR_SEP "Unit_Key_RO.inf"); | |
if (!fp) { | |
BD_DEBUG(DBG_AACS | DBG_CRIT, "Error opening unit key file (AACS/Unit_Key_RO.inf)\n"); | |
return AACS_ERROR_CORRUPTED_DISC; | |
} | |
if ((file_read(fp, buf, 4)) == 4) { | |
f_pos = MKINT_BE32(buf); | |
// Read number of keys | |
file_seek(fp, f_pos, SEEK_SET); | |
if ((file_read(fp, buf, 2)) == 2) { | |
aacs->num_uks = MKINT_BE16(buf); | |
X_FREE(aacs->uks); | |
aacs->uks = calloc(aacs->num_uks, 16); | |
BD_DEBUG(DBG_AACS, "%d CPS unit keys\n", aacs->num_uks); | |
} else { | |
aacs->num_uks = 0; | |
BD_DEBUG(DBG_AACS | DBG_CRIT, "Error reading number of unit keys\n"); | |
error_code = AACS_ERROR_CORRUPTED_DISC; | |
} | |
// Read keys | |
for (i = 0; i < aacs->num_uks; i++) { | |
f_pos += 48; | |
file_seek(fp, f_pos, SEEK_SET); | |
if ((file_read(fp, buf, 16)) != 16) { | |
BD_DEBUG(DBG_AACS, "Unit key %d: read error\n", i); | |
aacs->num_uks = i; | |
error_code = AACS_ERROR_CORRUPTED_DISC; | |
break; | |
} | |
crypto_aes128d(vuk, buf, aacs->uks + 16*i); | |
char str[40]; | |
BD_DEBUG(DBG_AACS, "Unit key %d: %s\n", i, | |
str_print_hex(str, aacs->uks + 16*i, 16)); | |
} | |
/* failing next is not fatal, it just slows down things */ | |
_read_uks_map(aacs, fp); | |
file_close(fp); | |
return error_code; | |
} | |
file_close(fp); | |
BD_DEBUG(DBG_AACS | DBG_CRIT, "Error reading unit keys\n"); | |
return AACS_ERROR_CORRUPTED_DISC; | |
} | |
static int _calc_title_hash(AACS *aacs) | |
{ | |
AACS_FILE_H *fp = NULL; | |
uint8_t *ukf_buf; | |
char str[48]; | |
int64_t f_size; | |
int result = AACS_SUCCESS; | |
fp = _file_open(aacs, "AACS" DIR_SEP "Unit_Key_RO.inf"); | |
if (!fp) { | |
BD_DEBUG(DBG_AACS | DBG_CRIT, "Error opening unit key file (AACS/Unit_Key_RO.inf)\n"); | |
return AACS_ERROR_CORRUPTED_DISC; | |
} | |
file_seek(fp, 0, SEEK_END); | |
f_size = file_tell(fp); | |
file_seek(fp, 0, SEEK_SET); | |
ukf_buf = malloc(f_size); | |
if ((file_read(fp, ukf_buf, f_size)) == f_size) { | |
crypto_aacs_title_hash(ukf_buf, f_size, aacs->disc_id); | |
BD_DEBUG(DBG_AACS, "Disc ID: %s\n", str_print_hex(str, aacs->disc_id, 20)); | |
} else { | |
result = AACS_ERROR_CORRUPTED_DISC; | |
BD_DEBUG(DBG_AACS | DBG_CRIT, "Failed to read %lu bytes from unit key file (AACS/Unit_Key_RO.inf)", (unsigned long)f_size); | |
} | |
file_close(fp); | |
X_FREE(ukf_buf); | |
return result; | |
} | |
static int _get_bus_encryption_enabled(AACS *aacs) | |
{ | |
AACS_FILE_H *fp = NULL; | |
uint8_t buf[2]; | |
int bee = 0; | |
fp = _file_open(aacs, "AACS" DIR_SEP "Content000.cer"); | |
if (!fp) { | |
BD_DEBUG(DBG_AACS | DBG_CRIT, "Unable to open content certificate (AACS/Content000.cer)\n"); | |
return 0; | |
} | |
if (file_read(fp, buf, 2) == 2) { | |
bee = (buf[1] & 0x80) >> 7; | |
BD_DEBUG(DBG_AACS, "Bus Encryption Enabled flag in content certificate: %d\n", bee); | |
} else { | |
BD_DEBUG(DBG_AACS | DBG_CRIT, "Failed to read Bus Encryption Enabled flag from content certificate file\n"); | |
} | |
file_close(fp); | |
return bee; | |
} | |
static int _get_bus_encryption_capable(const char *path) | |
{ | |
MMC* mmc = NULL; | |
uint8_t drive_cert[92]; | |
int bec = 0; | |
if (!(mmc = mmc_open(path))) { | |
return 0; | |
} | |
if (mmc_read_drive_cert(mmc, drive_cert) == MMC_SUCCESS) { | |
bec = drive_cert[1] & 1; | |
BD_DEBUG(DBG_AACS, "Bus Encryption Capable flag in drive certificate: %d\n", bec); | |
} else { | |
BD_DEBUG(DBG_AACS | DBG_CRIT, "Unable to read drive certificate\n"); | |
} | |
mmc_close(mmc); | |
return bec; | |
} | |
static int _verify_ts(uint8_t *buf) | |
{ | |
int i; | |
for (i = 0; i < ALIGNED_UNIT_LEN; i += 192) { | |
if (BD_UNLIKELY(buf[i + 4] != 0x47)) { | |
return 0; | |
} | |
/* Clear copy_permission_indicator bits */ | |
buf[i] &= ~0xc0; | |
} | |
return 1; | |
} | |
static int _decrypt_unit(AACS *aacs, uint8_t *out_buf, const uint8_t *in_buf, uint32_t curr_uk) | |
{ | |
/* inbuf == NULL means in-place decryption */ | |
gcry_cipher_hd_t gcry_h; | |
int a; | |
uint8_t key[16]; | |
if (BD_UNLIKELY(in_buf != NULL)) { | |
memcpy(out_buf, in_buf, 16); /* first 16 bytes are plain */ | |
} | |
gcry_cipher_open(&gcry_h, GCRY_CIPHER_AES, GCRY_CIPHER_MODE_ECB, 0); | |
gcry_cipher_setkey(gcry_h, aacs->uks + curr_uk * 16, 16); | |
gcry_cipher_encrypt(gcry_h, key, 16, out_buf, 16); /* here out_buf is plain data fron in_buf */ | |
gcry_cipher_close(gcry_h); | |
for (a = 0; a < 16; a++) { | |
key[a] ^= out_buf[a]; /* here out_buf is plain data fron in_buf */ | |
} | |
gcry_cipher_open(&gcry_h, GCRY_CIPHER_AES, GCRY_CIPHER_MODE_CBC, 0); | |
gcry_cipher_setkey(gcry_h, key, 16); | |
gcry_cipher_setiv(gcry_h, aacs_iv, 16); | |
if (BD_UNLIKELY(in_buf != NULL)) { | |
gcry_cipher_decrypt(gcry_h, out_buf + 16, ALIGNED_UNIT_LEN - 16, in_buf + 16, ALIGNED_UNIT_LEN - 16); | |
} else { | |
gcry_cipher_decrypt(gcry_h, out_buf + 16, ALIGNED_UNIT_LEN - 16, NULL, 0); | |
} | |
gcry_cipher_close(gcry_h); | |
if (_verify_ts(out_buf)) { | |
return 1; | |
} | |
return 0; | |
} | |
static void _decrypt_bus(AACS *aacs, uint8_t *buf) | |
{ | |
gcry_cipher_hd_t gcry_h; | |
gcry_cipher_open(&gcry_h, GCRY_CIPHER_AES, GCRY_CIPHER_MODE_CBC, 0); | |
gcry_cipher_setkey(gcry_h, aacs->read_data_key, 16); | |
gcry_cipher_setiv(gcry_h, aacs_iv, 16); | |
gcry_cipher_decrypt(gcry_h, buf + 16, SECTOR_LEN - 16, NULL, 0); | |
gcry_cipher_close(gcry_h); | |
} | |
void aacs_get_version(int *major, int *minor, int *micro) | |
{ | |
*major = AACS_VERSION_MAJOR; | |
*minor = AACS_VERSION_MINOR; | |
*micro = AACS_VERSION_MICRO; | |
} | |
/* aacs_open2() wrapper for backwards compability */ | |
AACS *aacs_open(const char *path, const char *configfile_path) | |
{ | |
int error_code; | |
AACS *aacs; | |
aacs = aacs_open2(path, configfile_path, &error_code); | |
if (!aacs || error_code == AACS_SUCCESS) { | |
return aacs; | |
} | |
aacs_close(aacs); | |
return NULL; | |
} | |
/* aacs_open_device() wrapper for backward compability */ | |
AACS *aacs_open2(const char *path, const char *configfile_path, int *error_code) | |
{ | |
AACS *aacs = aacs_init(); | |
if (aacs) { | |
*error_code = aacs_open_device(aacs, path, configfile_path); | |
} | |
return aacs; | |
} | |
AACS *aacs_init() | |
{ | |
BD_DEBUG(DBG_AACS, "libaacs "AACS_VERSION_STRING" [%u]\n", (unsigned)sizeof(AACS)); | |
BD_DEBUG(DBG_AACS, "Initializing libgcrypt...\n"); | |
if (!crypto_init()) { | |
BD_DEBUG(DBG_AACS | DBG_CRIT, "Failed to initialize libgcrypt\n"); | |
return NULL; | |
} | |
return calloc(1, sizeof(AACS)); | |
} | |
void aacs_set_fopen(AACS *aacs, void *handle, AACS_FILE_OPEN2 p) | |
{ | |
if (aacs) { | |
aacs->fopen = p; | |
aacs->fopen_handle = handle; | |
} | |
} | |
int aacs_open_device(AACS *aacs, const char *path, const char *configfile_path) | |
{ | |
config_file *cf; | |
int error_code; | |
aacs->path = path ? str_printf("%s", path) : NULL; | |
error_code = _calc_title_hash(aacs); | |
if (error_code != AACS_SUCCESS) { | |
return error_code; | |
} | |
cf = keydbcfg_config_load(configfile_path); | |
BD_DEBUG(DBG_AACS, "Starting AACS waterfall...\n"); | |
error_code = _calc_uks(aacs, cf); | |
if (error_code != AACS_SUCCESS) { | |
BD_DEBUG(DBG_AACS, "Failed to initialize AACS!\n"); | |
} | |
aacs->bee = _get_bus_encryption_enabled(aacs); | |
aacs->bec = _get_bus_encryption_capable(path); | |
if (error_code == AACS_SUCCESS && aacs->bee && aacs->bec) { | |
if (!cf) { | |
return AACS_ERROR_NO_CONFIG; | |
} | |
error_code = _read_read_data_key(aacs, cf->host_cert_list); | |
if (error_code != AACS_SUCCESS) { | |
BD_DEBUG(DBG_AACS | DBG_CRIT, "Unable to initialize bus encryption required by drive and disc\n"); | |
} | |
} | |
keydbcfg_config_file_close(cf); | |
BD_DEBUG(DBG_AACS, "AACS initialized!\n"); | |
return error_code; | |
} | |
void aacs_close(AACS *aacs) | |
{ | |
if (!aacs) | |
return; | |
/* erase sensitive data */ | |
if (aacs->uks) { | |
memset(aacs->uks, 0, 16 * aacs->num_uks); | |
} | |
X_FREE(aacs->uks); | |
X_FREE(aacs->cps_units); | |
X_FREE(aacs->path); | |
BD_DEBUG(DBG_AACS, "AACS destroyed!\n"); | |
/* erase sensitive data */ | |
memset(aacs, 0, sizeof(*aacs)); | |
X_FREE(aacs); | |
} | |
int aacs_decrypt_unit(AACS *aacs, uint8_t *buf) | |
{ | |
unsigned int i; | |
if (!(buf[0] & 0xc0)) { | |
// TP_extra_header Copy_permission_indicator == 0, unit is not encrypted | |
return 1; | |
} | |
/* handle bus encryption first */ | |
if (aacs->bee && aacs->bec) { | |
for (i = 0; i < ALIGNED_UNIT_LEN; i += SECTOR_LEN) { | |
_decrypt_bus(aacs, buf + i); | |
} | |
} | |
/* decrypt in-place if current unit key is known */ | |
if (BD_LIKELY(aacs->cps_unit_selected) || BD_LIKELY(aacs->num_uks == 1)) { | |
if (BD_LIKELY(_decrypt_unit(aacs, buf, NULL, aacs->current_cps_unit))) { | |
return 1; | |
} | |
} else { | |
/* unit key is unknown (playback without menus), try each key until right key is found */ | |
uint8_t out_buf[ALIGNED_UNIT_LEN]; | |
for (i = 0; i < aacs->num_uks; i++) { | |
if (_decrypt_unit(aacs, out_buf, buf, i)) { | |
BD_DEBUG(DBG_AACS, "autodetected current CPS unit (%d)\n", i); | |
aacs->current_cps_unit = i; | |
aacs->cps_unit_selected = 1; | |
memcpy(buf, out_buf, ALIGNED_UNIT_LEN); | |
return 1; | |
} | |
} | |
} | |
BD_DEBUG(DBG_AACS, "Failed decrypting unit [6144 bytes]\n"); | |
return 0; | |
} | |
int aacs_get_mkb_version(AACS *aacs) | |
{ | |
if (!aacs->mkb_version) { | |
MKB *mkb; | |
if ((mkb = _mkb_open(aacs))) { | |
aacs->mkb_version = mkb_version(mkb); | |
mkb_close(mkb); | |
} | |
} | |
return aacs->mkb_version; | |
} | |
const uint8_t *aacs_get_disc_id(AACS *aacs) | |
{ | |
return aacs->disc_id; | |
} | |
const uint8_t *aacs_get_mk(AACS *aacs) | |
{ | |
if (!memcmp(aacs->mk, empty_key, 16)) { | |
config_file *cf = keydbcfg_config_load(NULL); | |
if (cf) { | |
_calc_mk(aacs, aacs->mk, cf->pkl, cf->dkl); | |
keydbcfg_config_file_close(cf); | |
} | |
if (!memcmp(aacs->mk, empty_key, 16)) { | |
BD_DEBUG(DBG_AACS | DBG_CRIT, "aacs_get_mk() failed\n"); | |
return NULL; | |
} | |
} | |
return aacs->mk; | |
} | |
const uint8_t *aacs_get_vid(AACS *aacs) | |
{ | |
if (!memcmp(aacs->vid, empty_key, 16)) { | |
/* get cached vid */ | |
if (keycache_find("vid", aacs->disc_id, aacs->vid, 16)) { | |
BD_DEBUG(DBG_AACS, "Using cached VID\n"); | |
return aacs->vid; | |
} | |
config_file *cf = keydbcfg_config_load(NULL); | |
if (cf) { | |
_read_vid(aacs, cf->host_cert_list); | |
keydbcfg_config_file_close(cf); | |
} | |
if (!memcmp(aacs->vid, empty_key, 16)) { | |
BD_DEBUG(DBG_AACS | DBG_CRIT, "aacs_get_vid() failed\n"); | |
return NULL; | |
} | |
} | |
return aacs->vid; | |
} | |
const uint8_t *aacs_get_pmsn(AACS *aacs) | |
{ | |
if (!memcmp(aacs->pmsn, empty_key, 16)) { | |
config_file *cf = keydbcfg_config_load(NULL); | |
if (cf) { | |
_read_pmsn(aacs, cf->host_cert_list); | |
keydbcfg_config_file_close(cf); | |
} | |
if (!memcmp(aacs->pmsn, empty_key, 16)) { | |
BD_DEBUG(DBG_AACS, "aacs_get_pmsn() failed\n"); | |
return NULL; | |
} | |
} | |
return aacs->pmsn; | |
} | |
const uint8_t *aacs_get_device_binding_id(AACS *aacs) | |
{ | |
/* Device binding ID is used to encrypt online content. | |
* It needs to be cached so that downloaded content can be played later. | |
*/ | |
static const char config_file_name[] = "device_binding_id"; | |
uint32_t len = sizeof(aacs->device_binding_id); | |
BD_DEBUG(DBG_AACS, "reading device binding id\n"); | |
if (!config_get(config_file_name, &len, aacs->device_binding_id) || len != sizeof(aacs->device_binding_id)) { | |
BD_DEBUG(DBG_AACS, "creating device binding id\n"); | |
crypto_create_nonce(aacs->device_binding_id, sizeof(aacs->device_binding_id)); | |
config_save(config_file_name, aacs->device_binding_id, sizeof(aacs->device_binding_id)); | |
} | |
return aacs->device_binding_id; | |
} | |
const uint8_t *aacs_get_device_nonce(AACS *aacs) | |
{ | |
BD_DEBUG(DBG_AACS, "creating device nonce\n"); | |
crypto_create_nonce(aacs->device_nonce, sizeof(aacs->device_nonce)); | |
return aacs->device_nonce; | |
} | |
static AACS_RL_ENTRY *_get_rl(const char *type, int *num_records, int *mkbv) | |
{ | |
uint32_t len, version; | |
void *data = NULL; | |
*num_records = *mkbv = 0; | |
cache_get(type, &version, &len, NULL); | |
if (version > 0 && len > 24) { | |
data = malloc(len); | |
if (cache_get(type, &version, &len, data) && len > 24) { | |
if (_rl_verify_signature(data, len)) { | |
*mkbv = version; | |
*num_records = MKINT_BE32((uint8_t*)data + 20); | |
memmove(data, (uint8_t*)data + 24, len - 24); | |
int ii; | |
AACS_RL_ENTRY *rl = data; | |
for (ii = 0; ii < *num_records; ii++) { | |
rl[ii].range = MKINT_BE16((uint8_t*)&rl[ii].range); | |
} | |
return rl; | |
} | |
BD_DEBUG(DBG_AACS | DBG_CRIT, "invalid signature in cached %s\n", type); | |
} | |
X_FREE(data); | |
} | |
return data; | |
} | |
AACS_RL_ENTRY *aacs_get_hrl(int *num_records, int *mkbv) | |
{ | |
return _get_rl("hrl", num_records, mkbv); | |
} | |
AACS_RL_ENTRY *aacs_get_drl(int *num_records, int *mkbv) | |
{ | |
return _get_rl("drl", num_records, mkbv); | |
} | |
void aacs_free_rl(AACS_RL_ENTRY **rl) | |
{ | |
X_FREE(*rl); | |
} | |
uint32_t aacs_get_bus_encryption(AACS *aacs) | |
{ | |
return (aacs->bee * AACS_BUS_ENCRYPTION_ENABLED) | | |
(aacs->bec * AACS_BUS_ENCRYPTION_CAPABLE); | |
} | |
void aacs_select_title(AACS *aacs, uint32_t title) | |
{ | |
if (!aacs) { | |
return; | |
} | |
if (!aacs->cps_units) { | |
BD_DEBUG(DBG_AACS|DBG_CRIT, "aacs_select_title(): CPS units not read !\n"); | |
return; | |
} | |
if (title == 0xffff) { | |
/* first play */ | |
aacs->current_cps_unit = aacs->cps_units[0]; | |
aacs->cps_unit_selected = 0; | |
BD_DEBUG(DBG_AACS, "aacs_set_title(first_play): CPS unit %d\n", aacs->current_cps_unit); | |
return; | |
} | |
if (title <= aacs->num_titles) { | |
aacs->current_cps_unit = aacs->cps_units[title + 1]; | |
aacs->cps_unit_selected = 1; | |
BD_DEBUG(DBG_AACS, "aacs_set_title(%d): CPS unit %d\n", title, aacs->current_cps_unit); | |
return; | |
} | |
BD_DEBUG(DBG_AACS|DBG_CRIT, "aacs_set_title(%d): invalid title !\n", title); | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment