What is the best way to securely store TLS credentials on my nRF9151 outside of the modem flash, on persistent (ideally overwrite protected) non volatile flash?

Issue:

There seem to be plenty of ways to securely store TLS credentials (generated off the device), or any sort of string data, in persistent storage on an nRF91x device. I would like to know what the recommended and simplest approach is to do this for my application. 

We are using an Ethernet network interface along with the nRF9151 internal LTE modem, to communicate with an AWS IoT MQTT broker. I have found out that credentials stored on the modem flash are NOT accessible to the application core, and trying to use AT commands to read these credentials does not work (which makes sense, that would be a big security vulnerability).

I would like to use the same credentials for both the Ethernet and LTE interfaces, and thus it appears I will have to store a copy of the credentials somewhere outside of the modem flash. Ideally I want to use the simplest and most NCS / Zephyr idomatic way to do this, while keeping code bloat from added libraries as small as possible. 


What I've done:


I have tried to use the PSA Protected storage library, , following this sample code, but it seems like the keys are too large. The sample works fine on my nRF9151DK board out-of-the-box, but when I replace the test strings with my key, the program crashes only only console logs the boot header. The TF-M PSA Protected Storage README mentions that the PS_MAX_ASSET_SIZE value is how to set the size of a value in the PSA, but I have tried to use CONFIG_PSA_ASSET_SIZE=2048, and it appears this Kconfig symbol does not exist.

psa_protected_storage/prj.conf:10: warning: attempt to assign the value '2048' to the undefined symbol PS_MAX_ASSET_SIZE

I've also looked over the PSA_crypto sample, although that appears to be for handling cyptography and device cert signing, rather than just key-value persistent storage of private data. 

I've also read the Key storage in the nRF Connect SDK docs, and they mendion using the PSA crypto API, but I am not sure this is what I am looking for, or if this is the best approach to accomplish this. 

Given psa_import_key() fxn seems to import a key in binary format, I am not sure the *.pem type certs would work with this. 


I was thinking I could potentially create a seperate 'credential flashing' application, which will run at the factory when device firmware is initally flashed and modem firmware is updated, and put the keys in a seperate  directory and source file (like the zephyr aws iot mqtt sample seems to do), and then run the 'credential flashing' application to take those keys and write them to  device persistent storage. 

It seems like however I get the keys stored on the device securely, at the beginning of the devices life I can use the tls_credentials lib to pull the keys into RAM and assign a security tag to them, allowing me to configure the mqtt clients tls config struct accordingly. As a reference this is the fxn used in the zephyr aws iot mqtt sample:

static int setup_credentials(void)
{
	int ret;

	ret = tls_credential_add(TLS_TAG_DEVICE_CERTIFICATE, TLS_CREDENTIAL_SERVER_CERTIFICATE,
				 public_cert, public_cert_len);
	if (ret < 0) {
		LOG_ERR("Failed to add device certificate: %d", ret);
		goto exit;
	}

	ret = tls_credential_add(TLS_TAG_DEVICE_PRIVATE_KEY, TLS_CREDENTIAL_PRIVATE_KEY,
				 private_key, private_key_len);
	if (ret < 0) {
		LOG_ERR("Failed to add device private key: %d", ret);
		goto exit;
	}

	ret = tls_credential_add(TLS_TAG_AWS_CA_CERTIFICATE, TLS_CREDENTIAL_CA_CERTIFICATE, ca_cert,
				 ca_cert_len);
	if (ret < 0) {
		LOG_ERR("Failed to add device private key: %d", ret);
		goto exit;
	}

exit:
	return ret;
}

It seems like its 'left to the reader' to figure out how to place the credentials in secure persistent memory / non volatile flash, but however its done, the above approach should allow me to use  them with an mqtt client. 

Also for reference, I have been following the nRF AWS IoT docs on credential generation (nRF91: Keys generated by AWS), and this has worked fine so far with LTE (because the credentials are then flashed to modem firmware using the nrfcredstore cli utility - but these are not accessible to the application core code).


Dev setup:

OS: Linux Ubuntu 24.04.1

Board: nrf9151DK HW v0.9.0 , AND our custom board built around the nRF9151 SiP 

