End-to-end Data Protection & Integrity

Service overview

End-to-end Data Protection provides bidirectional application layer encryption between your application platform and the u-blox cellular module with no need to establish a permanent secure session between the two endpoints . The application data is encrypted on one endpoint (the module or your cloud application) and the corresponding decryption key and the relevant parameters are made available on the other endpoint. The secret keys are never exposed over the air.

In Upstream the data can be:

In Downstream the data can be:

The process does not depend on the security of the transport layers or storage mechanisms used between the device and the end service. The following picture shows the steps for the upstream encryption.

Note that on step (4)  of the sequence diagram below , the key identity is represented by the firsts 16 bytes of the payload.

Upstream (Device to cloud)

The application layer (device) wants to send a message to cloud securely without being involved in the security stack. The sequence diagram below demonstrate the main players and the interactions between them.

The diagram below demonstrates the steps from device to cloud.

Two working modes are available:

Downstream (Cloud to Device)

In this scenario, your application platform needs to send a message to a particular device securely. The sequence diagram demonstrates the players that the interactions between them.

The diagram below demonstrates the steps from cloud to device.

In Downstream it is available only the default working mode mentioned above for upstream.

Key rotation

To enhance the security of your data, the cryptographic key used for end-to-end encryption should be renewed frequently.  Key rotation is the procedure that let you to trigger the generation of a new fresh key set to be used for the encryption. It's worth to note that key is not accessible on device side, since encryption/decryption process is performed in the module, never exposing the key. 

Remember that the keys are different for each device and are in a number sufficient to ensure more than a new  key every day for the entire device lifetime

There are two types of key rotation available:

This rotation type specifies that on every encryption request, a new key will be generated and used. After setting this type on the cloud, following command needs to be run (just once) on the device:

AT+USECOPCMD="e2e_enc",0

This option is available only in upstream

This rotation type specifies that after a set number of days (configurable from 1 to 30), a new encryption key will be automatically generated and used by the encryption algorithm in the module.

Note: For key to be rotated on the device, the device needs to send a security heartbeat. When setting key rotation, please keep in mind that devices security heartbeat will be adjusted to reflect the number of days you want the key to be rotated. For example, if security heartbeat was  initially set at 5 days, and you want the key to be rotated every 3 days, security heartbeat will be updated by the Thingstream platform to happen every 3 days. 

After setting this working mode on the cloud, following command needs to be run (just once) on the device. Since this is is the default working mode, this AT Command shall be used only if previously the continuous working mode has ben set by you:

AT+USECOPCMD="e2e_enc",1

The following command line examples use cURL to set AUTOMATIC key rotation.

curl -X POST "https://ssapi.services.u-blox.com/v1/e2e/key/rotate" -H "accept: application/json" -H "Content-Type: application/json" -H Authorization: [AuthToken] -d '{"ROTPublicUIDs":["00080008003a9e26"], "Type": "AUTOMATIC", "Days": 3}'

curl -X POST "https://ssapi.services.u-blox.com/v1/e2e/key/rotate" -H "accept: application/json" -H "Content-Type: application/json" -H Authorization: [AuthToken] -d "{\"ROTPublicUIDs\":[\"00080008003a9e26\"], \"Type\": \"AUTOMATIC\", \"Days\": 3}"

The benefit of the automatic rotation approach is that on your cloud application you do not need to retrieve a new set of protection parameters for every message and furthermore that the derivation of a new key set is done automatically, without any logit to be implemented on your cloud application or on your device. The tradeoff is:

You can avoid to add this complexity by always asking the valid key set to Thingstream platform, independently if you are using continuous or automatic rotation

It is worth noting that:

Use cases

Once the device has been bootstrapped and the End-to-end Data Protection functionality has been enabled, the end-to-end encryption functionality can be called, as described below. To enable the service, you shall select one of the available 'E2E Security plans' (Developer, Daily, Flex or Freedom) during device profile configuration or when change the state of a Security Thing from allocated to active.

Similar AT commands are available in case you need to preserve only integrity and authenticity and will be shown in the examples below.

This may not be a direct call to the customer’s remote service but may involve the transporting of the data via 1 to [N] insecure internet servers (corresponding to step 3). During this transport phase the security and integrity of the data is still maintained as the data is encrypted and authenticated. The data can be retained by the customer’s remote service for decryption at a later date.

