2019-06-24 13:47:24 +02:00
|
|
|
/* BEGIN_HEADER */
|
|
|
|
#include "psa_crypto_helpers.h"
|
|
|
|
#include "psa/crypto_se_driver.h"
|
|
|
|
|
2019-06-24 14:34:59 +02:00
|
|
|
#include "psa_crypto_se.h"
|
2019-07-23 16:13:14 +02:00
|
|
|
#include "psa_crypto_storage.h"
|
2019-06-24 14:34:59 +02:00
|
|
|
|
2019-07-24 13:44:03 +02:00
|
|
|
|
|
|
|
|
|
|
|
/****************************************************************/
|
|
|
|
/* Test driver helpers */
|
|
|
|
/****************************************************************/
|
|
|
|
|
2019-07-12 23:47:47 +02:00
|
|
|
/** The minimum valid lifetime value for a secure element driver. */
|
2019-06-24 14:34:59 +02:00
|
|
|
#define MIN_DRIVER_LIFETIME 2
|
|
|
|
|
2019-07-12 23:47:47 +02:00
|
|
|
/** The driver detected a condition that shouldn't happen.
|
|
|
|
* This is probably a bug in the library. */
|
|
|
|
#define PSA_ERROR_DETECTED_BY_DRIVER ((psa_status_t)( -500 ))
|
|
|
|
|
|
|
|
/** Like #TEST_ASSERT for use in a driver method.
|
|
|
|
*
|
|
|
|
* Use this macro to assert on guarantees provided by the core.
|
|
|
|
*/
|
|
|
|
#define DRIVER_ASSERT( TEST ) \
|
|
|
|
do { \
|
|
|
|
if( ! (TEST) ) \
|
|
|
|
{ \
|
|
|
|
test_fail( #TEST, __LINE__, __FILE__ ); \
|
|
|
|
return( PSA_ERROR_DETECTED_BY_DRIVER ); \
|
|
|
|
} \
|
|
|
|
} while( 0 )
|
|
|
|
|
2019-07-24 13:44:03 +02:00
|
|
|
|
|
|
|
|
2019-07-24 13:45:36 +02:00
|
|
|
/****************************************************************/
|
|
|
|
/* Miscellaneous driver methods */
|
|
|
|
/****************************************************************/
|
|
|
|
|
2019-08-05 17:17:52 +02:00
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
psa_key_slot_number_t slot_number;
|
|
|
|
psa_key_creation_method_t method;
|
|
|
|
psa_status_t status;
|
|
|
|
} validate_slot_number_directions_t;
|
|
|
|
static validate_slot_number_directions_t validate_slot_number_directions;
|
|
|
|
|
|
|
|
/* Validate a choice of slot number as directed. */
|
|
|
|
static psa_status_t validate_slot_number_as_directed(
|
|
|
|
psa_drv_se_context_t *context,
|
|
|
|
const psa_key_attributes_t *attributes,
|
|
|
|
psa_key_creation_method_t method,
|
|
|
|
psa_key_slot_number_t slot_number )
|
|
|
|
{
|
|
|
|
(void) context;
|
|
|
|
(void) attributes;
|
|
|
|
DRIVER_ASSERT( slot_number == validate_slot_number_directions.slot_number );
|
|
|
|
DRIVER_ASSERT( method == validate_slot_number_directions.method );
|
|
|
|
return( validate_slot_number_directions.status );
|
|
|
|
}
|
|
|
|
|
2019-07-24 13:45:36 +02:00
|
|
|
/* Allocate slot numbers with a monotonic counter. */
|
|
|
|
static psa_status_t counter_allocate( psa_drv_se_context_t *context,
|
|
|
|
void *persistent_data,
|
|
|
|
const psa_key_attributes_t *attributes,
|
2019-08-05 16:44:14 +02:00
|
|
|
psa_key_creation_method_t method,
|
2019-07-24 13:45:36 +02:00
|
|
|
psa_key_slot_number_t *slot_number )
|
|
|
|
{
|
|
|
|
psa_key_slot_number_t *p_counter = persistent_data;
|
|
|
|
(void) attributes;
|
2019-08-05 16:44:14 +02:00
|
|
|
(void) method;
|
2019-07-24 13:45:36 +02:00
|
|
|
if( context->persistent_data_size != sizeof( psa_key_slot_number_t ) )
|
|
|
|
return( PSA_ERROR_DETECTED_BY_DRIVER );
|
|
|
|
++*p_counter;
|
|
|
|
if( *p_counter == 0 )
|
|
|
|
return( PSA_ERROR_INSUFFICIENT_STORAGE );
|
|
|
|
*slot_number = *p_counter;
|
|
|
|
return( PSA_SUCCESS );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Null import: do nothing, but pretend it worked. */
|
|
|
|
static psa_status_t null_import( psa_drv_se_context_t *context,
|
|
|
|
psa_key_slot_number_t slot_number,
|
2019-08-06 17:32:04 +02:00
|
|
|
const psa_key_attributes_t *attributes,
|
|
|
|
const uint8_t *data,
|
2019-07-24 20:25:59 +02:00
|
|
|
size_t data_length,
|
|
|
|
size_t *bits )
|
2019-07-24 13:45:36 +02:00
|
|
|
{
|
|
|
|
(void) context;
|
|
|
|
(void) slot_number;
|
2019-08-06 17:32:04 +02:00
|
|
|
(void) attributes;
|
|
|
|
(void) data;
|
2019-07-24 20:25:59 +02:00
|
|
|
/* We're supposed to return a key size. Return one that's correct for
|
|
|
|
* plain data keys. */
|
|
|
|
*bits = PSA_BYTES_TO_BITS( data_length );
|
2019-07-24 13:45:36 +02:00
|
|
|
return( PSA_SUCCESS );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-07-24 13:44:03 +02:00
|
|
|
/****************************************************************/
|
|
|
|
/* RAM-based test driver */
|
|
|
|
/****************************************************************/
|
|
|
|
|
2019-07-12 23:47:47 +02:00
|
|
|
#define RAM_MAX_KEY_SIZE 64
|
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
psa_key_lifetime_t lifetime;
|
|
|
|
psa_key_type_t type;
|
|
|
|
size_t bits;
|
|
|
|
uint8_t content[RAM_MAX_KEY_SIZE];
|
|
|
|
} ram_slot_t;
|
|
|
|
static ram_slot_t ram_slots[16];
|
|
|
|
|
|
|
|
/* A type with at least ARRAY_LENGTH(ram_slots) bits, containing a
|
|
|
|
* bit vector indicating which slots are in use. */
|
|
|
|
typedef uint16_t ram_slot_usage_t;
|
|
|
|
|
|
|
|
static uint8_t ram_min_slot = 0;
|
|
|
|
|
|
|
|
static void ram_slots_reset( void )
|
|
|
|
{
|
|
|
|
memset( ram_slots, 0, sizeof( ram_slots ) );
|
|
|
|
ram_min_slot = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static psa_status_t ram_import( psa_drv_se_context_t *context,
|
|
|
|
psa_key_slot_number_t slot_number,
|
2019-08-06 17:32:04 +02:00
|
|
|
const psa_key_attributes_t *attributes,
|
|
|
|
const uint8_t *data,
|
2019-07-24 20:25:59 +02:00
|
|
|
size_t data_length,
|
|
|
|
size_t *bits )
|
2019-07-12 23:47:47 +02:00
|
|
|
{
|
|
|
|
(void) context;
|
|
|
|
DRIVER_ASSERT( slot_number < ARRAY_LENGTH( ram_slots ) );
|
|
|
|
if( data_length > sizeof( ram_slots[slot_number].content ) )
|
|
|
|
return( PSA_ERROR_INSUFFICIENT_STORAGE );
|
2019-08-06 17:32:04 +02:00
|
|
|
ram_slots[slot_number].lifetime = psa_get_key_lifetime( attributes );
|
|
|
|
ram_slots[slot_number].type = psa_get_key_type( attributes );
|
2019-07-12 23:47:47 +02:00
|
|
|
ram_slots[slot_number].bits = PSA_BYTES_TO_BITS( data_length );
|
2019-07-24 20:25:59 +02:00
|
|
|
*bits = PSA_BYTES_TO_BITS( data_length );
|
2019-08-06 17:32:04 +02:00
|
|
|
memcpy( ram_slots[slot_number].content, data, data_length );
|
2019-07-12 23:47:47 +02:00
|
|
|
return( PSA_SUCCESS );
|
|
|
|
}
|
|
|
|
|
2019-07-24 13:44:03 +02:00
|
|
|
static psa_status_t ram_export( psa_drv_se_context_t *context,
|
|
|
|
psa_key_slot_number_t slot_number,
|
|
|
|
uint8_t *p_data,
|
|
|
|
size_t data_size,
|
|
|
|
size_t *p_data_length )
|
2019-07-12 23:47:47 +02:00
|
|
|
{
|
|
|
|
size_t actual_size;
|
|
|
|
(void) context;
|
|
|
|
DRIVER_ASSERT( slot_number < ARRAY_LENGTH( ram_slots ) );
|
|
|
|
actual_size = PSA_BITS_TO_BYTES( ram_slots[slot_number].bits );
|
|
|
|
if( actual_size > data_size )
|
|
|
|
return( PSA_ERROR_BUFFER_TOO_SMALL );
|
|
|
|
*p_data_length = actual_size;
|
|
|
|
memcpy( p_data, ram_slots[slot_number].content, actual_size );
|
|
|
|
return( PSA_SUCCESS );
|
|
|
|
}
|
|
|
|
|
2019-07-24 13:44:03 +02:00
|
|
|
static psa_status_t ram_destroy( psa_drv_se_context_t *context,
|
|
|
|
void *persistent_data,
|
|
|
|
psa_key_slot_number_t slot_number )
|
2019-07-12 23:47:47 +02:00
|
|
|
{
|
|
|
|
ram_slot_usage_t *slot_usage = persistent_data;
|
|
|
|
DRIVER_ASSERT( context->persistent_data_size == sizeof( ram_slot_usage_t ) );
|
|
|
|
DRIVER_ASSERT( slot_number < ARRAY_LENGTH( ram_slots ) );
|
|
|
|
memset( &ram_slots[slot_number], 0, sizeof( ram_slots[slot_number] ) );
|
|
|
|
*slot_usage &= ~(ram_slot_usage_t)( 1 << slot_number );
|
|
|
|
return( PSA_SUCCESS );
|
|
|
|
}
|
|
|
|
|
2019-07-24 13:44:03 +02:00
|
|
|
static psa_status_t ram_allocate( psa_drv_se_context_t *context,
|
|
|
|
void *persistent_data,
|
|
|
|
const psa_key_attributes_t *attributes,
|
2019-08-05 16:44:14 +02:00
|
|
|
psa_key_creation_method_t method,
|
2019-07-24 13:44:03 +02:00
|
|
|
psa_key_slot_number_t *slot_number )
|
2019-07-12 23:47:47 +02:00
|
|
|
{
|
|
|
|
ram_slot_usage_t *slot_usage = persistent_data;
|
|
|
|
(void) attributes;
|
2019-08-05 16:44:14 +02:00
|
|
|
(void) method;
|
2019-07-12 23:47:47 +02:00
|
|
|
DRIVER_ASSERT( context->persistent_data_size == sizeof( ram_slot_usage_t ) );
|
|
|
|
for( *slot_number = ram_min_slot;
|
|
|
|
*slot_number < ARRAY_LENGTH( ram_slots );
|
|
|
|
++( *slot_number ) )
|
|
|
|
{
|
|
|
|
if( ! ( *slot_usage & 1 << *slot_number ) )
|
|
|
|
return( PSA_SUCCESS );
|
|
|
|
}
|
|
|
|
return( PSA_ERROR_INSUFFICIENT_STORAGE );
|
|
|
|
}
|
|
|
|
|
2019-08-05 14:55:50 +02:00
|
|
|
static psa_status_t ram_validate_slot_number(
|
|
|
|
psa_drv_se_context_t *context,
|
|
|
|
const psa_key_attributes_t *attributes,
|
2019-08-05 16:44:14 +02:00
|
|
|
psa_key_creation_method_t method,
|
2019-08-05 14:55:50 +02:00
|
|
|
psa_key_slot_number_t slot_number )
|
|
|
|
{
|
|
|
|
(void) context;
|
|
|
|
(void) attributes;
|
2019-08-05 16:44:14 +02:00
|
|
|
(void) method;
|
2019-08-05 14:55:50 +02:00
|
|
|
if( slot_number >= ARRAY_LENGTH( ram_slots ) )
|
|
|
|
return( PSA_ERROR_INVALID_ARGUMENT );
|
|
|
|
return( PSA_SUCCESS );
|
|
|
|
}
|
|
|
|
|
2019-07-24 13:44:03 +02:00
|
|
|
|
|
|
|
|
|
|
|
/****************************************************************/
|
|
|
|
/* Other test helper functions */
|
|
|
|
/****************************************************************/
|
|
|
|
|
2019-07-24 19:09:30 +02:00
|
|
|
/* Check that the attributes of a key reported by psa_get_key_attributes()
|
|
|
|
* are consistent with the attributes used when creating the key. */
|
|
|
|
static int check_key_attributes(
|
|
|
|
psa_key_handle_t handle,
|
|
|
|
const psa_key_attributes_t *reference_attributes )
|
|
|
|
{
|
|
|
|
int ok = 0;
|
|
|
|
psa_key_attributes_t actual_attributes = PSA_KEY_ATTRIBUTES_INIT;
|
|
|
|
|
|
|
|
PSA_ASSERT( psa_get_key_attributes( handle, &actual_attributes ) );
|
|
|
|
|
|
|
|
TEST_EQUAL( psa_get_key_id( &actual_attributes ),
|
|
|
|
psa_get_key_id( reference_attributes ) );
|
|
|
|
TEST_EQUAL( psa_get_key_lifetime( &actual_attributes ),
|
|
|
|
psa_get_key_lifetime( reference_attributes ) );
|
|
|
|
TEST_EQUAL( psa_get_key_type( &actual_attributes ),
|
|
|
|
psa_get_key_type( reference_attributes ) );
|
|
|
|
TEST_EQUAL( psa_get_key_usage_flags( &actual_attributes ),
|
|
|
|
psa_get_key_usage_flags( reference_attributes ) );
|
|
|
|
TEST_EQUAL( psa_get_key_algorithm( &actual_attributes ),
|
|
|
|
psa_get_key_algorithm( reference_attributes ) );
|
|
|
|
TEST_EQUAL( psa_get_key_enrollment_algorithm( &actual_attributes ),
|
|
|
|
psa_get_key_enrollment_algorithm( reference_attributes ) );
|
|
|
|
if( psa_get_key_bits( reference_attributes ) != 0 )
|
|
|
|
{
|
|
|
|
TEST_EQUAL( psa_get_key_bits( &actual_attributes ),
|
|
|
|
psa_get_key_bits( reference_attributes ) );
|
|
|
|
}
|
|
|
|
|
2019-08-02 20:30:01 +02:00
|
|
|
{
|
|
|
|
psa_key_slot_number_t actual_slot_number = 0xdeadbeef;
|
|
|
|
psa_key_slot_number_t desired_slot_number = 0xb90cc011;
|
|
|
|
psa_key_lifetime_t lifetime =
|
|
|
|
psa_get_key_lifetime( &actual_attributes );
|
|
|
|
psa_status_t status = psa_get_key_slot_number( &actual_attributes,
|
|
|
|
&actual_slot_number );
|
|
|
|
if( lifetime < MIN_DRIVER_LIFETIME )
|
|
|
|
{
|
|
|
|
/* The key is not in a secure element. */
|
|
|
|
TEST_EQUAL( status, PSA_ERROR_INVALID_ARGUMENT );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* The key is in a secure element. If it had been created
|
|
|
|
* in a specific slot, check that it is reported there. */
|
|
|
|
PSA_ASSERT( status );
|
|
|
|
status = psa_get_key_slot_number( reference_attributes,
|
|
|
|
&desired_slot_number );
|
|
|
|
if( status == PSA_SUCCESS )
|
|
|
|
{
|
|
|
|
TEST_EQUAL( desired_slot_number, actual_slot_number );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-07-24 19:09:30 +02:00
|
|
|
ok = 1;
|
|
|
|
|
|
|
|
exit:
|
|
|
|
return( ok );
|
|
|
|
}
|
|
|
|
|
2019-07-24 13:45:36 +02:00
|
|
|
/* Check that a function's return status is "smoke-free", i.e. that
|
|
|
|
* it's an acceptable error code when calling an API function that operates
|
|
|
|
* on a key with potentially bogus parameters. */
|
|
|
|
static int is_status_smoke_free( psa_status_t status )
|
|
|
|
{
|
|
|
|
switch( status )
|
|
|
|
{
|
|
|
|
case PSA_SUCCESS:
|
|
|
|
case PSA_ERROR_NOT_SUPPORTED:
|
|
|
|
case PSA_ERROR_NOT_PERMITTED:
|
|
|
|
case PSA_ERROR_BUFFER_TOO_SMALL:
|
|
|
|
case PSA_ERROR_INVALID_ARGUMENT:
|
|
|
|
case PSA_ERROR_INVALID_SIGNATURE:
|
|
|
|
case PSA_ERROR_INVALID_PADDING:
|
|
|
|
return( 1 );
|
|
|
|
default:
|
|
|
|
return( 0 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#define SMOKE_ASSERT( expr ) \
|
|
|
|
TEST_ASSERT( is_status_smoke_free( expr ) )
|
|
|
|
|
|
|
|
/* Smoke test a key. There are mostly no wrong answers here since we pass
|
|
|
|
* mostly bogus parameters: the goal is to ensure that there is no memory
|
|
|
|
* corruption or crash. This test function is most useful when run under
|
|
|
|
* an environment with sanity checks such as ASan or MSan. */
|
|
|
|
static int smoke_test_key( psa_key_handle_t handle )
|
|
|
|
{
|
|
|
|
int ok = 0;
|
|
|
|
psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
|
|
|
|
psa_mac_operation_t mac_operation = PSA_MAC_OPERATION_INIT;
|
|
|
|
psa_cipher_operation_t cipher_operation = PSA_CIPHER_OPERATION_INIT;
|
|
|
|
psa_key_derivation_operation_t derivation_operation =
|
|
|
|
PSA_KEY_DERIVATION_OPERATION_INIT;
|
|
|
|
uint8_t buffer[80]; /* large enough for a public key for ECDH */
|
|
|
|
size_t length;
|
|
|
|
psa_key_handle_t handle2 = 0;
|
|
|
|
|
|
|
|
SMOKE_ASSERT( psa_get_key_attributes( handle, &attributes ) );
|
|
|
|
|
|
|
|
SMOKE_ASSERT( psa_export_key( handle,
|
|
|
|
buffer, sizeof( buffer ), &length ) );
|
|
|
|
SMOKE_ASSERT( psa_export_public_key( handle,
|
|
|
|
buffer, sizeof( buffer ), &length ) );
|
|
|
|
|
|
|
|
SMOKE_ASSERT( psa_copy_key( handle, &attributes, &handle2 ) );
|
|
|
|
if( handle2 != 0 )
|
|
|
|
PSA_ASSERT( psa_close_key( handle2 ) );
|
|
|
|
|
|
|
|
SMOKE_ASSERT( psa_mac_sign_setup( &mac_operation, handle, PSA_ALG_CMAC ) );
|
|
|
|
PSA_ASSERT( psa_mac_abort( &mac_operation ) );
|
|
|
|
SMOKE_ASSERT( psa_mac_verify_setup( &mac_operation, handle,
|
|
|
|
PSA_ALG_HMAC( PSA_ALG_SHA_256 ) ) );
|
|
|
|
PSA_ASSERT( psa_mac_abort( &mac_operation ) );
|
|
|
|
|
|
|
|
SMOKE_ASSERT( psa_cipher_encrypt_setup( &cipher_operation, handle,
|
|
|
|
PSA_ALG_CTR ) );
|
|
|
|
PSA_ASSERT( psa_cipher_abort( &cipher_operation ) );
|
|
|
|
SMOKE_ASSERT( psa_cipher_decrypt_setup( &cipher_operation, handle,
|
|
|
|
PSA_ALG_CTR ) );
|
|
|
|
PSA_ASSERT( psa_cipher_abort( &cipher_operation ) );
|
|
|
|
|
|
|
|
SMOKE_ASSERT( psa_aead_encrypt( handle, PSA_ALG_CCM,
|
|
|
|
buffer, sizeof( buffer ),
|
|
|
|
NULL, 0,
|
|
|
|
buffer, sizeof( buffer),
|
|
|
|
buffer, sizeof( buffer), &length ) );
|
|
|
|
SMOKE_ASSERT( psa_aead_decrypt( handle, PSA_ALG_CCM,
|
|
|
|
buffer, sizeof( buffer ),
|
|
|
|
NULL, 0,
|
|
|
|
buffer, sizeof( buffer),
|
|
|
|
buffer, sizeof( buffer), &length ) );
|
|
|
|
|
|
|
|
SMOKE_ASSERT( psa_asymmetric_sign( handle, PSA_ALG_ECDSA_ANY,
|
|
|
|
buffer, 32,
|
|
|
|
buffer, sizeof( buffer ), &length ) );
|
|
|
|
SMOKE_ASSERT( psa_asymmetric_verify( handle, PSA_ALG_ECDSA_ANY,
|
|
|
|
buffer, 32,
|
|
|
|
buffer, sizeof( buffer ) ) );
|
|
|
|
|
|
|
|
SMOKE_ASSERT( psa_asymmetric_encrypt( handle, PSA_ALG_RSA_PKCS1V15_CRYPT,
|
|
|
|
buffer, 10, NULL, 0,
|
|
|
|
buffer, sizeof( buffer ), &length ) );
|
|
|
|
SMOKE_ASSERT( psa_asymmetric_decrypt( handle, PSA_ALG_RSA_PKCS1V15_CRYPT,
|
|
|
|
buffer, sizeof( buffer ), NULL, 0,
|
|
|
|
buffer, sizeof( buffer ), &length ) );
|
|
|
|
|
|
|
|
#if defined(MBEDTLS_SHA256_C)
|
|
|
|
/* Try the key in a plain key derivation. */
|
|
|
|
PSA_ASSERT( psa_key_derivation_setup( &derivation_operation,
|
|
|
|
PSA_ALG_HKDF( PSA_ALG_SHA_256 ) ) );
|
|
|
|
PSA_ASSERT( psa_key_derivation_input_bytes( &derivation_operation,
|
|
|
|
PSA_KEY_DERIVATION_INPUT_SALT,
|
|
|
|
NULL, 0 ) );
|
|
|
|
SMOKE_ASSERT( psa_key_derivation_input_key( &derivation_operation,
|
|
|
|
PSA_KEY_DERIVATION_INPUT_SECRET,
|
|
|
|
handle ) );
|
|
|
|
PSA_ASSERT( psa_key_derivation_abort( &derivation_operation ) );
|
|
|
|
|
|
|
|
/* If the key is asymmetric, try it in a key agreement, both as
|
|
|
|
* part of a derivation operation and standalone. */
|
|
|
|
if( psa_export_public_key( handle, buffer, sizeof( buffer ), &length ) ==
|
|
|
|
PSA_SUCCESS )
|
|
|
|
{
|
|
|
|
psa_algorithm_t alg =
|
|
|
|
PSA_ALG_KEY_AGREEMENT( PSA_ALG_ECDH,
|
|
|
|
PSA_ALG_HKDF( PSA_ALG_SHA_256 ) );
|
|
|
|
PSA_ASSERT( psa_key_derivation_setup( &derivation_operation, alg ) );
|
|
|
|
PSA_ASSERT( psa_key_derivation_input_bytes(
|
|
|
|
&derivation_operation, PSA_KEY_DERIVATION_INPUT_SALT,
|
|
|
|
NULL, 0 ) );
|
|
|
|
SMOKE_ASSERT( psa_key_derivation_key_agreement(
|
|
|
|
&derivation_operation,
|
|
|
|
PSA_KEY_DERIVATION_INPUT_SECRET,
|
|
|
|
handle, buffer, length ) );
|
|
|
|
PSA_ASSERT( psa_key_derivation_abort( &derivation_operation ) );
|
|
|
|
|
|
|
|
SMOKE_ASSERT( psa_raw_key_agreement(
|
|
|
|
alg, handle, buffer, length,
|
|
|
|
buffer, sizeof( buffer ), &length ) );
|
|
|
|
}
|
|
|
|
#endif /* MBEDTLS_SHA256_C */
|
|
|
|
|
|
|
|
ok = 1;
|
|
|
|
|
|
|
|
exit:
|
|
|
|
psa_reset_key_attributes( &attributes );
|
|
|
|
return( ok );
|
|
|
|
}
|
|
|
|
|
2019-07-23 16:13:14 +02:00
|
|
|
#define MAX_KEY_ID_FOR_TEST 10
|
2019-07-24 13:44:03 +02:00
|
|
|
static void psa_purge_storage( void )
|
2019-07-23 16:13:14 +02:00
|
|
|
{
|
2019-07-23 17:38:08 +02:00
|
|
|
psa_key_id_t id;
|
|
|
|
psa_key_lifetime_t lifetime;
|
2019-07-23 16:13:14 +02:00
|
|
|
/* The tests may have potentially created key ids from 1 to
|
|
|
|
* MAX_KEY_ID_FOR_TEST. In addition, run the destroy function on key id
|
|
|
|
* 0, which file-based storage uses as a temporary file. */
|
2019-07-23 17:38:08 +02:00
|
|
|
for( id = 0; id <= MAX_KEY_ID_FOR_TEST; id++ )
|
|
|
|
psa_destroy_persistent_key( id );
|
|
|
|
/* Purge the transaction file. */
|
2019-07-23 16:13:14 +02:00
|
|
|
psa_crypto_stop_transaction( );
|
2019-07-23 17:38:08 +02:00
|
|
|
/* Purge driver persistent data. */
|
|
|
|
for( lifetime = 0; lifetime < PSA_MAX_SE_LIFETIME; lifetime++ )
|
|
|
|
psa_destroy_se_persistent_data( lifetime );
|
2019-07-23 16:13:14 +02:00
|
|
|
}
|
|
|
|
|
2019-06-24 13:47:24 +02:00
|
|
|
/* END_HEADER */
|
|
|
|
|
|
|
|
/* BEGIN_DEPENDENCIES
|
2019-06-26 11:24:49 +02:00
|
|
|
* depends_on:MBEDTLS_PSA_CRYPTO_SE_C
|
2019-06-24 13:47:24 +02:00
|
|
|
* END_DEPENDENCIES
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* BEGIN_CASE */
|
|
|
|
void register_one( int lifetime, int version, int expected_status_arg )
|
|
|
|
{
|
|
|
|
psa_status_t expected_status = expected_status_arg;
|
|
|
|
psa_drv_se_t driver;
|
|
|
|
|
|
|
|
memset( &driver, 0, sizeof( driver ) );
|
|
|
|
driver.hal_version = version;
|
|
|
|
|
|
|
|
TEST_EQUAL( psa_register_se_driver( lifetime, &driver ),
|
|
|
|
expected_status );
|
|
|
|
|
|
|
|
PSA_ASSERT( psa_crypto_init( ) );
|
|
|
|
|
|
|
|
exit:
|
|
|
|
PSA_DONE( );
|
|
|
|
}
|
|
|
|
/* END_CASE */
|
2019-06-24 14:34:59 +02:00
|
|
|
|
|
|
|
/* BEGIN_CASE */
|
|
|
|
void register_twice( int count )
|
|
|
|
{
|
|
|
|
psa_drv_se_t driver;
|
|
|
|
psa_key_lifetime_t lifetime;
|
|
|
|
psa_key_lifetime_t max = MIN_DRIVER_LIFETIME + count;
|
|
|
|
|
|
|
|
memset( &driver, 0, sizeof( driver ) );
|
|
|
|
driver.hal_version = PSA_DRV_SE_HAL_VERSION;
|
|
|
|
|
|
|
|
for( lifetime = MIN_DRIVER_LIFETIME; lifetime < max; lifetime++ )
|
|
|
|
PSA_ASSERT( psa_register_se_driver( lifetime, &driver ) );
|
|
|
|
for( lifetime = MIN_DRIVER_LIFETIME; lifetime < max; lifetime++ )
|
|
|
|
TEST_EQUAL( psa_register_se_driver( lifetime, &driver ),
|
|
|
|
PSA_ERROR_ALREADY_EXISTS );
|
|
|
|
|
|
|
|
PSA_ASSERT( psa_crypto_init( ) );
|
|
|
|
|
|
|
|
exit:
|
|
|
|
PSA_DONE( );
|
|
|
|
}
|
|
|
|
/* END_CASE */
|
|
|
|
|
|
|
|
/* BEGIN_CASE */
|
|
|
|
void register_max( )
|
|
|
|
{
|
|
|
|
psa_drv_se_t driver;
|
|
|
|
psa_key_lifetime_t lifetime;
|
|
|
|
psa_key_lifetime_t max = MIN_DRIVER_LIFETIME + PSA_MAX_SE_DRIVERS;
|
|
|
|
|
|
|
|
memset( &driver, 0, sizeof( driver ) );
|
|
|
|
driver.hal_version = PSA_DRV_SE_HAL_VERSION;
|
|
|
|
|
|
|
|
for( lifetime = MIN_DRIVER_LIFETIME; lifetime < max; lifetime++ )
|
|
|
|
PSA_ASSERT( psa_register_se_driver( lifetime, &driver ) );
|
|
|
|
|
|
|
|
TEST_EQUAL( psa_register_se_driver( lifetime, &driver ),
|
|
|
|
PSA_ERROR_INSUFFICIENT_MEMORY );
|
|
|
|
|
|
|
|
PSA_ASSERT( psa_crypto_init( ) );
|
|
|
|
|
|
|
|
exit:
|
|
|
|
PSA_DONE( );
|
|
|
|
}
|
|
|
|
/* END_CASE */
|
2019-07-12 23:47:47 +02:00
|
|
|
|
|
|
|
/* BEGIN_CASE */
|
2019-07-23 16:13:14 +02:00
|
|
|
void key_creation_import_export( int min_slot, int restart )
|
2019-07-12 23:47:47 +02:00
|
|
|
{
|
|
|
|
psa_drv_se_t driver;
|
|
|
|
psa_drv_se_key_management_t key_management;
|
|
|
|
psa_key_lifetime_t lifetime = 2;
|
|
|
|
psa_key_id_t id = 1;
|
|
|
|
psa_key_handle_t handle = 0;
|
|
|
|
psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
|
|
|
|
const uint8_t key_material[3] = {0xfa, 0xca, 0xde};
|
|
|
|
uint8_t exported[sizeof( key_material )];
|
|
|
|
size_t exported_length;
|
|
|
|
|
|
|
|
memset( &driver, 0, sizeof( driver ) );
|
|
|
|
memset( &key_management, 0, sizeof( key_management ) );
|
|
|
|
driver.hal_version = PSA_DRV_SE_HAL_VERSION;
|
|
|
|
driver.key_management = &key_management;
|
|
|
|
driver.persistent_data_size = sizeof( ram_slot_usage_t );
|
|
|
|
key_management.p_allocate = ram_allocate;
|
|
|
|
key_management.p_import = ram_import;
|
|
|
|
key_management.p_destroy = ram_destroy;
|
|
|
|
key_management.p_export = ram_export;
|
|
|
|
ram_min_slot = min_slot;
|
|
|
|
|
|
|
|
PSA_ASSERT( psa_register_se_driver( lifetime, &driver ) );
|
|
|
|
PSA_ASSERT( psa_crypto_init( ) );
|
|
|
|
|
|
|
|
/* Create a key. */
|
|
|
|
psa_set_key_id( &attributes, id );
|
|
|
|
psa_set_key_lifetime( &attributes, lifetime );
|
|
|
|
psa_set_key_usage_flags( &attributes, PSA_KEY_USAGE_EXPORT );
|
|
|
|
psa_set_key_type( &attributes, PSA_KEY_TYPE_RAW_DATA );
|
|
|
|
PSA_ASSERT( psa_import_key( &attributes,
|
|
|
|
key_material, sizeof( key_material ),
|
|
|
|
&handle ) );
|
|
|
|
|
2019-07-23 16:13:14 +02:00
|
|
|
/* Maybe restart, to check that the information is saved correctly. */
|
|
|
|
if( restart )
|
|
|
|
{
|
|
|
|
mbedtls_psa_crypto_free( );
|
|
|
|
PSA_ASSERT( psa_register_se_driver( lifetime, &driver ) );
|
|
|
|
PSA_ASSERT( psa_crypto_init( ) );
|
|
|
|
PSA_ASSERT( psa_open_key( id, &handle ) );
|
|
|
|
}
|
|
|
|
|
2019-07-12 23:47:47 +02:00
|
|
|
/* Test that the key was created in the expected slot. */
|
|
|
|
TEST_ASSERT( ram_slots[min_slot].type == PSA_KEY_TYPE_RAW_DATA );
|
|
|
|
|
2019-08-02 20:30:01 +02:00
|
|
|
/* Test the key attributes, including the reported slot number. */
|
2019-07-24 20:30:14 +02:00
|
|
|
psa_set_key_bits( &attributes,
|
|
|
|
PSA_BYTES_TO_BITS( sizeof( key_material ) ) );
|
2019-08-02 20:30:01 +02:00
|
|
|
psa_set_key_slot_number( &attributes, min_slot );
|
2019-07-24 19:09:30 +02:00
|
|
|
if( ! check_key_attributes( handle, &attributes ) )
|
|
|
|
goto exit;
|
2019-08-02 20:30:01 +02:00
|
|
|
|
|
|
|
/* Test the key data. */
|
2019-07-12 23:47:47 +02:00
|
|
|
PSA_ASSERT( psa_export_key( handle,
|
|
|
|
exported, sizeof( exported ),
|
|
|
|
&exported_length ) );
|
|
|
|
ASSERT_COMPARE( key_material, sizeof( key_material ),
|
|
|
|
exported, exported_length );
|
|
|
|
|
|
|
|
PSA_ASSERT( psa_destroy_key( handle ) );
|
|
|
|
|
|
|
|
/* Test that the key has been erased from the designated slot. */
|
|
|
|
TEST_ASSERT( ram_slots[min_slot].type == 0 );
|
|
|
|
|
|
|
|
exit:
|
|
|
|
PSA_DONE( );
|
|
|
|
ram_slots_reset( );
|
2019-07-23 16:13:14 +02:00
|
|
|
psa_purge_storage( );
|
2019-07-12 23:47:47 +02:00
|
|
|
}
|
|
|
|
/* END_CASE */
|
2019-07-24 13:45:02 +02:00
|
|
|
|
2019-08-05 14:55:50 +02:00
|
|
|
/* BEGIN_CASE */
|
|
|
|
void key_creation_in_chosen_slot( int slot_arg,
|
2019-08-05 14:59:15 +02:00
|
|
|
int restart,
|
2019-08-05 14:55:50 +02:00
|
|
|
int expected_status_arg )
|
|
|
|
{
|
|
|
|
psa_key_slot_number_t wanted_slot = slot_arg;
|
|
|
|
psa_status_t expected_status = expected_status_arg;
|
|
|
|
psa_status_t status;
|
|
|
|
psa_drv_se_t driver;
|
|
|
|
psa_drv_se_key_management_t key_management;
|
|
|
|
psa_key_lifetime_t lifetime = 2;
|
|
|
|
psa_key_id_t id = 1;
|
|
|
|
psa_key_handle_t handle = 0;
|
|
|
|
psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
|
|
|
|
const uint8_t key_material[3] = {0xfa, 0xca, 0xde};
|
|
|
|
|
|
|
|
memset( &driver, 0, sizeof( driver ) );
|
|
|
|
memset( &key_management, 0, sizeof( key_management ) );
|
|
|
|
driver.hal_version = PSA_DRV_SE_HAL_VERSION;
|
|
|
|
driver.key_management = &key_management;
|
|
|
|
driver.persistent_data_size = sizeof( ram_slot_usage_t );
|
|
|
|
key_management.p_validate_slot_number = ram_validate_slot_number;
|
|
|
|
key_management.p_import = ram_import;
|
|
|
|
key_management.p_destroy = ram_destroy;
|
|
|
|
key_management.p_export = ram_export;
|
|
|
|
|
|
|
|
PSA_ASSERT( psa_register_se_driver( lifetime, &driver ) );
|
|
|
|
PSA_ASSERT( psa_crypto_init( ) );
|
|
|
|
|
|
|
|
/* Create a key. */
|
|
|
|
psa_set_key_id( &attributes, id );
|
|
|
|
psa_set_key_lifetime( &attributes, lifetime );
|
|
|
|
psa_set_key_usage_flags( &attributes, PSA_KEY_USAGE_EXPORT );
|
|
|
|
psa_set_key_type( &attributes, PSA_KEY_TYPE_RAW_DATA );
|
|
|
|
psa_set_key_slot_number( &attributes, wanted_slot );
|
|
|
|
status = psa_import_key( &attributes,
|
|
|
|
key_material, sizeof( key_material ),
|
|
|
|
&handle );
|
|
|
|
TEST_EQUAL( status, expected_status );
|
|
|
|
|
2019-08-05 14:59:15 +02:00
|
|
|
if( status != PSA_SUCCESS )
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
/* Maybe restart, to check that the information is saved correctly. */
|
|
|
|
if( restart )
|
2019-08-05 14:55:50 +02:00
|
|
|
{
|
2019-08-05 14:59:15 +02:00
|
|
|
mbedtls_psa_crypto_free( );
|
|
|
|
PSA_ASSERT( psa_register_se_driver( lifetime, &driver ) );
|
|
|
|
PSA_ASSERT( psa_crypto_init( ) );
|
|
|
|
PSA_ASSERT( psa_open_key( id, &handle ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Test that the key was created in the expected slot. */
|
|
|
|
TEST_EQUAL( ram_slots[wanted_slot].type, PSA_KEY_TYPE_RAW_DATA );
|
2019-08-05 14:55:50 +02:00
|
|
|
|
2019-08-05 14:59:15 +02:00
|
|
|
/* Test that the key is reported with the correct attributes,
|
|
|
|
* including the expected slot. */
|
|
|
|
PSA_ASSERT( psa_get_key_attributes( handle, &attributes ) );
|
2019-08-05 14:55:50 +02:00
|
|
|
|
2019-08-05 14:59:15 +02:00
|
|
|
PSA_ASSERT( psa_destroy_key( handle ) );
|
2019-08-05 14:55:50 +02:00
|
|
|
|
|
|
|
exit:
|
|
|
|
PSA_DONE( );
|
|
|
|
ram_slots_reset( );
|
|
|
|
psa_purge_storage( );
|
|
|
|
}
|
|
|
|
/* END_CASE */
|
|
|
|
|
2019-07-24 13:45:36 +02:00
|
|
|
/* BEGIN_CASE */
|
|
|
|
void key_creation_smoke( int type_arg, int alg_arg,
|
|
|
|
data_t *key_material )
|
|
|
|
{
|
|
|
|
psa_key_type_t type = type_arg;
|
|
|
|
psa_algorithm_t alg = alg_arg;
|
|
|
|
psa_drv_se_t driver;
|
|
|
|
psa_drv_se_key_management_t key_management;
|
|
|
|
psa_key_lifetime_t lifetime = 2;
|
|
|
|
psa_key_id_t id = 1;
|
|
|
|
psa_key_handle_t handle = 0;
|
|
|
|
psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
|
|
|
|
|
|
|
|
memset( &driver, 0, sizeof( driver ) );
|
|
|
|
memset( &key_management, 0, sizeof( key_management ) );
|
|
|
|
driver.hal_version = PSA_DRV_SE_HAL_VERSION;
|
|
|
|
driver.key_management = &key_management;
|
|
|
|
driver.persistent_data_size = sizeof( psa_key_slot_number_t );
|
|
|
|
key_management.p_allocate = counter_allocate;
|
|
|
|
key_management.p_import = null_import;
|
|
|
|
|
|
|
|
PSA_ASSERT( psa_register_se_driver( lifetime, &driver ) );
|
|
|
|
PSA_ASSERT( psa_crypto_init( ) );
|
|
|
|
|
|
|
|
/* Create a key. */
|
|
|
|
psa_set_key_id( &attributes, id );
|
|
|
|
psa_set_key_lifetime( &attributes, lifetime );
|
|
|
|
psa_set_key_usage_flags( &attributes,
|
|
|
|
PSA_KEY_USAGE_SIGN | PSA_KEY_USAGE_VERIFY |
|
|
|
|
PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT |
|
|
|
|
PSA_KEY_USAGE_EXPORT );
|
|
|
|
psa_set_key_algorithm( &attributes, alg );
|
|
|
|
psa_set_key_type( &attributes, type );
|
|
|
|
PSA_ASSERT( psa_import_key( &attributes,
|
|
|
|
key_material->x, key_material->len,
|
|
|
|
&handle ) );
|
|
|
|
|
|
|
|
/* Do stuff with the key. */
|
|
|
|
if( ! smoke_test_key( handle ) )
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
/* Restart and try again. */
|
|
|
|
mbedtls_psa_crypto_free( );
|
|
|
|
PSA_ASSERT( psa_register_se_driver( lifetime, &driver ) );
|
|
|
|
PSA_ASSERT( psa_crypto_init( ) );
|
|
|
|
PSA_ASSERT( psa_open_key( id, &handle ) );
|
|
|
|
if( ! smoke_test_key( handle ) )
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
/* We're done. */
|
|
|
|
PSA_ASSERT( psa_destroy_key( handle ) );
|
|
|
|
|
|
|
|
exit:
|
|
|
|
PSA_DONE( );
|
|
|
|
ram_slots_reset( );
|
|
|
|
psa_purge_storage( );
|
|
|
|
}
|
|
|
|
/* END_CASE */
|
|
|
|
|
2019-07-24 13:45:02 +02:00
|
|
|
/* BEGIN_CASE */
|
|
|
|
void generate_key_not_supported( int type_arg, int bits_arg )
|
|
|
|
{
|
|
|
|
psa_key_type_t type = type_arg;
|
|
|
|
size_t bits = bits_arg;
|
|
|
|
psa_drv_se_t driver;
|
|
|
|
psa_drv_se_key_management_t key_management;
|
|
|
|
psa_key_lifetime_t lifetime = 2;
|
|
|
|
psa_key_id_t id = 1;
|
|
|
|
psa_key_handle_t handle = 0;
|
|
|
|
psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
|
|
|
|
|
|
|
|
memset( &driver, 0, sizeof( driver ) );
|
|
|
|
memset( &key_management, 0, sizeof( key_management ) );
|
|
|
|
driver.hal_version = PSA_DRV_SE_HAL_VERSION;
|
|
|
|
driver.key_management = &key_management;
|
|
|
|
driver.persistent_data_size = sizeof( psa_key_slot_number_t );
|
|
|
|
key_management.p_allocate = counter_allocate;
|
|
|
|
|
|
|
|
PSA_ASSERT( psa_register_se_driver( lifetime, &driver ) );
|
|
|
|
PSA_ASSERT( psa_crypto_init( ) );
|
|
|
|
|
|
|
|
psa_set_key_id( &attributes, id );
|
|
|
|
psa_set_key_lifetime( &attributes, lifetime );
|
|
|
|
psa_set_key_type( &attributes, type );
|
|
|
|
psa_set_key_bits( &attributes, bits );
|
|
|
|
TEST_EQUAL( psa_generate_key( &attributes, &handle ),
|
|
|
|
PSA_ERROR_NOT_SUPPORTED );
|
|
|
|
|
|
|
|
exit:
|
|
|
|
PSA_DONE( );
|
|
|
|
ram_slots_reset( );
|
|
|
|
psa_purge_storage( );
|
|
|
|
}
|
|
|
|
/* END_CASE */
|
2019-08-05 15:55:54 +02:00
|
|
|
|
|
|
|
/* BEGIN_CASE */
|
2019-08-05 17:17:52 +02:00
|
|
|
void register_key_smoke_test( int lifetime_arg,
|
|
|
|
int validate,
|
|
|
|
int expected_status_arg )
|
2019-08-05 15:55:54 +02:00
|
|
|
{
|
|
|
|
psa_key_lifetime_t lifetime = lifetime_arg;
|
|
|
|
psa_status_t expected_status = expected_status_arg;
|
|
|
|
psa_drv_se_t driver;
|
2019-08-05 17:17:52 +02:00
|
|
|
psa_drv_se_key_management_t key_management;
|
2019-08-05 15:55:54 +02:00
|
|
|
psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
|
|
|
|
psa_key_id_t id = 1;
|
|
|
|
size_t bit_size = 48;
|
|
|
|
psa_key_slot_number_t wanted_slot = 0x123456789;
|
|
|
|
psa_key_handle_t handle = 0;
|
|
|
|
psa_status_t status;
|
|
|
|
|
|
|
|
memset( &driver, 0, sizeof( driver ) );
|
|
|
|
driver.hal_version = PSA_DRV_SE_HAL_VERSION;
|
2019-08-05 17:17:52 +02:00
|
|
|
if( validate >= 0 )
|
|
|
|
{
|
|
|
|
memset( &key_management, 0, sizeof( key_management ) );
|
|
|
|
driver.key_management = &key_management;
|
|
|
|
key_management.p_validate_slot_number = validate_slot_number_as_directed;
|
|
|
|
validate_slot_number_directions.slot_number = wanted_slot;
|
|
|
|
validate_slot_number_directions.method = PSA_KEY_CREATION_REGISTER;
|
|
|
|
validate_slot_number_directions.status =
|
|
|
|
( validate > 0 ? PSA_SUCCESS : PSA_ERROR_NOT_PERMITTED );
|
|
|
|
}
|
2019-08-05 15:55:54 +02:00
|
|
|
|
|
|
|
PSA_ASSERT( psa_register_se_driver( MIN_DRIVER_LIFETIME, &driver ) );
|
|
|
|
PSA_ASSERT( psa_crypto_init( ) );
|
|
|
|
|
|
|
|
psa_set_key_id( &attributes, id );
|
|
|
|
psa_set_key_lifetime( &attributes, lifetime );
|
|
|
|
psa_set_key_usage_flags( &attributes, PSA_KEY_USAGE_EXPORT );
|
|
|
|
psa_set_key_type( &attributes, PSA_KEY_TYPE_RAW_DATA );
|
|
|
|
psa_set_key_bits( &attributes, bit_size );
|
|
|
|
psa_set_key_slot_number( &attributes, wanted_slot );
|
|
|
|
|
|
|
|
status = mbedtls_psa_register_se_key( &attributes );
|
|
|
|
TEST_EQUAL( status, expected_status );
|
|
|
|
|
|
|
|
if( status != PSA_SUCCESS )
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
/* Test that the key exists and has the expected attributes. */
|
|
|
|
PSA_ASSERT( psa_open_key( id, &handle ) );
|
|
|
|
if( ! check_key_attributes( handle, &attributes ) )
|
|
|
|
goto exit;
|
|
|
|
PSA_ASSERT( psa_close_key( handle ) );
|
|
|
|
|
|
|
|
/* Restart and try again. */
|
|
|
|
PSA_DONE( );
|
|
|
|
PSA_ASSERT( psa_register_se_driver( lifetime, &driver ) );
|
|
|
|
PSA_ASSERT( psa_crypto_init( ) );
|
|
|
|
PSA_ASSERT( psa_open_key( id, &handle ) );
|
|
|
|
if( ! check_key_attributes( handle, &attributes ) )
|
|
|
|
goto exit;
|
|
|
|
/* This time, destroy the key. */
|
|
|
|
PSA_ASSERT( psa_destroy_key( handle ) );
|
|
|
|
|
|
|
|
exit:
|
|
|
|
psa_reset_key_attributes( &attributes );
|
|
|
|
psa_destroy_key( handle );
|
|
|
|
PSA_DONE( );
|
|
|
|
psa_purge_storage( );
|
2019-08-05 17:17:52 +02:00
|
|
|
memset( &validate_slot_number_directions, 0,
|
|
|
|
sizeof( validate_slot_number_directions ) );
|
2019-08-05 15:55:54 +02:00
|
|
|
}
|
|
|
|
/* END_CASE */
|