1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-12-23 09:02:06 +00:00

keys: use __getline() to parse keys file.

* keysGetKeyAndValueFromFile() is now thread-safe -- may be useful for people reusing code from nxdumptool. The dynamic buffer allocated by __getline() must be freed by the caller. Furthermore, this fixes an out-of-bounds issue while writing data to the static array that was being used with fgets().
* Empty lines are now considered failures.
* keysGetKeyAndValueFromFile() now validates the value string and converts it to lowercase as well.
* Adjusted the example regex in the description for keysGetKeyAndValueFromFile() to accurately match what the function actually does.
* Added helper macros to keysReadKeysFromFile().
This commit is contained in:
Pablo Curiel 2022-04-18 23:38:18 +02:00
parent 4a512db93c
commit 215b677f02

View file

@ -109,8 +109,8 @@ static bool keysRetrieveKeysFromProgramMemory(KeysMemoryInfo *info);
static bool keysDeriveNcaHeaderKey(void);
static bool keysDeriveSealedNcaKeyAreaEncryptionKeys(void);
static int keysGetKeyAndValueFromFile(FILE *f, char **key, char **value);
static char keysConvertHexCharToBinary(char c);
static int keysGetKeyAndValueFromFile(FILE *f, char **line, char **key, char **value);
static char keysConvertHexDigitToBinary(char c);
static bool keysParseHexKey(u8 *out, const char *key, const char *value, u32 size);
static bool keysReadKeysFromFile(void);
@ -627,27 +627,23 @@ static bool keysDeriveSealedNcaKeyAreaEncryptionKeys(void)
/**
* Reads a line from file f and parses out the key and value from it.
* The format of a line must match /^ *[A-Za-z0-9_] *[,=] *.+$/.
* The format of a line must match /^[ \t]*\w+[ \t]*[,=][ \t]*(?:[A-Fa-f0-9]{2})+[ \t]*$/.
* If a line ends in \r, the final \r is stripped.
* The input file is assumed to have been opened with the 'b' flag.
* The input file is assumed to contain only ASCII.
*
* A line cannot exceed 512 bytes in length.
* Lines that are excessively long will be silently truncated.
* On success, *line will point to a dynamically allocated buffer that holds
* the read line, whilst *key and *value will be set to point to the key and
* value strings within *line, respectively. *line must be freed by the caller.
* On failure, *line, *key and *value will all be set to NULL.
* Empty lines and end of file are both considered failures.
*
* On success, *key and *value will be set to point to the key and value in
* the input line, respectively.
* *key and *value may also be NULL in case of empty lines.
* On failure, *key and *value will be set to NULL.
* End of file is considered failure.
* This function is thread-safe.
*
* Because *key and *value will point to a static buffer, their contents must be
* copied before calling this function again.
* For the same reason, this function is not thread-safe.
*
* The key will be converted to lowercase.
* An empty key is considered a parse error, but an empty value is returned as
* success.
* Both key and value strings will be converted to lowercase.
* Empty key and/or value strings are both considered a parse error.
* Furthermore, a parse error will also be returned if the value string length
* is not a multiple of 2.
*
* This function assumes that the file can be trusted not to contain any NUL in
* the contents.
@ -656,112 +652,200 @@ static bool keysDeriveSealedNcaKeyAreaEncryptionKeys(void)
* the line, at the end of the line as well as around = (or ,) will be ignored.
*
* @param f the file to read
* @param line pointer to change to point to the read line
* @param key pointer to change to point to the key
* @param value pointer to change to point to the value
* @return 0 on success,
* 1 on end of file,
* -1 on parse error (line too long, line malformed)
* -1 on parse error (line malformed, empty line)
* -2 on I/O error
*/
static int keysGetKeyAndValueFromFile(FILE *f, char **key, char **value)
static int keysGetKeyAndValueFromFile(FILE *f, char **line, char **key, char **value)
{
if (!f || !key || !value)
if (!f || !line || !key || !value)
{
LOG_MSG("Invalid parameters!");
return -2;
}
#define SKIP_SPACE(p) do {\
for (; (*p == ' ' || *p == '\t'); ++p);\
} while(0);
static char line[512] = {0};
char *k, *v, *p, *end;
*key = *value = NULL;
int ret = -1;
size_t n = 0;
ssize_t read = 0;
char *l = NULL, *k = NULL, *v = NULL, *p = NULL, *e = NULL;
/* Clear inputs beforehand. */
if (*line) free(*line);
*line = *key = *value = NULL;
errno = 0;
if (fgets(line, (int)sizeof(line), f) == NULL)
/* Read line. */
read = __getline(line, &n, f);
if (errno != 0 || read <= 0)
{
if (feof(f))
{
return 1;
} else {
return -2;
}
ret = ((errno == 0 && (read == 0 || feof(f))) ? 1 : -2);
if (ret != 1) LOG_MSG("__getline failed! (0x%lX, %ld, %d, %d).", ftell(f), read, errno, ret);
goto end;
}
if (errno != 0) return -2;
n = (ftell(f) - (size_t)read);
if (*line == '\n' || *line == '\r' || *line == '\0') return 0;
/* Not finding \r or \n is not a problem.
* The line might just be exactly 512 characters long, we have no way to
* tell.
* Additionally, it's possible that the last line of a file is not actually
* a line (i.e., does not end in '\n'); we do want to handle those.
*/
if ((p = strchr(line, '\r')) != NULL || (p = strchr(line, '\n')) != NULL)
/* Check if we're dealing with an empty line. */
l = *line;
if (*l == '\n' || *l == '\r' || *l == '\0')
{
end = p;
LOG_MSG("Empty line detected! (0x%lX, 0x%lX).", n, read);
goto end;
}
/* Not finding '\r' or '\n' is not a problem. */
/* It's possible that the last line of a file isn't actually a line (i.e., does not end in '\n'). */
/* We do want to handle those. */
if ((p = strchr(l, '\r')) != NULL || (p = strchr(l, '\n')) != NULL)
{
e = p;
*p = '\0';
} else {
end = (line + strlen(line) + 1);
e = (l + read + 1);
}
p = line;
#define SKIP_SPACE(p) do { \
for(; (*p == ' ' || *p == '\t'); ++p); \
} while(0);
/* Skip leading whitespace before the key name string. */
p = l;
SKIP_SPACE(p);
k = p;
/* Validate key and convert to lower case. */
for (; *p != ' ' && *p != ',' && *p != '\t' && *p != '='; ++p)
/* Validate key name string. */
for(; *p != ' ' && *p != '\t' && *p != ',' && *p != '='; ++p)
{
if (*p == '\0') return -1;
/* Bail out if we reached the end of string. */
if (*p == '\0')
{
LOG_MSG("End of string reached while validating key name string! (#1) (0x%lX, 0x%lX, 0x%lX).", n, read, (size_t)(p - l));
goto end;
}
/* Convert uppercase characters to lowercase. */
if (*p >= 'A' && *p <= 'Z')
{
*p = 'a' + (*p - 'A');
*p = ('a' + (*p - 'A'));
continue;
}
if (*p != '_' && (*p < '0' && *p > '9') && (*p < 'a' && *p > 'z')) return -1;
/* Handle unsupported characters. */
if (*p != '_' && (*p < '0' || *p > '9') && (*p < 'a' || *p > 'z'))
{
LOG_MSG("Unsupported character detected in key name string! (0x%lX, 0x%lX, 0x%lX, 0x%02X).", n, read, (size_t)(p - l), *p);
goto end;
}
}
/* Bail if the final ++p put us at the end of string. */
if (*p == '\0') return -1;
if (*p == '\0')
{
LOG_MSG("End of string reached while validating key name string! (#2) (0x%lX, 0x%lX, 0x%lX).", n, read, (size_t)(p - l));
goto end;
}
/* We should be at the end of key now and either whitespace or [,=] follows. */
/* We should be at the end of the key name string now and either whitespace or [,=] follows. */
if (*p == '=' || *p == ',')
{
*p++ = '\0';
} else {
/* Skip leading whitespace before [,=]. */
*p++ = '\0';
SKIP_SPACE(p);
if (*p != '=' && *p != ',') return -1;
if (*p != '=' && *p != ',')
{
LOG_MSG("Unable to find expected [,=]! (0x%lX, 0x%lX, 0x%lX).", n, read, (size_t)(p - l));
goto end;
}
*p++ = '\0';
}
/* Empty key is an error. */
if (*k == '\0') return -1;
/* Empty key name string is an error. */
if (*k == '\0')
{
LOG_MSG("Key name string empty! (0x%lX, 0x%lX).", n, read);
goto end;
}
/* Skip trailing whitespace after [,=]. */
SKIP_SPACE(p);
v = p;
/* Skip trailing whitespace. */
for (p = end - 1; *p == '\t' || *p == ' '; --p);
#undef SKIP_SPACE
*(p + 1) = '\0';
/* Validate value string. */
for(; p < e && *p != ' ' && *p != '\t'; ++p)
{
/* Bail out if we reached the end of string. */
if (*p == '\0')
{
LOG_MSG("End of string reached while validating value string! (0x%lX, 0x%lX, 0x%lX, %s).", n, read, (size_t)(p - l), k);
goto end;
}
/* Convert uppercase characters to lowercase. */
if (*p >= 'A' && *p <= 'F')
{
*p = ('a' + (*p - 'A'));
continue;
}
/* Handle unsupported characters. */
if ((*p < '0' || *p > '9') && (*p < 'a' || *p > 'f'))
{
LOG_MSG("Unsupported character detected in value string! (0x%lX, 0x%lX, 0x%lX, 0x%02X, %s).", n, read, (size_t)(p - l), *p, k);
goto end;
}
}
/* We should be at the end of the value string now and whitespace may optionally follow. */
l = p;
if (p < e)
{
/* Skip trailing whitespace after the value string. */
/* Make sure there's no additional data after this. */
*p++ = '\0';
for(; p < e && (*p == ' ' || *p == '\t'); ++p);
if (p < e)
{
LOG_MSG("Additional data detected after value string and before line end! (0x%lX, 0x%lX, 0x%lX, %s).", n, read, (size_t)(p - *line), k);
goto end;
}
}
/* Empty value string and value string length not being a multiple of 2 are both errors. */
if (*v == '\0' || ((l - v) % 2) != 0)
{
LOG_MSG("Invalid value string length! (0x%lX, 0x%lX, 0x%lX, %s).", n, read, (size_t)(l - v), k);
goto end;
}
/* Update pointers. */
*key = k;
*value = v;
return 0;
/* Update return value. */
ret = 0;
#undef SKIP_SPACE
end:
if (ret != 0)
{
if (*line) free(*line);
*line = *key = *value = NULL;
}
return ret;
}
static char keysConvertHexCharToBinary(char c)
static char keysConvertHexDigitToBinary(char c)
{
if ('a' <= c && c <= 'f') return (c - 'a' + 0xA);
if ('A' <= c && c <= 'F') return (c - 'A' + 0xA);
@ -790,7 +874,7 @@ static bool keysParseHexKey(u8 *out, const char *key, const char *value, u32 siz
for(u32 i = 0; i < hex_str_len; i++)
{
char val = keysConvertHexCharToBinary(value[i]);
char val = keysConvertHexDigitToBinary(value[i]);
if (val == 'z')
{
LOG_MSG("Invalid hex character in key \"%s\" at position %u!", key, i);
@ -809,9 +893,9 @@ static bool keysReadKeysFromFile(void)
int ret = 0;
u32 key_count = 0;
FILE *keys_file = NULL;
char *key = NULL, *value = NULL;
char *line = NULL, *key = NULL, *value = NULL;
char test_name[0x40] = {0};
bool parse_fail = false, eticket_rsa_kek_available = false;
bool eticket_rsa_kek_available = false;
const char *keys_file_path = (utilsIsDevelopmentUnit() ? DEV_KEYS_FILE_PATH : PROD_KEYS_FILE_PATH);
keys_file = fopen(keys_file_path, "rb");
@ -821,74 +905,56 @@ static bool keysReadKeysFromFile(void)
return false;
}
#define PARSE_HEX_KEY(name, out, decl) \
if (!strcmp(key, name) && keysParseHexKey(out, key, value, sizeof(out))) { \
key_count++; \
decl; \
}
#define PARSE_HEX_KEY_WITH_INDEX(name, out) \
snprintf(test_name, sizeof(test_name), "%s_%02x", name, i); \
PARSE_HEX_KEY(test_name, out, break);
while(true)
{
ret = keysGetKeyAndValueFromFile(keys_file, &key, &value);
if (ret == 1 || ret == -2) break; /* Break from the while loop if EOF is reached or if an I/O error occurs. */
/* Get key and value strings from the current line. */
/* Break from the while loop if EOF is reached or if an I/O error occurs. */
ret = keysGetKeyAndValueFromFile(keys_file, &line, &key, &value);
if (ret == 1 || ret == -2) break;
/* Ignore malformed lines. */
/* Ignore malformed or empty lines. */
if (ret != 0 || !key || !value) continue;
if (!strcasecmp(key, "eticket_rsa_kek"))
PARSE_HEX_KEY("eticket_rsa_kek", g_ncaKeyset.eticket_rsa_kek, eticket_rsa_kek_available = true; continue);
/* This only appears on consoles that use the new PRODINFO key generation scheme. */
PARSE_HEX_KEY("eticket_rsa_kek_personalized", g_ncaKeyset.eticket_rsa_kek_personalized, eticket_rsa_kek_available = true; continue);
for(u32 i = 0; i < NcaKeyGeneration_Max; i++)
{
if ((parse_fail = !keysParseHexKey(g_ncaKeyset.eticket_rsa_kek, key, value, sizeof(g_ncaKeyset.eticket_rsa_kek)))) break;
eticket_rsa_kek_available = true;
key_count++;
} else
if (!strcasecmp(key, "eticket_rsa_kek_personalized"))
{
/* This only appears on consoles that use the new PRODINFO key generation scheme. */
if ((parse_fail = !keysParseHexKey(g_ncaKeyset.eticket_rsa_kek_personalized, key, value, sizeof(g_ncaKeyset.eticket_rsa_kek_personalized)))) break;
eticket_rsa_kek_available = true;
key_count++;
} else {
for(u32 i = 0; i < NcaKeyGeneration_Max; i++)
{
snprintf(test_name, sizeof(test_name), "titlekek_%02x", i);
if (!strcasecmp(key, test_name))
{
if ((parse_fail = !keysParseHexKey(g_ncaKeyset.ticket_common_keys[i], key, value, sizeof(g_ncaKeyset.ticket_common_keys[i])))) break;
key_count++;
break;
}
snprintf(test_name, sizeof(test_name), "key_area_key_application_%02x", i);
if (!strcasecmp(key, test_name))
{
if ((parse_fail = !keysParseHexKey(g_ncaKeyset.nca_kaek[NcaKeyAreaEncryptionKeyIndex_Application][i], key, value, \
sizeof(g_ncaKeyset.nca_kaek[NcaKeyAreaEncryptionKeyIndex_Application][i])))) break;
key_count++;
break;
}
snprintf(test_name, sizeof(test_name), "key_area_key_ocean_%02x", i);
if (!strcasecmp(key, test_name))
{
if ((parse_fail = !keysParseHexKey(g_ncaKeyset.nca_kaek[NcaKeyAreaEncryptionKeyIndex_Ocean][i], key, value, \
sizeof(g_ncaKeyset.nca_kaek[NcaKeyAreaEncryptionKeyIndex_Ocean][i])))) break;
key_count++;
break;
}
snprintf(test_name, sizeof(test_name), "key_area_key_system_%02x", i);
if (!strcasecmp(key, test_name))
{
if ((parse_fail = !keysParseHexKey(g_ncaKeyset.nca_kaek[NcaKeyAreaEncryptionKeyIndex_System][i], key, value, \
sizeof(g_ncaKeyset.nca_kaek[NcaKeyAreaEncryptionKeyIndex_System][i])))) break;
key_count++;
break;
}
}
PARSE_HEX_KEY_WITH_INDEX("titlekek", g_ncaKeyset.ticket_common_keys[i]);
if (parse_fail) break;
PARSE_HEX_KEY_WITH_INDEX("key_area_key_application", g_ncaKeyset.nca_kaek[NcaKeyAreaEncryptionKeyIndex_Application][i]);
PARSE_HEX_KEY_WITH_INDEX("key_area_key_ocean", g_ncaKeyset.nca_kaek[NcaKeyAreaEncryptionKeyIndex_Ocean][i]);
PARSE_HEX_KEY_WITH_INDEX("key_area_key_system", g_ncaKeyset.nca_kaek[NcaKeyAreaEncryptionKeyIndex_System][i]);
}
}
#undef PARSE_HEX_KEY_WITH_INDEX
#undef PARSE_HEX_KEY
if (line) free(line);
fclose(keys_file);
if (parse_fail || !key_count)
if (key_count)
{
if (!key_count) LOG_MSG("Unable to parse necessary keys from \"%s\"! (keys file empty?).", keys_file_path);
LOG_MSG("Loaded %u key(s) from \"%s\".", key_count, keys_file_path);
} else {
LOG_MSG("Unable to parse keys from \"%s\"! (keys file empty?).", keys_file_path);
return false;
}