The key identity shall be forwarded to the u-blox security service via /e2e/uplink/protectionparameters/get API call, so that the matching decryption key and decryption parameters can be retrieved.


The AT command +USECE2EDATAENC can be called to encrypt data on device side

This operation corresponds to step 2 in  flow diagram above.

Examples - upstream

Following are some examples of the procedure described above.

The prerequisites are:

Important note: all the API responses the you see in the current guide have the unique aim to show which is the response format.  The data provided in the response cannot be reused, but you have to retrive your own valid protection parameters from the Thingstream platform.

Continuous key rotation

These are example commands and do not use real world API keys or authorization headers. Actual API secret key and authorization headers must first be created by you through the Thingstream service delivery platform before running these commands.

Check the Getting started guide and the Tools and Software section if you do not know how to do it

Use m-center in HEX mode (enable the checkbox on the top in the AT terminal). This way it’s easier to parse the encrypted data returned by the AT command.

The AT command to send is:

AT+USECE2EDATAENC=5

> HELLO

The command returns the length of the encrypted data and the encrypted data itself:

+USECE2EDATAENC: 37, "<encrypted_data>"

In this case you get back from the module  37 bytes of encrypted data, which in hex format is:

1101000089291e26ec1aeb00744500000b874c81e91d623addf63bcebe64fe8b290efb4cd7

This is the encryption part of the process. At this point the encrypted data can be transferred to the recipient by secure or insecure means using every network interface

The encrypted and authenticated data returned by the AT+USECE2EDATAENC command is composed, by:

First thing to do is to extract the key identity from the encrypted data. The most significant nibble (4 bits) of the encrypted data define the key identity length:

In our example we have:

Since the most significant nibble is 0x1, we extract the first 16 bytes:

Key identity: 1101000089291e26ec1aeb0074450000

The data encrypted using the +USECE2EDATAENC and +USECE2EFILEENC AT commands can only be decrypted using a key obtained using the /e2e/uplink/protectionparameters/get REST API. Make sure to have an AuthToken to use for the Authorization header – if not, call the Authorize API to get one.

Specifically, the REST API will provide the key and algorithm details needed to decrypt and verify the authenticity of the cipher text generated by +USECE2EDATAENC and +USECE2EFILEENC.

The following command line examples use cURL to retrieve the PSK and decryption details, by sending the key identity previously extracted in the “EncryptedHeader” parameter.

Request to be run on Linux and macOS:

curl -X POST "https://ssapi.services.u-blox.com/v1/e2e/uplink/protectionparameters/get" -H "accept: application/json" -H "Content-Type: application/json" -H Authorization: [AuthToken] -d '{"EncryptedHeader":"1101000089291e26ec1aeb0074450000"}'

Request to be run on Windows:

curl -X POST "https://ssapi.services.u-blox.com/v1/e2e/uplink/protectionparameters/get" -H "accept: application/json" -H "Content-Type: application/json" -H Authorization: [AuthToken] -d "{\"EncryptedHeader\":\"1101000089291e26ec1aeb0074450000\"}"


If the data was only signed on the device (and not encrypted), following API needs to be called to get respective parameters to verify authenticity of the data on cloud:

Request to be run on Linux and macOS:

curl -X POST "https://ssapi.services.u-blox.com/v1/e2e/uplink/integrityparameters/get" -H "accept: application/json" -H "Content-Type: application/json" -H Authorization: [AuthToken] -d '{"EncryptedHeader":" 290000020000192800bfff1"}'

Request to be run on Windows:

curl -X POST "https://ssapi.services.u-blox.com/v1/e2e/uplink/integrityparameters/get" -H "accept: application/json" -H "Content-Type: application/json" -H Authorization: [AuthToken] -d "{\"EncryptedHeader\":\"290000020000192800bfff1\"}"


 {

     'CipherSuite': 'AES_128_CS1_HMAC_SHA256',

     'AESIV': 'KgAABAAIAD+yUgABAAAAAA==',

     'KIV': 'KgAABAAIAD+yUgAB',

     'EncryptionKey': 'iGoE1WPNYd6+vcJxZmB2QQ==',

     'AuthenticationKey': 'CuHbo9SDN/Bbh3Qe8G5xYg==',

     'MACLength': 128,

     'KeyLength': 128,

        'AuthKeyLength': 128,

     'ROTPublicUID': '00041208003fa662'

   }

