It's impossible to completely secure a device against an attacker with physical access. But you can make it difficult and expensive.
Imagine that you have secured communications between your nRF9160 and your server using mutually authenticated TLS. That's great, until someone gains physical access to the device and reads your private key from flash. Even if you store your key in write-only1 memory, an attacker can still run their firmware on the device and use the key. And firmware signature validation won't help2 if the bootloader can simply be erased.
Enter erase protection. It does what it says: Protects against erasure. If your device can't be erased (which includes flashing new firmware) then you can be pretty darn sure only your trusted firmware is using your keys.
(Of course, someone with the right (read: expensive) tools could still perform side-channel attacks or systematically deconstruct your device. A processor like the ARM Cortex-M35P would mitigate the first case.)
Before you enable erase protection, you must make some preparations, or you will never be able to modify the firmware again:
- Your application should support some form of FOTA. After erase protection is enabled, the firmware will still be able to update itself. This is your escape hatch.
- Alternatively, you can modify the SPM (as described below) before enabling erase protection. But with FOTA you don't have to rely on getting it right the first time.
Enabling erase protection
Did you prepare yourself? Don't say you weren't warned.
The command to enable erase protection is
nrfjprog --memwr <addr> --val 0
<addr> should be replaced with
0x00FF8030. The setting will take effect after a reset, which should happen automatically after a few seconds.
You can verify that it worked by trying to erase or flash the device.
The above command writes the value zero to the
UICR.ERASEPROTECT register via the NVMC. It is important to note that the NVMC can write only zeros, not ones, so the only way to revert this change is to erase everything.
No, that's not a catch-22…
Disabling erase protection
…but it might be less confusing to say that disabling erase protection causes everything3 to be erased. Tomayto, tomahto.
Here's where our preparations show their quality. To disable erase protection, we have to write the same nonzero 32-bit key to the
CTRLAPPERI.ERASEPROTECT.DISABLE register from firmware and to the
ERASEPROTECT.DISABLE register from JLink.
The first one can only be written from a Trusted Application (TA). Unless you're special, your TA is the Secure Partition Manager (SPM). Modify its
// at the top: #include <nrf.h> // inside main: NRF_CTRL_AP_PERI_S->ERASEPROTECT.DISABLE = _key_;
_key_ replaced by a secret nonzero 32-bit value.
Now, build your application and update the device firmware over the air.
The second register can be written using JLink Commander. Connect to the device:
JLinkExe -if swd -device nrf91 # JLink.exe on Windows
and, in the new shell, select the serial wire debug port by entering:
Finally, write to the
ERASEPROTECT.DISABLE register with the following commands:
SWDWriteDP 2 0x04000010 SWDWriteAP 3 _key_ SWDWriteDP 2 0x04000000 SWDWriteAP 0 0x00000001
_key_ replaced by the same value you used in the SPM.
The first command selects access port (AP) 4 (CTRL-AP) and AP register bank 0x10. The second command writes your key to the third register in that bank. The final two commands reset the device, initiating the erasure.
At this point, if you were automating this, you might want to wait for the erasure to complete. Run the following commands to read the
SWDWriteDP 2 0x04000000 SWDReadAP 2 SWDReadAP 2
The double read is necessary; the second read returns the value read from the first one. Ignore the result of the first read, and keep reading until it returns zero.
The documentation suggests another way to disable erase protection that doesn't require FOTA. It involves using the mailbox interface to authenticate with a debugger at start up, presumably receiving from it the 32-bit key for disabling erase protection.
After startup, the firmware should set the
CTRLAPPERI.ERASEPROTECT.LOCK register to disallow disabling erase protection until the next reset. This can be done in a similar way to setting
NRF_CTRL_AP_PERI_S->ERASEPROTECT.LOCK = 1;
Finally, you probably want to to enable access port protection on your device to restrict debugger access (while still allowing CTRL-AP access for disabling erase protection). This can be done in the same way as for erase protection, but with
<addr> replaced by
0x00FF8000. And it also holds true for access port protection that it can only be disabled by erasing everything.