Add support for persistentPinUvAuthToken.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
This commit is contained in:
Pol Henarejos
2025-08-28 00:17:57 +02:00
parent 2b640a5c36
commit 73a7856866
8 changed files with 117 additions and 66 deletions

View File

@@ -105,11 +105,7 @@ int regenerate() {
mbedtls_ecdh_init(&hkey); mbedtls_ecdh_init(&hkey);
hkey_init = true; hkey_init = true;
mbedtls_ecdh_setup(&hkey, MBEDTLS_ECP_DP_SECP256R1); mbedtls_ecdh_setup(&hkey, MBEDTLS_ECP_DP_SECP256R1);
int ret = mbedtls_ecdh_gen_public(&hkey.ctx.mbed_ecdh.grp, int ret = mbedtls_ecdh_gen_public(&hkey.ctx.mbed_ecdh.grp, &hkey.ctx.mbed_ecdh.d, &hkey.ctx.mbed_ecdh.Q, random_gen, NULL);
&hkey.ctx.mbed_ecdh.d,
&hkey.ctx.mbed_ecdh.Q,
random_gen,
NULL);
mbedtls_mpi_lset(&hkey.ctx.mbed_ecdh.Qp.Z, 1); mbedtls_mpi_lset(&hkey.ctx.mbed_ecdh.Qp.Z, 1);
if (ret != 0) { if (ret != 0) {
return ret; return ret;
@@ -125,34 +121,15 @@ int kdf(uint8_t protocol, const mbedtls_mpi *z, uint8_t *sharedSecret) {
return ret; return ret;
} }
if (protocol == 1) { if (protocol == 1) {
return mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), return mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), buf, sizeof(buf), sharedSecret);
buf,
sizeof(buf),
sharedSecret);
} }
else if (protocol == 2) { else if (protocol == 2) {
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
ret = mbedtls_hkdf(md_info, ret = mbedtls_hkdf(md_info, NULL, 0, buf, sizeof(buf), (uint8_t *) "CTAP2 HMAC key", 14, sharedSecret, 32);
NULL,
0,
buf,
sizeof(buf),
(uint8_t *) "CTAP2 HMAC key",
14,
sharedSecret,
32);
if (ret != 0) { if (ret != 0) {
return ret; return ret;
} }
return mbedtls_hkdf(md_info, return mbedtls_hkdf(md_info, NULL, 0, buf, sizeof(buf), (uint8_t *) "CTAP2 AES key", 13, sharedSecret + 32, 32);
NULL,
0,
buf,
sizeof(buf),
(uint8_t *) "CTAP2 AES key",
13,
sharedSecret + 32,
32);
} }
return -1; return -1;
} }
@@ -160,26 +137,38 @@ int kdf(uint8_t protocol, const mbedtls_mpi *z, uint8_t *sharedSecret) {
int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSecret) { int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSecret) {
mbedtls_mpi z; mbedtls_mpi z;
mbedtls_mpi_init(&z); mbedtls_mpi_init(&z);
int ret = mbedtls_ecdh_compute_shared(&hkey.ctx.mbed_ecdh.grp, int ret = mbedtls_ecdh_compute_shared(&hkey.ctx.mbed_ecdh.grp, &z, Q, &hkey.ctx.mbed_ecdh.d, random_gen, NULL);
&z,
Q,
&hkey.ctx.mbed_ecdh.d,
random_gen,
NULL);
ret = kdf(protocol, &z, sharedSecret); ret = kdf(protocol, &z, sharedSecret);
mbedtls_mpi_free(&z); mbedtls_mpi_free(&z);
return ret; return ret;
} }
int resetPinUvAuthToken() { void resetAuthToken(bool persistent) {
uint16_t fid = EF_AUTHTOKEN;
if (persistent) {
fid = EF_PAUTHTOKEN;
}
file_t *ef = search_by_fid(fid, NULL, SPECIFY_EF);
uint8_t t[32]; uint8_t t[32];
random_gen(NULL, t, sizeof(t)); random_gen(NULL, t, sizeof(t));
file_put_data(ef_authtoken, t, sizeof(t)); file_put_data(ef, t, sizeof(t));
low_flash_available();
}
int resetPinUvAuthToken() {
resetAuthToken(false);
paut.permissions = 0; paut.permissions = 0;
paut.data = file_get_data(ef_authtoken); paut.data = file_get_data(ef_authtoken);
paut.len = file_get_size(ef_authtoken); paut.len = file_get_size(ef_authtoken);
return 0;
}
low_flash_available(); int resetPersistentPinUvAuthToken() {
resetAuthToken(true);
file_t *ef_pauthtoken = search_by_fid(EF_PAUTHTOKEN, NULL, SPECIFY_EF);
ppaut.permissions = 0;
ppaut.data = file_get_data(ef_pauthtoken);
ppaut.len = file_get_size(ef_pauthtoken);
return 0; return 0;
} }
@@ -578,6 +567,7 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
} }
low_flash_available(); low_flash_available();
resetPinUvAuthToken(); resetPinUvAuthToken();
resetPersistentPinUvAuthToken();
goto err; // No return goto err; // No return
} }
else if (subcommand == 0x9 || subcommand == 0x5) { //getPinUvAuthTokenUsingPinWithPermissions else if (subcommand == 0x9 || subcommand == 0x5) { //getPinUvAuthTokenUsingPinWithPermissions
@@ -598,7 +588,9 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
if ((permissions & CTAP_PERMISSION_BE)) { // Not supported yet if ((permissions & CTAP_PERMISSION_BE)) { // Not supported yet
CBOR_ERROR(CTAP2_ERR_UNAUTHORIZED_PERMISSION); CBOR_ERROR(CTAP2_ERR_UNAUTHORIZED_PERMISSION);
} }
if ((permissions & CTAP_PERMISSION_PCMR) && permissions != CTAP_PERMISSION_PCMR) {
CBOR_ERROR(CTAP2_ERR_UNAUTHORIZED_PERMISSION);
}
} }
if (!file_has_data(ef_pin)) { if (!file_has_data(ef_pin)) {
CBOR_ERROR(CTAP2_ERR_PIN_NOT_SET); CBOR_ERROR(CTAP2_ERR_PIN_NOT_SET);
@@ -664,21 +656,29 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) { if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) {
CBOR_ERROR(CTAP2_ERR_PIN_INVALID); CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
} }
resetPinUvAuthToken(); uint8_t pinUvAuthToken_enc[32 + IV_SIZE], *pdata = NULL;
beginUsingPinUvAuthToken(false); ;
if (subcommand == 0x05) { if (permissions & CTAP_PERMISSION_PCMR) {
permissions = CTAP_PERMISSION_MC | CTAP_PERMISSION_GA; ppaut.permissions = CTAP_PERMISSION_PCMR;
} pdata = ppaut.data;
paut.permissions = (uint8_t)permissions;
if (rpId.present == true) {
mbedtls_sha256((uint8_t *) rpId.data, rpId.len, paut.rp_id_hash, 0);
paut.has_rp_id = true;
} }
else { else {
paut.has_rp_id = false; resetPinUvAuthToken();
beginUsingPinUvAuthToken(false);
if (subcommand == 0x05) {
permissions = CTAP_PERMISSION_MC | CTAP_PERMISSION_GA;
}
paut.permissions = (uint8_t)permissions;
if (rpId.present == true) {
mbedtls_sha256((uint8_t *) rpId.data, rpId.len, paut.rp_id_hash, 0);
paut.has_rp_id = true;
}
else {
paut.has_rp_id = false;
}
pdata = paut.data;
} }
uint8_t pinUvAuthToken_enc[32 + IV_SIZE]; encrypt((uint8_t)pinUvAuthProtocol, sharedSecret, pdata, 32, pinUvAuthToken_enc);
encrypt((uint8_t)pinUvAuthProtocol, sharedSecret, paut.data, 32, pinUvAuthToken_enc);
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1)); CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, pinUvAuthToken_enc, 32 + poff)); CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, pinUvAuthToken_enc, 32 + poff));

