// SPDX-License-Identifier: Apache-2.0 #include #include #include #include #include #include #include #include #include #include #include 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(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(derLength >> 8); plain[1] = static_cast(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(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(-result)); return false; } // The DER is written at the end of the buffer *outLength = static_cast(result); *outDer = buffer + bufferSize - *outLength; return true; } bool generate(const char* directory) { LOG_I(TAG, "Generating device certificate"); auto* buffer = static_cast(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(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(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; }