Petri Hintukainen pushed to branch master at VideoLAN / libaacs
Commits: c9c8ad90 by npzacs at 2020-07-10T15:17:21+03:00 Add MKB and UK file dump tools - - - - - bcd6e023 by npzacs at 2020-07-10T15:18:04+03:00 log config summary - - - - - d1735467 by npzacs at 2020-07-10T15:18:46+03:00 Extend embedded keys table - - - - - ed63fc91 by npzacs at 2020-07-10T15:19:46+03:00 Cosmetics - - - - - 48a25022 by npzacs at 2020-07-10T15:25:12+03:00 Convert unit keys in parser - - - - - 7f63a556 by npzacs at 2020-07-10T15:27:01+03:00 Remove useless terminator entry from unit key list - - - - - 1880ccc6 by npzacs at 2020-07-10T15:29:01+03:00 parser: free temporary string in parser loop Avoid pointers to freed memory - - - - - 0e591b58 by npzacs at 2020-07-10T15:49:27+03:00 parser: use static buffer for disc id and disc keys Avoid >100k malloc() + free() - - - - - e9dc7188 by npzacs at 2020-07-10T15:49:27+03:00 load only current disc keys from config - - - - - 57e3e276 by npzacs at 2020-07-10T15:49:27+03:00 parser: fix leak (ignore multiple UK entries) - - - - - e2229467 by hpi1 at 2020-07-10T15:49:27+03:00 Fix make dist (missing files) - - - - - 76453650 by hpi1 at 2020-07-10T15:49:48+03:00 strutl: Remove duplicate function - - - - - 58fb6beb by John Doe at 2020-07-10T16:07:37+03:00 Don't discard unit keys unless VUK is available Previously libaacs discarded unit keys when e.g. config file had 1 unit key and the disc had 2 or more. This is fine when we have VUK or MK+VID available. However, when VUK is not available, discarding unit key means that the disc can't be decrypted. - - - - - 13 changed files: - Makefile.am - + src/devtools/mkb_dump.c - src/devtools/parser_test.c - + src/devtools/read_file.h - + src/devtools/uk_dump.c - src/file/keydb.h - src/file/keydbcfg-parser.y - src/file/keydbcfg.c - src/file/keydbcfg.h - src/libaacs/aacs.c - src/libaacs/crypto.c - src/util/strutl.c - src/util/strutl.h Changes: ===================================== Makefile.am ===================================== @@ -1,6 +1,6 @@ ACLOCAL_AMFLAGS=-I m4 -EXTRA_DIST=bootstrap COPYING KEYDB.cfg README.txt ChangeLog +EXTRA_DIST=bootstrap COPYING KEYDB.cfg README.md ChangeLog SET_FEATURES = @SET_FEATURES@ SET_INCLUDES = -I$(top_srcdir)/src -I$(top_builddir)/src/libaacs @@ -93,7 +93,7 @@ dist-hook: # programs # -noinst_PROGRAMS = parser_test +noinst_PROGRAMS = parser_test uk_dump mkb_dump bin_PROGRAMS = aacs_info parser_test_SOURCES = \ @@ -104,6 +104,23 @@ parser_test_SOURCES = \ src/util/logging.c parser_test_CFLAGS = -std=c99 $(SET_FEATURES) $(SET_INCLUDES) +uk_dump_SOURCES = \ + src/devtools/uk_dump.c \ + src/devtools/read_file.h \ + src/libaacs/unit_key.h \ + src/libaacs/unit_key.c \ + src/util/logging.c +uk_dump_CFLAGS = -std=c99 $(SET_FEATURES) $(SET_INCLUDES) + +mkb_dump_SOURCES = \ + src/devtools/mkb_dump.c \ + src/devtools/read_file.h \ + src/libaacs/mkb.h \ + src/libaacs/mkb.c \ + src/util/strutl.c \ + src/util/logging.c +mkb_dump_CFLAGS = -std=c99 $(SET_FEATURES) $(SET_INCLUDES) + aacs_info_SOURCES = src/examples/aacs_info.c aacs_info_CFLAGS = -std=c99 $(SET_FEATURES) $(SET_INCLUDES) aacs_info_LDADD = libaacs.la ===================================== src/devtools/mkb_dump.c ===================================== @@ -0,0 +1,260 @@ +/* + * This file is part of libaacs + * Copyright (C) 2020 VideoLAN + * + * 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/>. + */ + +#include "util/strutl.h" +#include "util/macro.h" +#include "libaacs/mkb.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <inttypes.h> + +#include "read_file.h" + +static const struct { + uint8_t type; + const char *name; +} rec_names[] = { + { 0x02, "End of media key block" }, + { 0x04, "Explicit Subset-Difference" }, + { 0x05, "Media Key Data / CVALUE" }, + { 0x07, "Subset-Difference Index" }, + { 0x0c, "Media Key Variant Data" }, + { 0x10, "Type and version" }, + { 0x20, "Drive revocation list (AACS1)" }, + { 0x21, "Host revocation list (AACS1)" }, + { 0x30, "Drive revocation list (AACS2)" }, + { 0x31, "Host revocation list (AACS2)" }, + { 0x81, "Verify media key / DVALUE" }, + { 0x86, "Verify media key / DVALUE (AACS2)" }, +}; + +static const struct { + uint32_t type; + const char *name; +} mkb_types[] = { + { MKB_TYPE_3, "Type 3" }, + { MKB_TYPE_4, "Type 4" }, + { MKB_TYPE_10_CLASS_II, "Type 10, class II" }, + { MKB_20_CATEGORY_C, "Type 2.0 Category C" }, + { MKB_21_CATEGORY_C, "Type 2.1 Category C" }, +}; + +static const char *rec_name(uint8_t type) { + size_t i; + for (i = 0; i < sizeof(rec_names)/sizeof(rec_names[0]); i++) + if (rec_names[i].type == type) + return rec_names[i].name; + return "(UNKNOWN)"; +} + +static const char *type_name(uint32_t type) { + size_t i; + for (i = 0; i < sizeof(mkb_types)/sizeof(mkb_types[0]); i++) + if (mkb_types[i].type == type) + return mkb_types[i].name; + return "(UNKNOWN)"; +} + +static void _dump_signature(const uint8_t *sig, size_t len) +{ + size_t i; + + printf(" Signature (%zu bytes): ", len); + for (i = 0; i < len; i++) { + printf("%02x", sig[i]); + } + printf("\n"); +} + +static void _dump_aacs1_rl(const uint8_t *rl, size_t rl_size) +{ + if (rl_size < 4) + return; + + uint32_t total_entries = MKINT_BE32(rl); + rl += 4; rl_size -= 4; + + while (total_entries > 0 && rl_size >= 4) { + uint32_t entries = MKINT_BE32(rl); + rl += 4; rl_size -= 4; + if (entries >= (0xffffffff - 8 - 40) / 8) { + printf(" invalid revocation list\n"); + break; + } + + size_t rec_len = 8 * entries + 40; + if (rec_len > rl_size) { + printf(" revocation list size mismatch\n"); + break; + } + + unsigned ii; + for (ii = 0; ii < entries; ii++) { + uint16_t len = MKINT_BE16(rl); + uint64_t id = MKINT_BE48(rl + 2); + printf(" %3d: id %12" PRIx64, ii, id); + if (len) { + printf("-%12" PRIx64, id + len); + } + printf("\n"); + rl += 8; rl_size -= 8; + } + _dump_signature(rl, 40); + rl += 40; rl_size -= 40; + total_entries -= entries; + } +} + +static void _dump_type_and_version(const uint8_t *p, size_t size) +{ + if (size >= 4) + printf(" MKB type: 0x%08x (%s)\n", MKINT_BE32(p), type_name(MKINT_BE32(p))); + if (size >= 8) + printf(" MKB version: %8d\n", MKINT_BE32(p+4)); +} + +static void _dump_record(MKB *mkb, int record) +{ + size_t size = mkb_data_size(mkb); + const uint8_t *data = mkb_data(mkb); + size_t pos, len; + + printf("\nRecord 0x%02x (%s):\n", record, rec_name(record)); + + for (pos = 0; pos + 4 <= size; pos += len) { + uint8_t type = data[pos]; + len = MKINT_BE24(data + pos + 1); + if (type == record) { + switch (record) { + case 0x02: _dump_signature(data + pos + 4, len - 4); break; + case 0x10: _dump_type_and_version(data + pos + 4, len - 4); break; + case 0x20: + case 0x21: _dump_aacs1_rl(data + pos + 4, len - 4); break; + } + printf(" Raw data (%zu bytes):\n", len - 4); + const uint8_t *p = data + pos + 4; + const uint8_t *e = data + pos + len; + while (p < e) { + size_t i; + printf(" "); + for (i = 0; i < 8 && p < e; i++, p++) + printf("%02x ", *p); + printf(" "); + for (i = 0; i < 8 && p < e; i++, p++) + printf("%02x ", *p); + printf("\n"); + } + break; + } + } +} + +static void _list_missing_records(const uint8_t *seen_map) +{ + int type; + + for (type = 0; type < 256; type++) { + if (!seen_map[type]) { + size_t i; + for (i = 0; i < sizeof(rec_names)/sizeof(rec_names[0]); i++) { + if (rec_names[i].type == type) { + printf(" %s\n", rec_names[i].name); + } + } + } + } +} + +static void _list_records(MKB *mkb, uint8_t *seen_map) +{ + const uint8_t *data = mkb_data(mkb); + size_t size = mkb_data_size(mkb); + size_t pos, len; + + for (pos = 0; pos + 4 <= size; pos += len) { + uint8_t type = data[pos]; + len = MKINT_BE24(data + pos + 1); + printf(" record 0x%02x: %10zu bytes %s\n", type, len, rec_name(type)); + seen_map[type] = 1; + if (len == 0) { + printf(" UNKNOWN: %10zu bytes\n", size - pos); + break; + } + } +} + +int main (int argc, char **argv) +{ + MKB *mkb; + uint8_t seen_map[256] = {0}; + uint8_t *data; + size_t size; + int arg; + + if (argc < 2) { + fprintf(stderr, "usage: mkb_dump <mkb_file> [record [record ...]]\n"); + exit(-1); + } + + size = _read_file(argv[1], 16, 1024*1024*1024, &data); + if (!size) { + exit(-1); + } + + printf("MKB file size: %10zu bytes\n", size); + + mkb = mkb_init(data, size); + if (!mkb) { + fprintf(stderr, "MKB init failed: %s (%d)\n", strerror(errno), errno); + free(data); + exit(-1); + } + + printf("MKB size: %10zu bytes\n\n", mkb_data_size(mkb)); + + printf("MKB type: %s (0x%08x)\n", type_name(mkb_type(mkb)), mkb_type(mkb)); + printf("MKB version: %d\n", mkb_version(mkb)); + + printf("MKB records:\n"); + _list_records(mkb, seen_map); + printf(" PADDING: %10zu bytes\n", size - mkb_data_size(mkb)); + + printf("\nNot present records:\n"); + _list_missing_records(seen_map); + + for (arg = 2; arg < argc; arg++) { + int record = strtol(argv[arg], NULL, 16); + _dump_record(mkb, record); + } + + /* check padding */ + for (size_t i = mkb_data_size(mkb); i < size; i++) { + if (data[i]) { + printf("\nData found from padding !\n"); + break; + } + } + + mkb_close(mkb); + + return 0; +} ===================================== src/devtools/parser_test.c ===================================== @@ -27,9 +27,13 @@ static int print_digit_key_pair_enties(digit_key_pair_list *list); static int print_title_entries(title_entry_list *list); +static const uint8_t empty_key[16] = {0}; + /* Function to print the entres in a digit key pair list */ static int print_digit_key_pair_enties(digit_key_pair_list *list) { + char tmp[256]; + if (!list) { printf("Error: No digit key pair list passed as parameter.\n"); @@ -39,10 +43,7 @@ static int print_digit_key_pair_enties(digit_key_pair_list *list) digit_key_pair_list *cursor = list; while (cursor) { - if (!cursor->key_pair.key) - break; - - printf(" %u - %s\n", cursor->key_pair.digit, cursor->key_pair.key); + printf(" %u - %s\n", cursor->key_pair.digit, str_print_hex(tmp, cursor->key_pair.key, 16)); cursor = cursor->next; } @@ -53,7 +54,6 @@ static int print_digit_key_pair_enties(digit_key_pair_list *list) /* Function that prints all entries parsed from a config file */ static int print_title_entries(title_entry_list *list) { - static const uint8_t empty_key[16] = {0}; char tmp[256]; if (!list) @@ -166,15 +166,35 @@ static int print_config_file(config_file *cfgfile) /* main */ int main (int argc, char **argv) { - if (argc != 2) { - fprintf(stderr, "usage: parser_test [config_file]\n"); + config_file *cfgfile; + uint8_t disc_id[20] = {0}; + const uint8_t *want_disc_id = NULL; + int retval = 0; + + if (argc < 2 || argc > 3) { + fprintf(stderr, "usage: parser_test [config_file [disc_id]]\n"); return EXIT_FAILURE; } - config_file *cfgfile = keydbcfg_new_config_file(); - int retval = keydbcfg_parse_config(cfgfile, argv[1]); - retval &= print_config_file(cfgfile); - keydbcfg_config_file_close(cfgfile); + if (argc > 2) { + if (strlen(argv[2]) != 40) { + fprintf(stderr, "disc id must be 40 chars long\n"); + return EXIT_FAILURE; + } + if (!hexstring_to_hex_array(disc_id, 20, argv[2])) { + fprintf(stderr, "invalid disc id %s\n", argv[2]); + return EXIT_FAILURE; + } + want_disc_id = disc_id; + } + + + cfgfile = keydbcfg_new_config_file(); + if (cfgfile) { + retval = keydbcfg_parse_config(cfgfile, argv[1], want_disc_id, want_disc_id == NULL); + retval &= print_config_file(cfgfile); + keydbcfg_config_file_close(cfgfile); + } if (!retval) return EXIT_FAILURE; ===================================== src/devtools/read_file.h ===================================== @@ -0,0 +1,77 @@ +/* + * This file is part of libaacs + * Copyright (C) 2020 VideoLAN + * + * 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/>. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + +static size_t _read_file(const char *name, off_t min_size, off_t max_size, uint8_t **pdata) +{ + FILE *f; + uint8_t *data = NULL; + off_t file_size; + size_t size; + + f = fopen(name, "rb"); + if (!f) { + fprintf(stderr, "error opening '%s': %s (%d)\n", name, strerror(errno), errno); + return 0; + } + + if (fseek(f, 0, SEEK_END) < 0) { + fprintf(stderr, "error seeking to end: %s (%d)\n", strerror(errno), errno); + goto fail; + } + + file_size = ftell(f); + + if (file_size < 0) { + fprintf(stderr, "error getting file size: %s (%d)\n", strerror(errno), errno); + goto fail; + } + if (file_size < min_size || file_size > max_size) { + fprintf(stderr, "weird file size: %lld\n", (long long)file_size); + goto fail; + } + if (fseek(f, 0, SEEK_SET) < 0) { + fprintf(stderr, "error seeking to start: %s (%d)\n", strerror(errno), errno); + goto fail; + } + + size = (size_t)file_size; + + data = malloc(size); + if (!data) { + fprintf(stderr, "error allocating %zu bytes: %s (%d)\n", size, strerror(errno), errno); + goto fail; + } + if (fread(data, size, 1, f) != 1) { + fprintf(stderr, "error reading %zu bytes: %s (%d)\n", size, strerror(errno), errno); + goto fail; + } + fclose(f); + + *pdata = data; + return size; + + fail: + free(data); + fclose(f); + return 0; +} ===================================== src/devtools/uk_dump.c ===================================== @@ -0,0 +1,113 @@ +/* + * This file is part of libaacs + * Copyright (C) 2020 VideoLAN + * + * 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/>. + */ + +#include "util/strutl.h" +#include "util/macro.h" +#include "libaacs/unit_key.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "read_file.h" + +static void _uk_dump(AACS_UK *uk) +{ + unsigned i, j; + + printf("Num BDMV dir: %d\n", uk->num_bdmv_dir); + printf("Application type: %d\n", uk->app_type); + printf("Use_SKB_Unified_MKB_Flag: %d\n", uk->use_skb_mkb); + + printf("Encrypted unit keys: %u\n", uk->num_uk); + for (i = 0; i < uk->num_uk; i++) { + printf(" %u: ", i + 1); + for (j = 0; j < 16; j++) { + printf("%02x", uk->enc_uk[i].key[j]); + } + printf("\n"); + } + + printf("Title CPS units (%d titles):\n", uk->num_titles); + if (uk->num_titles > 0) + printf(" FP: %u\n", uk->title_cps_unit[0]); + if (uk->num_titles > 1) + printf(" TM: %u\n", uk->title_cps_unit[1]); + for (i = 0; i < uk->num_titles; i++) + printf(" %2u: %u\n", i + 1, uk->title_cps_unit[i + 2]); +} + +int main (int argc, char **argv) +{ + AACS_UK *uk; + uint8_t *data; + size_t size; + int aacs2 = argc > 2; + size_t l, b; + + if (argc < 2) { + fprintf(stderr, "usage: uk_dump <Unit_Key_RO.inf> [AACS2]\n"); + exit(-1); + } + + size = _read_file(argv[1], 16, 1024*1024, &data); + if (!size) { + exit(-1); + } + + printf("Unit key file size: %8zu bytes\n", size); + uk = uk_parse(data, size, aacs2); + + if (!uk) { + fprintf(stderr, "Parsing failed\n"); + exit(-1); + } + + _uk_dump(uk); + + printf("Raw data:\n"); + uint32_t mask = 0; + for (l = 0; l < size; l += 16) { + mask <<= 8; + for (b = 0; b < 32 && l+b < size; b++) + mask |= data[l + b]; + if (mask) { + printf("%04lx: ", (long)l); + + for (b = 0; b < 8 && l+b < size; b++) + printf("%02x ", data[l + b]); + printf(" "); + for (b = 8; b < 16 && l+b < size; b++) + printf("%02x ", data[l + b]); + printf(" "); + for (b = 0; b < 8 && l+b < size; b++) + if (data[l+b]) printf("%02x ", data[l + b]); else printf(" "); + printf(" "); + for (b = 8; b < 16 && l+b < size; b++) + if (data[l+b]) printf("%02x ", data[l + b]); else printf(" "); + + printf("\n"); + } + } + + uk_free(&uk); + free(data); + + return 0; +} ===================================== src/file/keydb.h ===================================== @@ -1,8 +1,6 @@ /* encrypted keys */ -static const uint32_t internal_device_number = 0; - static const uint8_t internal_dk_list[][21] = { { }, ===================================== src/file/keydbcfg-parser.y ===================================== @@ -1,5 +1,22 @@ %code requires { #include "file/keydbcfg.h" + +#define MAX_KEY_SIZE 128 + +typedef struct { + title_entry_list *celist; /* current disc entry or NULL */ + digit_key_pair_list *dkplist; /* current list */ + + const uint64_t *want_disc_id; /* parse only this disc (none if NULL) */ + int all_discs; /* parse entries for all discs */ + + size_t hexkey_size; + union { /* make sure we're properly aligned */ + char b[MAX_KEY_SIZE]; + uint64_t u64[5]; + } hexkey; +} parser_state; + } %code { @@ -23,6 +40,7 @@ */ #include "util/macro.h" +#include "util/strutl.h" #include <stdio.h> #include <stdlib.h> @@ -48,7 +66,6 @@ while (X) \ { \ digit_key_pair_list *pnext = X->next;\ - X_FREE(X->key_pair.key); \ X_FREE(X); \ X = pnext; \ } \ @@ -78,19 +95,29 @@ static void add_pk_entry(config_file *cf, char *key); static void add_cert_entry(config_file *cf, char *host_priv_key, char *host_cert); static title_entry_list *new_title_entry_list(void); -static int add_entry(title_entry_list *list, int type, char *entry); -static digit_key_pair_list *new_digit_key_pair_list(void); -static digit_key_pair_list *add_digit_key_pair_entry(digit_key_pair_list *list, - int type, unsigned int digit, char *key); +static int add_entry(title_entry_list *list, int type, const char *entry); +static digit_key_pair_list *new_digit_key_pair_entry(int type, unsigned int digit, const char *key); /* static int add_date_entry(title_entry_list *list, unsigned int year, unsigned int month, unsigned int day); */ -void yyerror (void *scanner, config_file *cf, - title_entry_list *celist, digit_key_pair_list *dkplist, - const char *msg); +void yyerror (void *scanner, config_file *cf, parser_state *ps, const char *msg); extern int libaacs_yyget_lineno (void *scanner); +static inline int _discid_cmp(const uint64_t *want_disc_id, const uint64_t *disc_id) +{ + unsigned i; + + /* We know input strings are valid hex strings, len 40: + * want_disc_id was created from binary data, disc_id was checked by lexer and parser. + * -> we just need to make sure all letters are lower case (= bit 0x20 set) + */ + for (i = 0; i < 40/sizeof(uint64_t); i++) + if (want_disc_id[i] != (disc_id[i] | UINT64_C(0x2020202020202020))) + return 0; + return 1; +} + /* uncomment the line below for debugging */ // int yydebug = 1; } @@ -105,8 +132,7 @@ extern int libaacs_yyget_lineno (void *scanner); %lex-param{void *scanner} %parse-param{void *scanner} %parse-param{config_file *cf} -%parse-param{title_entry_list *celist} -%parse-param{digit_key_pair_list *dkplist} +%parse-param{parser_state *ps} %union { @@ -147,7 +173,7 @@ extern int libaacs_yyget_lineno (void *scanner); %token BAD_ENTRY %type <string> discid disc_title -%type <string> host_priv_key host_cert host_nonce host_key_point hexstring_list +%type <string> host_priv_key host_cert host_nonce host_key_point hexstring_list hexkey %type <string> device_key device_node key_uv key_u_mask_shift %% config_file @@ -283,20 +309,24 @@ newline_list disc_info : discid PUNCT_EQUALS_SIGN disc_title { - if (!cf->list) { - celist = cf->list = new_title_entry_list(); + if (ps->hexkey_size != 40) { + fprintf(stderr, "Ignoring invalid disc id: %s (len = %zu)\n", $1, ps->hexkey_size); + ps->celist = NULL; + } else if (!ps->all_discs && (!ps->want_disc_id || !_discid_cmp(ps->want_disc_id, ps->hexkey.u64))) { + ps->celist = NULL; /* ignore this disc */ } else { - for (; celist->next; celist = celist->next) ; - celist->next = new_title_entry_list(); - celist = celist->next; + ps->celist = new_title_entry_list(); + if (ps->celist) { + ps->celist->next = cf->list; + cf->list = ps->celist; + hexstring_to_hex_array(ps->celist->entry.discid, 20, ps->hexkey.b); + } } - add_entry(celist, ENTRY_TYPE_DISCID, $1); - /*add_entry(celist, ENTRY_TYPE_TITLE, $3);*/ } ; discid - : hexstring_list + : hexkey ; disc_title @@ -329,23 +359,23 @@ date_entry ; mek_entry - : ENTRY_ID_MEK hexstring_list + : ENTRY_ID_MEK hexkey { - add_entry(celist, ENTRY_TYPE_MEK, $2); + add_entry(ps->celist, ENTRY_TYPE_MEK, $2); } ; vid_entry - : ENTRY_ID_VID hexstring_list + : ENTRY_ID_VID hexkey { - add_entry(celist, ENTRY_TYPE_VID, $2); + add_entry(ps->celist, ENTRY_TYPE_VID, $2); } ; bn_entry : ENTRY_ID_BN bn_data_list { - dkplist = NULL; + ps->dkplist = NULL; } ; @@ -369,16 +399,16 @@ bn_data ; vuk_entry - : ENTRY_ID_VUK hexstring_list + : ENTRY_ID_VUK hexkey { - add_entry(celist, ENTRY_TYPE_VUK, $2); + add_entry(ps->celist, ENTRY_TYPE_VUK, $2); } ; pak_entry : ENTRY_ID_PAK pak_data_list { - dkplist = NULL; + ps->dkplist = NULL; } ; @@ -404,7 +434,7 @@ pak_data tk_entry : ENTRY_ID_TK tk_data_list { - dkplist = NULL; + ps->dkplist = NULL; } ; @@ -430,7 +460,7 @@ tk_data uk_entry : ENTRY_ID_UK uk_data_list { - dkplist = NULL; + ps->dkplist = NULL; } ; @@ -440,17 +470,54 @@ uk_data_list ; uk_data - : DIGIT PUNCT_HYPHEN hexstring_list + : DIGIT PUNCT_HYPHEN hexkey { - if (!dkplist) + if (ps->celist) { + if (!ps->dkplist) { - dkplist = new_digit_key_pair_list(); - celist->entry.uk = dkplist; + if (ps->celist->entry.uk) { + /* duplicate entry */ + char disc_id[41]; + fprintf(stderr, "Ignoring duplicate unit key entry for %s\n", + str_print_hex(disc_id, ps->celist->entry.discid, 20)); + } else { + ps->dkplist = new_digit_key_pair_entry(ENTRY_TYPE_UK, $1, $3); + ps->celist->entry.uk = ps->dkplist; + } + } else { + ps->dkplist->next = new_digit_key_pair_entry(ENTRY_TYPE_UK, $1, $3); + if (ps->dkplist->next) + ps->dkplist = ps->dkplist->next; + } } - dkplist = add_digit_key_pair_entry(dkplist, ENTRY_TYPE_UK, $1, $3); } ; +hexkey + : hexkey HEXSTRING + { + size_t len = strlen($2); + if (ps->hexkey_size + len >= sizeof(ps->hexkey.b)) { + fprintf(stderr, "too long key: %s %s\n", ps->hexkey.b, $2); + } else { + memcpy(ps->hexkey.b + ps->hexkey_size, $2, len + 1); + ps->hexkey_size += len; + } + $$ = ps->hexkey.b; + } + | HEXSTRING + { + size_t len = strlen($1); + if (len >= sizeof(ps->hexkey.b)) { + fprintf(stderr, "too long key: %s\n", $1); + ps->hexkey.b[0] = 0; + ps->hexkey_size = 0; + } else { + memcpy(ps->hexkey.b, $1, len + 1); + ps->hexkey_size = len; + } + $$ = ps->hexkey.b; + } hexstring_list : hexstring_list HEXSTRING { @@ -473,8 +540,22 @@ hexstring_list ; %% /* Function to parse a config file */ -int keydbcfg_parse_config(config_file *cfgfile, const char *path) +int keydbcfg_parse_config(config_file *cfgfile, const char *path, const uint8_t *disc_id, int all_discs) { + union { /* make sure we're properly aligned */ + uint64_t u64[5]; + char b[41]; + } want_disc_id; + + parser_state ps = { + .celist = NULL, + .dkplist = NULL, + .want_disc_id = NULL, + .all_discs = all_discs, + .hexkey_size = 0, + .hexkey.b = "", + }; + if (!cfgfile || !path) return 0; @@ -490,10 +571,14 @@ int keydbcfg_parse_config(config_file *cfgfile, const char *path) if (!fp) return 0; + if (disc_id) { + str_print_hex(want_disc_id.b, disc_id, 20); + ps.want_disc_id = want_disc_id.u64; + } void *scanner; libaacs_yylex_init(&scanner); libaacs_yyset_in(fp, scanner); - int retval = yyparse(scanner, cfgfile, cfgfile->list, NULL); + int retval = yyparse(scanner, cfgfile, &ps); libaacs_yylex_destroy(scanner); fclose(fp); @@ -697,16 +782,14 @@ title_entry_list *new_title_entry_list(void) #define CHECK_KEY_LENGTH(name, len) \ if (!entry || strlen(entry) != len) { \ fprintf(stderr, "Ignoring bad "name" entry %s\n", entry); \ - X_FREE(entry); \ break; \ } /* Function to add standard string entries to a config entry */ -static int add_entry(title_entry_list *list, int type, char *entry) +static int add_entry(title_entry_list *list, int type, const char *entry) { if (!list) { - fprintf(stderr, "Error: No title list passed as parameter.\n"); return 0; } @@ -715,7 +798,6 @@ static int add_entry(title_entry_list *list, int type, char *entry) case ENTRY_TYPE_DISCID: CHECK_KEY_LENGTH("discid", 40) hexstring_to_hex_array(list->entry.discid, 20, entry); - X_FREE(entry); break; #if 0 @@ -728,23 +810,19 @@ static int add_entry(title_entry_list *list, int type, char *entry) case ENTRY_TYPE_MEK: CHECK_KEY_LENGTH("mek", 32) hexstring_to_hex_array(list->entry.mk, 16, entry); - X_FREE(entry); break; case ENTRY_TYPE_VID: CHECK_KEY_LENGTH("vid", 32) hexstring_to_hex_array(list->entry.vid, 16, entry); - X_FREE(entry); break; case ENTRY_TYPE_VUK: CHECK_KEY_LENGTH("vuk", 32) hexstring_to_hex_array(list->entry.vuk, 16, entry); - X_FREE(entry); break; default: - X_FREE(entry); fprintf(stderr, "WARNING: entry type passed in unknown\n"); return 0; } @@ -752,32 +830,25 @@ static int add_entry(title_entry_list *list, int type, char *entry) return 1; } -/* Function that returns pointer to new digit key pair list */ -static digit_key_pair_list *new_digit_key_pair_list(void) +/* Function used to add a digit/key pair to a list of digit key pair entries */ +static digit_key_pair_list *new_digit_key_pair_entry(int type, unsigned int digit, const char *key) { - digit_key_pair_list *list = (digit_key_pair_list *)calloc(1, sizeof(*list)); - if (!list) { - fprintf(stderr, "Error allocating memory for new digit key pair entry list!\n"); + digit_key_pair_list *list; + + if (!key || strlen(key) != 32) { + fprintf(stderr, "Ignoring bad UK entry %s\n", key ? key : "<null>"); + return NULL; } - return list; -} -/* Function used to add a digit/key pair to a list of digit key pair entries */ -static digit_key_pair_list *add_digit_key_pair_entry(digit_key_pair_list *list, - int type, unsigned int digit, char *key) -{ - if (!list) - { - fprintf(stderr, "Error: No digit key pair list passed as parameter.\n"); + list = (digit_key_pair_list *)calloc(1, sizeof(*list)); + if (!list) { + fprintf(stderr, "Error allocating memory for new digit key pair entry list!\n"); return NULL; } list->key_pair.digit = digit; - list->key_pair.key = key; - - list->next = new_digit_key_pair_list(); - - return list->next; + hexstring_to_hex_array(list->key_pair.key, 16, key); + return list; } /* Function to add a date entry */ @@ -800,9 +871,7 @@ static int add_date_entry(title_entry_list *list, unsigned int year, #endif /* Our definition of yyerror */ -void yyerror (void *scanner, config_file *cf, - title_entry_list *celist, digit_key_pair_list *dkplist, - const char *msg) +void yyerror (void *scanner, config_file *cf, parser_state *ps, const char *msg) { fprintf(stderr, "%s: line %d\n", msg, libaacs_yyget_lineno(scanner)); } ===================================== src/file/keydbcfg.c ===================================== @@ -313,7 +313,7 @@ static char *_keycache_file(const char *type, const uint8_t *disc_id) return NULL; } - hex_array_to_hexstring(disc_id_str, disc_id, 20); + str_print_hex(disc_id_str, disc_id, 20); result = str_printf("%s"DIR_SEP"%s"DIR_SEP"%s"DIR_SEP"%s", cache_dir, CFG_DIR, type, disc_id_str); X_FREE(cache_dir); @@ -331,7 +331,7 @@ int keycache_save(const char *type, const uint8_t *disc_id, const uint8_t *key, AACS_FILE_H *fp = file_open(file, "w"); if (fp) { - hex_array_to_hexstring(key_str, key, len); + str_print_hex(key_str, key, len); if (file_write(fp, key_str, len*2) == len*2) { BD_DEBUG(DBG_FILE, "Wrote %s to %s\n", type, file); @@ -547,7 +547,7 @@ int config_get(const char *name, uint32_t *len, void *buf) } -static int _load_config_file(config_file *cf, int system) +static int _load_config_file(config_file *cf, int system, const uint8_t *disc_id) { static const char cfg_file_name[] = CFG_FILE_NAME; @@ -565,7 +565,7 @@ static int _load_config_file(config_file *cf, int system) BD_DEBUG(DBG_FILE, "found config file: %s\n", cfg_file); file_close(fp); - result = keydbcfg_parse_config(cf, cfg_file); + result = keydbcfg_parse_config(cf, cfg_file, disc_id, 0); } X_FREE(cfg_file); @@ -588,9 +588,9 @@ static int _parse_embedded(config_file *cf) break; decrypt_key(e->key, internal_dk_list[jj], 16); - e->node = internal_device_number; - e->uv = MKINT_BE32(internal_dk_list[jj] + 16); - e->u_mask_shift = internal_dk_list[jj][20]; + e->node = MKINT_BE16(internal_dk_list[jj] + 16); + e->uv = MKINT_BE32(internal_dk_list[jj] + 16 + 2); + e->u_mask_shift = internal_dk_list[jj][20 + 2]; if (!e->uv || _is_duplicate_dk(cf->dkl, e)) { X_FREE(e); @@ -646,7 +646,26 @@ static int _parse_embedded(config_file *cf) return result; } -config_file *keydbcfg_config_load(const char *configfile_path) +static void _config_summary(config_file *cf) +{ + int n; + dk_list *dkl = cf->dkl; + pk_list *pkl = cf->pkl; + cert_list *hcl = cf->host_cert_list; + title_entry_list *tel = cf->list; + + BD_DEBUG(DBG_AACS, "Config summary:\n"); + for (n = 0; dkl; dkl = dkl->next, n++) ; + BD_DEBUG(DBG_AACS, " %d Device keys\n", n); + for (n = 0; pkl; pkl = pkl->next, n++) ; + BD_DEBUG(DBG_AACS, " %d Processing keys\n", n); + for (n = 0; hcl; hcl = hcl->next, n++) ; + BD_DEBUG(DBG_AACS, " %d Host certificates\n", n); + for (n = 0; tel; tel = tel->next, n++) ; + BD_DEBUG(DBG_AACS, " %d Disc entries\n", n); +} + +config_file *keydbcfg_config_load(const char *configfile_path, const uint8_t *disc_id) { int config_ok = 0; @@ -658,14 +677,14 @@ config_file *keydbcfg_config_load(const char *configfile_path) /* try to load KEYDB.cfg */ if (configfile_path) { - config_ok = keydbcfg_parse_config(cf, configfile_path); + config_ok = keydbcfg_parse_config(cf, configfile_path, disc_id, 0); } else { /* If no configfile path given, check for config files in user's home and * under /etc. */ - config_ok = _load_config_file(cf, 0); - config_ok = _load_config_file(cf, 1) || config_ok; + config_ok = _load_config_file(cf, 0, disc_id); + config_ok = _load_config_file(cf, 1, disc_id) || config_ok; } /* Try to load simple (aacskeys) config files */ @@ -682,6 +701,8 @@ config_file *keydbcfg_config_load(const char *configfile_path) return NULL; } + _config_summary(cf); + return cf; } ===================================== src/file/keydbcfg.h ===================================== @@ -30,7 +30,7 @@ typedef struct digit_key_pair_t digit_key_pair; struct digit_key_pair_t { unsigned int digit; - char *key; + uint8_t key[16]; }; /* list of digit_key_pair struct used in title entry */ @@ -116,14 +116,13 @@ struct config_file_t title_entry_list *list; }; -/* Functions used throughout the parser */ -BD_PRIVATE int keydbcfg_parse_config(config_file *cfgfile, const char *path); +BD_PRIVATE int keydbcfg_parse_config(config_file *cfgfile, const char *path, const uint8_t *disc_id, int all_discs); BD_PRIVATE config_file *keydbcfg_new_config_file(void); BD_PRIVATE int keydbcfg_config_file_close(config_file *cfgfile); /* */ -BD_PRIVATE config_file *keydbcfg_config_load(const char *configfile_path); +BD_PRIVATE config_file *keydbcfg_config_load(const char *configfile_path, const uint8_t *disc_id); BD_PRIVATE int keycache_save(const char *type, const uint8_t *disc_id, const uint8_t *key, unsigned int len); ===================================== src/libaacs/aacs.c ===================================== @@ -829,9 +829,10 @@ static int _calc_vuk(AACS *aacs, uint8_t *mk, uint8_t *vuk, config_file *cf) } /* Function that collects keys from keydb config entry */ -static void _find_config_entry(AACS *aacs, title_entry_list *ce, +static void _find_config_entry(AACS *aacs, config_file *cf, uint8_t *mk, uint8_t *vuk) { + title_entry_list *ce = cf->list; char str[48]; char str2[48]; @@ -876,20 +877,29 @@ static void _find_config_entry(AACS *aacs, title_entry_list *ce, /* count keys */ unsigned num_uks = 0; digit_key_pair_list *ukcursor = ce->entry.uk; - while (ukcursor && ukcursor->key_pair.key) { + while (ukcursor) { num_uks++; ukcursor = ukcursor->next; } - /* check against Unit_Key_RO.inf */ + /* check against Unit_Key_RO.inf, only discard incorrect amount of UKs if VUK or MK+VID is available */ if (num_uks != aacs->uk->num_uk) { - BD_DEBUG(DBG_CRIT | DBG_AACS, "Ignoring unit keys from config file (expected %u keys, found %u)\n", + if (_calc_vuk(aacs, mk, vuk, cf) == AACS_SUCCESS) { + BD_DEBUG(DBG_CRIT | DBG_AACS, "Ignoring unit keys from config file (expected %u keys, found %u)\n", + aacs->uk->num_uk, num_uks); + return; + } + BD_DEBUG(DBG_CRIT | DBG_AACS, "Incomplete unit keys in config file (expected %u keys, found %u)\n", aacs->uk->num_uk, num_uks); - return; } /* get keys */ + if (num_uks > aacs->uk->num_uk) { + /* config file has more unit keys than the disc has */ + aacs->uk->num_uk = num_uks; + } + aacs->uk->uk = calloc(aacs->uk->num_uk, sizeof(aacs->uk->uk[0])); if (!aacs->uk->uk) { return; @@ -897,8 +907,8 @@ static void _find_config_entry(AACS *aacs, title_entry_list *ce, num_uks = 0; ukcursor = ce->entry.uk; - while (ukcursor && ukcursor->key_pair.key) { - hexstring_to_hex_array(aacs->uk->uk[num_uks].key, 16, ukcursor->key_pair.key); + while (ukcursor) { + memcpy(aacs->uk->uk[num_uks].key, ukcursor->key_pair.key, 16); BD_DEBUG(DBG_AACS, "Unit key %u from keydb entry: %s\n", num_uks, @@ -992,19 +1002,18 @@ static int _calc_uks(AACS *aacs, config_file *cf) vuk_error_code = _calc_vuk(aacs, mk, vuk, NULL); if (vuk_error_code != AACS_SUCCESS) { - if (cf) { - BD_DEBUG(DBG_AACS, "Searching for keydb config entry...\n"); - _find_config_entry(aacs, cf->list, mk, vuk); + if (cf) { + BD_DEBUG(DBG_AACS, "Searching for keydb config entry...\n"); + _find_config_entry(aacs, cf, mk, vuk); - /* Skip if retrieved from config file */ - if (aacs->uk->uk) { - return AACS_SUCCESS; + /* Skip if retrieved from config file */ + if (aacs->uk->uk) { + return AACS_SUCCESS; + } } - } - - /* Try to calculate VUK */ - vuk_error_code = _calc_vuk(aacs, mk, vuk, cf); + /* Try to calculate VUK */ + vuk_error_code = _calc_vuk(aacs, mk, vuk, cf); } BD_DEBUG(DBG_AACS, "Calculate CPS unit keys...\n"); @@ -1307,7 +1316,7 @@ int aacs_open_device(AACS *aacs, const char *path, const char *configfile_path) return error_code; } - cf = keydbcfg_config_load(configfile_path); + cf = keydbcfg_config_load(configfile_path, aacs->disc_id); BD_DEBUG(DBG_AACS, "Starting AACS waterfall...\n"); error_code = _calc_uks(aacs, cf); @@ -1461,7 +1470,7 @@ const uint8_t *aacs_get_bdj_root_cert_hash(AACS *aacs) const uint8_t *aacs_get_mk(AACS *aacs) { if (!memcmp(aacs->mk, empty_key, sizeof(aacs->mk))) { - config_file *cf = keydbcfg_config_load(NULL); + config_file *cf = keydbcfg_config_load(NULL, NULL); if (cf) { uint8_t mk[16] = {0}; if (_calc_mk(aacs, mk, cf->pkl, cf->dkl) == AACS_SUCCESS) { @@ -1489,7 +1498,7 @@ const uint8_t *aacs_get_vid(AACS *aacs) return aacs->vid; } - config_file *cf = keydbcfg_config_load(NULL); + config_file *cf = keydbcfg_config_load(NULL, NULL); if (cf) { _read_vid(aacs, cf->host_cert_list); @@ -1508,7 +1517,7 @@ const uint8_t *aacs_get_vid(AACS *aacs) const uint8_t *aacs_get_pmsn(AACS *aacs) { if (!memcmp(aacs->pmsn, empty_key, sizeof(aacs->pmsn))) { - config_file *cf = keydbcfg_config_load(NULL); + config_file *cf = keydbcfg_config_load(NULL, NULL); if (cf) { _read_pmsn(aacs, cf->host_cert_list); ===================================== src/libaacs/crypto.c ===================================== @@ -325,7 +325,7 @@ static gcry_error_t _aacs_sexp_key(gcry_sexp_t *p_sexp_key, /* Points are currently only supported in standard format, so get a * hexstring out of Q. */ - hex_array_to_hexstring(str_Q, Q, 1 + 2*key_len); + str_print_hex(str_Q, Q, 1 + 2*key_len); char *strfmt = str_printf( "(%s" ===================================== src/util/strutl.c ===================================== @@ -141,20 +141,6 @@ int hexstring_to_hex_array(uint8_t *hex_array, uint32_t size, return 1; } -/* Function to convert a hex array into a hex string. - * str must be allocated by caller - * size is the size of the hex_array - */ -void hex_array_to_hexstring(char *str, const uint8_t *hex_array, uint32_t size) -{ - unsigned int i; - - for (i = 0; i < size; i++) - { - sprintf(str + (i*2), "%02x", hex_array[i]); - } -} - char *str_dup(const char *str) { char *dup = NULL; ===================================== src/util/strutl.h ===================================== @@ -26,7 +26,6 @@ BD_PRIVATE int hexstring_to_hex_array(uint8_t *hex_array, uint32_t size, const char *hexstring); -BD_PRIVATE void hex_array_to_hexstring(char *str, const uint8_t *hex_array, uint32_t size); BD_PRIVATE char * str_dup(const char *str) BD_ATTR_MALLOC; BD_PRIVATE char * str_printf(const char *fmt, ...) BD_ATTR_FORMAT_PRINTF(1,2) BD_ATTR_MALLOC; View it on GitLab: https://code.videolan.org/videolan/libaacs/-/compare/7eab17148dfc5932cbd99457295355c40a6d1668...58fb6beb63f0097f04f9a3a48d84b691bece2df4 -- View it on GitLab: https://code.videolan.org/videolan/libaacs/-/compare/7eab17148dfc5932cbd99457295355c40a6d1668...58fb6beb63f0097f04f9a3a48d84b691bece2df4 You're receiving this email because of your account on code.videolan.org.
_______________________________________________ libaacs-devel mailing list libaacs-devel@videolan.org https://mailman.videolan.org/listinfo/libaacs-devel