View File

@@ -31,6 +31,8 @@
extern uint8_t keydev_dec[32]; extern uint8_t keydev_dec[32];
extern bool has_keydev_dec; extern bool has_keydev_dec;
extern void resetPersistentPinUvAuthToken();
extern void resetPinUvAuthToken();
int cbor_config(const uint8_t *data, size_t len) { int cbor_config(const uint8_t *data, size_t len) {
CborParser parser; CborParser parser;
@@ -207,6 +209,10 @@ int cbor_config(const uint8_t *data, size_t len) {
if (file_has_data(ef_pin) && file_get_data(ef_pin)[1] < newMinPinLength) { if (file_has_data(ef_pin) && file_get_data(ef_pin)[1] < newMinPinLength) {
forceChangePin = ptrue; forceChangePin = ptrue;
} }
if (forceChangePin) {
resetPersistentPinUvAuthToken();
resetPinUvAuthToken();
}
uint8_t *dataf = (uint8_t *) calloc(1, 2 + minPinLengthRPIDs_len * 32); uint8_t *dataf = (uint8_t *) calloc(1, 2 + minPinLengthRPIDs_len * 32);
dataf[0] = (uint8_t)newMinPinLength; dataf[0] = (uint8_t)newMinPinLength;
dataf[1] = forceChangePin == ptrue ? 1 : 0; dataf[1] = forceChangePin == ptrue ? 1 : 0;

View File

@@ -79,8 +79,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
if (strcmp(_fd3, "transports") == 0) { if (strcmp(_fd3, "transports") == 0) {
CBOR_PARSE_ARRAY_START(_f3, 4) CBOR_PARSE_ARRAY_START(_f3, 4)
{ {
CBOR_FIELD_GET_TEXT(credentialId.transports[credentialId. CBOR_FIELD_GET_TEXT(credentialId.transports[credentialId.transports_len], 4);
transports_len], 4);
credentialId.transports_len++; credentialId.transports_len++;
} }
CBOR_PARSE_ARRAY_END(_f3, 4); CBOR_PARSE_ARRAY_END(_f3, 4);
@@ -122,12 +121,19 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0); cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
if (subcommand == 0x01) { if (subcommand == 0x01) {
if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x01", 1, pinUvAuthParam.data) != CborNoError) { if (verify((uint8_t)pinUvAuthProtocol, ppaut.data, (const uint8_t *) "\x01", 1, pinUvAuthParam.data) == CborNoError) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); if (!(ppaut.permissions & CTAP_PERMISSION_PCMR)) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
} }
if (is_preview == false && else {
(!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) { if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x01", 1, pinUvAuthParam.data) != CborNoError) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
if (is_preview == false &&
(!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
} }
uint8_t existing = 0; uint8_t existing = 0;
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) { for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
@@ -144,11 +150,18 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
else if (subcommand == 0x02 || subcommand == 0x03) { else if (subcommand == 0x02 || subcommand == 0x03) {
file_t *rp_ef = NULL; file_t *rp_ef = NULL;
if (subcommand == 0x02) { if (subcommand == 0x02) {
if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x02", 1, pinUvAuthParam.data) != CborNoError) { if (verify((uint8_t)pinUvAuthProtocol, ppaut.data, (const uint8_t *) "\x02", 1, pinUvAuthParam.data) == CborNoError) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); if (!(ppaut.permissions & CTAP_PERMISSION_PCMR)) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
} }
if (is_preview == false && (!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) { else {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x02", 1, pinUvAuthParam.data) != CborNoError) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
if (is_preview == false && (!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
} }
rp_counter = 1; rp_counter = 1;
rp_total = 0; rp_total = 0;
@@ -199,13 +212,20 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
} }
if (subcommand == 0x04) { if (subcommand == 0x04) {
*(raw_subpara - 1) = 0x04; *(raw_subpara - 1) = 0x04;
if (verify((uint8_t)pinUvAuthProtocol, paut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) != CborNoError) { if (verify((uint8_t)pinUvAuthProtocol, ppaut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) == CborNoError) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); if (!(ppaut.permissions & CTAP_PERMISSION_PCMR)) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
} }
if (is_preview == false && else {
(!(paut.permissions & CTAP_PERMISSION_CM) || if (verify((uint8_t)pinUvAuthProtocol, paut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) != CborNoError) {
(paut.has_rp_id == true && memcmp(paut.rp_id_hash, rpIdHash.data, 32) != 0))) { CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); }
if (is_preview == false &&
(!(paut.permissions & CTAP_PERMISSION_CM) ||
(paut.has_rp_id == true && memcmp(paut.rp_id_hash, rpIdHash.data, 32) != 0))) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
} }
cred_counter = 1; cred_counter = 1;
cred_total = 0; cred_total = 0;

View File

@@ -134,6 +134,7 @@ typedef struct {
#define CTAP_PERMISSION_BE 0x08 // BioEnrollment #define CTAP_PERMISSION_BE 0x08 // BioEnrollment
#define CTAP_PERMISSION_LBW 0x10 // LargeBlobWrite #define CTAP_PERMISSION_LBW 0x10 // LargeBlobWrite
#define CTAP_PERMISSION_ACFG 0x20 // AuthenticatorConfiguration #define CTAP_PERMISSION_ACFG 0x20 // AuthenticatorConfiguration
#define CTAP_PERMISSION_PCMR 0x40 // PerCredentialManagementReadOnly
typedef struct mse { typedef struct mse {
uint8_t Qpt[65]; uint8_t Qpt[65];

View File

@@ -42,6 +42,7 @@ int fido_process_apdu();
int fido_unload(); int fido_unload();
pinUvAuthToken_t paut = { 0 }; pinUvAuthToken_t paut = { 0 };
persistentPinUvAuthToken_t ppaut = { 0 };
uint8_t keydev_dec[32]; uint8_t keydev_dec[32];
bool has_keydev_dec = false; bool has_keydev_dec = false;
@@ -419,6 +420,19 @@ int scan_files_fido() {
else { else {
printf("FATAL ERROR: Auth Token not found in memory!\r\n"); printf("FATAL ERROR: Auth Token not found in memory!\r\n");
} }
file_t *ef_pauthtoken = search_by_fid(EF_PAUTHTOKEN, NULL, SPECIFY_EF);
if (ef_pauthtoken) {
if (!file_has_data(ef_pauthtoken)) {
uint8_t t[32];
random_gen(NULL, t, sizeof(t));
file_put_data(ef_pauthtoken, t, sizeof(t));
}
ppaut.data = file_get_data(ef_pauthtoken);
ppaut.len = file_get_size(ef_pauthtoken);
}
else {
printf("FATAL ERROR: Persistent Auth Token not found in memory!\r\n");
}
ef_largeblob = search_by_fid(EF_LARGEBLOB, NULL, SPECIFY_EF); ef_largeblob = search_by_fid(EF_LARGEBLOB, NULL, SPECIFY_EF);
if (!file_has_data(ef_largeblob)) { if (!file_has_data(ef_largeblob)) {
file_put_data(ef_largeblob, (const uint8_t *) "\x80\x76\xbe\x8b\x52\x8d\x00\x75\xf7\xaa\xe9\x8d\x6f\xa5\x7a\x6d\x3c", 17); file_put_data(ef_largeblob, (const uint8_t *) "\x80\x76\xbe\x8b\x52\x8d\x00\x75\xf7\xaa\xe9\x8d\x6f\xa5\x7a\x6d\x3c", 17);

View File

@@ -129,9 +129,17 @@ typedef struct pinUvAuthToken {
bool user_verified; bool user_verified;
} pinUvAuthToken_t; } pinUvAuthToken_t;
typedef struct persistentPinUvAuthToken {
uint8_t *data;
size_t len;
uint8_t permissions;
} persistentPinUvAuthToken_t;
extern uint32_t user_present_time_limit; extern uint32_t user_present_time_limit;
extern pinUvAuthToken_t paut; extern pinUvAuthToken_t paut;
extern persistentPinUvAuthToken_t ppaut;
extern int verify(uint8_t protocol, const uint8_t *key, const uint8_t *data, uint16_t len, uint8_t *sign); extern int verify(uint8_t protocol, const uint8_t *key, const uint8_t *data, uint16_t len, uint8_t *sign);
extern uint8_t session_pin[32]; extern uint8_t session_pin[32];

View File

@@ -27,6 +27,7 @@ file_t file_entries[] = {
{ .fid = EF_COUNTER, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Global counter { .fid = EF_COUNTER, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Global counter
{ .fid = EF_PIN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // PIN { .fid = EF_PIN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // PIN
{ .fid = EF_AUTHTOKEN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // AUTH TOKEN { .fid = EF_AUTHTOKEN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // AUTH TOKEN
{ .fid = EF_PAUTHTOKEN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // PERSISTENT AUTH TOKEN
{ .fid = EF_MINPINLEN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // MIN PIN LENGTH { .fid = EF_MINPINLEN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // MIN PIN LENGTH
{ .fid = EF_OPTS, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Global options { .fid = EF_OPTS, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Global options
{ .fid = EF_LARGEBLOB, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Large Blob { .fid = EF_LARGEBLOB, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Large Blob

View File

@@ -29,6 +29,7 @@
#define EF_OPTS 0xC001 #define EF_OPTS 0xC001
#define EF_PIN 0x1080 #define EF_PIN 0x1080
#define EF_AUTHTOKEN 0x1090 #define EF_AUTHTOKEN 0x1090
#define EF_PAUTHTOKEN 0x1091
#define EF_MINPINLEN 0x1100 #define EF_MINPINLEN 0x1100
#define EF_DEV_CONF 0x1122 #define EF_DEV_CONF 0x1122
#define EF_CRED 0xCF00 // Creds at 0xCF00 - 0xCFFF #define EF_CRED 0xCF00 // Creds at 0xCF00 - 0xCFFF