/*
* This file is part of libaacs
* Copyright (C) 2010-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
* .
*/
#include "keydbcfg.h"
#include "dirs.h"
#include "file.h"
#include "util/strutl.h"
#include "util/logging.h"
#include "util/macro.h"
#include
#include
#include
#include
#include
#define CFG_DIR "aacs"
#define CFG_FILE_NAME "KEYDB.cfg"
#define CERT_FILE_NAME "HostKeyCertificate.txt"
#define PK_FILE_NAME "ProcessingDeviceKeysSimple.txt"
#define MIN_FILE_SIZE 20
#define MAX_FILE_SIZE 65535
static int _mkpath(const char *path)
{
struct stat s;
int result = 1;
char *dir = str_dup(path);
char *end = dir;
if (!dir) {
return -1;
}
#ifdef _WIN32
end += 2; /* skip drive */
#endif
while (*end == DIR_SEP_CHAR)
end++;
while ((end = strchr(end, DIR_SEP_CHAR))) {
*end = 0;
if (stat(dir, &s) != 0 || !S_ISDIR(s.st_mode)) {
BD_DEBUG(DBG_FILE, "Creating directory %s\n", dir);
if (file_mkdir(dir) == -1) {
BD_DEBUG(DBG_FILE | DBG_CRIT, "Error creating directory %s\n", dir);
result = 0;
break;
}
}
*end++ = DIR_SEP_CHAR;
}
X_FREE(dir);
return result;
}
static char *_load_file(FILE *fp)
{
char *data = NULL;
long file_size, read_size;
fseek(fp, 0, SEEK_END);
file_size = ftell(fp);
fseek(fp, 0, SEEK_SET);
if (file_size < MIN_FILE_SIZE || file_size > MAX_FILE_SIZE) {
BD_DEBUG(DBG_FILE, "Invalid file size\n");
return NULL;
}
data = malloc(file_size + 1);
read_size = fread(data, 1, file_size, fp);
if (read_size != file_size) {
BD_DEBUG(DBG_FILE, "Error reading file\n");
X_FREE(data);
return NULL;
}
data[file_size] = 0;
return data;
}
static char *_config_file_user(const char *file_name)
{
char *cfg_dir = file_get_config_home();
char *result;
if (!cfg_dir) {
return NULL;
}
result = str_printf("%s"DIR_SEP"%s"DIR_SEP"%s", cfg_dir, CFG_DIR, file_name);
X_FREE(cfg_dir);
return result;
}
static FILE *_open_cfg_file_user(const char *file_name, char **path, const char *mode)
{
char *cfg_file = _config_file_user(file_name);
if (!cfg_file) {
return NULL;
}
if (*mode == 'w') {
if (!_mkpath(cfg_file)) {
X_FREE(cfg_file);
return NULL;
}
}
FILE *fp = fopen(cfg_file, mode);
BD_DEBUG(DBG_FILE, fp ? "Opened %s for %s\n" : "%s not found\n", cfg_file, mode);
if (fp && path) {
*path = cfg_file;
} else {
X_FREE(cfg_file);
}
return fp;
}
static FILE *_open_cfg_file_system(const char *file_name, char **path)
{
const char *dir = NULL;
while (NULL != (dir = file_get_config_system(dir))) {
char *cfg_file = str_printf("%s"DIR_SEP"%s"DIR_SEP"%s", dir, CFG_DIR, file_name);
if (!cfg_file) {
continue;
}
FILE *fp = fopen(cfg_file, "r");
if (fp) {
BD_DEBUG(DBG_FILE, "Reading %s\n", cfg_file);
if (path) {
*path = cfg_file;
} else {
X_FREE(cfg_file);
}
return fp;
}
BD_DEBUG(DBG_FILE, "%s not found\n", cfg_file);
X_FREE(cfg_file);
}
return NULL;
}
static int _is_duplicate_pk(pk_list *list, const uint8_t *e)
{
while (list) {
if (!memcmp(list->key, e, 16)) {
return 1;
}
list = list->next;
}
return 0;
}
static int _parse_pk_file(config_file *cf, FILE *fp)
{
char *data = _load_file(fp);
int result = 0;
if (data) {
const char *p = data;
while (*p) {
char *str = str_get_hex_string(p, 2*16);
if (str) {
BD_DEBUG(DBG_FILE, "Found processing key %s\n", str);
pk_list *e = calloc(1, sizeof(pk_list));
hexstring_to_hex_array(e->key, 16, str);
if (_is_duplicate_pk(cf->pkl, e->key)) {
BD_DEBUG(DBG_FILE, "Skipping duplicate processing key %s\n", str);
X_FREE(e);
} else {
e->next = cf->pkl;
cf->pkl = e;
}
result++;
}
X_FREE(str);
p = str_next_line(p);
}
X_FREE(data);
}
return result;
}
static int _is_duplicate_cert(cert_list *list, cert_list *e)
{
while (list) {
if (!memcmp(list->host_priv_key, e->host_priv_key, 20) &&
!memcmp(list->host_cert, e->host_cert, 92)) {
return 1;
}
list = list->next;
}
return 0;
}
static int _parse_cert_file(config_file *cf, FILE *fp)
{
char *data = _load_file(fp);
int result = 0;
if (data) {
const char *p = data;
char *host_cert, *host_priv_key;
host_priv_key = str_get_hex_string(p, 2*20);
p = str_next_line(p);
host_cert = str_get_hex_string(p, 2*92);
X_FREE(data);
if (!host_priv_key || !host_cert) {
BD_DEBUG(DBG_FILE, "Invalid file\n");
} else {
BD_DEBUG(DBG_FILE, "Found certificate: %s %s\n", host_priv_key, host_cert);
cert_list *e = calloc(1, sizeof(cert_list));
hexstring_to_hex_array(e->host_priv_key, 20, host_priv_key);
hexstring_to_hex_array(e->host_cert, 92, host_cert);
if (_is_duplicate_cert(cf->host_cert_list, e)) {
BD_DEBUG(DBG_FILE, "Skipping duplicate certificate entry %s %s\n", host_priv_key, host_cert);
X_FREE(e);
} else {
e->next = cf->host_cert_list;
cf->host_cert_list = e;
result = 1;
}
}
X_FREE(host_priv_key);
X_FREE(host_cert);
}
return result;
}
static int _is_duplicate_dk(dk_list *list, dk_list *e)
{
while (list) {
if (!memcmp(list, e, sizeof(*e))) {
return 1;
}
list = list->next;
}
return 0;
}
static int _load_pk_file(config_file *cf)
{
static const char pk_file_name[] = PK_FILE_NAME;
FILE *fp;
int result = 0;
fp = _open_cfg_file_user(pk_file_name, NULL, "r");
if (fp) {
result += _parse_pk_file(cf, fp);
fclose(fp);
}
fp = _open_cfg_file_system(pk_file_name, NULL);
if (fp) {
result += _parse_pk_file(cf, fp);
fclose(fp);
}
return result;
}
static int _load_cert_file(config_file *cf)
{
static const char cert_file_name[] = CERT_FILE_NAME;
FILE *fp;
int result = 0;
fp = _open_cfg_file_user(cert_file_name, NULL, "r");
if (fp) {
result += _parse_cert_file(cf, fp);
fclose(fp);
}
fp = _open_cfg_file_system(cert_file_name, NULL);
if (fp) {
result += _parse_cert_file(cf, fp);
fclose(fp);
}
return result;
}
static char *_keycache_file(const char *type, const uint8_t *disc_id)
{
char *cache_dir = file_get_cache_home();
char *result;
char disc_id_str[41];
if (!cache_dir) {
return NULL;
}
hex_array_to_hexstring(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);
return result;
}
int keycache_save(const char *type, const uint8_t *disc_id, const uint8_t *key, unsigned int len)
{
int result = 0;
char *file = _keycache_file(type, disc_id);
if (file) {
if (_mkpath(file)) {
FILE *fp = fopen(file, "w");
if (fp) {
char *key_str = calloc(1, len*2 + 1);
hex_array_to_hexstring(key_str, key, len);
if (fwrite(key_str, 1, len*2, fp) == len*2) {
BD_DEBUG(DBG_FILE, "Wrote %s to %s\n", type, file);
result = 1;
} else {
BD_DEBUG(DBG_FILE, "Error writing to %s\n", file);
}
free(key_str);
fclose(fp);
}
}
X_FREE(file);
}
return result;
}
int keycache_find(const char *type, const uint8_t *disc_id, uint8_t *key, unsigned int len)
{
int result = 0;
char *file = _keycache_file(type, disc_id);
if (file) {
FILE *fp = fopen(file, "r");
if (fp) {
char *key_str = malloc(len*2);
BD_DEBUG(DBG_FILE, "Reading %s\n", file);
if (fread(key_str, 1, len*2, fp) == len*2) {
result = hexstring_to_hex_array(key, len, key_str);
if (!result) {
BD_DEBUG(DBG_FILE, "Error converting %s\n", file);
}
} else {
BD_DEBUG(DBG_FILE, "Error reading from %s\n", file);
}
X_FREE(key_str);
fclose(fp);
} else {
BD_DEBUG(DBG_FILE, "%s not found\n", file);
}
X_FREE(file);
}
return result;
}
static char *_cache_file(const char *name)
{
char *cache_dir = file_get_cache_home();
char *result;
if (!cache_dir) {
return NULL;
}
result = str_printf("%s"DIR_SEP"%s"DIR_SEP"%s", cache_dir, CFG_DIR, name);
X_FREE(cache_dir);
return result;
}
int cache_save(const char *name, uint32_t version, const void *data, uint32_t len)
{
int result = 0;
char *file = _cache_file(name);
if (file) {
if (_mkpath(file)) {
FILE *fp = fopen(file, "w");
if (fp) {
if (fwrite(&version, 1, 4, fp) == 4 &&
fwrite(&len, 1, 4, fp) == 4 &&
fwrite(data, 1, len, fp) == len) {
BD_DEBUG(DBG_FILE, "Wrote %d bytes to %s\n", len + 8, file);
result = 1;
} else {
BD_DEBUG(DBG_FILE, "Error writing to %s\n", file);
}
fclose(fp);
}
}
X_FREE(file);
}
return result;
}
int cache_get(const char *name, uint32_t *version, uint32_t *len, void *buf)
{
int result = 0;
char *file = _cache_file(name);
*version = 0;
if (len) {
*len = 0;
}
if (file) {
FILE *fp = fopen(file, "r");
if (fp) {
BD_DEBUG(DBG_FILE, "Reading %s\n", file);
if (fread(version, 1, 4, fp) == 4 &&
(!len || fread(len, 1, 4, fp) == 4) &&
(!buf || fread(buf, 1, *len, fp) == *len)) {
BD_DEBUG(DBG_FILE, "Read %d bytes from %s, version %d\n", 4 + (len ? 4 : 0) + (buf ? *len : 0), file, *version);
result = 1;
} else {
BD_DEBUG(DBG_FILE, "Error reading from %s\n", file);
}
fclose(fp);
} else {
BD_DEBUG(DBG_FILE, "%s not found\n", file);
}
X_FREE(file);
}
return result;
}
int cache_remove(const char *name)
{
char *file = _cache_file(name);
if (!file) {
return 0;
}
int result = !remove(file);
if (!result) {
BD_DEBUG(DBG_FILE, "Error removing %s\n", file);
}
X_FREE(file);
return result;
}
int config_save(const char *name, const void *data, uint32_t len)
{
char *path = NULL;
FILE *fp = _open_cfg_file_user(name, &path, "w");
int result = 0;
if (fp) {
if (fwrite(&len, 1, 4, fp) == 4 &&
fwrite(data, 1, len, fp) == len) {
BD_DEBUG(DBG_FILE, "Wrote %d bytes to %s\n", len + 4, path);
result = 1;
} else {
BD_DEBUG(DBG_FILE | DBG_CRIT, "Error writing to %s\n", path);
}
fclose(fp);
}
X_FREE(path);
return result;
}
int config_get(const char *name, uint32_t *len, void *buf)
{
char *path = NULL;
FILE *fp = _open_cfg_file_user(name, &path, "r");
int result = 0;
uint32_t size = *len;
*len = 0;
if (fp) {
BD_DEBUG(DBG_FILE, "Reading %s\n", path);
if (fread(len, 1, 4, fp) == 4 && (size <= *len) &&
(!buf || fread(buf, 1, *len, fp) == *len)) {
BD_DEBUG(DBG_FILE, "Read %d bytes from %s\n", 4 + (buf ? *len : 0), path);
result = 1;
} else {
BD_DEBUG(DBG_FILE | DBG_CRIT, "Error reading from %s\n", path);
}
fclose(fp);
}
X_FREE(path);
return result;
}
static char *_find_config_file(void)
{
static const char cfg_file_name[] = CFG_FILE_NAME;
char *cfg_file = NULL;
FILE *fp = NULL;
fp = _open_cfg_file_user(cfg_file_name, &cfg_file, "r");
if (!fp) {
fp = _open_cfg_file_system(cfg_file_name, &cfg_file);
}
if (fp) {
BD_DEBUG(DBG_FILE, "found config file: %s\n", cfg_file);
fclose(fp);
}
return cfg_file;
}
#include "keydb.h"
static int _parse_embedded(config_file *cf)
{
int result = 0, jj;
unsigned ii;
/* reverse order to maintain key positions (items are added to list head) */
for (jj = sizeof(internal_dk_list) / sizeof(internal_dk_list[0]) - 1; jj >= 0; --jj) {
dk_list *e = calloc(1, sizeof(dk_list));
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];
if (!e->uv || _is_duplicate_dk(cf->dkl, e)) {
X_FREE(e);
} else {
e->next = cf->dkl;
cf->dkl = e;
result++;
}
}
for (ii = 0; ii < sizeof(internal_pk_list) / sizeof(internal_pk_list[0]); ii++) {
pk_list *e = calloc(1, sizeof(pk_list));
decrypt_key(e->key, internal_pk_list[ii], 16);
if (_is_duplicate_pk(cf->pkl, e->key)) {
X_FREE(e);
} else {
e->next = cf->pkl;
cf->pkl = e;
result++;
}
}
for (ii = 0; ii < sizeof(internal_hc_list) / sizeof(internal_hc_list[0]); ii++) {
cert_list *e = calloc(1, sizeof(cert_list));
decrypt_key(e->host_priv_key, internal_hc_list[ii], 20);
decrypt_key(e->host_cert, internal_hc_list[ii] + 20, 92);
if (_is_duplicate_cert(cf->host_cert_list, e)) {
X_FREE(e);
} else {
e->next = cf->host_cert_list;
cf->host_cert_list = e;
result++;
}
}
return result;
}
config_file *keydbcfg_config_load(const char *configfile_path)
{
int config_ok = 0;
config_file *cf = keydbcfg_new_config_file();
/* try to load KEYDB.cfg */
if (configfile_path) {
config_ok = keydbcfg_parse_config(cf, configfile_path);
} else {
/* If no configfile path given, check for config files in user's home or
* under /etc.
*/
char *cfgfile = _find_config_file();
config_ok = keydbcfg_parse_config(cf, cfgfile);
X_FREE(cfgfile);
}
/* Try to load simple (aacskeys) config files */
config_ok = _load_pk_file(cf) || config_ok;
config_ok = _load_cert_file(cf) || config_ok;
/* embedded keys */
config_ok = _parse_embedded(cf) || config_ok;
if (!config_ok) {
BD_DEBUG(DBG_AACS | DBG_CRIT, "No valid AACS configuration files found\n");
keydbcfg_config_file_close(cf);
return NULL;
}
return cf;
}