SDK:  nRF Connect SDK V3.1.0 


All advice, support, and feedback is greatly appreciated!

Thank you Devzone team :) 

Parents
  • Hi,

    You can refer to the Crypto: PSA TLS sample for how to use TLS in the application. The sample demonstarte the certificate stored i TF-M Protected Storage, which reside in a separate partition. When doing DFU it is always essential to use static paritioning, so that the partition layout is the same in subsequent firwmare versions, and as long as you do, the data will not be overwritten or corrupted during of after a FOTA operation. See Static and dynamic configuration.

  • Thank you for pointing me in that direction.


    After many headaches, I am still not able to securely store and eventually use TLS credentials ... 

    Unfortunately I just now noticed that the 'boards' directory in the crypto PSA TLS example includes a config file for the nRF9151 (which we are using) that DISABLES the modem firmware lib .... see this here

    As originally mentioned in my post our teams intention is to use BOTH the Modem, and a seperate Ethernet network interface on our device. The conf file I found suggests that it is not possible to use the Crypto PSA TLS library while the modem firmware in enabled. 

    I have managed to setup a seperate 'credential flashing / provisioning' application, which I planned on running once when the device is still in the factory initially being flashed, to write the credentials to a static partition. I was able to successfully set this up, only now to find out that it seems like I can't even use the PSA crypto library if the nRF modem library is enabled.

    See the credential flashing / provisioning code here:

    #include <errno.h>
    #include <zephyr/kernel.h>
    #include <zephyr/logging/log.h>
    
    /* TF-M and PSA headers */
    #include <psa/crypto.h>
    #include <psa/protected_storage.h>
    #include <psa/storage_common.h>
    #include <tfm_ns_interface.h>
    
    /* Include generated credential files */
    #include "creds/creds.h"
    
    LOG_MODULE_REGISTER(aws_cred_provisioning, LOG_LEVEL_INF);
    
    /* PSA Protected Storage UIDs for AWS IoT credentials */
    #define PSA_PS_AWS_CA_CERT_UID 0x00000001
    #define PSA_PS_AWS_CLIENT_CERT_UID 0x00000002
    #define PSA_PS_AWS_PRIVATE_KEY_UID 0x00000003
    
    /* Application return codes */
    #define APP_SUCCESS 0
    #define APP_ERROR_PSA_INIT -1
    #define APP_ERROR_STORAGE_FAILED -2
    
    /**
     * @brief Initialize TF-M and PSA interfaces
     *
     * @return APP_SUCCESS on success, APP_ERROR_PSA_INIT on failure
     */
    static int initialize_tfm_psa(void)
    {
    	psa_status_t status;
    
    	LOG_INF("Initializing TF-M non-secure interface...");
    
    	/* Initialize TF-M non-secure interface */
    	status = tfm_ns_interface_init();
    	if (status != PSA_SUCCESS) {
    		LOG_ERR("Failed to initialize TF-M NS interface: %d", status);
    		return APP_ERROR_PSA_INIT;
    	}
    
    	/* Initialize PSA Crypto (required for TF-M storage services) */
    	status = psa_crypto_init();
    	if (status != PSA_SUCCESS) {
    		LOG_ERR("Failed to initialize PSA Crypto: %d", status);
    		return APP_ERROR_PSA_INIT;
    	}
    
    	LOG_INF("TF-M and PSA services initialized successfully");
    	return APP_SUCCESS;
    }
    
    /**
     * @brief Store AWS CA certificate in TF-M Protected Storage
     *
     * @return APP_SUCCESS on success, APP_ERROR_STORAGE_FAILED on failure
     */
    static int store_ca_certificate(void)
    {
    	psa_status_t status;
    
    	LOG_INF("Storing AWS CA certificate in TF-M Protected Storage...");
    	LOG_INF("CA certificate size: %u bytes", ca_cert_len);
    
    	status =
    	    psa_ps_set(PSA_PS_AWS_CA_CERT_UID, ca_cert_len, ca_cert, PSA_STORAGE_FLAG_WRITE_ONCE);
    
    	if (status == PSA_ERROR_NOT_PERMITTED) {
    		LOG_INF("AWS CA certificate already exists in Protected Storage");
    		return APP_SUCCESS;
    	} else if (status != PSA_SUCCESS) {
    		LOG_ERR("Failed to store AWS CA certificate. Status: %d", status);
    		return APP_ERROR_STORAGE_FAILED;
    	}
    
    	LOG_INF("AWS CA certificate stored successfully");
    	return APP_SUCCESS;
    }
    
    /**
     * @brief Store AWS client certificate in TF-M Protected Storage
     *
     * @return APP_SUCCESS on success, APP_ERROR_STORAGE_FAILED on failure
     */
    static int store_client_certificate(void)
    {
    	psa_status_t status;
    
    	LOG_INF("Storing AWS client certificate in TF-M Protected Storage...");
    	LOG_INF("Client certificate size: %u bytes", public_cert_len);
    
    	status = psa_ps_set(PSA_PS_AWS_CLIENT_CERT_UID, public_cert_len, public_cert,
    			    PSA_STORAGE_FLAG_WRITE_ONCE);
    
    	if (status == PSA_ERROR_NOT_PERMITTED) {
    		LOG_INF("AWS client certificate already exists in Protected Storage");
    		return APP_SUCCESS;
    	} else if (status != PSA_SUCCESS) {
    		LOG_ERR("Failed to store AWS client certificate. Status: %d", status);
    		return APP_ERROR_STORAGE_FAILED;
    	}
    
    	LOG_INF("AWS client certificate stored successfully");
    	return APP_SUCCESS;
    }
    
    /**
     * @brief Store AWS private key in TF-M Protected Storage
     *
     * @return APP_SUCCESS on success, APP_ERROR_STORAGE_FAILED on failure
     */
    static int store_private_key(void)
    {
    	psa_status_t status;
    
    	LOG_INF("Storing AWS private key in TF-M Protected Storage...");
    	LOG_INF("Private key size: %u bytes", private_key_len);
    
    	status = psa_ps_set(PSA_PS_AWS_PRIVATE_KEY_UID, private_key_len, private_key,
    			    PSA_STORAGE_FLAG_WRITE_ONCE);
    
    	if (status == PSA_ERROR_NOT_PERMITTED) {
    		LOG_INF("AWS private key already exists in Protected Storage");
    		return APP_SUCCESS;
    	} else if (status != PSA_SUCCESS) {
    		LOG_ERR("Failed to store AWS private key. Status: %d", status);
    		return APP_ERROR_STORAGE_FAILED;
    	}
    
    	LOG_INF("AWS private key stored successfully");
    	return APP_SUCCESS;
    }
    
    /**
     * @brief Verify stored credentials by checking their existence and sizes
     *
     * @return APP_SUCCESS on success, APP_ERROR_STORAGE_FAILED on failure
     */
    static int verify_stored_credentials(void)
    {
    	psa_status_t status;
    	struct psa_storage_info_t info;
    
    	LOG_INF("Verifying stored credentials in TF-M Protected Storage...");
    
    	/* Verify CA certificate */
    	status = psa_ps_get_info(PSA_PS_AWS_CA_CERT_UID, &info);
    	if (status != PSA_SUCCESS) {
    		LOG_ERR("Failed to verify CA certificate storage. Status: %d", status);
    		return APP_ERROR_STORAGE_FAILED;
    	}
    	LOG_INF("CA certificate verified: %u bytes, flags: 0x%x", info.size, info.flags);
    
    	/* Verify client certificate */
    	status = psa_ps_get_info(PSA_PS_AWS_CLIENT_CERT_UID, &info);
    	if (status != PSA_SUCCESS) {
    		LOG_ERR("Failed to verify client certificate storage. Status: %d", status);
    		return APP_ERROR_STORAGE_FAILED;
    	}
    	LOG_INF("Client certificate verified: %u bytes, flags: 0x%x", info.size, info.flags);
    
    	/* Verify private key */
    	status = psa_ps_get_info(PSA_PS_AWS_PRIVATE_KEY_UID, &info);
    	if (status != PSA_SUCCESS) {
    		LOG_ERR("Failed to verify private key storage. Status: %d", status);
    		return APP_ERROR_STORAGE_FAILED;
    	}
    	LOG_INF("Private key verified: %u bytes, flags: 0x%x", info.size, info.flags);
    
    	LOG_INF("All credentials verified successfully in TF-M Protected Storage");
    	return APP_SUCCESS;
    }
    
    /**
     * @brief Check if credentials already exist in TF-M Protected Storage
     *
     * @return Number of existing credentials (0-3), or negative error code
     */
    static int check_existing_credentials(void)
    {
    	psa_status_t status;
    	struct psa_storage_info_t info;
    	int existing_count = 0;
    
    	LOG_INF("Checking for existing credentials in TF-M Protected Storage...");
    
    	/* Check CA certificate */
    	status = psa_ps_get_info(PSA_PS_AWS_CA_CERT_UID, &info);
    	if (status == PSA_SUCCESS) {
    		LOG_INF("âś“ CA certificate found: %u bytes, flags: 0x%x", info.size, info.flags);
    		existing_count++;
    	} else if (status == PSA_ERROR_DOES_NOT_EXIST) {
    		LOG_INF("âś— CA certificate not found");
    	} else {
    		LOG_ERR("CA certificate check failed. Status: %d", status);
    		return APP_ERROR_STORAGE_FAILED;
    	}
    
    	/* Check client certificate */
    	status = psa_ps_get_info(PSA_PS_AWS_CLIENT_CERT_UID, &info);
    	if (status == PSA_SUCCESS) {
    		LOG_INF("âś“ Client certificate found: %u bytes, flags: 0x%x", info.size, info.flags);
    		existing_count++;
    	} else if (status == PSA_ERROR_DOES_NOT_EXIST) {
    		LOG_INF("âś— Client certificate not found");
    	} else {
    		LOG_ERR("Client certificate check failed. Status: %d", status);
    		return APP_ERROR_STORAGE_FAILED;
    	}
    
    	/* Check private key */
    	status = psa_ps_get_info(PSA_PS_AWS_PRIVATE_KEY_UID, &info);
    	if (status == PSA_SUCCESS) {
    		LOG_INF("âś“ Private key found: %u bytes, flags: 0x%x", info.size, info.flags);
    		existing_count++;
    	} else if (status == PSA_ERROR_DOES_NOT_EXIST) {
    		LOG_INF("âś— Private key not found");
    	} else {
    		LOG_ERR("Private key check failed. Status: %d", status);
    		return APP_ERROR_STORAGE_FAILED;
    	}
    
    	LOG_INF("Credential check complete: %d/3 credentials found", existing_count);
    
    	if (existing_count == 3) {
    		LOG_INF("All AWS IoT credentials already exist - no provisioning needed!");
    		LOG_INF("This confirms credentials survived the application re-flash.");
    	} else if (existing_count > 0) {
    		LOG_WRN("Partial credential set found (%d/3) - will provision missing credentials",
    			existing_count);
    	} else {
    		LOG_INF("No existing credentials found - will provision all credentials");
    	}
    
    	return existing_count;
    }
    
    /**
     * @brief Print stored credentials from TF-M Protected Storage (for debugging)
     *
     * @param print_full_data If true, prints full credential data (SECURITY RISK!)
     *                        If false, prints only first/last few bytes
     * @return APP_SUCCESS on success, error code on failure
     */
    static int print_stored_credentials(bool print_full_data)
    {
    	psa_status_t status;
    	size_t actual_length;
    	uint8_t buffer[4096]; // Large enough for typical certificates
    
    	LOG_INF("=== Printing Stored Credentials ===");
    	if (print_full_data) {
    		LOG_WRN("WARNING: Full credential data will be printed - SECURITY RISK!");
    	}
    
    	/* Print CA Certificate */
    	LOG_INF("--- CA Certificate ---");
    	status = psa_ps_get(PSA_PS_AWS_CA_CERT_UID, 0, sizeof(buffer), buffer, &actual_length);
    	if (status == PSA_SUCCESS) {
    		LOG_INF("CA Certificate retrieved: %u bytes", actual_length);
    
    		if (print_full_data) {
    			// Print full certificate data
    			for (size_t i = 0; i < actual_length; i += 16) {
    				char hex_line[64] = {0};
    				char ascii_line[17] = {0};
    
    				for (size_t j = 0; j < 16 && (i + j) < actual_length; j++) {
    					sprintf(&hex_line[j * 3], "%02x ", buffer[i + j]);
    					ascii_line[j] =
    					    (buffer[i + j] >= 32 && buffer[i + j] < 127) ?
    						buffer[i + j] :
    						'.';
    				}
    				LOG_INF("%04x: %-48s %s", i, hex_line, ascii_line);
    			}
    		} else {
    			// Print first 32 bytes
    			char hex_str[96] = {0};
    			size_t print_len = MIN(32, actual_length);
    			for (size_t i = 0; i < print_len; i++) {
    				sprintf(&hex_str[i * 3], "%02x ", buffer[i]);
    			}
    			LOG_INF("CA Cert (first %u bytes): %s", print_len, hex_str);
    
    			// Print last 16 bytes if certificate is long enough
    			if (actual_length > 32) {
    				memset(hex_str, 0, sizeof(hex_str));
    				for (size_t i = 0; i < 16; i++) {
    					sprintf(&hex_str[i * 3], "%02x ",
    						buffer[actual_length - 16 + i]);
    				}
    				LOG_INF("CA Cert (last 16 bytes): %s", hex_str);
    			}
    		}
    	} else {
    		LOG_ERR("Failed to retrieve CA certificate. Status: %d", status);
    		return APP_ERROR_STORAGE_FAILED;
    	}
    
    	/* Print Client Certificate */
    	LOG_INF("--- Client Certificate ---");
    	status = psa_ps_get(PSA_PS_AWS_CLIENT_CERT_UID, 0, sizeof(buffer), buffer, &actual_length);
    	if (status == PSA_SUCCESS) {
    		LOG_INF("Client Certificate retrieved: %u bytes", actual_length);
    
    		if (print_full_data) {
    			// Print full certificate data
    			for (size_t i = 0; i < actual_length; i += 16) {
    				char hex_line[64] = {0};
    				char ascii_line[17] = {0};
    
    				for (size_t j = 0; j < 16 && (i + j) < actual_length; j++) {
    					sprintf(&hex_line[j * 3], "%02x ", buffer[i + j]);
    					ascii_line[j] =
    					    (buffer[i + j] >= 32 && buffer[i + j] < 127) ?
    						buffer[i + j] :
    						'.';
    				}
    				LOG_INF("%04x: %-48s %s", i, hex_line, ascii_line);
    			}
    		} else {
    			// Print first 32 bytes
    			char hex_str[96] = {0};
    			size_t print_len = MIN(32, actual_length);
    			for (size_t i = 0; i < print_len; i++) {
    				sprintf(&hex_str[i * 3], "%02x ", buffer[i]);
    			}
    			LOG_INF("Client Cert (first %u bytes): %s", print_len, hex_str);
    		}
    	} else {
    		LOG_ERR("Failed to retrieve client certificate. Status: %d", status);
    		return APP_ERROR_STORAGE_FAILED;
    	}
    
    	/* Print Private Key */
    	LOG_INF("--- Private Key ---");
    	status = psa_ps_get(PSA_PS_AWS_PRIVATE_KEY_UID, 0, sizeof(buffer), buffer, &actual_length);
    	if (status == PSA_SUCCESS) {
    		LOG_INF("Private Key retrieved: %u bytes", actual_length);
    
    		if (print_full_data) {
    			LOG_WRN("PRINTING FULL PRIVATE KEY - MAJOR SECURITY RISK!");
    			// Print full private key data
    			for (size_t i = 0; i < actual_length; i += 16) {
    				char hex_line[64] = {0};
    				char ascii_line[17] = {0};
    
    				for (size_t j = 0; j < 16 && (i + j) < actual_length; j++) {
    					sprintf(&hex_line[j * 3], "%02x ", buffer[i + j]);
    					ascii_line[j] =
    					    (buffer[i + j] >= 32 && buffer[i + j] < 127) ?
    						buffer[i + j] :
    						'.';
    				}
    				LOG_INF("%04x: %-48s %s", i, hex_line, ascii_line);
    			}
    		} else {
    			// Print only first 16 bytes for private key
    			char hex_str[48] = {0};
    			size_t print_len = MIN(16, actual_length);
    			for (size_t i = 0; i < print_len; i++) {
    				sprintf(&hex_str[i * 3], "%02x ", buffer[i]);
    			}
    			LOG_INF("Private Key (first %u bytes only): %s", print_len, hex_str);
    			LOG_INF("Private Key: [REMAINING DATA REDACTED]");
    		}
    	} else {
    		LOG_ERR("Failed to retrieve private key. Status: %d", status);
    		return APP_ERROR_STORAGE_FAILED;
    	}
    
    	LOG_INF("=== Credential Printing Complete ===");
    	return APP_SUCCESS;
    }
    
    /**
     * @brief Main credential provisioning function
     *
     * Orchestrates the entire credential provisioning process:
     * 1. Initialize TF-M and PSA interfaces
     * 2. Store all AWS IoT credentials in TF-M Protected Storage
     * 3. Verify successful storage
     */
    int main(void)
    {
    	int ret;
    
    	LOG_INF("=== AWS IoT Credential Provisioning for TF-M ===");
    	LOG_INF("Starting credential provisioning process...");
    
    	/* Initialize TF-M and PSA interfaces */
    	ret = initialize_tfm_psa();
    	if (ret != APP_SUCCESS) {
    		LOG_ERR("TF-M/PSA initialization failed");
    		goto exit;
    	}
    
    	/* Check for existing credentials first */
    	// int existing_count;
    	// existing_count = check_existing_credentials();
    	// if (existing_count < 0) {
    	// 	LOG_ERR("Failed to check existing credentials");
    	// 	ret = existing_count;
    	// 	goto exit;
    	// }
    
    	// /* If all credentials already exist, skip provisioning */
    	// if (existing_count == 3) {
    	// 	LOG_INF("All credentials already provisioned - skipping storage steps");
    	// 	print_stored_credentials(true);
    	// 	ret = APP_SUCCESS;
    	// 	goto exit;
    	// }
    
    	/* Store CA certificate */
    	ret = store_ca_certificate();
    	if (ret != APP_SUCCESS) {
    		LOG_ERR("CA certificate storage failed");
    		goto exit;
    	}
    
    	/* Store client certificate */
    	ret = store_client_certificate();
    	if (ret != APP_SUCCESS) {
    		LOG_ERR("Client certificate storage failed");
    		goto exit;
    	}
    
    	/* Store private key */
    	ret = store_private_key();
    	if (ret != APP_SUCCESS) {
    		LOG_ERR("Private key storage failed");
    		goto exit;
    	}
    
    	/* Verify all credentials were stored correctly */
    	ret = verify_stored_credentials();
    	if (ret != APP_SUCCESS) {
    		LOG_ERR("Credential verification failed");
    		goto exit;
    	}
    
    	LOG_INF("=== Credential Provisioning Completed Successfully ===");
    	LOG_INF("All AWS IoT credentials are now securely stored in TF-M Protected Storage");
    	LOG_INF("Credentials are write-protected and will survive FOTA updates");
    	LOG_INF("You can now flash and run your main application");
    
    exit:
    	if (ret != APP_SUCCESS) {
    		LOG_ERR("=== Credential Provisioning Failed ===");
    		LOG_ERR("Error code: %d", ret);
    	}
    
    	/* Keep application running to see logs */
    	while (1) {
    		k_sleep(K_SECONDS(10));
    	}
    
    	return ret;
    }

    As I moved on to writing the code in our project to retreived the credentials from psa ps and register them with the Zephyr TLS lib, I was faced with issue that the modem lib init would fail, when all that was done was including Kconfigs which seemed to be necessary to use the psa protected storage lib. The following is an example of what I am trying to do in my  application to bring the keys from secure storage into RAM for use by the networking backend socket code: 

    static int fetch_tls_credentials_from_tfm_ps_and_register(void)
    {
    	psa_status_t status;
    	size_t actual_length;
    	uint8_t credential_buffer[4096]; /* Large enough for certificates/keys */
    	int ret;
    
    	LOG_INF("=== Fetching TLS Credentials from TF-M Protected Storage ===");
    
    	/* Initialize TF-M non-secure interface */
    	status = tfm_ns_interface_init();
    	if (status != PSA_SUCCESS) {
    		LOG_ERR("Failed to initialize TF-M NS interface: %d", status);
    		return -EINVAL;
    	}
    
    	/* Initialize PSA Crypto */
    	status = psa_crypto_init();
    	if (status != PSA_SUCCESS) {
    		LOG_ERR("Failed to initialize PSA Crypto: %d", status);
    		return -EINVAL;
    	}
    
    	LOG_INF("TF-M and PSA services initialized successfully");
    
    	/*-----------------------------------------------------------------------
    	 * Retrieve and register CA Certificate
    	 *-----------------------------------------------------------------------*/
    
    	LOG_INF("Retrieving CA certificate...");
    	status = psa_ps_get(PSA_PS_AWS_CA_CERT_UID, 0, sizeof(credential_buffer), credential_buffer,
    			    &actual_length);
    	if (status != PSA_SUCCESS) {
    		LOG_ERR("Failed to retrieve CA certificate from Protected Storage: %d", status);
    		return -ENOENT;
    	}
    
    	LOG_INF("CA certificate retrieved: %u bytes", actual_length);
    
    	ret = tls_credential_add(TLS_TAG_AWS_CA_CERTIFICATE, TLS_CREDENTIAL_CA_CERTIFICATE,
    				 credential_buffer, actual_length);
    	if (ret < 0) {
    		LOG_ERR("Failed to register CA certificate: %d", ret);
    		return ret;
    	}
    
    	LOG_INF("âś“ CA certificate registered with security tag %d", TLS_TAG_AWS_CA_CERTIFICATE);
    
    	/*-----------------------------------------------------------------------
    	 * Retrieve and register Client Certificate
    	 *-----------------------------------------------------------------------*/
    
    	LOG_INF("Retrieving client certificate...");
    	status = psa_ps_get(PSA_PS_AWS_CLIENT_CERT_UID, 0, sizeof(credential_buffer),
    			    credential_buffer, &actual_length);
    	if (status != PSA_SUCCESS) {
    		LOG_ERR("Failed to retrieve client certificate from Protected Storage: %d", status);
    		return -ENOENT;
    	}
    
    	LOG_INF("Client certificate retrieved: %u bytes", actual_length);
    
    	ret = tls_credential_add(TLS_TAG_AWS_CLIENT_CERT, TLS_CREDENTIAL_SERVER_CERTIFICATE, /* Client
    												certs
    												use
    												SERVER_CERTIFICATE
    												type
    											      */
    				 credential_buffer, actual_length);
    	if (ret < 0) {
    		LOG_ERR("Failed to register client certificate: %d", ret);
    		return ret;
    	}
    
    	LOG_INF("âś“ Client certificate registered with security tag %d", TLS_TAG_AWS_CLIENT_CERT);
    
    	/*-----------------------------------------------------------------------
    	 * Retrieve and register Private Key
    	 *-----------------------------------------------------------------------*/
    
    	LOG_INF("Retrieving private key...");
    	status = psa_ps_get(PSA_PS_AWS_PRIVATE_KEY_UID, 0, sizeof(credential_buffer),
    			    credential_buffer, &actual_length);
    	if (status != PSA_SUCCESS) {
    		LOG_ERR("Failed to retrieve private key from Protected Storage: %d", status);
    		return -ENOENT;
    	}
    
    	LOG_INF("Private key retrieved: %u bytes", actual_length);
    
    	ret = tls_credential_add(TLS_TAG_AWS_PRIVATE_KEY, TLS_CREDENTIAL_PRIVATE_KEY,
    				 credential_buffer, actual_length);
    	if (ret < 0) {
    		LOG_ERR("Failed to register private key: %d", ret);
    		return ret;
    	}
    
    	LOG_INF("âś“ Private key registered with security tag %d", TLS_TAG_AWS_PRIVATE_KEY);
    
    	/*-----------------------------------------------------------------------
    	 * Clear sensitive data from memory
    	 *-----------------------------------------------------------------------*/
    
    	/* Clear credential buffer to remove sensitive data from RAM */
    	memset(credential_buffer, 0, sizeof(credential_buffer));
    
    	LOG_INF("=== All TLS Credentials Successfully Loaded ===");
    	LOG_INF("AWS IoT TLS credentials are now available for network connections");
    
    	return 0;
    }


    These seem to be the troublesome Kconfig symbols:

    # TF-M Configuration
    CONFIG_BUILD_WITH_TFM=y
    CONFIG_TFM_PROFILE_TYPE_MEDIUM=y
    
    # TF-M Storage Services (NOT Zephyr's PSA)
    CONFIG_TFM_PARTITION_PROTECTED_STORAGE=y
    CONFIG_TFM_PARTITION_INTERNAL_TRUSTED_STORAGE=y
    
    # PSA Crypto support
    CONFIG_PSA_WANT_ALG_SHA_256=y
    

    I know that we MUST have TF-M enabled for our product (given it will be put in unsecure production environments) and that the nrf modem lib REQUIRES it. 

    I would greatly appreciate some clarity as to how I may move forward with this goal. This is the single largest roadblock in our product development and some concrete advice on how to securely store TLS credentials somewhere in  flash accessible to the application core (so that the Ethernet network interface can use them) would help our product design progress immensely. 

    There seems to be many moving parts, with no clear explanation as to what can be used where, when X is enabled or disabled etc. Static paritions, TF-M, and Kconfig spaghetti have combined to make what I thought would be a relatively simple task into a complex one. 


    Is what I am trying to do even possible? I assume in the worst case I have to setup a seperate partition myself and use some form of encryption and decryption manually, but this seems like the wrong direction to go. 

    Thank you for your help and support. 

  • Hi,

    I am sorry for the delay. We have looked into this but we are not sure we understand your use case. You write you want hardcoded credentials, but so I am not misunderstanding, do you mean in code (which could then be a #define if accessible in non-secure was OK), or device specific keys?

    I need to look further into the problem with TFM credentials and modem lib together. Other than that, increaseing the TF-M PS maximum asset size requiers other configs to be increased (see this post):

    • CONFIG_TFM_PS_MAX_ASSET_SIZE
    • CONFIG_TFM_CRYPTO_IOVEC_BUFFER_SIZE
    • CONFIG_TFM_ITS_BUF_SIZE
  • Hello Einar,


    I am not sure how to explain what I need better. To summarize, I simply need to store TLS credentials somewhere securely on the device, and retrieve them on device startup and place them in RAM using the tls_credentials api. It seems like the PSA PS lib is not able to be used (cannot be enabled in Kconfig) while TF-M is enabled, but I must have TF-M enabled so that I can use the on-chip LTE modem with the NCS modem lib. 

    I DO NOT want to store the credentials in source code / hardcoded, although I have (for our in house development) given the PSA PS library is not an option. I see this as a massive security vulnerability, which is why I am looking for a way to store the credentials somewhere they are secure, not in plain text, and in a static partition that will not be overwritten by DFU.

    Thank you for sharing that post - I will keep in mind the maximum asset size limitations as we move forward.

    Please let me know when you are able to find out more about issues with using TF-M and PSA PS lib together in the same application. That being said, I am not set on specifically using the PSA PS library, rather I just want some way to store the TLS credentials I need securely in persistent storage on my device, so that at device startup, I can fetch them and load them into RAM using the tls_credentials api and the function I shared above. This should allow them to be used by the networking stack code if I recall correctly. 

    Thank you very much for your help.

  • Hi,

    I fully agree that hardcoded credentials is a bad choice, so please to not take my question and comment about that as a generic suggestion.

    We are stil looking into using TF-M PS for TLS credentials while the modem lib is enbled though, and I will get back to you on that.

  • Hi,

    I am sorry for the delay. This should work with the modem lib if you set CONFIG_NET_SOCKETS_OFFLOAD=n.

  • Thanks for the response Einar, although unfortunately we cannot do this. As first outlined in my post we must have offloading enabled in order to use the nrf modem lib (so that we can use BOTH LTE and Ethernet in a single compiled application).

    Please let me know if you or the team have discovered any other solutions to this issue. 

    Thank you. 

Reply Children
Related