Parse RSA key at the packer side

This commit is contained in:
whowechina
2026-04-13 23:26:07 +08:00
parent 5c6fcb1078
commit 21f727d8e9
6 changed files with 339 additions and 52 deletions
+2 -9
View File
@@ -1,12 +1,8 @@
set(BTSTACK_ROOT ${PICO_SDK_PATH}/lib/btstack)
set(LWIP_ROOT ${PICO_SDK_PATH}/lib/lwip)
function(make_firmware board board_def)
pico_sdk_init()
add_executable(${board}
main.c slider.c rgb.c button.c savedata.c config.c commands.c
cli.c mpr121.c hebtn.c lzfx.c usb_descriptors.c ps4_feat.c gesture.c
ps4key.c)
ps4key.c crypto/ps4_crypto.c)
target_compile_definitions(${board} PUBLIC ${board_def})
pico_enable_stdio_usb(${board} 1)
pico_enable_stdio_uart(${board} 0)
@@ -15,13 +11,10 @@ function(make_firmware board board_def)
target_compile_options(${board} PRIVATE -Wall -Werror -Wfatal-errors -O3)
target_include_directories(${board} PRIVATE ${CMAKE_CURRENT_LIST_DIR})
target_include_directories(${board} PRIVATE
${BTSTACK_ROOT}/src
${LWIP_ROOT}/src/include)
target_link_libraries(${board} PRIVATE
pico_multicore pico_stdlib hardware_pio hardware_pwm hardware_flash
hardware_adc hardware_i2c hardware_watchdog
hardware_adc hardware_i2c hardware_watchdog pico_rand pico_mbedtls
tinyusb_device tinyusb_board)
pico_add_extra_outputs(${board})
+23 -2
View File
@@ -13,6 +13,7 @@
#include "savedata.h"
#include "cli.h"
#include "ps4key.h"
#include "crypto/ps4_crypto.h"
#include "gesture.h"
@@ -462,8 +463,27 @@ static void handle_ps4key(int argc, char *argv[])
}
savedata_write_global(&key, sizeof(key));
printf("PS4 key stored: Serial: %s, PEM: %d bytes, sig: %d bytes\n",
ps4key_get_serial(&key), key.pem_len - 1, key.sig_len);
printf("PS4 key stored: Serial: %s, sig: %d bytes, N/E/P/Q: %d/%d/%d/%d bytes\n",
ps4key_get_serial(&key),
key.sig_len,
key.rsa_n_len,
key.rsa_e_len,
key.rsa_p_len,
key.rsa_q_len);
}
static void handle_rsa(int argc, char *argv[])
{
(void)argv;
if (argc != 0) {
printf("Usage: rsa\n");
return;
}
if (!ps4_crypto_selftest()) {
printf("PS4 crypto selftest failed.\n");
return;
}
}
void commands_init()
@@ -482,4 +502,5 @@ void commands_init()
cli_register("save", handle_save, "Save config to flash.");
cli_register("factory", handle_factory_reset, "Reset everything to default.");
cli_register("ps4key", handle_ps4key, "Import or clear serialized PS4 key data.");
cli_register("rsa", handle_rsa, "Run hello-world SHA256, nonce SHA256, and RSA context smoke test.");
}
+38
View File
@@ -0,0 +1,38 @@
/* This can be enabled lated when we use SNTP */
#undef MBEDTLS_HAVE_TIME_DATE
#define MBEDTLS_CHECK_PARAMS_ASSERT
#define MBEDTLS_ENTROPY_HARDWARE_ALT
#define MBEDTLS_AES_ROM_TABLES
#define MBEDTLS_AES_FEWER_TABLES
#define MBEDTLS_CAMELLIA_SMALL_MEMORY
#define MBEDTLS_CHECK_RETURN_WARNING
#undef MBEDTLS_FS_IO
#define MBEDTLS_NO_PLATFORM_ENTROPY
#undef MBEDTLS_PSA_CRYPTO_CLIENT
#undef MBEDTLS_PSA_CRYPTO_DRIVERS
#define MBEDTLS_SHA256_C
#define MBEDTLS_RSA_C
#define MBEDTLS_X509_RSASSA_PSS_SUPPORT
#define MBEDTLS_PKCS1_V21
#define MBEDTLS_BIGNUM_C
#define MBEDTLS_MD_C
#define MBEDTLS_SSL_ASYNC_PRIVATE
#define MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH
#undef MBEDTLS_USE_PSA_CRYPTO
#define MBEDTLS_OID_C
#define MBEDTLS_X509_CRT_PARSE_C
#define MBEDTLS_SSL_MAX_FRAGMENT_LENGTH
#define MBEDTLS_X509_USE_C
#define MBEDTLS_ASN1_PARSE_C
#define MBEDTLS_BASE64_C
#define MBEDTLS_PEM_PARSE_C
#define MBEDTLS_PEM_WRITE_C
#define MBEDTLS_PK_PARSE_C
#define MBEDTLS_PK_WRITE_C
#define MBEDTLS_PKCS1_PARSE_C
#define MBEDTLS_PK_C
#undef MBEDTLS_NET_C
#undef MBEDTLS_PSA_CRYPTO_STORAGE_C
#undef MBEDTLS_PSA_ITS_FILE_C
#undef MBEDTLS_TIMING_C
+139 -18
View File
@@ -7,6 +7,9 @@
#include <stdio.h>
#include <string.h>
#include "mbedtls/pk.h"
#include "mbedtls/rsa.h"
#include "ps4key.h"
static uint32_t crc32_calc(const uint8_t *data, size_t size)
@@ -122,10 +125,86 @@ static bool serial_is_valid(const uint8_t *serial, size_t size)
return true;
}
static bool pem_is_valid(const char *pem)
static size_t key_payload_len(const ps4key_t *key)
{
return strstr(pem, "-----BEGIN RSA PRIVATE KEY-----") &&
strstr(pem, "-----END RSA PRIVATE KEY-----");
return key->serial_len + key->sig_len +
key->rsa_n_len + key->rsa_e_len +
key->rsa_p_len + key->rsa_q_len;
}
static size_t sig_offset(const ps4key_t *key)
{
return key->serial_len;
}
static size_t rsa_n_offset(const ps4key_t *key)
{
return sig_offset(key) + key->sig_len;
}
static size_t rsa_e_offset(const ps4key_t *key)
{
return rsa_n_offset(key) + key->rsa_n_len;
}
static size_t rsa_p_offset(const ps4key_t *key)
{
return rsa_e_offset(key) + key->rsa_e_len;
}
static size_t rsa_q_offset(const ps4key_t *key)
{
return rsa_p_offset(key) + key->rsa_p_len;
}
static bool rebuild_pem(const ps4key_t *key, char *pem, size_t pem_size)
{
mbedtls_mpi n;
mbedtls_mpi e;
mbedtls_mpi p;
mbedtls_mpi q;
mbedtls_mpi_init(&n);
mbedtls_mpi_init(&e);
mbedtls_mpi_init(&p);
mbedtls_mpi_init(&q);
int ret = mbedtls_mpi_read_binary(&n, ps4key_get_rsa_n(key), key->rsa_n_len);
if (ret != 0) goto cleanup;
ret = mbedtls_mpi_read_binary(&e, ps4key_get_rsa_e(key), key->rsa_e_len);
if (ret != 0) goto cleanup;
ret = mbedtls_mpi_read_binary(&p, ps4key_get_rsa_p(key), key->rsa_p_len);
if (ret != 0) goto cleanup;
ret = mbedtls_mpi_read_binary(&q, ps4key_get_rsa_q(key), key->rsa_q_len);
if (ret != 0) goto cleanup;
mbedtls_pk_context pk;
mbedtls_pk_init(&pk);
ret = mbedtls_pk_setup(&pk, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA));
if (ret != 0) {
mbedtls_pk_free(&pk);
goto cleanup;
}
mbedtls_rsa_context *rsa = mbedtls_pk_rsa(pk);
mbedtls_rsa_set_padding(rsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA256);
ret = mbedtls_rsa_import(rsa, &n, &p, &q, NULL, &e);
if (ret == 0) {
ret = mbedtls_rsa_complete(rsa);
}
if (ret == 0) {
ret = mbedtls_pk_write_key_pem(&pk, (unsigned char *)pem, pem_size);
}
mbedtls_pk_free(&pk);
cleanup:
mbedtls_mpi_free(&n);
mbedtls_mpi_free(&e);
mbedtls_mpi_free(&p);
mbedtls_mpi_free(&q);
return ret == 0;
}
static void set_error(const char **error, const char *message)
@@ -149,13 +228,16 @@ static bool ps4key_validate(const ps4key_t *key, const char **error)
}
if ((key->serial_len != (PS4KEY_SERIAL_LENGTH + 1)) ||
(key->pem_len <= 1) || (key->pem_len > (PS4KEY_PEM_MAX_LENGTH + 1)) ||
(key->sig_len == 0) || (key->sig_len > PS4KEY_SIG_MAX_LENGTH)) {
(key->sig_len != PS4KEY_SIG_LENGTH) ||
(key->rsa_n_len != PS4KEY_RSA_N_LENGTH) ||
(key->rsa_e_len == 0) || (key->rsa_e_len > PS4KEY_RSA_E_LENGTH) ||
(key->rsa_p_len != PS4KEY_RSA_P_LENGTH) ||
(key->rsa_q_len != PS4KEY_RSA_Q_LENGTH)) {
set_error(error, "Serialized part lengths are invalid.");
return false;
}
size_t payload_len = key->serial_len + key->pem_len + key->sig_len;
size_t payload_len = key_payload_len(key);
if (payload_len > sizeof(key->payload)) {
set_error(error, "Serialized length mismatch.");
return false;
@@ -171,14 +253,9 @@ static bool ps4key_validate(const ps4key_t *key, const char **error)
return false;
}
const uint8_t *pem_ptr = key->payload + key->serial_len;
if (pem_ptr[key->pem_len - 1] != '\0') {
set_error(error, "PEM data is not null terminated.");
return false;
}
if (!pem_is_valid((const char *)pem_ptr)) {
set_error(error, "PEM content does not look like an RSA private key.");
size_t rsa_q_end = rsa_q_offset(key) + key->rsa_q_len;
if (rsa_q_end != payload_len) {
set_error(error, "Serialized layout mismatch.");
return false;
}
@@ -211,7 +288,7 @@ bool ps4key_parse_text(const char *text, ps4key_t *key, const char **error)
return false;
}
const ps4key_t *src = (const ps4key_t *)decoded;
size_t payload_len = src->serial_len + src->pem_len + src->sig_len;
size_t payload_len = key_payload_len(src);
if (decoded_len != header_len + payload_len) {
set_error(error, "Serialized length mismatch.");
return false;
@@ -238,10 +315,22 @@ const char *ps4key_get_serial(const ps4key_t *key)
const char *ps4key_get_pem(const ps4key_t *key)
{
if (key == NULL) {
static char pem[2048] = {0};
static uint32_t last_crc = 0;
if ((key == NULL) || !ps4key_key_valid(key)) {
return NULL;
}
return (const char *)(key->payload + key->serial_len);
if ((last_crc != key->crc32) || (pem[0] == '\0')) {
memset(pem, 0, sizeof(pem));
if (!rebuild_pem(key, pem, sizeof(pem))) {
return NULL;
}
last_crc = key->crc32;
}
return pem;
}
const uint8_t *ps4key_get_sig(const ps4key_t *key)
@@ -249,7 +338,7 @@ const uint8_t *ps4key_get_sig(const ps4key_t *key)
if (key == NULL) {
return NULL;
}
return key->payload + key->serial_len + key->pem_len;
return key->payload + sig_offset(key);
}
size_t ps4key_get_sig_len(const ps4key_t *key)
@@ -259,3 +348,35 @@ size_t ps4key_get_sig_len(const ps4key_t *key)
}
return key->sig_len;
}
const uint8_t *ps4key_get_rsa_n(const ps4key_t *key)
{
if (key == NULL) {
return NULL;
}
return key->payload + rsa_n_offset(key);
}
const uint8_t *ps4key_get_rsa_e(const ps4key_t *key)
{
if (key == NULL) {
return NULL;
}
return key->payload + rsa_e_offset(key);
}
const uint8_t *ps4key_get_rsa_p(const ps4key_t *key)
{
if (key == NULL) {
return NULL;
}
return key->payload + rsa_p_offset(key);
}
const uint8_t *ps4key_get_rsa_q(const ps4key_t *key)
{
if (key == NULL) {
return NULL;
}
return key->payload + rsa_q_offset(key);
}
+16 -4
View File
@@ -14,17 +14,25 @@
#define PS4KEY_STORAGE_MAGIC 0x4b345350u
#define PS4KEY_STORAGE_VERSION 1u
#define PS4KEY_SERIAL_LENGTH 16
#define PS4KEY_PEM_MAX_LENGTH 2048
#define PS4KEY_SIG_MAX_LENGTH 512
#define PS4KEY_PAYLOAD_MAX_LENGTH (PS4KEY_SERIAL_LENGTH + 1 + PS4KEY_PEM_MAX_LENGTH + 1 + PS4KEY_SIG_MAX_LENGTH)
#define PS4KEY_SIG_LENGTH 256
#define PS4KEY_RSA_N_LENGTH 256
#define PS4KEY_RSA_E_LENGTH 4
#define PS4KEY_RSA_P_LENGTH 128
#define PS4KEY_RSA_Q_LENGTH 128
#define PS4KEY_PAYLOAD_MAX_LENGTH (PS4KEY_SERIAL_LENGTH + 1 + PS4KEY_SIG_LENGTH + \
PS4KEY_RSA_N_LENGTH + PS4KEY_RSA_E_LENGTH + \
PS4KEY_RSA_P_LENGTH + PS4KEY_RSA_Q_LENGTH)
typedef struct __attribute__((packed)) {
uint32_t magic;
uint8_t version;
uint8_t reserved;
uint16_t serial_len;
uint16_t pem_len;
uint16_t sig_len;
uint16_t rsa_n_len;
uint16_t rsa_e_len;
uint16_t rsa_p_len;
uint16_t rsa_q_len;
uint32_t crc32;
uint8_t payload[PS4KEY_PAYLOAD_MAX_LENGTH];
} ps4key_t;
@@ -36,5 +44,9 @@ const char *ps4key_get_serial(const ps4key_t *key);
const char *ps4key_get_pem(const ps4key_t *key);
const uint8_t *ps4key_get_sig(const ps4key_t *key);
size_t ps4key_get_sig_len(const ps4key_t *key);
const uint8_t *ps4key_get_rsa_n(const ps4key_t *key);
const uint8_t *ps4key_get_rsa_e(const ps4key_t *key);
const uint8_t *ps4key_get_rsa_p(const ps4key_t *key);
const uint8_t *ps4key_get_rsa_q(const ps4key_t *key);
#endif
+121 -19
View File
@@ -278,8 +278,11 @@
<script>
const PREFIX = "PS4K1";
const SERIAL_LENGTH = 16;
const PEM_MAX_LENGTH = 2048;
const SIG_MAX_LENGTH = 512;
const SIG_LENGTH = 256;
const RSA_N_LENGTH = 256;
const RSA_E_LENGTH = 4;
const RSA_P_LENGTH = 128;
const RSA_Q_LENGTH = 128;
const sigFileInput = document.getElementById("sigFile");
const serialFileInput = document.getElementById("serialFile");
@@ -320,8 +323,8 @@
if (!sigBytes || sigBytes.length === 0) {
throw new Error("Please upload the signature.bin file.");
}
if (sigBytes.length > SIG_MAX_LENGTH) {
throw new Error(`signature.bin is too large, max ${SIG_MAX_LENGTH} bytes.`);
if (sigBytes.length !== SIG_LENGTH) {
throw new Error(`signature.bin must be exactly ${SIG_LENGTH} bytes.`);
}
if (!/^\d{16}$/.test(serial)) {
throw new Error("serial.txt must be a 16-digit decimal string.");
@@ -330,9 +333,98 @@
!pem.includes("-----END RSA PRIVATE KEY-----")) {
throw new Error("key.pem must contain BEGIN/END RSA PRIVATE KEY markers.");
}
if (pem.length === 0 || pem.length > PEM_MAX_LENGTH) {
throw new Error(`key.pem length must be 1-${PEM_MAX_LENGTH} characters.`);
}
function base64ToBytes(text) {
const binary = atob(text.replace(/\s+/g, ""));
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes;
}
function parseLength(bytes, pos) {
if (pos >= bytes.length) {
throw new Error("Invalid DER: length overflow.");
}
const first = bytes[pos++];
if ((first & 0x80) === 0) {
return { len: first, pos };
}
const count = first & 0x7f;
if (count === 0 || count > 4 || pos + count > bytes.length) {
throw new Error("Invalid DER: unsupported length.");
}
let len = 0;
for (let i = 0; i < count; i++) {
len = (len << 8) | bytes[pos++];
}
return { len, pos };
}
function readTlv(bytes, pos, expectedTag) {
if (pos >= bytes.length || bytes[pos] !== expectedTag) {
throw new Error(`Invalid DER: expected tag 0x${expectedTag.toString(16)}.`);
}
pos += 1;
const parsed = parseLength(bytes, pos);
const len = parsed.len;
pos = parsed.pos;
const end = pos + len;
if (end > bytes.length) {
throw new Error("Invalid DER: truncated value.");
}
return { value: bytes.subarray(pos, end), end };
}
function trimInteger(bytes) {
let i = 0;
while (i < bytes.length - 1 && bytes[i] === 0) {
i += 1;
}
return bytes.subarray(i);
}
function toFixedWidth(bytes, width, name) {
const value = trimInteger(bytes);
if (value.length > width) {
throw new Error(`${name} is too large (${value.length} > ${width}).`);
}
const out = new Uint8Array(width);
out.set(value, width - value.length);
return out;
}
function parsePkcs1PrivateKey(pem) {
const base64 = pem
.replace(/-----BEGIN RSA PRIVATE KEY-----/g, "")
.replace(/-----END RSA PRIVATE KEY-----/g, "")
.replace(/\s+/g, "");
const der = base64ToBytes(base64);
const seq = readTlv(der, 0, 0x30);
let pos = 0;
const body = seq.value;
const version = readTlv(body, pos, 0x02);
pos = version.end;
const n = readTlv(body, pos, 0x02);
pos = n.end;
const e = readTlv(body, pos, 0x02);
pos = e.end;
const d = readTlv(body, pos, 0x02);
pos = d.end;
const p = readTlv(body, pos, 0x02);
pos = p.end;
const q = readTlv(body, pos, 0x02);
return {
n: toFixedWidth(n.value, RSA_N_LENGTH, "rsaN"),
e: toFixedWidth(e.value, RSA_E_LENGTH, "rsaE"),
p: toFixedWidth(p.value, RSA_P_LENGTH, "rsaP"),
q: toFixedWidth(q.value, RSA_Q_LENGTH, "rsaQ"),
};
}
function crc32(bytes) {
@@ -356,28 +448,38 @@
return btoa(binary);
}
function packPayload(serial, pem, sigBytes) {
function packPayload(serial, sigBytes, rsa) {
const serialBytes = encoder.encode(serial);
const pemBytes = encoder.encode(pem);
const serialLen = serialBytes.length + 1;
const pemLen = pemBytes.length + 1;
const payloadLength = serialLen + pemLen + sigBytes.length;
const payloadLength = serialLen + sigBytes.length +
rsa.n.length + rsa.e.length +
rsa.p.length + rsa.q.length;
const payload = new Uint8Array(payloadLength);
payload.set(serialBytes, 0);
payload[serialBytes.length] = 0;
payload.set(pemBytes, serialLen);
payload[serialLen + pemBytes.length] = 0;
payload.set(sigBytes, serialLen + pemLen);
let offset = serialLen;
payload.set(sigBytes, offset);
offset += sigBytes.length;
payload.set(rsa.n, offset);
offset += rsa.n.length;
payload.set(rsa.e, offset);
offset += rsa.e.length;
payload.set(rsa.p, offset);
offset += rsa.p.length;
payload.set(rsa.q, offset);
const header = new Uint8Array(16);
const header = new Uint8Array(22);
header.set([0x50, 0x53, 0x34, 0x4b], 0);
header[4] = 1;
header[5] = 0;
const view = new DataView(header.buffer);
view.setUint16(6, serialLen, true);
view.setUint16(8, pemLen, true);
view.setUint16(10, sigBytes.length, true);
view.setUint32(12, crc32(payload), true);
view.setUint16(8, sigBytes.length, true);
view.setUint16(10, rsa.n.length, true);
view.setUint16(12, rsa.e.length, true);
view.setUint16(14, rsa.p.length, true);
view.setUint16(16, rsa.q.length, true);
view.setUint32(18, crc32(payload), true);
const packed = new Uint8Array(header.length + payload.length);
packed.set(header, 0);
@@ -437,8 +539,8 @@
const pem = pemText ? normalizePem(pemText) : "";
assertInputs(serial, pem, sigBytes);
const packed = packPayload(serial, pem, sigBytes);
const rsa = parsePkcs1PrivateKey(pem);
const packed = packPayload(serial, sigBytes, rsa);
output.value = packed;
setStatus("ok", `Packed successfully, total ${packed.length} chars.`);
} catch (error) {