mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-10-17 10:55:06 +00:00
239 lines
9.2 KiB
C++
239 lines
9.2 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
#include <development/credentials.h>
|
|
|
|
#include <crypt/crypt.h>
|
|
|
|
#include <tactility/filesystem/fs.h>
|
|
#include <tactility/log.h>
|
|
#include <tactility/memory.h>
|
|
#include <tactility/paths.h>
|
|
|
|
#include <mbedtls/platform_util.h>
|
|
#include <mbedtls/x509_crt.h>
|
|
#include <psa/crypto.h>
|
|
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
|
|
namespace {
|
|
|
|
constexpr auto* TAG = "DevelopmentCredentials";
|
|
|
|
constexpr auto* KEY_FILE = "server.key";
|
|
constexpr auto* CERTIFICATE_FILE = "server.crt";
|
|
constexpr auto* SUBJECT_NAME = "CN=Tactility,O=Tactility";
|
|
|
|
constexpr size_t IV_SIZE = 16;
|
|
constexpr size_t BLOCK_SIZE = 16;
|
|
constexpr size_t KEY_DER_MAX = 256;
|
|
constexpr size_t KEY_FILE_MAX = IV_SIZE + KEY_DER_MAX + BLOCK_SIZE;
|
|
constexpr size_t CERTIFICATE_DER_MAX = 1024;
|
|
|
|
constexpr MemoryPolicy MEMORY_POLICY = { .required = 0, .desired = MEMORY_CAPABILITY_EXTERNAL, .alignment = 0 };
|
|
|
|
bool getPath(const char* directory, const char* file, char* outPath, size_t outSize) {
|
|
const int written = snprintf(outPath, outSize, "%s/%s", directory, file);
|
|
return written > 0 && static_cast<size_t>(written) < outSize;
|
|
}
|
|
|
|
bool writeFile(const char* directory, const char* file, const uint8_t* data, size_t length) {
|
|
char path[FILE_MAX_PATH_STRING_LENGTH];
|
|
if (!getPath(directory, file, path, sizeof(path))) {
|
|
return false;
|
|
}
|
|
FILE* handle = fopen(path, "wb");
|
|
if (handle == nullptr) {
|
|
LOG_E(TAG, "Failed to open %s", path);
|
|
return false;
|
|
}
|
|
const bool written = fwrite(data, 1, length, handle) == length;
|
|
return fclose(handle) == 0 && written;
|
|
}
|
|
|
|
bool readFile(const char* directory, const char* file, uint8_t* data, size_t* inOutLength) {
|
|
char path[FILE_MAX_PATH_STRING_LENGTH];
|
|
return getPath(directory, file, path, sizeof(path)) && file_read_binary(path, data, inOutLength) == ERROR_NONE;
|
|
}
|
|
|
|
// File layout: IV, then the encrypted [u16 big-endian DER length][DER][zero padding]
|
|
bool writeKey(const char* directory, const uint8_t* der, size_t derLength) {
|
|
uint8_t plain[KEY_DER_MAX + BLOCK_SIZE] = {};
|
|
plain[0] = static_cast<uint8_t>(derLength >> 8);
|
|
plain[1] = static_cast<uint8_t>(derLength & 0xFF);
|
|
memcpy(plain + 2, der, derLength);
|
|
const size_t encryptedLength = ((derLength + 2 + BLOCK_SIZE - 1) / BLOCK_SIZE) * BLOCK_SIZE;
|
|
|
|
uint8_t file[KEY_FILE_MAX];
|
|
crypt_generate_iv(file);
|
|
const bool encrypted = crypt_encrypt(file, plain, file + IV_SIZE, encryptedLength) == 0;
|
|
mbedtls_platform_zeroize(plain, sizeof(plain));
|
|
return encrypted && writeFile(directory, KEY_FILE, file, IV_SIZE + encryptedLength);
|
|
}
|
|
|
|
bool readKey(const char* directory, uint8_t* outDer, size_t* outLength) {
|
|
uint8_t file[KEY_FILE_MAX];
|
|
size_t fileLength = sizeof(file);
|
|
if (!readFile(directory, KEY_FILE, file, &fileLength) || fileLength <= IV_SIZE || (fileLength - IV_SIZE) % BLOCK_SIZE != 0) {
|
|
return false;
|
|
}
|
|
uint8_t plain[KEY_DER_MAX + BLOCK_SIZE];
|
|
const size_t encryptedLength = fileLength - IV_SIZE;
|
|
if (crypt_decrypt(file, file + IV_SIZE, plain, encryptedLength) != 0) {
|
|
return false;
|
|
}
|
|
const size_t derLength = (static_cast<size_t>(plain[0]) << 8) | plain[1];
|
|
const bool valid = derLength > 0 && derLength + 2 <= encryptedLength;
|
|
if (valid) {
|
|
memcpy(outDer, plain + 2, derLength);
|
|
*outLength = derLength;
|
|
}
|
|
mbedtls_platform_zeroize(plain, sizeof(plain));
|
|
return valid;
|
|
}
|
|
|
|
bool generateKey(mbedtls_pk_context* pk) {
|
|
psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
|
|
psa_set_key_type(&attributes, PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1));
|
|
psa_set_key_bits(&attributes, 256);
|
|
psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_SIGN_HASH | PSA_KEY_USAGE_EXPORT);
|
|
psa_set_key_algorithm(&attributes, PSA_ALG_ECDSA(PSA_ALG_SHA_256));
|
|
|
|
mbedtls_svc_key_id_t keyId = MBEDTLS_SVC_KEY_ID_INIT;
|
|
if (psa_generate_key(&attributes, &keyId) != PSA_SUCCESS) {
|
|
return false;
|
|
}
|
|
const bool copied = mbedtls_pk_copy_from_psa(keyId, pk) == 0;
|
|
psa_destroy_key(keyId);
|
|
return copied;
|
|
}
|
|
|
|
bool writeCertificate(mbedtls_pk_context* pk, uint8_t* buffer, size_t bufferSize, const uint8_t** outDer, size_t* outLength) {
|
|
uint8_t serial[16];
|
|
if (psa_generate_random(serial, sizeof(serial)) != PSA_SUCCESS) {
|
|
return false;
|
|
}
|
|
// Keeps the serial number positive
|
|
serial[0] &= 0x7F;
|
|
|
|
mbedtls_x509write_cert writer;
|
|
mbedtls_x509write_crt_init(&writer);
|
|
mbedtls_x509write_crt_set_version(&writer, MBEDTLS_X509_CRT_VERSION_3);
|
|
mbedtls_x509write_crt_set_md_alg(&writer, MBEDTLS_MD_SHA256);
|
|
mbedtls_x509write_crt_set_subject_key(&writer, pk);
|
|
mbedtls_x509write_crt_set_issuer_key(&writer, pk);
|
|
int result = mbedtls_x509write_crt_set_serial_raw(&writer, serial, sizeof(serial));
|
|
if (result == 0) result = mbedtls_x509write_crt_set_subject_name(&writer, SUBJECT_NAME);
|
|
if (result == 0) result = mbedtls_x509write_crt_set_issuer_name(&writer, SUBJECT_NAME);
|
|
// Devices often have no real-time clock, so the validity covers any plausible date
|
|
if (result == 0) result = mbedtls_x509write_crt_set_validity(&writer, "20000101000000", "20991231235959");
|
|
if (result == 0) result = mbedtls_x509write_crt_der(&writer, buffer, bufferSize);
|
|
mbedtls_x509write_crt_free(&writer);
|
|
if (result <= 0) {
|
|
LOG_E(TAG, "Failed to write certificate: -0x%04x", static_cast<unsigned>(-result));
|
|
return false;
|
|
}
|
|
// The DER is written at the end of the buffer
|
|
*outLength = static_cast<size_t>(result);
|
|
*outDer = buffer + bufferSize - *outLength;
|
|
return true;
|
|
}
|
|
|
|
bool generate(const char* directory) {
|
|
LOG_I(TAG, "Generating device certificate");
|
|
auto* buffer = static_cast<uint8_t*>(memory_alloc_with_policy(KEY_DER_MAX + CERTIFICATE_DER_MAX, &MEMORY_POLICY));
|
|
if (buffer == nullptr) {
|
|
return false;
|
|
}
|
|
uint8_t* keyBuffer = buffer;
|
|
uint8_t* certificateBuffer = buffer + KEY_DER_MAX;
|
|
|
|
mbedtls_pk_context pk;
|
|
mbedtls_pk_init(&pk);
|
|
bool success = generateKey(&pk);
|
|
|
|
const uint8_t* certificateDer = nullptr;
|
|
size_t certificateLength = 0;
|
|
success = success && writeCertificate(&pk, certificateBuffer, CERTIFICATE_DER_MAX, &certificateDer, &certificateLength);
|
|
|
|
int keyLength = success ? mbedtls_pk_write_key_der(&pk, keyBuffer, KEY_DER_MAX) : 0;
|
|
success = keyLength > 0 &&
|
|
writeKey(directory, keyBuffer + KEY_DER_MAX - keyLength, static_cast<size_t>(keyLength)) &&
|
|
writeFile(directory, CERTIFICATE_FILE, certificateDer, certificateLength);
|
|
|
|
mbedtls_pk_free(&pk);
|
|
mbedtls_platform_zeroize(keyBuffer, KEY_DER_MAX);
|
|
memory_free(buffer);
|
|
return success;
|
|
}
|
|
|
|
bool parse(const char* directory, DevelopmentCredentials* credentials) {
|
|
auto* buffer = static_cast<uint8_t*>(memory_alloc_with_policy(KEY_DER_MAX + CERTIFICATE_DER_MAX, &MEMORY_POLICY));
|
|
if (buffer == nullptr) {
|
|
return false;
|
|
}
|
|
uint8_t* keyDer = buffer;
|
|
uint8_t* certificateDer = buffer + KEY_DER_MAX;
|
|
size_t keyLength = 0;
|
|
size_t certificateLength = CERTIFICATE_DER_MAX;
|
|
const bool success = readKey(directory, keyDer, &keyLength) &&
|
|
readFile(directory, CERTIFICATE_FILE, certificateDer, &certificateLength) &&
|
|
mbedtls_pk_parse_key(&credentials->key, keyDer, keyLength, nullptr, 0) == 0 &&
|
|
mbedtls_x509_crt_parse_der(&credentials->certificate, certificateDer, certificateLength) == 0 &&
|
|
development_fingerprint(certificateDer, certificateLength, credentials->fingerprint) == ERROR_NONE;
|
|
mbedtls_platform_zeroize(keyDer, KEY_DER_MAX);
|
|
memory_free(buffer);
|
|
return success;
|
|
}
|
|
|
|
void reset(DevelopmentCredentials* credentials) {
|
|
development_credentials_free(credentials);
|
|
mbedtls_x509_crt_init(&credentials->certificate);
|
|
mbedtls_pk_init(&credentials->key);
|
|
credentials->fingerprint[0] = '\0';
|
|
}
|
|
|
|
} // namespace
|
|
|
|
error_t development_credentials_load(const char* directory, DevelopmentCredentials* credentials) {
|
|
mbedtls_x509_crt_init(&credentials->certificate);
|
|
mbedtls_pk_init(&credentials->key);
|
|
credentials->fingerprint[0] = '\0';
|
|
|
|
if (psa_crypto_init() != PSA_SUCCESS) {
|
|
return ERROR_RESOURCE;
|
|
}
|
|
|
|
if (parse(directory, credentials)) {
|
|
return ERROR_NONE;
|
|
}
|
|
reset(credentials);
|
|
|
|
const error_t directoryError = directory_make(directory, true);
|
|
if (directoryError != ERROR_NONE && directoryError != ERROR_ALREADY_EXISTS) {
|
|
LOG_E(TAG, "Failed to create %s", directory);
|
|
return ERROR_RESOURCE;
|
|
}
|
|
if (!generate(directory) || !parse(directory, credentials)) {
|
|
LOG_E(TAG, "Failed to create device certificate");
|
|
return ERROR_RESOURCE;
|
|
}
|
|
return ERROR_NONE;
|
|
}
|
|
|
|
void development_credentials_free(DevelopmentCredentials* credentials) {
|
|
mbedtls_x509_crt_free(&credentials->certificate);
|
|
mbedtls_pk_free(&credentials->key);
|
|
}
|
|
|
|
error_t development_fingerprint(const uint8_t* der, size_t length, char out_fingerprint[DEVELOPMENT_SERVER_FINGERPRINT_LENGTH + 1]) {
|
|
uint8_t hash[32];
|
|
size_t hashLength = 0;
|
|
if (psa_hash_compute(PSA_ALG_SHA_256, der, length, hash, sizeof(hash), &hashLength) != PSA_SUCCESS || hashLength != sizeof(hash)) {
|
|
return ERROR_RESOURCE;
|
|
}
|
|
for (size_t i = 0; i < sizeof(hash); i++) {
|
|
snprintf(&out_fingerprint[i * 2], 3, "%02x", hash[i]);
|
|
}
|
|
return ERROR_NONE;
|
|
}
|