The KI_LENGTH bytes long header returned by +USECE2EDATAENC and +USECE2EFILEENC, when converted to an HEX string, is used in the curl request as the value of the EncryptedHeader parameter.

The key and nonce values returned are base-64 encoded, and can be used to decode the ciphertext, i.e. the data returned by +USECE2EDATAENC and +USECE2EFILEENC after having removed the KI_LENGTH bytes header, using the algorithm specified, in this case AES with the CCM mode. The MAC (16 for CCM mode or 8 bytes for CCM_8 mode) is appended at the end of the encrypted payload and it must be verified by the customer.

Since the MAC tag is appended to the cipher text in the encoded data, we can now split the response of AT+USECE2EDATAENC this way:

At this point we have all the inputs to decrypt and authenticate the encrypted data.

From the API response:

From the encrypted AT+USECE2EDATAENC response:

We provide, as an example, a Python snippet which decrypts the cipher text by using the PyCryptodome package (https://pycryptodome.readthedocs.io/en/latest/):

from base64 import b64decode

from Cryptodome.Cipher import AES

import binascii

 

def aesccm_dec(enc_data, keyb64, nonceb64, mac_len):

    '''

    Decrypt a cipher text using AES-CCM algorithm.

    :param enc_data: the encrypted data string returned by AT+USECE2EDATAENC

    :param keyb64: the decryption key returned by GetE2EDecryptionParameters

    :param nonceb64: the nonce returned by GetE2EDecryptionParameters

    :mac_len: the MAC tag length returned by GetE2EDecryptionParameters

    '''

    # decryption key and nonce are given by the API response as base64 strings

    # they need to be converted to bytes (binary data)

    key = b64decode(keyb64)

    nonce = b64decode(nonceb64)

    # ciphertext and MAC tag are extracted from the encrypted data as hex strings

    # get key identity and MAC tag lengths

    mac_len = mac_len // 8      # convert to bytes

    key_id_len = 16 if enc_data[0] == '1' else 11     # in bytes

    # get ciphertext and MAC tag and convert them to bytes (binary data)

    ciphertext = binascii.unhexlify(enc_data[key_id_len*2:-mac_len*2])

    tag = binascii.unhexlify(enc_data[-mac_len*2:])

    try:

        cipher = AES.new(key, AES.MODE_CCM, nonce=nonce)

        plaintext = cipher.decrypt_and_verify(ciphertext, tag)

        print("The message is: " + plaintext.decode('utf-8'))

    except (ValueError, KeyError) as ex:

        print("Incorrect decryption")

        print(ex)

Running this function with the data from the example, you get back the original “HELLO” message.

Automatic key rotation

In case you are using the automatic rotation option, nothing changes on the device side. The same AT command can be used as above. except for the fact that you have to set the device, just once to use it issuing the AT Command AT+USECOPCMD=”e2e_enc”, 1

Remember that this is default option set in the module, therefore there is no need to send this command if you do not want to change the default working mode.

AT+USECE2EDATAENC=5

> HELLO

that  returns the length of the encrypted data and the encrypted data itself:

+USECE2EDATAENC: 33, "<encrypted_data>"

In this case we get 33 bytes of encrypted data, which in hex format is:

2a0000041908003fa6620001697e2fe027bc63a0c668d2c6a7973043b2307f8228

This is the encryption part of the process. At this point the encrypted data can be transferred to the recipient by secure or insecure means.

The encrypted and authenticated data returned by the AT+USECE2EDATAENC command is composed, in order, by:

The most significant nibble (4 bits) of the encrypted data defines the key identity length. The nibble would be set to 0x2, and key identity is 12 bytes long.

In our example we have:

Since the most significant nibble is 0x2, we extract the first 12 bytes:

Key identity: 2a0000041908003fa6620001

Request to be run on Linux and macOS:

curl -X POST "https://ssapi.services.u-blox.com/v1/e2e/uplink/protectionparameters/get" -H "accept: application/json" -H "Content-Type: application/json" -H Authorization: [AuthToken] -d '{"EncryptedHeader":"2a0000041908003fa6620001"}'

Request to be run on Windows:

curl -X POST "https://ssapi.services.u-blox.com/v1/e2e/uplink/protectionparameters/get" -H "accept: application/json" -H "Content-Type: application/json" -H Authorization: [AuthToken] -d "{\"EncryptedHeader\":\"2a0000041908003fa6620001\"}"

If the data was only signed on the device (and not encrypted), following API needs to be called to get respective parameters to verify authenticity of the data on cloud:

Request to be run on Linux and macOS:

curl -X POST "https://ssapi.services.u-blox.com/v1/e2e/uplink/integrityparameters/get" -H "accept: application/json" -H "Content-Type: application/json" -H Authorization: [AuthToken] -d '{"EncryptedHeader":" 2a0000041908003fa6620001"}'

Request to be run on Windows:

curl -X POST "https://ssapi.services.u-blox.com/v1/e2e/uplink/integrityparameters/get" -H "accept: application/json" -H "Content-Type: application/json" -H Authorization: [AuthToken] -d "{\"EncryptedHeader\":\"2a0000041908003fa6620001\"}"

If the request succeeds, API response would be:

  {

     'CipherSuite': 'AES_128_CS1_HMAC_SHA256',

     'AESIV': 'KgAABAAIAD+yUgABAAAAAA==',

     'KIV': 'KgAABAAIAD+yUgAB',

     'EncryptionKey': 'iGoE1WPNYd6+vcJxZmB2QQ==',

     'AuthenticationKey': 'CuHbo9SDN/Bbh3Qe8G5xYg==',

     'MACLength': 128,

     'KeyLength': 128,

        'AuthKeyLength': 128,

     'ROTPublicUID': '00041208003fa662'

   }

while this is the response format in case of signing only (integrity)

{

'CipherSuite': 'HMAC_SHA256', 

'AuthenticationKey': 'NR9u5wz3zpF17M5aSkOJpA==',

'KIV': 'KgAABAAIAD+mYg0C', 

'ROTPublicUID': '00040008003fa662',

'MACLength': 128,

'AuthKeyLength': 128

} 

The KI_LENGTH bytes long header returned by +USECE2EDATAENC and +USECE2EFILEENC, when converted to an HEX string, is used in the curl request as the value of the EncryptedHeader parameter.

The key and nonce values returned are base-64 encoded, and can be used to decode the ciphertext, i.e. the data returned by +USECE2EDATAENC and +USECE2EFILEENC after having removed the KI_LENGTH bytes header, using the algorithm specified, in this case AES with the CBC-CS1 mode. The MAC (16 bytes) is appended at the end of the encrypted payload and it must be verified by the customer.

Since the MAC tag is appended to plain text and whole data is encrypted so we can extract mac tag after decrypting the data first.

At this point we have all the inputs to decrypt and authenticate the encrypted data.

From the API response:

From the encrypted AT+USECE2EDATAENC response:

We provide, as an example, a Python snippet which decrypts the cipher text by using the PyCryptodome package (https://pycryptodome.readthedocs.io/en/latest/).

Sample code

from base64 import b64decode

import binascii

from Cryptodome.Cipher import AES

import hmac

import hashlib

import math



def to_decrypt_blocks(data, block_size):

    l = len(data)

    b = math.ceil(l / block_size)

    partial_block_length = l % block_size

    blocks = []

    for i in range(0, b):

        if (i == b - 2):  # second last block

            if partial_block_length > 0:

                blocks.append(data[i * block_size:(i * block_size) + partial_block_length])

            else:

                blocks.append(data[i * block_size:(i * block_size) + block_size])

        elif (i == b - 1):

            blocks.append(data[-block_size:])

        else:

            blocks.append(data[i * block_size:(i * block_size) + block_size])


    return blocks



def aescbc_sha256_dec(ENC_DATA, EncryptionKey, AuthenticationKey, KIV, AESIV, MACLength):

    # AES-CBC-CS1-SHA256

    key_bin = b64decode(EncryptionKey)

    key = key_bin.hex()


    auth_bin = b64decode(AuthenticationKey)


    kiv_bin = b64decode(KIV)


    aesiv = b64decode(AESIV)

    # ciphertext and MAC tag are extracted from the encrypted data as hex strings

    # get key identity and MAC tag lengths

    mac_len = MACLength // 8  # in bytes

    key_id_len = 12  # in bytes


    aes_block_size_hex = len(key)

    aes_block_size_bin = len(key) // 2


    ciphertext = ENC_DATA[key_id_len * 2:]


    # pycrptodomex does not support CBC-CS1

    # so completing partial block

    blocks = to_decrypt_blocks(ciphertext, aes_block_size_hex)


    padded_len = 0

    # Padding partially complete block

    if len(blocks) > 1:

        last_block = blocks[len(blocks) - 1]

        b = binascii.unhexlify(last_block)

cipher = AES.new(key_bin, AES.MODE_ECB)

        a = cipher.decrypt(b)

        ba = bytes(a)

        second_last_block = binascii.unhexlify(blocks[len(blocks) - 2])

        padded_len = aes_block_size_bin - len(second_last_block)

        padded_block_bin = second_last_block + ba[-padded_len:]

        blocks[len(blocks) - 2] = padded_block_bin.hex()


    ciphertext = binascii.unhexlify(''.join(blocks))


    try:

        cipher = AES.new(key_bin, AES.MODE_CBC, iv=aesiv)

        plaintext = cipher.decrypt(ciphertext)

        plaintext = plaintext[:len(plaintext) - padded_len]  # removing padded zeros from decrytped message

        tag = plaintext[-mac_len:]


        verify_data = kiv_bin + plaintext[0:len(plaintext) - 16]


        hmac_bin = hmac.new(auth_bin, verify_data, hashlib.sha256).digest()

        hmac_bin = hmac_bin[:16]

        if hmac_bin == tag:

            print("Message authenticity verified using authentication key")

        else:

            print("Failed to verify message authenticity.")

        print("The message was: " + plaintext[:-16].decode('utf-8'))

    except (ValueError, KeyError) as ex:

        print("Incorrect decryption")

        print(ex)


if __name__ == '__main__':

        print("V2: using AES_128_CS1_HMAC_SHA256 decryption")

        aescbc_sha256_dec(encrypted_data,EncryptionKey, AuthenticationKey, KIV, AESIV, MACLength)

Running this function with the data from the example we get back our original “HELLO” message.

Examples - downstream

The prerequisite to implement this example are the same one described in the upstream example section.

To encrypt in downstream (from your cloud application to the device,) you need to retrieve the protection parameters for that device using the proper REST API 

https://ssapi.services.u-blox.com/v1/e2e/downlink/protectionparameters/get

Make sure to have an auth. token to use for the Authorization header – if not, call the Authorize API to get one.

Specifically, the REST API will provide the key and algorithm details needed to encrypt and verify the authenticity of the cipher text, which will be used for decryption on the device side.

If you need only integrity refer to the following API

/e2e/downlink/integrityparameters/get

The following command line examples use cURL to retrieve the encryption details, by sending the ROTPublicUID of the device.

To encrypt data on the server, and send to device, you need to call following API and get encryption parameters:

Request to be run on Linux and macOS:

curl -X POST "https://ssapi.services.u-blox.com/v1/e2e/downlink/protectionparameters/get" -H "accept: application/json" -H "Content-Type: application/json" -H Authorization: [AuthToken] -d '{"ROTPublicUID":"2000089282245"}'

Request to be run on Windows:

curl -X POST "https://ssapi.services.u-blox.com/v1/e2e/downlink/protectionparameters/get" -H "accept: application/json" -H "Content-Type: application/json" -H Authorization: [AuthToken] -d "{\"ROTPublicUID\":\"2000089282245\"}"

 

If the request succeeds, the API response contains a JSON with this information (for data encryption and signing):

{

"MACLength": 128,

"KeyLength": 128,

"CipherSuite": "AES_128_CS1_HMAC_SHA256",

"AuthenticationKey": "lTZfP2dO5IAXvJT8FlXV5A==",

"AuthKeyLength": 128,

"EncryptionKey": "EuPzZxheaAoGAQ/+X2mYbg==",

"KIV": "EQEBAIkoIrZnYX4vnjgCCC==",

"AESIV": "KgXYZgAAiSgisgADBBBBBB",

"ROTPublicUID": 2000089282245

}

A similar JSON is returned in case you are using only integrity, with only the relevant parameters

{

'CipherSuite': 'HMAC_SHA256', 

'AuthenticationKey': 'NR9u5wz3zpF17M5aSkOJpA==',

'KIV': 'KgAABAAIAD+mYg0C', 

'ROTPublicUID': '00040008003fa662',

'MACLength': 128,

'AuthKeyLength': 128

}

Following steps need to be followed to encrypt data in your cloud application with the given information:

def sha256_sign(dec_params):

# SHA256

auth_bin = b64decode(dec_params.AuthenticationKey)

kiv_bin = b64decode(dec_params.KIV)

mac_len = dec_params.MACLength // 8  # in bytes

key_id_len = 12  # in bytes

msg_bin = bytearray(MESSAGE, 'UTF-8')

msg_hex = msg_bin.hex()

tag_payload = kiv_bin + msg_bin

hmac_bin = hmac.new(auth_bin, tag_payload, hashlib.sha256).digest()

Where:

NOTE: If the intention was to only sign the data, and not encrypt it, you don’t need to follow rest of the steps, and send following payload to device:

KIV + DATA + TAG

Whereas TAG is truncated to 16 bytes as shown in the above code example.

Sample code

def to_blocks(data, block_size):

    l = len(data)

    b = math.ceil(l / block_size)

    blocks=[]

    for i in range(0,b):

        blocks.append(data[i*block_size:(i*block_size)+block_size])

    return blocks


def aescbc_sha256_enc(dec_params):


    key_bin = b64decode(dec_params.EncryptionKey)

    key = key_bin.hex()


    auth_bin = b64decode(dec_params.AuthenticationKey)


    kiv_bin = b64decode(dec_params.KIV)

    kiv_hex = kiv_bin.hex()


    aesiv = b64decode(dec_params.AESIV)

    # ciphertext and MAC tag are extracted from the encrypted data as hex strings

    # get key identity and MAC tag lengths

    mac_len = dec_params.MACLength // 8  # in bytes

    key_id_len = 12  # in bytes


    aes_block_size_hex = len(key)

    aes_block_size_bin = len(key) // 2


    msg_bin = bytearray(MESSAGE, 'UTF-8')

    msg_hex = msg_bin.hex()


    tag_payload = kiv_bin + msg_bin


    hmac_bin = hmac.new(auth_bin, tag_payload, hashlib.sha256).digest()


    hmac_bin = hmac_bin[:16]


    payload = msg_bin+hmac_bin

    blocks = to_blocks(payload, aes_block_size_bin)

    last_block = None

    # encrypt block to block and follow the CS1 format

    enc_blocks_bin = []

    IV = aesiv

    for i in range(0, len(blocks)):

        # convert to binary the current block

        b_bin = blocks[i]

        if (i == len(blocks) - 1):

            # calculate how mouch do we need to pad

            padding_bin_len = aes_block_size_bin - len(b_bin)

            # prepare pt 0 padding

            padding = bytes([0] * (padding_bin_len))

            paded_block_bin = b_bin + padding

            # remove the XORED BYTES from previous encrypted block

            enc_blocks_bin[-1] = enc_blocks_bin[-1][0:len(b_bin)]

            b_bin = paded_block_bin



        try:

            cipher = AES.new(key_bin, AES.MODE_CBC, iv=IV)

            ba = cipher.encrypt(b_bin)

        except (ValueError, KeyError) as ex:

            print("Incorrect decryption")

            print(ex)

        enc_blocks_bin.append(ba)

        IV = ba


    return (kiv_bin+b''.join(enc_blocks_bin)).hex()

Where:

Once encrypted, you need to prepend KIV to encrypted data and send that over to the device for decryption as shown in the code above:

KIV + ENCRYPTED_DATA

Once the device has been bootstrapped and the end-to-end data protection functionality has been enabled, the end-to-end encryption functionality can be called.

After encrypting the data on the server and sending payload over to device, following steps are needed to decrypt it on the device:

AT+USECE2EDATADEC=72

>

*••••••0¤C••HbØÆZÂ(l†a¹•þA1Gü•B

Ô’¿¬^Y»íQU

Response will be

+USECE2EDATADEC: 44," The quick brown fox jumps over the lazy dog."

AT+USECE2EDATAAUTHN=72

>

*••••••0¤C••HbØÆZÂ(l†a¹•þA1Gü•B

Ô’¿¬^Y»íQU

Response will be

+USECE2EDATAAUTHN: 44," The quick brown fox jumps over the lazy dog."


Availability

E2E Data protection service  upstream  with continuous key rotation is available in the following FW version an subsequent releases:

E2E Data protection downstream, Automatic Key rotation feature, and E2E Data Integrity  are available starting from the below firmware releases