Introduction
Arm’s Platform Security Architecture (PSA) is a holistic set of threat models, security analyses, hardware and firmware architecture specifications, and an open source firmware reference implementation. PSA provides a recipe, based on industry best practice, that allows security to be consistently designed in, at both a hardware and firmware level.
The PSA Cryptographic API (Crypto API) described in this document is an important PSA component that provides an interface to modern cryptographic primitives on resource-constrained devices. The interface is user-friendly, while still providing access to the primitives used in modern cryptography. It does not require that the user have access to the key material. Instead, it uses opaque key handles.
This document is part of the PSA family of specifications. It defines an interface for cryptographic services, including cryptography primitives and a key storage functionality.
This document includes:
- A rationale for the design.
- A high-level overview of the functionality provided by the interface.
- A description of typical architectures of implementations for this specification.
- General considerations for implementers of this specification and for applications that use the interface defined in this specification.
- A detailed definition of the API.
Companion documents will define profiles for this specification. A profile is a minimum mandatory subset of the interface that a compliant implementation must provide.
Design goals
Suitable for constrained devices
The interface is suitable for a vast range of devices: from special-purpose cryptographic processors that process data with a built-in key, to constrained devices running custom application code, such as microcontrollers, and multi-application devices, such as servers. Consequentially, the interface is scalable and modular.
- Scalable: you shouldn’t pay for functionality that you don’t need.
- Modular: larger devices implement larger subsets of the same interface, rather than different interfaces.
Because this specification is suitable for very constrained devices, including those where memory is very limited, all operations on unbounded amounts of data allow multipart processing, as long as the calculations on the data are performed in a streaming manner. This means that the application does not need to store the whole message in memory at one time.
Memory outside the keystore boundary is managed by the application. An implementation of the interface is not required to retain any state between function calls, apart from the content of the keystore and other data that must be kept inside the keystore security boundary.
The interface does not expose the representation of keys and intermediate data, except when required for interchange. This allows each implementation to choose optimal data representations. Implementations with multiple components are also free to choose which memory area to use for internal data.
A keystore interface
The specification allows cryptographic operations to be performed on a key to which the application does not have direct access. Except where required for interchange, applications access all keys indirectly, by a handle. The key material corresponding to that handle can reside inside a security boundary that prevents it from being extracted, except as permitted by a policy that is defined when the key is created.
Optional isolation
Implementations can isolate the cryptoprocessor from the calling application, and can further isolate multiple calling applications. The interface allows the implementation to be separated between a frontend and a backend. In an isolated implementation, the frontend is the part of the implementation that is located in the same isolation boundary as the application, which the application accesses by function calls. The backend is the part of the implementation that is located in a different environment, which is protected from the frontend. Various technologies can provide protection, for example:
- Process isolation in an operating system.
- Partition isolation, either with a virtual machine or a partition manager.
- Physical separation between devices.
Communication between the frontend and backend is beyond the scope of this specification.
In an isolated implementation, the backend can serve more than one implementation instance. In this case, a single backend communicates with multiple instances of the frontend. The backend must enforce caller isolation: it must ensure that assets of one frontend are not visible to any other frontend. How callers are identified is beyond the scope of this specification. An implementation that provides caller isolation must document how callers are identified. An implementation that provides isolation must document any implementation-specific extension of the API that enables frontend instances to share data in any form.
In summary, there are three types of implementations:
- No isolation: there is no security boundary between the application and the cryptoprocessor. For example, a statically or dynamically linked library is an implementation with no isolation.
- Cryptoprocessor isolation: there is a security boundary between the application and the cryptoprocessor, but the cryptoprocessor does not communicate with other applications. For example, a cryptoprocessor chip that is a companion to an application processor is an implementation with cryptoprocessor isolation.
- Caller isolation: there are multiple application instances, with a security boundary between the application instances among themselves, as well as between the cryptoprocessor and the application instances. For example, a cryptography service in a multiprocess environment is an implementation with caller and cryptoprocessor isolation.
Choice of algorithms
The specification defines a low-level cryptographic interface, where the caller explicitly chooses which algorithm and which security parameters they use. This is necessary to implement protocols that are inescapable in various use cases. The design of the interface enables applications to implement widely-used protocols and data exchange formats, as well as custom ones.
As a consequence, all cryptographic functionality operates according to the precise algorithm specified by the caller. However, this does not apply to device-internal functionality, which does not involve any form of interoperability, such as random number generation. The specification does not include generic higher-level interfaces, where the implementation chooses the best algorithm for a purpose. However, higher-level libraries can be built on top of the PSA Crypto API.
Another consequence is that the specification permits the use of algorithms, key sizes and other parameters that, while known to be insecure, may be necessary to support legacy protocols or legacy data. Where major weaknesses are known, the algorithm description give applicable warnings. However, the lack of a warning does not and cannot indicate that an algorithm is secure in all circumstances. Application developers should research the security of the algorithms that they plan to use to determine if the algorithms meet their requirements.
The interface facilitates algorithm agility. As a consequence, cryptographic primitives are presented through generic functions with a parameter indicating the specific choice of algorithm. For example, there is a single function to calculate a message digest, which takes a parameter that identifies the specific hash algorithm.
Ease of use
The interface is designed to be as user-friendly as possible, given the aforementioned constraints on suitability for various types of devices and on the freedom to choose algorithms.
In particular, the code flows are designed to reduce the chance of dangerous misuse. The interface makes it harder to misuse than to use correctly, and typical mistakes result in test failures, rather than subtle security issues. Implementations avoid leaking data when a function is called with invalid parameters, to the extent allowed by the C language and by implementation size constraints.
Example use cases
This section lists some of the use cases that were considered while designing this API. This list is not exhaustive, nor are all implementations required to support all use cases.
Network Security (TLS)
The API provides everything needed to establish TLS connections on the device side: asymmetric key management inside a key store, symmetric ciphers, MAC, HMAC, message digests, and AEAD.
Secure Storage
The API provides all primitives related to storage encryption, block or file-based, with master encryption keys stored inside a key store.
Network Credentials
The API provides network credential management inside a key store, for example, for X.509-based authentication or pre-shared keys on enterprise networks.
Device Pairing
The API provides support for key agreement protocols that are often used for secure pairing of devices over wireless channels. For example, the pairing of an NFC token or a Bluetooth device could make use of key agreement protocols upon first use.
Secure Boot
The API provides primitives for use during firmware integrity and authenticity validation, during a secure or trusted boot process.
Attestation
The API provides primitives used in attestation activities. Attestation is the ability for a device to sign an array of bytes with a device private key and return the result to the caller. There are several use cases: from attestation of the device state to the ability to generate a key pair and prove that it has been generated inside a secure key store. The API provides access to the algorithms commonly used for attestation.
Factory Provisioning
Most IoT devices receive a unique identity during the factory provisioning process, or once deployed to the field. This API provides the APIs necessary for populating a device with keys that represent that identity.
Functionality overview
This section provides a high-level overview of the functionality provided by the interface defined in this specification. Refer to the API definition for a detailed description.
Due to the modularity of the interface, almost every part of the library
is optional. The only mandatory function is psa_crypto_init.
Library management
Before any use, applications must call psa_crypto_init to initialize
the library.
Key management
Applications always access keys via a handle. This allows keys to be non-extractable, that is, an application can perform operations using a key without having access to the key material. Non-extractable keys are bound to the device, can be rate-limited and can have their usage restricted by policies.
Each key has a set of attributes that describe the key and the policy
for using the key. A psa_key_attributes_t object contains all of the
attributes, which is used when creating a key and when querying key
attibutes.
Each key has a lifetime that determines when the key material is destroyed. There are two types of lifetimes: volatile and persistent.
Volatile keys
A volatile key is destroyed as soon as the application closes the
handle to the key. When the application terminates, it conceptually
closes all of its key handles. Conceptually, a volatile key is stored in
RAM. Volatile keys have the lifetime PSA_KEY_LIFETIME_VOLATILE.
To create a volatile key:
- Populate a
psa_key_attributes_tobject with the key’s type, size, policy and other attributes. - Create the key with
psa_import_key,psa_generate_key,psa_key_derivation_output_keyorpsa_copy_key.
To destroy a volatile key, call psa_close_key or psa_destroy_key
(these functions are equivalent when called on a volatile key).
Persistent keys
A persistent key exists until it explicitly destroyed with
psa_destroy_key or until it is wiped by the reset or destruction of
the device.
Each persistent key has a key identifier, which acts as a name for the key. Within an application, the key identifier corresponds to a single key. The application specifies the key identifier when the key is created, and uses the key identifier to obtain a handle to a persistent key that has already been created. If the implementation provides caller isolation, then key identifiers are local to each application: the same key identifier in two applications corresponds to different keys.
Persistent keys may be stored in different storage areas; this is
indicated through different lifetime values. This specification defines
a single lifetime value PSA_KEY_LIFETIME_PERSISTENT which
corresponds to a default storage area. Implementations may define
alternative lifetime values corresponding to different storage areas
with different retention policies, or to secure elements with different
security characteristics.
To create a persistent key:
- Populate a
psa_key_attributes_tobject with the key’s type, size, policy and other attributes. - In the attributes object, set the desired lifetime and persistent identifier for the key.
- Create the key with
psa_import_key,psa_generate_key,psa_key_derivation_output_keyorpsa_copy_key.
To release memory resources associated with a key but keep the key in
storage, call psa_close_key. To access an existing persistent key,
call psa_open_key with the same key identifier used when creating
the key.
To destroy a persistent key, open it (if it isn’t already open) and call
psa_destroy_key.
The key lifetime and identifier are set when the key is created and cannot be changed without destroying the key first. If the original key permits copying, then the application can specify a different lifetime for the copy of the key.
Recommendations of minimum standards for key management
Most implementations provide the function psa_import_key. The only
exceptions are implementations that only give access to a key or keys
that are provisioned by proprietary means, and do not allow the main
application to use its own cryptographic material.
Most implementations provide psa_get_key_attributes and the
psa_get_key_xxx accessor functions, as they are easy to implement,
and it is difficult to write applications and to diagnose issues without
being able to check the metadata.
Most implementations also provide psa_export_public_key if they
support any asymmetric algorithm, since public-key cryptography often
requires the delivery of a public key that is associated with a
protected private key.
Most implementations provide psa_export_key. However, highly
constrained implementations that are designed to work only with
short-term keys (no non-volatile storage), or only with long-term
non-extractable keys, may omit this function.
Usage policies
All keys have an associated policy that regulates which operations are
permitted on the key. Each key policy is a set of usage flags and a
specific algorithm that is permitted with the key. The policy is part of
the key attributes that are managed by a psa_key_attributes_t
object.
The usage flags are encoded in a bitmask, which has the type
psa_key_usage_t. Three kinds of usage flag can be specified: * The
extractable flag PSA_KEY_USAGE_EXPORT determines whether the key
material can be extracted. * The copyable flag PSA_KEY_USAGE_COPY
determines whether the key material can be copied into a new key, which
can have a different lifetime or a more restrictive policy. * The usage
flags PSA_KEY_USAGE_ENCRYPT, PSA_KEY_USAGE_SIGN, and so on
determine whether the corresponding operation is permitted on the key.
In addition to the usage bitmask, a policy specifies which algorithm is permitted with the key. This specification only defines policies that restrict keys to a single algorithm, which is in keeping with common practice and with security good practice.
A highly constrained implementation may not be able to support all the policies that can be expressed through this interface. If an implementation cannot create a key with the required policy, it must return an appropriate error code when the key is created.
Symmetric cryptography
This specification defines interfaces for message digests (hash functions), MAC (message authentication codes), symmetric ciphers and authenticated encryption with associated data (AEAD). For each type of primitive, the API includes two standalone functions (compute and verify, or encrypt and decrypt) as well as a series of functions that permit multipart operations.
The standalone functions are:
psa_hash_computeandpsa_hash_compareto calculate the hash of a message or compare the hash of a message with a reference value.psa_mac_computeandpsa_mac_verifyto calculate the MAC of a message of compare the MAC with a reference value.psa_cipher_encryptandpsa_cipher_decryptto encrypt or decrypt a message using an unauthenticated symmetric cipher. The encryption function generates a random IV; to use a deterministic IV (which is not secure in general, but can be secure in some conditions that depend on the algorithm), use the multipart API.psa_aead_encryptandpsa_aead_decryptto encrypt/decrypt and authenticate a message using an AEAD algorithm. These functions follow the interface recommended by RFC 5116.
Multipart operations
The API provides a multipart interface to hash, MAC, symmetric cipher and AEAD primitives. These interfaces process messages one chunk at a time, with the size of chunks determined by the caller. This allows the processing of messages that cannot be assembled in memory. To perform a multipart operation:
- Allocate an operation object of the appropriate type. You can use any allocation strategy: stack, heap, static, etc.
- Initialize the operation object by one of the following methods:
- Set it to all-bits-zero.
- Initialize it to logical zero.
- Assign the value of the associated macro
PSA_xxx_INIT. - Assign the result of calling the associated function
psa_xxx_init.
- Specify a key for the operation using the associated setup function:
psa_hash_setup,psa_mac_sign_setup,psa_mac_verify_setup,psa_cipher_encrypt_setup,psa_cipher_decrypt_setup,psa_aead_encrypt_setuporpsa_aead_decrypt_setup. - Provide additional parameters:
- When encrypting data, generate or set an initialization vector (IV), nonce, or similar initial value such as an initial counter value.
- When decrypting, set the IV or nonce.
- For a symmetric cipher, to generate a random IV, which is
recommended in most protocols, call
psa_cipher_generate_iv. To set the IV, callpsa_cipher_set_iv. - For AEAD, call
psa_aead_generate_nonceorpsa_aead_set_nonce.
- Call the associated update function on successive chunks of the
message:
psa_hash_update,psa_mac_update,psa_cipher_update,psa_aead_update_adorpsa_aead_update. - At the end of the message, call the applicable finishing function.
There are three kinds of finishing function, depending on what to do
with the verification tag.
- Unauthenticated encryption and decryption does not involve a
verification tag. Call
psa_cipher_finish. - To calculate the digest or MAC or authentication tag of a message,
call the associated function to calculate and output the
verification tag:
psa_hash_finish,psa_mac_sign_finishorpsa_aead_finish. - To verify the digest or MAC of a message against a reference value
or to verify the authentication tag at the end of AEAD decryption,
call the associated function to compare the verification tag with
the reference value:
psa_hash_verify,psa_mac_verify_finishorpsa_aead_verify.
- Unauthenticated encryption and decryption does not involve a
verification tag. Call
Calling the setup function allocates resources inside the
implementation. These resources are freed when calling the associated
finishing function. In addition, each family of functions defines a
function psa_xxx_abort, which can be called at any time to free the
resources associated with an operation.
Authenticated encryption
Having a multipart interface to authenticated encryption raises specific issues.
Multipart authenticated decryption produces partial results that are not
authenticated. Applications must not use or expose partial results of
authenticated decryption until psa_aead_verify has returned a
success status, and must destroy all partial results without revealing
them if psa_aead_verify returns a failure status. Revealing partial
results (directly, or indirectly through the application’s behavior) can
compromise the confidentiality of all inputs that are encrypted with the
same key.
For encryption, some common algorithms cannot be processed in a
streaming fashion. For SIV mode, the whole plaintext must be known
before the encryption can start; the multipart AEAD API is not meant to
be usable with SIV mode. For CCM mode, the length of the plaintext must
be known before the encryption can start; the application can call the
function psa_aead_set_lengths to provide these lengths before
providing input.
Key derivation
The specification defines a mechanism for key derivation that allows the output of the derivation to be split into multiple keys, as well as non-key outputs.
In an implementation with isolation, the intermediate state of the key derivation is not visible to the caller, and if an output of the derivation is a non-exportable key, then this output cannot be recovered outside the isolation boundary.
Key derivation operations
A key derivation operation encodes a deterministic method to generate a finite stream of bytes. This data stream is computed by the cryptoprocessor and extracted in chunks. If two key derivation operations are constructed with the same parameters, then they should produce the same outputs.
Some example uses of key derivation operations are:
- A key derivation function: initialized with a secret, a salt and other parameters.
- A key agreement function: initialized with a public key (peer key), a key pair (own key) and other parameters.
Applications use the psa_key_derivation_operation_t type to create
key derivation operations.
The lifecycle of a key derivation operation is as follows:
- Setup: construct a
psa_key_derivation_operation_tobject, and set its parameters and inputs. The setup phase determines the key derivation operation’s capacity, which is the maximum number of bytes that can be output from this key derivation operation. - Output: read bytes from the stream defined by the key derivation
operation. This can be done any number of times, until the stream is
exhausted when its capacity has been reached. Each output step can
either be used to populate a key object
(
psa_key_derivation_output_key), or to read some bytes and extract them as cleartext (psa_key_derivation_output_bytes). - Terminate: clear the key derivation operation and release associated
resources (
psa_key_derivation_abort).
A key derivation operation cannot be rewound. Once a part of the stream has been output, it cannot be output again. This ensures that the same part of the output will not be used for different purposes.
Key derivation function
This specification defines functions to set up a key derivation. A key derivation consists of two parts:
- Input collection. This is sometimes known as extraction: the operation “extracts” information from the inputs to generate a pseudorandom intermediate secret value.
- Output generation. This is sometimes known as expansion: the operation “expands” the intermediate secret value to the desired output length.
To perform a key derivation:
- Initialize a
psa_key_derivation_operation_tobject to zero or toPSA_KEY_DERIVATION_OPERAITON_INIT. - Call
psa_key_derivation_setupto select a key derivation algorithm. - Call the functions
psa_key_derivation_input_bytesandpsa_key_derivation_input_key, orpsa_key_derivation_key_agreementto provide the inputs to the key derivation algorithm. Many key derivation algorithms take multiple inputs; the “step” parameter to these functions indicates which input is being passed. The documentation for each key derivation algorithm describes the expected inputs for that algorithm. - Call
psa_key_derivation_output_keyto create a derived key, orpsa_key_derivation_output_bytesto export the derived data. These functions may be called multiple times to read successive output from the key derivation. - Call
psa_key_derivation_abortto release the key derivation operation memory.
Here is an example of a use case where a master key is used to generate both a message encryption key and an IV for the encryption, and the derived key and IV are then used to encrypt a message.
- Derive the message encryption material from the master key.
- Initialize a
psa_key_derivation_operation_tobject to zero or toPSA_KEY_DERIVATION_OPERATION_INIT. - Call
psa_key_derivation_setupwithPSA_ALG_HKDFas the algorithm. - Call
psa_key_derivation_input_keywith the stepPSA_KEY_DERIVATION_INPUT_SECRETand the master key. - Call
psa_key_derivation_input_byteswith the stepPSA_KEY_DERIVATION_INPUT_INFOand a public value that uniquely identifies the message. - Populate a
psa_key_attributes_tobject with the derived message encryption key’s attributes. - Call
psa_key_derivation_output_keyto create the derived message key. - Call
psa_key_derivation_output_bytesto generate the derived IV. - Call
psa_key_derivation_abortto release the key derivation operation memory.
- Initialize a
- Encrypt the message with the derived material.
- Initialize a
psa_cipher_operation_tobject to zero or toPSA_CIPHER_OPERATION_INIT. - Call
psa_cipher_encrypt_setupwith the derived message encryption key. - Call
psa_cipher_set_ivusing the derived IV retrieved above. - Call
psa_cipher_updateone or more times to encrypt the message. - Call
psa_cipher_finishat the end of the message.
- Initialize a
- Call
psa_destroy_keyto clear the generated key.
Asymmetric cryptography
The asymmetric cryptography part of this interface defines functions for asymmetric encryption, asymmetric signature and two-way key agreement.
Asymmetric encryption
Asymmetric encryption is provided through the functions
psa_asymmetric_encrypt and psa_asymmetric_decrypt.
Hash-and-sign
The signature and verification functions psa_asymmetric_sign and
psa_asymmetric_verify take a hash as one of their inputs. This hash
should be calculated with psa_hash_setup, psa_hash_update and
psa_hash_finish before calling psa_asymmetric_sign or
psa_asymmetric_verify. To determine which hash algorithm to use,
call the macro PSA_ALG_SIGN_GET_HASH on the corresponding signature
algorithm.
Key agreement
This specification defines two functions for a Diffie-Hellman-style key agreement where each party combines its own private key with the peer’s public key.
The recommended approach is to use a key derivation
operation with the
psa_key_derivation_key_agreement input function, which calculates a
shared secret for the key derivation function.
In case an application needs direct access to the shared secret, it can
call psa_raw_key_agreement instead. Note that in general the shared
secret is not directly suitable for use as a key because it is biased.
Randomness and key generation
We strongly recommended that implementations include a random generator, consisting of a cryptographically secure pseudo-random generator (CSPRNG), which is adequately seeded with a cryptographic-quality hardware entropy source, commonly referred to as a true random number generator (TRNG). Constrained implementations may omit the random generation functionality if they do not implement any algorithm that requires randomness internally, and they do not provide a key generation functionality. For example, a special-purpose component for signature verification can omit this.
Applications should use psa_generate_key,
psa_encrypt_generate_iv or psa_aead_generate_iv to generate
suitably-formatted random data, as applicable. In addition, the API
includes a function psa_generate_random to generate and extract
arbitrary random data.
Future additions
We plan to cover the following features in future drafts and editions of this specification:
- Single-shot functions for symmetric operations.
- Multi-part operations for hybrid cryptography. For example, this includes hash-and-sign for EdDSA, and hybrid encryption for ECIES.
- Key exchange and a more general interface to key derivation. This would enable an application to derive a non-extractable session key from non-extractable secrets, without leaking the intermediate material.
- Key wrapping mechanisms to extract and import keys in a protected form (encrypted and authenticated).
- Key discovery mechanisms. This would enable an application to locate a key by its name or attributes.
- Implementation capability description. This would enable an application to determine the algorithms, key types and storage lifetimes that the implementation provides.
- An ownership and access control mechanism allowing a multi-client implementation to have privileged clients that are able to manage keys of other clients.
Sample architectures
This section describes some example architectures that can be used for implementations of the interface described in this specification. This list is not exhaustive and the section is entirely non-normative.
Single-partition architecture
In this architecture, there is no security boundary inside the system. The application code may access all the system memory, including the memory used by the cryptographic services described in this specification. Thus, the architecture provides no isolation.
This architecture does not conform to the Arm Platform Security Architecture Security Model. However, it may be useful for providing cryptographic services that use the same interface, even on devices that cannot support any security boundary. So, while this architecture is not the primary design goal of the API defined in the present specification, it is supported.
The functions in this specification simply execute the underlying algorithmic code. Security checks can be kept to a minimum, since the cryptoprocessor cannot defend against a malicious application. Key import and export copy data inside the same memory space.
This architecture also describes a subset of some larger systems, where the cryptographic services are implemented inside a high-security partition, separate from the code of the main application, though it shares this high-security partition with other platform security services.
Cryptographic token and single-application processor
This system is composed of two partitions: one is a cryptoprocessor and the other partition runs an application. There is a security boundary between the two partitions, so that the application cannot access the cryptoprocessor, except through its public interface. Thus, the architecture provides cryptoprocessor isolation. The cryptoprocessor has some nonvolatile storage, a TRNG, and possibly, some cryptographic accelerators.
There are a number of potential physical realizations: the cryptoprocessor may be a separate chip, a separate processor on the same chip, or a logical partition using a combination of hardware and software to provide the isolation. These realizations are functionally equivalent in terms of the offered software interface, but they would typically offer different levels of security guarantees.
The PSA crypto API in the application processor consists of a thin layer of code that translates function calls to remote procedure calls in the cryptoprocessor. All cryptographic computations are, therefore, performed inside the cryptoprocessor. Non-volatile keys are stored inside the cryptoprocessor.
Cryptoprocessor with no key storage
As in the previous example, this system is also composed of two partitions separated by a security boundary. Thus, this architecture also provides cryptoprocessor isolation. However, unlike the previous architecture, in this system, the cryptoprocessor does not have any secure, persistent storage that could be used to store application keys.
If the cryptoprocessor is not capable of storing cryptographic material, then there is little use for a separate cryptoprocessor, since all data would have to be imported by the application.
The cryptoprocessor can provide useful services if it is able to store at least one key. This may be a hardware unique key that is burnt to one-time programmable memory during the manufacturing of the device. This key can be used for one or more purposes:
- Encrypt and authenticate data stored in the application processor.
- Communicate with a paired device.
- Allow the application to perform operations with keys that are derived from the hardware unique key.
Multi-client cryptoprocessor
This is an expanded variant of the cryptographic token plus application architecture. In this variant, the cryptoprocessor serves multiple applications that are mutually untrustworthy. This architecture provides caller isolation.
In this architecture, API calls are translated to remote procedure calls, which encode the identity of the client application. The cryptoprocessor carefully segments its internal storage to ensure that a client’s data is never leaked to another client.
Multi-cryptoprocessor architecture
This system includes multiple cryptoprocessors. There are several reasons to have multiple cryptoprocessors:
- Different compromises between security and performance for different keys. Typically, this means a cryptoprocessor that runs on the same hardware as the main application and processes short-term secrets, a secure element or a similar separate chip that retains long-term secrets.
- Independent provisioning of certain secrets.
- A combination of a non-removable cryptoprocessor and removable ones, for example, a smartcard or HSM.
- Cryptoprocessors managed by different stakeholders who do not trust each other.
The keystore implementation needs to dispatch each request to the correct processor. For example: * All requests involving a non-extractable key must be processed in the cryptoprocessor that holds that key. * Requests involving a persistent key must be processed in the cryptoprocessor that corresponds to the key’s lifetime value. * Requests involving a volatile key may target a cryptoprocessor based on parameters supplied by the application, or based on considerations such as performance inside the implementation.
Library conventions
Error handling
Return status
Almost all functions return a status indication of type
psa_status_t. This is an enumeration of integer values, with 0
(PSA_SUCCESS) indicating successful operation and other values
indicating errors. The exception is data structure accessor functions,
which cannot fail. Such functions may return void or a data value.
Unless specified otherwise, if multiple error conditions apply, an implementation is free to return any of the applicable error codes. The choice of error code is considered an implementation quality issue. Different implementations may make different choices, for example to favor code size over ease of debugging or vice versa.
Note that if the behavior is undefined (for example, if a function receives an invalid pointer as a parameter), this specification makes no guarantee that the function will return an error. Implementations are encouraged to return an error or halt the application in a manner that is appropriate for the platform if the undefined behavior condition can be detected. However, application programmers should be aware that undefined behavior conditions cannot be detected in general.
Behavior on error
All function calls must be implemented atomically:
- When a function returns a type other than
psa_status_t, the requested action has been carried out. - When a function returns the status
PSA_SUCCESS, the requested action has been carried out. - When a function returns another status of type
psa_status_t, no action has been carried out. The content of the output parameters is undefined, but otherwise the state of the system has not changed, except as described below.
In general, functions that modify the system state, for example, creating or destroying a key, must leave the system state unchanged if they return an error code. There are specific conditions that can result in different behavior:
- The status
PSA_ERROR_BAD_STATEindicates that a parameter was not in a valid state for the requested action. This parameter may have been modified by the call and is now in an undefined state. The only valid action on an object in an undefined state is to abort it with the appropriatepsa_abort_xxxfunction. - The status
PSA_ERROR_INSUFFICIENT_CAPACITYindicates that a key derivation object has reached its maximum capacity. The key derivation operation may have been modified by the call. Any further attempt to obtain output from the key derivation operation will returnPSA_ERROR_INSUFFICIENT_CAPACITY. - The status
PSA_ERROR_COMMUNICATION_FAILUREindicates that the communication between the application and the cryptoprocessor has broken down. In this case, the cryptoprocessor must either finish the requested action successfully, or interrupt the action and roll back the system to its original state. Because it is often impossible to report the outcome to the application after a communication failure, this specification does not provide a way for the application to determine whether the action was successful. - The statuses
PSA_ERROR_STORAGE_FAILURE,PSA_ERROR_HARDWARE_FAILUREandPSA_ERROR_TAMPERING_DETECTEDmay indicate data corruption in the system state. When a function returns one of these statuses, the system state may have changed from its previous state before the function call, even though the function call failed. - Some system states cannot be rolled back, for example, the internal state of the random number generator or the content of access logs.
Unless otherwise documented, the content of output parameters is not
defined when a function returns a status other than PSA_SUCCESS.
Implementations should set output parameters to safe defaults to avoid
leaking confidential data and limit risk, in case an application does
not properly handle all errors.
Parameter conventions
Pointer conventions
Unless explicitly stated in the documentation of a function, all pointers must be valid pointers to an object of the specified type.
A parameter is considered a buffer if it points to an array of
bytes. A buffer parameter always has the type uint8_t * or
const uint8_t *, and always has an associated parameter indicating
the size of the array. Note that a parameter of type void * is never
considered a buffer.
All parameters of pointer type must be valid non-null pointers, unless
the pointer is to a buffer of length 0 or the function’s documentation
explicitly describes the behavior when the pointer is null.
Implementations where a null pointer dereference usually aborts the
application, passing NULL as a function parameter where a null
pointer is not allowed, should abort the caller in the habitual manner.
Pointers to input parameters may be in read-only memory. Output parameters must be in writable memory. Output parameters that are not buffers must also be readable, and the implementation must be able to write to a non-buffer output parameter and read back the same value, as explained in the “Stability of parameters” section.
Input buffer sizes
For input buffers, the parameter convention is:
const uint8_t *foo: pointer to the first byte of the data. The pointer may be invalid if the buffer size is 0.size_t foo_length: size of the buffer in bytes.
The interface never uses input-output buffers.
Output buffer sizes
For output buffers, the parameter convention is:
uint8_t *foo: pointer to the first byte of the data. The pointer may be invalid if the buffer size is 0.size_t foo_size: the size of the buffer in bytes.size_t *foo_length: on successful return, contains the length of the output in bytes.
The content of the data buffer and of *foo_length on errors is
unspecified, unless explicitly mentioned in the function description.
They may be unmodified or set to a safe default. On successful
completion, the content of the buffer between the offsets
*foo_length and foo_size is also unspecified.
Functions return PSA_ERROR_BUFFER_TOO_SMALL if the buffer size is
insufficient to carry out the requested operation. The interface defines
macros to calculate a sufficient buffer size for each operation that has
an output buffer. These macros return compile-time constants if their
arguments are compile-time constants, so they are suitable for static or
stack allocation. Refer to an individual function’s documentation for
the associated output size macro.
Some functions always return exactly as much data as the size of the output buffer. In this case, the parameter convention changes to:
uint8_t *foo: pointer to the first byte of the output. The pointer may be invalid if the buffer size is 0.size_t foo_length: the number of bytes to return infooif successful.
Overlap between parameters
Output parameters that are not buffers must not overlap with any input buffer or with any other output parameter. Otherwise, the behavior is undefined.
Output buffers may overlap with input buffers. If this happens, the implementation must return the same result, as if the buffers did not overlap. In other words, the implementation must behave as if it had copied all the inputs into temporary memory, as far as the result is concerned. However, application developers should note that overlap between parameters may affect the performance of a function call. Overlap may also affect memory management security if the buffer is located in memory that the caller shares with another security context, as described in the “Stability of parameters” section.
Stability of parameters
In some environments, it is possible for the content of a parameter to change while a function is executing. It may also be possible for the content of an output parameter to be read before the function terminates. This can happen if the application is multithreaded. In some implementations, memory can be shared between security contexts, for example, between tasks in a multitasking operating system, between a user land task and the kernel, or between the non-secure world and the secure world of a trusted execution environment. This section describes what implementations need or need not guarantee in such cases.
Parameters that are not buffers are assumed to be under the caller’s full control. In a shared memory environment, this means that the parameter must be in memory that is exclusively accessible by the application. In a multithreaded environment, this means that the parameter may not be modified during the execution, and the value of an output parameter is undetermined until the function returns. The implementation may read an input parameter that is not a buffer multiple times and expect to read the same data. The implementation may write to an output parameter that is not a buffer and expect to read back the value that it last wrote. The implementation has the same permissions on buffers that overlap with a buffer in the opposite direction.
In an environment with multiple threads or with shared memory, the implementation carefully accesses non-overlapping buffer parameters in order to prevent any security risk resulting from the content of the buffer being modified or observed during the execution of the function. In an input buffer that does not overlap with an output buffer, the implementation reads each byte of the input once, at most. The implementation does not read from an output buffer that does not overlap with an input buffer. Additionally, the implementation does not write data to a non-overlapping output buffer if this data is potentially confidential and the implementation has not yet verified that outputting this data is authorized.
Key types and algorithms
Types of cryptographic keys and cryptographic algorithms are encoded
separately. Each is encoded by using an integral type:
psa_key_type_t and psa_algorithm_t, respectively.
There is some overlap in the information conveyed by key types and
algorithms. Both types contain enough information, so that the meaning
of an algorithm type value does not depend on what type of key it is
used with, and vice versa. However, the particular instance of an
algorithm may depend on the key type. For example, the algorithm
PSA_ALG_GCM can be instantiated as any AEAD algorithm using the GCM
mode over a block cipher. The underlying block cipher is determined by
the key type.
Key types do not encode the key size. For example, AES-128, AES-192 and
AES-256 share a key type PSA_KEY_TYPE_AES.
Structure of key and algorithm types
Both types use a partial bitmask structure, which allows the analysis and building of values from parts. However, the interface defines constants, so that applications do not need to depend on the encoding, and an implementation may only care about the encoding for code size optimization.
The encodings follows a few conventions:
- The highest bit is a vendor flag. Current and future versions of this specification will only define values where this bit is clear. Implementations that wish to define additional implementation-specific values must use values where this bit is set, to avoid conflicts with future versions of this specification.
- The next few highest bits indicate the corresponding algorithm category: hash, MAC, symmetric cipher, asymmetric encryption, and so on.
- The following bits identify a family of algorithms in a category-dependent manner.
- In some categories and algorithm families, the lowest-order bits indicate a variant in a systematic way. For example, algorithm families that are parametrized around a hash function encode the hash in the 8 lowest bits.
Concurrent calls
In some environments, an application can make calls to the PSA crypto API in separate threads. In such an environment, concurrent calls are performed correctly, as if the calls were executed in sequence, provided that they obey the following constraints:
- There is no overlap between an output parameter of one call and an input or output parameter of another call. Overlap between input parameters is permitted.
- If a call modifies a key, then no other call must modify or use that key. Using, in this context, includes all functions of multipart operations using the key. Concurrent calls that merely use the same key are permitted.
- Concurrent calls must not use the same operation object.
If any of these constraints are violated, the behavior is undefined.
Individual implementations may provide additional guarantees.
Implementation considerations
Implementation-specific aspects of the interface
Implementation profile
Implementations may implement a subset of the API and a subset of the available algorithms. The implemented subset is known as the implementation’s profile. The documentation for each implementation must describe the profile that it implements. This specification’s companion documents also define a number of standard profiles.
Implementation-specific types
This specification defines a number of platform-specific types, which
represent data structures whose content depends on the implementation.
These are C struct types. In the associated header files,
crypto.h declares the struct tags and crypto_struct.h
provides a definition for the structures.
Implementation-specific macros
Some macros compute a result based on an algorithm or key type. This specification provides a sample implementation of these macros, which works for all standard types. If an implementation defines vendor-specific algorithms or key types, then it must provide an implementation for such macros that takes all relevant algorithms and types into account. Conversely, an implementation that does not support a certain algorithm or key type can define such macros in a simpler way that does not take unsupported argument values into account.
Some macros define the minimum sufficient output buffer size for certain functions. In some cases, an implementation is allowed to require a buffer size that is larger than the theoretical minimum. An implementation must define minimum-size macros in such a way that it guarantees that the buffer of the resulting size is sufficient for the output of the corresponding function. Refer to each macro’s documentation for the applicable requirements.
Porting to a platform
Platform assumptions
This specification is designed for a C89 platform. The interface is defined in terms of C macros, functions and objects.
The specification assumes 8-bit bytes, and “byte” and “octet” are used synonymously.
Platform-specific types
The specification makes use of some platform-specific types, which
should be defined in crypto_platform.h or by a header included in
this file. crypto_platform.h must define the following types:
uint8_t,uint16_t,uint32_t: unsigned integer types with 8, 16 and 32 value bits respectively. These may be the types defined by the C99 headerstdint.h.psa_key_handle_t: an unsigned integer type of the implementation’s choice.
Cryptographic hardware support
Implementations are encouraged to make use of hardware accelerators where available. A future version of this specification will define a function interface that calls drivers for hardware accelerators and external cryptographic hardware.
Security requirements and recommendations
Error detection
Implementations that provide isolation between the caller and the cryptography processing environment must validate parameters to ensure that the cryptography processing environment is protected from attacks caused by passing invalid parameters.
Even implementations that do not provide isolation should strive to detect bad parameters and fail-safe as much as possible.
Memory cleanup
Implementations must wipe all sensitive data from memory when it is no longer used. They should wipe this sensitive data as soon as possible. In any case, all temporary data used during the execution of a function, such as stack buffers, must be wiped before the function returns. All data associated with an object, such as a multipart operation, must be wiped, at the latest, when the object becomes inactive, for example, when a multipart operation is aborted.
The rationale for this non-functional requirement is to minimize impact if the system is compromised. If sensitive data is wiped immediately after use, only data that is currently in use can be leaked. It does not compromise past data.
Safe outputs on error
Implementations must ensure that confidential data is not written to output parameters before validating that the disclosure of this confidential data is authorized. This requirement is especially important for implementations where the caller may share memory with another security context, as described in the “Stability of parameters” section.
In most cases, the specification does not define the content of output parameters when an error occurs. Implementations should try to ensure that the content of output parameters is as safe as possible, in case an application flaw or a data leak causes it to be used. In particular, Arm recommends that implementations avoid placing partial output in output buffers when an action is interrupted. The meaning of “safe as possible” depends on the implementation, as different environments require different compromises between implementation complexity, overall robustness and performance. Some common strategies are to leave output parameters unchanged, in case of errors, or zeroing them out.
Attack resistance
Cryptographic code tends to manipulate high-value secrets, from which other secrets can be unlocked. As such, it is a high-value target for attacks. There is a vast body of literature on attack types, such as side channel attacks and glitch attacks. Typical side channels include timing, cache access patterns, branch-prediction access patterns, power consumption, radio emissions and more.
This specification does not specify particular requirements for attack resistance. Therefore, implementers should consider the attack resistance desired in each use case and design their implementation accordingly. Security standards for attack resistance for particular targets may be applicable in certain use cases.
Other implementation considerations
Philosophy of resource management
The specification allows most functions to return
PSA_ERROR_INSUFFICIENT_MEMORY. This gives implementations the
freedom to manage memory as they please.
Alternatively, the interface is also designed for conservative strategies of memory management. An implementation may avoid dynamic memory allocation altogether by obeying certain restrictions:
- Pre-allocate memory for a predefined number of keys, each with sufficient memory for all key types that can be stored.
- For multipart operations, in an implementation without isolation, place all the data that needs to be carried over from one step to the next in the operation object. The application is then fully in control of how memory is allocated for the operation.
- In an implementation with isolation, pre-allocate memory for a predefined number of operations inside the cryptoprocessor.
Usage considerations
Security recommendations
Always check for errors
Most functions in this API can return errors. All functions that can
fail have the return type psa_status_t. A few functions cannot fail,
and thus, return void or some other type.
If an error occurs, unless otherwise specified, the content of the output parameters is undefined and must not be used.
Some common causes of errors include:
- In implementations where the keys are stored and processed in a separate environment from the application, all functions that need to access the cryptography processing environment may fail due to an error in the communication between the two environments.
- If an algorithm is implemented with a hardware accelerator, which is logically separate from the application processor, the accelerator may fail, even when the application processor keeps running normally.
- All functions may fail due to a lack of resources. However, some implementations guarantee that certain functions always have sufficient memory.
- All functions that access persistent keys may fail due to a storage failure.
- All functions that require randomness may fail due to a lack of
entropy. Implementations are encouraged to seed the random generator
with sufficient entropy during the execution of
psa_crypto_init. However, some security standards require periodic reseeding from a hardware random generator, which can fail.
Cleaning up after use
To minimize impact if the system is compromised, applications should wipe all sensitive data from memory when it is no longer used. That way, only data that is currently in use may be leaked, and past data is not compromised.
Wiping sensitive data includes:
- Clearing temporary buffers in the stack or on the heap.
- Aborting operations if they will not be finished.
- Destroying keys that are no longer used.