Implements the PSA Crypto API specification. More...
Implements the PSA Crypto API specification.
This module implements the PSA Cryptography API Version 1.1 as specified here and the PSA Status code API Version 1.0 as specified here. It provides an OS level access to cryptographic operations and supports software and hardware backends as well as the use of secure elements. The API automatically builds a hardware backend for an operation, if there's one available, otherwise it falls back to software. Specific backends can be configured, if needed. For configuration options see Configuration.
PSA Crypto has an integrated key management module, which stores keys internally without exposing them to applications. To learn how to use keys with PSA, read Using Keys.
A basic usage and configuration example can be found in examples/psa_crypto
. For more usage instructions, please read the documentation.
If you want to add your own crypto backend, see Porting Guide.
To use PSA Crypto, add psa/crypto.h
to your includes. This will make all operations and macros available.
Call psa_crypto_init()
before calling any other operation.
Whenever you declare a PSA Crypto structure (e.g. operation contexts or key attributes), it needs to be initialized with zeroes. A structure that is not initialized will be interpreted by PSA as active and can not be used for a new operation. The example function and macro shown below result in the same thing: A new, inactive structure.
An already active operation can be set to zero by reinitializing it. It then becomes inactive again and can be used for a new operation.
When errors occur during execution, PSA resets the operation contexts and makes them inactive, to prevent unauthorized access to an operation's state. Users can also call psa_<operation>_abort()
anytime in between function calls to do the same.
PSA can only operate on keys, that are registered with and stored within the internal key storage module. This means you need to either generate keys with PSA or import an existing key. For this purpose there are a number of key management functions (external link).
When creating a key for PSA, the implementation needs to know what kind of key it is dealing with, what it can be used for, where it's supposed to be stored, etc. That information needs to be specified in a set of Key Attributes (external link).
The example below defines attributes for an AES-128 key, which can be used for CBC encryption and decryption and will be stored in local volatile memory.
After setting the attributes, an exiting key can be imported:
The PSA Crypto implementation will assign an identifier to the key and return it via the key_id
parameter. This identifier can then be used for operations with this specific key.
All the supported key types, algorithms and usage flags can be found in the documentation.
The PSA API specifies two ways of storing keys: volatile and persistent. Volatile keys will be stored only in RAM, which means they will be destroyed after application termination or a device reset. Persistent keys will also be written into flash memory for later access. To destroy them they must be explicitly deleted with the psa_destroy_key()
function.
native
and on the nRF52840dk
. For this, add USEMODULE += psa_persistent_storage
to your application makefile or CONFIG_MODULE_PSA_PERSISTENT_STORAGE=y
to your app.config.test
file. Example: tests/sys/psa_crypto_persistent_storage
When creating a key, the user needs to specify a lifetime value, which actually consists of two values: persistence and location. The location defines the actual memory location of the key (e.g. whether the key will be stored in RAM, in a hardware protected memory slot or on an external device like a secure element).
The persistence value defines whether the key will be stored in RAM (volatile) in flash (persistent). Some default values that exist are:
Other lifetime values can be constructed with the macro PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION(persistence, location)
. All supported PSA_KEY_PERSISTENCE_*
and PSA_KEY_LOCATION_*
values can be combined.
In addition to the location values defined by the specification, this implementation also supports values for Secure Elements.
Currently there are two ways to configure PSA Crypto: Kconfig and Makefiles. An example for both can be found in RIOT/examples/psa_crypto
.
We recommend using Kconfig and choosing your features in menuconfig
. You can access the GUI by calling
from your application directory. There you can find the available PSA features and options under System->PSA Crypto
. If you only select the operations you want to use (e.g. PSA Ciphers->AES-128 CBC
), Kconfig will automatically select the best backend for you depending on the board (e.g. a hardware accelerator if it is available). Optionally you can force a custom backend.
Further you can specify the exact number of keys you need to store (section PSA Key Management Configuration
in menuconfig
), or choose your Secure Element configurations.
Alternatively you can create an app.config.test
file in your application folder and choose your symbols there (see examples/psa_crypto
).
In the app.config.test
file, modules can be chosen with the following syntax: CONFIG_MODULE_<MODULENAME>=y
, as shown below.
If you don't want to use Kconfig, you can use the traditional way in RIOT of selecting modules in your application Makefile.
Here you need to set the base module and individual modules for each operation you need. The example below also chooses a default backend depending on your board.
If desired, you can choose a specific backend at compile time. For this you need to specify that you want to set a custom backend and then explicitly choose the one you want (see below).
The currently available modules, are listed below.
The key management of PSA keeps track of keys by storing them in virtual key slot representations, along with their attributes. Since keys can come in various sizes, it would be inefficient to allocate the same amount of memory for all keys. To reduce the amount of memory used for key storage, PSA internally differentiates between three types of key slots (see below). Depending on the operations your application uses, PSA will automatically detect the key sizes needed and will allocate the required memory. The number of key slots allocated of each type is set to five per default, but can be changed by the user depending on their requirements.
Single Key Slot | Asymmetric Key Slot | Protected Key Slot |
---|---|---|
Single keys or unstructured data, e.g. AES keys or asymmetric public keys in local memory | Asymmetric key pairs<br>(private and public parts) in local memory | Any keys stored on a secure element or on-chip in hardware protected memory |
If you want to change the default number of allocated key slots you can do so by updating the number in menuconfig
, or adding them to the app.config.test
file like so:
When using Makefiles, you can pass CFLAGS as shown below.
Below are the currently available modules. No matter which operation you need, you always have to choose the base module. If you want to specify a backend other than the default, you need to select psa_<operation>_custom_backend
in addition to the actual backend module.
The names listed are are the version used in makefiles with the USEMODULE += <modulename>
syntax. In Kconfig you don't need to know the exact names, you can simply choose the features in menuconfig
. When using app.config.test
files in your application directory, you need to write the names in uppercase and add the prefix CONFIG_MODULE_
to all of them.
Base:
Currently uses the RIOT Random Module as a backend. See the documentation for configuration options.
An example showing the use of SEs can be found in examples/psa_crypto
.
To use secure elements, you first need to assign a static location value to each device, so PSA can find it. If you only use one device, you can use PSA_KEY_LOCATION_PRIMARY_SECURE_ELEMENT
. For additional devices this value must be within the range of PSA_KEY_LOCATION_SE_MIN
and PSA_KEY_LOCATION_SE_MAX
. When booting the system, the auto_init
module in RIOT will automatically register the device with the location with PSA Crypto.
You can now import or create keys on the secure element by constructing a key lifetime containing a device's location value.
Some secure elements come with their own key management and device configurations. In this case the configuration parameters must be passed to PSA Crypto during the registration. For this, you need to define a psa_se_config_t
structure containing the configuration. PSA Crypto will use this structure to keep track of what types of keys are allowed on the device and how much storage is available. Where this structure should be placed, how it looks and what parameters are required depends on the type of your device.
A good place to define that structure and the location values is a drivers <driver>_params.h
file, but this may vary depending on how your device is integrated in RIOT.
For detailed, device specific information, please check the device driver documentation or the example.
To use SEs, the appropriate modules must be chosen in Kconfig:
or added to the the Makefile:
This implementation supports the use of one or more secure elements (SE) as backends. In this case the number of used secure elements must be specified (must be at least 2 and at most 255). When using more than one SE, add
or, respectively,
This porting guide focuses on how to add your software library or hardware driver as a backend to PSA Crypto without actually touching the PSA implementation. We will provide some general information and then some case examples for different kinds of backends:
Some examples to look at are:
An example integrating a secure element can be found in the Cryptoauthlib Package.
You should always check the status of your function calls and translate your library's or driver's errors to PSA error values (please be as thorough as possible). The PSA Crypto specification describes exactly what kind of error values should be returned by which function. Please read the API documentation and comply with the instructions. We recommend writing a<mylibrary>_to_psa_error()
function right in the beginning (see for example CRYS_to_psa_error()
in pkg/driver_cryptocell_310/psa_cryptocell_310/error_conversion.c
).
As mentioned before, there are two ways of selecting build time configurations in RIOT: Kconfig and Makefiles. Kconfig dependency resolution is currently an experimental feature and will at some point replace Makefiles. Until then, our implementation needs to support both, which means we need to define features and symbols in multiple places. Luckily, the modules have the exact same names in both systems, which makes the transfer easier. The examples below show both ways.
In RIOT, module names are generated from path names, so if you create a directory for your sourcefiles, the module name will be the same as the directory name. It is possible to change that by declaring a new module name in the Makefile by adding the line MODULE := your_module_name
.
If you leave it like this, all sourcefiles in the path corresponding to the module name will be built (e.g. if you choose the module hashes
, all files in sys/hashes
will be included). For better configurability it is possible to add submodules (see sys/hashes/psa_riot_hashes
for example). In that case the base module name will be the directory name and each file inside the directory becomes its own submodule that must be explicitly chosen. The module name will then be the directory name with the file name as a postfix. For example:
We also need to create so-called pseudomodules for each available submodule. Those must follow the scheme psa_<modulename>_<filename>
. Where they are declared depends on where your module is located. Pseudomodules in RIOT/sys
must be added in pseudomodules.inc.mk
. When integrating packages or drivers, the pseudomodules can be added in the Makefile.include
file of the individual module's directory (see pkg/micro-ecc/Makefile.include
).
When adding backends to PSA Crypto, please name your modules in ways that fit within the current naming scheme: psa_<library>_<algorithm>
. Also, when adding software libraries and hardware drivers, use the submodule approach. That makes PSA Crypto more configurable.
The drawback of the submodule approach is, that if one of our sourcefiles depends on another sourcefile in the same folder, we need to select it explicitly. For example, in pkg/driver_cryptocell_310/psa_cryptocell_310
you can see that there are some common source files that all the others use (e.g. for hashes there is a hashes_common.c
file).
If that is the case for your driver, you need to make sure the modules are selected in the Kconfig file as well as the Makefile.dep
file (see psa_cryptocell_310/Makefile.dep
or psa_cryptocell_310/Kconfig
).
We define a number of wrapper APIs, which are called by PSA to invoke crypto backends. Software libraries and hardware drivers use the same methods, secure elements are handled in a different way (see Case Example – Secure Elements for details).
The names, parameters and return values for wrapper methods are defined in header files in sys/psa_crypto/include/psa_<algorithm>.h
. The functions declared in those files are the ones that are currently supported by this PSA implementation. They will be extended in the future.
You need to implement those functions with glue code calling your library or driver code and converting types and error values between PSA and your backend. Below is an example of how this might look (it's very reduced, your library may need much more glue code).
Some cryptographic operations use driver specific context to store the operation state in between function calls. These must be defined somewhere. Examples can be found in pkg/driver_cryptocell_310/include/psa_periph_hashes_ctx.h
and sys/include/hashes/psa/riot_hashes.h
.
When defining the contexts for a hardware driver, all you need to do is add a file called psa_periph_<algorithm>_ctx.h
to your driver's include folder and define the available types (see supported types below). Those files are automatically included in crypto_includes.h
and it is important that they always have the same name for each algorithm.
When defining the contexts for a software library, the headerfile should be called <library>_<algorithm>.h
(e.g. riot_hashes.h
) and must be added to crypto_includes.h
as shown below:
When defining the context types, those must always depend on the specific algorithm module, for example
psa_hashes_md5_ctx_t
psa_hashes_sha1_ctx_t
psa_hashes_sha224_ctx_t
psa_hashes_sha256_ctx_t
psa_hashes_sha384_ctx_t
psa_hashes_sha512_ctx_t
psa_hashes_sha512_224_ctx_t
psa_hashes_sha512_256_ctx_t
psa_cipher_aes_128_ctx_t
psa_cipher_aes_192_ctx_t
psa_cipher_aes_256_ctx_t
Secure Elements need their own contexts. For this, see Case Example – Secure Elements.
The integration of hardware drivers, software libraries and secure element drivers differs a bit. Below we describe the necessary steps for each of them.
Software libraries are the easiest backends, because they are not platform or hardware specific. They can generally run on all platforms in RIOT and we can combine different software backends for different operations (we could, for example, use the Micro-ECC package for ECC NIST curves and the C25519 package for operations with the Curve25519).
Let's say we have an imaginary software library called FancyCrypt
and want to use it as a backend of PSA. We've already added it to RIOT as a third party package in pkg/fancycrypt
. Our library provides hashes and elliptic curve operations and to make it accessible to PSA Crypto we need to write wrappers for our API calls.
First we create a folder called psa_fancycrypt
in the package directory. Inside we create a file with the name of each operation you want to integrate, e.g. p256.c
and hashes_sha_224.c
(when adding operations, remember that the path of the files will also be the module name, so please comply with the current naming scheme).
In these files we need to implement the methods that are called by PSA as described above.
We add a Makefile to the psa_fancycrypt
folder with the following content:
This tells RIOT that the psa_fancycrypt
module has submodules, which can be selected individually.
In pkg/fancycrypt
we now need to declare explicit pseudomodules in Makefile.include
and add the psa_fancycrypt
folder to the source files and the sys/psa_crypto/include
folder to the includes. These should be dependent on the PSA Crypto module as shown below.
If the implementation has any dependencies, they need to be added in Makefile.dep
, for example:
We add a file called Kconfig
to the psa_fancycrypt
folder. Here we declare the modules for Kconfig like so:
If the implementation has any dependencies, we can select them in this Kconfig file:
In pkg/fancycrypt/Kconfig
we need to add the line
at the bottom.
To be able to choose fancycrypt
as a PSA backend, we need to add the option to the Kconfig and Makefiles of the PSA Crypto Module.
In sys/psa_crypto/
we need to modify Kconfig.asymmetric
, sys/psa_crypto/Kconfig.hashes
, Makefile.dep
and Makefile.include
.
To Kconfig.asymmetric
we need to add
This will expose FancyCrypt as a backend option in PSA and then enable all the necessary features, when users select it. You need to do the same thing for the hash operation in Kconfig.hashes
.
To achieve the same thing with Makefiles we need to do this in two places: In Makefile.include
there are some existing pseudomodules for asymmetric crypto and hashes. There we need to create the backend modules for FancyCrypt by adding
and
The automatic module selection happens in Makefile.dep
. To the place where exiting P256 curves and hashes are selected we add cases for our backend modules:
Now you should be able to select your package as a backend for PSA Crypto and use it to perform operations.
The first steps of porting a hardware driver are the same as for the software library. Only we skip the last part where we add the modules to the PSA Crypto Kconfig and Makefiles and do something else instead.
Hardware drivers are treated a little differently, mostly because they are tied to a specific platform and users can not just choose a different driver for their accelerator. Therefore we just want PSA Crypto to automatically use this driver whenever it runs on the corresponding platform, which means that we have to add some additional options and features, not only to the driver but also to the CPU it belongs to. A good example for this is the CryptoCell 310 driver for the accelerator on the nRF52840 CPU.
Now, let's say we have a CPU called myCPU
with an on-chip accelerator called speedycrypt
. Let's say that speedycrypt
provides hashes and ECC curves. The vendor provides a driver, which we already have included in RIOT as a package. Also we've followed the steps in the glue code section and provide a folder called pkg/driver_speedycrypt/psa_speedycrypt
with the required wrapper files. We have also added the module names in a Kconfig file and in the Makefiles.
This is where we diverge from the software library example. If you take a look at the available backends in PSA, you'll notice one with the postfix *_BACKEND_PERIPH
for each available algorithm. Periph here is short for peripheral hardware accelerator. The *_BACKEND_PERIPH
modules depend on the presence of such an accelerator. They are a generic module for all crypto hardware accelerators and will automatically resolve to the driver that is associated with the available accelerator.
Before we're able to use it we need to tell RIOT that those hardware features exist for our myCPU
(see cpu/nrf52/Kconfig
and cpu/nrf52/Makefile.features
as an example). In cpu/myCPU
we add all the provided features as shown below.
Files we need to touch:
cpu/myCPU/Makefile.features
cpu/myCPU/Kconfig
cpu/myCPU/periph/Makefile.dep
cpu/myCPU/periph/Kconfig
RIOT/kconfigs/Kconfig.features
cpu/myCPU/Makefile.features:
cpu/myCPU/Kconfig:
The HAS_PERIPH_*
symbols are defined in ``. If your device provides capabilities that are not yet defined, you can add them to that file.
Next we need to define selectable modules for this in the cpu/myCPU/periph
folder, which then automatically enable the driver. An example for this is cpu/nrf52/periph
. We add the following to the cpu/myCPU/periph/Kconfig
file and cpu/myCPU/periph/Makefile.dep
:
cpu/myCPU/periph/Makefile.dep:
cpu/myCPU/periph/Kconfig:
Here we basically say "If the user chooses the `periph_hash_sha_256 module`, also select the `periph_speedycrypt` feature, which will then enable the speedycrypt driver". Of course you need to do this for all your available features.
Now, if you build PSA Crypto with default configurations, it should automatically detect that your board has a hardware accelerator for hashes and ECC operations and build the hardware driver as a backend.
Secure elements (SEs) are handled almost completely separate from the other backends. When we use software libraries or hardware drivers, we only build one implementation per algorithm. When it comes to secure elements we want to be able to build them in addition to the other backends and we may want to connect and use more than one of them at the same time. Another difference is that when using software libraries and hardware drivers, PSA handles the storage of key material. When using SEs, keys are stored on the SE, which means, we need additional functionality for the key management.
An existing example in RIOT is the Microchip ATECCX08A device family, whose driver can be found in pkg/cryptoauthlib
.
PSA Crypto has an integrated SE driver registry, which stores all registered drivers in a list. When an application calls a cryptographic operation that's supposed to be performed by a secure element, the registry will find the correct driver in the list and PSA will invoke the operation. Each driver is stored with a context that contains persistent as well as transient driver data. Transient driver data can be anything the driver needs to function. Persistent data is supposed to be used to keep track of how many keys are stored on the device and if there is still some free space available.
For this example we integrate an imaginary SE called superSE
, which comes with a driver called superSE_lib
. Again, we assume that we have already added the driver as a package in RIOT and it can be found at pkg/superse_lib
.
Secure element drivers need to implement a different API than the other backends. It is defined here. In our package folder we now create a new folder called psa_superse_driver
and add a source file called psa_superse_lib_driver.c
. Here we now implement glue code for all the cryptographic operations our SE supports.
You will notice that the SE interface also provides some key management functions. This is because keys are stored on the device and PSA can not access the memory and key data itself, but needs to tell the driver to do it.
Some operations need driver specific contexts. For secure elements these are wrapped in types defined in crypto_contexts.h
(currently only psa_se_cipher_context_t
is supported). In this header file add operation contexts that belong to your driver to the available SE context unions as shown in the example below:
The first thing PSA will do, when an application creates a key on an SE, is ask the driver to find a free key slot on the device. This is what the allocate
function is for. How exactly the slot is allocated, depends on the driver. It may be possible to query that information directly from the device. If that is not possible, we can use the persistent data stored in the driver context. An example for this can be found in pkg/cryptoauthlib/psa_atca_driver/psa_atca_se_driver.c
. This example requires the user to provide information about the configurations for each key slot, which is then stored in the persistent driver data and used for key management (for a better description read Using Cryptoauthlib as a backend for PSA Crypto). At this point you can decide what the best approach for your device is.
The allocate
function should then return some reference to the slot it has allocated for the key (possibly a pointer or a slot number). Next PSA Crypto will invoke the import
or generate
function to store a key.
When you want to use persistent data to keep track of keys, you should utilize the psa_se_config_t
structure, which is declared in crypto_se_config.h
. You can define a structure that can hold your device configuration and make sure it is available then your SE is used.
At the bottom of the wrapper code, define structures with pointers to the available methods. For example if you have implemented a superse_allocate
and superse_generate_key
function, you need to add a psa_drv_se_key_management_t
structure as shown below. Fill the unimplemented methods with NULL
pointers. The last structure should be a psa_drv_se_t
struct containing pointers to the other structures. That one will be stored during driver registration to get access to all the implemented functions.
You should do this for all available functions. The structures for the functions are declared in sys/psa_crypto/include/psa_crypto_se_driver.h
.
At start-up all secure element drivers need to be registered with the PSA SE management module. This happens by calling psa_register_secure_element()
during the automatic driver initialization in RIOT. When you added support for our device to RIOT, you should have implemented an auto_init_<device>
function, which initializes the connected devices. In this function, after initializing a device, you should call psa_register_secure_element()
and pass the device's location value, and pointers to the psa_drv_se_t
structure, the persistent data and some device specific context. An example implementation of this can be seen in sys/auto_init/security/auto_init_atca.c
.
To be able to choose our superSE
during configuration, we need to define the corresponding modules in the Kconfig files and Makefiles.
To pkg/super_se_lib/Kconfig
we add something like
This tells the build system that whenever this driver and PSA Crypto are used at the same time, the wrapper and the PSA key management module are needed, too.
To sys/psa_crypto/psa_se_mgmt/Kconfig
we add a menu for the SE like so:
This makes our driver selectable whenever an application configuration selects the PSA secure element module.
As described in the Configuration Section, references to keys on secure elements are stored by PSA in a different type of key slot than other keys. The slot for protected keys usually only contains a slot number or address and not the actual key, which requires a lot less memory space.
BUT: If your secure element supports asymmetric cryptography and exports a public key part during key generation, that key part must be stored somewhere. So when you choose an asymmetric operation, the protected key slots will have the space to store a public key.
Secure Element operations also depend on the PSA modules. E.g. when you want to use an ECC operation, you need to make sure that you also build the asymmetric PSA functions.
For this we need to add the following to the superSE
menu:
This tells us, what size a key slot should have to store the public key. If your SE supports other curves, you need to modify this accordingly or add more of them.
Now we need to add the same to the Makefiles. In Makefile.include
we add the source file path and the PSA include folders and define the new available pseudomodules:
In Makefile.dep
we automatically add required modules when PSA Crypto and the ECC curve module are chosen:
This needs to be done for all other supported operations (e.g. ATECCX08 operations in pkg/cryptoauthlib/Makefile.include
, pkg/cryptoauthlib/Makefile.dep
and sys/psa_crypto/psa_se_mgmt/Kconfig
. Now the secure element should be available for use with PSA Crypto.
Files | |
file | crypto.h |
Function declarations for PSA Crypto. | |
file | crypto_contexts.h |
Context definitions for PSA Crypto. | |
file | crypto_se_config.h |
Define structures für SE slot configurations. | |
file | crypto_sizes.h |
Size definitions for PSA Crypto. | |
file | crypto_struct.h |
Structure definitions for PSA Crypto. | |
file | crypto_types.h |
Type definitions for PSA Crypto. | |
file | crypto_values.h |
Value definitions for PSA Crypto. | |
file | error.h |
Error definitions for the PSA Crypto API. | |
file | psa_crypto_operation_encoder.h |
Macros used to map PSA algorithms, key types and key sizes to specific key types and operations to call the corresponding driver functions. | |